You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2018/01/05 19:23:04 UTC

nifi git commit: NIFI-4740 Fix User Group Data Integrity Checks. This closes #2378

Repository: nifi
Updated Branches:
  refs/heads/master 4b8c80ccc -> 81d3f6f32


NIFI-4740 Fix User Group Data Integrity Checks. This closes #2378

Removes user existence check from FileUserGroupProvider when
group is created or updated. Replaces it with check in the
Authorizer Decorator class created by Authorizer Factory, so
that all providers are used.

Also fixes bug when searching for group membership by user
that returns results across all providers.


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

Branch: refs/heads/master
Commit: 81d3f6f3269efacfadbeabd6ec84fad990535299
Parents: 4b8c80c
Author: Kevin Doran <kd...@gmail.com>
Authored: Fri Jan 5 12:29:24 2018 -0500
Committer: Matt Gilman <ma...@gmail.com>
Committed: Fri Jan 5 14:22:35 2018 -0500

----------------------------------------------------------------------
 .../nifi/authorization/UserAndGroups.java       |  15 ++
 .../nifi/authorization/AuthorizerFactory.java   |  33 ++-
 .../authorization/FileUserGroupProvider.java    |  22 --
 .../nifi/authorization/FileAuthorizerTest.java  |   4 +-
 .../FileUserGroupProviderTest.java              |   3 +-
 .../CompositeConfigurableUserGroupProvider.java |  36 ++-
 .../authorization/CompositeUserAndGroups.java   |  78 ++++++
 .../CompositeUserGroupProvider.java             |  60 +++--
 ...positeConfigurableUserGroupProviderTest.java |  55 ++++-
 .../CompositeUserGroupProviderTest.java         | 196 +++------------
 .../CompositeUserGroupProviderTestBase.java     | 237 +++++++++++++++++++
 11 files changed, 521 insertions(+), 218 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index 486eba4..1011c01 100644
