From 439e13a8d510a6954b9a4d5cd8fa81f9d9ce95cc Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Thu, 16 Nov 2017 09:21:02 -0500 Subject: [PATCH] NIFI-4567: - Adding new properties to allow the referenced attribute of a user/group to be configurable when detecting group membership. - Expanding on documentation regarding the new properties. This closes #2274. Signed-off-by: Bryan Bende --- .../main/asciidoc/administration-guide.adoc | 123 +++++++++++++++++- .../src/main/resources/conf/authorizers.xml | 20 ++- .../ldap/tenants/LdapUserGroupProvider.java | 114 ++++++++++++---- .../tenants/LdapUserGroupProviderTest.java | 107 +++++++++++++-- .../src/test/resources/nifi-example.ldif | 30 +++++ 5 files changed, 353 insertions(+), 41 deletions(-) diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 432812804e..5a24bfec8e 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -504,13 +504,15 @@ Another option for the UserGroupProvider is the LdapUserGroupProvider. By defaul * User Search Scope - Search scope for searching users (ONE_LEVEL, OBJECT, or SUBTREE). Required if searching users. * User Search Filter - Filter for searching for users against the 'User Search Base' (i.e. (memberof=cn=team1,ou=groups,o=nifi) ). Optional. * User Identity Attribute - Attribute to use to extract user identity (i.e. cn). Optional. If not set, the entire DN is used. -* User Group Name Attribute - Attribute to use to define group membership (i.e. memberof). Optional. If not set group membership will not be calculated through the users. Will rely on group membership being defined through 'Group Member Attribute' if set. +* User Group Name Attribute - Attribute to use to define group membership (i.e. memberof). Optional. If not set group membership will not be calculated through the users. Will rely on group membership being defined through 'Group Member Attribute' if set. The value of this property is the name of the attribute in the user ldap entry that associates them with a group. The value of that user attribute could be a dn or group name for instance. What value is expected is configured in the 'User Group Name Attribute - Referenced Group Attribute'. +* User Group Name Attribute - Referenced Group Attribute - If blank, the value of the attribute defined in 'User Group Name Attribute' is expected to be the full dn of the group. If not blank, this property will define the attribute of the group ldap entry that the value of the attribute defined in 'User Group Name Attribute' is referencing (i.e. name). Use of this property requires that 'Group Search Base' is also configured. * Group Search Base - Base DN for searching for groups (i.e. ou=groups,o=nifi). Required to search groups. * Group Object Class - Object class for identifying groups (i.e. groupOfNames). Required if searching groups. * Group Search Scope - Search scope for searching groups (ONE_LEVEL, OBJECT, or SUBTREE). Required if searching groups. * Group Search Filter - Filter for searching for groups against the 'Group Search Base'. Optional. * Group Name Attribute - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used. -* Group Member Attribute - Attribute to use to define group membership (i.e. member). Optional. If not set group membership will not be calculated through the groups. Will rely on group member being defined through 'User Group Name Attribute' if set. +* Group Member Attribute - Attribute to use to define group membership (i.e. member). Optional. If not set group membership will not be calculated through the groups. Will rely on group membership being defined through 'User Group Name Attribute' if set. The value of this property is the name of the attribute in the group ldap entry that associates them with a user. The value of that group attribute could be a dn or memberUid for instance. What value is expected is configured in the 'Group Member Attribute - Referenced User Attribute'. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) +* Group Member Attribute - Referenced User Attribute - If blank, the value of the attribute defined in 'Group Member Attribute' is expected to be the full dn of the user. If not blank, this property will define the attribute of the user ldap entry that the value of the attribute defined in 'Group Member Attribute' is referencing (i.e. uid). Use of this property requires that 'User Search Base' is also configured. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) Another option for the UserGroupProvider are composite implementations. This means that multiple sources/implementations can be configured and composed. For instance, an admin can configure users/groups to be loaded from a file and a directory server. There are two composite implementations, one that supports multiple UserGroupProviders and one that supports multiple UserGroupProviders and a single configurable UserGroupProvider. @@ -616,9 +618,34 @@ After you have edited and saved the 'authorizers.xml' file, restart NiFi. The NOTE: For a brand new secure flow, providing the "Initial Admin Identity" gives that user access to get into the UI and to manage users, groups and policies. But if that user wants to start modifying the flow, they need to grant themselves policies for the root process group. The system is unable to do this automatically because in a new flow the UUID of the root process group is not permanent until the flow.xml.gz is generated. If the NiFi instance is an upgrade from an existing flow.xml.gz or a 1.x instance going from unsecure to secure, then the "Initial Admin Identity" user is automatically given the privileges to modify the flow. -Here is an example loading users and groups from LDAP but still using file based access policies: +Here is an example loading users and groups from LDAP. Group membership will be driven through the member attribute of each group. Authorization will still use file based access policies: ---- +dn: cn=User 1,ou=users,o=nifi +objectClass: organizationalPerson +objectClass: person +objectClass: inetOrgPerson +objectClass: top +cn: User 1 +sn: User1 +uid: user1 + +dn: cn=User 2,ou=users,o=nifi +objectClass: organizationalPerson +objectClass: person +objectClass: inetOrgPerson +objectClass: top +cn: User 2 +sn: User2 +uid: user2 + +dn: cn=admins,ou=groups,o=nifi +objectClass: groupOfNames +objectClass: top +cn: admins +member: cn=User 1,ou=users,o=nifi +member: cn=User 2,ou=users,o=nifi + ldap-user-group-provider @@ -652,6 +679,7 @@ Here is an example loading users and groups from LDAP but still using file based cn + ou=groups,o=nifi groupOfNames @@ -659,6 +687,7 @@ Here is an example loading users and groups from LDAP but still using file based cn member + file-access-policy-provider @@ -680,7 +709,91 @@ Here is an example loading users and groups from LDAP but still using file based The 'Initial Admin Identity' value would have loaded from the cn from John Smith's entry based on the 'User Identity Attribute' value. -Here is an example composite implementation loading users from LDAP and a local file. The users from LDAP will be read only while the users loaded from the file will be configurable in UI. +Here is an example loading users and groups from LDAP. Group membership will be driven through the member attribute of each group. Authorization will still use file based access policies: + +---- +dn: uid=User 1,ou=Users,dc=local +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: user1 +cn: User 1 + +dn: uid=User 2,ou=Users,dc=local +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: user2 +cn: User 2 + +dn: cn=Managers,ou=Groups,dc=local +objectClass: posixGroup +cn: Managers +memberUid: user1 +memberUid: user2 + + + + ldap-user-group-provider + org.apache.nifi.ldap.tenants.LdapUserGroupProvider + ANONYMOUS + + + + + + + + + + + + + + + FOLLOW + 10 secs + 10 secs + + ldap://localhost:10389 + + 30 mins + + ou=Groups,dc=local + posixAccount + ONE_LEVEL + + cn + + + + ou=Groups,dc=local + posixGroup + ONE_LEVEL + + cn + memberUid + uid + + + file-access-policy-provider + org.apache.nifi.authorization.FileAccessPolicyProvider + ldap-user-group-provider + ./conf/authorizations.xml + John Smith + + + + + + managed-authorizer + org.apache.nifi.authorization.StandardManagedAuthorizer + file-access-policy-provider + + +---- + +Here is an example composite implementation loading users and groups from LDAP and a local file. Group membership will be driven through the member attribute of each group. The users from LDAP will be read only while the users loaded from the file will be configurable in UI. ---- @@ -725,6 +838,7 @@ Here is an example composite implementation loading users from LDAP and a local cn + ou=groups,o=nifi groupOfNames @@ -732,6 +846,7 @@ Here is an example composite implementation loading users from LDAP and a local cn member + composite-user-group-provider diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml index d85a79a3fe..830a2ede7a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml @@ -95,7 +95,13 @@ 'User Identity Attribute' - Attribute to use to extract user identity (i.e. cn). Optional. If not set, the entire DN is used. 'User Group Name Attribute' - Attribute to use to define group membership (i.e. memberof). Optional. If not set group membership will not be calculated through the users. Will rely on group membership being defined - through 'Group Member Attribute' if set. + through 'Group Member Attribute' if set. The value of this property is the name of the attribute in the user ldap entry that + associates them with a group. The value of that user attribute could be a dn or group name for instance. What value is expected + is configured in the 'User Group Name Attribute - Referenced Group Attribute'. + 'User Group Name Attribute - Referenced Group Attribute' - If blank, the value of the attribute defined in 'User Group Name Attribute' + is expected to be the full dn of the group. If not blank, this property will define the attribute of the group ldap entry that + the value of the attribute defined in 'User Group Name Attribute' is referencing (i.e. name). Use of this property requires that + 'Group Search Base' is also configured. 'Group Search Base' - Base DN for searching for groups (i.e. ou=groups,o=nifi). Required to search groups. 'Group Object Class' - Object class for identifying groups (i.e. groupOfNames). Required if searching groups. @@ -103,8 +109,14 @@ 'Group Search Filter' - Filter for searching for groups against the 'Group Search Base'. Optional. 'Group Name Attribute' - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used. 'Group Member Attribute' - Attribute to use to define group membership (i.e. member). Optional. If not set - group membership will not be calculated through the groups. Will rely on group member being defined - through 'User Group Name Attribute' if set. + group membership will not be calculated through the groups. Will rely on group membership being defined + through 'User Group Name Attribute' if set. The value of this property is the name of the attribute in the group ldap entry that + associates them with a user. The value of that group attribute could be a dn or memberUid for instance. What value is expected + is configured in the 'Group Member Attribute - Referenced User Attribute'. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) + 'Group Member Attribute - Referenced User Attribute' - If blank, the value of the attribute defined in 'Group Member Attribute' + is expected to be the full dn of the user. If not blank, this property will define the attribute of the user ldap entry that + the value of the attribute defined in 'Group Member Attribute' is referencing (i.e. uid). Use of this property requires that + 'User Search Base' is also configured. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1) NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities. Group names are not mapped. @@ -142,6 +154,7 @@ + group @@ -149,6 +162,7 @@ + To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. --> diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java index d7d030ca84..ba7c4a922e 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java @@ -103,6 +103,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { public static final String PROP_USER_SEARCH_FILTER = "User Search Filter"; public static final String PROP_USER_IDENTITY_ATTRIBUTE = "User Identity Attribute"; public static final String PROP_USER_GROUP_ATTRIBUTE = "User Group Name Attribute"; + public static final String PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE = "User Group Name Attribute - Referenced Group Attribute"; public static final String PROP_GROUP_SEARCH_BASE = "Group Search Base"; public static final String PROP_GROUP_OBJECT_CLASS = "Group Object Class"; @@ -110,6 +111,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { public static final String PROP_GROUP_SEARCH_FILTER = "Group Search Filter"; public static final String PROP_GROUP_NAME_ATTRIBUTE = "Group Name Attribute"; public static final String PROP_GROUP_MEMBER_ATTRIBUTE = "Group Member Attribute"; + public static final String PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE = "Group Member Attribute - Referenced User Attribute"; public static final String PROP_SYNC_INTERVAL = "Sync Interval"; @@ -125,6 +127,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { private String userIdentityAttribute; private String userObjectClass; private String userGroupNameAttribute; + private String userGroupReferencedGroupAttribute; private boolean useDnForUserIdentity; private boolean performUserSearch; @@ -132,6 +135,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { private SearchScope groupSearchScope; private String groupSearchFilter; private String groupMemberAttribute; + private String groupMemberReferencedUserAttribute; private String groupNameAttribute; private String groupObjectClass; private boolean useDnForGroupName; @@ -270,6 +274,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { userSearchFilter = configurationContext.getProperty(PROP_USER_SEARCH_FILTER).getValue(); userIdentityAttribute = configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE).getValue(); userGroupNameAttribute = configurationContext.getProperty(PROP_USER_GROUP_ATTRIBUTE).getValue(); + userGroupReferencedGroupAttribute = configurationContext.getProperty(PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE).getValue(); try { userSearchScope = SearchScope.valueOf(rawUserSearchScope.getValue()); @@ -303,6 +308,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { groupSearchFilter = configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER).getValue(); groupNameAttribute = configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE).getValue(); groupMemberAttribute = configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE).getValue(); + groupMemberReferencedUserAttribute = configurationContext.getProperty(PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE).getValue(); try { groupSearchScope = SearchScope.valueOf(rawGroupSearchScope.getValue()); @@ -325,6 +331,16 @@ public class LdapUserGroupProvider implements UserGroupProvider { throw new AuthorizerCreationException("'Group Member Attribute' is required when searching groups but not users."); } + // ensure that performUserSearch is set when groupMemberReferencedUserAttribute is specified + if (StringUtils.isNotBlank(groupMemberReferencedUserAttribute) && !performUserSearch) { + throw new AuthorizerCreationException("''User Search Base' must be set when specifying 'Group Member Attribute - Referenced User Attribute'."); + } + + // ensure that performGroupSearch is set when userGroupReferencedGroupAttribute is specified + if (StringUtils.isNotBlank(userGroupReferencedGroupAttribute) && !performGroupSearch) { + throw new AuthorizerCreationException("'Group Search Base' must be set when specifying 'User Group Name Attribute - Referenced Group Attribute'."); + } + // get the page size if configured final PropertyValue rawPageSize = configurationContext.getProperty(PROP_PAGE_SIZE); if (rawPageSize.isSet() && StringUtils.isNotBlank(rawPageSize.getValue())) { @@ -430,10 +446,10 @@ public class LdapUserGroupProvider implements UserGroupProvider { final List groupList = new ArrayList<>(); // group dn -> user identifiers lookup - final Map> groupDnToUserIdentifierMappings = new HashMap<>(); + final Map> groupToUserIdentifierMappings = new HashMap<>(); // user dn -> user lookup - final Map userDnLookup = new HashMap<>(); + final Map userLookup = new HashMap<>(); if (performUserSearch) { // search controls @@ -461,14 +477,14 @@ public class LdapUserGroupProvider implements UserGroupProvider { userList.addAll(ldapTemplate.search(userSearchBase, userFilter.encode(), userControls, new AbstractContextMapper() { @Override protected User doMapFromContext(DirContextOperations ctx) { - final String dn = ctx.getDn().toString(); - // get the user identity final String identity = getUserIdentity(ctx); // build the user final User user = new User.Builder().identifierGenerateFromSeed(identity).identity(identity).build(); - userDnLookup.put(dn, user); + + // store the user for group member later + userLookup.put(getReferencedUserValue(ctx), user); if (StringUtils.isNotBlank(userGroupNameAttribute)) { final Attribute attributeGroups = ctx.getAttributes().get(userGroupNameAttribute); @@ -477,10 +493,10 @@ public class LdapUserGroupProvider implements UserGroupProvider { logger.warn("User group name attribute [" + userGroupNameAttribute + "] does not exist. Ignoring group membership."); } else { try { - final NamingEnumeration groupDns = (NamingEnumeration) attributeGroups.getAll(); - while (groupDns.hasMoreElements()) { - // store the group dn -> user identifier mapping - groupDnToUserIdentifierMappings.computeIfAbsent(groupDns.next(), g -> new HashSet<>()).add(user.getIdentifier()); + final NamingEnumeration groupValues = (NamingEnumeration) attributeGroups.getAll(); + while (groupValues.hasMoreElements()) { + // store the group -> user identifier mapping + groupToUserIdentifierMappings.computeIfAbsent(groupValues.next(), g -> new HashSet<>()).add(user.getIdentifier()); } } catch (NamingException e) { throw new AuthorizationAccessException("Error while retrieving user group name attribute [" + userIdentityAttribute + "]."); @@ -524,30 +540,36 @@ public class LdapUserGroupProvider implements UserGroupProvider { // get the group identity final String name = getGroupName(ctx); + // get the value of this group that may associate it to users + final String referencedGroupValue = getReferencedGroupValue(ctx); + if (!StringUtils.isBlank(groupMemberAttribute)) { Attribute attributeUsers = ctx.getAttributes().get(groupMemberAttribute); if (attributeUsers == null) { logger.warn("Group member attribute [" + groupMemberAttribute + "] does not exist. Ignoring group membership."); } else { try { - final NamingEnumeration userDns = (NamingEnumeration) attributeUsers.getAll(); - while (userDns.hasMoreElements()) { - final String userDn = userDns.next(); + final NamingEnumeration userValues = (NamingEnumeration) attributeUsers.getAll(); + while (userValues.hasMoreElements()) { + final String userValue = userValues.next(); if (performUserSearch) { - // find the user by dn add the identifier to this group - final User user = userDnLookup.get(userDn); + // find the user by it's referenced attribute and add the identifier to this group + final User user = userLookup.get(userValue); // ensure the user is known if (user != null) { - groupDnToUserIdentifierMappings.computeIfAbsent(dn, g -> new HashSet<>()).add(user.getIdentifier()); + groupToUserIdentifierMappings.computeIfAbsent(referencedGroupValue, g -> new HashSet<>()).add(user.getIdentifier()); } else { - logger.warn(String.format("%s contains member %s but that user was not found while searching users. Ignoring group membership.", name, userDn)); + logger.warn(String.format("%s contains member %s but that user was not found while searching users. Ignoring group membership.", name, userValue)); } } else { + // since performUserSearch is false, then the referenced group attribute must be blank... the user value must be the dn + final String userDn = userValue; + final String userIdentity; if (useDnForUserIdentity) { - // use the dn to avoid the unnecessary look up + // use the user value to avoid the unnecessary look up userIdentity = userDn; } else { // lookup the user to extract the user identity @@ -559,7 +581,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { // add this user userList.add(user); - groupDnToUserIdentifierMappings.computeIfAbsent(dn, g -> new HashSet<>()).add(user.getIdentifier()); + groupToUserIdentifierMappings.computeIfAbsent(referencedGroupValue, g -> new HashSet<>()).add(user.getIdentifier()); } } } catch (NamingException e) { @@ -571,9 +593,9 @@ public class LdapUserGroupProvider implements UserGroupProvider { // build this group final Group.Builder groupBuilder = new Group.Builder().identifierGenerateFromSeed(name).name(name); - // add all users that were associated with this group dn - if (groupDnToUserIdentifierMappings.containsKey(dn)) { - groupDnToUserIdentifierMappings.remove(dn).forEach(userIdentifier -> groupBuilder.addUser(userIdentifier)); + // add all users that were associated with this referenced group attribute + if (groupToUserIdentifierMappings.containsKey(referencedGroupValue)) { + groupToUserIdentifierMappings.remove(referencedGroupValue).forEach(userIdentifier -> groupBuilder.addUser(userIdentifier)); } return groupBuilder.build(); @@ -582,13 +604,15 @@ public class LdapUserGroupProvider implements UserGroupProvider { } while (hasMorePages(groupProcessor)); // any remaining groupDn's were referenced by a user but not found while searching groups - groupDnToUserIdentifierMappings.forEach((groupDn, userIdentifiers) -> { + groupToUserIdentifierMappings.forEach((referencedGroupValue, userIdentifiers) -> { logger.warn(String.format("[%s] are members of %s but that group was not found while searching users. Ignoring group membership.", - StringUtils.join(userIdentifiers, ", "), groupDn)); + StringUtils.join(userIdentifiers, ", "), referencedGroupValue)); }); } else { + // since performGroupSearch is false, then the referenced user attribute must be blank... the group value must be the dn + // groups are not being searched so lookup any groups identified while searching users - groupDnToUserIdentifierMappings.forEach((groupDn, userIdentifiers) -> { + groupToUserIdentifierMappings.forEach((groupDn, userIdentifiers) -> { final String groupName; if (useDnForGroupName) { // use the dn to avoid the unnecessary look up @@ -640,6 +664,27 @@ public class LdapUserGroupProvider implements UserGroupProvider { return IdentityMappingUtil.mapIdentity(identity, identityMappings); } + private String getReferencedUserValue(final DirContextOperations ctx) { + final String referencedUserValue; + + if (StringUtils.isBlank(groupMemberReferencedUserAttribute)) { + referencedUserValue = ctx.getDn().toString(); + } else { + final Attribute attributeName = ctx.getAttributes().get(groupMemberReferencedUserAttribute); + if (attributeName == null) { + throw new AuthorizationAccessException("Referenced user value attribute [" + groupMemberReferencedUserAttribute + "] does not exist."); + } + + try { + referencedUserValue = (String) attributeName.get(); + } catch (NamingException e) { + throw new AuthorizationAccessException("Error while retrieving reference user value attribute [" + groupMemberReferencedUserAttribute + "]."); + } + } + + return referencedUserValue; + } + private String getGroupName(final DirContextOperations ctx) { final String name; @@ -661,6 +706,27 @@ public class LdapUserGroupProvider implements UserGroupProvider { return name; } + private String getReferencedGroupValue(final DirContextOperations ctx) { + final String referencedGroupValue; + + if (StringUtils.isBlank(userGroupReferencedGroupAttribute)) { + referencedGroupValue = ctx.getDn().toString(); + } else { + final Attribute attributeName = ctx.getAttributes().get(userGroupReferencedGroupAttribute); + if (attributeName == null) { + throw new AuthorizationAccessException("Referenced group value attribute [" + userGroupReferencedGroupAttribute + "] does not exist."); + } + + try { + referencedGroupValue = (String) attributeName.get(); + } catch (NamingException e) { + throw new AuthorizationAccessException("Error while retrieving referenced group value attribute [" + userGroupReferencedGroupAttribute + "]."); + } + } + + return referencedGroupValue; + } + @AuthorizerContext public void setNiFiProperties(NiFiProperties properties) { this.properties = properties; diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java index f2cc28e777..0ea99ba9f0 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java @@ -36,14 +36,13 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import java.util.Properties; import java.util.Set; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_AUTHENTICATION_STRATEGY; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_CONNECT_TIMEOUT; +import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_MEMBER_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_NAME_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_OBJECT_CLASS; @@ -57,6 +56,7 @@ import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_READ_TIMEO import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_REFERRAL_STRATEGY; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_SYNC_INTERVAL; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_URL; +import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_GROUP_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_IDENTITY_ATTRIBUTE; import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_OBJECT_CLASS; @@ -167,7 +167,7 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.SUBTREE.name(), null)); ldapUserGroupProvider.onConfigured(configurationContext); - assertEquals(8, ldapUserGroupProvider.getUsers().size()); + assertEquals(9, ldapUserGroupProvider.getUsers().size()); assertTrue(ldapUserGroupProvider.getGroups().isEmpty()); } @@ -507,6 +507,97 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1,ou=users")); } + @Test(expected = AuthorizerCreationException.class) + public void testReferencedGroupAttributeWithoutGroupSearchBase() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", null); + when(configurationContext.getProperty(PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + ldapUserGroupProvider.onConfigured(configurationContext); + } + + @Test + public void testReferencedGroupWithoutDefiningReferencedAttribute() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", "ou=groups-2,o=nifi"); + when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null)); + when(configurationContext.getProperty(PROP_USER_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("room", null)); // using room due to reqs of groupOfNames + when(configurationContext.getProperty(PROP_USER_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue("description", null)); // using description in lieu of member + when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("room", null)); // using room due to reqs of groupOfNames + ldapUserGroupProvider.onConfigured(configurationContext); + + final Set groups = ldapUserGroupProvider.getGroups(); + assertEquals(1, groups.size()); + + final Group team3 = groups.stream().filter(group -> "team3".equals(group.getName())).findFirst().orElse(null); + assertNotNull(team3); + assertTrue(team3.getUsers().isEmpty()); + } + + @Test + public void testReferencedGroupUsingReferencedAttribute() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", "ou=groups-2,o=nifi"); + when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null)); + when(configurationContext.getProperty(PROP_USER_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue("description", null)); // using description in lieu of member + when(configurationContext.getProperty(PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("room", null)); // using room because groupOfNames requires a member + ldapUserGroupProvider.onConfigured(configurationContext); + + final Set groups = ldapUserGroupProvider.getGroups(); + assertEquals(1, groups.size()); + + final Group team3 = groups.stream().filter(group -> "team3".equals(group.getName())).findFirst().orElse(null); + assertNotNull(team3); + assertEquals(1, team3.getUsers().size()); + assertEquals(1, team3.getUsers().stream().map( + userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter( + user -> "user9".equals(user.getIdentity())).count()); + } + + @Test(expected = AuthorizerCreationException.class) + public void testReferencedUserWithoutUserSearchBase() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, "ou=groups-2,o=nifi"); + when(configurationContext.getProperty(PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null)); + ldapUserGroupProvider.onConfigured(configurationContext); + } + + @Test + public void testReferencedUserWithoutDefiningReferencedAttribute() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", "ou=groups-2,o=nifi"); + when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null)); + when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("room", null)); // using room due to reqs of groupOfNames + when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("description", null)); // using description in lieu of member + when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + ldapUserGroupProvider.onConfigured(configurationContext); + + final Set groups = ldapUserGroupProvider.getGroups(); + assertEquals(1, groups.size()); + + final Group team3 = groups.stream().filter(group -> "team3".equals(group.getName())).findFirst().orElse(null); + assertNotNull(team3); + assertTrue(team3.getUsers().isEmpty()); + } + + @Test + public void testReferencedUserUsingReferencedAttribute() throws Exception { + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", "ou=groups-2,o=nifi"); + when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("sn", null)); + when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("room", null)); // using room due to reqs of groupOfNames + when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("description", null)); // using description in lieu of member + when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null)); + when(configurationContext.getProperty(PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null)); // does not need to be the same as user id attr + ldapUserGroupProvider.onConfigured(configurationContext); + + final Set groups = ldapUserGroupProvider.getGroups(); + assertEquals(1, groups.size()); + + final Group team3 = groups.stream().filter(group -> "team3".equals(group.getName())).findFirst().orElse(null); + assertNotNull(team3); + assertEquals(1, team3.getUsers().size()); + assertEquals(1, team3.getUsers().stream().map( + userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter( + user -> "User9".equals(user.getIdentity())).count()); + } + private AuthorizerConfigurationContext getBaseConfiguration(final String userSearchBase, final String groupSearchBase) { final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class); when(configurationContext.getProperty(PROP_URL)).thenReturn(new StandardPropertyValue("ldap://127.0.0.1:" + getLdapServer().getPort(), null)); @@ -526,6 +617,7 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue(null, null)); when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); when(configurationContext.getProperty(PROP_USER_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); + when(configurationContext.getProperty(PROP_USER_GROUP_REFERENCED_GROUP_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); when(configurationContext.getProperty(PROP_GROUP_SEARCH_BASE)).thenReturn(new StandardPropertyValue(groupSearchBase, null)); when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("groupOfNames", null)); @@ -533,6 +625,7 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { when(configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER)).thenReturn(new StandardPropertyValue(null, null)); when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); + when(configurationContext.getProperty(PROP_GROUP_MEMBER_REFERENCED_USER_ATTRIBUTE)).thenReturn(new StandardPropertyValue(null, null)); return configurationContext; } @@ -540,13 +633,7 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { private NiFiProperties getNiFiProperties(final Properties properties) { final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class); when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames()); - - when(nifiProperties.getProperty(anyString())).then(new Answer() { - @Override - public String answer(InvocationOnMock invocationOnMock) throws Throwable { - return properties.getProperty((String)invocationOnMock.getArguments()[0]); - } - }); + when(nifiProperties.getProperty(anyString())).then(invocationOnMock -> properties.getProperty((String) invocationOnMock.getArguments()[0])); return nifiProperties; } } \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/resources/nifi-example.ldif b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/resources/nifi-example.ldif index 2b5bdacc42..c91feac12b 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/resources/nifi-example.ldif +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/resources/nifi-example.ldif @@ -29,6 +29,11 @@ objectClass: organizationalUnit objectClass: top ou: users +dn: ou=users-2,o=nifi +objectClass: organizationalUnit +objectClass: top +ou: users-2 + dn: cn=User 1,ou=users,o=nifi objectClass: organizationalPerson objectClass: person @@ -56,6 +61,8 @@ cn: User 3 sn: User3 uid: user3 +## since the embedded ldap does not support memberof, we are using description to simulate + dn: cn=User 4,ou=users,o=nifi objectClass: organizationalPerson objectClass: person @@ -105,11 +112,26 @@ cn: User 8 sn: User8 uid: user8 +dn: cn=User 9,ou=users-2,o=nifi +objectClass: organizationalPerson +objectClass: person +objectClass: inetOrgPerson +objectClass: top +cn: User 9 +sn: User9 +description: team3 +uid: user9 + dn: ou=groups,o=nifi objectClass: organizationalUnit objectClass: top ou: groups +dn: ou=groups-2,o=nifi +objectClass: organizationalUnit +objectClass: top +ou: groups + dn: cn=admins,ou=groups,o=nifi objectClass: groupOfNames objectClass: top @@ -134,3 +156,11 @@ objectClass: groupOfNames objectClass: top cn: team2 member: cn=User 1,ou=users,o=nifi + +## since the embedded ldap requires member to be fqdn, we are simulating using room and description + +dn: cn=team3,ou=groups-2,o=nifi +objectClass: room +objectClass: top +cn: team3 +description: user9