You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/06/09 17:55:08 UTC

[01/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Repository: nifi
Updated Branches:
  refs/heads/master f447fc73f -> 4ed7511be


http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java
index a4ac9eb..6fa1865 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-optimistic-locking/src/test/java/org/apache/nifi/web/revision/TestNaiveRevisionManager.java
@@ -18,19 +18,19 @@
 
 package org.apache.nifi.web.revision;
 
-import java.util.HashSet;
-import java.util.Set;
-
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.web.FlowModification;
 import org.apache.nifi.web.Revision;
 
+import java.util.HashSet;
+import java.util.Set;
+
 
 public class TestNaiveRevisionManager {
     private static final String CLIENT_1 = "client-1";
     private static final String COMPONENT_1 = "component-1";
-    private static final NiFiUser USER_1 = new StandardNiFiUser("user-1");
+    private static final NiFiUser USER_1 = new Builder().identity("user-1").build();
 
     private RevisionUpdate<Object> components(final Revision revision) {
         return new StandardRevisionUpdate<Object>(null, new FlowModification(revision, null));

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
index 62d0858..5636c2d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -16,8 +16,10 @@
  */
 package org.apache.nifi.web.security;
 
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.util.IdentityMapping;
 import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
 import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,6 +27,7 @@ import org.springframework.security.authentication.AuthenticationProvider;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Base AuthenticationProvider that provides common functionality to mapping identities.
@@ -34,12 +37,13 @@ public abstract class NiFiAuthenticationProvider implements AuthenticationProvid
     private static final Logger LOGGER = LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
 
     private NiFiProperties properties;
+    private Authorizer authorizer;
     private List<IdentityMapping> mappings;
 
     /**
      * @param properties the NiFiProperties instance
      */
-    public NiFiAuthenticationProvider(final NiFiProperties properties) {
+    public NiFiAuthenticationProvider(final NiFiProperties properties, final Authorizer authorizer) {
         this.properties = properties;
         this.mappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
     }
@@ -52,4 +56,7 @@ public abstract class NiFiAuthenticationProvider implements AuthenticationProvid
         return IdentityMappingUtil.mapIdentity(identity, mappings);
     }
 
+    protected Set<String> getUserGroups(final String identity) {
+        return UserGroupUtil.getUserGroups(authorizer, identity);
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java
index 9b33f77..075720d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/jwt/JwtAuthenticationProvider.java
@@ -16,9 +16,11 @@
  */
 package org.apache.nifi.web.security.jwt;
 
+import io.jsonwebtoken.JwtException;
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationProvider;
@@ -26,8 +28,6 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
-import io.jsonwebtoken.JwtException;
-
 /**
  *
  */
@@ -35,8 +35,8 @@ public class JwtAuthenticationProvider extends NiFiAuthenticationProvider {
 
     private final JwtService jwtService;
 
-    public JwtAuthenticationProvider(JwtService jwtService, NiFiProperties nifiProperties) {
-        super(nifiProperties);
+    public JwtAuthenticationProvider(JwtService jwtService, NiFiProperties nifiProperties, Authorizer authorizer) {
+        super(nifiProperties, authorizer);
         this.jwtService = jwtService;
     }
 
@@ -46,7 +46,8 @@ public class JwtAuthenticationProvider extends NiFiAuthenticationProvider {
 
         try {
             final String jwtPrincipal = jwtService.getAuthenticationFromToken(request.getToken());
-            final NiFiUser user = new StandardNiFiUser(mapIdentity(jwtPrincipal), request.getClientAddress());
+            final String mappedIdentity = mapIdentity(jwtPrincipal);
+            final NiFiUser user = new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build();
             return new NiFiAuthenticationToken(new NiFiUserDetails(user));
         } catch (JwtException e) {
             throw new InvalidAuthenticationException(e.getMessage(), e);

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java
index 1e8825d..f375df2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/otp/OtpAuthenticationProvider.java
@@ -16,9 +16,10 @@
  */
 package org.apache.nifi.web.security.otp;
 
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationProvider;
@@ -33,8 +34,8 @@ public class OtpAuthenticationProvider extends NiFiAuthenticationProvider {
 
     private OtpService otpService;
 
-    public OtpAuthenticationProvider(OtpService otpService, NiFiProperties nifiProperties) {
-        super(nifiProperties);
+    public OtpAuthenticationProvider(OtpService otpService, NiFiProperties nifiProperties, Authorizer authorizer) {
+        super(nifiProperties, authorizer);
         this.otpService = otpService;
     }
 
@@ -49,7 +50,8 @@ public class OtpAuthenticationProvider extends NiFiAuthenticationProvider {
             } else {
                 otpPrincipal = otpService.getAuthenticationFromUiExtensionToken(request.getToken());
             }
-            final NiFiUser user = new StandardNiFiUser(mapIdentity(otpPrincipal), request.getClientAddress());
+            final String mappedIdentity = mapIdentity(otpPrincipal);
+            final NiFiUser user = new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build();
             return new NiFiAuthenticationToken(new NiFiUserDetails(user));
         } catch (OtpAuthenticationException e) {
             throw new InvalidAuthenticationException(e.getMessage(), e);

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
index b5835d0..510e136 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
@@ -16,11 +16,6 @@
  */
 package org.apache.nifi.web.security.x509;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authentication.AuthenticationResponse;
 import org.apache.nifi.authorization.AuthorizationRequest;
@@ -33,6 +28,7 @@ import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
 import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.InvalidAuthenticationException;
 import org.apache.nifi.web.security.NiFiAuthenticationProvider;
@@ -42,6 +38,13 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
 /**
  *
  */
@@ -51,7 +54,7 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
     private Authorizer authorizer;
 
     public X509AuthenticationProvider(final X509IdentityProvider certificateIdentityProvider, final Authorizer authorizer, final NiFiProperties nifiProperties) {
-        super(nifiProperties);
+        super(nifiProperties, authorizer);
         this.certificateIdentityProvider = certificateIdentityProvider;
         this.authorizer = authorizer;
     }
@@ -70,7 +73,7 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
 
         if (StringUtils.isBlank(request.getProxiedEntitiesChain())) {
             final String mappedIdentity = mapIdentity(authenticationResponse.getIdentity());
-            return new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(mappedIdentity, request.getClientAddress())));
+            return new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(mappedIdentity).groups(getUserGroups(mappedIdentity)).clientAddress(request.getClientAddress()).build()));
         } else {
             // build the entire proxy chain if applicable - <end-user><proxy1><proxy2>
             final List<String> proxyChain = new ArrayList<>(ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(request.getProxiedEntitiesChain()));
@@ -89,10 +92,13 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
                     identity = mapIdentity(identity);
                 }
 
+                final Set<String> groups = getUserGroups(identity);
+
                 if (chainIter.hasPrevious()) {
                     // authorize this proxy in order to authenticate this user
                     final AuthorizationRequest proxyAuthorizationRequest = new AuthorizationRequest.Builder()
                             .identity(identity)
+                            .groups(groups)
                             .anonymous(isAnonymous)
                             .accessAttempt(true)
                             .action(RequestAction.WRITE)
@@ -108,7 +114,7 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
 
                 // Only set the client address for user making the request because we don't know the client address of the proxies
                 String clientAddress = (proxy == null) ? request.getClientAddress() : null;
-                proxy = createUser(identity, proxy, clientAddress, isAnonymous);
+                proxy = createUser(identity, groups, proxy, clientAddress, isAnonymous);
             }
 
             return new NiFiAuthenticationToken(new NiFiUserDetails(proxy));
@@ -124,11 +130,11 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
      * @param isAnonymous   if true, an anonymous user will be returned (identity will be ignored)
      * @return the populated user
      */
-    protected static NiFiUser createUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
+    protected static NiFiUser createUser(String identity, Set<String> groups, NiFiUser chain, String clientAddress, boolean isAnonymous) {
         if (isAnonymous) {
             return StandardNiFiUser.populateAnonymousUser(chain, clientAddress);
         } else {
-            return new StandardNiFiUser(identity, chain, clientAddress);
+            return new Builder().identity(identity).groups(groups).chain(chain).clientAddress(clientAddress).build();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
index ff1aff1..369b33c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/resources/nifi-web-security-context.xml
@@ -43,7 +43,7 @@
     <bean id="x509AuthenticationProvider" class="org.apache.nifi.web.security.x509.X509AuthenticationProvider">
         <constructor-arg ref="certificateIdentityProvider" index="0"/>
         <constructor-arg ref="authorizer" index="1"/>
-        <constructor-arg ref="nifiProperties"/>
+        <constructor-arg ref="nifiProperties" index="2"/>
     </bean>
 
     <!-- jwt service -->
@@ -53,8 +53,9 @@
 
     <!-- jwt authentication provider -->
     <bean id="jwtAuthenticationProvider" class="org.apache.nifi.web.security.jwt.JwtAuthenticationProvider">
-        <constructor-arg ref="jwtService"/>
-        <constructor-arg ref="nifiProperties"/>
+        <constructor-arg ref="jwtService" index="0"/>
+        <constructor-arg ref="nifiProperties" index="1"/>
+        <constructor-arg ref="authorizer" index="2"/>
     </bean>
 
     <!-- otp service -->
@@ -62,8 +63,9 @@
 
     <!-- otp authentication provider -->
     <bean id="otpAuthenticationProvider" class="org.apache.nifi.web.security.otp.OtpAuthenticationProvider">
-        <constructor-arg ref="otpService"/>
-        <constructor-arg ref="nifiProperties"/>
+        <constructor-arg ref="otpService" index="0"/>
+        <constructor-arg ref="nifiProperties" index="1"/>
+        <constructor-arg ref="authorizer" index="2"/>
     </bean>
 
     <!-- Kerberos service -->

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
index eb89c22..479034a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
@@ -16,10 +16,10 @@
  */
 package org.apache.nifi.web.security;
 
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.util.IdentityMapping;
 import org.apache.nifi.util.NiFiProperties;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.springframework.security.core.Authentication;
@@ -30,6 +30,7 @@ import java.util.Properties;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 public class NiFiAuthenticationProviderTest {
@@ -169,7 +170,7 @@ public class NiFiAuthenticationProviderTest {
     }
 
     private NiFiProperties getNiFiProperties(final Properties properties) {
-        final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
+        final NiFiProperties nifiProperties = mock(NiFiProperties.class);
         when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames());
 
         when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
@@ -186,7 +187,7 @@ public class NiFiAuthenticationProviderTest {
          * @param properties the NiFiProperties instance
          */
         public TestableNiFiAuthenticationProvider(NiFiProperties properties) {
-            super(properties);
+            super(properties, mock(Authorizer.class));
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java
index 1b5f447..1b649e8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/otp/OtpAuthenticationProviderTest.java
@@ -16,12 +16,12 @@
  */
 package org.apache.nifi.web.security.otp;
 
+import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
@@ -75,7 +75,7 @@ public class OtpAuthenticationProviderTest {
             }
         }).when(otpService).getAuthenticationFromUiExtensionToken(anyString());
 
-        otpAuthenticationProvider = new OtpAuthenticationProvider(otpService, Mockito.mock(NiFiProperties.class));
+        otpAuthenticationProvider = new OtpAuthenticationProvider(otpService, mock(NiFiProperties.class), mock(Authorizer.class));
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
index 43aea86..70df649 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java
@@ -16,20 +16,6 @@
  */
 package org.apache.nifi.web.security.x509;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import java.security.Principal;
-import java.security.cert.X509Certificate;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.stream.Collectors;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authentication.AuthenticationResponse;
 import org.apache.nifi.authorization.AuthorizationRequest;
@@ -45,6 +31,21 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.junit.Before;
 import org.junit.Test;
 
+import java.security.Principal;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
 public class X509AuthenticationProviderTest {
 
     private static final String INVALID_CERTIFICATE = "invalid-certificate";
@@ -190,7 +191,7 @@ public class X509AuthenticationProviderTest {
         String identity = "someone";
 
         // Act
-        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, true);
+        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, null, true);
 
         // Assert
         assert user != null;
@@ -205,7 +206,7 @@ public class X509AuthenticationProviderTest {
         String identity = "someone";
 
         // Act
-        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, false);
+        NiFiUser user = X509AuthenticationProvider.createUser(identity, null, null, null, false);
 
         // Assert
         assert user != null;

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
index c7064f8..13e2146 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/canvas-header.jsp
@@ -146,15 +146,15 @@
                             <i class="fa fa-history"></i>Flow Configuration History
                         </a>
                     </md-menu-item>
-                    <md-menu-divider ng-if="appCtrl.nf.CanvasUtils.isConfigurableAuthorizer()"></md-menu-divider>
-                    <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isConfigurableAuthorizer()">
+                    <md-menu-divider ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()"></md-menu-divider>
+                    <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">
                         <a id="users-link" layout="row"
                            ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.users.shell.launch();"
                            ng-class="{disabled: !(appCtrl.nf.Common.canAccessTenants())}">
                             <i class="fa fa-users"></i>Users
                         </a>
                     </md-menu-item>
-                    <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isConfigurableAuthorizer()">
+                    <md-menu-item layout-align="space-around center" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">
                         <a id="policies-link" layout="row"
                            ng-click="appCtrl.serviceProvider.headerCtrl.globalMenuCtrl.policies.shell.launch();"
                            ng-class="{disabled: !(appCtrl.nf.Common.canAccessTenants() && appCtrl.nf.Common.canModifyPolicies())}">

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
index 6d97b0e..0732b3d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
@@ -95,8 +95,8 @@
                                 ng-disabled="!(appCtrl.serviceProvider.graphControlsCtrl.canConfigureOrOpenDetails())">
                             <div class="graph-control-action-icon fa fa-gear"></div></button>
                     </div>
-                    <div class="button-spacer-small" ng-if="appCtrl.nf.CanvasUtils.isConfigurableAuthorizer()">&nbsp;</div>
-                    <div id="operate-policy" class="action-button" title="Access Policies" ng-if="appCtrl.nf.CanvasUtils.isConfigurableAuthorizer()">
+                    <div class="button-spacer-small" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">&nbsp;</div>
+                    <div id="operate-policy" class="action-button" title="Access Policies" ng-if="appCtrl.nf.CanvasUtils.isManagedAuthorizer()">
                         <button ng-click="appCtrl.nf.Actions['managePolicies'](appCtrl.nf.CanvasUtils.getSelection());"
                                 ng-disabled="!(appCtrl.nf.CanvasUtils.canManagePolicies())">
                             <div class="graph-control-action-icon fa fa-key"></div></button>

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css
index 0e92a9c..667291d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/policy-management.css
@@ -35,12 +35,14 @@
 
 #delete-policy-button {
     margin-top: -4px;
+    display: none;
 }
 
 #new-policy-user-button {
     margin-top: -4px;
     margin-right: 5px;
     padding-left: 5px;
+    display: none;
 }
 
 button.policy-button {

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css
index f380c89..dc2a404 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/css/users.css
@@ -67,6 +67,7 @@
 #new-user-button {
     float: right;
     padding-left: 5px;
+    display: none;
 }
 
 /* users table */

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
index cef0b49..2fa8a2b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-bootstrap.js
@@ -325,7 +325,9 @@
                     var autoRefreshIntervalSeconds = parseInt(configDetails.autoRefreshIntervalSeconds, 10);
 
                     // record whether we can configure the authorizer
+                    nfCanvas.setManagedAuthorizer(configDetails.supportsManagedAuthorizer);
                     nfCanvas.setConfigurableAuthorizer(configDetails.supportsConfigurableAuthorizer);
+                    nfCanvas.setConfigurableUsersAndGroups(configDetails.supportsConfigurableUsersAndGroups);
 
                     // init nfStorage
                     nfStorage.init();

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index efa21c4..54f1d14 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -1726,6 +1726,13 @@
         },
 
         /**
+         * Returns whether the authorizer is managed.
+         */
+        isManagedAuthorizer: function () {
+            return nfCanvas.isManagedAuthorizer();
+        },
+
+        /**
          * Returns whether the authorizer is configurable.
          */
         isConfigurableAuthorizer: function () {
@@ -1733,6 +1740,13 @@
         },
 
         /**
+         * Returns whether the authorizer support configurable users and groups.
+         */
+        isConfigurableUsersAndGroups: function () {
+            return nfCanvas.isConfigurableUsersAndGroups();
+        },
+
+        /**
          * Set the group id.
          *
          * @argument {string} gi       The group id

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
index ecb1269..0180441 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas.js
@@ -84,7 +84,9 @@
     var groupName = null;
     var permissions = null;
     var parentGroupId = null;
+    var managedAuthorizer = false;
     var configurableAuthorizer = false;
+    var configurableUsersAndGroups = false;
     var svg = null;
     var canvas = null;
 
@@ -878,6 +880,22 @@
         },
 
         /**
+         * Set whether the authorizer is managed.
+         *
+         * @param bool The boolean value representing whether the authorizer is managed
+         */
+        setManagedAuthorizer: function (bool) {
+            managedAuthorizer = bool;
+        },
+
+        /**
+         * Returns whether the authorizer is managed.
+         */
+        isManagedAuthorizer: function () {
+            return managedAuthorizer;
+        },
+
+        /**
          * Set whether the authorizer is configurable.
          *
          * @param bool The boolean value representing whether the authorizer is configurable.
@@ -894,6 +912,22 @@
         },
 
         /**
+         * Set whether the users and groups is configurable.
+         *
+         * @param bool The boolean value representing whether the users and groups is configurable.
+         */
+        setConfigurableUsersAndGroups: function(bool){
+            configurableUsersAndGroups = bool;
+        },
+
+        /**
+         * Returns whether the users and groups is configurable.
+         */
+        isConfigurableUsersAndGroups: function () {
+            return configurableUsersAndGroups;
+        },
+
+        /**
          * Whether the current user can read from this group.
          *
          * @returns {boolean}   can write

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
index 1c16626..c5e05f9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
@@ -133,7 +133,7 @@
      * @param {selection} selection         The selection of currently selected components
      */
     var canManagePolicies = function (selection) {
-        return nfCanvasUtils.isConfigurableAuthorizer() && nfCanvasUtils.canManagePolicies(selection);
+        return nfCanvasUtils.isManagedAuthorizer() && nfCanvasUtils.canManagePolicies(selection);
     };
 
     /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
index ef98d38..a4fb774 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
@@ -882,7 +882,7 @@
             }
 
             // allow policy configuration conditionally
-            if (nfCanvasUtils.isConfigurableAuthorizer() && nfCommon.canAccessTenants()) {
+            if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) {
                 markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
             }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
index c4c3cd8..c4ae1ba 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
@@ -567,8 +567,11 @@
                 sortable: true,
                 resizable: true,
                 formatter: identityFormatter
-            },
-            {
+            }
+        ];
+
+        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+            usersColumns.push({
                 id: 'actions',
                 name: '&nbsp;',
                 sortable: false,
@@ -576,8 +579,8 @@
                 formatter: actionFormatter,
                 width: 100,
                 maxWidth: 100
-            }
-        ];
+            });
+        }
 
         var usersOptions = {
             forceFitColumns: true,
@@ -874,20 +877,24 @@
 
         // see if the policy is for this resource
         if (resourceAndAction.resource === policy.resource) {
-            // allow remove when policy is not inherited
-            $('#delete-policy-button').prop('disabled', policyEntity.permissions.canWrite === false);
+            if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                // allow remove when policy is not inherited
+                $('#delete-policy-button').prop('disabled', policyEntity.permissions.canWrite === false);
 
-            // allow modification if allowed
-            $('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false);
+                // allow modification if allowed
+                $('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false);
+            }
         } else {
             $('#policy-message').append(getResourceMessage(policy.resource));
 
-            // policy is inherited, we do not know if the user has permissions to modify the desired policy... show button and let server decide
-            $('#override-policy-message').show();
+            if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                // policy is inherited, we do not know if the user has permissions to modify the desired policy... show button and let server decide
+                $('#override-policy-message').show();
 
-            // do not support policy deletion/modification
-            $('#delete-policy-button').prop('disabled', true);
-            $('#new-policy-user-button').prop('disabled', true);
+                // do not support policy deletion/modification
+                $('#delete-policy-button').prop('disabled', true);
+                $('#new-policy-user-button').prop('disabled', true);
+            }
         }
 
         // populate the table
@@ -928,8 +935,10 @@
                             // show an appropriate message
                             $('#policy-message').text('No component specific administrators.');
 
-                            // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
-                            $('#add-local-admin-message').show();
+                            if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                                // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
+                                $('#add-local-admin-message').show();
+                            }
                         }
                     } else {
                         // reset the policy
@@ -938,8 +947,10 @@
                         // show an appropriate message
                         $('#policy-message').text('No component specific administrators.');
 
-                        // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
-                        $('#add-local-admin-message').show();
+                        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                            // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
+                            $('#add-local-admin-message').show();
+                        }
                     }
 
                     deferred.resolve();
@@ -951,8 +962,10 @@
                         // show an appropriate message
                         $('#policy-message').text('No component specific administrators.');
 
-                        // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
-                        $('#add-local-admin-message').show();
+                        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                            // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
+                            $('#add-local-admin-message').show();
+                        }
 
                         deferred.resolve();
                     } else if (xhr.status === 403) {
@@ -997,8 +1010,10 @@
                         // since we cannot read, the policy may be inherited or not... we cannot tell
                         $('#policy-message').text('Not authorized to view the policy.');
 
-                        // allow option to override because we don't know if it's supported or not
-                        $('#override-policy-message').show();
+                        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                            // allow option to override because we don't know if it's supported or not
+                            $('#override-policy-message').show();
+                        }
                     }
 
                     deferred.resolve();
@@ -1010,8 +1025,10 @@
                         // show an appropriate message
                         $('#policy-message').text('No policy for the specified resource.');
 
-                        // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
-                        $('#new-policy-message').show();
+                        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                            // we don't know if the user has permissions to the desired policy... show create button and allow the server to decide
+                            $('#new-policy-message').show();
+                        }
 
                         deferred.resolve();
                     } else if (xhr.status === 403) {
@@ -1183,9 +1200,11 @@
      */
     var resetPolicyMessage = function () {
         $('#policy-message').text('').empty();
-        $('#new-policy-message').hide();
-        $('#override-policy-message').hide();
-        $('#add-local-admin-message').hide();
+        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+            $('#new-policy-message').hide();
+            $('#override-policy-message').hide();
+            $('#add-local-admin-message').hide();
+        }
     };
 
     /**
@@ -1194,9 +1213,11 @@
     var resetPolicy = function () {
         resetPolicyMessage();
 
-        // reset button state
-        $('#delete-policy-button').prop('disabled', true);
-        $('#new-policy-user-button').prop('disabled', true);
+        if (nfCanvasUtils.isConfigurableAuthorizer()) {
+            // reset button state
+            $('#delete-policy-button').prop('disabled', true);
+            $('#new-policy-user-button').prop('disabled', true);
+        }
 
         // reset the current policy
         $('#policy-table').removeData('policy');
@@ -1229,6 +1250,11 @@
             initAddTenantToPolicyDialog();
             initPolicyTable();
 
+            if (nfCanvasUtils.isConfigurableAuthorizer()) {
+                $('#delete-policy-button').show();
+                $('#new-policy-user-button').show();
+            }
+
             $('#policy-refresh-button').on('click', function () {
                 loadPolicy();
             });

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
index 075f712..3f346f5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -871,7 +871,7 @@
             }
 
             // allow policy configuration conditionally
-            if (nfCanvasUtils.isConfigurableAuthorizer() && nfCommon.canAccessTenants()) {
+            if (nfCanvasUtils.isManagedAuthorizer() && nfCommon.canAccessTenants()) {
                 markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
             }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
index c942bdb..89c7cf2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
@@ -293,7 +293,7 @@
 
                 // allow policy configuration conditionally if embedded in
                 if (top !== window && nfCommon.canAccessTenants()) {
-                    if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && parent.nf.CanvasUtils.isConfigurableAuthorizer()) {
+                    if (nfCommon.isDefinedAndNotNull(parent.nf) && nfCommon.isDefinedAndNotNull(parent.nf.CanvasUtils) && parent.nf.CanvasUtils.isManagedAuthorizer()) {
                         markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
                     }
                 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
index 7623234..14c8fe7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
@@ -750,7 +750,7 @@
     /**
      * Initializes the processor list.
      */
-    var initUsersTable = function () {
+    var initUsersTable = function (configurableUsersAndGroups) {
         // define the function for filtering the list
         $('#users-filter').keyup(function () {
             applyFilter();
@@ -797,7 +797,7 @@
             var markup = '';
 
             // ensure user can modify the user
-            if (nfCommon.canModifyTenants()) {
+            if (configurableUsersAndGroups && nfCommon.canModifyTenants()) {
                 markup += '<div title="Edit" class="pointer edit-user fa fa-pencil" style="margin-right: 3px;"></div>';
                 markup += '<div title="Remove" class="pointer delete-user fa fa-trash"></div>';
             }
@@ -1215,28 +1215,32 @@
     };
 
     var nfUsersTable = {
-        init: function () {
+        init: function (configurableUsersAndGroups) {
             initUserDialog();
             initUserPoliciesDialog();
             initUserPoliciesTable();
             initUserDeleteDialog();
-            initUsersTable();
+            initUsersTable(configurableUsersAndGroups);
 
-            if (nfCommon.canModifyTenants()) {
-                $('#new-user-button').on('click', function () {
-                    buildUsersList();
-                    buildGroupsList();
+            if (configurableUsersAndGroups) {
+                $('#new-user-button').show();
 
-                    // show the dialog
-                    $('#user-dialog').modal('show');
+                if (nfCommon.canModifyTenants()) {
+                    $('#new-user-button').on('click', function () {
+                        buildUsersList();
+                        buildGroupsList();
 
-                    // set the focus automatically, only when adding a new user
-                    $('#user-identity-edit-dialog').focus();
-                });
+                        // show the dialog
+                        $('#user-dialog').modal('show');
 
-                $('#new-user-button').prop('disabled', false);
-            } else {
-                $('#new-user-button').prop('disabled', true);
+                        // set the focus automatically, only when adding a new user
+                        $('#user-identity-edit-dialog').focus();
+                    });
+
+                    $('#new-user-button').prop('disabled', false);
+                } else {
+                    $('#new-user-button').prop('disabled', true);
+                }
             }
         },
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js
index e39e160..6b53ce8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users.js
@@ -66,7 +66,8 @@
         urls: {
             banners: '../nifi-api/flow/banners',
             controllerAbout: '../nifi-api/flow/about',
-            currentUser: '../nifi-api/flow/current-user'
+            currentUser: '../nifi-api/flow/current-user',
+            flowConfig: '../nifi-api/flow/config'
         }
     };
 
@@ -83,6 +84,14 @@
         }).fail(nfErrorHandler.handleAjaxError);
     };
 
+    var getFlowConfig = function () {
+        return $.ajax({
+            type: 'GET',
+            url: config.urls.flowConfig,
+            dataType: 'json'
+        }).fail(nfErrorHandler.handleAjaxError);
+    }
+
     var initializeUsersPage = function () {
         // define mouse over event for the refresh button
         nfCommon.addHoverEffect('#user-refresh-button', 'button-refresh', 'button-refresh-hover').click(function () {
@@ -149,9 +158,12 @@
             nfClient.init();
 
             // load the users authorities
-            ensureAccess().done(function () {
+            $.when(getFlowConfig(), ensureAccess()).done(function (configResult) {
+                var configResponse = configResult[0];
+                var configDetails = configResponse.flowConfiguration;
+
                 // create the counters table
-                nfUsersTable.init();
+                nfUsersTable.init(configDetails.supportsConfigurableUsersAndGroups);
 
                 // load the users table
                 nfUsersTable.loadUsersTable().done(function () {

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java
index 5eeb7de..f031710 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/TestPersistentProvenanceRepository.java
@@ -16,41 +16,6 @@
  */
 package org.apache.nifi.provenance;
 
-import static org.apache.nifi.provenance.TestUtil.createFlowFile;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-
-import java.io.DataOutputStream;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
-import java.util.zip.GZIPOutputStream;
-
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.core.SimpleAnalyzer;
 import org.apache.lucene.document.Document;
@@ -90,7 +55,6 @@ import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.file.FileUtils;
 import org.junit.After;
 import org.junit.Assert;
-import static org.junit.Assume.assumeFalse;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -102,6 +66,42 @@ import org.junit.rules.TestName;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.zip.GZIPOutputStream;
+
+import static org.apache.nifi.provenance.TestUtil.createFlowFile;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.mockito.Mockito.mock;
+
 public class TestPersistentProvenanceRepository {
 
     @Rule
@@ -2273,6 +2273,11 @@ public class TestPersistentProvenanceRepository {
             }
 
             @Override
+            public Set<String> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+
+            @Override
             public NiFiUser getChain() {
                 return null;
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java
index 3079b87..44c5402 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-persistent-provenance-repository/src/test/java/org/apache/nifi/provenance/index/lucene/TestLuceneEventIndex.java
@@ -16,23 +16,6 @@
  */
 package org.apache.nifi.provenance.index.lucene;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.stream.Collectors;
-
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.events.EventReporter;
@@ -56,7 +39,6 @@ import org.apache.nifi.provenance.serialization.StorageSummary;
 import org.apache.nifi.provenance.store.ArrayListEventStore;
 import org.apache.nifi.provenance.store.EventStore;
 import org.apache.nifi.provenance.store.StorageResult;
-import static org.junit.Assume.assumeFalse;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
@@ -65,6 +47,25 @@ import org.mockito.Mockito;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
 public class TestLuceneEventIndex {
 
     private final AtomicLong idGenerator = new AtomicLong(0L);
@@ -344,6 +345,11 @@ public class TestLuceneEventIndex {
             }
 
             @Override
+            public Set<String> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+
+            @Override
             public NiFiUser getChain() {
                 return null;
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
index 942fea4..5ccf6ea 100644
--- a/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
+++ b/nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java
@@ -26,9 +26,11 @@ import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.UUID;
 
 import static org.junit.Assert.assertEquals;
@@ -186,6 +188,11 @@ public class TestVolatileProvenanceRepository {
             }
 
             @Override
+            public Set<String> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+
+            @Override
             public NiFiUser getChain() {
                 return null;
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d3db7c6..bdab77a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1718,7 +1718,7 @@
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>jaxb2-maven-plugin</artifactId>
-                    <version>1.6</version>
+                    <version>2.3.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>


[10/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java
new file mode 100644
index 0000000..a7b7a0b
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProvider.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.Set;
+
+/**
+ * Provides access to Users and Groups.
+ *
+ * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
+ * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
+ *
+ * Additionally, extensions need to be thread safe.
+ */
+public interface UserGroupProvider {
+
+    /**
+     * Retrieves all users. Must be non null
+     *
+     * @return a list of users
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    Set<User> getUsers() throws AuthorizationAccessException;
+
+    /**
+     * Retrieves the user with the given identifier.
+     *
+     * @param identifier the id of the user to retrieve
+     * @return the user with the given id, or null if no matching user was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    User getUser(String identifier) throws AuthorizationAccessException;
+
+    /**
+     * Retrieves the user with the given identity.
+     *
+     * @param identity the identity of the user to retrieve
+     * @return the user with the given identity, or null if no matching user was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    User getUserByIdentity(String identity) throws AuthorizationAccessException;
+
+    /**
+     * Retrieves all groups. Must be non null
+     *
+     * @return a list of groups
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    Set<Group> getGroups() throws AuthorizationAccessException;
+
+    /**
+     * Retrieves a Group by id.
+     *
+     * @param identifier the identifier of the Group to retrieve
+     * @return the Group with the given identifier, or null if no matching group was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    Group getGroup(String identifier) throws AuthorizationAccessException;
+
+    /**
+     * Gets a user and their groups. Must be non null. If the user is not known the UserAndGroups.getUser() and
+     * UserAndGroups.getGroups() should return null
+     *
+     * @return the UserAndGroups for the specified identity
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException;
+
+    /**
+     * Called immediately after instance creation for implementers to perform additional setup
+     *
+     * @param initializationContext in which to initialize
+     */
+    void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException;
+
+    /**
+     * Called to configure the Authorizer.
+     *
+     * @param configurationContext at the time of configuration
+     * @throws AuthorizerCreationException for any issues configuring the provider
+     */
+    void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
+
+    /**
+     * Called immediately before instance destruction for implementers to release resources.
+     *
+     * @throws AuthorizerDestructionException If pre-destruction fails.
+     */
+    void preDestruction() throws AuthorizerDestructionException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java
new file mode 100644
index 0000000..d83b4b3
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderInitializationContext.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+/**
+ * Initialization content for UserGroupProviders.
+ */
+public interface UserGroupProviderInitializationContext {
+
+    /**
+     * The identifier of the UserGroupProvider.
+     *
+     * @return  The identifier
+     */
+    String getIdentifier();
+
+    /**
+     * The lookup for accessing other configured UserGroupProviders.
+     *
+     * @return  The UserGroupProvider lookup
+     */
+    UserGroupProviderLookup getUserGroupProviderLookup();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java
new file mode 100644
index 0000000..b879374
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserGroupProviderLookup.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+/**
+ *
+ */
+public interface UserGroupProviderLookup {
+
+    /**
+     * Looks up the UserGroupProvider with the specified identifier
+     *
+     * @param identifier        The identifier of the UserGroupProvider
+     * @return                  The UserGroupProvider
+     */
+    UserGroupProvider getUserGroupProvider(String identifier);
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java
new file mode 100644
index 0000000..f75ead8
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/exception/UninheritableAuthorizationsException.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization.exception;
+
+/**
+ * Represents the case when the proposed authorizations are not inheritable.
+ */
+public class UninheritableAuthorizationsException extends RuntimeException {
+
+    public UninheritableAuthorizationsException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
index 3219ac2..cd27e10 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/resource/Authorizable.java
@@ -84,6 +84,7 @@ public interface Authorizable {
         final Resource resource = getResource();
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(false)
                 .action(action)
@@ -188,6 +189,7 @@ public interface Authorizable {
         final Resource resource = getResource();
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(action)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
index c450bc4..6b8012b 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/user/NiFiUser.java
@@ -17,6 +17,8 @@
 
 package org.apache.nifi.authorization.user;
 
+import java.util.Set;
+
 /**
  * A representation of a NiFi user that has logged into the application
  */
@@ -28,6 +30,11 @@ public interface NiFiUser {
     String getIdentity();
 
     /**
+     * @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise.
+     */
+    Set<String> getGroups();
+
+    /**
      * @return the next user in the proxied entities chain, or <code>null</code> if no more users exist in the chain.
      */
     NiFiUser getChain();

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
index cda2ac5..08c8bb3 100644
--- a/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
+++ b/nifi-framework-api/src/test/java/org/apache/nifi/authorization/TestAbstractPolicyBasedAuthorizer.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.nifi.authorization.exception.AuthorizerCreationException;
 import org.junit.Assert;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -251,10 +250,7 @@ public class TestAbstractPolicyBasedAuthorizer {
         when(authorizer2.getAccessPolicies()).thenReturn(policies2);
 
         // compare the fingerprints
-
         assertEquals(authorizer1.getFingerprint(), authorizer2.getFingerprint());
-
-        //System.out.println(authorizer1.getFingerprint());
     }
 
     @Test
@@ -332,211 +328,4 @@ public class TestAbstractPolicyBasedAuthorizer {
         Assert.assertTrue(fingerprint.length() > 0);
     }
 
-    @Test(expected = AuthorizerCreationException.class)
-    public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() {
-        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
-
-        AccessPolicy policy1 = new AccessPolicy.Builder()
-                .identifier("policy-id-1")
-                .resource("resource1")
-                .action(RequestAction.READ)
-                .addUser(user1.getIdentifier())
-                .build();
-
-        AccessPolicy policy2 = new AccessPolicy.Builder()
-                .identifier("policy-id-2")
-                .resource("resource1")
-                .action(RequestAction.READ)
-                .addUser(user1.getIdentifier())
-                .build();
-
-        Set<AccessPolicy> policies = new LinkedHashSet<>();
-        policies.add(policy1);
-        policies.add(policy2);
-
-        Set<User> users = new LinkedHashSet<>();
-        users.add(user1);
-
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies);
-        authorizer.onConfigured(context);
-    }
-
-    @Test(expected = AuthorizerCreationException.class)
-    public void testOnConfiguredWhenUsersWithSameIdentity() {
-        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
-        User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
-
-        Set<User> users = new LinkedHashSet<>();
-        users.add(user1);
-        users.add(user2);
-
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>());
-        authorizer.onConfigured(context);
-    }
-
-    @Test(expected = AuthorizerCreationException.class)
-    public void testOnConfiguredWhenGroupsWithSameName() {
-        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
-        Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
-
-        Set<Group> groups = new LinkedHashSet<>();
-        groups.add(group1);
-        groups.add(group2);
-
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>());
-        authorizer.onConfigured(context);
-    }
-
-    @Test
-    public void testAddPoliciesWithSameResourceAndAction() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
-        authorizer.addUser(user1);
-
-        AccessPolicy policy1 = new AccessPolicy.Builder()
-                .identifier("policy-id-1")
-                .resource("resource1")
-                .action(RequestAction.READ)
-                .addUser(user1.getIdentifier())
-                .build();
-        authorizer.addAccessPolicy(policy1);
-
-        AccessPolicy policy2 = new AccessPolicy.Builder()
-                .identifier("policy-id-2")
-                .resource("resource1")
-                .action(RequestAction.READ)
-                .addUser(user1.getIdentifier())
-                .build();
-
-        try {
-            authorizer.addAccessPolicy(policy2);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testAddUsersWithSameIdentity() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
-        authorizer.addUser(user1);
-
-        User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
-
-        try {
-            authorizer.addUser(user2);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testAddGroupsWithSameName() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
-        authorizer.addGroup(group1);
-
-        Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
-
-        try {
-            authorizer.addGroup(group2);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testAddUsersWithSameIdentityAsGroupName() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
-        authorizer.addGroup(group1);
-
-        User user = new User.Builder().identifier("user-id-2").identity("abc").build();
-
-        try {
-            authorizer.addUser(user);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testAddGroupWithSameNameAsUserIdentity() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        User user = new User.Builder().identifier("user-id-2").identity("abc").build();
-        authorizer.addUser(user);
-
-        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
-        try {
-            authorizer.addGroup(group1);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testUpdateUserWithSameIdentity() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        User user1 = new User.Builder().identifier("user-id-1").identity("abc").build();
-        authorizer.addUser(user1);
-
-        User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build();
-        authorizer.addUser(user2);
-
-        try {
-            User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build();
-            authorizer.updateUser(user1Updated);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
-    @Test
-    public void testUpdateGroupWithSameName() {
-        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
-        AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
-        authorizer.onConfigured(context);
-
-        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
-        authorizer.addGroup(group1);
-
-        Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build();
-        authorizer.addGroup(group2);
-
-        try {
-            Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build();
-            authorizer.updateGroup(group1Updated);
-            Assert.fail("Should have thrown exception");
-        } catch (IllegalStateException e) {
-
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
index 1fe42d9..b0a64d9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
@@ -65,15 +65,27 @@
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-framework-core</artifactId>
+            <artifactId>nifi-nar-utils</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-nar-utils</artifactId>
+            <artifactId>nifi-properties</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.nifi</groupId>
-            <artifactId>nifi-properties</artifactId>
+            <artifactId>nifi-framework-nar-utils</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-authorization</artifactId>
         </dependency>
     </dependencies>
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java
new file mode 100644
index 0000000..c71c982
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderFactory.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.nar.NarCloseable;
+
+import java.util.Set;
+
+public final class AccessPolicyProviderFactory {
+
+    public static AccessPolicyProvider withNarLoader(final AccessPolicyProvider baseAccessPolicyProvider) {
+        if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+            final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
+            return new ConfigurableAccessPolicyProvider() {
+                @Override
+                public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
+                    }
+                }
+
+                @Override
+                public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
+                    }
+                }
+
+                @Override
+                public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
+                    }
+                }
+
+                @Override
+                public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicies();
+                    }
+                }
+
+                @Override
+                public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
+                    }
+                }
+
+                @Override
+                public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
+                    }
+                }
+
+                @Override
+                public UserGroupProvider getUserGroupProvider() {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.getUserGroupProvider();
+                    }
+                }
+
+                @Override
+                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
+                    }
+                }
+
+                @Override
+                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
+                    }
+                }
+
+                @Override
+                public String getFingerprint() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableAccessPolicyProvider.getFingerprint();
+                    }
+                }
+
+                @Override
+                public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableAccessPolicyProvider.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableAccessPolicyProvider.preDestruction();
+                    }
+                }
+            };
+        } else {
+            return new AccessPolicyProvider() {
+                @Override
+                public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseAccessPolicyProvider.getAccessPolicies();
+                    }
+                }
+
+                @Override
+                public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseAccessPolicyProvider.getAccessPolicy(identifier);
+                    }
+                }
+
+                @Override
+                public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
+                    }
+                }
+
+                @Override
+                public UserGroupProvider getUserGroupProvider() {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseAccessPolicyProvider.getUserGroupProvider();
+                    }
+                }
+
+                @Override
+                public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAccessPolicyProvider.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAccessPolicyProvider.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAccessPolicyProvider.preDestruction();
+                    }
+                }
+            };
+        }
+    }
+
+    private AccessPolicyProviderFactory() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java
new file mode 100644
index 0000000..59de908
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerCapabilityDetection.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+public final class AuthorizerCapabilityDetection {
+
+    public static boolean isManagedAuthorizer(final Authorizer authorizer) {
+        return authorizer instanceof ManagedAuthorizer;
+    }
+
+    public static boolean isConfigurableAccessPolicyProvider(final Authorizer authorizer) {
+        if (!isManagedAuthorizer(authorizer)) {
+            return false;
+        }
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+        return managedAuthorizer.getAccessPolicyProvider() instanceof ConfigurableAccessPolicyProvider;
+    }
+
+    public static boolean isConfigurableUserGroupProvider(final Authorizer authorizer) {
+        if (!isManagedAuthorizer(authorizer)) {
+            return false;
+        }
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+        final AccessPolicyProvider accessPolicyProvider = managedAuthorizer.getAccessPolicyProvider();
+        return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider;
+    }
+
+    private AuthorizerCapabilityDetection() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java
new file mode 100644
index 0000000..660b47b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactory.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.nar.NarCloseable;
+
+import java.util.Set;
+
+public final class AuthorizerFactory {
+
+    /**
+     * Checks if another policy exists with the same resource and action as the given policy.
+     *
+     * @param checkAccessPolicy an access policy being checked
+     * @return true if another access policy exists with the same resource and action, false otherwise
+     */
+    private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) {
+        for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
+            if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
+                    && accessPolicy.getResource().equals(checkAccessPolicy.getResource())
+                    && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks if another user exists with the same identity.
+     *
+     * @param identifier identity of the user
+     * @param identity identity of the user
+     * @return true if another user exists with the same identity, false otherwise
+     */
+    private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) {
+        for (User user : userGroupProvider.getUsers()) {
+            if (!user.getIdentifier().equals(identifier)
+                    && user.getIdentity().equals(identity)) {
+                return true;
+            }
+        }
+
+        for (Group group : userGroupProvider.getGroups()) {
+            if (!group.getIdentifier().equals(identifier)
+                    && group.getName().equals(identity)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) {
+        if (baseAuthorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
+            return new ManagedAuthorizer() {
+                @Override
+                public String getFingerprint() throws AuthorizationAccessException {
+                    return baseManagedAuthorizer.getFingerprint();
+                }
+
+                @Override
+                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                    baseManagedAuthorizer.inheritFingerprint(fingerprint);
+                }
+
+                @Override
+                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                    baseManagedAuthorizer.checkInheritability(proposedFingerprint);
+                }
+
+                @Override
+                public AccessPolicyProvider getAccessPolicyProvider() {
+                    final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
+                    if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                        final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
+                        return new ConfigurableAccessPolicyProvider() {
+                            @Override
+                            public String getFingerprint() throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.getFingerprint();
+                            }
+
+                            @Override
+                            public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                                baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
+                            }
+
+                            @Override
+                            public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                                baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
+                            }
+
+                            @Override
+                            public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                                if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) {
+                                    throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
+                                }
+                                return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
+                            }
+
+                            @Override
+                            public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
+                            }
+
+                            @Override
+                            public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
+                            }
+
+                            @Override
+                            public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.getAccessPolicies();
+                            }
+
+                            @Override
+                            public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
+                            }
+
+                            @Override
+                            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                                return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
+                            }
+
+                            @Override
+                            public UserGroupProvider getUserGroupProvider() {
+                                final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider();
+                                if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
+                                    final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
+                                    return new ConfigurableUserGroupProvider() {
+                                        @Override
+                                        public String getFingerprint() throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getFingerprint();
+                                        }
+
+                                        @Override
+                                        public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                                            baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
+                                        }
+
+                                        @Override
+                                        public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                                            baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
+                                        }
+
+                                        @Override
+                                        public User addUser(User user) throws AuthorizationAccessException {
+                                            if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
+                                            }
+                                            return baseConfigurableUserGroupProvider.addUser(user);
+                                        }
+
+                                        @Override
+                                        public User updateUser(User user) throws AuthorizationAccessException {
+                                            if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
+                                            }
+                                            return baseConfigurableUserGroupProvider.updateUser(user);
+                                        }
+
+                                        @Override
+                                        public User deleteUser(User user) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.deleteUser(user);
+                                        }
+
+                                        @Override
+                                        public Group addGroup(Group group) throws AuthorizationAccessException {
+                                            if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
+                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
+                                            }
+                                            return baseConfigurableUserGroupProvider.addGroup(group);
+                                        }
+
+                                        @Override
+                                        public Group updateGroup(Group group) throws AuthorizationAccessException {
+                                            if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
+                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
+                                            }
+                                            return baseConfigurableUserGroupProvider.updateGroup(group);
+                                        }
+
+                                        @Override
+                                        public Group deleteGroup(Group group) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.deleteGroup(group);
+                                        }
+
+                                        @Override
+                                        public Set<User> getUsers() throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getUsers();
+                                        }
+
+                                        @Override
+                                        public User getUser(String identifier) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getUser(identifier);
+                                        }
+
+                                        @Override
+                                        public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
+                                        }
+
+                                        @Override
+                                        public Set<Group> getGroups() throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getGroups();
+                                        }
+
+                                        @Override
+                                        public Group getGroup(String identifier) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getGroup(identifier);
+                                        }
+
+                                        @Override
+                                        public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                                            return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
+                                        }
+
+                                        @Override
+                                        public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                                            baseConfigurableUserGroupProvider.initialize(initializationContext);
+                                        }
+
+                                        @Override
+                                        public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                                            baseConfigurableUserGroupProvider.onConfigured(configurationContext);
+                                        }
+
+                                        @Override
+                                        public void preDestruction() throws AuthorizerDestructionException {
+                                            baseConfigurableUserGroupProvider.preDestruction();
+                                        }
+                                    };
+                                } else {
+                                    return baseUserGroupProvider;
+                                }
+                            }
+
+                            @Override
+                            public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                                baseConfigurableAccessPolicyProvider.initialize(initializationContext);
+                            }
+
+                            @Override
+                            public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                                baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
+                            }
+
+                            @Override
+                            public void preDestruction() throws AuthorizerDestructionException {
+                                baseConfigurableAccessPolicyProvider.preDestruction();
+                            }
+                        };
+                    } else {
+                        return baseAccessPolicyProvider;
+                    }
+                }
+
+                @Override
+                public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+                    return baseManagedAuthorizer.authorize(request);
+                }
+
+                @Override
+                public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+                    baseManagedAuthorizer.initialize(initializationContext);
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    baseManagedAuthorizer.onConfigured(configurationContext);
+
+                    final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
+                    final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
+
+                    // ensure that only one policy per resource-action exists
+                    for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
+                        if (policyExists(accessPolicyProvider, accessPolicy)) {
+                            throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
+                        }
+                    }
+
+                    // ensure that only one group exists per identity
+                    for (User user : userGroupProvider.getUsers()) {
+                        if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                            throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
+                        }
+                    }
+
+                    // ensure that only one group exists per identity
+                    for (Group group : userGroupProvider.getGroups()) {
+                        if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) {
+                            throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
+                        }
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    baseManagedAuthorizer.preDestruction();
+                }
+            };
+        } else {
+            return baseAuthorizer;
+        }
+    }
+
+    /**
+     * Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods.
+     *
+     * @param baseAuthorizer base authorizer
+     * @return authorizer
+     */
+    public static Authorizer withNarLoader(final Authorizer baseAuthorizer) {
+        if (baseAuthorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
+            return new ManagedAuthorizer() {
+                @Override
+                public String getFingerprint() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseManagedAuthorizer.getFingerprint();
+                    }
+                }
+
+                @Override
+                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseManagedAuthorizer.inheritFingerprint(fingerprint);
+                    }
+                }
+
+                @Override
+                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseManagedAuthorizer.checkInheritability(proposedFingerprint);
+                    }
+                }
+
+                @Override
+                public AccessPolicyProvider getAccessPolicyProvider() {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseManagedAuthorizer.getAccessPolicyProvider();
+                    }
+                }
+
+                @Override
+                public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseManagedAuthorizer.authorize(request);
+                    }
+                }
+
+                @Override
+                public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseManagedAuthorizer.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseManagedAuthorizer.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseManagedAuthorizer.preDestruction();
+                    }
+                }
+            };
+        } else {
+            return new Authorizer() {
+                @Override
+                public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseAuthorizer.authorize(request);
+                    }
+                }
+
+                @Override
+                public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAuthorizer.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAuthorizer.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseAuthorizer.preDestruction();
+                    }
+                }
+            };
+        }
+    }
+
+    private AuthorizerFactory() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
index b934cb8..9de8756 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
@@ -25,7 +25,6 @@ import org.apache.nifi.authorization.generated.Authorizers;
 import org.apache.nifi.authorization.generated.Property;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.nar.ExtensionManager;
-import org.apache.nifi.nar.NarCloseable;
 import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,12 +48,11 @@ import java.lang.reflect.Method;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Set;
 
 /**
  * Factory bean for loading the configured authorizer.
  */
-public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, AuthorizerLookup {
+public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup {
 
     private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactoryBean.class);
     private static final String AUTHORIZERS_XSD = "/authorizers.xsd";
@@ -74,8 +72,19 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
 
     private Authorizer authorizer;
     private NiFiProperties properties;
+    private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>();
+    private final Map<String, AccessPolicyProvider> accessPolicyProviders = new HashMap<>();
     private final Map<String, Authorizer> authorizers = new HashMap<>();
 
+    @Override
+    public UserGroupProvider getUserGroupProvider(String identifier) {
+        return userGroupProviders.get(identifier);
+    }
+
+    @Override
+    public AccessPolicyProvider getAccessPolicyProvider(String identifier) {
+        return accessPolicyProviders.get(identifier);
+    }
 
     @Override
     public Authorizer getAuthorizer(String identifier) {
@@ -98,6 +107,28 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
                 } else {
                     final Authorizers authorizerConfiguration = loadAuthorizersConfiguration();
 
+                    // create each user group provider
+                    for (final org.apache.nifi.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) {
+                        userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz()));
+                    }
+
+                    // configure each user group provider
+                    for (final org.apache.nifi.authorization.generated.UserGroupProvider provider : authorizerConfiguration.getUserGroupProvider()) {
+                        final UserGroupProvider instance = userGroupProviders.get(provider.getIdentifier());
+                        instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
+                    }
+
+                    // create each access policy provider
+                    for (final org.apache.nifi.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) {
+                        accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz()));
+                    }
+
+                    // configure each access policy provider
+                    for (final org.apache.nifi.authorization.generated.AccessPolicyProvider provider : authorizerConfiguration.getAccessPolicyProvider()) {
+                        final AccessPolicyProvider instance = accessPolicyProviders.get(provider.getIdentifier());
+                        instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
+                    }
+
                     // create each authorizer
                     for (final org.apache.nifi.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) {
                         authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz()));
@@ -106,7 +137,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
                     // configure each authorizer
                     for (final org.apache.nifi.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) {
                         final Authorizer instance = authorizers.get(provider.getIdentifier());
-                        instance.onConfigured(loadAuthorizerConfiguration(provider));
+                        instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
                     }
 
                     // get the authorizer instance
@@ -146,6 +177,102 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
         }
     }
 
+    private UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception {
+        // get the classloader for the specified user group provider
+        final List<Bundle> userGroupProviderBundles = ExtensionManager.getBundles(userGroupProviderClassName);
+
+        if (userGroupProviderBundles.size() == 0) {
+            throw new Exception(String.format("The specified user group provider class '%s' is not known to this nifi.", userGroupProviderClassName));
+        }
+
+        if (userGroupProviderBundles.size() > 1) {
+            throw new Exception(String.format("Multiple bundles found for the specified user group provider class '%s', only one is allowed.", userGroupProviderClassName));
+        }
+
+        final Bundle userGroupProviderBundle = userGroupProviderBundles.get(0);
+        final ClassLoader userGroupProviderClassLoader = userGroupProviderBundle.getClassLoader();
+
+        // get the current context classloader
+        final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+
+        final UserGroupProvider instance;
+        try {
+            // set the appropriate class loader
+            Thread.currentThread().setContextClassLoader(userGroupProviderClassLoader);
+
+            // attempt to load the class
+            Class<?> rawUserGroupProviderClass = Class.forName(userGroupProviderClassName, true, userGroupProviderClassLoader);
+            Class<? extends UserGroupProvider> userGroupProviderClass = rawUserGroupProviderClass.asSubclass(UserGroupProvider.class);
+
+            // otherwise create a new instance
+            Constructor constructor = userGroupProviderClass.getConstructor();
+            instance = (UserGroupProvider) constructor.newInstance();
+
+            // method injection
+            performMethodInjection(instance, userGroupProviderClass);
+
+            // field injection
+            performFieldInjection(instance, userGroupProviderClass);
+
+            // call post construction lifecycle event
+            instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
+        } finally {
+            if (currentClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(currentClassLoader);
+            }
+        }
+
+        return UserGroupProviderFactory.withNarLoader(instance);
+    }
+
+    private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception {
+        // get the classloader for the specified access policy provider
+        final List<Bundle> accessPolicyProviderBundles = ExtensionManager.getBundles(accessPolicyProviderClassName);
+
+        if (accessPolicyProviderBundles.size() == 0) {
+            throw new Exception(String.format("The specified access policy provider class '%s' is not known to this nifi.", accessPolicyProviderClassName));
+        }
+
+        if (accessPolicyProviderBundles.size() > 1) {
+            throw new Exception(String.format("Multiple bundles found for the specified access policy provider class '%s', only one is allowed.", accessPolicyProviderClassName));
+        }
+
+        final Bundle accessPolicyProviderBundle = accessPolicyProviderBundles.get(0);
+        final ClassLoader accessPolicyProviderClassLoader = accessPolicyProviderBundle.getClassLoader();
+
+        // get the current context classloader
+        final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+
+        final AccessPolicyProvider instance;
+        try {
+            // set the appropriate class loader
+            Thread.currentThread().setContextClassLoader(accessPolicyProviderClassLoader);
+
+            // attempt to load the class
+            Class<?> rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName, true, accessPolicyProviderClassLoader);
+            Class<? extends AccessPolicyProvider> accessPolicyClass = rawAccessPolicyProviderClass.asSubclass(AccessPolicyProvider.class);
+
+            // otherwise create a new instance
+            Constructor constructor = accessPolicyClass.getConstructor();
+            instance = (AccessPolicyProvider) constructor.newInstance();
+
+            // method injection
+            performMethodInjection(instance, accessPolicyClass);
+
+            // field injection
+            performFieldInjection(instance, accessPolicyClass);
+
+            // call post construction lifecycle event
+            instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
+        } finally {
+            if (currentClassLoader != null) {
+                Thread.currentThread().setContextClassLoader(currentClassLoader);
+            }
+        }
+
+        return AccessPolicyProviderFactory.withNarLoader(instance);
+    }
+
     private Authorizer createAuthorizer(final String identifier, final String authorizerClassName) throws Exception {
         // get the classloader for the specified authorizer
         final List<Bundle> authorizerBundles = ExtensionManager.getBundles(authorizerClassName);
@@ -184,26 +311,26 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
             performFieldInjection(instance, authorizerClass);
 
             // call post construction lifecycle event
-            instance.initialize(new StandardAuthorizerInitializationContext(identifier, this));
+            instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
         } finally {
             if (currentClassLoader != null) {
                 Thread.currentThread().setContextClassLoader(currentClassLoader);
             }
         }
 
-        return withNarLoader(instance);
+        return AuthorizerFactory.installIntegrityChecks(AuthorizerFactory.withNarLoader(instance));
     }
 
-    private AuthorizerConfigurationContext loadAuthorizerConfiguration(final org.apache.nifi.authorization.generated.Authorizer authorizer) {
+    private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Property> properties) {
         final Map<String, String> authorizerProperties = new HashMap<>();
 
-        for (final Property property : authorizer.getProperty()) {
+        for (final Property property : properties) {
             authorizerProperties.put(property.getName(), property.getValue());
         }
-        return new StandardAuthorizerConfigurationContext(authorizer.getIdentifier(), authorizerProperties);
+        return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties);
     }
 
-    private void performMethodInjection(final Authorizer instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+    private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
         for (final Method method : authorizerClass.getMethods()) {
             if (method.isAnnotationPresent(AuthorizerContext.class)) {
                 // make the method accessible
@@ -235,7 +362,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
         }
     }
 
-    private void performFieldInjection(final Authorizer instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException {
+    private void performFieldInjection(final Object instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException {
         for (final Field field : authorizerClass.getDeclaredFields()) {
             if (field.isAnnotationPresent(AuthorizerContext.class)) {
                 // make the method accessible
@@ -291,189 +418,6 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
         };
     }
 
-    /**
-     * Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods.
-     *
-     * @param baseAuthorizer base authorizer
-     * @return authorizer
-     */
-    public Authorizer withNarLoader(final Authorizer baseAuthorizer) {
-        if (baseAuthorizer instanceof AbstractPolicyBasedAuthorizer) {
-            AbstractPolicyBasedAuthorizer policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) baseAuthorizer;
-            return new AbstractPolicyBasedAuthorizer() {
-                @Override
-                public Group doAddGroup(Group group) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.addGroup(group);
-                    }
-                }
-
-                @Override
-                public Group getGroup(String identifier) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getGroup(identifier);
-                    }
-                }
-
-                @Override
-                public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.updateGroup(group);
-                    }
-                }
-
-                @Override
-                public Group deleteGroup(Group group) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.deleteGroup(group);
-                    }
-                }
-
-                @Override
-                public Set<Group> getGroups() throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getGroups();
-                    }
-                }
-
-                @Override
-                public User doAddUser(User user) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.addUser(user);
-                    }
-                }
-
-                @Override
-                public User getUser(String identifier) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getUser(identifier);
-                    }
-                }
-
-                @Override
-                public User getUserByIdentity(String identity) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getUserByIdentity(identity);
-                    }
-                }
-
-                @Override
-                public User doUpdateUser(User user) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.updateUser(user);
-                    }
-                }
-
-                @Override
-                public User deleteUser(User user) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.deleteUser(user);
-                    }
-                }
-
-                @Override
-                public Set<User> getUsers() throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getUsers();
-                    }
-                }
-
-                @Override
-                public AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.addAccessPolicy(accessPolicy);
-                    }
-                }
-
-                @Override
-                public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getAccessPolicy(identifier);
-                    }
-                }
-
-                @Override
-                public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.updateAccessPolicy(accessPolicy);
-                    }
-                }
-
-                @Override
-                public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.deleteAccessPolicy(accessPolicy);
-                    }
-                }
-
-                @Override
-                public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getAccessPolicies();
-                    }
-                }
-
-                @Override
-                public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return policyBasedAuthorizer.getUsersAndAccessPolicies();
-                    }
-                }
-
-                @Override
-                public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        policyBasedAuthorizer.initialize(initializationContext);
-                    }
-                }
-
-                @Override
-                public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        policyBasedAuthorizer.onConfigured(configurationContext);
-                    }
-                }
-
-                @Override
-                public void preDestruction() throws AuthorizerDestructionException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        baseAuthorizer.preDestruction();
-                    }
-                }
-            };
-        } else {
-            return new Authorizer() {
-                @Override
-                public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        return baseAuthorizer.authorize(request);
-                    }
-                }
-
-                @Override
-                public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        baseAuthorizer.initialize(initializationContext);
-                    }
-                }
-
-                @Override
-                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        baseAuthorizer.onConfigured(configurationContext);
-                    }
-                }
-
-                @Override
-                public void preDestruction() throws AuthorizerDestructionException {
-                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
-                        baseAuthorizer.preDestruction();
-                    }
-                }
-            };
-        }
-    }
-
     @Override
     public Class getObjectType() {
         return Authorizer.class;


[09/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java
new file mode 100644
index 0000000..caa265f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupProviderFactory.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.nar.NarCloseable;
+
+import java.util.Set;
+
+public final class UserGroupProviderFactory {
+
+    public static UserGroupProvider withNarLoader(final UserGroupProvider baseUserGroupProvider) {
+        if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
+            return new ConfigurableUserGroupProvider() {
+                @Override
+                public User addUser(User user) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.addUser(user);
+                    }
+                }
+
+                @Override
+                public User updateUser(User user) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.updateUser(user);
+                    }
+                }
+
+                @Override
+                public User deleteUser(User user) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.deleteUser(user);
+                    }
+                }
+
+                @Override
+                public Group addGroup(Group group) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.addGroup(group);
+                    }
+                }
+
+                @Override
+                public Group updateGroup(Group group) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.updateGroup(group);
+                    }
+                }
+
+                @Override
+                public Group deleteGroup(Group group) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.deleteGroup(group);
+                    }
+                }
+
+                @Override
+                public Set<User> getUsers() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getUsers();
+                    }
+                }
+
+                @Override
+                public User getUser(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getUser(identifier);
+                    }
+                }
+
+                @Override
+                public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
+                    }
+                }
+
+                @Override
+                public Set<Group> getGroups() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getGroups();
+                    }
+                }
+
+                @Override
+                public Group getGroup(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getGroup(identifier);
+                    }
+                }
+
+                @Override
+                public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
+                    }
+                }
+
+                @Override
+                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
+                    }
+                }
+
+                @Override
+                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
+                    }
+                }
+
+                @Override
+                public String getFingerprint() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseConfigurableUserGroupProvider.getFingerprint();
+                    }
+                }
+
+                @Override
+                public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableUserGroupProvider.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableUserGroupProvider.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseConfigurableUserGroupProvider.preDestruction();
+                    }
+                }
+            };
+        } else {
+            return new UserGroupProvider() {
+                @Override
+                public Set<User> getUsers() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getUsers();
+                    }
+                }
+
+                @Override
+                public User getUser(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getUser(identifier);
+                    }
+                }
+
+                @Override
+                public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getUserByIdentity(identity);
+                    }
+                }
+
+                @Override
+                public Set<Group> getGroups() throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getGroups();
+                    }
+                }
+
+                @Override
+                public Group getGroup(String identifier) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getGroup(identifier);
+                    }
+                }
+
+                @Override
+                public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        return baseUserGroupProvider.getUserAndGroups(identity);
+                    }
+                }
+
+                @Override
+                public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseUserGroupProvider.initialize(initializationContext);
+                    }
+                }
+
+                @Override
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseUserGroupProvider.onConfigured(configurationContext);
+                    }
+                }
+
+                @Override
+                public void preDestruction() throws AuthorizerDestructionException {
+                    try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
+                        baseUserGroupProvider.preDestruction();
+                    }
+                }
+            };
+        }
+    }
+
+    private UserGroupProviderFactory() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd
index 4b68b00..46c004a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/xsd/authorizers.xsd
@@ -14,7 +14,25 @@
   limitations under the License.
 -->
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
-    <!-- role -->
+    <!-- user group providers type -->
+    <xs:complexType name="UserGroupProvider">
+        <xs:sequence>
+            <xs:element name="identifier" type="NonEmptyStringType"/>
+            <xs:element name="class" type="NonEmptyStringType"/>
+            <xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- access policy provider type -->
+    <xs:complexType name="AccessPolicyProvider">
+        <xs:sequence>
+            <xs:element name="identifier" type="NonEmptyStringType"/>
+            <xs:element name="class" type="NonEmptyStringType"/>
+            <xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded" />
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- authorizers type -->
     <xs:complexType name="Authorizer">
         <xs:sequence>
             <xs:element name="identifier" type="NonEmptyStringType"/>
@@ -38,10 +56,12 @@
         </xs:restriction>
     </xs:simpleType>
 
-    <!-- users -->
+    <!-- authorizers -->
     <xs:element name="authorizers">
         <xs:complexType>
             <xs:sequence>
+                <xs:element name="userGroupProvider" type="UserGroupProvider" minOccurs="0" maxOccurs="unbounded"/>
+                <xs:element name="accessPolicyProvider" type="AccessPolicyProvider" minOccurs="0" maxOccurs="unbounded"/>
                 <xs:element name="authorizer" type="Authorizer" minOccurs="0" maxOccurs="unbounded"/>
             </xs:sequence>
         </xs:complexType>

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java
new file mode 100644
index 0000000..13d6f5a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/AuthorizerFactoryTest.java
@@ -0,0 +1,264 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+public class AuthorizerFactoryTest {
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() {
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+
+        AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-id-1")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .build();
+
+        AccessPolicy policy2 = new AccessPolicy.Builder()
+                .identifier("policy-id-2")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .build();
+
+        Set<AccessPolicy> policies = new LinkedHashSet<>();
+        policies.add(policy1);
+        policies.add(policy2);
+
+        Set<User> users = new LinkedHashSet<>();
+        users.add(user1);
+
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+        Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies));
+        authorizer.onConfigured(context);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenUsersWithSameIdentity() {
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+        User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
+
+        Set<User> users = new LinkedHashSet<>();
+        users.add(user1);
+        users.add(user2);
+
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+        Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>()));
+        authorizer.onConfigured(context);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenGroupsWithSameName() {
+        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
+        Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
+
+        Set<Group> groups = new LinkedHashSet<>();
+        groups.add(group1);
+        groups.add(group2);
+
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+        Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>()));
+        authorizer.onConfigured(context);
+    }
+
+    @Test
+    public void testAddPoliciesWithSameResourceAndAction() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+        userGroupProvider.addUser(user1);
+
+        AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-id-1")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .build();
+        accessPolicyProvider.addAccessPolicy(policy1);
+
+        AccessPolicy policy2 = new AccessPolicy.Builder()
+                .identifier("policy-id-2")
+                .resource("resource1")
+                .action(RequestAction.READ)
+                .addUser(user1.getIdentifier())
+                .build();
+
+        try {
+            accessPolicyProvider.addAccessPolicy(policy2);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testAddUsersWithSameIdentity() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
+        userGroupProvider.addUser(user1);
+
+        User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
+
+        try {
+            userGroupProvider.addUser(user2);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testAddGroupsWithSameName() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
+        userGroupProvider.addGroup(group1);
+
+        Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
+
+        try {
+            userGroupProvider.addGroup(group2);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testAddUsersWithSameIdentityAsGroupName() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
+        userGroupProvider.addGroup(group1);
+
+        User user = new User.Builder().identifier("user-id-2").identity("abc").build();
+
+        try {
+            userGroupProvider.addUser(user);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testAddGroupWithSameNameAsUserIdentity() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        User user = new User.Builder().identifier("user-id-2").identity("abc").build();
+        userGroupProvider.addUser(user);
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
+        try {
+            userGroupProvider.addGroup(group1);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testUpdateUserWithSameIdentity() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        User user1 = new User.Builder().identifier("user-id-1").identity("abc").build();
+        userGroupProvider.addUser(user1);
+
+        User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build();
+        userGroupProvider.addUser(user2);
+
+        try {
+            User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build();
+            userGroupProvider.updateUser(user1Updated);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+
+    @Test
+    public void testUpdateGroupWithSameName() {
+        AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
+        managedAuthorizer.onConfigured(context);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
+        final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
+
+        Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
+        userGroupProvider.addGroup(group1);
+
+        Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build();
+        userGroupProvider.addGroup(group2);
+
+        try {
+            Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build();
+            userGroupProvider.updateGroup(group1Updated);
+            Assert.fail("Should have thrown exception");
+        } catch (IllegalStateException e) {
+
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
new file mode 100644
index 0000000..9b50b50
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Mock implementation of AbstractPolicyBasedAuthorizer.
+ */
+public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer {
+
+    private Set<Group> groups = new HashSet<>();
+    private Set<User> users = new HashSet<>();
+    private Set<AccessPolicy> policies = new HashSet<>();
+
+    public MockPolicyBasedAuthorizer() {
+
+    }
+
+    public MockPolicyBasedAuthorizer(Set<Group> groups, Set<User> users, Set<AccessPolicy> policies) {
+        if (groups != null) {
+            this.groups.addAll(groups);
+        }
+        if (users != null) {
+            this.users.addAll(users);
+        }
+        if (policies != null) {
+            this.policies.addAll(policies);
+        }
+    }
+
+    @Override
+    public Group doAddGroup(Group group) throws AuthorizationAccessException {
+        groups.add(group);
+        return group;
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws AuthorizationAccessException {
+        return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
+        deleteGroup(group);
+        return addGroup(group);
+    }
+
+    @Override
+    public Group deleteGroup(Group group) throws AuthorizationAccessException {
+        groups.remove(group);
+        return group;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return groups;
+    }
+
+    @Override
+    public User doAddUser(User user) throws AuthorizationAccessException {
+        users.add(user);
+        return user;
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException {
+        return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+        return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get();
+    }
+
+    @Override
+    public User doUpdateUser(User user) throws AuthorizationAccessException {
+        deleteUser(user);
+        return addUser(user);
+    }
+
+    @Override
+    public User deleteUser(User user) throws AuthorizationAccessException {
+        users.remove(user);
+        return user;
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return users;
+    }
+
+    @Override
+    protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        policies.add(accessPolicy);
+        return accessPolicy;
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+        return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        deleteAccessPolicy(accessPolicy);
+        return addAccessPolicy(accessPolicy);
+    }
+
+    @Override
+    public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
+        policies.remove(policy);
+        return policy;
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+        return policies;
+    }
+
+    @Override
+    public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
+        return new UsersAndAccessPolicies() {
+            @Override
+            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
+                return null;
+            }
+
+            @Override
+            public User getUser(String identity) {
+                return getUserByIdentity(identity);
+            }
+
+            @Override
+            public Set<Group> getGroups(String userIdentity) {
+                User user = getUserByIdentity(userIdentity);
+                if (user == null) {
+                    return new HashSet<>();
+                } else {
+                    return groups.stream()
+                            .filter(g -> g.getUsers().contains(user.getIdentifier()))
+                            .collect(Collectors.toSet());
+                }
+            }
+        };
+    }
+
+    @Override
+    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+
+    }
+
+    @Override
+    public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
index 9e546b1..fa039fe 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/FlowConfigurationDTO.java
@@ -29,7 +29,9 @@ import java.util.Date;
 @XmlType(name = "flowConfiguration")
 public class FlowConfigurationDTO {
 
+    private Boolean supportsManagedAuthorizer;
     private Boolean supportsConfigurableAuthorizer;
+    private Boolean supportsConfigurableUsersAndGroups;
     private Long autoRefreshIntervalSeconds;
 
     private Date currentTime;
@@ -51,6 +53,37 @@ public class FlowConfigurationDTO {
     }
 
     /**
+     * @return whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups,
+     * and policies in the UI. This value is read only
+     */
+    @ApiModelProperty(
+            value = "Whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups, and policies in the UI.",
+            readOnly = true
+    )
+    public Boolean getSupportsManagedAuthorizer() {
+        return supportsManagedAuthorizer;
+    }
+
+    public void setSupportsManagedAuthorizer(Boolean supportsManagedAuthorizer) {
+        this.supportsManagedAuthorizer = supportsManagedAuthorizer;
+    }
+
+    /**
+     * @return whether this NiFi supports configurable users and groups. This value is read only
+     */
+    @ApiModelProperty(
+            value = "Whether this NiFi supports configurable users and groups.",
+            readOnly = true
+    )
+    public Boolean getSupportsConfigurableUsersAndGroups() {
+        return supportsConfigurableUsersAndGroups;
+    }
+
+    public void setSupportsConfigurableUsersAndGroups(Boolean supportsConfigurableUsersAndGroups) {
+        this.supportsConfigurableUsersAndGroups = supportsConfigurableUsersAndGroups;
+    }
+
+    /**
      * @return whether this NiFi supports a configurable authorizer. This value is read only
      */
     @ApiModelProperty(

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java
index d42f498..19d831d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/AccessPolicyEntity.java
@@ -28,7 +28,7 @@ import java.util.Date;
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to an {@link AccessPolicyDTO}.
  */
 @XmlRootElement(name = "accessPolicyEntity")
-public class AccessPolicyEntity extends ComponentEntity {
+public class AccessPolicyEntity extends ComponentEntity implements Permissible<AccessPolicyDTO> {
 
     private Date generated;
     private AccessPolicyDTO component;

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java
index 49c51c3..cc97619 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/TenantsEntity.java
@@ -24,7 +24,7 @@ import java.util.Collection;
  * TenantEntity objects.
  */
 @XmlRootElement(name = "tenantsEntity")
-public class TenantsEntity {
+public class TenantsEntity extends Entity {
 
     private Collection<TenantEntity> users;
     private Collection<TenantEntity> userGroups;

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java
index 983cdfb..11d2f9d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserEntity.java
@@ -24,7 +24,7 @@ import javax.xml.bind.annotation.XmlRootElement;
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserDTO.
  */
 @XmlRootElement(name = "userEntity")
-public class UserEntity extends ComponentEntity {
+public class UserEntity extends ComponentEntity implements Permissible<UserDTO> {
 
     private UserDTO component;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java
index ea8238a..d70489c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupEntity.java
@@ -16,14 +16,15 @@
  */
 package org.apache.nifi.web.api.entity;
 
-import javax.xml.bind.annotation.XmlRootElement;
 import org.apache.nifi.web.api.dto.UserGroupDTO;
 
+import javax.xml.bind.annotation.XmlRootElement;
+
 /**
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserGroupDTO.
  */
 @XmlRootElement(name = "userGroupEntity")
-public class UserGroupEntity extends ComponentEntity {
+public class UserGroupEntity extends ComponentEntity implements Permissible<UserGroupDTO> {
 
     private UserGroupDTO component;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
index bdde662..d4b4c5d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
@@ -24,7 +24,7 @@ import java.util.Collection;
  * UserGroupEntity objects.
  */
 @XmlRootElement(name = "userGroupsEntity")
-public class UserGroupsEntity {
+public class UserGroupsEntity extends Entity {
 
     private Collection<UserGroupEntity> userGroups;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml
index 0f44b6a..bba0413 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml
@@ -41,8 +41,9 @@
                             <goal>xjc</goal>
                         </goals>
                         <configuration>
-                            <schemaDirectory>src/main/xsd</schemaDirectory>
-                            <schemaFiles>authorizations.xsd</schemaFiles>
+                            <sources>
+                                <source>src/main/xsd/authorizations.xsd</source>
+                            </sources>
                             <packageName>org.apache.nifi.authorization.file.generated</packageName>
                         </configuration>
                     </execution>
@@ -52,8 +53,9 @@
                             <goal>xjc</goal>
                         </goals>
                         <configuration>
-                            <schemaDirectory>src/main/xsd</schemaDirectory>
-                            <schemaFiles>tenants.xsd</schemaFiles>
+                            <sources>
+                                <source>src/main/xsd/tenants.xsd</source>
+                            </sources>
                             <packageName>org.apache.nifi.authorization.file.tenants.generated</packageName>
                             <clearOutputDir>false</clearOutputDir>
                         </configuration>
@@ -64,8 +66,9 @@
                             <goal>xjc</goal>
                         </goals>
                         <configuration>
-                            <schemaDirectory>src/main/xsd</schemaDirectory>
-                            <schemaFiles>legacy-users.xsd</schemaFiles>
+                            <sources>
+                                <source>src/main/xsd/legacy-users.xsd</source>
+                            </sources>
                             <packageName>org.apache.nifi.user.generated</packageName>
                             <clearOutputDir>false</clearOutputDir>
                         </configuration>

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
index e407289..0d3ea64 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizationsHolder.java
@@ -19,9 +19,6 @@ package org.apache.nifi.authorization;
 
 import org.apache.nifi.authorization.file.generated.Authorizations;
 import org.apache.nifi.authorization.file.generated.Policies;
-import org.apache.nifi.authorization.file.tenants.generated.Groups;
-import org.apache.nifi.authorization.file.tenants.generated.Tenants;
-import org.apache.nifi.authorization.file.tenants.generated.Users;
 
 import java.util.Collections;
 import java.util.HashMap;
@@ -32,56 +29,26 @@ import java.util.Set;
 /**
  * A holder to provide atomic access to data structures.
  */
-public class AuthorizationsHolder implements UsersAndAccessPolicies {
+public class AuthorizationsHolder {
 
-    private final Tenants tenants;
     private final Authorizations authorizations;
 
     private final Set<AccessPolicy> allPolicies;
     private final Map<String, Set<AccessPolicy>> policiesByResource;
     private final Map<String, AccessPolicy> policiesById;
 
-    private final Set<User> allUsers;
-    private final Map<String,User> usersById;
-    private final Map<String,User> usersByIdentity;
-
-    private final Set<Group> allGroups;
-    private final Map<String,Group> groupsById;
-    private final Map<String, Set<Group>> groupsByUserIdentity;
-
     /**
-     * Creates a new holder and populates all convenience data structures.
+     * Creates a new holder and populates all convenience authorizations data structures.
      *
      * @param authorizations the current authorizations instance
      */
-    public AuthorizationsHolder(final Authorizations authorizations, final Tenants tenants) {
+    public AuthorizationsHolder(final Authorizations authorizations) {
         this.authorizations = authorizations;
-        this.tenants = tenants;
-
-        // load all users
-        final Users users = tenants.getUsers();
-        final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
-
-        // load all groups
-        final Groups groups = tenants.getGroups();
-        final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
 
         // load all access policies
         final Policies policies = authorizations.getPolicies();
         final Set<AccessPolicy> allPolicies = Collections.unmodifiableSet(createAccessPolicies(policies));
 
-        // create a convenience map to retrieve a user by id
-        final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
-
-        // create a convenience map to retrieve a user by identity
-        final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
-
-        // create a convenience map to retrieve a group by id
-        final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
-
-        // create a convenience map to retrieve the groups for a user identity
-        final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
-
         // create a convenience map from resource id to policies
         final Map<String, Set<AccessPolicy>> policiesByResourceMap = Collections.unmodifiableMap(createResourcePolicyMap(allPolicies));
 
@@ -89,13 +56,7 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
         final Map<String, AccessPolicy> policiesByIdMap = Collections.unmodifiableMap(createPoliciesByIdMap(allPolicies));
 
         // set all the holders
-        this.allUsers = allUsers;
-        this.allGroups = allGroups;
         this.allPolicies = allPolicies;
-        this.usersById = userByIdMap;
-        this.usersByIdentity = userByIdentityMap;
-        this.groupsById = groupByIdMap;
-        this.groupsByUserIdentity = groupsByUserIdentityMap;
         this.policiesByResource = policiesByResourceMap;
         this.policiesById = policiesByIdMap;
     }
@@ -134,9 +95,9 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
 
             // add the appropriate request actions
             final String authorizationCode = policy.getAction();
-            if (authorizationCode.equals(FileAuthorizer.READ_CODE)) {
+            if (authorizationCode.equals(FileAccessPolicyProvider.READ_CODE)) {
                 builder.action(RequestAction.READ);
-            } else if (authorizationCode.equals(FileAuthorizer.WRITE_CODE)){
+            } else if (authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){
                 builder.action(RequestAction.WRITE);
             } else {
                 throw new IllegalStateException("Unknown Policy Action: " + authorizationCode);
@@ -150,57 +111,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
     }
 
     /**
-     * Creates a set of Users from the JAXB Users.
-     *
-     * @param users the JAXB Users
-     * @return a set of API Users matching the provided JAXB Users
-     */
-    private Set<User> createUsers(org.apache.nifi.authorization.file.tenants.generated.Users users) {
-        Set<User> allUsers = new HashSet<>();
-        if (users == null || users.getUser() == null) {
-            return allUsers;
-        }
-
-        for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) {
-            final User.Builder builder = new User.Builder()
-                    .identity(user.getIdentity())
-                    .identifier(user.getIdentifier());
-
-            allUsers.add(builder.build());
-        }
-
-        return allUsers;
-    }
-
-    /**
-     * Creates a set of Groups from the JAXB Groups.
-     *
-     * @param groups the JAXB Groups
-     * @return a set of API Groups matching the provided JAXB Groups
-     */
-    private Set<Group> createGroups(org.apache.nifi.authorization.file.tenants.generated.Groups groups,
-                                    org.apache.nifi.authorization.file.tenants.generated.Users users) {
-        Set<Group> allGroups = new HashSet<>();
-        if (groups == null || groups.getGroup() == null) {
-            return allGroups;
-        }
-
-        for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) {
-            final Group.Builder builder = new Group.Builder()
-                    .identifier(group.getIdentifier())
-                    .name(group.getName());
-
-            for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
-                builder.addUser(groupUser.getIdentifier());
-            }
-
-            allGroups.add(builder.build());
-        }
-
-        return allGroups;
-    }
-
-    /**
      * Creates a map from resource identifier to the set of policies for the given resource.
      *
      * @param allPolicies the set of all policies
@@ -222,74 +132,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
     }
 
     /**
-     * Creates a Map from user identifier to User.
-     *
-     * @param users the set of all users
-     * @return the Map from user identifier to User
-     */
-    private Map<String,User> createUserByIdMap(final Set<User> users) {
-        Map<String,User> usersMap = new HashMap<>();
-        for (User user : users) {
-            usersMap.put(user.getIdentifier(), user);
-        }
-        return usersMap;
-    }
-
-    /**
-     * Creates a Map from user identity to User.
-     *
-     * @param users the set of all users
-     * @return the Map from user identity to User
-     */
-    private Map<String,User> createUserByIdentityMap(final Set<User> users) {
-        Map<String,User> usersMap = new HashMap<>();
-        for (User user : users) {
-            usersMap.put(user.getIdentity(), user);
-        }
-        return usersMap;
-    }
-
-    /**
-     * Creates a Map from group identifier to Group.
-     *
-     * @param groups the set of all groups
-     * @return the Map from group identifier to Group
-     */
-    private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
-        Map<String,Group> groupsMap = new HashMap<>();
-        for (Group group : groups) {
-            groupsMap.put(group.getIdentifier(), group);
-        }
-        return groupsMap;
-    }
-
-    /**
-     * Creates a Map from user identity to the set of Groups for that identity.
-     *
-     * @param groups all groups
-     * @param users all users
-     * @return a Map from User identity to the set of Groups for that identity
-     */
-    private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
-        Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
-
-        for (User user : users) {
-            Set<Group> userGroups = new HashSet<>();
-            for (Group group : groups) {
-                for (String groupUser : group.getUsers()) {
-                    if (groupUser.equals(user.getIdentifier())) {
-                        userGroups.add(group);
-                    }
-                }
-            }
-
-            groupsByUserIdentity.put(user.getIdentity(), userGroups);
-        }
-
-        return groupsByUserIdentity;
-    }
-
-    /**
      * Creates a Map from policy identifier to AccessPolicy.
      *
      * @param policies the set of all access policies
@@ -307,10 +149,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
         return authorizations;
     }
 
-    public Tenants getTenants() {
-        return tenants;
-    }
-
     public Set<AccessPolicy> getAllPolicies() {
         return allPolicies;
     }
@@ -323,27 +161,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
         return policiesById;
     }
 
-    public Set<User> getAllUsers() {
-        return allUsers;
-    }
-
-    public Map<String, User> getUsersById() {
-        return usersById;
-    }
-
-    public Map<String, User> getUsersByIdentity() {
-        return usersByIdentity;
-    }
-
-    public Set<Group> getAllGroups() {
-        return allGroups;
-    }
-
-    public Map<String, Group> getGroupsById() {
-        return groupsById;
-    }
-
-    @Override
     public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) {
         if (resourceIdentifier == null) {
             throw new IllegalArgumentException("Resource Identifier cannot be null");
@@ -363,20 +180,4 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
         return null;
     }
 
-    @Override
-    public User getUser(String identity) {
-        if (identity == null) {
-            throw new IllegalArgumentException("Identity cannot be null");
-        }
-        return usersByIdentity.get(identity);
-    }
-
-    @Override
-    public Set<Group> getGroups(String userIdentity) {
-        if (userIdentity == null) {
-            throw new IllegalArgumentException("User Identity cannot be null");
-        }
-        return groupsByUserIdentity.get(userIdentity);
-    }
-
 }


[04/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
new file mode 100644
index 0000000..8b91f78
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
@@ -0,0 +1,698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.file.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileUserGroupProviderTest {
+
+    private static final String EMPTY_TENANTS_CONCISE =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants/>";
+
+    private static final String EMPTY_TENANTS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants>"
+        + "</tenants>";
+
+    private static final String BAD_SCHEMA_TENANTS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenant>"
+        + "</tenant>";
+
+    private static final String SIMPLE_TENANTS_BY_USER =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<tenants>" +
+            "  <users>" +
+            "    <user identifier=\"user-1\" identity=\"user-1\"/>" +
+            "    <user identifier=\"user-2\" identity=\"user-2\"/>" +
+            "  </users>" +
+            "</tenants>";
+
+    private static final String TENANTS =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<tenants>" +
+            "  <groups>" +
+            "    <group identifier=\"group-1\" name=\"group-1\">" +
+            "       <user identifier=\"user-1\" />" +
+            "    </group>" +
+            "    <group identifier=\"group-2\" name=\"group-2\">" +
+            "       <user identifier=\"user-2\" />" +
+            "    </group>" +
+            "  </groups>" +
+            "  <users>" +
+            "    <user identifier=\"user-1\" identity=\"user-1\" />" +
+            "    <user identifier=\"user-2\" identity=\"user-2\" />" +
+            "  </users>" +
+            "</tenants>";
+
+    private NiFiProperties properties;
+    private FileUserGroupProvider userGroupProvider;
+    private File primaryTenants;
+    private File restoreTenants;
+
+    private AuthorizerConfigurationContext configurationContext;
+
+    @Before
+    public void setup() throws IOException {
+        // primary tenants
+        primaryTenants = new File("target/authorizations/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile());
+
+        // restore authorizations
+        restoreTenants = new File("target/restore/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile());
+
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
+
+        configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
+        when(configurationContext.getProperties()).then((invocation) -> {
+            final Map<String, String> properties = new HashMap<>();
+
+            final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
+            if (tenantFile != null) {
+                properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
+            }
+
+            final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            if (legacyAuthFile != null) {
+                properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
+            }
+
+            int i = 1;
+            while (true) {
+                final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                final PropertyValue value = configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            return properties;
+        });
+
+        userGroupProvider = new FileUserGroupProvider();
+        userGroupProvider.setNiFiProperties(properties);
+        userGroupProvider.initialize(null);
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        deleteFile(primaryTenants);
+        deleteFile(restoreTenants);
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        // verify all users got created correctly
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(6, users.size());
+
+        final User user1 = userGroupProvider.getUserByIdentity("user1");
+        assertNotNull(user1);
+
+        final User user2 = userGroupProvider.getUserByIdentity("user2");
+        assertNotNull(user2);
+
+        final User user3 = userGroupProvider.getUserByIdentity("user3");
+        assertNotNull(user3);
+
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        assertNotNull(user4);
+
+        final User user5 = userGroupProvider.getUserByIdentity("user5");
+        assertNotNull(user5);
+
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+        assertNotNull(user6);
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+        final Group group1 = groups.iterator().next();
+        assertEquals("group1", group1.getName());
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
+
+        properties = getNiFiProperties(props);
+        when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
+        userGroupProvider.setNiFiProperties(properties);
+
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final User user1 = userGroupProvider.getUserByIdentity("user1");
+        assertNotNull(user1);
+
+        final User user2 = userGroupProvider.getUserByIdentity("user2");
+        assertNotNull(user2);
+
+        final User user3 = userGroupProvider.getUserByIdentity("user3");
+        assertNotNull(user3);
+
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        assertNotNull(user4);
+
+        final User user5 = userGroupProvider.getUserByIdentity("user5");
+        assertNotNull(user5);
+
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+        assertNotNull(user6);
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+        final Group group1 = groups.iterator().next();
+        assertEquals("group1", group1.getName());
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialUsersNotProvided() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(0, users.size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialUsersProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node-identity-1";
+        final String nodeIdentity2 = "node-identity-2";
+
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(3, users.size());
+
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(adminIdentity).identity(adminIdentity).build()));
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity1).identity(nodeIdentity1).build()));
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity2).identity(nodeIdentity2).build()));
+    }
+
+    @Test
+    public void testOnConfiguredWhenTenantsExistAndInitialUsersProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node-identity-1";
+        final String nodeIdentity2 = "node-identity-2";
+
+        // despite setting initial users, they will not be loaded as the tenants file is non-empty
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryTenants, SIMPLE_TENANTS_BY_USER);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+
+        assertTrue(users.contains(new User.Builder().identifier("user-1").identity("user-1").build()));
+        assertTrue(users.contains(new User.Builder().identifier("user-2").identity("user-2").build()));
+    }
+
+    @Test
+    public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getUsers().size());
+        assertEquals(0, userGroupProvider.getGroups().size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        assertEquals(primaryTenants.length(), restoreTenants.length());
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception {
+        writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryTenantsDifferentThanRestore() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWithBadTenantsSchema() throws Exception {
+        writeFile(primaryTenants, BAD_SCHEMA_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testGetAllUsersGroupsPolicies() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(2, groups.size());
+
+        boolean foundGroup1 = false;
+        boolean foundGroup2 = false;
+
+        for (Group group : groups) {
+            if (group.getIdentifier().equals("group-1") && group.getName().equals("group-1")
+                    && group.getUsers().size() == 1 && group.getUsers().contains("user-1")) {
+                foundGroup1 = true;
+            } else if (group.getIdentifier().equals("group-2") && group.getName().equals("group-2")
+                    && group.getUsers().size() == 1 && group.getUsers().contains("user-2")) {
+                foundGroup2 = true;
+            }
+        }
+
+        assertTrue(foundGroup1);
+        assertTrue(foundGroup2);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+
+        boolean foundUser1 = false;
+        boolean foundUser2 = false;
+
+        for (User user : users) {
+            if (user.getIdentifier().equals("user-1") && user.getIdentity().equals("user-1")) {
+                foundUser1 = true;
+            } else if (user.getIdentifier().equals("user-2") && user.getIdentity().equals("user-2")) {
+                foundUser2 = true;
+            }
+        }
+
+        assertTrue(foundUser1);
+        assertTrue(foundUser2);
+    }
+
+    // --------------- User Tests ------------------------
+
+    @Test
+    public void testAddUser() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-1")
+                .identity("user-identity-1")
+                .build();
+
+        final User addedUser = userGroupProvider.addUser(user);
+        assertNotNull(addedUser);
+        assertEquals(user.getIdentifier(), addedUser.getIdentifier());
+        assertEquals(user.getIdentity(), addedUser.getIdentity());
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(1, users.size());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identifier = "user-1";
+        final User user = userGroupProvider.getUser(identifier);
+        assertNotNull(user);
+        assertEquals(identifier, user.getIdentifier());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identifier = "user-X";
+        final User user = userGroupProvider.getUser(identifier);
+        assertNull(user);
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identity = "user-1";
+        final User user = userGroupProvider.getUserByIdentity(identity);
+        assertNotNull(user);
+        assertEquals(identity, user.getIdentifier());
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identity = "user-X";
+        final User user = userGroupProvider.getUserByIdentity(identity);
+        assertNull(user);
+    }
+
+    @Test
+    public void testDeleteUser() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        // retrieve user-1 and verify it exists
+        final User user = userGroupProvider.getUser("user-1");
+        assertEquals("user-1", user.getIdentifier());
+
+        // delete user-1
+        final User deletedUser = userGroupProvider.deleteUser(user);
+        assertNotNull(deletedUser);
+        assertEquals("user-1", deletedUser.getIdentifier());
+
+        // should be one less user
+        assertEquals(1, userGroupProvider.getUsers().size());
+        assertNull(userGroupProvider.getUser(user.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteUserWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        //user that doesn't exist
+        final User user = new User.Builder().identifier("user-X").identity("user-identity-X").build();
+
+        // should return null and still have 2 users because nothing was deleted
+        final User deletedUser = userGroupProvider.deleteUser(user);
+        assertNull(deletedUser);
+        assertEquals(2, userGroupProvider.getUsers().size());
+    }
+
+    @Test
+    public void testUpdateUserWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-1")
+                .identity("new-identity")
+                .build();
+
+        final User updatedUser = userGroupProvider.updateUser(user);
+        assertNotNull(updatedUser);
+        assertEquals(user.getIdentifier(), updatedUser.getIdentifier());
+        assertEquals(user.getIdentity(), updatedUser.getIdentity());
+    }
+
+    @Test
+    public void testUpdateUserWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-X")
+                .identity("new-identity")
+                .build();
+
+        final User updatedUser = userGroupProvider.updateUser(user);
+        assertNull(updatedUser);
+    }
+
+    // --------------- Group Tests ------------------------
+
+    @Test
+    public void testAddGroup() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-1")
+                .name("group-name-1")
+                .build();
+
+        final Group addedGroup = userGroupProvider.addGroup(group);
+        assertNotNull(addedGroup);
+        assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
+        assertEquals(group.getName(), addedGroup.getName());
+        assertEquals(0, addedGroup.getUsers().size());
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+    }
+
+    @Test
+    public void testAddGroupWithUser() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-XXX")
+                .name("group-name-XXX")
+                .addUser("user-1")
+                .build();
+
+        final Group addedGroup = userGroupProvider.addGroup(group);
+        assertNotNull(addedGroup);
+        assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
+        assertEquals(group.getName(), addedGroup.getName());
+        assertEquals(1, addedGroup.getUsers().size());
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(3, groups.size());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAddGroupWhenUserDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-1")
+                .name("group-name-1")
+                .addUser("user1")
+                .build();
+
+        userGroupProvider.addGroup(group);
+    }
+
+    @Test
+    public void testGetGroupByIdentifierWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final String identifier = "group-1";
+        final Group group = userGroupProvider.getGroup(identifier);
+        assertNotNull(group);
+        assertEquals(identifier, group.getIdentifier());
+    }
+
+    @Test
+    public void testGetGroupByIdentifierWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final String identifier = "group-X";
+        final Group group = userGroupProvider.getGroup(identifier);
+        assertNull(group);
+    }
+
+    @Test
+    public void testDeleteGroupWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // retrieve group-1
+        final Group group = userGroupProvider.getGroup("group-1");
+        assertEquals("group-1", group.getIdentifier());
+
+        // delete group-1
+        final Group deletedGroup = userGroupProvider.deleteGroup(group);
+        assertNotNull(deletedGroup);
+        assertEquals("group-1", deletedGroup.getIdentifier());
+
+        // verify there is one less overall group
+        assertEquals(1, userGroupProvider.getGroups().size());
+
+        // verify we can no longer retrieve group-1 by identifier
+        assertNull(userGroupProvider.getGroup(group.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteGroupWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-X")
+                .name("group-name-X")
+                .build();
+
+        final Group deletedGroup = userGroupProvider.deleteGroup(group);
+        assertNull(deletedGroup);
+        assertEquals(2, userGroupProvider.getGroups().size());
+    }
+
+    @Test
+    public void testUpdateGroupWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // verify user-1 is in group-1 before the update
+        final Group groupBefore = userGroupProvider.getGroup("group-1");
+        assertEquals(1, groupBefore.getUsers().size());
+        assertTrue(groupBefore.getUsers().contains("user-1"));
+
+        final Group group = new Group.Builder()
+                .identifier("group-1")
+                .name("new-name")
+                .addUser("user-2")
+                .build();
+
+        final Group updatedGroup = userGroupProvider.updateGroup(group);
+        assertEquals(group.getIdentifier(), updatedGroup.getIdentifier());
+        assertEquals(group.getName(), updatedGroup.getName());
+
+        assertEquals(1, updatedGroup.getUsers().size());
+        assertTrue(updatedGroup.getUsers().contains("user-2"));
+    }
+
+    @Test
+    public void testUpdateGroupWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-X")
+                .name("group-X")
+                .build();
+
+        final Group updatedGroup = userGroupProvider.updateGroup(group);
+        assertNull(updatedGroup);
+        assertEquals(2, userGroupProvider.getGroups().size());
+    }
+
+    private static void writeFile(final File file, final String content) throws Exception {
+        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
+        try (final FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        }
+    }
+
+    private static boolean deleteFile(final File file) {
+        if (file.isDirectory()) {
+            FileUtils.deleteFilesInDir(file, null, null, true, true);
+        }
+        return FileUtils.deleteFile(file, null, 10);
+    }
+
+    private NiFiProperties getNiFiProperties(final Properties properties) {
+        final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
+        when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames());
+
+        when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
+                return properties.getProperty((String)invocationOnMock.getArguments()[0]);
+            }
+        });
+        return nifiProperties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
index 344f49c..58343e8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
@@ -22,10 +22,15 @@ package org.apache.nifi.authorization;
 public class StandardAuthorizerInitializationContext implements AuthorizerInitializationContext {
 
     private final String identifier;
+    private final UserGroupProviderLookup userGroupProviderLookup;
+    private final AccessPolicyProviderLookup accessPolicyProviderLookup;
     private final AuthorizerLookup authorizerLookup;
 
-    public StandardAuthorizerInitializationContext(String identifier, AuthorizerLookup authorizerLookup) {
+    public StandardAuthorizerInitializationContext(String identifier, UserGroupProviderLookup userGroupProviderLookup,
+                                                   AccessPolicyProviderLookup accessPolicyProviderLookup, AuthorizerLookup authorizerLookup) {
         this.identifier = identifier;
+        this.userGroupProviderLookup = userGroupProviderLookup;
+        this.accessPolicyProviderLookup = accessPolicyProviderLookup;
         this.authorizerLookup = authorizerLookup;
     }
 
@@ -38,4 +43,13 @@ public class StandardAuthorizerInitializationContext implements AuthorizerInitia
         return authorizerLookup;
     }
 
+    @Override
+    public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
+        return accessPolicyProviderLookup;
+    }
+
+    @Override
+    public UserGroupProviderLookup getUserGroupProviderLookup() {
+        return userGroupProviderLookup;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
new file mode 100644
index 0000000..8e726f7
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+
+public class StandardManagedAuthorizer implements ManagedAuthorizer {
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
+
+    private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
+    private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider";
+
+    private AccessPolicyProviderLookup accessPolicyProviderLookup;
+    private AccessPolicyProvider accessPolicyProvider;
+    private UserGroupProvider userGroupProvider;
+
+    @Override
+    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+        accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup();
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+        final String accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider").getValue();
+        accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey);
+
+        // ensure the desired access policy provider was found
+        if (accessPolicyProvider == null) {
+            throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey));
+        }
+
+        userGroupProvider = accessPolicyProvider.getUserGroupProvider();
+
+        // ensure the desired access policy provider has a user group provider
+        if (userGroupProvider == null) {
+            throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey));
+        }
+    }
+
+    @Override
+    public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+        final String resourceIdentifier = request.getResource().getIdentifier();
+        final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction());
+        if (policy == null) {
+            return AuthorizationResult.resourceNotFound();
+        }
+
+        final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity());
+
+        final User user = userAndGroups.getUser();
+        if (user == null) {
+            return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity()));
+        }
+
+        final Set<Group> userGroups = userAndGroups.getGroups();
+        if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
+            return AuthorizationResult.approved();
+        }
+
+        return AuthorizationResult.denied(request.getExplanationSupplier().get());
+    }
+
+    /**
+     * Determines if the policy contains one of the user's groups.
+     *
+     * @param userGroups the set of the user's groups
+     * @param policy the policy
+     * @return true if one of the Groups in userGroups is contained in the policy
+     */
+    private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
+        if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) {
+            return false;
+        }
+
+        for (Group userGroup : userGroups) {
+            if (policy.getGroups().contains(userGroup.getIdentifier())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("managedAuthorizations");
+
+            writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT);
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint());
+            }
+            writer.writeEndElement();
+
+            writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT);
+            if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+                writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint());
+            }
+            writer.writeEndElement();
+
+            writer.writeEndElement();
+            writer.writeEndDocument();
+            writer.flush();
+        } catch (XMLStreamException e) {
+            throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (XMLStreamException e) {
+                    // nothing to do here
+                }
+            }
+        }
+
+        return out.toString();
+    }
+
+    @Override
+    public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        if (StringUtils.isBlank(fingerprint)) {
+            return;
+        }
+
+        final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint);
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+            ((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint());
+        }
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint());
+        }
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint);
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) {
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                ((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint());
+            } else {
+                throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting.");
+            }
+        }
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) {
+            if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+                ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint());
+            } else {
+                throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting.");
+            }
+        }
+    }
+
+    private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
+        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+
+        try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+            final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+            final Document document = docBuilder.parse(in);
+            final Element rootElement = document.getDocumentElement();
+
+            final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT);
+            if (accessPolicyProviderList.getLength() != 1) {
+                throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint));
+            }
+
+            final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT);
+            if (userGroupProviderList.getLength() != 1) {
+                throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint));
+            }
+
+            final Node accessPolicyProvider = accessPolicyProviderList.item(0);
+            final Node userGroupProvider = userGroupProviderList.item(0);
+            return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent());
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+        }
+    }
+
+    @Override
+    public AccessPolicyProvider getAccessPolicyProvider() {
+        return accessPolicyProvider;
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+
+    }
+
+    private static class FingerprintHolder {
+        private final String policyFingerprint;
+        private final String userGroupFingerprint;
+
+        public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) {
+            this.policyFingerprint = policyFingerprint;
+            this.userGroupFingerprint = userGroupFingerprint;
+        }
+
+        public String getPolicyFingerprint() {
+            return policyFingerprint;
+        }
+
+        public String getUserGroupFingerprint() {
+            return userGroupFingerprint;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
index 2a82795..8c1619a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
@@ -16,7 +16,11 @@
  */
 package org.apache.nifi.authorization.user;
 
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * An implementation of NiFiUser.
@@ -24,42 +28,20 @@ import java.util.Objects;
 public class StandardNiFiUser implements NiFiUser {
 
     public static final String ANONYMOUS_IDENTITY = "anonymous";
-    public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser(ANONYMOUS_IDENTITY, null, null, true);
+    public static final StandardNiFiUser ANONYMOUS = new Builder().identity(ANONYMOUS_IDENTITY).anonymous(true).build();
 
     private final String identity;
+    private final Set<String> groups;
     private final NiFiUser chain;
     private final String clientAddress;
     private final boolean isAnonymous;
 
-    public StandardNiFiUser(String identity) {
-        this(identity, null, null, false);
-    }
-
-    public StandardNiFiUser(String identity, String clientAddress) {
-        this(identity, null, clientAddress, false);
-    }
-
-    public StandardNiFiUser(String identity, NiFiUser chain) {
-        this(identity, chain, null, false);
-    }
-
-    public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
-        this(identity, chain, clientAddress, false);
-    }
-
-    /**
-     * This constructor is private as the only instance of this class which should have {@code isAnonymous} set to true is the singleton ANONYMOUS.
-     *
-     * @param identity      the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
-     * @param chain         the proxy chain that leads to this users
-     * @param clientAddress the source address of the request
-     * @param isAnonymous   true to represent the canonical "anonymous" user
-     */
-    private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
-        this.identity = identity;
-        this.chain = chain;
-        this.clientAddress = clientAddress;
-        this.isAnonymous = isAnonymous;
+    private StandardNiFiUser(final Builder builder) {
+        this.identity = builder.identity;
+        this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
+        this.chain = builder.chain;
+        this.clientAddress = builder.clientAddress;
+        this.isAnonymous = builder.isAnonymous;
     }
 
     /**
@@ -70,7 +52,7 @@ public class StandardNiFiUser implements NiFiUser {
      * @return an anonymous user instance with the identity "anonymous"
      */
     public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) {
-        return new StandardNiFiUser(ANONYMOUS_IDENTITY, chain, clientAddress, true);
+        return new Builder().identity(ANONYMOUS_IDENTITY).chain(chain).clientAddress(clientAddress).anonymous(true).build();
     }
 
     @Override
@@ -79,6 +61,11 @@ public class StandardNiFiUser implements NiFiUser {
     }
 
     @Override
+    public Set<String> getGroups() {
+        return groups;
+    }
+
+    @Override
     public NiFiUser getChain() {
         return chain;
     }
@@ -116,6 +103,87 @@ public class StandardNiFiUser implements NiFiUser {
 
     @Override
     public String toString() {
-        return String.format("identity[%s]", getIdentity());
+        final String formattedGroups;
+        if (groups == null) {
+            formattedGroups = "none";
+        } else {
+            formattedGroups = StringUtils.join(groups, ", ");
+        }
+
+        return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups);
+    }
+
+    /**
+     * Builder for a StandardNiFiUser
+     */
+    public static class Builder {
+
+        private String identity;
+        private Set<String> groups;
+        private NiFiUser chain;
+        private String clientAddress;
+        private boolean isAnonymous = false;
+
+        /**
+         * Sets the identity.
+         *
+         * @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
+         * @return the builder
+         */
+        public Builder identity(final String identity) {
+            this.identity = identity;
+            return this;
+        }
+
+        /**
+         * Sets the groups.
+         *
+         * @param groups the user groups
+         * @return the builder
+         */
+        public Builder groups(final Set<String> groups) {
+            this.groups = groups;
+            return this;
+        }
+
+        /**
+         * Sets the chain.
+         *
+         * @param chain the proxy chain that leads to this users
+         * @return the builder
+         */
+        public Builder chain(final NiFiUser chain) {
+            this.chain = chain;
+            return this;
+        }
+
+        /**
+         * Sets the client address.
+         *
+         * @param clientAddress the source address of the request
+         * @return the builder
+         */
+        public Builder clientAddress(final String clientAddress) {
+            this.clientAddress = clientAddress;
+            return this;
+        }
+
+        /**
+         * Sets whether this user is the canonical "anonymous" user
+         *
+         * @param isAnonymous true to represent the canonical "anonymous" user
+         * @return the builder
+         */
+        private Builder anonymous(final boolean isAnonymous) {
+            this.isAnonymous = isAnonymous;
+            return this;
+        }
+
+        /**
+         * @return builds a StandardNiFiUser from the current state of the builder
+         */
+        public StandardNiFiUser build() {
+            return new StandardNiFiUser(this);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
new file mode 100644
index 0000000..a4dc27e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization.util;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.ManagedAuthorizer;
+import org.apache.nifi.authorization.UserAndGroups;
+import org.apache.nifi.authorization.UserGroupProvider;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class UserGroupUtil {
+
+    /**
+     * Gets the groups for the user with the specified identity. Returns null if the authorizer is not able to load user groups.
+     *
+     * @param authorizer the authorizer to load the groups from
+     * @param userIdentity the user identity
+     * @return the listing of groups for the user
+     */
+    public static Set<String> getUserGroups(final Authorizer authorizer, final String userIdentity) {
+        if (authorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+            final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+            final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity);
+            final Set<Group> userGroups = userAndGroups.getGroups();
+
+            if (userGroups == null || userGroups.isEmpty()) {
+                return Collections.EMPTY_SET;
+            } else {
+                return userAndGroups.getGroups().stream().map(group -> group.getName()).collect(Collectors.toSet());
+            }
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
new file mode 100755
index 0000000..966a289
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.nifi.authorization.StandardManagedAuthorizer

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
new file mode 100644
index 0000000..a40c6f9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class StandardManagedAuthorizerTest {
+
+    private static final String EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+            + "<managedAuthorizations>"
+                + "<accessPolicyProvider></accessPolicyProvider>"
+                + "<userGroupProvider></userGroupProvider>"
+            + "</managedAuthorizations>";
+
+    private static final String NON_EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+            + "<managedAuthorizations>"
+                + "<accessPolicyProvider>"
+                    + "&lt;accessPolicies&gt;"
+                        + "&lt;policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\"&gt;"
+                            + "&lt;policyUser identifier=\"user-id-1\"&gt;&lt;/policyUser&gt;"
+                            + "&lt;policyGroup identifier=\"group-id-1\"&gt;&lt;/policyGroup&gt;"
+                        + "&lt;/policy&gt;"
+                    + "&lt;/accessPolicies&gt;"
+                + "</accessPolicyProvider>"
+                + "<userGroupProvider>"
+                    + "&lt;tenants&gt;"
+                        + "&lt;user identifier=\"user-id-1\" identity=\"user-1\"&gt;&lt;/user&gt;"
+                        + "&lt;group identifier=\"group-id-1\" name=\"group-1\"&gt;"
+                            + "&lt;groupUser identifier=\"user-id-1\"&gt;&lt;/groupUser&gt;"
+                        + "&lt;/group&gt;"
+                    + "&lt;/tenants&gt;"
+                + "</userGroupProvider>"
+            + "</managedAuthorizations>";
+
+    private static final String ACCESS_POLICY_FINGERPRINT =
+            "<accessPolicies>"
+                + "<policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\">"
+                    + "<policyUser identifier=\"user-id-1\"></policyUser>"
+                    + "<policyGroup identifier=\"group-id-1\"></policyGroup>"
+                + "</policy>"
+            + "</accessPolicies>";
+
+    private static final String TENANT_FINGERPRINT =
+            "<tenants>"
+                + "<user identifier=\"user-id-1\" identity=\"user-1\"></user>"
+                + "<group identifier=\"group-id-1\" name=\"group-1\">"
+                    + "<groupUser identifier=\"user-id-1\"></groupUser>"
+                + "</group>"
+            + "</tenants>";
+
+    private static final Resource TEST_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return "1";
+        }
+
+        @Override
+        public String getName() {
+            return "resource1";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "description1";
+        }
+    };
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testNullAccessPolicyProvider() throws Exception {
+        getStandardManagedAuthorizer(null);
+    }
+
+    @Test
+    public void testEmptyFingerPrint() throws Exception {
+        final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+        final AccessPolicyProvider accessPolicyProvider = mock(AccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        Assert.assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+    }
+
+    @Test
+    public void testNonEmptyFingerPrint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getFingerprint()).thenReturn(ACCESS_POLICY_FINGERPRINT);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        Assert.assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+    }
+
+    @Test
+    public void testInheritEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint(EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+        verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
+    }
+
+    @Test(expected = AuthorizationAccessException.class)
+    public void testInheritInvalidFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint("not a valid fingerprint");
+    }
+
+    @Test
+    public void testInheritNonEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT);
+        verify(accessPolicyProvider, times(1)).inheritFingerprint(ACCESS_POLICY_FINGERPRINT);
+    }
+
+    @Test
+    public void testCheckInheritEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability(EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+        verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
+    }
+
+    @Test(expected = AuthorizationAccessException.class)
+    public void testCheckInheritInvalidFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability("not a valid fingerprint");
+    }
+
+    @Test
+    public void testCheckInheritNonEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT);
+        verify(accessPolicyProvider, times(1)).checkInheritability(ACCESS_POLICY_FINGERPRINT);
+    }
+
+    @Test
+    public void testAuthorizationByUser() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser(userIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testAuthorizationByGroup() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+        final String groupIdentifier = "groupIdentifier1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final Group group = new Group.Builder()
+                .identifier(groupIdentifier)
+                .name(groupIdentifier)
+                .addUser(user.getIdentifier())
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addGroup(groupIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Stream.of(group).collect(Collectors.toSet());
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testResourceNotFound() throws Exception {
+        final String userIdentity = "userIdentity1";
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.resourceNotFound(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testUnauthorizedDueToUnknownUser() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+        final String notUser1Identity = "not userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser(userIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(notUser1Identity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return null;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(notUser1Identity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
+    }
+
+    @Test
+    public void testUnauthorizedDueToLackOfPermission() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser("userIdentity2")
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
+    }
+
+    private StandardManagedAuthorizer getStandardManagedAuthorizer(final AccessPolicyProvider accessPolicyProvider) {
+        final StandardManagedAuthorizer managedAuthorizer = new StandardManagedAuthorizer();
+
+        final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperty("Access Policy Provider")).thenReturn(new StandardPropertyValue("access-policy-provider", null));
+
+        final AccessPolicyProviderLookup accessPolicyProviderLookup = mock(AccessPolicyProviderLookup.class);
+        when(accessPolicyProviderLookup.getAccessPolicyProvider("access-policy-provider")).thenReturn(accessPolicyProvider);
+
+        final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+        when(initializationContext.getAccessPolicyProviderLookup()).thenReturn(accessPolicyProviderLookup);
+
+        managedAuthorizer.initialize(initializationContext);
+        managedAuthorizer.onConfigured(configurationContext);
+
+        return managedAuthorizer;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
index 069bf79..003835f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
@@ -16,14 +16,6 @@
  */
 package org.apache.nifi.authorization.resource;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.AuthorizationRequest;
 import org.apache.nifi.authorization.AuthorizationResult;
@@ -31,11 +23,19 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 public class DataAuthorizableTest {
 
     private static final String IDENTITY_1 = "identity-1";
@@ -83,20 +83,20 @@ public class DataAuthorizableTest {
 
     @Test(expected = AccessDeniedException.class)
     public void testAuthorizeUnauthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser("unknown");
+        final NiFiUser user = new Builder().identity("unknown").build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
     }
 
     @Test
     public void testCheckAuthorizationUnauthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser("unknown");
+        final NiFiUser user = new Builder().identity("unknown").build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
         assertEquals(Result.Denied, result.getResult());
     }
 
     @Test
     public void testAuthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        final NiFiUser user = new Builder().identity(IDENTITY_1).build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
 
         verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
@@ -109,7 +109,7 @@ public class DataAuthorizableTest {
 
     @Test
     public void testCheckAuthorizationUser() {
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        final NiFiUser user = new Builder().identity(IDENTITY_1).build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
 
         assertEquals(Result.Approved, result.getResult());
@@ -123,9 +123,9 @@ public class DataAuthorizableTest {
 
     @Test
     public void testAuthorizedUserChain() {
-        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
-        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
+        final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
+        final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
 
         verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class));
@@ -136,9 +136,9 @@ public class DataAuthorizableTest {
 
     @Test
     public void testCheckAuthorizationUserChain() {
-        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
-        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
+        final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
+        final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
 
         assertEquals(Result.Approved, result.getResult());

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index bbaeb26..2fc55a4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.cluster.coordination.http;
 
+import org.apache.nifi.cluster.coordination.http.endpoints.AccessPolicyEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.BulletinBoardEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
@@ -60,9 +61,14 @@ import org.apache.nifi.cluster.coordination.http.endpoints.RemoteProcessGroupsEn
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskTypesEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTasksEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.SearchUsersEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.StatusHistoryEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.SystemDiagnosticsEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.TemplatesEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
 import org.apache.nifi.cluster.manager.NodeResponse;
 import org.apache.nifi.stream.io.NullOutputStream;
@@ -141,6 +147,12 @@ public class StandardHttpResponseMapper implements HttpResponseMapper {
         endpointMergers.add(new FunnelEndpointMerger());
         endpointMergers.add(new FunnelsEndpointMerger());
         endpointMergers.add(new ControllerEndpointMerger());
+        endpointMergers.add(new UsersEndpointMerger());
+        endpointMergers.add(new UserEndpointMerger());
+        endpointMergers.add(new UserGroupsEndpointMerger());
+        endpointMergers.add(new UserGroupEndpointMerger());
+        endpointMergers.add(new AccessPolicyEndpointMerger());
+        endpointMergers.add(new SearchUsersEndpointMerger());
     }
 
     @Override


[06/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
new file mode 100644
index 0000000..59c829c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java
@@ -0,0 +1,820 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.authorization.file.tenants.generated.Groups;
+import org.apache.nifi.authorization.file.tenants.generated.Tenants;
+import org.apache.nifi.authorization.file.tenants.generated.Users;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.file.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);
+
+    private static final String TENANTS_XSD = "/tenants.xsd";
+    private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated";
+
+    private static final String USERS_XSD = "/legacy-users.xsd";
+    private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated";
+
+    private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);
+    private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH);
+
+    /**
+     * Load the JAXBContext.
+     */
+    private static JAXBContext initializeJaxbContext(final String contextPath) {
+        try {
+            return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext.");
+        }
+    }
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
+
+    private static final String USER_ELEMENT = "user";
+    private static final String GROUP_USER_ELEMENT = "groupUser";
+    private static final String GROUP_ELEMENT = "group";
+    private static final String IDENTIFIER_ATTR = "identifier";
+    private static final String IDENTITY_ATTR = "identity";
+    private static final String NAME_ATTR = "name";
+
+    static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
+    static final String PROP_TENANTS_FILE = "Users File";
+    static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
+
+    private Schema usersSchema;
+    private Schema tenantsSchema;
+    private NiFiProperties properties;
+    private File tenantsFile;
+    private File restoreTenantsFile;
+    private String legacyAuthorizedUsersFile;
+    private Set<String> initialUserIdentities;
+    private List<IdentityMapping> identityMappings;
+
+    private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
+
+    @Override
+    public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+        try {
+            final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
+            usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
+        } catch (Exception e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+        try {
+            final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
+            if (StringUtils.isBlank(tenantsPath.getValue())) {
+                throw new AuthorizerCreationException("The users file must be specified.");
+            }
+
+            // get the tenants file and ensure it exists
+            tenantsFile = new File(tenantsPath.getValue());
+            if (!tenantsFile.exists()) {
+                logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
+                saveTenants(new Tenants());
+            }
+
+            final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
+
+            // the restore directory is optional and may be null
+            final File restoreDirectory = properties.getRestoreDirectory();
+            if (restoreDirectory != null) {
+                // sanity check that restore directory is a directory, creating it if necessary
+                FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory);
+
+                // check that restore directory is not the same as the user's directory
+                if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
+                    throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ",
+                            tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
+                }
+
+                // the restore copy will have same file name, but reside in a different directory
+                restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName());
+
+                try {
+                    // sync the primary copy with the restore copy
+                    FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger);
+                } catch (final IOException | IllegalStateException ioe) {
+                    throw new AuthorizerCreationException(ioe);
+                }
+            }
+
+            // extract the identity mappings from nifi.properties if any are provided
+            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
+            // get the value of the legacy authorized users file
+            final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null;
+
+            // extract any node identities
+            initialUserIdentities = new HashSet<>();
+            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+                Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
+                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                    initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
+                }
+            }
+
+            load();
+
+            // if we've copied the authorizations file to a restore directory synchronize it
+            if (restoreTenantsFile != null) {
+                FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
+            }
+
+            logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
+        } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return userGroupHolder.get().getAllUsers();
+    }
+
+    @Override
+    public synchronized User addUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+        tenants.getUsers().getUser().add(jaxbUser);
+
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getUsersById().get(user.getIdentifier());
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        return holder.getUsersById().get(identifier);
+    }
+
+    @Override
+    public synchronized User updateUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
+
+        // fine the User that needs to be updated
+        org.apache.nifi.authorization.file.tenants.generated.User updateUser = null;
+        for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
+            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
+                updateUser = jaxbUser;
+                break;
+            }
+        }
+
+        // if user wasn't found return null, otherwise update the user and save changes
+        if (updateUser == null) {
+            return null;
+        } else {
+            updateUser.setIdentity(user.getIdentity());
+            saveAndRefreshHolder(tenants);
+
+            return userGroupHolder.get().getUsersById().get(user.getIdentifier());
+        }
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+        if (identity == null) {
+            return null;
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        return holder.getUsersByIdentity().get(identity);
+    }
+
+    @Override
+    public synchronized User deleteUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
+
+        // for each group iterate over the user references and remove the user reference if it matches the user being deleted
+        for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
+            Iterator<org.apache.nifi.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
+            while (groupUserIter.hasNext()) {
+                org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
+                if (groupUser.getIdentifier().equals(user.getIdentifier())) {
+                    groupUserIter.remove();
+                    break;
+                }
+            }
+        }
+
+        // remove the actual user if it exists
+        boolean removedUser = false;
+        Iterator<org.apache.nifi.authorization.file.tenants.generated.User> iter = users.iterator();
+        while (iter.hasNext()) {
+            org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next();
+            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
+                iter.remove();
+                removedUser = true;
+                break;
+            }
+        }
+
+        if (removedUser) {
+            saveAndRefreshHolder(tenants);
+            return user;
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return userGroupHolder.get().getAllGroups();
+    }
+
+    @Override
+    public synchronized Group addGroup(Group group) throws AuthorizationAccessException {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        // determine that all users in the group exist before doing anything, throw an exception if they don't
+        checkGroupUsers(group, tenants.getUsers().getUser());
+
+        // create a new JAXB Group based on the incoming Group
+        final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
+        jaxbGroup.setIdentifier(group.getIdentifier());
+        jaxbGroup.setName(group.getName());
+
+        // add each user to the group
+        for (String groupUser : group.getUsers()) {
+            org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
+            jaxbGroupUser.setIdentifier(groupUser);
+            jaxbGroup.getUser().add(jaxbGroupUser);
+        }
+
+        tenants.getGroups().getGroup().add(jaxbGroup);
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+        return userGroupHolder.get().getGroupsById().get(identifier);
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
+        final UserGroupHolder holder = userGroupHolder.get();
+        final User user = holder.getUser(identity);
+        final Set<Group> groups = holder.getGroups(identity);
+
+        return new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return groups;
+            }
+        };
+    }
+
+    @Override
+    public synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        // find the group that needs to be update
+        org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null;
+        for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
+            if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
+                updateGroup = jaxbGroup;
+                break;
+            }
+        }
+
+        // if the group wasn't found return null, otherwise update the group and save changes
+        if (updateGroup == null) {
+            return null;
+        }
+
+        // reset the list of users and add each user to the group
+        updateGroup.getUser().clear();
+        for (String groupUser : group.getUsers()) {
+            org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
+            jaxbGroupUser.setIdentifier(groupUser);
+            updateGroup.getUser().add(jaxbGroupUser);
+        }
+
+        updateGroup.setName(group.getName());
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
+    }
+
+    @Override
+    public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        final List<org.apache.nifi.authorization.file.tenants.generated.Group> groups = tenants.getGroups().getGroup();
+
+        // now remove the actual group from the top-level list of groups
+        boolean removedGroup = false;
+        Iterator<org.apache.nifi.authorization.file.tenants.generated.Group> iter = groups.iterator();
+        while (iter.hasNext()) {
+            org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
+            if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) {
+                iter.remove();
+                removedGroup = true;
+                break;
+            }
+        }
+
+        if (removedGroup) {
+            saveAndRefreshHolder(tenants);
+            return group;
+        } else {
+            return null;
+        }
+    }
+
+    UserGroupHolder getUserGroupHolder() {
+        return userGroupHolder.get();
+    }
+
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint);
+        usersAndGroups.getUsers().forEach(user -> addUser(user));
+        usersAndGroups.getGroups().forEach(group -> addGroup(group));
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException {
+        try {
+            // ensure we understand the proposed fingerprint
+            parseUsersAndGroups(proposedFingerprint);
+        } catch (final AuthorizationAccessException e) {
+            throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
+        }
+
+        final UserGroupHolder usersAndGroups = userGroupHolder.get();
+
+        // ensure we are in a proper state to inherit the fingerprint
+        if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) {
+            throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty.");
+        }
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        final UserGroupHolder usersAndGroups = userGroupHolder.get();
+
+        final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers());
+        Collections.sort(users, Comparator.comparing(User::getIdentifier));
+
+        final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups());
+        Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
+
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("tenants");
+
+            for (User user : users) {
+                writeUser(writer, user);
+            }
+            for (Group group : groups) {
+                writeGroup(writer, group);
+            }
+
+            writer.writeEndElement();
+            writer.writeEndDocument();
+            writer.flush();
+        } catch (XMLStreamException e) {
+            throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (XMLStreamException e) {
+                    // nothing to do here
+                }
+            }
+        }
+
+        return out.toString();
+    }
+
+    private UsersAndGroups parseUsersAndGroups(final String fingerprint) {
+        final List<User> users = new ArrayList<>();
+        final List<Group> groups = new ArrayList<>();
+
+        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+        try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+            final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+            final Document document = docBuilder.parse(in);
+            final Element rootElement = document.getDocumentElement();
+
+            // parse all the users and add them to the current user group provider
+            NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
+            for (int i=0; i < userNodes.getLength(); i++) {
+                Node userNode = userNodes.item(i);
+                users.add(parseUser((Element) userNode));
+            }
+
+            // parse all the groups and add them to the current user group provider
+            NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
+            for (int i=0; i < groupNodes.getLength(); i++) {
+                Node groupNode = groupNodes.item(i);
+                groups.add(parseGroup((Element) groupNode));
+            }
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+        }
+
+        return new UsersAndGroups(users, groups);
+    }
+
+    private User parseUser(final Element element) {
+        final User.Builder builder = new User.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .identity(element.getAttribute(IDENTITY_ATTR));
+
+        return builder.build();
+    }
+
+    private Group parseGroup(final Element element) {
+        final Group.Builder builder = new Group.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .name(element.getAttribute(NAME_ATTR));
+
+        NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
+        for (int i=0; i < groupUsers.getLength(); i++) {
+            Element groupUserNode = (Element) groupUsers.item(i);
+            builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        return builder.build();
+    }
+
+    private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
+        writer.writeStartElement(USER_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
+        writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
+        writer.writeEndElement();
+    }
+
+    private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
+        List<String> users = new ArrayList<>(group.getUsers());
+        Collections.sort(users);
+
+        writer.writeStartElement(GROUP_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
+        writer.writeAttribute(NAME_ATTR, group.getName());
+
+        for (String user : users) {
+            writer.writeStartElement(GROUP_USER_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, user);
+            writer.writeEndElement();
+        }
+
+        writer.writeEndElement();
+    }
+
+    private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) {
+        final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User();
+        jaxbUser.setIdentifier(user.getIdentifier());
+        jaxbUser.setIdentity(user.getIdentity());
+        return jaxbUser;
+    }
+
+    private Set<org.apache.nifi.authorization.file.tenants.generated.User> checkGroupUsers(final Group group, final List<org.apache.nifi.authorization.file.tenants.generated.User> users) {
+        final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = new HashSet<>();
+        for (String groupUser : group.getUsers()) {
+            boolean found = false;
+            for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
+                if (jaxbUser.getIdentifier().equals(groupUser)) {
+                    jaxbUsers.add(jaxbUser);
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist");
+            }
+        }
+        return jaxbUsers;
+    }
+
+    /**
+     * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
+     *
+     * @throws JAXBException            Unable to reload the authorized users file
+     * @throws IllegalStateException    Unable to sync file with restore
+     * @throws SAXException             Unable to unmarshall tenants
+     */
+    private synchronized void load() throws JAXBException, IllegalStateException, SAXException {
+        final Tenants tenants = unmarshallTenants();
+        if (tenants.getUsers() == null) {
+            tenants.setUsers(new Users());
+        }
+        if (tenants.getGroups() == null) {
+            tenants.setGroups(new Groups());
+        }
+
+        final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants);
+        final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty();
+        final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
+
+        if (emptyTenants) {
+            if (hasLegacyAuthorizedUsers) {
+                logger.info("Loading users from legacy model " + legacyAuthorizedUsersFile + " into new users file.");
+                convertLegacyAuthorizedUsers(tenants);
+            }
+
+            populateInitialUsers(tenants);
+
+            // save any changes that were made and repopulate the holder
+            saveAndRefreshHolder(tenants);
+        } else {
+            this.userGroupHolder.set(userGroupHolder);
+        }
+    }
+
+    private void saveTenants(final Tenants tenants) throws JAXBException {
+        final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
+        marshaller.setSchema(tenantsSchema);
+        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        marshaller.marshal(tenants, tenantsFile);
+    }
+
+    private Tenants unmarshallTenants() throws JAXBException {
+        final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(tenantsSchema);
+
+        final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
+        return element.getValue();
+    }
+
+    private void populateInitialUsers(final Tenants tenants) {
+        for (String initialUserIdentity : initialUserIdentities) {
+            getOrCreateUser(tenants, initialUserIdentity);
+        }
+    }
+
+    /**
+     * Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
+     *
+     * @param tenants the current Tenants instance users and groups will be added to
+     * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
+     * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
+     */
+    private void convertLegacyAuthorizedUsers(final Tenants tenants) throws AuthorizerCreationException, JAXBException {
+        final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
+        if (!authorizedUsersFile.exists()) {
+            throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
+        }
+
+        final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(usersSchema);
+
+        final JAXBElement<org.apache.nifi.user.generated.Users> element = unmarshaller.unmarshal(
+                new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class);
+
+        final org.apache.nifi.user.generated.Users users = element.getValue();
+        if (users.getUser().isEmpty()) {
+            logger.info("Legacy Authorized Users File contained no users, nothing to convert");
+            return;
+        }
+
+        for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
+            // create the identifier of the new user based on the DN
+            final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
+            org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn);
+
+            // if there was a group name find or create the group and add the user to it
+            org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup());
+            if (group != null) {
+                org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
+                groupUser.setIdentifier(user.getIdentifier());
+                group.getUser().add(groupUser);
+            }
+        }
+    }
+
+    /**
+     * Finds the User with the given identity, or creates a new one and adds it to the Tenants.
+     *
+     * @param tenants the Tenants reference
+     * @param userIdentity the user identity to find or create
+     * @return the User from Tenants with the given identity, or a new instance that was added to Tenants
+     */
+    private org.apache.nifi.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
+        if (StringUtils.isBlank(userIdentity)) {
+            return null;
+        }
+
+        org.apache.nifi.authorization.file.tenants.generated.User foundUser = null;
+        for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
+            if (user.getIdentity().equals(userIdentity)) {
+                foundUser = user;
+                break;
+            }
+        }
+
+        if (foundUser == null) {
+            final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity);
+            foundUser = new org.apache.nifi.authorization.file.tenants.generated.User();
+            foundUser.setIdentifier(userIdentifier);
+            foundUser.setIdentity(userIdentity);
+            tenants.getUsers().getUser().add(foundUser);
+        }
+
+        return foundUser;
+    }
+
+    /**
+     * Finds the Group with the given name, or creates a new one and adds it to Tenants.
+     *
+     * @param tenants the Tenants reference
+     * @param groupName the name of the group to look for
+     * @return the Group from Tenants with the given name, or a new instance that was added to Tenants
+     */
+    private org.apache.nifi.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
+        if (StringUtils.isBlank(groupName)) {
+            return null;
+        }
+
+        org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null;
+        for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
+            if (group.getName().equals(groupName)) {
+                foundGroup = group;
+                break;
+            }
+        }
+
+        if (foundGroup == null) {
+            final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName);
+            foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
+            foundGroup.setIdentifier(newGroupIdentifier);
+            foundGroup.setName(groupName);
+            tenants.getGroups().getGroup().add(foundGroup);
+        }
+
+        return foundGroup;
+    }
+
+    /**
+     * Saves the Authorizations instance by marshalling to a file, then re-populates the
+     * in-memory data structures and sets the new holder.
+     *
+     * Synchronized to ensure only one thread writes the file at a time.
+     *
+     * @param tenants the tenants to save and populate from
+     * @throws AuthorizationAccessException if an error occurs saving the authorizations
+     */
+    private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException {
+        try {
+            saveTenants(tenants);
+
+            this.userGroupHolder.set(new UserGroupHolder(tenants));
+        } catch (JAXBException e) {
+            throw new AuthorizationAccessException("Unable to save Authorizations", e);
+        }
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+
+    private static class UsersAndGroups {
+        final List<User> users;
+        final List<Group> groups;
+
+        public UsersAndGroups(List<User> users, List<Group> groups) {
+            this.users = users;
+            this.groups = groups;
+        }
+
+        public List<User> getUsers() {
+            return users;
+        }
+
+        public List<Group> getGroups() {
+            return groups;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
new file mode 100644
index 0000000..dfd6700
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/IdentifierUtil.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.util.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+public final class IdentifierUtil {
+
+    static String getIdentifier(final String seed) {
+        if (StringUtils.isBlank(seed)) {
+            return null;
+        }
+
+        return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+    }
+
+    private IdentifierUtil() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java
new file mode 100644
index 0000000..44cedd8
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/UserGroupHolder.java
@@ -0,0 +1,239 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+
+import org.apache.nifi.authorization.file.tenants.generated.Groups;
+import org.apache.nifi.authorization.file.tenants.generated.Tenants;
+import org.apache.nifi.authorization.file.tenants.generated.Users;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A holder to provide atomic access to user group data structures.
+ */
+public class UserGroupHolder {
+
+    private final Tenants tenants;
+
+    private final Set<User> allUsers;
+    private final Map<String,User> usersById;
+    private final Map<String,User> usersByIdentity;
+
+    private final Set<Group> allGroups;
+    private final Map<String,Group> groupsById;
+    private final Map<String, Set<Group>> groupsByUserIdentity;
+
+    /**
+     * Creates a new holder and populates all convenience data structures.
+     *
+     * @param tenants the current tenants instance
+     */
+    public UserGroupHolder(final Tenants tenants) {
+        this.tenants = tenants;
+
+        // load all users
+        final Users users = tenants.getUsers();
+        final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
+
+        // load all groups
+        final Groups groups = tenants.getGroups();
+        final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
+
+        // create a convenience map to retrieve a user by id
+        final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
+
+        // create a convenience map to retrieve a user by identity
+        final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
+
+        // create a convenience map to retrieve a group by id
+        final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
+
+        // create a convenience map to retrieve the groups for a user identity
+        final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
+
+        // set all the holders
+        this.allUsers = allUsers;
+        this.allGroups = allGroups;
+        this.usersById = userByIdMap;
+        this.usersByIdentity = userByIdentityMap;
+        this.groupsById = groupByIdMap;
+        this.groupsByUserIdentity = groupsByUserIdentityMap;
+    }
+
+    /**
+     * Creates a set of Users from the JAXB Users.
+     *
+     * @param users the JAXB Users
+     * @return a set of API Users matching the provided JAXB Users
+     */
+    private Set<User> createUsers(Users users) {
+        Set<User> allUsers = new HashSet<>();
+        if (users == null || users.getUser() == null) {
+            return allUsers;
+        }
+
+        for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) {
+            final User.Builder builder = new User.Builder()
+                    .identity(user.getIdentity())
+                    .identifier(user.getIdentifier());
+
+            allUsers.add(builder.build());
+        }
+
+        return allUsers;
+    }
+
+    /**
+     * Creates a set of Groups from the JAXB Groups.
+     *
+     * @param groups the JAXB Groups
+     * @return a set of API Groups matching the provided JAXB Groups
+     */
+    private Set<Group> createGroups(Groups groups,
+                                    Users users) {
+        Set<Group> allGroups = new HashSet<>();
+        if (groups == null || groups.getGroup() == null) {
+            return allGroups;
+        }
+
+        for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) {
+            final Group.Builder builder = new Group.Builder()
+                    .identifier(group.getIdentifier())
+                    .name(group.getName());
+
+            for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
+                builder.addUser(groupUser.getIdentifier());
+            }
+
+            allGroups.add(builder.build());
+        }
+
+        return allGroups;
+    }
+
+    /**
+     * Creates a Map from user identifier to User.
+     *
+     * @param users the set of all users
+     * @return the Map from user identifier to User
+     */
+    private Map<String,User> createUserByIdMap(final Set<User> users) {
+        Map<String,User> usersMap = new HashMap<>();
+        for (User user : users) {
+            usersMap.put(user.getIdentifier(), user);
+        }
+        return usersMap;
+    }
+
+    /**
+     * Creates a Map from user identity to User.
+     *
+     * @param users the set of all users
+     * @return the Map from user identity to User
+     */
+    private Map<String,User> createUserByIdentityMap(final Set<User> users) {
+        Map<String,User> usersMap = new HashMap<>();
+        for (User user : users) {
+            usersMap.put(user.getIdentity(), user);
+        }
+        return usersMap;
+    }
+
+    /**
+     * Creates a Map from group identifier to Group.
+     *
+     * @param groups the set of all groups
+     * @return the Map from group identifier to Group
+     */
+    private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
+        Map<String,Group> groupsMap = new HashMap<>();
+        for (Group group : groups) {
+            groupsMap.put(group.getIdentifier(), group);
+        }
+        return groupsMap;
+    }
+
+    /**
+     * Creates a Map from user identity to the set of Groups for that identity.
+     *
+     * @param groups all groups
+     * @param users all users
+     * @return a Map from User identity to the set of Groups for that identity
+     */
+    private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
+        Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
+
+        for (User user : users) {
+            Set<Group> userGroups = new HashSet<>();
+            for (Group group : groups) {
+                for (String groupUser : group.getUsers()) {
+                    if (groupUser.equals(user.getIdentifier())) {
+                        userGroups.add(group);
+                    }
+                }
+            }
+
+            groupsByUserIdentity.put(user.getIdentity(), userGroups);
+        }
+
+        return groupsByUserIdentity;
+    }
+
+    public Tenants getTenants() {
+        return tenants;
+    }
+
+    public Set<User> getAllUsers() {
+        return allUsers;
+    }
+
+    public Map<String, User> getUsersById() {
+        return usersById;
+    }
+
+    public Map<String, User> getUsersByIdentity() {
+        return usersByIdentity;
+    }
+
+    public Set<Group> getAllGroups() {
+        return allGroups;
+    }
+
+    public Map<String, Group> getGroupsById() {
+        return groupsById;
+    }
+
+    public User getUser(String identity) {
+        if (identity == null) {
+            throw new IllegalArgumentException("Identity cannot be null");
+        }
+        return usersByIdentity.get(identity);
+    }
+
+    public Set<Group> getGroups(String userIdentity) {
+        if (userIdentity == null) {
+            throw new IllegalArgumentException("User Identity cannot be null");
+        }
+        return groupsByUserIdentity.get(userIdentity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider
new file mode 100755
index 0000000..a4e735b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.AccessPolicyProvider
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.nifi.authorization.FileAccessPolicyProvider

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
new file mode 100755
index 0000000..2e1e407
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
@@ -0,0 +1,15 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+org.apache.nifi.authorization.FileUserGroupProvider


[08/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
new file mode 100644
index 0000000..653a949
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java
@@ -0,0 +1,947 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.authorization.file.generated.Authorizations;
+import org.apache.nifi.authorization.file.generated.Policies;
+import org.apache.nifi.authorization.file.generated.Policy;
+import org.apache.nifi.authorization.resource.ResourceFactory;
+import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.user.generated.Users;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.file.FileUtils;
+import org.apache.nifi.web.api.dto.PortDTO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class);
+
+    private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
+    private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated";
+
+    private static final String USERS_XSD = "/legacy-users.xsd";
+    private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated";
+
+    private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH);
+    private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH);
+
+    /**
+     * Load the JAXBContext.
+     */
+    private static JAXBContext initializeJaxbContext(final String contextPath) {
+        try {
+            return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext.");
+        }
+    }
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
+
+    private static final String POLICY_ELEMENT = "policy";
+    private static final String POLICY_USER_ELEMENT = "policyUser";
+    private static final String POLICY_GROUP_ELEMENT = "policyGroup";
+    private static final String IDENTIFIER_ATTR = "identifier";
+    private static final String RESOURCE_ATTR = "resource";
+    private static final String ACTIONS_ATTR = "actions";
+
+    static final String READ_CODE = "R";
+    static final String WRITE_CODE = "W";
+
+    static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity ";
+    static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
+    static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
+    static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
+    static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+");
+
+    private Schema usersSchema;
+    private Schema authorizationsSchema;
+    private NiFiProperties properties;
+    private File authorizationsFile;
+    private File restoreAuthorizationsFile;
+    private String rootGroupId;
+    private String initialAdminIdentity;
+    private String legacyAuthorizedUsersFile;
+    private Set<String> nodeIdentities;
+    private List<PortDTO> ports = new ArrayList<>();
+    private List<IdentityMapping> identityMappings;
+
+    private UserGroupProvider userGroupProvider;
+    private UserGroupProviderLookup userGroupProviderLookup;
+    private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
+
+    @Override
+    public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+        userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
+
+        try {
+            final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
+            usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
+        } catch (Exception e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+        try {
+            final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
+            if (!userGroupProviderIdentifier.isSet()) {
+                throw new AuthorizerCreationException("The user group provider must be specified.");
+            }
+
+            userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
+            if (userGroupProvider == null) {
+                throw new AuthorizerCreationException("Unable to locate user group provider with identifier " + userGroupProviderIdentifier.getValue());
+            }
+
+            final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
+            if (StringUtils.isBlank(authorizationsPath.getValue())) {
+                throw new AuthorizerCreationException("The authorizations file must be specified.");
+            }
+
+            // get the authorizations file and ensure it exists
+            authorizationsFile = new File(authorizationsPath.getValue());
+            if (!authorizationsFile.exists()) {
+                logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()});
+                saveAuthorizations(new Authorizations());
+            }
+
+            final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile();
+
+            // the restore directory is optional and may be null
+            final File restoreDirectory = properties.getRestoreDirectory();
+            if (restoreDirectory != null) {
+                // sanity check that restore directory is a directory, creating it if necessary
+                FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory);
+
+                // check that restore directory is not the same as the authorizations directory
+                if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
+                    throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ",
+                            authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
+                }
+
+                // the restore copy will have same file name, but reside in a different directory
+                restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName());
+
+                try {
+                    // sync the primary copy with the restore copy
+                    FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger);
+                } catch (final IOException | IllegalStateException ioe) {
+                    throw new AuthorizerCreationException(ioe);
+                }
+            }
+
+            // extract the identity mappings from nifi.properties if any are provided
+            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
+            // get the value of the initial admin identity
+            final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
+            initialAdminIdentity = initialAdminIdentityProp.isSet() ? IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings) : null;
+
+            // get the value of the legacy authorized users file
+            final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null;
+
+            // extract any node identities
+            nodeIdentities = new HashSet<>();
+            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+                Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
+                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                    nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
+                }
+            }
+
+            // load the authorizations
+            load();
+
+            // if we've copied the authorizations file to a restore directory synchronize it
+            if (restoreAuthorizationsFile != null) {
+                FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger);
+            }
+
+            logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
+        } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public UserGroupProvider getUserGroupProvider() {
+        return userGroupProvider;
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+        return authorizationsHolder.get().getAllPolicies();
+    }
+
+    @Override
+    public synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        // create the new JAXB Policy
+        final Policy policy = createJAXBPolicy(accessPolicy);
+
+        // add the new Policy to the top-level list of policies
+        final AuthorizationsHolder holder = authorizationsHolder.get();
+        final Authorizations authorizations = holder.getAuthorizations();
+        authorizations.getPolicies().getPolicy().add(policy);
+
+        saveAndRefreshHolder(authorizations);
+
+        return authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+
+        final AuthorizationsHolder holder = authorizationsHolder.get();
+        return holder.getPoliciesById().get(identifier);
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+        return authorizationsHolder.get().getAccessPolicy(resourceIdentifier, action);
+    }
+
+    @Override
+    public synchronized AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        final AuthorizationsHolder holder = this.authorizationsHolder.get();
+        final Authorizations authorizations = holder.getAuthorizations();
+
+        // try to find an existing Authorization that matches the policy id
+        Policy updatePolicy = null;
+        for (Policy policy : authorizations.getPolicies().getPolicy()) {
+            if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
+                updatePolicy = policy;
+                break;
+            }
+        }
+
+        // no matching Policy so return null
+        if (updatePolicy == null) {
+            return null;
+        }
+
+        // update the Policy, save, reload, and return
+        transferUsersAndGroups(accessPolicy, updatePolicy);
+        saveAndRefreshHolder(authorizations);
+
+        return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
+    }
+
+    @Override
+    public synchronized AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        final AuthorizationsHolder holder = this.authorizationsHolder.get();
+        final Authorizations authorizations = holder.getAuthorizations();
+
+        // find the matching Policy and remove it
+        boolean deletedPolicy = false;
+        Iterator<Policy> policyIter = authorizations.getPolicies().getPolicy().iterator();
+        while (policyIter.hasNext()) {
+            final Policy policy = policyIter.next();
+            if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
+                policyIter.remove();
+                deletedPolicy = true;
+                break;
+            }
+        }
+
+        // never found a matching Policy so return null
+        if (!deletedPolicy) {
+            return null;
+        }
+
+        saveAndRefreshHolder(authorizations);
+        return accessPolicy;
+    }
+
+    AuthorizationsHolder getAuthorizationsHolder() {
+        return authorizationsHolder.get();
+    }
+
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        parsePolicies(fingerprint).forEach(policy -> addAccessPolicy(policy));
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        try {
+            // ensure we can understand the proposed fingerprint
+            parsePolicies(proposedFingerprint);
+        } catch (final AuthorizationAccessException e) {
+            throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
+        }
+
+        // ensure we are in a proper state to inherit the fingerprint
+        if (!getAccessPolicies().isEmpty()) {
+            throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current access policies is not empty.");
+        }
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
+        Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
+
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("accessPolicies");
+
+            for (AccessPolicy policy : policies) {
+                writePolicy(writer, policy);
+            }
+
+            writer.writeEndElement();
+            writer.writeEndDocument();
+            writer.flush();
+        } catch (XMLStreamException e) {
+            throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (XMLStreamException e) {
+                    // nothing to do here
+                }
+            }
+        }
+
+        return out.toString();
+    }
+
+    private List<AccessPolicy> parsePolicies(final String fingerprint) {
+        final List<AccessPolicy> policies = new ArrayList<>();
+
+        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+        try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+            final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+            final Document document = docBuilder.parse(in);
+            final Element rootElement = document.getDocumentElement();
+
+            // parse all the policies and add them to the current access policy provider
+            NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
+            for (int i = 0; i < policyNodes.getLength(); i++) {
+                Node policyNode = policyNodes.item(i);
+                policies.add(parsePolicy((Element) policyNode));
+            }
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+        }
+
+        return policies;
+    }
+
+    private AccessPolicy parsePolicy(final Element element) {
+        final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .resource(element.getAttribute(RESOURCE_ATTR));
+
+        final String actions = element.getAttribute(ACTIONS_ATTR);
+        if (actions.equals(RequestAction.READ.name())) {
+            builder.action(RequestAction.READ);
+        } else if (actions.equals(RequestAction.WRITE.name())) {
+            builder.action(RequestAction.WRITE);
+        } else {
+            throw new IllegalStateException("Unknown Policy Action: " + actions);
+        }
+
+        NodeList policyUsers = element.getElementsByTagName(POLICY_USER_ELEMENT);
+        for (int i=0; i < policyUsers.getLength(); i++) {
+            Element policyUserNode = (Element) policyUsers.item(i);
+            builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        NodeList policyGroups = element.getElementsByTagName(POLICY_GROUP_ELEMENT);
+        for (int i=0; i < policyGroups.getLength(); i++) {
+            Element policyGroupNode = (Element) policyGroups.item(i);
+            builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        return builder.build();
+    }
+
+    private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy) throws XMLStreamException {
+        // sort the users for the policy
+        List<String> policyUsers = new ArrayList<>(policy.getUsers());
+        Collections.sort(policyUsers);
+
+        // sort the groups for this policy
+        List<String> policyGroups = new ArrayList<>(policy.getGroups());
+        Collections.sort(policyGroups);
+
+        writer.writeStartElement(POLICY_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier());
+        writer.writeAttribute(RESOURCE_ATTR, policy.getResource());
+        writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name());
+
+        for (String policyUser : policyUsers) {
+            writer.writeStartElement(POLICY_USER_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, policyUser);
+            writer.writeEndElement();
+        }
+
+        for (String policyGroup : policyGroups) {
+            writer.writeStartElement(POLICY_GROUP_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, policyGroup);
+            writer.writeEndElement();
+        }
+
+        writer.writeEndElement();
+    }
+
+    /**
+     * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
+     *
+     * @throws JAXBException            Unable to reload the authorized users file
+     * @throws IOException              Unable to sync file with restore
+     * @throws IllegalStateException    Unable to sync file with restore
+     */
+    private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException {
+        // attempt to unmarshal
+        final Authorizations authorizations = unmarshallAuthorizations();
+        if (authorizations.getPolicies() == null) {
+            authorizations.setPolicies(new Policies());
+        }
+
+        final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations);
+        final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty();
+        final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity));
+        final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
+
+        // if we are starting fresh then we might need to populate an initial admin or convert legacy users
+        if (emptyAuthorizations) {
+            parseFlow();
+
+            if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) {
+                throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File");
+            } else if (hasInitialAdminIdentity) {
+                logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity);
+                populateInitialAdmin(authorizations);
+            } else if (hasLegacyAuthorizedUsers) {
+                logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model");
+                convertLegacyAuthorizedUsers(authorizations);
+            }
+
+            populateNodes(authorizations);
+
+            // save any changes that were made and repopulate the holder
+            saveAndRefreshHolder(authorizations);
+        } else {
+            this.authorizationsHolder.set(authorizationsHolder);
+        }
+    }
+
+    private void saveAuthorizations(final Authorizations authorizations) throws JAXBException {
+        final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
+        marshaller.setSchema(authorizationsSchema);
+        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        marshaller.marshal(authorizations, authorizationsFile);
+    }
+
+    private Authorizations unmarshallAuthorizations() throws JAXBException {
+        final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(authorizationsSchema);
+
+        final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class);
+        return element.getValue();
+    }
+
+    /**
+     * Try to parse the flow configuration file to extract the root group id and port information.
+     *
+     * @throws SAXException if an error occurs creating the schema
+     */
+    private void parseFlow() throws SAXException {
+        final FlowParser flowParser = new FlowParser();
+        final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile());
+
+        if (flowInfo != null) {
+            rootGroupId = flowInfo.getRootGroupId();
+            ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts();
+        }
+    }
+
+    /**
+     *  Creates the initial admin user and policies for access the flow and managing users and policies.
+     */
+    private void populateInitialAdmin(final Authorizations authorizations) {
+        final User initialAdmin = userGroupProvider.getUserByIdentity(initialAdminIdentity);
+        if (initialAdmin == null) {
+            throw new AuthorizerCreationException("Unable to locate initial admin " + initialAdminIdentity + " to seed policies");
+        }
+
+        // grant the user read access to the /flow resource
+        addUserToAccessPolicy(authorizations, ResourceType.Flow.getValue(), initialAdmin.getIdentifier(), READ_CODE);
+
+        // grant the user read access to the root process group resource
+        if (rootGroupId != null) {
+            addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE);
+            addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE);
+
+            addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE);
+            addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE);
+        }
+
+        // grant the user write to restricted components
+        addUserToAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
+
+        // grant the user read/write access to the /tenants resource
+        addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), READ_CODE);
+        addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
+
+        // grant the user read/write access to the /policies resource
+        addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), READ_CODE);
+        addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
+
+        // grant the user read/write access to the /controller resource
+        addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), READ_CODE);
+        addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
+    }
+
+    /**
+     * Creates a user for each node and gives the nodes write permission to /proxy.
+     *
+     * @param authorizations the overall authorizations
+     */
+    private void populateNodes(Authorizations authorizations) {
+        for (String nodeIdentity : nodeIdentities) {
+            final User node = userGroupProvider.getUserByIdentity(nodeIdentity);
+            if (node == null) {
+                throw new AuthorizerCreationException("Unable to locate node " + nodeIdentity + " to seed policies.");
+            }
+
+            // grant access to the proxy resource
+            addUserToAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.getIdentifier(), WRITE_CODE);
+
+            // grant the user read/write access data of the root group
+            if (rootGroupId != null) {
+                addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), READ_CODE);
+                addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE);
+            }
+        }
+    }
+
+    /**
+     * Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
+     *
+     * @param authorizations the current Authorizations instance that policies will be added to
+     * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
+     * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
+     */
+    private void convertLegacyAuthorizedUsers(final Authorizations authorizations) throws AuthorizerCreationException, JAXBException {
+        final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
+        if (!authorizedUsersFile.exists()) {
+            throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
+        }
+
+        final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(usersSchema);
+
+        final JAXBElement<Users> element = unmarshaller.unmarshal(
+                new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class);
+
+        final org.apache.nifi.user.generated.Users users = element.getValue();
+        if (users.getUser().isEmpty()) {
+            logger.info("Legacy Authorized Users File contained no users, nothing to convert");
+            return;
+        }
+
+        // get all the user DNs into a list
+        List<String> userIdentities = new ArrayList<>();
+        for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
+            userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings));
+        }
+
+        // sort the list and pull out the first identity
+        Collections.sort(userIdentities);
+        final String seedIdentity = userIdentities.get(0);
+
+        // create mapping from Role to access policies
+        final Map<Role,Set<RoleAccessPolicy>> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId);
+
+        final List<Policy> allPolicies = new ArrayList<>();
+
+        for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
+            // create the identifier of the new user based on the DN
+            final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
+            final User user = userGroupProvider.getUserByIdentity(legacyUserDn);
+            if (user == null) {
+                throw new AuthorizerCreationException("Unable to locate legacy user " + legacyUserDn + " to seed policies.");
+            }
+
+            // create policies based on the given role
+            for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) {
+                Role role = Role.valueOf(jaxbRole.getName());
+                Set<RoleAccessPolicy> policies = roleAccessPolicies.get(role);
+
+                for (RoleAccessPolicy roleAccessPolicy : policies) {
+
+                    // get the matching policy, or create a new one
+                    Policy policy = getOrCreatePolicy(
+                            allPolicies,
+                            seedIdentity,
+                            roleAccessPolicy.getResource(),
+                            roleAccessPolicy.getAction());
+
+                    // add the user to the policy if it doesn't exist
+                    addUserToPolicy(user.getIdentifier(), policy);
+                }
+            }
+
+        }
+
+        // convert any access controls on ports to the appropriate policies
+        for (PortDTO portDTO : ports) {
+            final Resource resource;
+            if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) {
+                resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName()));
+            } else {
+                resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName()));
+            }
+
+            if (portDTO.getUserAccessControl() != null) {
+                for (String userAccessControl : portDTO.getUserAccessControl()) {
+                    // need to perform the identity mapping on the access control so it matches the identities in the User objects
+                    final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings);
+                    final User foundUser = userGroupProvider.getUserByIdentity(mappedUserAccessControl);
+
+                    // couldn't find the user matching the access control so log a warning and skip
+                    if (foundUser == null) {
+                        logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...",
+                                new Object[] {mappedUserAccessControl});
+                        continue;
+                    }
+
+                    // we found the user so create the appropriate policy and add the user to it
+                    Policy policy = getOrCreatePolicy(
+                            allPolicies,
+                            seedIdentity,
+                            resource.getIdentifier(),
+                            WRITE_CODE);
+
+                    addUserToPolicy(foundUser.getIdentifier(), policy);
+                }
+            }
+
+            if (portDTO.getGroupAccessControl() != null) {
+                for (String groupAccessControl : portDTO.getGroupAccessControl()) {
+                    // find a group where the name is the groupAccessControl
+                    Group foundGroup = null;
+                    for (Group group : userGroupProvider.getGroups()) {
+                        if (group.getName().equals(groupAccessControl)) {
+                            foundGroup = group;
+                            break;
+                        }
+                    }
+
+                    // couldn't find the group matching the access control so log a warning and skip
+                    if (foundGroup == null) {
+                        logger.warn("Found port with group access control for {} but no group exists with this name, skipping...",
+                                new Object[] {groupAccessControl});
+                        continue;
+                    }
+
+                    // we found the group so create the appropriate policy and add all the users to it
+                    Policy policy = getOrCreatePolicy(
+                            allPolicies,
+                            seedIdentity,
+                            resource.getIdentifier(),
+                            WRITE_CODE);
+
+                    addGroupToPolicy(IdentifierUtil.getIdentifier(groupAccessControl), policy);
+                }
+            }
+        }
+
+        authorizations.getPolicies().getPolicy().addAll(allPolicies);
+    }
+
+    /**
+     * Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations.
+     *
+     * @param authorizations the Authorizations instance to add the policy to
+     * @param resource the resource for the policy
+     * @param userIdentifier the identifier for the user to add to the policy
+     * @param action the action for the policy
+     */
+    private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String action) {
+        // first try to find an existing policy for the given resource and action
+        Policy foundPolicy = null;
+        for (Policy policy : authorizations.getPolicies().getPolicy()) {
+            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
+                foundPolicy = policy;
+                break;
+            }
+        }
+
+        if (foundPolicy == null) {
+            // if we didn't find an existing policy create a new one
+            final String uuidSeed = resource + action;
+
+            final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                    .identifierGenerateFromSeed(uuidSeed)
+                    .resource(resource)
+                    .addUser(userIdentifier);
+
+            if (action.equals(READ_CODE)) {
+                builder.action(RequestAction.READ);
+            } else if (action.equals(WRITE_CODE)) {
+                builder.action(RequestAction.WRITE);
+            } else {
+                throw new IllegalStateException("Unknown Policy Action: " + action);
+            }
+
+            final AccessPolicy accessPolicy = builder.build();
+            final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
+            authorizations.getPolicies().getPolicy().add(jaxbPolicy);
+        } else {
+            // otherwise add the user to the existing policy
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            foundPolicy.getUser().add(policyUser);
+        }
+    }
+
+    private Policy createJAXBPolicy(final AccessPolicy accessPolicy) {
+        final Policy policy = new Policy();
+        policy.setIdentifier(accessPolicy.getIdentifier());
+        policy.setResource(accessPolicy.getResource());
+
+        switch (accessPolicy.getAction()) {
+            case READ:
+                policy.setAction(READ_CODE);
+                break;
+            case WRITE:
+                policy.setAction(WRITE_CODE);
+                break;
+            default:
+                break;
+        }
+
+        transferUsersAndGroups(accessPolicy, policy);
+        return policy;
+    }
+
+    /**
+     * Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and
+     * set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy.
+     *
+     * Does not set the identifier.
+     *
+     * @param accessPolicy the AccessPolicy to transfer state from
+     * @param policy the Policy to transfer state to
+     */
+    private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) {
+        // add users to the policy
+        policy.getUser().clear();
+        for (String userIdentifier : accessPolicy.getUsers()) {
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            policy.getUser().add(policyUser);
+        }
+
+        // add groups to the policy
+        policy.getGroup().clear();
+        for (String groupIdentifier : accessPolicy.getGroups()) {
+            Policy.Group policyGroup = new Policy.Group();
+            policyGroup.setIdentifier(groupIdentifier);
+            policy.getGroup().add(policyGroup);
+        }
+    }
+
+    /**
+     * Adds the given user identifier to the policy if it doesn't already exist.
+     *
+     * @param userIdentifier a user identifier
+     * @param policy a policy to add the user to
+     */
+    private void addUserToPolicy(final String userIdentifier, final Policy policy) {
+        // determine if the user already exists in the policy
+        boolean userExists = false;
+        for (Policy.User policyUser : policy.getUser()) {
+            if (policyUser.getIdentifier().equals(userIdentifier)) {
+                userExists = true;
+                break;
+            }
+        }
+
+        // add the user to the policy if doesn't already exist
+        if (!userExists) {
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            policy.getUser().add(policyUser);
+        }
+    }
+
+    /**
+     * Adds the given group identifier to the policy if it doesn't already exist.
+     *
+     * @param groupIdentifier a group identifier
+     * @param policy a policy to add the user to
+     */
+    private void addGroupToPolicy(final String groupIdentifier, final Policy policy) {
+        // determine if the group already exists in the policy
+        boolean groupExists = false;
+        for (Policy.Group policyGroup : policy.getGroup()) {
+            if (policyGroup.getIdentifier().equals(groupIdentifier)) {
+                groupExists = true;
+                break;
+            }
+        }
+
+        // add the group to the policy if doesn't already exist
+        if (!groupExists) {
+            Policy.Group policyGroup = new Policy.Group();
+            policyGroup.setIdentifier(groupIdentifier);
+            policy.getGroup().add(policyGroup);
+        }
+    }
+
+    /**
+     * Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies.
+     *
+     * @param policies the policies to search through
+     * @param seedIdentity the seedIdentity to use when creating identifiers for new policies
+     * @param resource the resource for the policy
+     * @param action the action string for the police (R or RW)
+     * @return the matching policy or a new policy
+     */
+    private Policy getOrCreatePolicy(final List<Policy> policies, final String seedIdentity, final String resource, final String action) {
+        Policy foundPolicy = null;
+
+        // try to find a policy with the same resource and actions
+        for (Policy policy : policies) {
+            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
+                foundPolicy = policy;
+                break;
+            }
+        }
+
+        // if a matching policy wasn't found then create one
+        if (foundPolicy == null) {
+            final String uuidSeed = resource + action + seedIdentity;
+            final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed);
+
+            foundPolicy = new Policy();
+            foundPolicy.setIdentifier(policyIdentifier);
+            foundPolicy.setResource(resource);
+            foundPolicy.setAction(action);
+
+            policies.add(foundPolicy);
+        }
+
+        return foundPolicy;
+    }
+
+    /**
+     * Saves the Authorizations instance by marshalling to a file, then re-populates the
+     * in-memory data structures and sets the new holder.
+     *
+     * Synchronized to ensure only one thread writes the file at a time.
+     *
+     * @param authorizations the authorizations to save and populate from
+     * @throws AuthorizationAccessException if an error occurs saving the authorizations
+     */
+    private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException {
+        try {
+            saveAuthorizations(authorizations);
+
+            this.authorizationsHolder.set(new AuthorizationsHolder(authorizations));
+        } catch (JAXBException e) {
+            throw new AuthorizationAccessException("Unable to save Authorizations", e);
+        }
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+}


[07/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
index 9a310a2..b64a36a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
@@ -16,52 +16,17 @@
  */
 package org.apache.nifi.authorization;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.annotation.AuthorizerContext;
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
-import org.apache.nifi.authorization.file.generated.Authorizations;
-import org.apache.nifi.authorization.file.tenants.generated.Groups;
-import org.apache.nifi.authorization.file.generated.Policies;
-import org.apache.nifi.authorization.file.generated.Policy;
-import org.apache.nifi.authorization.file.tenants.generated.Users;
-import org.apache.nifi.authorization.file.tenants.generated.Tenants;
-import org.apache.nifi.authorization.resource.ResourceFactory;
-import org.apache.nifi.authorization.resource.ResourceType;
-import org.apache.nifi.authorization.util.IdentityMapping;
-import org.apache.nifi.authorization.util.IdentityMappingUtil;
-import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.util.file.FileUtils;
-import org.apache.nifi.web.api.dto.PortDTO;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.xml.sax.SAXException;
 
-import javax.xml.XMLConstants;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBElement;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.Marshaller;
-import javax.xml.bind.Unmarshaller;
-import javax.xml.transform.stream.StreamSource;
-import javax.xml.validation.Schema;
-import javax.xml.validation.SchemaFactory;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Provides authorizes requests to resources using policies persisted in a file.
@@ -70,687 +35,106 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
 
     private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class);
 
-    private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
-    private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated";
+    private static final String FILE_USER_GROUP_PROVIDER_ID = "file-user-group-provider";
+    private static final String FILE_ACCESS_POLICY_PROVIDER_ID = "file-access-policy-provider";
 
-    private static final String TENANTS_XSD = "/tenants.xsd";
-    private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated";
-
-    private static final String USERS_XSD = "/legacy-users.xsd";
-    private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated";
-
-    private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH);
-    private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);
-    private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH);
-
-    /**
-     * Load the JAXBContext.
-     */
-    private static JAXBContext initializeJaxbContext(final String contextPath) {
-        try {
-            return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
-        } catch (JAXBException e) {
-            throw new RuntimeException("Unable to create JAXBContext.");
-        }
-    }
-
-    static final String READ_CODE = "R";
-    static final String WRITE_CODE = "W";
-
-    static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
-    static final String PROP_TENANTS_FILE = "Users File";
-    static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
     static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File";
-    static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile("Node Identity \\S+");
 
-    private Schema usersSchema;
-    private Schema tenantsSchema;
-    private Schema authorizationsSchema;
-    private SchemaFactory schemaFactory;
-    private NiFiProperties properties;
-    private File tenantsFile;
-    private File authorizationsFile;
-    private File restoreAuthorizationsFile;
-    private File restoreTenantsFile;
-    private String rootGroupId;
-    private String initialAdminIdentity;
-    private String legacyAuthorizedUsersFile;
-    private Set<String> nodeIdentities;
-    private List<PortDTO> ports = new ArrayList<>();
-    private List<IdentityMapping> identityMappings;
-
-    private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
+    private FileUserGroupProvider userGroupProvider = new FileUserGroupProvider();
+    private FileAccessPolicyProvider accessPolicyProvider = new FileAccessPolicyProvider();
 
     @Override
     public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-        try {
-            schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
-            tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
-            authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
-            usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
-        } catch (Exception e) {
-            throw new AuthorizerCreationException(e);
-        }
-    }
-
-    @Override
-    public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
-        try {
-            final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
-            if (StringUtils.isBlank(tenantsPath.getValue())) {
-                throw new AuthorizerCreationException("The users file must be specified.");
-            }
-
-            // get the tenants file and ensure it exists
-            tenantsFile = new File(tenantsPath.getValue());
-            if (!tenantsFile.exists()) {
-                logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
-                saveTenants(new Tenants());
-            }
-
-            final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
-            if (StringUtils.isBlank(authorizationsPath.getValue())) {
-                throw new AuthorizerCreationException("The authorizations file must be specified.");
-            }
-
-            // get the authorizations file and ensure it exists
-            authorizationsFile = new File(authorizationsPath.getValue());
-            if (!authorizationsFile.exists()) {
-                logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()});
-                saveAuthorizations(new Authorizations());
-            }
-
-            final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile();
-            final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
-
-            // the restore directory is optional and may be null
-            final File restoreDirectory = properties.getRestoreDirectory();
-            if (restoreDirectory != null) {
-                // sanity check that restore directory is a directory, creating it if necessary
-                FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory);
-
-                // check that restore directory is not the same as the authorizations directory
-                if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
-                    throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ",
-                            authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
-                }
-
-                // check that restore directory is not the same as the user's directory
-                if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
-                    throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ",
-                            tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
-                }
-
-                // the restore copy will have same file name, but reside in a different directory
-                restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName());
-                restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName());
-
-                try {
-                    // sync the primary copy with the restore copy
-                    FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger);
-                    FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger);
-                } catch (final IOException | IllegalStateException ioe) {
-                    throw new AuthorizerCreationException(ioe);
-                }
-            }
-
-            // extract the identity mappings from nifi.properties if any are provided
-            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
-
-            // get the value of the initial admin identity
-            final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
-            initialAdminIdentity = initialAdminIdentityProp == null ? null : IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings);
-
-            // get the value of the legacy authorized users file
-            final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE);
-            legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue();
-
-            // extract any node identities
-            nodeIdentities = new HashSet<>();
-            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
-                Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
-                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
-                    nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
-                }
-            }
-
-            // load the authorizations
-            load();
-
-            // if we've copied the authorizations file to a restore directory synchronize it
-            if (restoreAuthorizationsFile != null) {
-                FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger);
-            }
-
-            // if we've copied the authorizations file to a restore directory synchronize it
-            if (restoreTenantsFile != null) {
-                FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
-            }
-
-            logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
-
-        } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
-            throw new AuthorizerCreationException(e);
-        }
-    }
-
-    /**
-     * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
-     *
-     * @throws JAXBException            Unable to reload the authorized users file
-     * @throws IOException              Unable to sync file with restore
-     * @throws IllegalStateException    Unable to sync file with restore
-     */
-    private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException {
-        // attempt to unmarshal
-        final Authorizations authorizations = unmarshallAuthorizations();
-        if (authorizations.getPolicies() == null) {
-            authorizations.setPolicies(new Policies());
-        }
-
-        final Tenants tenants = unmarshallTenants();
-        if (tenants.getUsers() == null) {
-            tenants.setUsers(new Users());
-        }
-        if (tenants.getGroups() == null) {
-            tenants.setGroups(new Groups());
-        }
-
-        final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants);
-        final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty();
-        final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity));
-        final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
-
-        // if we are starting fresh then we might need to populate an initial admin or convert legacy users
-        if (emptyAuthorizations) {
-            parseFlow();
-
-            if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) {
-                throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File");
-            } else if (hasInitialAdminIdentity) {
-                logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity);
-                populateInitialAdmin(authorizations, tenants);
-            } else if (hasLegacyAuthorizedUsers) {
-                logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model");
-                convertLegacyAuthorizedUsers(authorizations, tenants);
-            }
-
-            populateNodes(authorizations, tenants);
-
-            // save any changes that were made and repopulate the holder
-            saveAndRefreshHolder(authorizations, tenants);
-        } else {
-            this.authorizationsHolder.set(authorizationsHolder);
-        }
-    }
-
-    private Authorizations unmarshallAuthorizations() throws JAXBException {
-        final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
-        unmarshaller.setSchema(authorizationsSchema);
-
-        final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class);
-        return element.getValue();
-    }
-
-    private Tenants unmarshallTenants() throws JAXBException {
-        final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
-        unmarshaller.setSchema(tenantsSchema);
-
-        final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
-        return element.getValue();
-    }
-
-    /**
-     * Try to parse the flow configuration file to extract the root group id and port information.
-     *
-     * @throws SAXException if an error occurs creating the schema
-     */
-    private void parseFlow() throws SAXException {
-        final FlowParser flowParser = new FlowParser();
-        final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile());
-
-        if (flowInfo != null) {
-            rootGroupId = flowInfo.getRootGroupId();
-            ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts();
-        }
-    }
-
-    /**
-     *  Creates the initial admin user and policies for access the flow and managing users and policies.
-     */
-    private void populateInitialAdmin(final Authorizations authorizations, Tenants tenants) {
-        final org.apache.nifi.authorization.file.tenants.generated.User adminUser = getOrCreateUser(tenants, initialAdminIdentity);
-
-        // grant the user read access to the /flow resource
-        addAccessPolicy(authorizations, ResourceType.Flow.getValue(), adminUser.getIdentifier(), READ_CODE);
-
-        // grant the user read access to the root process group resource
-        if (rootGroupId != null) {
-            addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE);
-            addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE);
-
-            addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE);
-            addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE);
-        }
-
-        // grant the user write to restricted components
-        addAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), adminUser.getIdentifier(), WRITE_CODE);
-
-        // grant the user read/write access to the /tenants resource
-        addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), READ_CODE);
-        addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), WRITE_CODE);
-
-        // grant the user read/write access to the /policies resource
-        addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE);
-        addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), WRITE_CODE);
-
-        // grant the user read/write access to the /controller resource
-        addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), READ_CODE);
-        addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), WRITE_CODE);
-    }
-
-    /**
-     * Creates a user for each node and gives the nodes write permission to /proxy.
-     *
-     * @param authorizations the overall authorizations
-     */
-    private void populateNodes(Authorizations authorizations, Tenants tenants) {
-        for (String nodeIdentity : nodeIdentities) {
-            final org.apache.nifi.authorization.file.tenants.generated.User jaxbNodeUser = getOrCreateUser(tenants, nodeIdentity);
-
-            // grant access to the proxy resource
-            addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), WRITE_CODE);
-
-            // grant the user read/write access data of the root group
-            if (rootGroupId != null) {
-                addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), READ_CODE);
-                addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), WRITE_CODE);
-            }
-        }
-    }
-
-    /**
-     * Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
-     *
-     * @param authorizations the current Authorizations instance that policies will be added to
-     * @param tenants the current Tenants instance users and groups will be added to
-     * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
-     * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
-     */
-    private void convertLegacyAuthorizedUsers(final Authorizations authorizations, final Tenants tenants) throws AuthorizerCreationException, JAXBException {
-        final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
-        if (!authorizedUsersFile.exists()) {
-            throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
-        }
-
-        final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller();
-        unmarshaller.setSchema(usersSchema);
-
-        final JAXBElement<org.apache.nifi.user.generated.Users> element = unmarshaller.unmarshal(
-                new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class);
-
-        final org.apache.nifi.user.generated.Users users = element.getValue();
-        if (users.getUser().isEmpty()) {
-            logger.info("Legacy Authorized Users File contained no users, nothing to convert");
-            return;
-        }
-
-        // get all the user DNs into a list
-        List<String> userIdentities = new ArrayList<>();
-        for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
-            userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings));
-        }
-
-        // sort the list and pull out the first identity
-        Collections.sort(userIdentities);
-        final String seedIdentity = userIdentities.get(0);
-
-        // create mapping from Role to access policies
-        final Map<Role,Set<RoleAccessPolicy>> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId);
-
-        final List<Policy> allPolicies = new ArrayList<>();
-
-        for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
-            // create the identifier of the new user based on the DN
-            final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
-            org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn);
-
-            // if there was a group name find or create the group and add the user to it
-            org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup());
-            if (group != null) {
-                org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
-                groupUser.setIdentifier(user.getIdentifier());
-                group.getUser().add(groupUser);
+        // initialize the user group provider
+        userGroupProvider.initialize(new UserGroupProviderInitializationContext() {
+            @Override
+            public String getIdentifier() {
+                return FILE_USER_GROUP_PROVIDER_ID;
             }
 
-            // create policies based on the given role
-            for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) {
-                Role role = Role.valueOf(jaxbRole.getName());
-                Set<RoleAccessPolicy> policies = roleAccessPolicies.get(role);
-
-                for (RoleAccessPolicy roleAccessPolicy : policies) {
-
-                    // get the matching policy, or create a new one
-                    Policy policy = getOrCreatePolicy(
-                            allPolicies,
-                            seedIdentity,
-                            roleAccessPolicy.getResource(),
-                            roleAccessPolicy.getAction());
-
-                    // add the user to the policy if it doesn't exist
-                    addUserToPolicy(user.getIdentifier(), policy);
-                }
+            @Override
+            public UserGroupProviderLookup getUserGroupProviderLookup() {
+                return (identifier) -> null;
             }
+        });
 
-        }
-
-        // convert any access controls on ports to the appropriate policies
-        for (PortDTO portDTO : ports) {
-            final Resource resource;
-            if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) {
-                resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName()));
-            } else {
-                resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName()));
+        // initialize the access policy provider
+        accessPolicyProvider.initialize(new AccessPolicyProviderInitializationContext() {
+            @Override
+            public String getIdentifier() {
+                return FILE_ACCESS_POLICY_PROVIDER_ID;
             }
 
-            if (portDTO.getUserAccessControl() != null) {
-                for (String userAccessControl : portDTO.getUserAccessControl()) {
-                    // need to perform the identity mapping on the access control so it matches the identities in the User objects
-                    final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings);
-
-                    // find a user where the identity is the userAccessControl
-                    org.apache.nifi.authorization.file.tenants.generated.User foundUser = null;
-                    for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : tenants.getUsers().getUser()) {
-                        if (jaxbUser.getIdentity().equals(mappedUserAccessControl)) {
-                            foundUser = jaxbUser;
-                            break;
-                        }
-                    }
-
-                    // couldn't find the user matching the access control so log a warning and skip
-                    if (foundUser == null) {
-                        logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...",
-                                new Object[] {mappedUserAccessControl});
-                        continue;
+            @Override
+            public UserGroupProviderLookup getUserGroupProviderLookup() {
+                return (identifier) -> {
+                    if (FILE_USER_GROUP_PROVIDER_ID.equals(identifier)) {
+                        return userGroupProvider;
                     }
 
-                    // we found the user so create the appropriate policy and add the user to it
-                    Policy policy = getOrCreatePolicy(
-                            allPolicies,
-                            seedIdentity,
-                            resource.getIdentifier(),
-                            WRITE_CODE);
-
-                    addUserToPolicy(foundUser.getIdentifier(), policy);
-                }
-            }
-
-            if (portDTO.getGroupAccessControl() != null) {
-                for (String groupAccessControl : portDTO.getGroupAccessControl()) {
-                    // find a group where the name is the groupAccessControl
-                    org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null;
-                    for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
-                        if (jaxbGroup.getName().equals(groupAccessControl)) {
-                            foundGroup = jaxbGroup;
-                            break;
-                        }
-                    }
-
-                    // couldn't find the group matching the access control so log a warning and skip
-                    if (foundGroup == null) {
-                        logger.warn("Found port with group access control for {} but no group exists with this name, skipping...",
-                                new Object[] {groupAccessControl});
-                        continue;
-                    }
-
-                    // we found the group so create the appropriate policy and add all the users to it
-                    Policy policy = getOrCreatePolicy(
-                            allPolicies,
-                            seedIdentity,
-                            resource.getIdentifier(),
-                            WRITE_CODE);
-
-                    addGroupToPolicy(foundGroup.getIdentifier(), policy);
-                }
+                    return null;
+                };
             }
-        }
-
-        authorizations.getPolicies().getPolicy().addAll(allPolicies);
-    }
 
-    /**
-     * Adds the given user identifier to the policy if it doesn't already exist.
-     *
-     * @param userIdentifier a user identifier
-     * @param policy a policy to add the user to
-     */
-    private void addUserToPolicy(final String userIdentifier, final Policy policy) {
-        // determine if the user already exists in the policy
-        boolean userExists = false;
-        for (Policy.User policyUser : policy.getUser()) {
-            if (policyUser.getIdentifier().equals(userIdentifier)) {
-                userExists = true;
-                break;
+            @Override
+            public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
+                return (identifier) ->  null;
             }
-        }
-
-        // add the user to the policy if doesn't already exist
-        if (!userExists) {
-            Policy.User policyUser = new Policy.User();
-            policyUser.setIdentifier(userIdentifier);
-            policy.getUser().add(policyUser);
-        }
+        });
     }
 
-    /**
-     * Adds the given group identifier to the policy if it doesn't already exist.
-     *
-     * @param groupIdentifier a group identifier
-     * @param policy a policy to add the user to
-     */
-    private void addGroupToPolicy(final String groupIdentifier, final Policy policy) {
-        // determine if the group already exists in the policy
-        boolean groupExists = false;
-        for (Policy.Group policyGroup : policy.getGroup()) {
-            if (policyGroup.getIdentifier().equals(groupIdentifier)) {
-                groupExists = true;
-                break;
-            }
-        }
-
-        // add the group to the policy if doesn't already exist
-        if (!groupExists) {
-            Policy.Group policyGroup = new Policy.Group();
-            policyGroup.setIdentifier(groupIdentifier);
-            policy.getGroup().add(policyGroup);
-        }
-    }
-
-    /**
-     * Finds the User with the given identity, or creates a new one and adds it to the Tenants.
-     *
-     * @param tenants the Tenants reference
-     * @param userIdentity the user identity to find or create
-     * @return the User from Tenants with the given identity, or a new instance that was added to Tenants
-     */
-    private org.apache.nifi.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
-        if (StringUtils.isBlank(userIdentity)) {
-            return null;
-        }
-
-        org.apache.nifi.authorization.file.tenants.generated.User foundUser = null;
-        for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
-            if (user.getIdentity().equals(userIdentity)) {
-                foundUser = user;
-                break;
-            }
-        }
-
-        if (foundUser == null) {
-            final String userIdentifier = UUID.nameUUIDFromBytes(userIdentity.getBytes(StandardCharsets.UTF_8)).toString();
-            foundUser = new org.apache.nifi.authorization.file.tenants.generated.User();
-            foundUser.setIdentifier(userIdentifier);
-            foundUser.setIdentity(userIdentity);
-            tenants.getUsers().getUser().add(foundUser);
-        }
-
-        return foundUser;
-    }
+    @Override
+    public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+        final Map<String, String> configurationProperties = configurationContext.getProperties();
 
-    /**
-     * Finds the Group with the given name, or creates a new one and adds it to Tenants.
-     *
-     * @param tenants the Tenants reference
-     * @param groupName the name of the group to look for
-     * @return the Group from Tenants with the given name, or a new instance that was added to Tenants
-     */
-    private org.apache.nifi.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
-        if (StringUtils.isBlank(groupName)) {
-            return null;
+        // relay the relevant config
+        final Map<String, String> userGroupProperties = new HashMap<>();
+        if (configurationProperties.containsKey(FileUserGroupProvider.PROP_TENANTS_FILE)) {
+            userGroupProperties.put(FileUserGroupProvider.PROP_TENANTS_FILE, configurationProperties.get(FileUserGroupProvider.PROP_TENANTS_FILE));
         }
-
-        org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null;
-        for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
-            if (group.getName().equals(groupName)) {
-                foundGroup = group;
-                break;
-            }
+        if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) {
+            userGroupProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
         }
 
-        if (foundGroup == null) {
-            UUID newGroupIdentifier = UUID.nameUUIDFromBytes(groupName.getBytes(StandardCharsets.UTF_8));
-            foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
-            foundGroup.setIdentifier(newGroupIdentifier.toString());
-            foundGroup.setName(groupName);
-            tenants.getGroups().getGroup().add(foundGroup);
+        // relay the relevant config
+        final Map<String, String> accessPolicyProperties = new HashMap<>();
+        accessPolicyProperties.put(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER, FILE_USER_GROUP_PROVIDER_ID);
+        if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE)) {
+            accessPolicyProperties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, configurationProperties.get(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE));
         }
-
-        return foundGroup;
-    }
-
-    /**
-     * Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies.
-     *
-     * @param policies the policies to search through
-     * @param seedIdentity the seedIdentity to use when creating identifiers for new policies
-     * @param resource the resource for the policy
-     * @param action the action string for the police (R or RW)
-     * @return the matching policy or a new policy
-     */
-    private Policy getOrCreatePolicy(final List<Policy> policies, final String seedIdentity, final String resource, final String action) {
-        Policy foundPolicy = null;
-
-        // try to find a policy with the same resource and actions
-        for (Policy policy : policies) {
-            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
-                foundPolicy = policy;
-                break;
-            }
+        if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
+            accessPolicyProperties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
         }
-
-        // if a matching policy wasn't found then create one
-        if (foundPolicy == null) {
-            final String uuidSeed = resource + action + seedIdentity;
-            final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
-
-            foundPolicy = new Policy();
-            foundPolicy.setIdentifier(policyIdentifier.toString());
-            foundPolicy.setResource(resource);
-            foundPolicy.setAction(action);
-
-            policies.add(foundPolicy);
+        if (configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)) {
+            accessPolicyProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
         }
 
-        return foundPolicy;
-    }
-
-    /**
-     * Creates and adds an access policy for the given resource, identity, and actions.
-     *
-     * @param authorizations the Authorizations instance to add the policy to
-     * @param resource the resource for the policy
-     * @param identity the identity for the policy
-     * @param action the action for the policy
-     */
-    private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String action) {
-        // first try to find an existing policy for the given resource and action
-        Policy foundPolicy = null;
-        for (Policy policy : authorizations.getPolicies().getPolicy()) {
-            if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
-                foundPolicy = policy;
-                break;
+        // ensure all node identities are seeded into the user provider
+        configurationProperties.forEach((property, value) -> {
+            final Matcher matcher = FileAccessPolicyProvider.NODE_IDENTITY_PATTERN.matcher(property);
+            if (matcher.matches()) {
+                accessPolicyProperties.put(property, value);
+                userGroupProperties.put(property.replace(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX, FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX), value);
             }
-        }
+        });
 
-        if (foundPolicy == null) {
-            // if we didn't find an existing policy create a new one
-            final String uuidSeed = resource + identity + action;
-            final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8));
-
-            final AccessPolicy.Builder builder = new AccessPolicy.Builder()
-                    .identifier(policyIdentifier.toString())
-                    .resource(resource)
-                    .addUser(identity);
-
-            if (action.equals(READ_CODE)) {
-                builder.action(RequestAction.READ);
-            } else if (action.equals(WRITE_CODE)) {
-                builder.action(RequestAction.WRITE);
-            } else {
-                throw new IllegalStateException("Unknown Policy Action: " + action);
+        // ensure the initial admin is seeded into the user provider if appropriate
+        if (configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
+            int i = 0;
+            while (true) {
+                final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                if (!userGroupProperties.containsKey(key)) {
+                    userGroupProperties.put(key, configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+                    break;
+                }
             }
-
-            final AccessPolicy accessPolicy = builder.build();
-            final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
-            authorizations.getPolicies().getPolicy().add(jaxbPolicy);
-        } else {
-            // otherwise add the user to the existing policy
-            Policy.User policyUser = new Policy.User();
-            policyUser.setIdentifier(identity);
-            foundPolicy.getUser().add(policyUser);
-        }
-    }
-
-    /**
-     * Saves the Authorizations instance by marshalling to a file, then re-populates the
-     * in-memory data structures and sets the new holder.
-     *
-     * Synchronized to ensure only one thread writes the file at a time.
-     *
-     * @param authorizations the authorizations to save and populate from
-     * @param tenants the tenants to save and populate from
-     * @throws AuthorizationAccessException if an error occurs saving the authorizations
-     */
-    private synchronized void saveAndRefreshHolder(final Authorizations authorizations, final Tenants tenants) throws AuthorizationAccessException {
-        try {
-            saveTenants(tenants);
-            saveAuthorizations(authorizations);
-
-            final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants);
-            this.authorizationsHolder.set(authorizationsHolder);
-        } catch (JAXBException e) {
-            throw new AuthorizationAccessException("Unable to save Authorizations", e);
         }
-    }
 
-    private void saveAuthorizations(final Authorizations authorizations) throws JAXBException {
-        final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
-        marshaller.setSchema(authorizationsSchema);
-        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
-        marshaller.marshal(authorizations, authorizationsFile);
-    }
+        // configure the user group provider
+        userGroupProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, userGroupProperties));
 
-    private void saveTenants(final Tenants tenants) throws JAXBException {
-        final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
-        marshaller.setSchema(tenantsSchema);
-        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
-        marshaller.marshal(tenants, tenantsFile);
-    }
-
-
-    @AuthorizerContext
-    public void setNiFiProperties(NiFiProperties properties) {
-        this.properties = properties;
+        // configure the access policy provider
+        accessPolicyProvider.onConfigured(new StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, accessPolicyProperties));
     }
 
     @Override
@@ -762,434 +146,115 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
 
     @Override
     public synchronized Group doAddGroup(Group group) throws AuthorizationAccessException {
-        if (group == null) {
-            throw new IllegalArgumentException("Group cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        // determine that all users in the group exist before doing anything, throw an exception if they don't
-        final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = checkGroupUsers(group, tenants.getUsers().getUser());
-
-        // create a new JAXB Group based on the incoming Group
-        final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
-        jaxbGroup.setIdentifier(group.getIdentifier());
-        jaxbGroup.setName(group.getName());
-
-        // add each user to the group
-        for (String groupUser : group.getUsers()) {
-            org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
-            jaxbGroupUser.setIdentifier(groupUser);
-            jaxbGroup.getUser().add(jaxbGroupUser);
-        }
-
-        tenants.getGroups().getGroup().add(jaxbGroup);
-        saveAndRefreshHolder(authorizations, tenants);
-
-        return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier());
+        return userGroupProvider.addGroup(group);
     }
 
     @Override
     public Group getGroup(String identifier) throws AuthorizationAccessException {
-        if (identifier == null) {
-            return null;
-        }
-        return authorizationsHolder.get().getGroupsById().get(identifier);
+        return userGroupProvider.getGroup(identifier);
     }
 
     @Override
     public synchronized Group doUpdateGroup(Group group) throws AuthorizationAccessException {
-        if (group == null) {
-            throw new IllegalArgumentException("Group cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        // find the group that needs to be update
-        org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null;
-        for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
-            if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
-                updateGroup = jaxbGroup;
-                break;
-            }
-        }
-
-        // if the group wasn't found return null, otherwise update the group and save changes
-        if (updateGroup == null) {
-            return null;
-        }
-
-        // reset the list of users and add each user to the group
-        updateGroup.getUser().clear();
-        for (String groupUser : group.getUsers()) {
-            org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
-            jaxbGroupUser.setIdentifier(groupUser);
-            updateGroup.getUser().add(jaxbGroupUser);
-        }
-
-        updateGroup.setName(group.getName());
-        saveAndRefreshHolder(authorizations, tenants);
-
-        return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier());
+        return userGroupProvider.updateGroup(group);
     }
 
     @Override
     public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        final List<org.apache.nifi.authorization.file.tenants.generated.Group> groups = tenants.getGroups().getGroup();
-
-        // for each policy iterate over the group reference and remove the group reference if it matches the group being deleted
-        for (Policy policy : authorizations.getPolicies().getPolicy()) {
-            Iterator<Policy.Group> policyGroupIter = policy.getGroup().iterator();
-            while (policyGroupIter.hasNext()) {
-                Policy.Group policyGroup = policyGroupIter.next();
-                if (policyGroup.getIdentifier().equals(group.getIdentifier())) {
-                    policyGroupIter.remove();
-                    break;
-                }
-            }
-        }
-
-        // now remove the actual group from the top-level list of groups
-        boolean removedGroup = false;
-        Iterator<org.apache.nifi.authorization.file.tenants.generated.Group> iter = groups.iterator();
-        while (iter.hasNext()) {
-            org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
-            if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) {
-                iter.remove();
-                removedGroup = true;
-                break;
-            }
-        }
-
-        if (removedGroup) {
-            saveAndRefreshHolder(authorizations, tenants);
-            return group;
-        } else {
-            return null;
-        }
+        return userGroupProvider.deleteGroup(group);
     }
 
     @Override
     public Set<Group> getGroups() throws AuthorizationAccessException {
-        return authorizationsHolder.get().getAllGroups();
-    }
-
-    private Set<org.apache.nifi.authorization.file.tenants.generated.User> checkGroupUsers(final Group group, final List<org.apache.nifi.authorization.file.tenants.generated.User> users) {
-        final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = new HashSet<>();
-        for (String groupUser : group.getUsers()) {
-            boolean found = false;
-            for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
-                if (jaxbUser.getIdentifier().equals(groupUser)) {
-                    jaxbUsers.add(jaxbUser);
-                    found = true;
-                    break;
-                }
-            }
-
-            if (!found) {
-                throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist");
-            }
-        }
-        return jaxbUsers;
+        return userGroupProvider.getGroups();
     }
 
     // ------------------ Users ------------------
 
     @Override
     public synchronized User doAddUser(final User user) throws AuthorizationAccessException {
-        if (user == null) {
-            throw new IllegalArgumentException("User cannot be null");
-        }
-
-        final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-        tenants.getUsers().getUser().add(jaxbUser);
-
-        saveAndRefreshHolder(authorizations, tenants);
-
-        return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier());
-    }
-
-    private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) {
-        final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User();
-        jaxbUser.setIdentifier(user.getIdentifier());
-        jaxbUser.setIdentity(user.getIdentity());
-        return jaxbUser;
+        return userGroupProvider.addUser(user);
     }
 
     @Override
     public User getUser(final String identifier) throws AuthorizationAccessException {
-        if (identifier == null) {
-            return null;
-        }
-
-        final AuthorizationsHolder holder = authorizationsHolder.get();
-        return holder.getUsersById().get(identifier);
+        return userGroupProvider.getUser(identifier);
     }
 
     @Override
     public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
-        if (identity == null) {
-            return null;
-        }
-
-        final AuthorizationsHolder holder = authorizationsHolder.get();
-        return holder.getUsersByIdentity().get(identity);
+        return userGroupProvider.getUserByIdentity(identity);
     }
 
     @Override
     public synchronized User doUpdateUser(final User user) throws AuthorizationAccessException {
-        if (user == null) {
-            throw new IllegalArgumentException("User cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
-
-        // fine the User that needs to be updated
-        org.apache.nifi.authorization.file.tenants.generated.User updateUser = null;
-        for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
-            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
-                updateUser = jaxbUser;
-                break;
-            }
-        }
-
-        // if user wasn't found return null, otherwise update the user and save changes
-        if (updateUser == null) {
-            return null;
-        } else {
-            updateUser.setIdentity(user.getIdentity());
-            saveAndRefreshHolder(authorizations, tenants);
-
-            return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier());
-        }
+        return userGroupProvider.updateUser(user);
     }
 
     @Override
     public synchronized User deleteUser(final User user) throws AuthorizationAccessException {
-        if (user == null) {
-            throw new IllegalArgumentException("User cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
-
-        // for each group iterate over the user references and remove the user reference if it matches the user being deleted
-        for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
-            Iterator<org.apache.nifi.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
-            while (groupUserIter.hasNext()) {
-                org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
-                if (groupUser.getIdentifier().equals(user.getIdentifier())) {
-                    groupUserIter.remove();
-                    break;
-                }
-            }
-        }
-
-        // remove any references to the user being deleted from policies
-        for (Policy policy : authorizations.getPolicies().getPolicy()) {
-            Iterator<Policy.User> policyUserIter = policy.getUser().iterator();
-            while (policyUserIter.hasNext()) {
-                Policy.User policyUser = policyUserIter.next();
-                if (policyUser.getIdentifier().equals(user.getIdentifier())) {
-                    policyUserIter.remove();
-                    break;
-                }
-            }
-        }
-
-        // remove the actual user if it exists
-        boolean removedUser = false;
-        Iterator<org.apache.nifi.authorization.file.tenants.generated.User> iter = users.iterator();
-        while (iter.hasNext()) {
-            org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next();
-            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
-                iter.remove();
-                removedUser = true;
-                break;
-            }
-        }
-
-        if (removedUser) {
-            saveAndRefreshHolder(authorizations, tenants);
-            return user;
-        } else {
-            return null;
-        }
+        return userGroupProvider.deleteUser(user);
     }
 
     @Override
     public Set<User> getUsers() throws AuthorizationAccessException {
-        return authorizationsHolder.get().getAllUsers();
+        return userGroupProvider.getUsers();
     }
 
     // ------------------ AccessPolicies ------------------
 
     @Override
     public synchronized AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        if (accessPolicy == null) {
-            throw new IllegalArgumentException("AccessPolicy cannot be null");
-        }
-
-        // create the new JAXB Policy
-        final Policy policy = createJAXBPolicy(accessPolicy);
-
-        // add the new Policy to the top-level list of policies
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-        authorizations.getPolicies().getPolicy().add(policy);
-
-        saveAndRefreshHolder(authorizations, tenants);
-
-        return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
-    }
-
-    private Policy createJAXBPolicy(final AccessPolicy accessPolicy) {
-        final Policy policy = new Policy();
-        policy.setIdentifier(accessPolicy.getIdentifier());
-        policy.setResource(accessPolicy.getResource());
-
-        switch (accessPolicy.getAction()) {
-            case READ:
-                policy.setAction(READ_CODE);
-                break;
-            case WRITE:
-                policy.setAction(WRITE_CODE);
-                break;
-            default:
-                break;
-        }
-
-        transferUsersAndGroups(accessPolicy, policy);
-        return policy;
+        return accessPolicyProvider.addAccessPolicy(accessPolicy);
     }
 
     @Override
     public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
-        if (identifier == null) {
-            return null;
-        }
-
-        final AuthorizationsHolder holder = authorizationsHolder.get();
-        return holder.getPoliciesById().get(identifier);
+        return accessPolicyProvider.getAccessPolicy(identifier);
     }
 
     @Override
     public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        if (accessPolicy == null) {
-            throw new IllegalArgumentException("AccessPolicy cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        // try to find an existing Authorization that matches the policy id
-        Policy updatePolicy = null;
-        for (Policy policy : authorizations.getPolicies().getPolicy()) {
-            if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
-                updatePolicy = policy;
-                break;
-            }
-        }
-
-        // no matching Policy so return null
-        if (updatePolicy == null) {
-            return null;
-        }
-
-        // update the Policy, save, reload, and return
-        transferUsersAndGroups(accessPolicy, updatePolicy);
-        saveAndRefreshHolder(authorizations, tenants);
-
-        return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
+        return accessPolicyProvider.updateAccessPolicy(accessPolicy);
     }
 
     @Override
     public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        if (accessPolicy == null) {
-            throw new IllegalArgumentException("AccessPolicy cannot be null");
-        }
-
-        final AuthorizationsHolder holder = this.authorizationsHolder.get();
-        final Tenants tenants = holder.getTenants();
-        final Authorizations authorizations = holder.getAuthorizations();
-
-        // find the matching Policy and remove it
-        boolean deletedPolicy = false;
-        Iterator<Policy> policyIter = authorizations.getPolicies().getPolicy().iterator();
-        while (policyIter.hasNext()) {
-            final Policy policy = policyIter.next();
-            if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
-                policyIter.remove();
-                deletedPolicy = true;
-                break;
-            }
-        }
-
-        // never found a matching Policy so return null
-        if (!deletedPolicy) {
-            return null;
-        }
-
-        saveAndRefreshHolder(authorizations, tenants);
-        return accessPolicy;
+        return accessPolicyProvider.deleteAccessPolicy(accessPolicy);
     }
 
     @Override
     public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-        return authorizationsHolder.get().getAllPolicies();
+        return accessPolicyProvider.getAccessPolicies();
     }
 
-    @Override
-    public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
-        return authorizationsHolder.get();
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiProperties properties) {
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
     }
 
-    /**
-     * Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and
-     * set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy.
-     *
-     * Does not set the identifier.
-     *
-     * @param accessPolicy the AccessPolicy to transfer state from
-     * @param policy the Policy to transfer state to
-     */
-    private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) {
-        // add users to the policy
-        policy.getUser().clear();
-        for (String userIdentifier : accessPolicy.getUsers()) {
-            Policy.User policyUser = new Policy.User();
-            policyUser.setIdentifier(userIdentifier);
-            policy.getUser().add(policyUser);
-        }
+    @Override
+    public synchronized UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
+        final AuthorizationsHolder authorizationsHolder = accessPolicyProvider.getAuthorizationsHolder();
+        final UserGroupHolder userGroupHolder = userGroupProvider.getUserGroupHolder();
 
-        // add groups to the policy
-        policy.getGroup().clear();
-        for (String groupIdentifier : accessPolicy.getGroups()) {
-            Policy.Group policyGroup = new Policy.Group();
-            policyGroup.setIdentifier(groupIdentifier);
-            policy.getGroup().add(policyGroup);
-        }
+        return new UsersAndAccessPolicies() {
+            @Override
+            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
+                return authorizationsHolder.getAccessPolicy(resourceIdentifier, action);
+            }
+
+            @Override
+            public User getUser(String identity) {
+                return userGroupHolder.getUser(identity);
+            }
+
+            @Override
+            public Set<Group> getGroups(String userIdentity) {
+                return userGroupHolder.getGroups(userIdentity);
+            }
+        };
     }
 
 }


[05/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
new file mode 100644
index 0000000..3c9e4c5
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAccessPolicyProviderTest.java
@@ -0,0 +1,1050 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.resource.ResourceFactory;
+import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.file.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileAccessPolicyProviderTest {
+
+    private static final String EMPTY_AUTHORIZATIONS_CONCISE =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<authorizations/>";
+
+    private static final String EMPTY_TENANTS_CONCISE =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants/>";
+
+    private static final String EMPTY_AUTHORIZATIONS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<authorizations>"
+        + "</authorizations>";
+
+    private static final String EMPTY_TENANTS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants>"
+        + "</tenants>";
+
+    private static final String BAD_SCHEMA_AUTHORIZATIONS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<authorization>"
+        + "</authorization>";
+
+    private static final String AUTHORIZATIONS =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<authorizations>" +
+            "  <policies>" +
+            "      <policy identifier=\"policy-1\" resource=\"/flow\" action=\"R\">" +
+                    "  <group identifier=\"group-1\" />" +
+                    "  <group identifier=\"group-2\" />" +
+                    "  <user identifier=\"user-1\" />" +
+            "      </policy>" +
+            "      <policy identifier=\"policy-2\" resource=\"/flow\" action=\"W\">" +
+            "        <user identifier=\"user-2\" />" +
+            "      </policy>" +
+            "  </policies>" +
+            "</authorizations>";
+
+    private static final String TENANTS =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<tenants>" +
+            "  <groups>" +
+            "    <group identifier=\"group-1\" name=\"group-1\">" +
+            "       <user identifier=\"user-1\" />" +
+            "    </group>" +
+            "    <group identifier=\"group-2\" name=\"group-2\">" +
+            "       <user identifier=\"user-2\" />" +
+            "    </group>" +
+            "  </groups>" +
+            "  <users>" +
+            "    <user identifier=\"user-1\" identity=\"user-1\" />" +
+            "    <user identifier=\"user-2\" identity=\"user-2\" />" +
+            "  </users>" +
+            "</tenants>";
+
+    private static final String TENANTS_FOR_ADMIN_AND_NODES =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+                    "<tenants>" +
+                    "  <users>" +
+                    "    <user identifier=\"admin-user\" identity=\"admin-user\"/>" +
+                    "    <user identifier=\"node1\" identity=\"node1\"/>" +
+                    "    <user identifier=\"node2\" identity=\"node2\"/>" +
+                    "  </users>" +
+                    "</tenants>";
+
+    // This is the root group id from the flow.xml.gz in src/test/resources
+    private static final String ROOT_GROUP_ID = "e530e14c-adcf-41c2-b5d6-d9a59ba8765c";
+
+    private NiFiProperties properties;
+    private FileAccessPolicyProvider accessPolicyProvider;
+    private FileUserGroupProvider userGroupProvider;
+    private File primaryAuthorizations;
+    private File primaryTenants;
+    private File restoreAuthorizations;
+    private File restoreTenants;
+    private File flow;
+    private File flowNoPorts;
+    private File flowWithDns;
+
+    private AuthorizerConfigurationContext configurationContext;
+
+    @Before
+    public void setup() throws IOException {
+        // primary authorizations
+        primaryAuthorizations = new File("target/authorizations/authorizations.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(primaryAuthorizations.getParentFile());
+
+        // primary tenants
+        primaryTenants = new File("target/authorizations/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile());
+
+        // restore authorizations
+        restoreAuthorizations = new File("target/restore/authorizations.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(restoreAuthorizations.getParentFile());
+
+        // restore authorizations
+        restoreTenants = new File("target/restore/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile());
+
+        flow = new File("src/test/resources/flow.xml.gz");
+        FileUtils.ensureDirectoryExistAndCanAccess(flow.getParentFile());
+
+        flowNoPorts = new File("src/test/resources/flow-no-ports.xml.gz");
+        FileUtils.ensureDirectoryExistAndCanAccess(flowNoPorts.getParentFile());
+
+        flowWithDns = new File("src/test/resources/flow-with-dns.xml.gz");
+        FileUtils.ensureDirectoryExistAndCanAccess(flowWithDns.getParentFile());
+
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flow);
+
+        userGroupProvider = new FileUserGroupProvider();
+        userGroupProvider.setNiFiProperties(properties);
+        userGroupProvider.initialize(null);
+
+        // this same configuration is being used for both the user group provider and the access policy provider
+        configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))).thenReturn(new StandardPropertyValue(null, null));
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null));
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER))).thenReturn(new StandardPropertyValue("user-group-provider", null));
+        when(configurationContext.getProperties()).then((invocation) -> {
+            final Map<String, String> properties = new HashMap<>();
+
+            final PropertyValue authFile = configurationContext.getProperty(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE);
+            if (authFile != null) {
+                properties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, authFile.getValue());
+            }
+
+            final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
+            if (tenantFile != null) {
+                properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
+            }
+
+            final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            if (legacyAuthFile != null) {
+                properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
+            }
+
+            final PropertyValue initialAdmin = configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY);
+            if (initialAdmin != null) {
+                properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin.getValue());
+            }
+
+            int i = 1;
+            while (true) {
+                final String key = FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++;
+                final PropertyValue value = configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            i = 1;
+            while (true) {
+                final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                final PropertyValue value = configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            // ensure the initial admin is seeded into the user provider if appropriate
+            if (properties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)) {
+                i = 0;
+                while (true) {
+                    final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                    if (!properties.containsKey(key)) {
+                        properties.put(key, properties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+                        break;
+                    }
+                }
+            }
+
+            return properties;
+        });
+
+        final AccessPolicyProviderInitializationContext initializationContext = mock(AccessPolicyProviderInitializationContext.class);
+        when(initializationContext.getUserGroupProviderLookup()).thenReturn(new UserGroupProviderLookup() {
+            @Override
+            public UserGroupProvider getUserGroupProvider(String identifier) {
+                return userGroupProvider;
+            }
+        });
+
+        accessPolicyProvider = new FileAccessPolicyProvider();
+        accessPolicyProvider.setNiFiProperties(properties);
+        accessPolicyProvider.initialize(initializationContext);
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        deleteFile(primaryAuthorizations);
+        deleteFile(primaryTenants);
+        deleteFile(restoreAuthorizations);
+        deleteFile(restoreTenants);
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvidedWithOverlappingRoles() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-multirole.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Flow.getValue(), RequestAction.READ));
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.READ));
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.Controller.getValue(), RequestAction.WRITE));
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.System.getValue(), RequestAction.READ));
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.READ));
+        assertNotNull(accessPolicyProvider.getAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvidedAndFlowHasNoPorts() throws Exception {
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flowNoPorts);
+
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        boolean foundDataTransferPolicy = false;
+        for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
+            if (policy.getResource().contains(ResourceType.DataTransfer.name())) {
+                foundDataTransferPolicy = true;
+                break;
+            }
+        }
+
+        assertFalse(foundDataTransferPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final User user1 = userGroupProvider.getUserByIdentity("user1");
+        final User user2 = userGroupProvider.getUserByIdentity("user2");
+        final User user3 = userGroupProvider.getUserByIdentity("user3");
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        final User user5 = userGroupProvider.getUserByIdentity("user5");
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group group1 = groups.iterator().next();
+
+        // verify more than one policy got created
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertTrue(policies.size() > 0);
+
+        // verify user1's policies
+        final Map<String,Set<RequestAction>> user1Policies = getResourceActions(policies, user1);
+        assertEquals(4, user1Policies.size());
+
+        assertTrue(user1Policies.containsKey(ResourceType.Flow.getValue()));
+        assertEquals(1, user1Policies.get(ResourceType.Flow.getValue()).size());
+        assertTrue(user1Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ));
+
+        assertTrue(user1Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID));
+        assertEquals(1, user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
+        assertTrue(user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ));
+
+        // verify user2's policies
+        final Map<String,Set<RequestAction>> user2Policies = getResourceActions(policies, user2);
+        assertEquals(2, user2Policies.size());
+
+        assertTrue(user2Policies.containsKey(ResourceType.Provenance.getValue()));
+        assertEquals(1, user2Policies.get(ResourceType.Provenance.getValue()).size());
+        assertTrue(user2Policies.get(ResourceType.Provenance.getValue()).contains(RequestAction.READ));
+
+        // verify user3's policies
+        final Map<String,Set<RequestAction>> user3Policies = getResourceActions(policies, user3);
+        assertEquals(6, user3Policies.size());
+
+        assertTrue(user3Policies.containsKey(ResourceType.Flow.getValue()));
+        assertEquals(1, user3Policies.get(ResourceType.Flow.getValue()).size());
+        assertTrue(user3Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ));
+
+        assertTrue(user3Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID));
+        assertEquals(2, user3Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
+        assertTrue(user3Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.WRITE));
+
+        // verify user4's policies
+        final Map<String,Set<RequestAction>> user4Policies = getResourceActions(policies, user4);
+        assertEquals(6, user4Policies.size());
+
+        assertTrue(user4Policies.containsKey(ResourceType.Flow.getValue()));
+        assertEquals(1, user4Policies.get(ResourceType.Flow.getValue()).size());
+        assertTrue(user4Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ));
+
+        assertTrue(user4Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID));
+        assertEquals(1, user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
+        assertTrue(user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ));
+
+        assertTrue(user4Policies.containsKey(ResourceType.Tenant.getValue()));
+        assertEquals(2, user4Policies.get(ResourceType.Tenant.getValue()).size());
+        assertTrue(user4Policies.get(ResourceType.Tenant.getValue()).contains(RequestAction.WRITE));
+
+        assertTrue(user4Policies.containsKey(ResourceType.Policy.getValue()));
+        assertEquals(2, user4Policies.get(ResourceType.Policy.getValue()).size());
+        assertTrue(user4Policies.get(ResourceType.Policy.getValue()).contains(RequestAction.WRITE));
+
+        // verify user5's policies
+        final Map<String,Set<RequestAction>> user5Policies = getResourceActions(policies, user5);
+        assertEquals(2, user5Policies.size());
+
+        assertTrue(user5Policies.containsKey(ResourceType.Proxy.getValue()));
+        assertEquals(1, user5Policies.get(ResourceType.Proxy.getValue()).size());
+        assertTrue(user5Policies.get(ResourceType.Proxy.getValue()).contains(RequestAction.WRITE));
+
+        // verify user6's policies
+        final Map<String,Set<RequestAction>> user6Policies = getResourceActions(policies, user6);
+        assertEquals(3, user6Policies.size());
+
+        assertTrue(user6Policies.containsKey(ResourceType.SiteToSite.getValue()));
+        assertEquals(1, user6Policies.get(ResourceType.SiteToSite.getValue()).size());
+        assertTrue(user6Policies.get(ResourceType.SiteToSite.getValue()).contains(RequestAction.READ));
+
+        final Resource inputPortResource = ResourceFactory.getDataTransferResource(
+                ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input"));
+        final AccessPolicy inputPortPolicy = accessPolicyProvider.getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE);
+        assertNotNull(inputPortPolicy);
+        assertEquals(1, inputPortPolicy.getUsers().size());
+        assertTrue(inputPortPolicy.getUsers().contains(user6.getIdentifier()));
+        assertEquals(1, inputPortPolicy.getGroups().size());
+        assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier()));
+
+        final Resource outputPortResource = ResourceFactory.getDataTransferResource(
+                ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output"));
+        final AccessPolicy outputPortPolicy = accessPolicyProvider.getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE);
+        assertNotNull(outputPortPolicy);
+        assertEquals(1, outputPortPolicy.getUsers().size());
+        assertTrue(outputPortPolicy.getUsers().contains(user4.getIdentifier()));
+    }
+
+    private Map<String,Set<RequestAction>> getResourceActions(final Set<AccessPolicy> policies, final User user) {
+        Map<String,Set<RequestAction>> resourceActionMap = new HashMap<>();
+
+        for (AccessPolicy accessPolicy : policies) {
+            if (accessPolicy.getUsers().contains(user.getIdentifier())) {
+                Set<RequestAction> actions = resourceActionMap.get(accessPolicy.getResource());
+                if (actions == null) {
+                    actions = new HashSet<>();
+                    resourceActionMap.put(accessPolicy.getResource(), actions);
+                }
+                actions.add(accessPolicy.getAction());
+            }
+        }
+
+        return resourceActionMap;
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
+
+        properties = getNiFiProperties(props);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns);
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        final Group group1 = groups.iterator().next();
+
+        final Resource inputPortResource = ResourceFactory.getDataTransferResource(
+                ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input"));
+        final AccessPolicy inputPortPolicy = accessPolicyProvider.getAccessPolicy(inputPortResource.getIdentifier(), RequestAction.WRITE);
+        assertNotNull(inputPortPolicy);
+        assertEquals(1, inputPortPolicy.getUsers().size());
+        assertTrue(inputPortPolicy.getUsers().contains(user6.getIdentifier()));
+        assertEquals(1, inputPortPolicy.getGroups().size());
+        assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier()));
+
+        final Resource outputPortResource = ResourceFactory.getDataTransferResource(
+                ResourceFactory.getComponentResource(ResourceType.OutputPort, "2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output"));
+        final AccessPolicy outputPortPolicy = accessPolicyProvider.getAccessPolicy(outputPortResource.getIdentifier(), RequestAction.WRITE);
+        assertNotNull(outputPortPolicy);
+        assertEquals(1, outputPortPolicy.getUsers().size());
+        assertTrue(outputPortPolicy.getUsers().contains(user4.getIdentifier()));
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        accessPolicyProvider.onConfigured(configurationContext);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        accessPolicyProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminNotProvided() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertEquals(0, policies.size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        final User adminUser = users.iterator().next();
+        assertEquals(adminIdentity, adminUser.getIdentity());
+
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertEquals(12, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertTrue(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminProvidedAndNoFlowExists() throws Exception {
+        // setup NiFi properties to return a file that does not exist
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(new File("src/test/resources/does-not-exist.xml.gz"));
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        final String adminIdentity = "admin-user";
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        final User adminUser = users.iterator().next();
+        assertEquals(adminIdentity, adminUser.getIdentity());
+
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertEquals(8, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertFalse(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminProvidedAndFlowIsNull() throws Exception {
+        // setup NiFi properties to return a file that does not exist
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(null);
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        final String adminIdentity = "admin-user";
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        final User adminUser = users.iterator().next();
+        assertEquals(adminIdentity, adminUser.getIdentity());
+
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertEquals(8, policies.size());
+
+        final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
+
+        boolean foundRootGroupPolicy = false;
+        for (AccessPolicy policy : policies) {
+            if (policy.getResource().equals(rootGroupResource)) {
+                foundRootGroupPolicy = true;
+                break;
+            }
+        }
+
+        assertFalse(foundRootGroupPolicy);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1_$2_$3");
+
+        properties = getNiFiProperties(props);
+        when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flow);
+
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+
+        final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        final User adminUser = users.iterator().next();
+        assertEquals("localhost_Apache NiFi_Apache", adminUser.getIdentity());
+    }
+
+    @Test
+    public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node1";
+        final String nodeIdentity2 = "node2";
+
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        User nodeUser1 = userGroupProvider.getUserByIdentity(nodeIdentity1);
+        User nodeUser2 = userGroupProvider.getUserByIdentity(nodeIdentity2);
+
+        AccessPolicy proxyWritePolicy = accessPolicyProvider.getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE);
+
+        assertNotNull(proxyWritePolicy);
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier()));
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier()));
+    }
+
+    @Test
+    public void testOnConfiguredWhenNodeIdentitiesProvidedAndUsersAlreadyExist() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node1";
+        final String nodeIdentity2 = "node2";
+
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, TENANTS_FOR_ADMIN_AND_NODES);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        User nodeUser1 = userGroupProvider.getUserByIdentity(nodeIdentity1);
+        User nodeUser2 = userGroupProvider.getUserByIdentity(nodeIdentity2);
+
+        AccessPolicy proxyWritePolicy = accessPolicyProvider.getAccessPolicy(ResourceType.Proxy.getValue(), RequestAction.WRITE);
+
+        assertNotNull(proxyWritePolicy);
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser1.getIdentifier()));
+        assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier()));
+    }
+
+
+    @Test
+    public void testOnConfiguredWhenTenantsAndAuthorizationsFileDoesNotExist() {
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+        assertEquals(0, accessPolicyProvider.getAccessPolicies().size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+        assertEquals(0, accessPolicyProvider.getAccessPolicies().size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+        assertEquals(0, accessPolicyProvider.getAccessPolicies().size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(primaryAuthorizations.length(), restoreAuthorizations.length());
+        assertEquals(primaryTenants.length(), restoreTenants.length());
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception {
+        writeFile(restoreAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryAuthorizationsDifferentThanRestore() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
+        writeFile(restoreAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWithBadAuthorizationsSchema() throws Exception {
+        writeFile(primaryAuthorizations, BAD_SCHEMA_AUTHORIZATIONS);
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testGetAllUsersGroupsPolicies() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        final Set<AccessPolicy> policies = accessPolicyProvider.getAccessPolicies();
+        assertEquals(2, policies.size());
+
+        boolean foundPolicy1 = false;
+        boolean foundPolicy2 = false;
+
+        for (AccessPolicy policy : policies) {
+            if (policy.getIdentifier().equals("policy-1")
+                    && policy.getResource().equals("/flow")
+                    && policy.getAction() == RequestAction.READ
+                    && policy.getGroups().size() == 2
+                    && policy.getGroups().contains("group-1")
+                    && policy.getGroups().contains("group-2")
+                    && policy.getUsers().size() == 1
+                    && policy.getUsers().contains("user-1")) {
+                foundPolicy1 = true;
+            } else if (policy.getIdentifier().equals("policy-2")
+                    && policy.getResource().equals("/flow")
+                    && policy.getAction() == RequestAction.WRITE
+                    && policy.getGroups().size() == 0
+                    && policy.getUsers().size() == 1
+                    && policy.getUsers().contains("user-2")) {
+                foundPolicy2 = true;
+            }
+        }
+
+        assertTrue(foundPolicy1);
+        assertTrue(foundPolicy2);
+    }
+
+    // --------------- AccessPolicy Tests ------------------------
+
+    @Test
+    public void testAddAccessPolicy() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
+        writeFile(primaryTenants, EMPTY_TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(0, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-1")
+                .resource("resource-1")
+                .addUser("user-1")
+                .addGroup("group-1")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy returnedPolicy1 = accessPolicyProvider.addAccessPolicy(policy1);
+        assertNotNull(returnedPolicy1);
+        assertEquals(policy1.getIdentifier(), returnedPolicy1.getIdentifier());
+        assertEquals(policy1.getResource(), returnedPolicy1.getResource());
+        assertEquals(policy1.getUsers(), returnedPolicy1.getUsers());
+        assertEquals(policy1.getGroups(), returnedPolicy1.getGroups());
+        assertEquals(policy1.getAction(), returnedPolicy1.getAction());
+
+        assertEquals(1, accessPolicyProvider.getAccessPolicies().size());
+    }
+
+    @Test
+    public void testAddAccessPolicyWithEmptyUsersAndGroups() throws Exception {
+        writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
+        writeFile(primaryTenants, EMPTY_TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(0, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy1 = new AccessPolicy.Builder()
+                .identifier("policy-1")
+                .resource("resource-1")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy returnedPolicy1 = accessPolicyProvider.addAccessPolicy(policy1);
+        assertNotNull(returnedPolicy1);
+        assertEquals(policy1.getIdentifier(), returnedPolicy1.getIdentifier());
+        assertEquals(policy1.getResource(), returnedPolicy1.getResource());
+        assertEquals(policy1.getUsers(), returnedPolicy1.getUsers());
+        assertEquals(policy1.getGroups(), returnedPolicy1.getGroups());
+        assertEquals(policy1.getAction(), returnedPolicy1.getAction());
+
+        assertEquals(1, accessPolicyProvider.getAccessPolicies().size());
+    }
+
+    @Test
+    public void testGetAccessPolicy() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = accessPolicyProvider.getAccessPolicy("policy-1");
+        assertNotNull(policy);
+        assertEquals("policy-1", policy.getIdentifier());
+        assertEquals("/flow", policy.getResource());
+
+        assertEquals(RequestAction.READ, policy.getAction());
+
+        assertEquals(1, policy.getUsers().size());
+        assertTrue(policy.getUsers().contains("user-1"));
+
+        assertEquals(2, policy.getGroups().size());
+        assertTrue(policy.getGroups().contains("group-1"));
+        assertTrue(policy.getGroups().contains("group-2"));
+    }
+
+    @Test
+    public void testGetAccessPolicyWhenNotFound() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = accessPolicyProvider.getAccessPolicy("policy-X");
+        assertNull(policy);
+    }
+
+    @Test
+    public void testUpdateAccessPolicy() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("policy-1")
+                .resource("resource-A")
+                .addUser("user-A")
+                .addGroup("group-A")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy updateAccessPolicy = accessPolicyProvider.updateAccessPolicy(policy);
+        assertNotNull(updateAccessPolicy);
+        assertEquals("policy-1", updateAccessPolicy.getIdentifier());
+        assertEquals("/flow", updateAccessPolicy.getResource());
+
+        assertEquals(1, updateAccessPolicy.getUsers().size());
+        assertTrue(updateAccessPolicy.getUsers().contains("user-A"));
+
+        assertEquals(1, updateAccessPolicy.getGroups().size());
+        assertTrue(updateAccessPolicy.getGroups().contains("group-A"));
+
+        assertEquals(RequestAction.READ, updateAccessPolicy.getAction());
+    }
+
+    @Test
+    public void testUpdateAccessPolicyWhenResourceNotFound() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("policy-XXX")
+                .resource("resource-A")
+                .addUser("user-A")
+                .addGroup("group-A")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy updateAccessPolicy = accessPolicyProvider.updateAccessPolicy(policy);
+        assertNull(updateAccessPolicy);
+    }
+
+    @Test
+    public void testDeleteAccessPolicy() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("policy-1")
+                .resource("resource-A")
+                .addUser("user-A")
+                .addGroup("group-A")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy deletedAccessPolicy = accessPolicyProvider.deleteAccessPolicy(policy);
+        assertNotNull(deletedAccessPolicy);
+        assertEquals(policy.getIdentifier(), deletedAccessPolicy.getIdentifier());
+
+        // should have one less policy, and get by policy id should return null
+        assertEquals(1, accessPolicyProvider.getAccessPolicies().size());
+        assertNull(accessPolicyProvider.getAccessPolicy(policy.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteAccessPolicyWhenNotFound() throws Exception {
+        writeFile(primaryAuthorizations, AUTHORIZATIONS);
+        writeFile(primaryTenants, TENANTS);
+
+        userGroupProvider.onConfigured(configurationContext);
+        accessPolicyProvider.onConfigured(configurationContext);
+
+        assertEquals(2, accessPolicyProvider.getAccessPolicies().size());
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("policy-XXX")
+                .resource("resource-A")
+                .addUser("user-A")
+                .addGroup("group-A")
+                .action(RequestAction.READ)
+                .build();
+
+        final AccessPolicy deletedAccessPolicy = accessPolicyProvider.deleteAccessPolicy(policy);
+        assertNull(deletedAccessPolicy);
+    }
+
+    private static void writeFile(final File file, final String content) throws Exception {
+        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
+        try (final FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        }
+    }
+
+    private static boolean deleteFile(final File file) {
+        if (file.isDirectory()) {
+            FileUtils.deleteFilesInDir(file, null, null, true, true);
+        }
+        return FileUtils.deleteFile(file, null, 10);
+    }
+
+    private NiFiProperties getNiFiProperties(final Properties properties) {
+        final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
+        when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames());
+
+        when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
+                return properties.getProperty((String)invocationOnMock.getArguments()[0]);
+            }
+        });
+        return nifiProperties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
index de76d93..79e70a0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
@@ -21,10 +21,10 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.file.FileUtils;
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -46,6 +46,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -188,8 +189,44 @@ public class FileAuthorizerTest {
         when(properties.getFlowConfigurationFile()).thenReturn(flow);
 
         configurationContext = mock(AuthorizerConfigurationContext.class);
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null));
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null));
+        when(configurationContext.getProperty(Mockito.eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
+        when(configurationContext.getProperties()).then((invocation) -> {
+            final Map<String, String> properties = new HashMap<>();
+
+            final PropertyValue authFile = configurationContext.getProperty(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE);
+            if (authFile != null) {
+                properties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, authFile.getValue());
+            }
+
+            final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
+            if (tenantFile != null) {
+                properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
+            }
+
+            final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            if (legacyAuthFile != null) {
+                properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
+            }
+
+            final PropertyValue initialAdmin = configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY);
+            if (initialAdmin != null) {
+                properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin.getValue());
+            }
+
+            int i = 1;
+            while (true) {
+                final String key = FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++;
+                final PropertyValue value = configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            return properties;
+        });
 
         authorizer = new FileAuthorizer();
         authorizer.setNiFiProperties(properties);
@@ -464,7 +501,7 @@ public class FileAuthorizerTest {
     @Test(expected = AuthorizerCreationException.class)
     public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception {
         final String adminIdentity = "admin-user";
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
                 .thenReturn(new StandardPropertyValue(adminIdentity, null));
 
         when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
@@ -492,7 +529,7 @@ public class FileAuthorizerTest {
     public void testOnConfiguredWhenInitialAdminProvided() throws Exception {
         final String adminIdentity = "admin-user";
 
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
                 .thenReturn(new StandardPropertyValue(adminIdentity, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@@ -530,7 +567,7 @@ public class FileAuthorizerTest {
         authorizer.setNiFiProperties(properties);
 
         final String adminIdentity = "admin-user";
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
                 .thenReturn(new StandardPropertyValue(adminIdentity, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@@ -568,7 +605,7 @@ public class FileAuthorizerTest {
         authorizer.setNiFiProperties(properties);
 
         final String adminIdentity = "admin-user";
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
                 .thenReturn(new StandardPropertyValue(adminIdentity, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@@ -609,7 +646,7 @@ public class FileAuthorizerTest {
         authorizer.setNiFiProperties(properties);
 
         final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
                 .thenReturn(new StandardPropertyValue(adminIdentity, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@@ -626,18 +663,15 @@ public class FileAuthorizerTest {
     @Test
     public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
         final String adminIdentity = "admin-user";
-
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
-                .thenReturn(new StandardPropertyValue(adminIdentity, null));
-
         final String nodeIdentity1 = "node1";
         final String nodeIdentity2 = "node2";
 
-        final Map<String,String> props = new HashMap<>();
-        props.put("Node Identity 1", nodeIdentity1);
-        props.put("Node Identity 2", nodeIdentity2);
-
-        when(configurationContext.getProperties()).thenReturn(props);
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
         writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
@@ -662,18 +696,15 @@ public class FileAuthorizerTest {
     @Test
     public void testOnConfiguredWhenNodeIdentitiesProvidedAndUsersAlreadyExist() throws Exception {
         final String adminIdentity = "admin-user";
-
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
-                .thenReturn(new StandardPropertyValue(adminIdentity, null));
-
         final String nodeIdentity1 = "node1";
         final String nodeIdentity2 = "node2";
 
-        final Map<String,String> props = new HashMap<>();
-        props.put("Node Identity 1", nodeIdentity1);
-        props.put("Node Identity 2", nodeIdentity2);
-
-        when(configurationContext.getProperties()).thenReturn(props);
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
         writeFile(primaryTenants, TENANTS_FOR_ADMIN_AND_NODES);
@@ -709,17 +740,15 @@ public class FileAuthorizerTest {
         authorizer.setNiFiProperties(properties);
 
         final String adminIdentity = "CN=user1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
-        when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
-                .thenReturn(new StandardPropertyValue(adminIdentity, null));
-
         final String nodeIdentity1 = "CN=node1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
         final String nodeIdentity2 = "CN=node2, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
 
-        final Map<String,String> nodeProps = new HashMap<>();
-        nodeProps.put("Node Identity 1", nodeIdentity1);
-        nodeProps.put("Node Identity 2", nodeIdentity2);
-
-        when(configurationContext.getProperties()).thenReturn(nodeProps);
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
 
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
         writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
@@ -735,9 +764,12 @@ public class FileAuthorizerTest {
         assertNotNull(nodeUser2);
     }
 
+    @Test
     public void testOnConfiguredWhenTenantsAndAuthorizationsFileDoesNotExist() {
         authorizer.onConfigured(configurationContext);
         assertEquals(0, authorizer.getAccessPolicies().size());
+        assertEquals(0, authorizer.getUsers().size());
+        assertEquals(0, authorizer.getGroups().size());
     }
 
     @Test
@@ -745,6 +777,8 @@ public class FileAuthorizerTest {
         writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
         authorizer.onConfigured(configurationContext);
         assertEquals(0, authorizer.getAccessPolicies().size());
+        assertEquals(0, authorizer.getUsers().size());
+        assertEquals(0, authorizer.getGroups().size());
     }
 
     @Test
@@ -752,6 +786,8 @@ public class FileAuthorizerTest {
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
         authorizer.onConfigured(configurationContext);
         assertEquals(0, authorizer.getAccessPolicies().size());
+        assertEquals(0, authorizer.getUsers().size());
+        assertEquals(0, authorizer.getGroups().size());
     }
 
     @Test
@@ -1007,9 +1043,6 @@ public class FileAuthorizerTest {
         final User user = authorizer.getUser("user-1");
         assertEquals("user-1", user.getIdentifier());
 
-        final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1");
-        assertTrue(policy1.getUsers().contains("user-1"));
-
         // delete user-1
         final User deletedUser = authorizer.deleteUser(user);
         assertNotNull(deletedUser);
@@ -1018,10 +1051,6 @@ public class FileAuthorizerTest {
         // should be one less user
         assertEquals(1, authorizer.getUsers().size());
         assertNull(authorizer.getUser(user.getIdentifier()));
-
-        // verify policy-1 no longer has a reference to user-1
-        final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1");
-        assertFalse(updatedPolicy1.getUsers().contains("user-1"));
     }
 
     @Test
@@ -1121,7 +1150,6 @@ public class FileAuthorizerTest {
         assertEquals(3, groups.size());
     }
 
-
     @Test(expected = IllegalStateException.class)
     public void testAddGroupWhenUserDoesNotExist() throws Exception {
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
@@ -1170,9 +1198,6 @@ public class FileAuthorizerTest {
         authorizer.onConfigured(configurationContext);
         assertEquals(2, authorizer.getGroups().size());
 
-        final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1");
-        assertTrue(policy1.getGroups().contains("group-1"));
-
         // retrieve group-1
         final Group group = authorizer.getGroup("group-1");
         assertEquals("group-1", group.getIdentifier());
@@ -1187,10 +1212,6 @@ public class FileAuthorizerTest {
 
         // verify we can no longer retrieve group-1 by identifier
         assertNull(authorizer.getGroup(group.getIdentifier()));
-
-        // verify group-1 is no longer in policy-1
-        final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1");
-        assertFalse(updatedPolicy1.getGroups().contains("group-1"));
     }
 
     @Test
@@ -1289,9 +1310,11 @@ public class FileAuthorizerTest {
                 .action(RequestAction.READ)
                 .build();
 
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(authorizer);
+        final ConfigurableAccessPolicyProvider accessPolicyProviderWithChecks = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
         try {
-            final AccessPolicy returnedPolicy2 = authorizer.addAccessPolicy(policy2);
-            Assert.fail("Should have thrown exception");
+            final AccessPolicy returnedPolicy2 = accessPolicyProviderWithChecks.addAccessPolicy(policy2);
+            fail("Should have thrown exception");
         } catch (Exception e) {
         }
 


[03/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java
new file mode 100644
index 0000000..4b2406a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMerger.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.AccessPolicyEntityMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class AccessPolicyEndpointMerger extends AbstractSingleEntityEndpoint<AccessPolicyEntity> implements EndpointResponseMerger {
+    public static final Pattern ACCESS_POLICIES_URI_PATTERN = Pattern.compile("/nifi-api/policies");
+    public static final Pattern ACCESS_POLICY_URI_PATTERN = Pattern.compile("/nifi-api/policies/[a-f0-9\\-]{36}");
+    public static final Pattern ACCESS_POLICY_LOOKUP_URI_PATTERN = Pattern.compile("/nifi-api/policies/(?:read|write)/(?:[\\w-]+?/?)+");
+    private final AccessPolicyEntityMerger accessPolicyEntityMerger = new AccessPolicyEntityMerger();
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (ACCESS_POLICY_URI_PATTERN.matcher(uri.getPath()).matches())) {
+            return true;
+        } else if ("GET".equalsIgnoreCase(method) && ACCESS_POLICY_LOOKUP_URI_PATTERN.matcher(uri.getPath()).matches()) {
+            return true;
+        } else if ("POST".equalsIgnoreCase(method) && ACCESS_POLICIES_URI_PATTERN.matcher(uri.getPath()).matches()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    protected Class<AccessPolicyEntity> getEntityClass() {
+        return AccessPolicyEntity.class;
+    }
+
+    @Override
+    protected void mergeResponses(final AccessPolicyEntity clientEntity, final Map<NodeIdentifier, AccessPolicyEntity> entityMap,
+                                  final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+        accessPolicyEntityMerger.merge(clientEntity, entityMap);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java
new file mode 100644
index 0000000..31996cb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/SearchUsersEndpointMerger.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.TenantsEntity;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class SearchUsersEndpointMerger implements EndpointResponseMerger {
+    public static final Pattern SEARCH_TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/search-results");
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        return "GET".equalsIgnoreCase(method) && SEARCH_TENANTS_URI_PATTERN.matcher(uri.getPath()).matches();
+    }
+
+    @Override
+    public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
+        if (!canHandle(uri, method)) {
+            throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
+        }
+
+        final TenantsEntity responseEntity = clientResponse.getClientResponse().getEntity(TenantsEntity.class);
+        final Collection<TenantEntity> userEntities = responseEntity.getUsers();
+        final Collection<TenantEntity> userGroupEntities = responseEntity.getUserGroups();
+
+        for (final NodeResponse nodeResponse : successfulResponses) {
+            final TenantsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(TenantsEntity.class);
+
+            // only retain users/groups that all nodes agree on
+            userEntities.retainAll(nodeResponseEntity.getUsers());
+            userGroupEntities.retainAll(nodeResponseEntity.getUserGroups());
+        }
+
+        // create a new client response
+        return new NodeResponse(clientResponse, responseEntity);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java
new file mode 100644
index 0000000..afac159
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserEndpointMerger.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.UserEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class UserEndpointMerger extends AbstractSingleEntityEndpoint<UserEntity> implements EndpointResponseMerger {
+    public static final Pattern USERS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users");
+    public static final Pattern USER_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users/[a-f0-9\\-]{36}");
+    private final UserEntityMerger userEntityMerger = new UserEntityMerger();
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_URI_PATTERN.matcher(uri.getPath()).matches())) {
+            return true;
+        } else if ("POST".equalsIgnoreCase(method) && USERS_URI_PATTERN.matcher(uri.getPath()).matches()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    protected Class<UserEntity> getEntityClass() {
+        return UserEntity.class;
+    }
+
+    @Override
+    protected void mergeResponses(final UserEntity clientEntity, final Map<NodeIdentifier, UserEntity> entityMap,
+                                  final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+        userEntityMerger.merge(clientEntity, entityMap);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java
new file mode 100644
index 0000000..ca2d6e1
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupEndpointMerger.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.UserGroupEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class UserGroupEndpointMerger extends AbstractSingleEntityEndpoint<UserGroupEntity> implements EndpointResponseMerger {
+    public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups");
+    public static final Pattern USER_GROUP_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups/[a-f0-9\\-]{36}");
+    private final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger();
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_GROUP_URI_PATTERN.matcher(uri.getPath()).matches())) {
+            return true;
+        } else if ("POST".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches()) {
+            return true;
+        }
+
+        return false;
+    }
+
+    @Override
+    protected Class<UserGroupEntity> getEntityClass() {
+        return UserGroupEntity.class;
+    }
+
+    @Override
+    protected void mergeResponses(final UserGroupEntity clientEntity, final Map<NodeIdentifier, UserGroupEntity> entityMap,
+                                  final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+        userGroupEntityMerger.merge(clientEntity, entityMap);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java
new file mode 100644
index 0000000..3b18814
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UserGroupsEndpointMerger.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.UserGroupsEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+import org.apache.nifi.web.api.entity.UserGroupsEntity;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class UserGroupsEndpointMerger implements EndpointResponseMerger {
+    public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups");
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        return "GET".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches();
+    }
+
+    @Override
+    public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
+        if (!canHandle(uri, method)) {
+            throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
+        }
+
+        final UserGroupsEntity responseEntity = clientResponse.getClientResponse().getEntity(UserGroupsEntity.class);
+        final Collection<UserGroupEntity> userGroupEntities = responseEntity.getUserGroups();
+
+        final Map<String, Map<NodeIdentifier, UserGroupEntity>> entityMap = new HashMap<>();
+        for (final NodeResponse nodeResponse : successfulResponses) {
+            final UserGroupsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UserGroupsEntity.class);
+            final Collection<UserGroupEntity> nodeUserGroupEntities = nodeResponseEntity.getUserGroups();
+
+            // only retain user groups that all nodes agree on
+            userGroupEntities.retainAll(nodeUserGroupEntities);
+
+            for (final UserGroupEntity nodeUserGroupEntity : nodeUserGroupEntities) {
+                final NodeIdentifier nodeId = nodeResponse.getNodeId();
+                Map<NodeIdentifier, UserGroupEntity> innerMap = entityMap.get(nodeId);
+                if (innerMap == null) {
+                    innerMap = new HashMap<>();
+                    entityMap.put(nodeUserGroupEntity.getId(), innerMap);
+                }
+
+                innerMap.put(nodeResponse.getNodeId(), nodeUserGroupEntity);
+            }
+        }
+
+        UserGroupsEntityMerger.mergeUserGroups(userGroupEntities, entityMap);
+
+        // create a new client response
+        return new NodeResponse(clientResponse, responseEntity);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java
new file mode 100644
index 0000000..581b359
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/UsersEndpointMerger.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.UsersEntityMerger;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserEntity;
+import org.apache.nifi.web.api.entity.UsersEntity;
+
+import java.net.URI;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class UsersEndpointMerger implements EndpointResponseMerger {
+    public static final Pattern TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users");
+
+    @Override
+    public boolean canHandle(final URI uri, final String method) {
+        return "GET".equalsIgnoreCase(method) && TENANTS_URI_PATTERN.matcher(uri.getPath()).matches();
+    }
+
+    @Override
+    public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
+        if (!canHandle(uri, method)) {
+            throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
+        }
+
+        final UsersEntity responseEntity = clientResponse.getClientResponse().getEntity(UsersEntity.class);
+        final Collection<UserEntity> userEntities = responseEntity.getUsers();
+
+        final Map<String, Map<NodeIdentifier, UserEntity>> entityMap = new HashMap<>();
+        for (final NodeResponse nodeResponse : successfulResponses) {
+            final UsersEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UsersEntity.class);
+            final Collection<UserEntity> nodeUserEntities = nodeResponseEntity.getUsers();
+
+            // only retain users that all nodes agree on
+            userEntities.retainAll(nodeUserEntities);
+
+            for (final UserEntity nodeUserEntity : nodeUserEntities) {
+                final NodeIdentifier nodeId = nodeResponse.getNodeId();
+                Map<NodeIdentifier, UserEntity> innerMap = entityMap.get(nodeId);
+                if (innerMap == null) {
+                    innerMap = new HashMap<>();
+                    entityMap.put(nodeUserEntity.getId(), innerMap);
+                }
+
+                innerMap.put(nodeResponse.getNodeId(), nodeUserEntity);
+            }
+        }
+
+        UsersEntityMerger.mergeUsers(userEntities, entityMap);
+
+        // create a new client response
+        return new NodeResponse(clientResponse, responseEntity);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java
new file mode 100644
index 0000000..88a0faf
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMerger.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AccessPolicyDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class AccessPolicyEntityMerger implements ComponentEntityMerger<AccessPolicyEntity> {
+    @Override
+    public void merge(AccessPolicyEntity clientEntity, Map<NodeIdentifier, AccessPolicyEntity> entityMap) {
+        ComponentEntityMerger.super.merge(clientEntity, entityMap);
+    }
+
+    /**
+     * Merges the AccessPolicyEntity responses.
+     *
+     * @param clientEntity the entity being returned to the client
+     * @param entityMap all node responses
+     */
+    public void mergeComponents(final AccessPolicyEntity clientEntity, final Map<NodeIdentifier, AccessPolicyEntity> entityMap) {
+        final AccessPolicyDTO clientDto = clientEntity.getComponent();
+        final Map<NodeIdentifier, AccessPolicyDTO> dtoMap = new HashMap<>();
+        for (final Map.Entry<NodeIdentifier, AccessPolicyEntity> entry : entityMap.entrySet()) {
+            final AccessPolicyEntity nodeAccessPolicyEntity = entry.getValue();
+            final AccessPolicyDTO nodeAccessPolicyDto = nodeAccessPolicyEntity.getComponent();
+            dtoMap.put(entry.getKey(), nodeAccessPolicyDto);
+        }
+
+        mergeDtos(clientDto, dtoMap);
+    }
+
+    private static void mergeDtos(final AccessPolicyDTO clientDto, final Map<NodeIdentifier, AccessPolicyDTO> dtoMap) {
+        // if unauthorized for the client dto, simple return
+        if (clientDto == null) {
+            return;
+        }
+
+        final Set<TenantEntity> users = new HashSet<>(clientDto.getUsers());
+        final Set<TenantEntity> userGroups = new HashSet<>(clientDto.getUserGroups());
+
+        for (final Map.Entry<NodeIdentifier, AccessPolicyDTO> nodeEntry : dtoMap.entrySet()) {
+            final AccessPolicyDTO nodeAccessPolicy = nodeEntry.getValue();
+
+            if (nodeAccessPolicy != null) {
+                users.retainAll(nodeAccessPolicy.getUsers());
+                userGroups.retainAll(nodeAccessPolicy.getUserGroups());
+            }
+        }
+
+        clientDto.setUsers(users);
+        clientDto.setUserGroups(userGroups);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java
new file mode 100644
index 0000000..e1e3fcc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserEntityMerger.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.UserDTO;
+import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.UserEntity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class UserEntityMerger implements ComponentEntityMerger<UserEntity> {
+
+    @Override
+    public void merge(UserEntity clientEntity, Map<NodeIdentifier, UserEntity> entityMap) {
+        ComponentEntityMerger.super.merge(clientEntity, entityMap);
+    }
+
+    /**
+     * Merges the UserEntity responses.
+     *
+     * @param clientEntity the entity being returned to the client
+     * @param entityMap all node responses
+     */
+    public void mergeComponents(final UserEntity clientEntity, final Map<NodeIdentifier, UserEntity> entityMap) {
+        final UserDTO clientDto = clientEntity.getComponent();
+        final Map<NodeIdentifier, UserDTO> dtoMap = new HashMap<>();
+        for (final Map.Entry<NodeIdentifier, UserEntity> entry : entityMap.entrySet()) {
+            final UserEntity nodeUserEntity = entry.getValue();
+            final UserDTO nodeUserDto = nodeUserEntity.getComponent();
+            dtoMap.put(entry.getKey(), nodeUserDto);
+        }
+
+        mergeDtos(clientDto, dtoMap);
+    }
+
+    private static void mergeDtos(final UserDTO clientDto, final Map<NodeIdentifier, UserDTO> dtoMap) {
+        // if unauthorized for the client dto, simple return
+        if (clientDto == null) {
+            return;
+        }
+
+        final Set<AccessPolicySummaryEntity> accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies());
+        final Set<TenantEntity> tenantEntities = new HashSet<>(clientDto.getUserGroups());
+
+        for (final Map.Entry<NodeIdentifier, UserDTO> nodeEntry : dtoMap.entrySet()) {
+            final UserDTO nodeUser = nodeEntry.getValue();
+
+            if (nodeUser != null) {
+                accessPolicyEntities.retainAll(nodeUser.getAccessPolicies());
+                tenantEntities.retainAll(nodeUser.getUserGroups());
+            }
+        }
+
+        clientDto.setAccessPolicies(accessPolicyEntities);
+        clientDto.setUserGroups(tenantEntities);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java
new file mode 100644
index 0000000..8142b45
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupEntityMerger.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.UserGroupDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class UserGroupEntityMerger implements ComponentEntityMerger<UserGroupEntity> {
+    @Override
+    public void merge(UserGroupEntity clientEntity, Map<NodeIdentifier, UserGroupEntity> entityMap) {
+        ComponentEntityMerger.super.merge(clientEntity, entityMap);
+    }
+
+    /**
+     * Merges the UserGroupEntity responses.
+     *
+     * @param clientEntity the entity being returned to the client
+     * @param entityMap all node responses
+     */
+    public void mergeComponents(final UserGroupEntity clientEntity, final Map<NodeIdentifier, UserGroupEntity> entityMap) {
+        final UserGroupDTO clientDto = clientEntity.getComponent();
+        final Map<NodeIdentifier, UserGroupDTO> dtoMap = new HashMap<>();
+        for (final Map.Entry<NodeIdentifier, UserGroupEntity> entry : entityMap.entrySet()) {
+            final UserGroupEntity nodeUserGroupEntity = entry.getValue();
+            final UserGroupDTO nodeUserGroupDto = nodeUserGroupEntity.getComponent();
+            dtoMap.put(entry.getKey(), nodeUserGroupDto);
+        }
+
+        mergeDtos(clientDto, dtoMap);
+    }
+
+    private static void mergeDtos(final UserGroupDTO clientDto, final Map<NodeIdentifier, UserGroupDTO> dtoMap) {
+        // if unauthorized for the client dto, simple return
+        if (clientDto == null) {
+            return;
+        }
+
+        final Set<AccessPolicyEntity> accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies());
+        final Set<TenantEntity> userEntities = new HashSet<>(clientDto.getUsers());
+
+        for (final Map.Entry<NodeIdentifier, UserGroupDTO> nodeEntry : dtoMap.entrySet()) {
+            final UserGroupDTO nodeUserGroup = nodeEntry.getValue();
+
+            if (nodeUserGroup != null) {
+                accessPolicyEntities.retainAll(nodeUserGroup.getAccessPolicies());
+                userEntities.retainAll(nodeUserGroup.getUsers());
+            }
+        }
+
+        clientDto.setAccessPolicies(accessPolicyEntities);
+        clientDto.setUsers(userEntities);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java
new file mode 100644
index 0000000..6dbb0c6
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UserGroupsEntityMerger.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class UserGroupsEntityMerger implements ComponentEntityMerger<UserGroupEntity> {
+    private static final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger();
+
+    /**
+     * Merges multiple UserGroupEntity responses.
+     *
+     * @param userGroupEntities entities being returned to the client
+     * @param entityMap all node responses
+     */
+    public static void mergeUserGroups(final Collection<UserGroupEntity> userGroupEntities, final Map<String, Map<NodeIdentifier, UserGroupEntity>> entityMap) {
+        for (final UserGroupEntity entity : userGroupEntities) {
+            userGroupEntityMerger.merge(entity, entityMap.get(entity.getId()));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java
new file mode 100644
index 0000000..4874336
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/UsersEntityMerger.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.entity.UserEntity;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class UsersEntityMerger implements ComponentEntityMerger<UserEntity> {
+    private static final UserEntityMerger userEntityMerger = new UserEntityMerger();
+
+    /**
+     * Merges multiple UserEntity responses.
+     *
+     * @param userEntities entities being returned to the client
+     * @param entityMap all node responses
+     */
+    public static void mergeUsers(final Collection<UserEntity> userEntities, final Map<String, Map<NodeIdentifier, UserEntity>> entityMap) {
+        for (final UserEntity entity : userEntities) {
+            userEntityMerger.merge(entity, entityMap.get(entity.getId()));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java
new file mode 100644
index 0000000..c8a06ed
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/endpoints/AccessPolicyEndpointMergerTest.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.UUID;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AccessPolicyEndpointMergerTest {
+
+    @Test
+    public void testCanHandle() throws Exception {
+        final AccessPolicyEndpointMerger merger = new AccessPolicyEndpointMerger();
+        assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "POST"));
+        assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "GET"));
+        assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "PUT"));
+        assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/" + UUID.randomUUID().toString()), "PUT"));
+        assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/Read/flow"), "GET"));
+        assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/flow"), "GET"));
+        assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/processors/" + UUID.randomUUID().toString()), "GET"));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
index d90c49b..b5eff63 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
@@ -27,6 +27,7 @@ import org.apache.commons.collections4.map.MultiValueMap;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
 import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.cluster.coordination.ClusterCoordinator;
 import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
 import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
@@ -157,9 +158,9 @@ public class TestThreadPoolRequestReplicator {
             final Entity entity = new ProcessorEntity();
 
             // set the user
-            final NiFiUser proxy2 = new StandardNiFiUser(proxyIdentity2);
-            final NiFiUser proxy1 = new StandardNiFiUser(proxyIdentity1, proxy2);
-            final NiFiUser user = new StandardNiFiUser(userIdentity, proxy1);
+            final NiFiUser proxy2 = new Builder().identity(proxyIdentity2).build();
+            final NiFiUser proxy1 = new Builder().identity(proxyIdentity1).chain(proxy2).build();
+            final NiFiUser user = new Builder().identity(userIdentity).chain(proxy1).build();
             final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(user));
             SecurityContextHolder.getContext().setAuthentication(authentication);
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java
new file mode 100644
index 0000000..70a941a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/AccessPolicyEntityMergerTest.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AccessPolicyDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.TenantDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class AccessPolicyEntityMergerTest {
+
+    @Test
+    public void testMergeAccessPolicy() throws Exception {
+        final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
+        final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
+
+        final PermissionsDTO permissed = new PermissionsDTO();
+        permissed.setCanRead(true);
+        permissed.setCanWrite(true);
+
+        final TenantDTO user1DTO = new TenantDTO();
+        user1DTO.setId("user-1");
+
+        final TenantEntity user1Entity = new TenantEntity();
+        user1Entity.setPermissions(permissed);
+        user1Entity.setId(user1DTO.getId());
+        user1Entity.setComponent(user1DTO);
+
+        final TenantDTO user2DTO = new TenantDTO();
+        user1DTO.setId("user-2");
+
+        final TenantEntity user2Entity = new TenantEntity();
+        user2Entity.setPermissions(permissed);
+        user2Entity.setId(user2DTO.getId());
+        user2Entity.setComponent(user2DTO);
+
+        final AccessPolicyDTO accessPolicy1DTO = new AccessPolicyDTO();
+        accessPolicy1DTO.setId("policy-1");
+        accessPolicy1DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
+        accessPolicy1DTO.setUserGroups(Stream.of(user2Entity).collect(Collectors.toSet()));
+
+        final AccessPolicyEntity accessPolicy1Entity = new AccessPolicyEntity();
+        accessPolicy1Entity.setPermissions(permissed);
+        accessPolicy1Entity.setId(accessPolicy1DTO.getId());
+        accessPolicy1Entity.setComponent(accessPolicy1DTO);
+
+        final AccessPolicyDTO accessPolicy2DTO = new AccessPolicyDTO();
+        accessPolicy2DTO.setId("policy-2");
+        accessPolicy2DTO.setUsers(Stream.of(user1Entity).collect(Collectors.toSet()));
+        accessPolicy2DTO.setUserGroups(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
+
+        final AccessPolicyEntity accessPolicy2Entity = new AccessPolicyEntity();
+        accessPolicy2Entity.setPermissions(permissed);
+        accessPolicy2Entity.setId(accessPolicy2DTO.getId());
+        accessPolicy2Entity.setComponent(accessPolicy2DTO);
+
+        final Map<NodeIdentifier, AccessPolicyEntity> nodeMap = new HashMap<>();
+        nodeMap.put(node1, accessPolicy1Entity);
+        nodeMap.put(node2, accessPolicy2Entity);
+
+        final AccessPolicyEntityMerger merger = new AccessPolicyEntityMerger();
+        merger.merge(accessPolicy1Entity, nodeMap);
+
+        assertEquals(1, accessPolicy1DTO.getUserGroups().size());
+        assertTrue(accessPolicy1DTO.getUsers().contains(user1Entity));
+
+        assertEquals(1, accessPolicy1DTO.getUserGroups().size());
+        assertTrue(accessPolicy1DTO.getUserGroups().contains(user2Entity));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java
new file mode 100644
index 0000000..03db8b4
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserEntityMergerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AccessPolicySummaryDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.TenantDTO;
+import org.apache.nifi.web.api.dto.UserDTO;
+import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.UserEntity;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class UserEntityMergerTest {
+
+    @Test
+    public void testMergeAccessPolicy() throws Exception {
+        final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
+        final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
+
+        final PermissionsDTO permissed = new PermissionsDTO();
+        permissed.setCanRead(true);
+        permissed.setCanWrite(true);
+
+        final TenantDTO userGroup1DTO = new TenantDTO();
+        userGroup1DTO.setId("user-group-1");
+
+        final TenantEntity userGroup1Entity = new TenantEntity();
+        userGroup1Entity.setPermissions(permissed);
+        userGroup1Entity.setId(userGroup1DTO.getId());
+        userGroup1Entity.setComponent(userGroup1DTO);
+
+        final TenantDTO userGroup2DTO = new TenantDTO();
+        userGroup1DTO.setId("user-group-2");
+
+        final TenantEntity userGroup2Entity = new TenantEntity();
+        userGroup2Entity.setPermissions(permissed);
+        userGroup2Entity.setId(userGroup2DTO.getId());
+        userGroup2Entity.setComponent(userGroup2DTO);
+
+        final AccessPolicySummaryDTO policy1DTO = new AccessPolicySummaryDTO();
+        policy1DTO.setId("policy-1");
+
+        final AccessPolicySummaryEntity policy1Entity = new AccessPolicySummaryEntity();
+        policy1Entity.setPermissions(permissed);
+        policy1Entity.setId(policy1DTO.getId());
+        policy1Entity.setComponent(policy1DTO);
+
+        final AccessPolicySummaryDTO policy2DTO = new AccessPolicySummaryDTO();
+        policy2DTO.setId("policy-2");
+
+        final AccessPolicySummaryEntity policy2Entity = new AccessPolicySummaryEntity();
+        policy2Entity.setPermissions(permissed);
+        policy2Entity.setId(policy2DTO.getId());
+        policy2Entity.setComponent(policy2DTO);
+
+        final UserDTO user1DTO = new UserDTO();
+        user1DTO.setId("user-1");
+        user1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet()));
+        user1DTO.setUserGroups(Stream.of(userGroup2Entity).collect(Collectors.toSet()));
+
+        final UserEntity user1Entity = new UserEntity();
+        user1Entity.setPermissions(permissed);
+        user1Entity.setId(user1DTO.getId());
+        user1Entity.setComponent(user1DTO);
+
+        final UserDTO user2DTO = new UserDTO();
+        user2DTO.setId("user-2");
+        user2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet()));
+        user2DTO.setUserGroups(Stream.of(userGroup1Entity, userGroup2Entity).collect(Collectors.toSet()));
+
+        final UserEntity user2Entity = new UserEntity();
+        user2Entity.setPermissions(permissed);
+        user2Entity.setId(user2DTO.getId());
+        user2Entity.setComponent(user2DTO);
+
+        final Map<NodeIdentifier, UserEntity> nodeMap = new HashMap<>();
+        nodeMap.put(node1, user1Entity);
+        nodeMap.put(node2, user2Entity);
+
+        final UserEntityMerger merger = new UserEntityMerger();
+        merger.merge(user1Entity, nodeMap);
+
+        assertEquals(1, user1DTO.getUserGroups().size());
+        assertTrue(user1DTO.getAccessPolicies().contains(policy1Entity));
+
+        assertEquals(1, user1DTO.getUserGroups().size());
+        assertTrue(user1DTO.getUserGroups().contains(userGroup2Entity));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java
new file mode 100644
index 0000000..cc975cb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/UserGroupEntityMergerTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.cluster.manager;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.AccessPolicyDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.TenantDTO;
+import org.apache.nifi.web.api.dto.UserGroupDTO;
+import org.apache.nifi.web.api.entity.AccessPolicyEntity;
+import org.apache.nifi.web.api.entity.TenantEntity;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+
+public class UserGroupEntityMergerTest {
+
+    @Test
+    public void testMergeAccessPolicy() throws Exception {
+        final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
+        final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
+
+        final PermissionsDTO permissed = new PermissionsDTO();
+        permissed.setCanRead(true);
+        permissed.setCanWrite(true);
+
+        final TenantDTO user1DTO = new TenantDTO();
+        user1DTO.setId("user-1");
+
+        final TenantEntity user1Entity = new TenantEntity();
+        user1Entity.setPermissions(permissed);
+        user1Entity.setId(user1DTO.getId());
+        user1Entity.setComponent(user1DTO);
+
+        final TenantDTO user2DTO = new TenantDTO();
+        user1DTO.setId("user-2");
+
+        final TenantEntity user2Entity = new TenantEntity();
+        user2Entity.setPermissions(permissed);
+        user2Entity.setId(user2DTO.getId());
+        user2Entity.setComponent(user2DTO);
+
+        final AccessPolicyDTO policy1DTO = new AccessPolicyDTO();
+        policy1DTO.setId("policy-1");
+
+        final AccessPolicyEntity policy1Entity = new AccessPolicyEntity();
+        policy1Entity.setPermissions(permissed);
+        policy1Entity.setId(policy1DTO.getId());
+        policy1Entity.setComponent(policy1DTO);
+
+        final AccessPolicyDTO policy2DTO = new AccessPolicyDTO();
+        policy2DTO.setId("policy-2");
+
+        final AccessPolicyEntity policy2Entity = new AccessPolicyEntity();
+        policy2Entity.setPermissions(permissed);
+        policy2Entity.setId(policy2DTO.getId());
+        policy2Entity.setComponent(policy2DTO);
+
+        final UserGroupDTO userGroup1DTO = new UserGroupDTO();
+        userGroup1DTO.setId("user-1");
+        userGroup1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet()));
+        userGroup1DTO.setUsers(Stream.of(user2Entity).collect(Collectors.toSet()));
+
+        final UserGroupEntity userGroup1Entity = new UserGroupEntity();
+        userGroup1Entity.setPermissions(permissed);
+        userGroup1Entity.setId(userGroup1DTO.getId());
+        userGroup1Entity.setComponent(userGroup1DTO);
+
+        final UserGroupDTO userGroup2DTO = new UserGroupDTO();
+        userGroup2DTO.setId("user-2");
+        userGroup2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet()));
+        userGroup2DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
+
+        final UserGroupEntity userGroup2Entity = new UserGroupEntity();
+        userGroup2Entity.setPermissions(permissed);
+        userGroup2Entity.setId(userGroup2DTO.getId());
+        userGroup2Entity.setComponent(userGroup2DTO);
+
+        final Map<NodeIdentifier, UserGroupEntity> nodeMap = new HashMap<>();
+        nodeMap.put(node1, userGroup1Entity);
+        nodeMap.put(node2, userGroup2Entity);
+
+        final UserGroupEntityMerger merger = new UserGroupEntityMerger();
+        merger.merge(userGroup1Entity, nodeMap);
+
+        assertEquals(1, userGroup1DTO.getUsers().size());
+        assertTrue(userGroup1DTO.getAccessPolicies().contains(policy1Entity));
+
+        assertEquals(1, userGroup1DTO.getUsers().size());
+        assertTrue(userGroup1DTO.getUsers().contains(user2Entity));
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java
index 6e2b9fe..0317584 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/cluster/protocol/DataFlow.java
@@ -32,7 +32,7 @@ public interface DataFlow {
 
     /**
      * @return the raw byte array of the Authorizer's fingerprint,
-     *              null when not using a sub-class of AbstractPolicyBasedAuthorizer
+     *              null when not using a ManagedAuthorizer
      */
     public byte[] getAuthorizerFingerprint();
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index 57522f5..9d00f49 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -188,6 +188,10 @@
             <version>4.3.1.201605051710-r</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-authorizer</artifactId>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
index b2c1628..f2387c2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java
@@ -17,8 +17,9 @@
 package org.apache.nifi.controller;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.authorization.ManagedAuthorizer;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.cluster.ConnectionException;
 import org.apache.nifi.cluster.coordination.ClusterCoordinator;
@@ -572,8 +573,8 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
     }
 
     private byte[] getAuthorizerFingerprint() {
-        final boolean isInternalAuthorizer = (authorizer instanceof AbstractPolicyBasedAuthorizer);
-        return isInternalAuthorizer ? ((AbstractPolicyBasedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null;
+        final boolean isInternalAuthorizer = AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer);
+        return isInternalAuthorizer ? ((ManagedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
index 6f1e8e1..01dd35e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowSynchronizer.java
@@ -18,8 +18,10 @@ package org.apache.nifi.controller;
 
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.authorization.ManagedAuthorizer;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.cluster.protocol.DataFlow;
 import org.apache.nifi.cluster.protocol.StandardDataFlow;
@@ -224,15 +226,15 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
         logger.trace("Getting Authorizer fingerprint from controller");
 
         final byte[] existingAuthFingerprint;
-        final AbstractPolicyBasedAuthorizer policyBasedAuthorizer;
+        final ManagedAuthorizer managedAuthorizer;
         final Authorizer authorizer = controller.getAuthorizer();
 
-        if (authorizer instanceof AbstractPolicyBasedAuthorizer) {
-            policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) authorizer;
-            existingAuthFingerprint = policyBasedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8);
+        if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            managedAuthorizer = (ManagedAuthorizer) authorizer;
+            existingAuthFingerprint = managedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8);
         } else {
             existingAuthFingerprint = null;
-            policyBasedAuthorizer = null;
+            managedAuthorizer = null;
         }
 
         final Set<String> missingComponents = new HashSet<>();
@@ -249,7 +251,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
             if (existingFlowEmpty) {
                 configuration = parseFlowBytes(proposedFlow.getFlow());
                 if (configuration != null) {
-                    logger.trace("Checking bunde compatibility");
+                    logger.trace("Checking bundle compatibility");
                     checkBundleCompatibility(configuration);
                 }
             } else {
@@ -272,7 +274,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
 
         logger.trace("Checking authorizer inheritability");
 
-        final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(existingDataFlow, proposedFlow);
+        final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(authorizer, existingDataFlow, proposedFlow);
         if (!authInheritability.isInheritable() && authInheritability.getReason() != null) {
             throw new UninheritableFlowException("Proposed Authorizer is not inheritable by the flow controller because of Authorizer differences: " + authInheritability.getReason());
         }
@@ -415,10 +417,10 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
             }
 
             // if auths are inheritable and we have a policy based authorizer, then inherit
-            if (authInheritability.isInheritable() && policyBasedAuthorizer != null) {
+            if (authInheritability.isInheritable() && managedAuthorizer != null) {
                 logger.trace("Inheriting authorizations");
                 final String proposedAuthFingerprint = new String(proposedFlow.getAuthorizerFingerprint(), StandardCharsets.UTF_8);
-                policyBasedAuthorizer.inheritFingerprint(proposedAuthFingerprint);
+                managedAuthorizer.inheritFingerprint(proposedAuthFingerprint);
             }
 
             logger.debug("Finished syncing flows");
@@ -1391,7 +1393,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
      * @param proposedFlow the proposed DataFlow
      * @return the AuthorizerInheritability result
      */
-    public AuthorizerInheritability checkAuthorizerInheritability(final DataFlow existingFlow, final DataFlow proposedFlow) {
+    private AuthorizerInheritability checkAuthorizerInheritability(final Authorizer authorizer, final DataFlow existingFlow, final DataFlow proposedFlow) {
         final byte[] existing = existingFlow.getAuthorizerFingerprint();
         final byte[] proposed = proposedFlow.getAuthorizerFingerprint();
 
@@ -1414,15 +1416,20 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
 
         // both are internal, but not the same
         if (!Arrays.equals(existing, proposed)) {
-            final byte[] emptyAuthBytes = AbstractPolicyBasedAuthorizer.EMPTY_FINGERPRINT.getBytes(StandardCharsets.UTF_8);
+            if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+                final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
 
-            // if current is empty then we can take all the proposed authorizations
-            // otherwise they are both internal authorizers and don't match so we can't proceed
-            if (Arrays.equals(emptyAuthBytes, existing)) {
-                return AuthorizerInheritability.inheritable();
+                try {
+                    // if the configurations are not equal, see if the manager indicates the proposed configuration is inheritable
+                    managedAuthorizer.checkInheritability(new String(proposed, StandardCharsets.UTF_8));
+                    return AuthorizerInheritability.inheritable();
+                } catch (final UninheritableAuthorizationsException e) {
+                    return AuthorizerInheritability.uninheritable("Proposed Authorizations do not match current Authorizations: " + e.getMessage());
+                }
             } else {
+                // should never hit since the existing is only null when authorizer is not managed
                 return AuthorizerInheritability.uninheritable(
-                        "Proposed Authorizations do not match current Authorizations");
+                        "Proposed Authorizations do not match current Authorizations and are not configured with an internal Authorizer");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
index 14d3dcc..8326889 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
@@ -18,7 +18,9 @@ package org.apache.nifi.nar;
 
 import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
 import org.apache.nifi.authentication.LoginIdentityProvider;
+import org.apache.nifi.authorization.AccessPolicyProvider;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.UserGroupProvider;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.bundle.BundleCoordinate;
 import org.apache.nifi.components.ConfigurableComponent;
@@ -82,6 +84,8 @@ public class ExtensionManager {
         definitionMap.put(ReportingTask.class, new HashSet<>());
         definitionMap.put(ControllerService.class, new HashSet<>());
         definitionMap.put(Authorizer.class, new HashSet<>());
+        definitionMap.put(UserGroupProvider.class, new HashSet<>());
+        definitionMap.put(AccessPolicyProvider.class, new HashSet<>());
         definitionMap.put(LoginIdentityProvider.class, new HashSet<>());
         definitionMap.put(ProvenanceRepository.class, new HashSet<>());
         definitionMap.put(ComponentStatusRepository.class, new HashSet<>());

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java
index e7faa02..0be99dc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/NarThreadContextClassLoader.java
@@ -17,7 +17,9 @@
 package org.apache.nifi.nar;
 
 import org.apache.nifi.authentication.LoginIdentityProvider;
+import org.apache.nifi.authorization.AccessPolicyProvider;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.UserGroupProvider;
 import org.apache.nifi.bundle.Bundle;
 import org.apache.nifi.components.Validator;
 import org.apache.nifi.components.state.StateProvider;
@@ -64,6 +66,8 @@ public class NarThreadContextClassLoader extends URLClassLoader {
         narSpecificClasses.add(StreamCallback.class);
         narSpecificClasses.add(ControllerService.class);
         narSpecificClasses.add(Authorizer.class);
+        narSpecificClasses.add(UserGroupProvider.class);
+        narSpecificClasses.add(AccessPolicyProvider.class);
         narSpecificClasses.add(LoginIdentityProvider.class);
         narSpecificClasses.add(ProvenanceRepository.class);
         narSpecificClasses.add(ComponentStatusRepository.class);

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 1816297..8e740a9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -141,7 +141,7 @@
         <nifi.security.truststoreType />
         <nifi.security.truststorePasswd />
         <nifi.security.needClientAuth />
-        <nifi.security.user.authorizer>file-provider</nifi.security.user.authorizer>
+        <nifi.security.user.authorizer>managed-authorizer</nifi.security.user.authorizer>
         <nifi.security.user.login.identity.provider />
         <nifi.security.x509.principal.extractor />
         <nifi.security.ocsp.responder.url />

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
index 46bf637..247c0e8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
@@ -14,13 +14,105 @@
   limitations under the License.
 -->
 <!--
-    This file lists the authority providers to use when running securely. In order
-    to use a specific provider it must be configured here and it's identifier
-    must be specified in the nifi.properties file.
+    This file lists the userGroupProviders, accessPolicyProviders, and authorizers to use when running securely. In order
+    to use a specific authorizer it must be configured here and it's identifier must be specified in the nifi.properties file.
+    If the authorizer is a managedAuthorizer, it may need to be configured with an accessPolicyProvider and an userGroupProvider.
+    This file allows for configuration of them, but they must be configured in order:
+
+    ...
+    all userGroupProviders
+    all accessPolicyProviders
+    all Authorizers
+    ...
 -->
 <authorizers>
 
     <!--
+        The FileUserGroupProvider will provide support for managing users and groups which is backed by a file
+        on the local file system.
+
+        - Users File - The file where the FileUserGroupProvider will store users and groups.
+
+        - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
+            be used to load the users and groups into the Users File.
+
+        - Initial User Identity [unique key] - The identity of a users and systems to seed the Users File. The name of
+            each property must be unique, for example: "Initial User Identity A", "Initial User Identity B",
+            "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities,
+            so the values should be the unmapped identities (i.e. full DN from a certificate).
+    -->
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
+        <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Initial User Identity 1"></property>
+    </userGroupProvider>
+
+    <!--
+        The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file
+        on the local file system.
+
+        - User Group Provider - The identifier for an User Group Provider defined above that will be used to access
+            users and groups for use in the managed access policies.
+
+        - Authorizations File - The file where the FileAccessPolicyProvider will store policies.
+
+        - Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and
+            given the ability to create additional users, groups, and policies. The value of this property could be
+            a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there
+            are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified.
+
+            NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity,
+            so the value should be the unmapped identity. This identity must be found in the configured User Group Provider.
+
+        - Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
+            converted to the new authorizations model. If this property is specified then an Initial Admin Identity can
+            not be specified, and this property will only be used when there are no other users, groups, and policies defined.
+
+            NOTE: Any users in the legacy users file must be found in the configured User Group Provider.
+
+        - Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node
+            should be defined, so that every node knows about every other node. If not clustered these properties can be ignored.
+            The name of each property must be unique, for example for a three node cluster:
+            "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities,
+            so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found
+            in the configured User Group Provider.
+    -->
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">file-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
+        <property name="Initial Admin Identity"></property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Node Identity 1"></property>
+    </accessPolicyProvider>
+
+    <!--
+        The StandardManagedAuthorizer. This authorizer implementation must be configured with the
+        Access Policy Provider which it will use to access and manage users, groups, and policies.
+        These users, groups, and policies will be used to make all access decisions during authorization
+        requests.
+
+        - Access Policy Provider - The identifier for an Access Policy Provider defined above.
+    -->
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
+    </authorizer>
+
+    <!--
+        NOTE: This Authorizer has been replaced with the more granular approach configured above with the Standard
+        Managed Authorizer. However, it is still available for backwards compatibility reasons.
+
         The FileAuthorizer is NiFi's provided authorizer and has the following properties:
 
         - Authorizations File - The file where the FileAuthorizer will store policies.
@@ -48,7 +140,7 @@
             NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities,
             so the values should be the unmapped identities (i.e. full DN from a certificate).
     -->
-    <authorizer>
+    <!-- <authorizer>
         <identifier>file-provider</identifier>
         <class>org.apache.nifi.authorization.FileAuthorizer</class>
         <property name="Authorizations File">./conf/authorizations.xml</property>
@@ -56,9 +148,7 @@
         <property name="Initial Admin Identity"></property>
         <property name="Legacy Authorized Users File"></property>
 
-        <!-- Provide the identity (typically a DN) of each node when clustered, see above description of Node Identity.
         <property name="Node Identity 1"></property>
-        <property name="Node Identity 2"></property>
-        -->
     </authorizer>
+    -->
 </authorizers>
\ No newline at end of file


[02/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java
index 873cd33..ea79675 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/StandardRootGroupPort.java
@@ -16,24 +16,6 @@
  */
 package org.apache.nifi.remote;
 
-import static java.util.Objects.requireNonNull;
-
-import java.io.IOException;
-import java.net.SocketTimeoutException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
 import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
@@ -41,9 +23,10 @@ import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.DataTransferAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.authorization.util.IdentityMapping;
 import org.apache.nifi.authorization.util.IdentityMappingUtil;
+import org.apache.nifi.authorization.util.UserGroupUtil;
 import org.apache.nifi.components.ValidationResult;
 import org.apache.nifi.connectable.ConnectableType;
 import org.apache.nifi.controller.AbstractPort;
@@ -73,6 +56,24 @@ import org.apache.nifi.util.NiFiProperties;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static java.util.Objects.requireNonNull;
+
 public class StandardRootGroupPort extends AbstractPort implements RootGroupPort {
 
     private static final String CATEGORY = "Site to Site";
@@ -362,8 +363,8 @@ public class StandardRootGroupPort extends AbstractPort implements RootGroupPort
         }
 
         final String identity = IdentityMappingUtil.mapIdentity(dn, identityMappings);
-
-        return checkUserAuthorization(new StandardNiFiUser(identity));
+        final Set<String> groups = UserGroupUtil.getUserGroups(authorizer, identity);
+        return checkUserAuthorization(new Builder().identity(identity).groups(groups).build());
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index dbb2aaf..36a9524 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -2559,6 +2559,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getDataTransferResource(port.getResource()))
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(false)
                 .action(RequestAction.WRITE)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
index cbae778..1444040 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -17,20 +17,6 @@
 package org.apache.nifi.web;
 
 import com.sun.jersey.core.util.MultivaluedMapImpl;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.action.Action;
 import org.apache.nifi.action.Component;
@@ -79,6 +65,21 @@ import org.apache.nifi.web.util.ClientResponseUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
 /**
  * Implements the NiFiWebConfigurationContext interface to support a context in both standalone and clustered environments.
  */
@@ -110,6 +111,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
             final AuthorizationRequest request = new AuthorizationRequest.Builder()
                     .resource(ResourceFactory.getFlowResource())
                     .identity(user.getIdentity())
+                    .groups(user.getGroups())
                     .anonymous(user.isAnonymous())
                     .accessAttempt(true)
                     .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java
index 999c832..689ce7b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/AccessPolicyResource.java
@@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponse;
 import com.wordnik.swagger.annotations.ApiResponses;
 import com.wordnik.swagger.annotations.Authorization;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -140,8 +140,8 @@ public class AccessPolicyResource extends ApplicationResource {
             ) @PathParam("resource") String rawResource) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         // parse the action and resource type
@@ -203,8 +203,8 @@ public class AccessPolicyResource extends ApplicationResource {
             ) final AccessPolicyEntity requestAccessPolicyEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
         }
 
         if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) {
@@ -294,8 +294,8 @@ public class AccessPolicyResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {
@@ -356,8 +356,8 @@ public class AccessPolicyResource extends ApplicationResource {
             ) final AccessPolicyEntity requestAccessPolicyEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
         }
 
         if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) {
@@ -454,8 +454,8 @@ public class AccessPolicyResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
         }
 
         if (isReplicateRequest()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index cb87ca2..0a3b0e0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -110,6 +110,7 @@ public class ControllerResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getControllerResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(action)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
index 3d6c3be..2a2c2b9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/CountersResource.java
@@ -94,6 +94,7 @@ public class CountersResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getCountersResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(action)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index a380aa7..14f2108 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -217,6 +217,7 @@ public class FlowResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getFlowResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)
@@ -2236,6 +2237,7 @@ public class FlowResource extends ApplicationResource {
             final AuthorizationRequest request = new AuthorizationRequest.Builder()
                     .resource(ResourceFactory.getControllerResource())
                     .identity(user.getIdentity())
+                    .groups(user.getGroups())
                     .anonymous(user.isAnonymous())
                     .accessAttempt(true)
                     .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
index 10663be..2aff9ca 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProvenanceResource.java
@@ -114,6 +114,7 @@ public class ProvenanceResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getProvenanceResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
index cd41ed9..56c62bc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ResourceResource.java
@@ -74,6 +74,7 @@ public class ResourceResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getResourceResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java
index 744a9f4..ce5b327 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SiteToSiteResource.java
@@ -112,6 +112,7 @@ public class SiteToSiteResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getSiteToSiteResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
index 04e8683..b000060 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SystemDiagnosticsResource.java
@@ -77,6 +77,7 @@ public class SystemDiagnosticsResource extends ApplicationResource {
         final AuthorizationRequest request = new AuthorizationRequest.Builder()
                 .resource(ResourceFactory.getSystemResource())
                 .identity(user.getIdentity())
+                .groups(user.getGroups())
                 .anonymous(user.isAnonymous())
                 .accessAttempt(true)
                 .action(RequestAction.READ)

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
index ab82ace..d489009 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
@@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponse;
 import com.wordnik.swagger.annotations.ApiResponses;
 import com.wordnik.swagger.annotations.Authorization;
 import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
@@ -149,8 +149,8 @@ public class TenantsResource extends ApplicationResource {
             ) final UserEntity requestUserEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (requestUserEntity == null || requestUserEntity.getComponent() == null) {
@@ -234,8 +234,8 @@ public class TenantsResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {
@@ -284,8 +284,8 @@ public class TenantsResource extends ApplicationResource {
     public Response getUsers() {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {
@@ -352,8 +352,8 @@ public class TenantsResource extends ApplicationResource {
             ) final UserEntity requestUserEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (requestUserEntity == null || requestUserEntity.getComponent() == null) {
@@ -448,8 +448,8 @@ public class TenantsResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (isReplicateRequest()) {
@@ -538,8 +538,8 @@ public class TenantsResource extends ApplicationResource {
             ) final UserGroupEntity requestUserGroupEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) {
@@ -623,8 +623,8 @@ public class TenantsResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {
@@ -673,8 +673,8 @@ public class TenantsResource extends ApplicationResource {
     public Response getUserGroups() {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {
@@ -740,8 +740,8 @@ public class TenantsResource extends ApplicationResource {
             ) final UserGroupEntity requestUserGroupEntity) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) {
@@ -836,8 +836,8 @@ public class TenantsResource extends ApplicationResource {
             @PathParam("id") final String id) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
         }
 
         if (isReplicateRequest()) {
@@ -897,7 +897,7 @@ public class TenantsResource extends ApplicationResource {
                     @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
             }
     )
-    public Response searchCluster(
+    public Response searchTenants(
             @ApiParam(
                     value = "Identity to search for.",
                     required = true
@@ -905,8 +905,8 @@ public class TenantsResource extends ApplicationResource {
             @QueryParam("q") @DefaultValue(StringUtils.EMPTY) String value) {
 
         // ensure we're running with a configurable authorizer
-        if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
-            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+        if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
         }
 
         if (isReplicateRequest()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java
index 9c5ba4d..47b28d9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/config/AccessDeniedExceptionMapper.java
@@ -55,7 +55,7 @@ public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDenied
         if (user == null) {
             identity = "<no user found>";
         } else {
-            identity = user.getIdentity();
+            identity = user.toString();
         }
 
         logger.info(String.format("%s does not have permission to access the requested resource. %s Returning %s response.", identity, exception.getMessage(), status));

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 95b1ebc..4b59de9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -38,9 +38,9 @@ import org.apache.nifi.annotation.behavior.Stateful;
 import org.apache.nifi.annotation.documentation.CapabilityDescription;
 import org.apache.nifi.annotation.documentation.DeprecationNotice;
 import org.apache.nifi.annotation.documentation.Tags;
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.AccessPolicy;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
 import org.apache.nifi.authorization.Group;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.Resource;
@@ -220,7 +220,9 @@ public final class DtoFactory {
         // get the refresh interval
         final long refreshInterval = FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.SECONDS);
         dto.setAutoRefreshIntervalSeconds(refreshInterval);
-        dto.setSupportsConfigurableAuthorizer(authorizer instanceof AbstractPolicyBasedAuthorizer);
+        dto.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer));
+        dto.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer));
+        dto.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer));
 
         final Date now = new Date();
         dto.setTimeOffset(TimeZone.getDefault().getOffset(now.getTime()));

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java
index 1d48ee0..853ee41 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/AccessPolicyDAO.java
@@ -23,7 +23,9 @@ import org.apache.nifi.web.api.dto.AccessPolicyDTO;
 
 public interface AccessPolicyDAO {
 
-    String MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, and policies.  Please contact your system administrator.";
+    String MSG_NON_MANAGED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, or policies.  Please contact your system administrator.";
+    String MSG_NON_CONFIGURABLE_POLICIES = "This NiFi is not configured to allow configurable policies. Please contact your system administrator.";
+    String MSG_NON_CONFIGURABLE_USERS = "This NiFi is not configured to allow configurable users and groups. Please contact your system administrator.";
 
     /**
      * Whether or not NiFi supports a configurable authorizer.

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java
index a47c051..9290470 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAO.java
@@ -16,15 +16,21 @@
  */
 package org.apache.nifi.web.dao.impl;
 
-import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
 import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.AccessPolicyProvider;
+import org.apache.nifi.authorization.AccessPolicyProviderInitializationContext;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
 import org.apache.nifi.authorization.AuthorizerConfigurationContext;
-import org.apache.nifi.authorization.AuthorizerInitializationContext;
 import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.ManagedAuthorizer;
+import org.apache.nifi.authorization.ConfigurableAccessPolicyProvider;
+import org.apache.nifi.authorization.ConfigurableUserGroupProvider;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.User;
-import org.apache.nifi.authorization.UsersAndAccessPolicies;
+import org.apache.nifi.authorization.UserAndGroups;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
 import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
@@ -44,118 +50,101 @@ import java.util.stream.Collectors;
 
 public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGroupDAO, UserDAO {
 
-    private final AbstractPolicyBasedAuthorizer authorizer;
-    private final boolean supportsConfigurableAuthorizer;
+    private final AccessPolicyProvider accessPolicyProvider;
+    private final UserGroupProvider userGroupProvider;
 
     public StandardPolicyBasedAuthorizerDAO(final Authorizer authorizer) {
-        if (authorizer instanceof AbstractPolicyBasedAuthorizer) {
-            this.authorizer = (AbstractPolicyBasedAuthorizer) authorizer;
-            this.supportsConfigurableAuthorizer = true;
+        if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
+            accessPolicyProvider = ((ManagedAuthorizer) authorizer).getAccessPolicyProvider();
         } else {
-            this.authorizer = new AbstractPolicyBasedAuthorizer() {
+            accessPolicyProvider = new AccessPolicyProvider() {
                 @Override
-                public Group doAddGroup(final Group group) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+                public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                    throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
                 }
 
                 @Override
-                public Group getGroup(final String identifier) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+                public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                    throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
                 }
 
                 @Override
-                public Group doUpdateGroup(final Group group) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+                public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                    throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
                 }
 
                 @Override
-                public Group deleteGroup(final Group group) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                public UserGroupProvider getUserGroupProvider() {
+                    return new UserGroupProvider() {
+                        @Override
+                        public Set<User> getUsers() throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public Set<Group> getGroups() throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public User getUser(String identifier) throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public User doAddUser(final User user) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public User getUser(final String identifier) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public Set<Group> getGroups() throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public Group getGroup(String identifier) throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public User doUpdateUser(final User user) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                            throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
+                        }
 
-                @Override
-                public User deleteUser(final User user) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
 
-                @Override
-                public Set<User> getUsers() throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        }
 
-                @Override
-                public AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
 
-                @Override
-                public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        }
 
-                @Override
-                public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                        @Override
+                        public void preDestruction() throws AuthorizerDestructionException {
 
-                @Override
-                public AccessPolicy deleteAccessPolicy(final AccessPolicy policy) throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
+                        }
+                    };
                 }
 
                 @Override
-                public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
-                }
+                public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
 
-                @Override
-                public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
-                    throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
                 }
 
                 @Override
-                public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-                }
+                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
 
-                @Override
-                public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
                 }
 
                 @Override
                 public void preDestruction() throws AuthorizerDestructionException {
+
                 }
             };
-            this.supportsConfigurableAuthorizer = false;
         }
+
+        userGroupProvider = accessPolicyProvider.getUserGroupProvider();
     }
 
     private AccessPolicy findAccessPolicy(final RequestAction requestAction, final String resource) {
-        return authorizer.getAccessPolicies().stream()
+        return accessPolicyProvider.getAccessPolicies().stream()
                 .filter(policy -> policy.getAction().equals(requestAction) && policy.getResource().equals(resource))
                 .findFirst()
                 .orElse(null);
@@ -163,23 +152,28 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public boolean supportsConfigurableAuthorizer() {
-        return supportsConfigurableAuthorizer;
+        return accessPolicyProvider instanceof ConfigurableAccessPolicyProvider;
     }
 
     @Override
     public boolean hasAccessPolicy(final String accessPolicyId) {
-        return authorizer.getAccessPolicy(accessPolicyId) != null;
+        return accessPolicyProvider.getAccessPolicy(accessPolicyId) != null;
     }
 
     @Override
     public AccessPolicy createAccessPolicy(final AccessPolicyDTO accessPolicyDTO) {
-        return authorizer.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(),
-                accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO));
+        if (supportsConfigurableAuthorizer()) {
+            final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
+            return configurableAccessPolicyProvider.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(),
+                    accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
+        }
     }
 
     @Override
     public AccessPolicy getAccessPolicy(final String accessPolicyId) {
-        final AccessPolicy accessPolicy = authorizer.getAccessPolicy(accessPolicyId);
+        final AccessPolicy accessPolicy = accessPolicyProvider.getAccessPolicy(accessPolicyId);
         if (accessPolicy == null) {
             throw new ResourceNotFoundException(String.format("Unable to find access policy with id '%s'.", accessPolicyId));
         }
@@ -210,14 +204,25 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public AccessPolicy updateAccessPolicy(final AccessPolicyDTO accessPolicyDTO) {
-        final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId());
-        return authorizer.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(),
-                currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO));
+        if (supportsConfigurableAuthorizer()) {
+            final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
+
+            final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId());
+            return configurableAccessPolicyProvider.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(),
+                    currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
+        }
     }
 
     @Override
     public AccessPolicy deleteAccessPolicy(final String accessPolicyId) {
-        return authorizer.deleteAccessPolicy(getAccessPolicy(accessPolicyId));
+        if (supportsConfigurableAuthorizer()) {
+            final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
+            return configurableAccessPolicyProvider.deleteAccessPolicy(getAccessPolicy(accessPolicyId));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
+        }
     }
 
     private AccessPolicy buildAccessPolicy(final String identifier, final String resource, final RequestAction action, final AccessPolicyDTO accessPolicyDTO) {
@@ -238,17 +243,22 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public boolean hasUserGroup(final String userGroupId) {
-        return authorizer.getGroup(userGroupId) != null;
+        return userGroupProvider.getGroup(userGroupId) != null;
     }
 
     @Override
     public Group createUserGroup(final UserGroupDTO userGroupDTO) {
-        return authorizer.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO));
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+            return configurableUserGroupProvider.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     @Override
     public Group getUserGroup(final String userGroupId) {
-        final Group userGroup = authorizer.getGroup(userGroupId);
+        final Group userGroup = userGroupProvider.getGroup(userGroupId);
         if (userGroup == null) {
             throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", userGroupId));
         }
@@ -257,14 +267,14 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public Set<Group> getUserGroupsForUser(String userId) {
-        return authorizer.getGroups().stream()
+        return userGroupProvider.getGroups().stream()
                 .filter(g -> g.getUsers().contains(userId))
                 .collect(Collectors.toSet());
     }
 
     @Override
     public Set<AccessPolicy> getAccessPoliciesForUser(String userId) {
-        return authorizer.getAccessPolicies().stream()
+        return accessPolicyProvider.getAccessPolicies().stream()
                 .filter(p -> {
                     // policy contains the user
                     if (p.getUsers().contains(userId)) {
@@ -272,14 +282,14 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
                     }
 
                     // policy contains a group with the user
-                    return !p.getGroups().stream().filter(g -> authorizer.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty();
+                    return !p.getGroups().stream().filter(g -> userGroupProvider.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty();
                 })
                 .collect(Collectors.toSet());
     }
 
     @Override
     public Set<AccessPolicy> getAccessPoliciesForUserGroup(String userGroupId) {
-        return authorizer.getAccessPolicies().stream()
+        return accessPolicyProvider.getAccessPolicies().stream()
                 .filter(p -> {
                     // policy contains the user group
                     return p.getGroups().contains(userGroupId);
@@ -289,17 +299,46 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public Set<Group> getUserGroups() {
-        return authorizer.getGroups();
+        return userGroupProvider.getGroups();
     }
 
     @Override
     public Group updateUserGroup(final UserGroupDTO userGroupDTO) {
-        return authorizer.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO));
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+            return configurableUserGroupProvider.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     @Override
     public Group deleteUserGroup(final String userGroupId) {
-        return authorizer.deleteGroup(getUserGroup(userGroupId));
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+
+            final Group group = getUserGroup(userGroupId);
+            final Group removedGroup = configurableUserGroupProvider.deleteGroup(group);
+
+            // ensure the user was removed
+            if (removedGroup == null) {
+                throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", removedGroup));
+            }
+
+            // remove any references to the user group being deleted from policies if possible
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
+                    if (policy.getGroups().contains(removedGroup.getIdentifier())) {
+                        final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeGroup(removedGroup.getIdentifier());
+                        ((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build());
+                    }
+                }
+            }
+
+            return removedGroup;
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     private Group buildUserGroup(final String identifier, final UserGroupDTO userGroupDTO) {
@@ -313,17 +352,22 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public boolean hasUser(final String userId) {
-        return authorizer.getUser(userId) != null;
+        return userGroupProvider.getUser(userId) != null;
     }
 
     @Override
     public User createUser(final UserDTO userDTO) {
-        return authorizer.addUser(buildUser(userDTO.getId(), userDTO));
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+            return configurableUserGroupProvider.addUser(buildUser(userDTO.getId(), userDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     @Override
     public User getUser(final String userId) {
-        final User user = authorizer.getUser(userId);
+        final User user = userGroupProvider.getUser(userId);
         if (user == null) {
             throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId));
         }
@@ -332,18 +376,46 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
 
     @Override
     public Set<User> getUsers() {
-        return authorizer.getUsers();
+        return userGroupProvider.getUsers();
     }
 
     @Override
     public User updateUser(final UserDTO userDTO) {
-        return authorizer.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO));
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+            return configurableUserGroupProvider.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO));
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     @Override
     public User deleteUser(final String userId) {
-        final User user = getUser(userId);
-        return authorizer.deleteUser(user);
+        if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
+
+            final User user = getUser(userId);
+            final User removedUser = configurableUserGroupProvider.deleteUser(user);
+
+            // ensure the user was removed
+            if (removedUser == null) {
+                throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId));
+            }
+
+            // remove any references to the user being deleted from policies if possible
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
+                    if (policy.getUsers().contains(removedUser.getIdentifier())) {
+                        final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeUser(removedUser.getIdentifier());
+                        ((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build());
+                    }
+                }
+            }
+
+            return removedUser;
+        } else {
+            throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
+        }
     }
 
     private User buildUser(final String identifier, final UserDTO userDTO) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy
index 29ab83a..a830a87 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/StandardNiFiServiceFacadeSpec.groovy
@@ -16,35 +16,51 @@
  */
 package org.apache.nifi.web
 
-import org.apache.nifi.authorization.*
+import org.apache.nifi.authorization.AccessDeniedException
+import org.apache.nifi.authorization.AccessPolicy
+import org.apache.nifi.authorization.AuthorizableLookup
+import org.apache.nifi.authorization.AuthorizationResult
+import org.apache.nifi.authorization.Authorizer
+import org.apache.nifi.authorization.Group
+import org.apache.nifi.authorization.RequestAction
+import org.apache.nifi.authorization.Resource
+import org.apache.nifi.authorization.User
 import org.apache.nifi.authorization.resource.Authorizable
 import org.apache.nifi.authorization.resource.ResourceFactory
 import org.apache.nifi.authorization.user.NiFiUser
-import org.apache.nifi.authorization.user.StandardNiFiUser
 import org.apache.nifi.authorization.user.NiFiUserDetails
+import org.apache.nifi.authorization.user.StandardNiFiUser
 import org.apache.nifi.controller.service.ControllerServiceProvider
 import org.apache.nifi.reporting.Bulletin
 import org.apache.nifi.reporting.BulletinRepository
-import org.apache.nifi.reporting.ComponentType
-import org.apache.nifi.web.api.dto.*
+import org.apache.nifi.web.api.dto.AccessPolicyDTO
+import org.apache.nifi.web.api.dto.BulletinDTO
+import org.apache.nifi.web.api.dto.DtoFactory
+import org.apache.nifi.web.api.dto.EntityFactory
+import org.apache.nifi.web.api.dto.RevisionDTO
+import org.apache.nifi.web.api.dto.UserDTO
+import org.apache.nifi.web.api.dto.UserGroupDTO
 import org.apache.nifi.web.api.entity.BulletinEntity
 import org.apache.nifi.web.api.entity.UserEntity
 import org.apache.nifi.web.controller.ControllerFacade
 import org.apache.nifi.web.dao.AccessPolicyDAO
 import org.apache.nifi.web.dao.UserDAO
 import org.apache.nifi.web.dao.UserGroupDAO
-import org.apache.nifi.web.revision.*
+import org.apache.nifi.web.revision.DeleteRevisionTask
+import org.apache.nifi.web.revision.ReadOnlyRevisionCallback
+import org.apache.nifi.web.revision.RevisionClaim
+import org.apache.nifi.web.revision.RevisionManager
+import org.apache.nifi.web.revision.UpdateRevisionTask
 import org.apache.nifi.web.security.token.NiFiAuthenticationToken
 import org.springframework.security.core.context.SecurityContextHolder
 import spock.lang.Ignore
 import spock.lang.Specification
 import spock.lang.Unroll
 
-
 class StandardNiFiServiceFacadeSpec extends Specification {
 
     def setup() {
-        final NiFiUser user = new StandardNiFiUser("nifi-user");
+        final NiFiUser user = new StandardNiFiUser.Builder().identity("nifi-user").build();
         final NiFiAuthenticationToken auth = new NiFiAuthenticationToken(new NiFiUserDetails(user));
         SecurityContextHolder.getContext().setAuthentication(auth);
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy
index 340f6f9..5a4cc3b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/groovy/org/apache/nifi/web/dao/impl/StandardPolicyBasedAuthorizerDAOSpec.groovy
@@ -27,6 +27,14 @@ import spock.lang.Unroll
 
 class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
 
+    private AbstractPolicyBasedAuthorizer mockAuthorizer() {
+        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        authorizer.getAccessPolicyProvider() >> {
+            callRealMethod();
+        }
+        return authorizer;
+    }
+
     @Unroll
     def "test non-policy-based authorizer #method throws IllegalStateException"() {
         when:
@@ -34,31 +42,57 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
 
         then:
         def e = thrown(IllegalStateException)
-        assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER)
+        assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_MANAGED_AUTHORIZER)
 
         where:
         method               | daoMethod
-        'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
-        'createUser'         | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) }
-        'createUserGroup'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
-        'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') }
-        'deleteUser'         | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') }
-        'deleteUserGroup'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') }
         'getAccessPolicy'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getAccessPolicy('1') }
         'getUser'            | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUser('1') }
         'getUserGroup'       | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUserGroup('1') }
         'hasAccessPolicy'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasAccessPolicy('1') }
         'hasUser'            | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUser('1') }
         'hasUserGroup'       | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUserGroup('1') }
-        'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
+    }
+
+    @Unroll
+    def "test non-configurable user group provider #method throws IllegalStateException"() {
+        when:
+        daoMethod()
+
+        then:
+        def e = thrown(IllegalStateException)
+        assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_USERS)
+
+        where:
+        method               | daoMethod
+        'createUser'         | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) }
+        'createUserGroup'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
+        'deleteUser'         | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') }
+        'deleteUserGroup'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') }
         'updateUser'         | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUser(new UserDTO(id: '1', identity: 'a')) }
         'updateUserGroup'    | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
     }
 
     @Unroll
+    def "test non-configurable access policy provider #method throws IllegalStateException"() {
+        when:
+        daoMethod()
+
+        then:
+        def e = thrown(IllegalStateException)
+        assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_POLICIES)
+
+        where:
+        method               | daoMethod
+        'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
+        'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') }
+        'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
+    }
+
+    @Unroll
     def "HasAccessPolicy: accessPolicy: #accessPolicy"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -79,7 +113,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "CreateAccessPolicy: accessPolicy=#accessPolicy"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
                 users: [new TenantEntity(id: 'user-id-1')] as Set,
@@ -92,7 +126,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
         noExceptionThrown()
 
         then:
-        1 * authorizer.getAccessPolicies() >> accessPolicies
         1 * authorizer.doAddAccessPolicy(accessPolicy) >> accessPolicy
         0 * _
         result?.equals accessPolicy
@@ -106,7 +139,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetAccessPolicy: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -126,7 +159,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetAccessPolicy: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -141,7 +174,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateAccessPolicy: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
                 users: [new TenantEntity(id: 'user-id-1')] as Set,
@@ -165,7 +198,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateAccessPolicy: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
                 users: [new TenantEntity(id: 'user-id-1')] as Set,
@@ -183,7 +216,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteAccessPolicy: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -204,7 +237,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteAccessPolicy: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -219,7 +252,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "HasUserGroup: userGroup=#userGroup"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -239,7 +272,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "CreateUserGroup: userGroup=#userGroup"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
 
@@ -250,8 +283,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
         noExceptionThrown()
 
         then:
-        1 * authorizer.getUsers() >> users
-        1 * authorizer.getGroups() >> groups
         1 * authorizer.doAddGroup(userGroup) >> userGroup
         0 * _
         result?.equals userGroup
@@ -265,7 +296,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUserGroup: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -284,7 +315,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUserGroup: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -299,7 +330,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUserGroups: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -318,7 +349,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateUserGroup: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
 
@@ -327,8 +358,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
 
         then:
         1 * authorizer.getGroup(requestDTO.id) >> userGroup
-        1 * authorizer.getUsers() >> users
-        1 * authorizer.getGroups() >> groups
         1 * authorizer.doUpdateGroup(userGroup) >> userGroup
         0 * _
         result?.equals(userGroup)
@@ -342,7 +371,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateUserGroup: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
 
@@ -358,7 +387,10 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteUserGroup: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
+        authorizer.getAccessPolicyProvider().getAccessPolicies() >> {
+            callRealMethod();
+        }
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -367,6 +399,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
         then:
         1 * authorizer.getGroup('user-group-id-1') >> userGroup
         1 * authorizer.deleteGroup(userGroup) >> userGroup
+        1 * authorizer.getAccessPolicies() >> []
         0 * _
         assert result?.equals(userGroup)
 
@@ -378,7 +411,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteUserGroup: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -393,7 +426,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "HasUser: user=#user"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -412,7 +445,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "CreateUser: user=#user"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
 
@@ -423,8 +456,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
         noExceptionThrown()
 
         then:
-        1 * authorizer.getUsers() >> users
-        1 * authorizer.getGroups() >> groups
         1 * authorizer.doAddUser(user) >> user
         0 * _
         result?.equals user
@@ -438,7 +469,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUser: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -457,7 +488,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUser: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -472,7 +503,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "GetUsers: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -491,7 +522,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateUser: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
 
@@ -500,8 +531,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
 
         then:
         1 * authorizer.getUser(requestDTO.id) >> user
-        1 * authorizer.getUsers() >> users
-        1 * authorizer.getGroups() >> groups
         1 * authorizer.doUpdateUser(user) >> user
         0 * _
         result?.equals(user)
@@ -515,7 +544,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "UpdateUser: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
         def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
 
@@ -531,7 +560,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteUser: success"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:
@@ -540,6 +569,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
         then:
         1 * authorizer.getUser('user-id-1') >> user
         1 * authorizer.deleteUser(user) >> user
+        1 * authorizer.getAccessPolicies() >> []
         0 * _
         result?.equals(user)
 
@@ -551,7 +581,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
     @Unroll
     def "DeleteUser: failure"() {
         given:
-        def authorizer = Mock AbstractPolicyBasedAuthorizer
+        def authorizer = mockAuthorizer()
         def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
 
         when:

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java
index ea7fa7d..68be1cd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/audit/TestRemoteProcessGroupAuditor.java
@@ -25,7 +25,7 @@ import org.apache.nifi.action.details.ConfigureDetails;
 import org.apache.nifi.admin.service.AuditService;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.groups.RemoteProcessGroup;
 import org.apache.nifi.remote.RemoteGroupPort;
 import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
@@ -62,7 +62,7 @@ public class TestRemoteProcessGroupAuditor {
         final SecurityContext securityContext = SecurityContextHolder.getContext();
         final Authentication authentication = mock(Authentication.class);
         securityContext.setAuthentication(authentication);
-        final NiFiUser user = new StandardNiFiUser("user-id");
+        final NiFiUser user = new Builder().identity("user-id").build();
         final NiFiUserDetails userDetail = new NiFiUserDetails(user);
         when(authentication.getPrincipal()).thenReturn(userDetail);
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
index 9933d4b..05e4451 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java
@@ -31,7 +31,7 @@ import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.resource.ResourceType;
 import org.apache.nifi.authorization.user.NiFiUserDetails;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.apache.nifi.controller.FlowController;
 import org.apache.nifi.history.History;
 import org.apache.nifi.history.HistoryQuery;
@@ -190,7 +190,7 @@ public class StandardNiFiServiceFacadeTest {
     @Test
     public void testGetActionApprovedThroughAction() throws Exception {
         // set the user
-        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1)));
+        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
         // get the action
@@ -218,7 +218,7 @@ public class StandardNiFiServiceFacadeTest {
     @Test(expected = AccessDeniedException.class)
     public void testGetActionDeniedDespiteControllerAccess() throws Exception {
         // set the user
-        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2)));
+        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build()));
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
         try {
@@ -245,7 +245,7 @@ public class StandardNiFiServiceFacadeTest {
     @Test
     public void testGetActionApprovedThroughController() throws Exception {
         // set the user
-        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2)));
+        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build()));
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
         // get the action
@@ -273,7 +273,7 @@ public class StandardNiFiServiceFacadeTest {
     @Test
     public void testGetActionsForUser1() throws Exception {
         // set the user
-        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_1)));
+        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
         final HistoryDTO dto = serviceFacade.getActions(new HistoryQueryDTO());
@@ -292,7 +292,7 @@ public class StandardNiFiServiceFacadeTest {
     @Test
     public void testGetActionsForUser2() throws Exception {
         // set the user
-        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new StandardNiFiUser(USER_2)));
+        final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_2).build()));
         SecurityContextHolder.getContext().setAuthentication(authentication);
 
         final HistoryDTO  dto = serviceFacade.getActions(new HistoryQueryDTO());


[11/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

Posted by bb...@apache.org.
NIFI-3653: - Introducing UserGroup and Policy provider interfaces.
- Introducing FileUserGroupProvider and FileAccessPolicyProvider.
- Refactoring FileAuthorizer to utilize the file based implementations.
- Introducing the StandardManagedAuthorizer.
- Decorating the configured ManagedAuthorizer to ensure integrity checks are still performed.
- Loading user groups if possible to use during access decisions.
- Merging responses for requests for AccessPolicies, Users, and UserGroups.
- Adding unit tests as appropriate.
- Adding methods to the User, Group, and AccessPolicy builder that more easily supports generating UUIDs.
- Fixing typo when seeding policies during startup.
- Fixing type in documentation and error messages.

This closes #1897.

Signed-off-by: Bryan Bende <bb...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/4ed7511b
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/4ed7511b
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/4ed7511b

Branch: refs/heads/master
Commit: 4ed7511bee11b319487d07b4bdcff3438a44b6dc
Parents: f447fc7
Author: Matt Gilman <ma...@gmail.com>
Authored: Fri May 26 15:02:44 2017 -0400
Committer: Bryan Bende <bb...@apache.org>
Committed: Fri Jun 9 13:54:10 2017 -0400

----------------------------------------------------------------------
 .../src/main/asciidoc/administration-guide.adoc |  133 +-
 .../AbstractPolicyBasedAuthorizer.java          |  364 ++++--
 .../apache/nifi/authorization/AccessPolicy.java |   37 +
 .../authorization/AccessPolicyProvider.java     |   90 ++
 ...cessPolicyProviderInitializationContext.java |   30 +
 .../AccessPolicyProviderLookup.java             |   31 +
 .../authorization/AuthorizationRequest.java     |   19 +
 .../apache/nifi/authorization/Authorizer.java   |    4 +
 .../AuthorizerConfigurationContext.java         |    4 +-
 .../AuthorizerInitializationContext.java        |   11 +-
 .../ConfigurableAccessPolicyProvider.java       |   84 ++
 .../ConfigurableUserGroupProvider.java          |  115 ++
 .../org/apache/nifi/authorization/Group.java    |   37 +
 .../nifi/authorization/ManagedAuthorizer.java   |   59 +
 .../org/apache/nifi/authorization/User.java     |   37 +
 .../nifi/authorization/UserAndGroups.java       |   40 +
 .../nifi/authorization/UserGroupProvider.java   |  108 ++
 .../UserGroupProviderInitializationContext.java |   37 +
 .../authorization/UserGroupProviderLookup.java  |   31 +
 .../UninheritableAuthorizationsException.java   |   28 +
 .../authorization/resource/Authorizable.java    |    2 +
 .../nifi/authorization/user/NiFiUser.java       |    7 +
 .../TestAbstractPolicyBasedAuthorizer.java      |  211 ----
 .../nifi-framework/nifi-authorizer/pom.xml      |   18 +-
 .../AccessPolicyProviderFactory.java            |  179 +++
 .../AuthorizerCapabilityDetection.java          |   45 +
 .../nifi/authorization/AuthorizerFactory.java   |  426 +++++++
 .../authorization/AuthorizerFactoryBean.java    |  332 +++--
 .../authorization/UserGroupProviderFactory.java |  228 ++++
 .../src/main/xsd/authorizers.xsd                |   24 +-
 .../authorization/AuthorizerFactoryTest.java    |  264 ++++
 .../MockPolicyBasedAuthorizer.java              |  183 +++
 .../nifi/web/api/dto/FlowConfigurationDTO.java  |   33 +
 .../nifi/web/api/entity/AccessPolicyEntity.java |    2 +-
 .../nifi/web/api/entity/TenantsEntity.java      |    2 +-
 .../apache/nifi/web/api/entity/UserEntity.java  |    2 +-
 .../nifi/web/api/entity/UserGroupEntity.java    |    5 +-
 .../nifi/web/api/entity/UserGroupsEntity.java   |    2 +-
 .../nifi-framework/nifi-file-authorizer/pom.xml |   15 +-
 .../authorization/AuthorizationsHolder.java     |  209 +---
 .../authorization/FileAccessPolicyProvider.java |  947 ++++++++++++++
 .../nifi/authorization/FileAuthorizer.java      | 1151 ++----------------
 .../authorization/FileUserGroupProvider.java    |  820 +++++++++++++
 .../nifi/authorization/IdentifierUtil.java      |   35 +
 .../nifi/authorization/UserGroupHolder.java     |  239 ++++
 ...ache.nifi.authorization.AccessPolicyProvider |   15 +
 ....apache.nifi.authorization.UserGroupProvider |   15 +
 .../FileAccessPolicyProviderTest.java           | 1050 ++++++++++++++++
 .../nifi/authorization/FileAuthorizerTest.java  |  125 +-
 .../FileUserGroupProviderTest.java              |  698 +++++++++++
 ...StandardAuthorizerInitializationContext.java |   16 +-
 .../StandardManagedAuthorizer.java              |  251 ++++
 .../authorization/user/StandardNiFiUser.java    |  132 +-
 .../nifi/authorization/util/UserGroupUtil.java  |   54 +
 .../org.apache.nifi.authorization.Authorizer    |   15 +
 .../StandardManagedAuthorizerTest.java          |  438 +++++++
 .../resource/DataAuthorizableTest.java          |   38 +-
 .../http/StandardHttpResponseMapper.java        |   12 +
 .../endpoints/AccessPolicyEndpointMerger.java   |   61 +
 .../endpoints/SearchUsersEndpointMerger.java    |   59 +
 .../http/endpoints/UserEndpointMerger.java      |   58 +
 .../http/endpoints/UserGroupEndpointMerger.java |   58 +
 .../endpoints/UserGroupsEndpointMerger.java     |   76 ++
 .../http/endpoints/UsersEndpointMerger.java     |   76 ++
 .../manager/AccessPolicyEntityMerger.java       |   74 ++
 .../nifi/cluster/manager/UserEntityMerger.java  |   76 ++
 .../cluster/manager/UserGroupEntityMerger.java  |   75 ++
 .../cluster/manager/UserGroupsEntityMerger.java |   39 +
 .../nifi/cluster/manager/UsersEntityMerger.java |   39 +
 .../AccessPolicyEndpointMergerTest.java         |   41 +
 .../TestThreadPoolRequestReplicator.java        |    7 +-
 .../manager/AccessPolicyEntityMergerTest.java   |   96 ++
 .../cluster/manager/UserEntityMergerTest.java   |  114 ++
 .../manager/UserGroupEntityMergerTest.java      |  114 ++
 .../apache/nifi/cluster/protocol/DataFlow.java  |    2 +-
 .../nifi-framework/nifi-framework-core/pom.xml  |    4 +
 .../nifi/controller/StandardFlowService.java    |    7 +-
 .../controller/StandardFlowSynchronizer.java    |   41 +-
 .../org/apache/nifi/nar/ExtensionManager.java   |    4 +
 .../nifi/nar/NarThreadContextClassLoader.java   |    4 +
 .../nifi-framework/nifi-resources/pom.xml       |    2 +-
 .../src/main/resources/conf/authorizers.xml     |  104 +-
 .../nifi/remote/StandardRootGroupPort.java      |   43 +-
 .../nifi/web/StandardNiFiServiceFacade.java     |    1 +
 .../StandardNiFiWebConfigurationContext.java    |   30 +-
 .../nifi/web/api/AccessPolicyResource.java      |   22 +-
 .../apache/nifi/web/api/ControllerResource.java |    1 +
 .../apache/nifi/web/api/CountersResource.java   |    1 +
 .../org/apache/nifi/web/api/FlowResource.java   |    2 +
 .../apache/nifi/web/api/ProvenanceResource.java |    1 +
 .../apache/nifi/web/api/ResourceResource.java   |    1 +
 .../apache/nifi/web/api/SiteToSiteResource.java |    1 +
 .../nifi/web/api/SystemDiagnosticsResource.java |    1 +
 .../apache/nifi/web/api/TenantsResource.java    |   48 +-
 .../api/config/AccessDeniedExceptionMapper.java |    2 +-
 .../org/apache/nifi/web/api/dto/DtoFactory.java |    6 +-
 .../apache/nifi/web/dao/AccessPolicyDAO.java    |    4 +-
 .../impl/StandardPolicyBasedAuthorizerDAO.java  |  270 ++--
 .../web/StandardNiFiServiceFacadeSpec.groovy    |   30 +-
 .../StandardPolicyBasedAuthorizerDAOSpec.groovy |  116 +-
 .../audit/TestRemoteProcessGroupAuditor.java    |    4 +-
 .../nifi/web/StandardNiFiServiceFacadeTest.java |   12 +-
 .../web/revision/TestNaiveRevisionManager.java  |   10 +-
 .../security/NiFiAuthenticationProvider.java    |    9 +-
 .../security/jwt/JwtAuthenticationProvider.java |   13 +-
 .../security/otp/OtpAuthenticationProvider.java |   10 +-
 .../x509/X509AuthenticationProvider.java        |   26 +-
 .../resources/nifi-web-security-context.xml     |   12 +-
 .../NiFiAuthenticationProviderTest.java         |    7 +-
 .../otp/OtpAuthenticationProviderTest.java      |    4 +-
 .../x509/X509AuthenticationProviderTest.java    |   33 +-
 .../WEB-INF/partials/canvas/canvas-header.jsp   |    6 +-
 .../WEB-INF/partials/canvas/navigation.jsp      |    4 +-
 .../src/main/webapp/css/policy-management.css   |    2 +
 .../nifi-web-ui/src/main/webapp/css/users.css   |    1 +
 .../webapp/js/nf/canvas/nf-canvas-bootstrap.js  |    2 +
 .../main/webapp/js/nf/canvas/nf-canvas-utils.js |   14 +
 .../src/main/webapp/js/nf/canvas/nf-canvas.js   |   34 +
 .../main/webapp/js/nf/canvas/nf-context-menu.js |    2 +-
 .../js/nf/canvas/nf-controller-services.js      |    2 +-
 .../webapp/js/nf/canvas/nf-policy-management.js |   84 +-
 .../src/main/webapp/js/nf/canvas/nf-settings.js |    2 +-
 .../js/nf/templates/nf-templates-table.js       |    2 +-
 .../main/webapp/js/nf/users/nf-users-table.js   |   36 +-
 .../src/main/webapp/js/nf/users/nf-users.js     |   18 +-
 .../TestPersistentProvenanceRepository.java     |   77 +-
 .../index/lucene/TestLuceneEventIndex.java      |   42 +-
 .../TestVolatileProvenanceRepository.java       |    7 +
 pom.xml                                         |    2 +-
 129 files changed, 9476 insertions(+), 2351 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-docs/src/main/asciidoc/administration-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 36ae533..cc1197c 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -385,7 +385,32 @@ Authorizers are configured using two properties in the 'nifi.properties' file:
 Authorizers.xml Setup
 ~~~~~~~~~~~~~~~~~~~~~
 
-The 'authorizers.xml' file is used to define and configure available authorizers.  The default authorizer is the FileAuthorizer, however, you can develop additional authorizers as extensions.  The FileAuthorizer has the following properties:
+The 'authorizers.xml' file is used to define and configure available authorizers.  The default authorizer is the StandardManagedAuthorizer.  The managed authorizer is comprised of a UserGroupProvider
+and a AccessPolicyProvider.  The users, group, and access policies will be loaded and optionally configured through these providers.  The managed authorizer will make all access decisions based on
+these provided users, groups, and access policies.
+
+The default UserGroupProvider is the FileUserGroupProvider, however, you can develop additional UserGroupProviders as extensions.  The FileUserGroupProvider has the following properties:
+
+* Users File - The file where the FileUserGroupProvider stores users and groups.  By default, the 'users.xml' in the 'conf' directory is chosen.
+* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically be used to load the users and groups into the Users File.
+* Initial User Identity - The identity of a users and systems to seed the Users File. The name of each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
+
+The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions.  The FileAccessPolicyProvider has the following properties:
+
+* User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies.
+* Authorizations File - The file where the FileAccessPolicyProvider will store policies.
+* Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and given the ability to create additional users, groups, and policies. The value of this property could be a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified.
+* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically converted to the new authorizations model. If this property is specified then an Initial Admin Identity can not be specified, and this property will only be used when there are no other users, groups, and policies defined.
+* Node Identity - The identity of a NiFi cluster node. When clustered, a property for each node should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. The name of each property must be unique, for example for a three node cluster: "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3"
+
+The identities configured in the Initial Admin Identity, the Node Identity properties, or discovered in a Legacy Authorized Users File must be available in the configured User Group Provider.
+
+The default authorizer is the StandardManagedAuthorizer, however, you can develop additional authorizers as extensions.  The StandardManagedAuthorizer has the following properties:
+
+* Access Policy Provider - The identifier for an Access Policy Provider defined above.
+
+The FileAuthorizer has been replaced with the more granular StandardManagedAuthorizer approach described above. However, it is still available for backwards compatibility reasons. The
+FileAuthorizer has the following properties.
 
 * Authorizations File - The file where the FileAuthorizer stores policies.  By default, the 'authorizations.xml' in the 'conf' directory is chosen.
 * Users File - The file where the FileAuthorizer stores users and groups.  By default, the 'users.xml' in the 'conf' directory is chosen.
@@ -402,17 +427,29 @@ If you are setting up a secured NiFi instance for the first time, you must manua
 Here is an example LDAP entry using the name John Smith:
 
 ----
-<authorizer>
-        <identifier>file-provider</identifier>
-        <class>org.apache.nifi.authorization.FileAuthorizer</class>
-        <property name="Authorizations File">./conf/authorizations.xml</property>
+<authorizers>
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
         <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Initial User Identity 1">cn=John Smith,ou=people,dc=example,dc=com</property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">file-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity">cn=John Smith,ou=people,dc=example,dc=com</property>
         <property name="Legacy Authorized Users File"></property>
-        <!--
+
         <property name="Node Identity 1"></property>
-        <property name="Node Identity 2"></property>
-        -->
+    </accessPolicyProvider>
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
     </authorizer>
 </authorizers>
 ----
@@ -420,17 +457,29 @@ Here is an example LDAP entry using the name John Smith:
 Here is an example Kerberos entry using the name John Smith and realm `NIFI.APACHE.ORG`:
 
 ----
-<authorizer>
-        <identifier>file-provider</identifier>
-        <class>org.apache.nifi.authorization.FileAuthorizer</class>
-        <property name="Authorizations File">./conf/authorizations.xml</property>
+<authorizers>
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
         <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Initial User Identity 1">johnsmith@NIFI.APACHE.ORG</property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">file-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity">johnsmith@NIFI.APACHE.ORG</property>
         <property name="Legacy Authorized Users File"></property>
-        <!--
+
         <property name="Node Identity 1"></property>
-        <property name="Node Identity 2"></property>
-        -->
+    </accessPolicyProvider>
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
     </authorizer>
 </authorizers>
 ----
@@ -449,13 +498,28 @@ Here is an example entry:
 
 ----
 <authorizers>
-    <authorizer>
-        <identifier>file-provider</identifier>
-        <class>org.apache.nifi.authorization.FileAuthorizer</class>
-        <property name="Authorizations File">./conf/authorizations.xml</property>
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
         <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File">/Users/johnsmith/config_files/authorized-users.xml</property>
+
+        <property name="Initial User Identity 1"></property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">file-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity"></property>
         <property name="Legacy Authorized Users File">/Users/johnsmith/config_files/authorized-users.xml</property>
+
+        <property name="Node Identity 1"></property>
+    </accessPolicyProvider>
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
     </authorizer>
 </authorizers>
 ----
@@ -514,15 +578,32 @@ cn=nifi-2,ou=people,dc=example,dc=com
 ----
 
 ----
-<authorizer>
-        <identifier>file-provider</identifier>
-        <class>org.apache.nifi.authorization.FileAuthorizer</class>
-        <property name="Authorizations File">./conf/authorizations.xml</property>
+<authorizers>
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
         <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Initial User Identity 1">johnsmith@NIFI.APACHE.ORG</property>
+        <property name="Initial User Identity 2">cn=nifi-1,ou=people,dc=example,dc=com</property>
+        <property name="Initial User Identity 3">cn=nifi-2,ou=people,dc=example,dc=com</property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">file-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
         <property name="Initial Admin Identity">johnsmith@NIFI.APACHE.ORG</property>
         <property name="Legacy Authorized Users File"></property>
+
         <property name="Node Identity 1">cn=nifi-1,ou=people,dc=example,dc=com</property>
         <property name="Node Identity 2">cn=nifi-2,ou=people,dc=example,dc=com</property>
+    </accessPolicyProvider>
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
     </authorizer>
 </authorizers>
 ----
@@ -535,7 +616,11 @@ Now that initial authorizations have been created, additional users, groups and
 Configuring Users & Access Policies
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This section describes:
+Depending on the capabilities of the configured UserGroupProvider and AccessPolicyProvider the users, groups, and policies will be configurable in the UI. If the extensions are not configurable the
+users, groups, and policies will read-only in the UI. If the configured authorizer does not use UserGroupProvider and AccessPolicyProvider the users and policies may or may not be visible and
+configurable in the UI based on the underlying implementation.
+
+This section assumes the users, groups, and policies are configurable in the UI and describes:
 
 * How to create users and groups
 * How access policies are used to define authorizations

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
index 0d047f1..929e2ad 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AbstractPolicyBasedAuthorizer.java
@@ -18,6 +18,8 @@ package org.apache.nifi.authorization;
 
 import org.apache.nifi.authorization.exception.AuthorizationAccessException;
 import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -43,7 +45,7 @@ import java.util.Set;
 /**
  * An Authorizer that provides management of users, groups, and policies.
  */
-public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
+public abstract class AbstractPolicyBasedAuthorizer implements ManagedAuthorizer {
 
     static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
     static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
@@ -60,32 +62,9 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
     static final String RESOURCE_ATTR = "resource";
     static final String ACTIONS_ATTR = "actions";
 
-    public static final String EMPTY_FINGERPRINT = "EMPTY";
-
     @Override
     public final void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
         doOnConfigured(configurationContext);
-
-        // ensure that only one policy per resource-action exists
-        for (AccessPolicy accessPolicy : getAccessPolicies()) {
-            if (policyExists(accessPolicy)) {
-                throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
-            }
-        }
-
-        // ensure that only one group exists per identity
-        for (User user : getUsers()) {
-            if (tenantExists(user.getIdentifier(), user.getIdentity())) {
-                throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
-            }
-        }
-
-        // ensure that only one group exists per identity
-        for (Group group : getGroups()) {
-            if (tenantExists(group.getIdentifier(), group.getName())) {
-                throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
-            }
-        }
     }
 
     /**
@@ -96,48 +75,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      */
     protected abstract void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
 
-    /**
-     * Checks if another policy exists with the same resource and action as the given policy.
-     *
-     * @param checkAccessPolicy an access policy being checked
-     * @return true if another access policy exists with the same resource and action, false otherwise
-     */
-    private boolean policyExists(final AccessPolicy checkAccessPolicy) {
-        for (AccessPolicy accessPolicy : getAccessPolicies()) {
-            if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
-                    && accessPolicy.getResource().equals(checkAccessPolicy.getResource())
-                    && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Checks if another user exists with the same identity.
-     *
-     * @param identifier identity of the user
-     * @param identity identity of the user
-     * @return true if another user exists with the same identity, false otherwise
-     */
-    private boolean tenantExists(final String identifier, final String identity) {
-        for (User user : getUsers()) {
-            if (!user.getIdentifier().equals(identifier)
-                    && user.getIdentity().equals(identity)) {
-                return true;
-            }
-        }
-
-        for (Group group : getGroups()) {
-            if (!group.getIdentifier().equals(identifier)
-                    && group.getName().equals(identity)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
     @Override
     public final AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
         final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies();
@@ -191,9 +128,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      * @throws IllegalStateException if a group with the same name already exists
      */
     public final synchronized Group addGroup(Group group) throws AuthorizationAccessException {
-        if (tenantExists(group.getIdentifier(), group.getName())) {
-            throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
-        }
         return doAddGroup(group);
     }
 
@@ -224,9 +158,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      * @throws IllegalStateException if there is already a group with the same name
      */
     public final synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
-        if (tenantExists(group.getIdentifier(), group.getName())) {
-            throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
-        }
         return doUpdateGroup(group);
     }
 
@@ -266,9 +197,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      * @throws IllegalStateException if there is already a user with the same identity
      */
     public final synchronized User addUser(User user) throws AuthorizationAccessException {
-        if (tenantExists(user.getIdentifier(), user.getIdentity())) {
-            throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
-        }
         return doAddUser(user);
     }
 
@@ -308,9 +236,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      * @throws IllegalStateException if there is already a user with the same identity
      */
     public final synchronized User updateUser(final User user) throws AuthorizationAccessException {
-        if (tenantExists(user.getIdentifier(), user.getIdentity())) {
-            throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
-        }
         return doUpdateUser(user);
     }
 
@@ -348,9 +273,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      * @throws AuthorizationAccessException if there was an unexpected error performing the operation
      */
     public final synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        if (policyExists(accessPolicy)) {
-            throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
-        }
         return doAddAccessPolicy(accessPolicy);
     }
 
@@ -407,17 +329,54 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
     public abstract UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException;
 
     /**
+     * Returns whether the proposed fingerprint is inheritable.
+     *
+     * @param proposedFingerprint the proposed fingerprint
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
+     */
+    @Override
+    public final void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        try {
+            // ensure we understand the proposed fingerprint
+            parsePoliciesUsersAndGroups(proposedFingerprint);
+        } catch (final AuthorizationAccessException e) {
+            throw new UninheritableAuthorizationsException("Unable to parse proposed fingerprint: " + e);
+        }
+
+        final List<User> users = getSortedUsers();
+        final List<Group> groups = getSortedGroups();
+        final List<AccessPolicy> accessPolicies = getSortedAccessPolicies();
+
+        // ensure we're in a state to inherit
+        if (!users.isEmpty() || !groups.isEmpty() || !accessPolicies.isEmpty()) {
+            throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current Authorizations is not empty..");
+        }
+    }
+
+    /**
      * Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
      *
      * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
      */
+    @Override
     public final void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
         if (fingerprint == null || fingerprint.trim().isEmpty()) {
             return;
         }
 
-        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+        final PoliciesUsersAndGroups policiesUsersAndGroups = parsePoliciesUsersAndGroups(fingerprint);
+        policiesUsersAndGroups.getUsers().forEach(user -> addUser(user));
+        policiesUsersAndGroups.getGroups().forEach(group -> addGroup(group));
+        policiesUsersAndGroups.getAccessPolicies().forEach(policy -> addAccessPolicy(policy));
+    }
+
+    private PoliciesUsersAndGroups parsePoliciesUsersAndGroups(final String fingerprint) {
+        final List<AccessPolicy> accessPolicies = new ArrayList<>();
+        final List<User> users = new ArrayList<>();
+        final List<Group> groups = new ArrayList<>();
 
+        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
         try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
             final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
             final Document document = docBuilder.parse(in);
@@ -427,29 +386,27 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
             NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
             for (int i=0; i < userNodes.getLength(); i++) {
                 Node userNode = userNodes.item(i);
-                User user = parseUser((Element) userNode);
-                addUser(user);
+                users.add(parseUser((Element) userNode));
             }
 
             // parse all the groups and add them to the current authorizer
             NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
             for (int i=0; i < groupNodes.getLength(); i++) {
                 Node groupNode = groupNodes.item(i);
-                Group group = parseGroup((Element) groupNode);
-                addGroup(group);
+                groups.add(parseGroup((Element) groupNode));
             }
 
             // parse all the policies and add them to the current authorizer
             NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
             for (int i=0; i < policyNodes.getLength(); i++) {
                 Node policyNode = policyNodes.item(i);
-                AccessPolicy policy = parsePolicy((Element) policyNode);
-                addAccessPolicy(policy);
+                accessPolicies.add(parsePolicy((Element) policyNode));
             }
-
         } catch (SAXException | ParserConfigurationException | IOException e) {
             throw new AuthorizationAccessException("Unable to parse fingerprint", e);
         }
+
+        return new PoliciesUsersAndGroups(accessPolicies, users, groups);
     }
 
     private User parseUser(final Element element) {
@@ -503,6 +460,181 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
         return builder.build();
     }
 
+    @Override
+    public final AccessPolicyProvider getAccessPolicyProvider() {
+        return new ConfigurableAccessPolicyProvider() {
+            @Override
+            public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                return AbstractPolicyBasedAuthorizer.this.getAccessPolicies();
+            }
+
+            @Override
+            public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                return AbstractPolicyBasedAuthorizer.this.getAccessPolicy(identifier);
+            }
+
+            @Override
+            public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                return AbstractPolicyBasedAuthorizer.this.addAccessPolicy(accessPolicy);
+            }
+
+            @Override
+            public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                return AbstractPolicyBasedAuthorizer.this.updateAccessPolicy(accessPolicy);
+            }
+
+            @Override
+            public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicy);
+            }
+
+            @Override
+            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
+                return usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, action);
+            }
+
+            @Override
+            public String getFingerprint() throws AuthorizationAccessException {
+                // fingerprint is managed by the encapsulating class
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                // fingerprint is managed by the encapsulating class
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                // fingerprint is managed by the encapsulating class
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public UserGroupProvider getUserGroupProvider() {
+                return new ConfigurableUserGroupProvider() {
+                    @Override
+                    public User addUser(User user) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.addUser(user);
+                    }
+
+                    @Override
+                    public User updateUser(User user) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.updateUser(user);
+                    }
+
+                    @Override
+                    public User deleteUser(User user) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.deleteUser(user);
+                    }
+
+                    @Override
+                    public Group addGroup(Group group) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.addGroup(group);
+                    }
+
+                    @Override
+                    public Group updateGroup(Group group) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.updateGroup(group);
+                    }
+
+                    @Override
+                    public Group deleteGroup(Group group) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.deleteGroup(group);
+                    }
+
+                    @Override
+                    public Set<User> getUsers() throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.getUsers();
+                    }
+
+                    @Override
+                    public User getUser(String identifier) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.getUser(identifier);
+                    }
+
+                    @Override
+                    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.getUserByIdentity(identity);
+                    }
+
+                    @Override
+                    public Set<Group> getGroups() throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.getGroups();
+                    }
+
+                    @Override
+                    public Group getGroup(String identifier) throws AuthorizationAccessException {
+                        return AbstractPolicyBasedAuthorizer.this.getGroup(identifier);
+                    }
+
+                    @Override
+                    public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                        final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
+                        final User user = usersAndAccessPolicies.getUser(identity);
+                        final Set<Group> groups = usersAndAccessPolicies.getGroups(identity);
+
+                        return new UserAndGroups() {
+                            @Override
+                            public User getUser() {
+                                return user;
+                            }
+
+                            @Override
+                            public Set<Group> getGroups() {
+                                return groups;
+                            }
+                        };
+                    }
+
+                    @Override
+                    public String getFingerprint() throws AuthorizationAccessException {
+                        // fingerprint is managed by the encapsulating class
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                        // fingerprint is managed by the encapsulating class
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                        // fingerprint is managed by the encapsulating class
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+                    }
+
+                    @Override
+                    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+                    }
+
+                    @Override
+                    public void preDestruction() throws AuthorizerDestructionException {
+                    }
+                };
+            }
+
+            @Override
+            public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+            }
+
+            @Override
+            public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+            }
+
+            @Override
+            public void preDestruction() throws AuthorizerDestructionException {
+            }
+        };
+    }
+
     /**
      * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
      * used for comparison to determine if two policy-based authorizers represent a compatible set of users,
@@ -510,17 +642,12 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
      *
      * @return the fingerprint for this Authorizer
      */
+    @Override
     public final String getFingerprint() throws AuthorizationAccessException {
         final List<User> users = getSortedUsers();
         final List<Group> groups = getSortedGroups();
         final List<AccessPolicy> policies = getSortedAccessPolicies();
 
-        // when there are no users, groups, policies we want to always return a simple indicator so
-        // it can easily be determined when comparing fingerprints
-        if (users.isEmpty() && groups.isEmpty() && policies.isEmpty()) {
-            return EMPTY_FINGERPRINT;
-        }
-
         XMLStreamWriter writer = null;
         final StringWriter out = new StringWriter();
         try {
@@ -611,38 +738,43 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
 
     private List<AccessPolicy> getSortedAccessPolicies() {
         final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
-
-        Collections.sort(policies, new Comparator<AccessPolicy>() {
-            @Override
-            public int compare(AccessPolicy p1, AccessPolicy p2) {
-                return p1.getIdentifier().compareTo(p2.getIdentifier());
-            }
-        });
+        Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
         return policies;
     }
 
     private List<Group> getSortedGroups() {
         final List<Group> groups = new ArrayList<>(getGroups());
-
-        Collections.sort(groups, new Comparator<Group>() {
-            @Override
-            public int compare(Group g1, Group g2) {
-                return g1.getIdentifier().compareTo(g2.getIdentifier());
-            }
-        });
+        Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
         return groups;
     }
 
     private List<User> getSortedUsers() {
         final List<User> users = new ArrayList<>(getUsers());
-
-        Collections.sort(users, new Comparator<User>() {
-            @Override
-            public int compare(User u1, User u2) {
-                return u1.getIdentifier().compareTo(u2.getIdentifier());
-            }
-        });
+        Collections.sort(users, Comparator.comparing(User::getIdentifier));
         return users;
     }
 
+    private static class PoliciesUsersAndGroups {
+        final List<AccessPolicy> accessPolicies;
+        final List<User> users;
+        final List<Group> groups;
+
+        public PoliciesUsersAndGroups(List<AccessPolicy> accessPolicies, List<User> users, List<Group> groups) {
+            this.accessPolicies = accessPolicies;
+            this.users = users;
+            this.groups = groups;
+        }
+
+        public List<AccessPolicy> getAccessPolicies() {
+            return accessPolicies;
+        }
+
+        public List<User> getUsers() {
+            return users;
+        }
+
+        public List<Group> getGroups() {
+            return groups;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java
index 93cabb2..1a7f751 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicy.java
@@ -16,10 +16,12 @@
  */
 package org.apache.nifi.authorization;
 
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * Defines a policy for a set of userIdentifiers to perform a set of actions on a given resource.
@@ -174,6 +176,41 @@ public class AccessPolicy {
         }
 
         /**
+         * Sets the identifier of the builder to a random UUID.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
+         */
+        public Builder identifierGenerateRandom() {
+            if (fromPolicy) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing policy");
+            }
+
+            this.identifier = UUID.randomUUID().toString();
+            return this;
+        }
+
+        /**
+         * Sets the identifier of the builder with a UUID generated from the specified seed string.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
+         */
+        public Builder identifierGenerateFromSeed(final String seed) {
+            if (fromPolicy) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing policy");
+            }
+            if (seed == null) {
+                throw new IllegalArgumentException("Cannot seed the policy identifier with a null value.");
+            }
+
+            this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+            return this;
+        }
+
+        /**
          * Sets the resource of the builder.
          *
          * @param resource the resource to set

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java
new file mode 100644
index 0000000..59ada24
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProvider.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.Set;
+
+/**
+ * Provides access to AccessPolicies and the configured UserGroupProvider.
+ *
+ * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
+ * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
+ *
+ * Additionally, extensions need to be thread safe.
+ */
+public interface AccessPolicyProvider {
+
+    /**
+     * Retrieves all access policies. Must be non null
+     *
+     * @return a list of policies
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException;
+
+    /**
+     * Retrieves the policy with the given identifier.
+     *
+     * @param identifier the id of the policy to retrieve
+     * @return the policy with the given id, or null if no matching policy exists
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException;
+
+    /**
+     * Gets the access policies for the specified resource identifier and request action.
+     *
+     * @param resourceIdentifier the resource identifier
+     * @param action the request action
+     * @return the policy matching the resouce and action, or null if no matching policy exists
+     * @throws AuthorizationAccessException if there was any unexpected error performing the operation
+     */
+    AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException;
+
+    /**
+     * Returns the UserGroupProvider for this managed Authorizer. Must be non null
+     *
+     * @return the UserGroupProvider
+     */
+    UserGroupProvider getUserGroupProvider();
+
+    /**
+     * Called immediately after instance creation for implementers to perform additional setup
+     *
+     * @param initializationContext in which to initialize
+     */
+    void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException;
+
+    /**
+     * Called to configure the Authorizer.
+     *
+     * @param configurationContext at the time of configuration
+     * @throws AuthorizerCreationException for any issues configuring the provider
+     */
+    void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
+
+    /**
+     * Called immediately before instance destruction for implementers to release resources.
+     *
+     * @throws AuthorizerDestructionException If pre-destruction fails.
+     */
+    void preDestruction() throws AuthorizerDestructionException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java
new file mode 100644
index 0000000..1013c15
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderInitializationContext.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+/**
+ * Initialization content for AccessPolicyProviders.
+ */
+public interface AccessPolicyProviderInitializationContext extends UserGroupProviderInitializationContext {
+
+    /**
+     * The lookup for accessing other configured AccessPolicyProviders.
+     *
+     * @return  The AccessPolicyProvider lookup
+     */
+    AccessPolicyProviderLookup getAccessPolicyProviderLookup();
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java
new file mode 100644
index 0000000..d387761
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AccessPolicyProviderLookup.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+/**
+ *
+ */
+public interface AccessPolicyProviderLookup {
+
+    /**
+     * Looks up the AccessPolicyProvider with the specified identifier
+     *
+     * @param identifier        The identifier of the AccessPolicyProvider
+     * @return                  The AccessPolicyProvider
+     */
+    AccessPolicyProvider getAccessPolicyProvider(String identifier);
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java
index 4f5a8f8..d5b1600 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizationRequest.java
@@ -20,6 +20,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Supplier;
 
 /**
@@ -31,6 +32,7 @@ public class AuthorizationRequest {
 
     private final Resource resource;
     private final String identity;
+    private final Set<String> groups;
     private final RequestAction action;
     private final boolean isAccessAttempt;
     private final boolean isAnonymous;
@@ -46,6 +48,7 @@ public class AuthorizationRequest {
 
         this.resource = builder.resource;
         this.identity = builder.identity;
+        this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
         this.action = builder.action;
         this.isAccessAttempt = builder.isAccessAttempt;
         this.isAnonymous = builder.isAnonymous;
@@ -82,6 +85,16 @@ public class AuthorizationRequest {
     }
 
     /**
+     * The groups the user making this request belongs to. May be null if this NiFi is not configured to load user
+     * groups or empty if the user has no groups
+     *
+     * @return The groups
+     */
+    public Set<String> getGroups() {
+        return groups;
+    }
+
+    /**
      * Whether this is a direct access attempt of the Resource if if it's being checked as part of another response.
      *
      * @return if this is a direct access attempt
@@ -142,6 +155,7 @@ public class AuthorizationRequest {
 
         private Resource resource;
         private String identity;
+        private Set<String> groups;
         private Boolean isAnonymous;
         private Boolean isAccessAttempt;
         private RequestAction action;
@@ -159,6 +173,11 @@ public class AuthorizationRequest {
             return this;
         }
 
+        public Builder groups(final Set<String> groups) {
+            this.groups = groups;
+            return this;
+        }
+
         public Builder anonymous(final Boolean isAnonymous) {
             this.isAnonymous = isAnonymous;
             return this;

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java
index cb8c7f1..28c73ff 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Authorizer.java
@@ -27,6 +27,10 @@ public interface Authorizer {
 
     /**
      * Determines if the specified user/entity is authorized to access the specified resource within the given context.
+     * These details are all contained in the AuthorizationRequest.
+     *
+     * NOTE: This method will be called often and frequently. Because of this, if the underlying implementation needs to
+     * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
      *
      * @param   request The authorization request
      * @return  the authorization result

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java
index 3721ab4..cd126ac 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java
@@ -42,9 +42,7 @@ public interface AuthorizerConfigurationContext {
 
     /**
      * @param property to lookup the descriptor and value of
-     * @return the value the component currently understands for the given
-     * PropertyDescriptor. This method does not substitute default
-     * PropertyDescriptor values, so the value returned will be null if not set
+     * @return the value the component currently understands for the given PropertyDescriptor
      */
     PropertyValue getProperty(String property);
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java
index 4b3d77c..179a179 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/AuthorizerInitializationContext.java
@@ -19,19 +19,12 @@ package org.apache.nifi.authorization;
 /**
  * Initialization content for Authorizers.
  */
-public interface AuthorizerInitializationContext {
-
-    /**
-     * The identifier of the Authorizer.
-     *
-     * @return  The identifier
-     */
-    public String getIdentifier();
+public interface AuthorizerInitializationContext extends AccessPolicyProviderInitializationContext {
 
     /**
      * The lookup for accessing other configured Authorizers.
      *
      * @return  The Authorizer lookup
      */
-    public AuthorizerLookup getAuthorizerLookup();
+    AuthorizerLookup getAuthorizerLookup();
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java
new file mode 100644
index 0000000..71258c3
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableAccessPolicyProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+
+/**
+ * Provides support for configuring AccessPolicies.
+ *
+ * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
+ * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
+ *
+ * Additionally, extensions need to be thread safe.
+ */
+public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider {
+
+    /**
+     * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
+     * used for comparison to determine if two policy-based authorizers represent a compatible set of policies.
+     *
+     * @return the fingerprint for this Authorizer
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    String getFingerprint() throws AuthorizationAccessException;
+
+    /**
+     * Parses the fingerprint and adds any policies to the current AccessPolicyProvider.
+     *
+     * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
+
+    /**
+     * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
+     * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
+     *
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
+     */
+    void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
+
+    /**
+     * Adds the given policy ensuring that multiple policies can not be added for the same resource and action.
+     *
+     * @param accessPolicy the policy to add
+     * @return the policy that was added
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
+
+    /**
+     * The policy represented by the provided instance will be updated based on the provided instance.
+     *
+     * @param accessPolicy an updated policy
+     * @return the updated policy, or null if no matching policy was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
+
+    /**
+     * Deletes the given policy.
+     *
+     * @param accessPolicy the policy to delete
+     * @return the deleted policy, or null if no matching policy was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java
new file mode 100644
index 0000000..90c0def
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ConfigurableUserGroupProvider.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+
+/**
+ * Provides support for configuring Users and Groups.
+ *
+ * NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
+ * make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
+ *
+ * Additionally, extensions need to be thread safe.
+ */
+public interface ConfigurableUserGroupProvider extends UserGroupProvider {
+
+    /**
+     * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
+     * used for comparison to determine if two policy-based authorizers represent a compatible set of users and/or groups.
+     *
+     * @return the fingerprint for this Authorizer
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    String getFingerprint() throws AuthorizationAccessException;
+
+    /**
+     * Parses the fingerprint and adds any users and groups to the current Authorizer.
+     *
+     * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
+
+    /**
+     * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
+     * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
+     *
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
+     */
+    void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
+
+    /**
+     * Adds the given user.
+     *
+     * @param user the user to add
+     * @return the user that was added
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws IllegalStateException if there is already a user with the same identity
+     */
+    User addUser(User user) throws AuthorizationAccessException;
+
+    /**
+     * The user represented by the provided instance will be updated based on the provided instance.
+     *
+     * @param user an updated user instance
+     * @return the updated user instance, or null if no matching user was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws IllegalStateException if there is already a user with the same identity
+     */
+    User updateUser(final User user) throws AuthorizationAccessException;
+
+    /**
+     * Deletes the given user.
+     *
+     * @param user the user to delete
+     * @return the user that was deleted, or null if no matching user was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    User deleteUser(User user) throws AuthorizationAccessException;
+
+    /**
+     * Adds a new group.
+     *
+     * @param group the Group to add
+     * @return the added Group
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws IllegalStateException if a group with the same name already exists
+     */
+    Group addGroup(Group group) throws AuthorizationAccessException;
+
+    /**
+     * The group represented by the provided instance will be updated based on the provided instance.
+     *
+     * @param group an updated group instance
+     * @return the updated group instance, or null if no matching group was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws IllegalStateException if there is already a group with the same name
+     */
+    Group updateGroup(Group group) throws AuthorizationAccessException;
+
+    /**
+     * Deletes the given group.
+     *
+     * @param group the group to delete
+     * @return the deleted group, or null if no matching group was found
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    Group deleteGroup(Group group) throws AuthorizationAccessException;
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java
index 7db619a..7908e85 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/Group.java
@@ -16,10 +16,12 @@
  */
 package org.apache.nifi.authorization;
 
+import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 
 /**
  * A group that users can belong to.
@@ -142,6 +144,41 @@ public class Group { // TODO rename to UserGroup
         }
 
         /**
+         * Sets the identifier of the builder to a random UUID.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
+         */
+        public Builder identifierGenerateRandom() {
+            if (fromGroup) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing group");
+            }
+
+            this.identifier = UUID.randomUUID().toString();
+            return this;
+        }
+
+        /**
+         * Sets the identifier of the builder with a UUID generated from the specified seed string.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
+         */
+        public Builder identifierGenerateFromSeed(final String seed) {
+            if (fromGroup) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing group");
+            }
+            if (seed == null) {
+                throw new IllegalArgumentException("Cannot seed the group identifier with a null value.");
+            }
+
+            this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+            return this;
+        }
+
+        /**
          * Sets the name of the builder.
          *
          * @param name the name

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java
new file mode 100644
index 0000000..d70ee55
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/ManagedAuthorizer.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+
+public interface ManagedAuthorizer extends Authorizer {
+
+    /**
+     * Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
+     * used for comparison to determine if two managed authorizers represent a compatible set of users,
+     * groups, and/or policies. Must be non null
+     *
+     * @return the fingerprint for this Authorizer
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    String getFingerprint() throws AuthorizationAccessException;
+
+    /**
+     * Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
+     *
+     * @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     */
+    void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
+
+    /**
+     * When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
+     * If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
+     *
+     * @param proposedFingerprint the proposed fingerprint
+     * @throws AuthorizationAccessException if there was an unexpected error performing the operation
+     * @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
+     */
+    void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
+
+    /**
+     * Returns the AccessPolicy provider for this managed Authorizer. Must be non null
+     *
+     * @return the AccessPolicy provider
+     */
+    AccessPolicyProvider getAccessPolicyProvider();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java
index 371241b..83368ea 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/User.java
@@ -16,7 +16,9 @@
  */
 package org.apache.nifi.authorization;
 
+import java.nio.charset.StandardCharsets;
 import java.util.Objects;
+import java.util.UUID;
 
 /**
  * A user to create authorization policies for.
@@ -129,6 +131,41 @@ public class User {
         }
 
         /**
+         * Sets the identifier of the builder to a random UUID.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing User
+         */
+        public Builder identifierGenerateRandom() {
+            if (fromUser) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing user");
+            }
+
+            this.identifier = UUID.randomUUID().toString();
+            return this;
+        }
+
+        /**
+         * Sets the identifier of the builder with a UUID generated from the specified seed string.
+         *
+         * @return the builder
+         * @throws IllegalStateException if this method is called when this builder was constructed from an existing User
+         */
+        public Builder identifierGenerateFromSeed(final String seed) {
+            if (fromUser) {
+                throw new IllegalStateException(
+                        "Identifier can not be changed when initialized from an existing user");
+            }
+            if (seed == null) {
+                throw new IllegalArgumentException("Cannot seed the user identifier with a null value.");
+            }
+
+            this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+            return this;
+        }
+
+        /**
          * Sets the identity of the builder.
          *
          * @param identity the identity to set

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java
new file mode 100644
index 0000000..486eba4
--- /dev/null
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/authorization/UserAndGroups.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import java.util.Set;
+
+/**
+ * A holder object to provide atomic access to a user and their groups.
+ */
+public interface UserAndGroups {
+
+    /**
+     * Retrieves the user, or null if the user is unknown
+     *
+     * @return the user with the given identity
+     */
+    User getUser();
+
+    /**
+     * Retrieves the groups for the user, or null if the user is unknown or has no groups.
+     *
+     * @return the set of groups for the given user identity
+     */
+    Set<Group> getGroups();
+
+}