You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/11/20 15:10:23 UTC

nifi git commit: 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.

Repository: nifi
Updated Branches:
  refs/heads/master 6b7592172 -> 439e13a8d


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 <bb...@apache.org>


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

Branch: refs/heads/master
Commit: 439e13a8d510a6954b9a4d5cd8fa81f9d9ce95cc
Parents: 6b75921
Author: Matt Gilman <ma...@gmail.com>
Authored: Thu Nov 16 09:21:02 2017 -0500
Committer: Bryan Bende <bb...@apache.org>
Committed: Mon Nov 20 10:09:53 2017 -0500

----------------------------------------------------------------------
 .../src/main/asciidoc/administration-guide.adoc | 123 ++++++++++++++++++-
 .../src/main/resources/conf/authorizers.xml     |  20 ++-
 .../ldap/tenants/LdapUserGroupProvider.java     | 114 +++++++++++++----
 .../ldap/tenants/LdapUserGroupProviderTest.java | 107 ++++++++++++++--
 .../src/test/resources/nifi-example.ldif        |  30 +++++
 5 files changed, 353 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/439e13a8/nifi-docs/src/main/asciidoc/administration-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 4328128..5a24bfe 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
+
 <authorizers>
     <userGroupProvider>
         <identifier>ldap-user-group-provider</identifier>
@@ -652,6 +679,7 @@ Here is an example loading users and groups from LDAP but still using file based
         <property name="User Search Filter"></property>
         <property name="User Identity Attribute">cn</property>
         <property name="User Group Name Attribute"></property>
+        <property name="User Group Name Attribute - Referenced Group Attribute"></property>
 
         <property name="Group Search Base">ou=groups,o=nifi</property>
         <property name="Group Object Class">groupOfNames</property>
@@ -659,6 +687,7 @@ Here is an example loading users and groups from LDAP but still using file based
         <property name="Group Search Filter"></property>
         <property name="Group Name Attribute">cn</property>
         <property name="Group Member Attribute">member</property>
+        <property name="Group Member Attribute - Referenced User Attribute"></property>
     </userGroupProvider>
     <accessPolicyProvider>
         <identifier>file-access-policy-provider</identifier>
@@ -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
+
+<authorizers>
+    <userGroupProvider>
+        <identifier>ldap-user-group-provider</identifier>
+        <class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
+        <property name="Authentication Strategy">ANONYMOUS</property>
+
+        <property name="Manager DN"></property>
+        <property name="Manager Password"></property>
+
+        <property name="TLS - Keystore"></property>
+        <property name="TLS - Keystore Password"></property>
+        <property name="TLS - Keystore Type"></property>
+        <property name="TLS - Truststore"></property>
+        <property name="TLS - Truststore Password"></property>
+        <property name="TLS - Truststore Type"></property>
+        <property name="TLS - Client Auth"></property>
+        <property name="TLS - Protocol"></property>
+        <property name="TLS - Shutdown Gracefully"></property>
+
+        <property name="Referral Strategy">FOLLOW</property>
+        <property name="Connect Timeout">10 secs</property>
+        <property name="Read Timeout">10 secs</property>
+
+        <property name="Url">ldap://localhost:10389</property>
+        <property name="Page Size"></property>
+        <property name="Sync Interval">30 mins</property>
+
+        <property name="User Search Base">ou=Groups,dc=local</property>
+        <property name="User Object Class">posixAccount</property>
+        <property name="User Search Scope">ONE_LEVEL</property>
+        <property name="User Search Filter"></property>
+        <property name="User Identity Attribute">cn</property>
+        <property name="User Group Name Attribute"></property>
+        <property name="User Group Name Attribute - Referenced Group Attribute"></property>
+
+        <property name="Group Search Base">ou=Groups,dc=local</property>
+        <property name="Group Object Class">posixGroup</property>
+        <property name="Group Search Scope">ONE_LEVEL</property>
+        <property name="Group Search Filter"></property>
+        <property name="Group Name Attribute">cn</property>
+        <property name="Group Member Attribute">memberUid</property>
+        <property name="Group Member Attribute - Referenced User Attribute">uid</property>
+    </userGroupProvider>
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group Provider">ldap-user-group-provider</property>
+        <property name="Authorizations File">./conf/authorizations.xml</property>
+        <property name="Initial Admin Identity">John Smith</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Node Identity 1"></property>
+    </accessPolicyProvider>
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy Provider">file-access-policy-provider</property>
+    </authorizer>
+</authorizers>
+----
+
+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.
 
 ----
 <authorizers>
