NIFI-4127:
- Introducing composite ConfigurableUserGroupProvider and UserGroupProvider.
- Adding appropriate unit tests.
- Updating object model to support per resource (user/group/policy) 
configuration.
- Updating UI to support per resource (user/group/policy) configuration.
- Adding necessary documentation.
- Updating documentation to clarify integrity checks.
- Providing an example of configuring a composite implementation.

Signed-off-by: Pierre Villard <[email protected]>

This closes #1978.


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

Branch: refs/heads/master
Commit: eefad291674905b6e6df078f6bf07f7526ddc575
Parents: 325fe53
Author: Matt Gilman <[email protected]>
Authored: Wed Jun 28 16:40:41 2017 -0400
Committer: Pierre Villard <[email protected]>
Committed: Tue Jul 11 18:13:04 2017 +0200

----------------------------------------------------------------------
 .../src/main/asciidoc/administration-guide.adoc |  92 ++++++
 .../ConfigurableAccessPolicyProvider.java       |  15 +
 .../ConfigurableUserGroupProvider.java          |  30 ++
 .../AccessPolicyProviderFactory.java            |   7 +
 .../AuthorizerCapabilityDetection.java          |  30 ++
 .../nifi/authorization/AuthorizerFactory.java   |  33 ++
 .../authorization/UserGroupProviderFactory.java |  14 +
 .../web/api/dto/AccessPolicySummaryDTO.java     |  13 +
 .../org/apache/nifi/web/api/dto/TenantDTO.java  |  11 +
 .../CompositeConfigurableUserGroupProvider.java | 187 ++++++++++++
 .../CompositeUserGroupProvider.java             | 177 +++++++++++
 .../StandardManagedAuthorizer.java              |   9 +-
 ....apache.nifi.authorization.UserGroupProvider |  16 +
 ...positeConfigurableUserGroupProviderTest.java | 151 ++++++++++
 .../CompositeUserGroupProviderTest.java         | 301 +++++++++++++++++++
 .../SimpleConfigurableUserGroupProvider.java    |  88 ++++++
 .../authorization/SimpleUserGroupProvider.java  |  92 ++++++
 .../src/main/resources/conf/authorizers.xml     |  41 +++
 .../apache/nifi/web/api/TenantsResource.java    |   2 +
 .../org/apache/nifi/web/api/dto/DtoFactory.java |   7 +
 .../impl/StandardPolicyBasedAuthorizerDAO.java  |  14 +-
 .../webapp/js/nf/canvas/nf-policy-management.js |   4 +-
 .../main/webapp/js/nf/users/nf-users-table.js   |   2 +-
 23 files changed, 1327 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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 a3be524..ef17cff 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -425,6 +425,8 @@ The 'authorizers.xml' file is used to define and configure 
available authorizers
 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.
 
+During startup there is a check to ensure that there are no two users/groups 
with the same identity/name. This check is executed regardless of the 
configured implementation. This is necessary because this is how users/groups 
are identified and authorized during access decisions.
+
 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.
@@ -464,6 +466,17 @@ Another option for the UserGroupProvider is the 
LdapUserGroupProvider. By defaul
 * Group Name Attribute - Attribute to use to extract group name (i.e. cn). 
Optional. If not set, the entire DN is used.
 * Group Member Attribute - Attribute to use to define group membership (i.e. 
member). Optional. If not set group membership will not be calculated through 
the groups. Will rely on group member being defined through 'User Group Name 
Attribute' if set.
 
+Another option for the UserGroupProvider are composite implementations. This 
means that multiple sources/implementations can be configured and composed. For 
instance, an admin can configure users/groups to be loaded from a file and a 
directory server. There are two composite implementations, one that supports 
multiple UserGroupProviders and one that supports multiple UserGroupProviders 
and a single configurable UserGroupProvider.
+
+The CompositeUserGroupProvider will provide support for retrieving users and 
groups from multiple sources. The CompositeUserGroupProvider has the following 
properties:
+
+* User Group Provider - The identifier of user group providers to load from. 
The name of each property must be unique, for example: "User Group Provider A", 
"User Group Provider B", "User Group Provider C" or "User Group Provider 1", 
"User Group Provider 2", "User Group Provider 3"
+
+The CompositeConfigurableUserGroupProvider will provide support for retrieving 
users and groups from multiple sources. Additionally, a single configurable 
user group provider is required. Users from the configurable user group 
provider are configurable, however users loaded from one of the User Group 
Provider [unique key] will not be. The CompositeConfigurableUserGroupProvider 
has the following properties:
+
+* Configurable User Group Provider - A configurable user group provider.
+* User Group Provider - The identifier of user group providers to load from. 
The name of each property must be unique, for example: "User Group Provider A", 
"User Group Provider B", "User Group Provider C" or "User Group Provider 1", 
"User Group Provider 2", "User Group Provider 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.
@@ -621,6 +634,85 @@ Here is an example loading users and groups from LDAP but 
still using file based
 
 The 'Initial Admin Identity' value would have loaded from the cn from John 
Smith's entry based on the 'User Identity Attribute' value.
 
