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.
This commit is contained in:
Kevin Doran 2018-01-05 12:29:24 -05:00 committed by Matt Gilman
parent 4b8c80cccc
commit 81d3f6f326
No known key found for this signature in database
GPG Key ID: DF61EC19432AEE37
11 changed files with 523 additions and 220 deletions

View File

@ -23,6 +23,21 @@ import java.util.Set;
*/ */
public interface UserAndGroups { 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 * Retrieves the user, or null if the user is unknown
* *

View File

@ -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 userGroupProvider the userGroupProvider to use to lookup the tenant
* @param identity identity of the user * @param identifier identity of the tenant
* @return true if another user exists with the same identity, false otherwise * @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) { private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) {
for (User user : userGroupProvider.getUsers()) { for (User user : userGroupProvider.getUsers()) {
@ -71,6 +72,24 @@ public final class AuthorizerFactory {
return false; 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) { private static void audit(final Authorizer authorizer, final AuthorizationRequest request, final AuthorizationResult result) {
// audit when... // audit when...
// 1 - the authorizer supports auditing // 1 - the authorizer supports auditing
@ -223,6 +242,9 @@ public final class AuthorizerFactory {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", 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); return baseConfigurableUserGroupProvider.addGroup(group);
} }
@ -236,6 +258,9 @@ public final class AuthorizerFactory {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", 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)) { if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification."); throw new IllegalArgumentException("The specified group does not support modification.");
} }

View File

@ -331,9 +331,6 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
final UserGroupHolder holder = userGroupHolder.get(); final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants(); 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 // 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(); final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
jaxbGroup.setIdentifier(group.getIdentifier()); jaxbGroup.setIdentifier(group.getIdentifier());
@ -601,25 +598,6 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
return jaxbUser; 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. * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
* *

View File

@ -1150,7 +1150,7 @@ public class FileAuthorizerTest {
assertEquals(3, groups.size()); assertEquals(3, groups.size());
} }
@Test(expected = IllegalStateException.class) @Test
public void testAddGroupWhenUserDoesNotExist() throws Exception { public void testAddGroupWhenUserDoesNotExist() throws Exception {
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS); writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
writeFile(primaryTenants, EMPTY_TENANTS); writeFile(primaryTenants, EMPTY_TENANTS);
@ -1164,6 +1164,8 @@ public class FileAuthorizerTest {
.build(); .build();
authorizer.addGroup(group); authorizer.addGroup(group);
assertEquals(1, authorizer.getGroups().size());
} }
@Test @Test

View File

@ -551,7 +551,7 @@ public class FileUserGroupProviderTest {
assertEquals(3, groups.size()); assertEquals(3, groups.size());
} }
@Test(expected = IllegalStateException.class) @Test
public void testAddGroupWhenUserDoesNotExist() throws Exception { public void testAddGroupWhenUserDoesNotExist() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS); writeFile(primaryTenants, EMPTY_TENANTS);
userGroupProvider.onConfigured(configurationContext); userGroupProvider.onConfigured(configurationContext);
@ -564,6 +564,7 @@ public class FileUserGroupProviderTest {
.build(); .build();
userGroupProvider.addGroup(group); userGroupProvider.addGroup(group);
assertEquals(1, userGroupProvider.getGroups().size());
} }
@Test @Test

View File

@ -171,13 +171,41 @@ public class CompositeConfigurableUserGroupProvider extends CompositeUserGroupPr
@Override @Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
UserAndGroups userAndGroups = configurableUserGroupProvider.getUserAndGroups(identity); final CompositeUserAndGroups combinedResult;
if (userAndGroups.getUser() == null) { // First, lookup user and groups by identity and combine data from all providers
userAndGroups = super.getUserAndGroups(identity); 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;
} }
return userAndGroups; // 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 combinedResult;
} }
@Override @Override

View File

@ -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);
}
}
}

View File