@@ -725,6 +838,7 @@ Here is an example composite implementation loading users from LDAP and a local
         <property name="User Search Filter"></property>
         <property name="User Identity Attribute">cn</property>
         <property name="User Group Name Attribute"></property>
+        <property name="User Group Name Attribute - Referenced Group Attribute"></property>
 
         <property name="Group Search Base">ou=groups,o=nifi</property>
         <property name="Group Object Class">groupOfNames</property>
@@ -732,6 +846,7 @@ Here is an example composite implementation loading users from LDAP and a local
         <property name="Group Search Filter"></property>
         <property name="Group Name Attribute">cn</property>
         <property name="Group Member Attribute">member</property>
+        <property name="Group Member Attribute - Referenced User Attribute"></property>
     </userGroupProvider>
     <userGroupProvider>
         <identifier>composite-user-group-provider</identifier>

http://git-wip-us.apache.org/repos/asf/nifi/blob/439e13a8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
index d85a79a..830a2ed 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 @@
         <property name="User Search Filter"></property>
         <property name="User Identity Attribute"></property>
         <property name="User Group Name Attribute"></property>
+        <property name="User Group Name Attribute - Referenced Group Attribute"></property>
 
         <property name="Group Search Base"></property>
         <property name="Group Object Class">group</property>
@@ -149,6 +162,7 @@
         <property name="Group Search Filter"></property>
         <property name="Group Name Attribute"></property>
         <property name="Group Member Attribute"></property>
+        <property name="Group Member Attribute - Referenced User Attribute"></property>
     </userGroupProvider>
     To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. -->
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/439e13a8/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java
----------------------------------------------------------------------
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 d7d030c..ba7c4a9 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<Group> groupList = new ArrayList<>();
 
             // group dn -> user identifiers lookup
-            final Map<String, Set<String>> groupDnToUserIdentifierMappings = new HashMap<>();
+            final Map<String, Set<String>> groupToUserIdentifierMappings = new HashMap<>();
 
             // user dn -> user lookup
-            final Map<String, User> userDnLookup = new HashMap<>();
+            final Map<String, User> 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<User>() {
                         @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<String> groupDns = (NamingEnumeration<String>) attributeGroups.getAll();
-                                        while (groupDns.hasMoreElements()) {
-                                            // store the group dn -> user identifier mapping
-                                            groupDnToUserIdentifierMappings.computeIfAbsent(groupDns.next(), g -> new HashSet<>()).add(user.getIdentifier());
+                                        final NamingEnumeration<String> groupValues = (NamingEnumeration<String>) 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<String> userDns = (NamingEnumeration<String>) attributeUsers.getAll();
-                                        while (userDns.hasMoreElements()) {
-                                            final String userDn = userDns.next();
+                                        final NamingEnumeration<String> userValues = (NamingEnumeration<String>) 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;

http://git-wip-us.apache.org/repos/asf/nifi/blob/439e13a8/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java
----------------------------------------------------------------------
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 f2cc28e..0ea99ba 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<Group> 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<Group> 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<Group> 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<Group> 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<String>() {
-            @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

http://git-wip-us.apache.org/repos/asf/nifi/blob/439e13a8/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/resources/nifi-example.ldif
----------------------------------------------------------------------
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 2b5bdac..c91feac 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