+Here is an example composite implementation loading users from LDAP and a 
local file. The users from LDAP will be read only while the users loaded from 
the file will be configurable in UI.
+
+----
+<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=nifi-node1,ou=servers,dc=example,dc=com</property>
+        <property name="Initial User Identity 
2">cn=nifi-node2,ou=servers,dc=example,dc=com</property>
+    </userGroupProvider>
+    <userGroupProvider>
+        <identifier>ldap-user-group-provider</identifier>
+        <class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
+        <property name="Authentication Strategy">ANONYMOUS</property>
+
+        <property name="Manager DN"></property>
+        <property name="Manager Password"></property>
+
+        <property name="TLS - Keystore"></property>
+        <property name="TLS - Keystore Password"></property>
+        <property name="TLS - Keystore Type"></property>
+        <property name="TLS - Truststore"></property>
+        <property name="TLS - Truststore Password"></property>
+        <property name="TLS - Truststore Type"></property>
+        <property name="TLS - Client Auth"></property>
+        <property name="TLS - Protocol"></property>
+        <property name="TLS - Shutdown Gracefully"></property>
+
+        <property name="Referral Strategy">FOLLOW</property>
+        <property name="Connect Timeout">10 secs</property>
+        <property name="Read Timeout">10 secs</property>
+
+        <property name="Url">ldap://localhost:10389</property>
+        <property name="Page Size"></property>
+        <property name="Sync Interval">30 mins</property>
+
+        <property name="User Search Base">ou=users,o=nifi</property>
+        <property name="User Object Class">person</property>
+        <property name="User Search Scope">ONE_LEVEL</property>
+        <property name="User Search Filter"></property>
+        <property name="User Identity Attribute">cn</property>
+        <property name="User Group Name Attribute"></property>
+
+        <property name="Group Search Base">ou=groups,o=nifi</property>
+        <property name="Group Object Class">groupOfNames</property>
+        <property name="Group Search Scope">ONE_LEVEL</property>
+        <property name="Group Search Filter"></property>
+        <property name="Group Name Attribute">cn</property>
+        <property name="Group Member Attribute">member</property>
+    </userGroupProvider>
+    <userGroupProvider>
+        <identifier>composite-user-group-provider</identifier>
+        
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
+        <property name="Configurable User Group 
Provider">file-user-group-provider</property>
+        <property name="User Group Provider 
1">ldap-user-group-provider</property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group 
Provider">composite-user-group-provider</property>
+        <property name="Authorizations 
File">./conf/authorizations.xml</property>
+        <property name="Initial Admin Identity">John Smith</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Node Identity 
1">cn=nifi-node1,ou=servers,dc=example,dc=com</property>
+        <property name="Node Identity 
2">cn=nifi-node2,ou=servers,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>
+----
+
+In this example, the users and groups are loaded from LDAP but the servers are 
managed in a local file. The 'Initial Admin Identity' value came from an 
attribute in a LDAP entry based on the 'User Identity Attribute'. The 'Node 
Identity' values are established in the local file using the 'Initial User 
Identity' properties.
 
 [[legacy-authorized-users]]
 Legacy Authorized Users (NiFi Instance Upgrade)

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index 71258c3..1ec4dde 100644
--- 
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
@@ -65,6 +65,21 @@ public interface ConfigurableAccessPolicyProvider extends 
AccessPolicyProvider {
     AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws 
AuthorizationAccessException;
 
     /**
+     * Determines whether the specified access policy is configurable. 
Provides the opportunity for a ConfigurableAccessPolicyProvider to prevent
+     * editing of a specific access policy. By default, all known access 
policies are configurable.
+     *
+     * @param accessPolicy the access policy
+     * @return is configurable
+     */
+    default boolean isConfigurable(AccessPolicy accessPolicy) {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("Access policy cannot be null");
+        }
+
+        return getAccessPolicy(accessPolicy.getIdentifier()) != null;
+    }
+
+    /**
      * The policy represented by the provided instance will be updated based 
on the provided instance.
      *
      * @param accessPolicy an updated policy

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index 90c0def..ad592aa 100644
--- 
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
@@ -66,6 +66,21 @@ public interface ConfigurableUserGroupProvider extends 
UserGroupProvider {
     User addUser(User user) throws AuthorizationAccessException;
 
     /**
+     * Determines whether the specified user is configurable. Provides the 
opportunity for a ConfigurableUserGroupProvider to prevent
+     * editing of a specific user. By default, all known users are 
configurable.
+     *
+     * @param user the user
+     * @return is configurable
+     */
+    default boolean isConfigurable(User user) {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        return getUser(user.getIdentifier()) != null;
+    }
+
+    /**
      * The user represented by the provided instance will be updated based on 
the provided instance.
      *
      * @param user an updated user instance
@@ -95,6 +110,21 @@ public interface ConfigurableUserGroupProvider extends 
UserGroupProvider {
     Group addGroup(Group group) throws AuthorizationAccessException;
 
     /**
+     * Determines whether the specified group is configurable. Provides the 
opportunity for a ConfigurableUserGroupProvider to prevent
+     * editing of a specific group. By default, all known groups are 
configurable.
+     *
+     * @param group the group
+     * @return is configurable
+     */
+    default boolean isConfigurable(Group group) {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        return getGroup(group.getIdentifier()) != null;
+    }
+
+    /**
      * The group represented by the provided instance will be updated based on 
the provided instance.
      *
      * @param group an updated group instance

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index c71c982..07ac168 100644
--- 
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
@@ -38,6 +38,13 @@ public final class AccessPolicyProviderFactory {
                 }
 
                 @Override
+                public boolean isConfigurable(AccessPolicy accessPolicy) {
+                    try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
+                        return 
baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
+                    }
+                }
+
+                @Override
                 public AccessPolicy updateAccessPolicy(AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
                     try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
                         return 
baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index 59de908..d8e17ff 100644
--- 
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
@@ -41,5 +41,35 @@ public final class AuthorizerCapabilityDetection {
         return accessPolicyProvider.getUserGroupProvider() instanceof 
ConfigurableUserGroupProvider;
     }
 
+    public static boolean isUserConfigurable(final Authorizer authorizer, 
final User user) {
+        if (!isConfigurableUserGroupProvider(authorizer)) {
+            return false;
+        }
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) 
authorizer;
+        final ConfigurableUserGroupProvider configurableUserGroupProvider = 
(ConfigurableUserGroupProvider) 
managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+        return configurableUserGroupProvider.isConfigurable(user);
+    }
+
+    public static boolean isGroupConfigurable(final Authorizer authorizer, 
final Group group) {
+        if (!isConfigurableUserGroupProvider(authorizer)) {
+            return false;
+        }
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) 
authorizer;
+        final ConfigurableUserGroupProvider configurableUserGroupProvider = 
(ConfigurableUserGroupProvider) 
managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+        return configurableUserGroupProvider.isConfigurable(group);
+    }
+
+    public static boolean isAccessPolicyConfigurable(final Authorizer 
authorizer, final AccessPolicy accessPolicy) {
+        if (!isConfigurableAccessPolicyProvider(authorizer)) {
+            return false;
+        }
+
+        final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) 
authorizer;
+        final ConfigurableAccessPolicyProvider 
configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) 
managedAuthorizer.getAccessPolicyProvider();
+        return configurableAccessPolicyProvider.isConfigurable(accessPolicy);
+    }
+
     private AuthorizerCapabilityDetection() {}
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index 8940c78..1573e0d 100644
--- 
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
@@ -117,12 +117,23 @@ public final class AuthorizerFactory {
                             }
 
                             @Override
+                            public boolean isConfigurable(AccessPolicy 
accessPolicy) {
+                                return 
baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
+                            }
+
+                            @Override
                             public AccessPolicy 
updateAccessPolicy(AccessPolicy accessPolicy) throws 
AuthorizationAccessException {
+                                if 
(!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
+                                    throw new IllegalArgumentException("The 
specified access policy is not support modification.");
+                                }
                                 return 
baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
                             }
 
                             @Override
                             public AccessPolicy 
deleteAccessPolicy(AccessPolicy accessPolicy) throws 
AuthorizationAccessException {
+                                if 
(!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
+                                    throw new IllegalArgumentException("The 
specified access policy is not support modification.");
+                                }
                                 return 
baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
                             }
 
@@ -171,15 +182,26 @@ public final class AuthorizerFactory {
                                         }
 
                                         @Override
+                                        public boolean isConfigurable(User 
user) {
+                                            return 
baseConfigurableUserGroupProvider.isConfigurable(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()));
                                             }
+                                            if 
(!baseConfigurableUserGroupProvider.isConfigurable(user)) {
+                                                throw new 
IllegalArgumentException("The specified user does not support modification.");
+                                            }
                                             return 
baseConfigurableUserGroupProvider.updateUser(user);
                                         }
 
                                         @Override
                                         public User deleteUser(User user) 
throws AuthorizationAccessException {
+                                            if 
(!baseConfigurableUserGroupProvider.isConfigurable(user)) {
+                                                throw new 
IllegalArgumentException("The specified user does not support modification.");
+                                            }
                                             return 
baseConfigurableUserGroupProvider.deleteUser(user);
                                         }
 
@@ -192,15 +214,26 @@ public final class AuthorizerFactory {
                                         }
 
                                         @Override
+                                        public boolean isConfigurable(Group 
group) {
+                                            return 
baseConfigurableUserGroupProvider.isConfigurable(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()));
                                             }
+                                            if 
(!baseConfigurableUserGroupProvider.isConfigurable(group)) {
+                                                throw new 
IllegalArgumentException("The specified group does not support modification.");
+                                            }
                                             return 
baseConfigurableUserGroupProvider.updateGroup(group);
                                         }
 
                                         @Override
                                         public Group deleteGroup(Group group) 
throws AuthorizationAccessException {
+                                            if 
(!baseConfigurableUserGroupProvider.isConfigurable(group)) {
+                                                throw new 
IllegalArgumentException("The specified group does not support modification.");
+                                            }
                                             return 
baseConfigurableUserGroupProvider.deleteGroup(group);
                                         }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index caa265f..5d1b020 100644
--- 
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
@@ -38,6 +38,13 @@ public final class UserGroupProviderFactory {
                 }
 
                 @Override
+                public boolean isConfigurable(User user) {
+                    try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
+                        return 
baseConfigurableUserGroupProvider.isConfigurable(user);
+                    }
+                }
+
+                @Override
                 public User updateUser(User user) throws 
AuthorizationAccessException {
                     try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
                         return 
baseConfigurableUserGroupProvider.updateUser(user);
@@ -59,6 +66,13 @@ public final class UserGroupProviderFactory {
                 }
 
                 @Override
+                public boolean isConfigurable(Group group) {
+                    try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
+                        return 
baseConfigurableUserGroupProvider.isConfigurable(group);
+                    }
+                }
+
+                @Override
                 public Group updateGroup(Group group) throws 
AuthorizationAccessException {
                     try (final NarCloseable narCloseable = 
NarCloseable.withNarLoader()) {
                         return 
baseConfigurableUserGroupProvider.updateGroup(group);

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessPolicySummaryDTO.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessPolicySummaryDTO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessPolicySummaryDTO.java
index e5a6903..21c8c43 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessPolicySummaryDTO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/AccessPolicySummaryDTO.java
@@ -30,6 +30,7 @@ public class AccessPolicySummaryDTO extends ComponentDTO {
     private String resource;
     private String action;
     private ComponentReferenceEntity componentReference;
+    private Boolean configurable;
 
     /**
      * @return The action associated with this access policy.
@@ -69,4 +70,16 @@ public class AccessPolicySummaryDTO extends ComponentDTO {
     public void setComponentReference(ComponentReferenceEntity 
componentReference) {
         this.componentReference = componentReference;
     }
+
+    /**
+     * @return whether this policy is configurable
+     */
+    @ApiModelProperty(value = "Whether this policy is configurable.")
+    public Boolean getConfigurable() {
+        return configurable;
+    }
+
+    public void setConfigurable(Boolean configurable) {
+        this.configurable = configurable;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/TenantDTO.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/TenantDTO.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/TenantDTO.java
index 7915ae4..cc44403 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/TenantDTO.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/TenantDTO.java
@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlType;
 @XmlType(name = "tenant")
 public class TenantDTO extends ComponentDTO {
     private String identity;
+    private Boolean configurable;
 
     /**
      * @return tenant's identity
@@ -39,5 +40,15 @@ public class TenantDTO extends ComponentDTO {
         this.identity = identity;
     }
 
+    /**
+     * @return whether this tenant is configurable
+     */
+    @ApiModelProperty(value = "Whether this tenant is configurable.")
+    public Boolean getConfigurable() {
+        return configurable;
+    }
 
+    public void setConfigurable(Boolean configurable) {
+        this.configurable = configurable;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
new file mode 100644
index 0000000..badd37d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProvider.java
@@ -0,0 +1,187 @@
+/*
+ * 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.components.PropertyValue;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class CompositeConfigurableUserGroupProvider extends 
CompositeUserGroupProvider implements ConfigurableUserGroupProvider {
+
+    static final String PROP_CONFIGURABLE_USER_GROUP_PROVIDER = "Configurable 
User Group Provider";
+
+    private UserGroupProviderLookup userGroupProviderLookup;
+    private ConfigurableUserGroupProvider configurableUserGroupProvider;
+
+    public CompositeConfigurableUserGroupProvider() {
+        super(true);
+    }
+
+    @Override
+    public void initialize(UserGroupProviderInitializationContext 
initializationContext) throws AuthorizerCreationException {
+        userGroupProviderLookup = 
initializationContext.getUserGroupProviderLookup();
+
+        // initialize the CompositeUserGroupProvider
+        super.initialize(initializationContext);
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+        final PropertyValue configurableUserGroupProviderKey = 
configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER);
+        if (!configurableUserGroupProviderKey.isSet()) {
+            throw new AuthorizerCreationException("The Configurable User Group 
Provider must be set.");
+        }
+
+        final UserGroupProvider userGroupProvider = 
userGroupProviderLookup.getUserGroupProvider(configurableUserGroupProviderKey.getValue());
+
+        if (userGroupProvider == null) {
+            throw new AuthorizerCreationException(String.format("Unable to 
locate the Configurable User Group Provider: %s", 
configurableUserGroupProviderKey));
+        }
+
+        if (!(userGroupProvider instanceof ConfigurableUserGroupProvider)) {
+            throw new AuthorizerCreationException(String.format("The 
Configurable User Group Provider is not configurable: %s", 
configurableUserGroupProviderKey));
+        }
+
+        configurableUserGroupProvider = (ConfigurableUserGroupProvider) 
userGroupProvider;
+
+        // configure the CompositeUserGroupProvider
+        super.onConfigured(configurationContext);
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        return configurableUserGroupProvider.getFingerprint();
+    }
+
+    @Override
+    public void inheritFingerprint(String fingerprint) throws 
AuthorizationAccessException {
+        configurableUserGroupProvider.inheritFingerprint(fingerprint);
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws 
AuthorizationAccessException, UninheritableAuthorizationsException {
+        configurableUserGroupProvider.checkInheritability(proposedFingerprint);
+    }
+
+    @Override
+    public User addUser(User user) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.addUser(user);
+    }
+
+    @Override
+    public boolean isConfigurable(User user) {
+        return configurableUserGroupProvider.isConfigurable(user);
+    }
+
+    @Override
+    public User updateUser(User user) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.updateUser(user);
+    }
+
+    @Override
+    public User deleteUser(User user) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.deleteUser(user);
+    }
+
+    @Override
+    public Group addGroup(Group group) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.addGroup(group);
+    }
+
+    @Override
+    public boolean isConfigurable(Group group) {
+        return configurableUserGroupProvider.isConfigurable(group);
+    }
+
+    @Override
+    public Group updateGroup(Group group) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.updateGroup(group);
+    }
+
+    @Override
+    public Group deleteGroup(Group group) throws AuthorizationAccessException {
+        return configurableUserGroupProvider.deleteGroup(group);
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        final Set<User> users = new 
HashSet<>(configurableUserGroupProvider.getUsers());
+        users.addAll(super.getUsers());
+        return users;
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException 
{
+        User user = configurableUserGroupProvider.getUser(identifier);
+
+        if (user == null) {
+            user = super.getUser(identifier);
+        }
+
+        return user;
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws 
AuthorizationAccessException {
+        User user = configurableUserGroupProvider.getUserByIdentity(identity);
+
+        if (user == null) {
+            user = super.getUserByIdentity(identity);
+        }
+
+        return user;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        final Set<Group> groups = new 
HashSet<>(configurableUserGroupProvider.getGroups());
+        groups.addAll(super.getGroups());
+        return groups;
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws 
AuthorizationAccessException {
+        Group group = configurableUserGroupProvider.getGroup(identifier);
+
+        if (group == null) {
+            group = super.getGroup(identifier);
+        }
+
+        return group;
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(String identity) throws 
AuthorizationAccessException {
+        UserAndGroups userAndGroups = 
configurableUserGroupProvider.getUserAndGroups(identity);
+
+        if (userAndGroups.getUser() == null) {
+            userAndGroups = super.getUserAndGroups(identity);
+        }
+
+        return userAndGroups;
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+        super.preDestruction();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
new file mode 100644
index 0000000..e27f08e
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserGroupProvider.java
@@ -0,0 +1,177 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CompositeUserGroupProvider implements UserGroupProvider {
+
+    static final String PROP_USER_GROUP_PROVIDER_PREFIX = "User Group Provider 
";
+    static final Pattern USER_GROUP_PROVIDER_PATTERN = 
Pattern.compile(PROP_USER_GROUP_PROVIDER_PREFIX + "\\S+");
+
+    private final boolean allowEmptyProviderList;
+
+    private UserGroupProviderLookup userGroupProviderLookup;
+    private List<UserGroupProvider> userGroupProviders = new ArrayList<>(); // 
order matters
+
+    public CompositeUserGroupProvider() {
+        this(false);
+    }
+
+    public CompositeUserGroupProvider(boolean allowEmptyProviderList) {
+        this.allowEmptyProviderList = allowEmptyProviderList;
+    }
+
+    @Override
+    public void initialize(UserGroupProviderInitializationContext 
initializationContext) throws AuthorizerCreationException {
+        userGroupProviderLookup = 
initializationContext.getUserGroupProviderLookup();
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+        for (Map.Entry<String,String> entry : 
configurationContext.getProperties().entrySet()) {
+            Matcher matcher = 
USER_GROUP_PROVIDER_PATTERN.matcher(entry.getKey());
+            if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                final String userGroupProviderKey = entry.getValue();
+                final UserGroupProvider userGroupProvider = 
userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey);
+
+                if (userGroupProvider == null) {
+                    throw new 
AuthorizerCreationException(String.format("Unable to locate the configured User 
Group Provider: %s", userGroupProviderKey));
+                }
+
+                userGroupProviders.add(userGroupProvider);
+            }
+        }
+
+        if (!allowEmptyProviderList && userGroupProviders.isEmpty()) {
+            throw new AuthorizerCreationException("At least one User Group 
Provider must be configured.");
+        }
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        final Set<User> users = new HashSet<>();
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            users.addAll(userGroupProvider.getUsers());
+        }
+
+        return users;
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException 
{
+        User user = null;
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            user = userGroupProvider.getUser(identifier);
+
+            if (user != null) {
+                break;
+            }
+        }
+
+        return user;
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws 
AuthorizationAccessException {
+        User user = null;
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            user = userGroupProvider.getUserByIdentity(identity);
+
+            if (user != null) {
+                break;
+            }
+        }
+
+        return user;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        final Set<Group> groups = new HashSet<>();
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            groups.addAll(userGroupProvider.getGroups());
+        }
+
+        return groups;
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws 
AuthorizationAccessException {
+        Group group = null;
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            group = userGroupProvider.getGroup(identifier);
+
+            if (group != null) {
+                break;
+            }
+        }
+
+        return group;
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(String identity) throws 
AuthorizationAccessException {
+        UserAndGroups userAndGroups = null;
+
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            userAndGroups = userGroupProvider.getUserAndGroups(identity);
+
+            if (userAndGroups.getUser() != null) {
+                break;
+            }
+        }
+
+        if (userAndGroups == null) {
+            // per API, returning non null with null user/groups
+            return new UserAndGroups() {
+                @Override
+                public User getUser() {
+                    return null;
+                }
+
+                @Override
+                public Set<Group> getGroups() {
+                    return null;
+                }
+            };
+        } else {
+            // a delegated provider contained a matching user
+            return userAndGroups;
+        }
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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
index 8e726f7..4dfe040 100644
--- 
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
@@ -21,6 +21,7 @@ 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.components.PropertyValue;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -58,8 +59,12 @@ public class StandardManagedAuthorizer implements 
ManagedAuthorizer {
 
     @Override
     public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
-        final String accessPolicyProviderKey = 
configurationContext.getProperty("Access Policy Provider").getValue();
-        accessPolicyProvider = 
accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey);
+        final PropertyValue accessPolicyProviderKey = 
configurationContext.getProperty("Access Policy Provider");
+        if (!accessPolicyProviderKey.isSet()) {
+            throw new AuthorizerCreationException("The Access Policy Provider 
must be set.");
+        }
+
+        accessPolicyProvider = 
accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey.getValue());
 
         // ensure the desired access policy provider was found
         if (accessPolicyProvider == null) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
new file mode 100755
index 0000000..7bfc96a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.UserGroupProvider
@@ -0,0 +1,16 @@
+# 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.CompositeUserGroupProvider
+org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
new file mode 100644
index 0000000..53c4a1d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeConfigurableUserGroupProviderTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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.junit.Test;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static 
org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider.PROP_CONFIGURABLE_USER_GROUP_PROVIDER;
+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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CompositeConfigurableUserGroupProviderTest extends 
CompositeUserGroupProviderTest {
+
+    public static final String USER_5_IDENTIFIER = "user-identifier-5";
+    public static final String USER_5_IDENTITY = "user-identity-5";
+
+    public static final String CONFIGURABLE_USER_GROUP_PROVIDER = 
"configurable-user-group-provider";
+    public static final String NOT_CONFIGURABLE_USER_GROUP_PROVIDER = 
"not-configurable-user-group-provider";
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testNoConfigurableUserGroupProviderSpecified() throws 
Exception {
+        initCompositeUserGroupProvider(new 
CompositeConfigurableUserGroupProvider(), null, configurationContext -> {
+            
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new
 StandardPropertyValue(null, null));
+        });
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testUnknownConfigurableUserGroupProvider() throws Exception {
+        initCompositeUserGroupProvider(new 
CompositeConfigurableUserGroupProvider(), null, configurationContext -> {
+            
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new
 StandardPropertyValue("unknown-user-group-provider", null));
+        });
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testNonConfigurableUserGroupProvider() throws Exception {
+        initCompositeUserGroupProvider(new 
CompositeConfigurableUserGroupProvider(), lookup -> {
+            
when(lookup.getUserGroupProvider(eq(NOT_CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(mock(UserGroupProvider.class));
+        }, configurationContext -> {
+            
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new
 StandardPropertyValue(NOT_CONFIGURABLE_USER_GROUP_PROVIDER, null));
+        });
+    }
+
+    @Test
+    public void testConfigurableUserGroupProviderOnly() throws Exception {
+        final UserGroupProvider userGroupProvider = 
initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), 
lookup -> {
+            
when(lookup.getUserGroupProvider(eq(CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(getConfigurableUserGroupProvider());
+        }, configurationContext -> {
+            
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new
 StandardPropertyValue(CONFIGURABLE_USER_GROUP_PROVIDER, null));
+        });
+
+        // users and groups
+        assertEquals(2, userGroupProvider.getUsers().size());
+        assertEquals(1, userGroupProvider.getGroups().size());
+
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
+
+        final UserAndGroups unknownUserAndGroups = 
userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
+
+        // providers
+        testConfigurableUserGroupProvider(userGroupProvider);
+    }
+
+    @Test
+    public void 
testConfigurableUserGroupProviderWithConflictingUserGroupProvider() throws 
Exception {
+        final UserGroupProvider userGroupProvider = 
initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), 
lookup -> {
+            
when(lookup.getUserGroupProvider(eq(CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(getConfigurableUserGroupProvider());
+        }, configurationContext -> {
+            
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new
 StandardPropertyValue(CONFIGURABLE_USER_GROUP_PROVIDER, null));
+        }, getConflictingUserGroupProvider());
+
+        // users and groups
+        assertEquals(3, userGroupProvider.getUsers().size());
+        assertEquals(1, userGroupProvider.getGroups().size());
+
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
+
+        final UserAndGroups unknownUserAndGroups = 
userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
+
+        // providers
+        testConfigurableUserGroupProvider(userGroupProvider);
+        testConflictingUserGroupProvider(userGroupProvider);
+    }
+
+    private UserGroupProvider getConfigurableUserGroupProvider() {
+        final Set<User> users = Stream.of(
+                new 
User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
+                new 
User.Builder().identifier(USER_5_IDENTIFIER).identity(USER_5_IDENTITY).build()
+        ).collect(Collectors.toSet());
+
+        final Set<Group> groups = Stream.of(
+                new 
Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_1_IDENTIFIER).build()
+        ).collect(Collectors.toSet());
+
+        return new SimpleConfigurableUserGroupProvider(users, groups);
+    }
+
+    private void testConfigurableUserGroupProvider(final UserGroupProvider 
userGroupProvider) {
+        assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY));
+
+        final UserAndGroups user1AndGroups = 
userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
+        assertNotNull(user1AndGroups);
+        assertNotNull(user1AndGroups.getUser());
+        assertEquals(1, user1AndGroups.getGroups().size());
+
+        assertNotNull(userGroupProvider.getUser(USER_5_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_5_IDENTITY));
+
+        final UserAndGroups user5AndGroups = 
userGroupProvider.getUserAndGroups(USER_5_IDENTITY);
+        assertNotNull(user5AndGroups);
+        assertNotNull(user5AndGroups.getUser());
+        assertTrue(user5AndGroups.getGroups().isEmpty());
+
+        assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
+        assertEquals(1, 
userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
new file mode 100644
index 0000000..ba4499d
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static 
org.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
+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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CompositeUserGroupProviderTest {
+
+    public static final String USER_1_IDENTIFIER = "user-identifier-1";
+    public static final String USER_1_IDENTITY = "user-identity-1";
+
+    public static final String USER_2_IDENTIFIER = "user-identifier-2";
+    public static final String USER_2_IDENTITY = "user-identity-2";
+
+    public static final String USER_3_IDENTIFIER = "user-identifier-3";
+    public static final String USER_3_IDENTITY = "user-identity-3";
+
+    public static final String USER_4_IDENTIFIER = "user-identifier-4";
+    public static final String USER_4_IDENTITY = "user-identity-4";
+
+    public static final String GROUP_1_IDENTIFIER = "group-identifier-1";
+    public static final String GROUP_1_NAME = "group-name-1";
+
+    public static final String GROUP_2_IDENTIFIER = "group-identifier-2";
+    public static final String GROUP_2_NAME = "group-name-2";
+
+    public static final String NOT_A_REAL_USER_IDENTIFIER = 
"not-a-real-user-identifier";
+    public static final String NOT_A_REAL_USER_IDENTITY = 
"not-a-real-user-identity";
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testNoConfiguredProviders() throws Exception {
+        initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, 
null);
+    }
+
+    @Test
+    public void testNoConfiguredProvidersAllowed() throws Exception {
+        initCompositeUserGroupProvider(new CompositeUserGroupProvider(true), 
null, null);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testUnknownProvider() throws Exception {
+        // initialization
+        final UserGroupProviderInitializationContext initializationContext = 
mock(UserGroupProviderInitializationContext.class);
+        
when(initializationContext.getUserGroupProviderLookup()).thenReturn(new 
UserGroupProviderLookup() {
+            @Override
+            public UserGroupProvider getUserGroupProvider(String identifier) {
+                return null;
+            }
+        });
+
+        // configuration
+        final AuthorizerConfigurationContext configurationContext = 
mock(AuthorizerConfigurationContext.class);
+        
when(configurationContext.getProperty(eq(PROP_USER_GROUP_PROVIDER_PREFIX + 
"1"))).thenReturn(new StandardPropertyValue(String.valueOf("1"), null));
+        mockProperties(configurationContext);
+
+        final CompositeUserGroupProvider compositeUserGroupProvider = new 
CompositeUserGroupProvider();
+        compositeUserGroupProvider.initialize(initializationContext);
+        compositeUserGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testOneProvider() throws Exception {
+        final UserGroupProvider userGroupProvider = 
initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null, 
getUserGroupProviderOne());
+
+        // users and groups
+        assertEquals(2, userGroupProvider.getUsers().size());
+        assertEquals(1, userGroupProvider.getGroups().size());
+
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
+
+        final UserAndGroups unknownUserAndGroups = 
userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
+
+        // providers
+        testUserGroupProviderOne(userGroupProvider);
+    }
+
+    @Test
+    public void testMultipleProviders() throws Exception {
+        final UserGroupProvider userGroupProvider = 
initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
+                getUserGroupProviderOne(), getUserGroupProviderTwo());
+
+        // users and groups
+        assertEquals(3, userGroupProvider.getUsers().size());
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
+
+        final UserAndGroups unknownUserAndGroups = 
userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
+
+        // providers
+        testUserGroupProviderOne(userGroupProvider);
+        testUserGroupProviderTwo(userGroupProvider);
+    }
+
+    @Test
+    public void testMultipleProvidersWithConflictingUsers() throws Exception {
+        final UserGroupProvider userGroupProvider = 
initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
+                getUserGroupProviderOne(), getUserGroupProviderTwo(), 
getConflictingUserGroupProvider());
+
+        // users and groups
+        assertEquals(4, userGroupProvider.getUsers().size());
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
+
+        final UserAndGroups unknownUserAndGroups = 
userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
+
+        // providers
+        testUserGroupProviderOne(userGroupProvider);
+        testUserGroupProviderTwo(userGroupProvider);
+        testConflictingUserGroupProvider(userGroupProvider);
+    }
+
+    protected UserGroupProvider getUserGroupProviderOne() {
+        final Set<User> users = Stream.of(
+                new 
User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
+                new 
User.Builder().identifier(USER_2_IDENTIFIER).identity(USER_2_IDENTITY).build()
+        ).collect(Collectors.toSet());
+
+        final Set<Group> groups = Stream.of(
+                new 
Group.Builder().identifier(GROUP_1_IDENTIFIER).name(GROUP_1_NAME).addUser(USER_1_IDENTIFIER).build()
+        ).collect(Collectors.toSet());
+
+        return new SimpleUserGroupProvider(users, groups);
+    }
+
+    protected void testUserGroupProviderOne(final UserGroupProvider 
userGroupProvider) {
+        // users
+        assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY));
+
+        assertNotNull(userGroupProvider.getUser(USER_2_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_2_IDENTITY));
+
+        final UserAndGroups user1AndGroups = 
userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
+        assertNotNull(user1AndGroups);
+        assertNotNull(user1AndGroups.getUser());
+        assertEquals(1, user1AndGroups.getGroups().size());
+
+        final UserAndGroups user2AndGroups = 
userGroupProvider.getUserAndGroups(USER_2_IDENTITY);
+        assertNotNull(user2AndGroups);
+        assertNotNull(user2AndGroups.getUser());
+        assertTrue(user2AndGroups.getGroups().isEmpty());
+
+        // groups
+        assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
+        assertEquals(1, 
userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
+    }
+
+    protected UserGroupProvider getUserGroupProviderTwo() {
+        final Set<User> users = Stream.of(
+                new 
User.Builder().identifier(USER_3_IDENTIFIER).identity(USER_3_IDENTITY).build()
+        ).collect(Collectors.toSet());
+
+        final Set<Group> groups = Stream.of(
+                new 
Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_3_IDENTIFIER).build()
+        ).collect(Collectors.toSet());
+
+        return new SimpleUserGroupProvider(users, groups);
+    }
+
+    protected void testUserGroupProviderTwo(final UserGroupProvider 
userGroupProvider) {
+        // users
+        assertNotNull(userGroupProvider.getUser(USER_3_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_3_IDENTITY));
+
+        final UserAndGroups user3AndGroups = 
userGroupProvider.getUserAndGroups(USER_3_IDENTITY);
+        assertNotNull(user3AndGroups);
+        assertNotNull(user3AndGroups.getUser());
+        assertEquals(1, user3AndGroups.getGroups().size());
+
+        // groups
+        assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
+        assertEquals(1, 
userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+    }
+
+    protected UserGroupProvider getConflictingUserGroupProvider() {
+        final Set<User> users = Stream.of(
+                new 
User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
+                new 
User.Builder().identifier(USER_4_IDENTIFIER).identity(USER_4_IDENTITY).build()
+        ).collect(Collectors.toSet());
+
+        final Set<Group> groups = Stream.of(
+                new 
Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_1_IDENTIFIER).addUser(USER_4_IDENTIFIER).build()
+        ).collect(Collectors.toSet());
+
+        return new SimpleUserGroupProvider(users, groups);
+    }
+
+    protected void testConflictingUserGroupProvider(final UserGroupProvider 
userGroupProvider) {
+        assertNotNull(userGroupProvider.getUser(USER_4_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_4_IDENTITY));
+    }
+
+    private void mockProperties(final AuthorizerConfigurationContext 
configurationContext) {
+        when(configurationContext.getProperties()).then((invocation) -> {
+            final Map<String, String> properties = new HashMap<>();
+
+            int i = 1;
+            while (true) {
+                final String key = PROP_USER_GROUP_PROVIDER_PREFIX + i++;
+                final PropertyValue value = 
configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            return properties;
+        });
+    }
+
+    protected UserGroupProvider initCompositeUserGroupProvider(
+            final CompositeUserGroupProvider compositeUserGroupProvider, final 
Consumer<UserGroupProviderLookup> lookupConsumer,
+            final Consumer<AuthorizerConfigurationContext> 
configurationContextConsumer, final UserGroupProvider... providers) {
+
+        // initialization
+        final UserGroupProviderLookup lookup = 
mock(UserGroupProviderLookup.class);
+
+        for (int i = 1; i <= providers.length; i++) {
+            
when(lookup.getUserGroupProvider(eq(String.valueOf(i)))).thenReturn(providers[i 
- 1]);
+        }
+
+        // allow callers to mock additional providers
+        if (lookupConsumer != null) {
+            lookupConsumer.accept(lookup);
+        }
+
+        final UserGroupProviderInitializationContext initializationContext = 
mock(UserGroupProviderInitializationContext.class);
+        
when(initializationContext.getUserGroupProviderLookup()).thenReturn(lookup);
+
+        compositeUserGroupProvider.initialize(initializationContext);
+
+        // configuration
+        final AuthorizerConfigurationContext configurationContext = 
mock(AuthorizerConfigurationContext.class);
+
+        for (int i = 1; i <= providers.length; i++) {
+            
when(configurationContext.getProperty(eq(PROP_USER_GROUP_PROVIDER_PREFIX + 
i))).thenReturn(new StandardPropertyValue(String.valueOf(i), null));
+        }
+
+        // allow callers to mock additional properties
+        if (configurationContextConsumer != null) {
+            configurationContextConsumer.accept(configurationContext);
+        }
+
+        mockProperties(configurationContext);
+
+        compositeUserGroupProvider.onConfigured(configurationContext);
+
+        return compositeUserGroupProvider;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
new file mode 100644
index 0000000..791d968
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleConfigurableUserGroupProvider.java
@@ -0,0 +1,88 @@
+/*
+ * 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;
+
+import java.util.Set;
+
+public class SimpleConfigurableUserGroupProvider extends 
SimpleUserGroupProvider implements ConfigurableUserGroupProvider {
+
+    private final Set<User> users;
+    private final Set<Group> groups;
+
+    public SimpleConfigurableUserGroupProvider(Set<User> users, Set<Group> 
groups) {
+        super(users, groups);
+
+        this.users = users;
+        this.groups = groups;
+    }
+
+    @Override
+    public User addUser(User user) throws AuthorizationAccessException {
+        users.add(user);
+        return user;
+    }
+
+    @Override
+    public User updateUser(User user) throws AuthorizationAccessException {
+        users.remove(user);
+        users.add(user);
+        return user;
+    }
+
+    @Override
+    public User deleteUser(User user) throws AuthorizationAccessException {
+        users.remove(user);
+        return user;
+    }
+
+    @Override
+    public Group addGroup(Group group) throws AuthorizationAccessException {
+        groups.add(group);
+        return group;
+    }
+
+    @Override
+    public Group updateGroup(Group group) throws AuthorizationAccessException {
+        groups.remove(group);
+        groups.add(group);
+        return group;
+    }
+
+    @Override
+    public Group deleteGroup(Group group) throws AuthorizationAccessException {
+        groups.remove(group);
+        return group;
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        return "fingerprint";
+    }
+
+    @Override
+    public void inheritFingerprint(String fingerprint) throws 
AuthorizationAccessException {
+
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws 
AuthorizationAccessException, UninheritableAuthorizationsException {
+
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
new file mode 100644
index 0000000..7bafe3f
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/SimpleUserGroupProvider.java
@@ -0,0 +1,92 @@
+/*
+ * 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;
+import java.util.stream.Collectors;
+
+public class SimpleUserGroupProvider implements UserGroupProvider {
+
+    private final Set<User> users;
+    private final Set<Group> groups;
+
+    public SimpleUserGroupProvider(Set<User> users, Set<Group> groups) {
+        this.users = users;
+        this.groups = groups;
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return users;
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException 
{
+        return users.stream().filter(user -> 
user.getIdentifier().equals(identifier)).findFirst().orElse(null);
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws 
AuthorizationAccessException {
+        return users.stream().filter(user -> 
user.getIdentity().equals(identity)).findFirst().orElse(null);
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return groups;
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws 
AuthorizationAccessException {
+        return groups.stream().filter(groups -> 
groups.getIdentifier().equals(identifier)).findFirst().orElse(null);
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(String identity) throws 
AuthorizationAccessException {
+        final User user = users.stream().filter(u -> 
u.getIdentity().equals(identity)).findFirst().orElse(null);
+        return new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                if (user == null) {
+                    return null;
+                } else {
+                    return groups.stream().filter(group -> 
group.getUsers().contains(user.getIdentifier())).collect(Collectors.toSet());
+                }
+            }
+        };
+    }
+
+    @Override
+    public void initialize(UserGroupProviderInitializationContext 
initializationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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 1cf29b1..d85a79a 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
@@ -153,6 +153,47 @@
     To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. -->
 
     <!--
+        The CompositeUserGroupProvider will provide support for retrieving 
users and groups from multiple sources.
+
+        - User Group Provider [unique key] - The identifier of user group 
providers to load from. The name of
+            each property must be unique, for example: "User Group Provider 
A", "User Group Provider B",
+            "User Group Provider C" or "User Group Provider 1", "User Group 
Provider 2", "User Group Provider 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties are 
not applied in this implementation. This behavior
+            would need to be applied by the base implementation.
+    -->
+    <!-- To enable the composite-user-group-provider remove 2 lines. This is 1 
of 2.
+    <userGroupProvider>
+        <identifier>composite-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.CompositeUserGroupProvider</class>
+        <property name="User Group Provider 1"></property>
+    </userGroupProvider>
+    To enable the composite-user-group-provider remove 2 lines. This is 2 of 
2. -->
+
+    <!--
+        The CompositeConfigurableUserGroupProvider will provide support for 
retrieving users and groups from multiple sources.
+        Additionally, a single configurable user group provider is required. 
Users from the configurable user group provider
+        are configurable, however users loaded from one of the User Group 
Provider [unique key] will not be.
+
+        - Configurable User Group Provider - A configurable user group 
provider.
+
+        - User Group Provider [unique key] - The identifier of user group 
providers to load from. The name of
+            each property must be unique, for example: "User Group Provider 
A", "User Group Provider B",
+            "User Group Provider C" or "User Group Provider 1", "User Group 
Provider 2", "User Group Provider 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties are 
not applied in this implementation. This behavior
+            would need to be applied by the base implementation.
+    -->
+    <!-- To enable the composite-configurable-user-group-provider remove 2 
lines. This is 1 of 2.
+    <userGroupProvider>
+        <identifier>composite-configurable-user-group-provider</identifier>
+        
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
+        <property name="Configurable User Group 
Provider">file-user-group-provider</property>
+        <property name="User Group Provider 1"></property>
+    </userGroupProvider>
+    To enable the composite-configurable-user-group-provider remove 2 lines. 
This is 2 of 2. -->
+
+    <!--
         The FileAccessPolicyProvider will provide support for managing access 
policies which is backed by a file
         on the local file system.
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/eefad291/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 46b3571..080ae5f 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
@@ -929,6 +929,7 @@ public class TenantsResource extends ApplicationResource {
                 final TenantDTO tenant = new TenantDTO();
                 tenant.setId(user.getId());
                 tenant.setIdentity(user.getIdentity());
+                tenant.setConfigurable(user.getConfigurable());
 
                 final TenantEntity entity = new TenantEntity();
                 entity.setPermissions(userEntity.getPermissions());
@@ -947,6 +948,7 @@ public class TenantsResource extends ApplicationResource {
                 final TenantDTO tenant = new TenantDTO();
                 tenant.setId(userGroup.getId());
                 tenant.setIdentity(userGroup.getIdentity());
+                tenant.setConfigurable(userGroup.getConfigurable());
 
                 final TenantEntity entity = new TenantEntity();
                 entity.setPermissions(userGroupEntity.getPermissions());

Reply via email to