@ -20,6 +20,8 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException; import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -30,6 +32,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class CompositeUserGroupProvider implements UserGroupProvider { 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 String PROP_USER_GROUP_PROVIDER_PREFIX = "User Group Provider ";
static final Pattern USER_GROUP_PROVIDER_PATTERN = Pattern.compile(PROP_USER_GROUP_PROVIDER_PREFIX + "\\S+"); static final Pattern USER_GROUP_PROVIDER_PATTERN = Pattern.compile(PROP_USER_GROUP_PROVIDER_PREFIX + "\\S+");
@ -142,35 +145,58 @@ public class CompositeUserGroupProvider implements UserGroupProvider {
@Override @Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { 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) { for (final UserGroupProvider userGroupProvider : userGroupProviders) {
userAndGroups = userGroupProvider.getUserAndGroups(identity); UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(identity);
if (userAndGroups.getUser() != null) { 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();
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 { } else {
// a delegated provider contained a matching user logger.warn("Multiple UserGroupProviders are claiming to provide user '{}': [{} and {}] ",
return userAndGroups; 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 (compositeUserAndGroups.getUser() == null) {
logger.debug("No user found for identity {}", identity);
return UserAndGroups.EMPTY;
}
// 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);
}
}
}
return compositeUserAndGroups;
}
@Override @Override
public void preDestruction() throws AuthorizerDestructionException { public void preDestruction() throws AuthorizerDestructionException {
} }

View File

@ -33,7 +33,7 @@ import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; 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_IDENTIFIER = "user-identifier-5";
public static final String USER_5_IDENTITY = "user-identity-5"; public static final String USER_5_IDENTITY = "user-identity-5";
@ -99,7 +99,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
// users and groups // users and groups
assertEquals(3, userGroupProvider.getUsers().size()); assertEquals(3, userGroupProvider.getUsers().size());
assertEquals(1, userGroupProvider.getGroups().size()); assertEquals(2, userGroupProvider.getGroups().size());
// unknown // unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER)); assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
@ -111,8 +111,49 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
assertNull(unknownUserAndGroups.getGroups()); assertNull(unknownUserAndGroups.getGroups());
// providers // providers
try {
testConfigurableUserGroupProvider(userGroupProvider); 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); 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() { private UserGroupProvider getConfigurableUserGroupProvider() {
@ -122,7 +163,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
).collect(Collectors.toSet()); ).collect(Collectors.toSet());
final Set<Group> groups = Stream.of( 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()); ).collect(Collectors.toSet());
return new SimpleConfigurableUserGroupProvider(users, groups); return new SimpleConfigurableUserGroupProvider(users, groups);
@ -145,7 +186,7 @@ public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGro
assertNotNull(user5AndGroups.getUser()); assertNotNull(user5AndGroups.getUser());
assertTrue(user5AndGroups.getGroups().isEmpty()); assertTrue(user5AndGroups.getGroups().isEmpty());
assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER)); assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size()); assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
} }
} }

View File

@ -18,16 +18,8 @@ package org.apache.nifi.authorization;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue; import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.components.PropertyValue;
import org.junit.Test; 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.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; 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.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
public class CompositeUserGroupProviderTest { public class CompositeUserGroupProviderTest extends 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";
@Test(expected = AuthorizerCreationException.class) @Test(expected = AuthorizerCreationException.class)
public void testNoConfiguredProviders() throws Exception { public void testNoConfiguredProviders() throws Exception {
@ -154,148 +125,49 @@ public class CompositeUserGroupProviderTest {
assertNull(unknownUserAndGroups.getGroups()); assertNull(unknownUserAndGroups.getGroups());
// providers // providers
testUserGroupProviderOne(userGroupProvider);
testUserGroupProviderTwo(userGroupProvider); testUserGroupProviderTwo(userGroupProvider);
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));
}
try {
testConflictingUserGroupProvider(userGroupProvider); 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));
}
} }
protected UserGroupProvider getUserGroupProviderOne() { @Test
final Set<User> users = Stream.of( public void testMultipleProvidersWithCollaboratingUserGroupProvider() throws Exception {
new User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(), final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
new User.Builder().identifier(USER_2_IDENTIFIER).identity(USER_2_IDENTITY).build() getUserGroupProviderOne(), getUserGroupProviderTwo(), getCollaboratingUserGroupProvider());
).collect(Collectors.toSet());
final Set<Group> groups = Stream.of( // users and groups
new Group.Builder().identifier(GROUP_1_IDENTIFIER).name(GROUP_1_NAME).addUser(USER_1_IDENTIFIER).build() assertEquals(4, userGroupProvider.getUsers().size());
).collect(Collectors.toSet()); assertEquals(2, userGroupProvider.getGroups().size());
return new SimpleUserGroupProvider(users, groups); // unknown
} assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
protected void testUserGroupProviderOne(final UserGroupProvider userGroupProvider) { final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
// users assertNotNull(unknownUserAndGroups);
assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER)); assertNull(unknownUserAndGroups.getUser());
assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY)); assertNull(unknownUserAndGroups.getGroups());
assertNotNull(userGroupProvider.getUser(USER_2_IDENTIFIER)); // providers
assertNotNull(userGroupProvider.getUserByIdentity(USER_2_IDENTITY)); testUserGroupProviderTwo(userGroupProvider);
final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY); final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
assertNotNull(user1AndGroups); assertNotNull(user1AndGroups);
assertNotNull(user1AndGroups.getUser()); assertNotNull(user1AndGroups.getUser());
assertEquals(1, user1AndGroups.getGroups().size()); assertEquals(2, user1AndGroups.getGroups().size()); // from UGP1 and CollaboratingUGP
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;
} }
} }

View File

@ -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;
}
}