--- 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
@@ -24,6 +24,21 @@ import java.util.Set;
 public interface UserAndGroups {
 
     /**
+     * A static, immutable, empty implementation of the UserAndGroups interface.
+     */
+    UserAndGroups EMPTY = new UserAndGroups() {
+        @Override
+        public User getUser() {
+            return null;
+        }
+
+        @Override
+        public Set<Group> getGroups() {
+            return null;
+        }
+    };
+
+    /**
      * Retrieves the user, or null if the user is unknown
      *
      * @return the user with the given identity

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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 f466ce7..915bff0 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
@@ -47,11 +47,12 @@ public final class AuthorizerFactory {
     }
 
     /**
-     * Checks if another user exists with the same identity.
+     * Checks if another tenant (user or group) 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
+     * @param userGroupProvider the userGroupProvider to use to lookup the tenant
+     * @param identifier identity of the tenant
+     * @param identity identity of the tenant
+     * @return true if another tenant 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()) {
@@ -71,6 +72,24 @@ public final class AuthorizerFactory {
         return false;
     }
 
+    /**
+     * Check that all users in the group exist.
+     *
+     * @param userGroupProvider the userGroupProvider to use to lookup the users
+     * @param group the group whose users will be checked for existence.
+     * @return true if another user exists with the same identity, false otherwise
+     */
+    private static boolean allGroupUsersExist(final UserGroupProvider userGroupProvider, final Group group) {
+        for (String userIdentifier : group.getUsers()) {
+            User user = userGroupProvider.getUser(userIdentifier);
+            if (user == null) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private static void audit(final Authorizer authorizer, final AuthorizationRequest request, final AuthorizationResult result) {
         // audit when...
         // 1 - the authorizer supports auditing
@@ -223,6 +242,9 @@ public final class AuthorizerFactory {
                                             if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
                                                 throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
                                             }
+                                            if (!allGroupUsersExist(baseConfigurableUserGroupProvider, group)) {
+                                                throw new IllegalStateException(String.format("Cannot create group '%s' with users that don't exist.", group.getName()));
+                                            }
                                             return baseConfigurableUserGroupProvider.addGroup(group);
                                         }
 
@@ -236,6 +258,9 @@ public final class AuthorizerFactory {
                                             if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
                                                 throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
                                             }
+                                            if (!allGroupUsersExist(baseConfigurableUserGroupProvider, group)) {
+                                                throw new IllegalStateException(String.format("Cannot update group '%s' to add users that don't exist.", group.getName()));
+                                            }
                                             if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
                                                 throw new IllegalArgumentException("The specified group does not support modification.");
                                             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index edcfa51..2add45e 100644
--- 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
@@ -331,9 +331,6 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
         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());
@@ -601,25 +598,6 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
         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.
      *

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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 79e70a0..8140ef9 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
@@ -1150,7 +1150,7 @@ public class FileAuthorizerTest {
         assertEquals(3, groups.size());
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testAddGroupWhenUserDoesNotExist() throws Exception {
         writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
         writeFile(primaryTenants, EMPTY_TENANTS);
@@ -1164,6 +1164,8 @@ public class FileAuthorizerTest {
                 .build();
 
         authorizer.addGroup(group);
+
+        assertEquals(1, authorizer.getGroups().size());
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index 8b91f78..a4b7764 100644
--- 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
@@ -551,7 +551,7 @@ public class FileUserGroupProviderTest {
         assertEquals(3, groups.size());
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testAddGroupWhenUserDoesNotExist() throws Exception {
         writeFile(primaryTenants, EMPTY_TENANTS);
         userGroupProvider.onConfigured(configurationContext);
@@ -564,6 +564,7 @@ public class FileUserGroupProviderTest {
                 .build();
 
         userGroupProvider.addGroup(group);
+        assertEquals(1, userGroupProvider.getGroups().size());
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index badd37d..2140926 100644
--- 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
@@ -171,13 +171,41 @@ public class CompositeConfigurableUserGroupProvider extends CompositeUserGroupPr
 
     @Override
     public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
-        UserAndGroups userAndGroups = configurableUserGroupProvider.getUserAndGroups(identity);
+        final CompositeUserAndGroups combinedResult;
 
-        if (userAndGroups.getUser() == null) {
-            userAndGroups = super.getUserAndGroups(identity);
+        // First, lookup user and groups by identity and combine data from all providers
+        UserAndGroups configurableProviderResult = configurableUserGroupProvider.getUserAndGroups(identity);
+        UserAndGroups compositeProvidersResult = super.getUserAndGroups(identity);
+
+        if (configurableProviderResult.getUser() != null && compositeProvidersResult.getUser() != null) {
+            throw new IllegalStateException("Multiple UserGroupProviders claim to provide user " + identity);
+
+        } else if (configurableProviderResult.getUser() != null) {
+            combinedResult = new CompositeUserAndGroups(configurableProviderResult.getUser(), configurableProviderResult.getGroups());
+            combinedResult.addAllGroups(compositeProvidersResult.getGroups());
+
+        } else if (compositeProvidersResult.getUser() != null) {
+            combinedResult = new CompositeUserAndGroups(compositeProvidersResult.getUser(), compositeProvidersResult.getGroups());
+            combinedResult.addAllGroups(configurableProviderResult.getGroups());
+
+        } else {
+            return UserAndGroups.EMPTY;
+        }
+
+        // Second, lookup groups containing the user identifier
+        String userIdentifier = combinedResult.getUser().getIdentifier();
+        for (final Group group : configurableUserGroupProvider.getGroups()) {
+            if (group.getUsers() != null && group.getUsers().contains(userIdentifier)) {
+                combinedResult.addGroup(group);
+            }
+        }
+        for (final Group group : super.getGroups()) {
+            if (group.getUsers() != null && group.getUsers().contains(userIdentifier)) {
+                combinedResult.addGroup(group);
+            }
         }
 
-        return userAndGroups;
+        return combinedResult;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
new file mode 100644
index 0000000..010de8b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/CompositeUserAndGroups.java
@@ -0,0 +1,78 @@
+/*
+ * 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.HashSet;
+import java.util.Set;
+
+public class CompositeUserAndGroups implements UserAndGroups {
+
+    private User user;
+    private Set<Group> groups;
+
+    public CompositeUserAndGroups() {
+        this.user = null;
+        this.groups = null;
+    }
+
+    public CompositeUserAndGroups(User user, Set<Group> groups) {
+        this.user = user;
+        setGroups(groups);
+    }
+
+    @Override
+    public User getUser() {
+        return user;
+    }
+
+    public void setUser(User user) {
+        this.user = user;
+    }
+
+    @Override
+    public Set<Group> getGroups() {
+        return groups;
+    }
+
+    public void setGroups(Set<Group> groups) {
+        // copy the collection so that if we add to this collection it does not modify other references
+        if (groups != null) {
+            this.groups = new HashSet<>(groups);
+        } else {
+            this.groups = null;
+        }
+    }
+
+    public void addAllGroups(Set<Group> groups) {
+        if (groups != null) {
+            if (this.groups == null) {
+                this.groups = new HashSet<>();
+            }
+            this.groups.addAll(groups);
+        }
+    }
+
+    public void addGroup(Group group) {
+        if (group != null) {
+            if (this.groups == null) {
+                this.groups = new HashSet<>();
+            }
+            this.groups.add(group);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index e27f08e..5f35191 100644
--- 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
@@ -20,6 +20,8 @@ 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -30,6 +32,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 public class CompositeUserGroupProvider implements UserGroupProvider {
+    private static final Logger logger = LoggerFactory.getLogger(CompositeUserGroupProvider.class);
 
     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+");
@@ -142,33 +145,56 @@ public class CompositeUserGroupProvider implements UserGroupProvider {
 
     @Override
     public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
-        UserAndGroups userAndGroups = null;
 
+        // This method builds a UserAndGroups response by combining the data from all providers using a two-pass approach
+
+        CompositeUserAndGroups compositeUserAndGroups = new CompositeUserAndGroups();
+
+        // First pass - call getUserAndGroups(identity) on all providers, aggregate the responses, check for multiple
+        // user identity matches, which should not happen as identities should by globally unique.
+        String providerClassForUser = "";
         for (final UserGroupProvider userGroupProvider : userGroupProviders) {
-            userAndGroups = userGroupProvider.getUserAndGroups(identity);
+            UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(identity);
 
             if (userAndGroups.getUser() != null) {
-                break;
+                // is this the first match on the user?
+                if(compositeUserAndGroups.getUser() == null) {
+                    compositeUserAndGroups.setUser(userAndGroups.getUser());
+                    providerClassForUser = userGroupProvider.getClass().getName();
+                } else {
+                    logger.warn("Multiple UserGroupProviders are claiming to provide user '{}': [{} and {}] ",
+                            identity,
+                            userAndGroups.getUser(),
+                            providerClassForUser, userGroupProvider.getClass().getName());
+                    throw new IllegalStateException("Multiple UserGroupProviders are claiming to provide user " + identity);
+                }
+            }
+
+            if (userAndGroups.getGroups() != null) {
+                compositeUserAndGroups.addAllGroups(userAndGroups.getGroups());
             }
         }
 
-        if (userAndGroups == null) {
-            // per API, returning non null with null user/groups
-            return new UserAndGroups() {
-                @Override
-                public User getUser() {
-                    return null;
-                }
+        if (compositeUserAndGroups.getUser() == null) {
+            logger.debug("No user found for identity {}", identity);
+            return UserAndGroups.EMPTY;
+        }
 
-                @Override
-                public Set<Group> getGroups() {
-                    return null;
+        // Second pass - Now that we've matched a user, call getGroups() on all providers, and
+        // check all groups to see if they contain the user identifier corresponding to the identity.
+        // This is necessary because a provider might only know about a group<->userIdentifier mapping
+        // without knowing the user identifier.
+        String userIdentifier = compositeUserAndGroups.getUser().getIdentifier();
+        for (final UserGroupProvider userGroupProvider : userGroupProviders) {
+            for (final Group group : userGroupProvider.getGroups()) {
+                if (group.getUsers() != null && group.getUsers().contains(userIdentifier)) {
+                    compositeUserAndGroups.addGroup(group);
                 }
-            };
-        } else {
-            // a delegated provider contained a matching user
-            return userAndGroups;
+            }
         }
+
+        return compositeUserAndGroups;
+
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index 53c4a1d..cdef935 100644
--- 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
@@ -33,7 +33,7 @@ import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGroupProviderTest {
+public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGroupProviderTestBase {
 
     public static final String USER_5_IDENTIFIER = "user-identifier-5";
     public static final String USER_5_IDENTITY = "user-identity-5";
@@ -99,7 +99,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
 
         // users and groups
         assertEquals(3, userGroupProvider.getUsers().size());
-        assertEquals(1, userGroupProvider.getGroups().size());
+        assertEquals(2, userGroupProvider.getGroups().size());
 
         // unknown
         assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@@ -111,8 +111,49 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         assertNull(unknownUserAndGroups.getGroups());
 
         // providers
-        testConfigurableUserGroupProvider(userGroupProvider);
-        testConflictingUserGroupProvider(userGroupProvider);
+        try {
+            testConfigurableUserGroupProvider(userGroupProvider);
+            assertTrue("Should never get here as we expect the line above to throw an exception", false);
+        } catch (Exception e) {
+            assertTrue(e instanceof IllegalStateException);
+            assertTrue(e.getMessage().contains(USER_1_IDENTITY));
+        }
+
+        try {
+            testConflictingUserGroupProvider(userGroupProvider);
+            assertTrue("Should never get here as we expect the line above to throw an exception", false);
+        } catch (Exception e) {
+            assertTrue(e instanceof IllegalStateException);
+            assertTrue(e.getMessage().contains(USER_1_IDENTITY));
+        }
+    }
+
+    @Test
+    public void testConfigurableUserGroupProviderWithCollaboratingUserGroupProvider() 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));
+        }, getCollaboratingUserGroupProvider());
+
+        // 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
+        final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
+        assertNotNull(user1AndGroups);
+        assertNotNull(user1AndGroups.getUser());
+        assertEquals(2, user1AndGroups.getGroups().size()); // from CollaboratingUGP
     }
 
     private UserGroupProvider getConfigurableUserGroupProvider() {
@@ -122,7 +163,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         ).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()
+                new Group.Builder().identifier(GROUP_1_IDENTIFIER).name(GROUP_1_NAME).addUser(USER_1_IDENTIFIER).build()
         ).collect(Collectors.toSet());
 
         return new SimpleConfigurableUserGroupProvider(users, groups);
@@ -145,7 +186,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
         assertNotNull(user5AndGroups.getUser());
         assertTrue(user5AndGroups.getGroups().isEmpty());
 
-        assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
-        assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+        assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
+        assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/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
index ba4499d..acc351b 100644
--- 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
@@ -18,16 +18,8 @@ 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;
@@ -37,28 +29,7 @@ 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";
+public class CompositeUserGroupProviderTest extends CompositeUserGroupProviderTestBase {
 
     @Test(expected = AuthorizerCreationException.class)
     public void testNoConfiguredProviders() throws Exception {
@@ -154,148 +125,49 @@ public class CompositeUserGroupProviderTest {
         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]);
+        try {
+            testUserGroupProviderOne(userGroupProvider);
+            assertTrue("Should never get here as we expect the line above to throw an exception", false);
+        } catch (Exception e) {
+            assertTrue(e instanceof IllegalStateException);
+            assertTrue(e.getMessage().contains(USER_1_IDENTITY));
         }
 
-        // allow callers to mock additional providers
-        if (lookupConsumer != null) {
-            lookupConsumer.accept(lookup);
+        try {
+            testConflictingUserGroupProvider(userGroupProvider);
+            assertTrue("Should never get here as we expect the line above to throw an exception", false);
+        } catch (Exception e) {
+            assertTrue(e instanceof IllegalStateException);
+            assertTrue(e.getMessage().contains(USER_1_IDENTITY));
         }
+    }
 
-        final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
-        when(initializationContext.getUserGroupProviderLookup()).thenReturn(lookup);
-
-        compositeUserGroupProvider.initialize(initializationContext);
-
-        // configuration
-        final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+    @Test
+    public void testMultipleProvidersWithCollaboratingUserGroupProvider() throws Exception {
+        final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
+                getUserGroupProviderOne(), getUserGroupProviderTwo(), getCollaboratingUserGroupProvider());
 
-        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));
-        }
+        // users and groups
+        assertEquals(4, userGroupProvider.getUsers().size());
+        assertEquals(2, userGroupProvider.getGroups().size());
 
-        // allow callers to mock additional properties
-        if (configurationContextConsumer != null) {
-            configurationContextConsumer.accept(configurationContext);
-        }
+        // unknown
+        assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
+        assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
 
-        mockProperties(configurationContext);
+        final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
+        assertNotNull(unknownUserAndGroups);
+        assertNull(unknownUserAndGroups.getUser());
+        assertNull(unknownUserAndGroups.getGroups());
 
-        compositeUserGroupProvider.onConfigured(configurationContext);
+        // providers
+        testUserGroupProviderTwo(userGroupProvider);
 
-        return compositeUserGroupProvider;
+        final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
+        assertNotNull(user1AndGroups);
+        assertNotNull(user1AndGroups.getUser());
+        assertEquals(2, user1AndGroups.getGroups().size()); // from UGP1 and CollaboratingUGP
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/81d3f6f3/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
new file mode 100644
index 0000000..9ff8552
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/CompositeUserGroupProviderTestBase.java
@@ -0,0 +1,237 @@
+/*
+ * 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.components.PropertyValue;
+
+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.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CompositeUserGroupProviderTestBase {
+
+    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";
+
+    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));
+
+        // When used with ConflictingUserGroupProvider, we expect the line below to throw IllegalStateException
+        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));
+
+        assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY));
+
+        // When used with UserGroupProviderOne, we expect the line below to throw IllegalStateException
+        final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
+        assertNotNull(user1AndGroups);
+        assertNotNull(user1AndGroups.getUser());
+        assertEquals(1, user1AndGroups.getGroups().size());
+    }
+
+    protected UserGroupProvider getCollaboratingUserGroupProvider() {
+        final Set<User> users = Stream.of(
+                new User.Builder().identifier(USER_4_IDENTIFIER).identity(USER_4_IDENTITY).build()
+        ).collect(Collectors.toSet());
+
+        final Set<Group> groups = Stream.of(
+                // USER_1_IDENTIFIER is from UserGroupProviderOne
+                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 testCollaboratingUserGroupProvider(final UserGroupProvider userGroupProvider) {
+        // users
+        assertNotNull(userGroupProvider.getUser(USER_4_IDENTIFIER));
+        assertNotNull(userGroupProvider.getUserByIdentity(USER_4_IDENTITY));
+
+        final UserAndGroups user4AndGroups = userGroupProvider.getUserAndGroups(USER_4_IDENTITY);
+        assertNotNull(user4AndGroups);
+        assertNotNull(user4AndGroups.getUser());
+        assertEquals(1, user4AndGroups.getGroups().size());
+
+        // groups
+        assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
+        assertEquals(2, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
+    }
+
+    protected 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