NIFI-4059:

- Introducing the LdapUserGroupProvider.
- Updating documentation accordingly.
- Moving the IdentityMapping utilities so they were accessible.

Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com>

This closes #1923.
This commit is contained in:
Matt Gilman 2017-06-12 16:53:56 -04:00 committed by Pierre Villard
parent ad6af1d941
commit 6bc6f955c0
14 changed files with 1869 additions and 0 deletions

View File

@ -55,6 +55,10 @@
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId> <artifactId>bcpkix-jdk15on</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -395,6 +395,39 @@ The default UserGroupProvider is the FileUserGroupProvider, however, you can dev
* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically be used to load the users and groups into the Users File. * Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically be used to load the users and groups into the Users File.
* Initial User Identity - The identity of a users and systems to seed the Users File. The name of each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3" * Initial User Identity - The identity of a users and systems to seed the Users File. The name of each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
Another option for the UserGroupProvider is the LdapUserGroupProvider. By default, this option is commented out but can be configured in lieu of the FileUserGroupProvider. This will sync users and groups from a directory server and will present them in NiFi UI in read only form. The LdapUserGroupProvider has the following properties:
* Authentication Strategy - How the connection to the LDAP server is authenticated. Possible values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS
* Manager DN - The DN of the manager that is used to bind to the LDAP server to search for users.
* Manager Password - The password of the manager that is used to bind to the LDAP server to search for users.
* TLS - Keystore - Path to the Keystore that is used when connecting to LDAP using LDAPS or START_TLS.
* TLS - Keystore Password - Password for the Keystore that is used when connecting to LDAP using LDAPS or START_TLS.
* TLS - Keystore Type - Type of the Keystore that is used when connecting to LDAP using LDAPS or START_TLS (i.e. JKS or PKCS12).
* TLS - Truststore - Path to the Truststore that is used when connecting to LDAP using LDAPS or START_TLS.
* TLS - Truststore Password - Password for the Truststore that is used when connecting to LDAP using LDAPS or START_TLS.
* TLS - Truststore Type - Type of the Truststore that is used when connecting to LDAP using LDAPS or START_TLS (i.e. JKS or PKCS12).
* TLS - Client Auth - Client authentication policy when connecting to LDAP using LDAPS or START_TLS. Possible values are REQUIRED, WANT, NONE.
* TLS - Protocol - Protocol to use when connecting to LDAP using LDAPS or START_TLS. (i.e. TLS, TLSv1.1, TLSv1.2, etc).
* TLS - Shutdown Gracefully - Specifies whether the TLS should be shut down gracefully before the target context is closed. Defaults to false.
* Referral Strategy - Strategy for handling referrals. Possible values are FOLLOW, IGNORE, THROW.
* Connect Timeout - Duration of connect timeout. (i.e. 10 secs).
* Read Timeout - Duration of read timeout. (i.e. 10 secs).
* Url - Space-separated list of URLs of the LDAP servers (i.e. ldap://<hostname>:<port>).
* Page Size - Sets the page size when retrieving users and groups. If not specified, no paging is performed.
* Sync Interval - Duration of time between syncing users and groups. (i.e. 30 mins).
* User Search Base - Base DN for searching for users (i.e. ou=users,o=nifi). Required to search users.
* User Object Class - Object class for identifying users (i.e. person). Required if searching users.
* 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.
* 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.
The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties: The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties:
* User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies. * User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies.
@ -488,6 +521,71 @@ 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. 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:
----
<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=users,o=nifi</property>
<property name="User Object Class">person</property>
<property name="User Search Scope">ONE_LEVEL</property>
<property name="User Search Filter"></property>
<property name="User Identity Attribute">cn</property>
<property name="User Group Name Attribute"></property>
<property name="Group Search Base">ou=groups,o=nifi</property>
<property name="Group Object Class">groupOfNames</property>
<property name="Group Search Scope">ONE_LEVEL</property>
<property name="Group Search Filter"></property>
<property name="Group Name Attribute">cn</property>
<property name="Group Member Attribute">member</property>
</userGroupProvider>
<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>
----
The 'Initial Admin Identity' value would have loaded from the cn from John Smith's entry based on the 'User Identity Attribute' value.
[[legacy-authorized-users]] [[legacy-authorized-users]]
Legacy Authorized Users (NiFi Instance Upgrade) Legacy Authorized Users (NiFi Instance Upgrade)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -100,6 +100,10 @@
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId> <artifactId>nifi-utils</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId> <artifactId>nifi-properties</artifactId>

View File

@ -52,6 +52,106 @@
<property name="Initial User Identity 1"></property> <property name="Initial User Identity 1"></property>
</userGroupProvider> </userGroupProvider>
<!--
The LdapUserGroupProvider will retrieve users and groups from an LDAP server. The users and groups
are not configurable.
'Authentication Strategy' - How the connection to the LDAP server is authenticated. Possible
values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS.
'Manager DN' - The DN of the manager that is used to bind to the LDAP server to search for users.
'Manager Password' - The password of the manager that is used to bind to the LDAP server to
search for users.
'TLS - Keystore' - Path to the Keystore that is used when connecting to LDAP using LDAPS or START_TLS.
'TLS - Keystore Password' - Password for the Keystore that is used when connecting to LDAP
using LDAPS or START_TLS.
'TLS - Keystore Type' - Type of the Keystore that is used when connecting to LDAP using
LDAPS or START_TLS (i.e. JKS or PKCS12).
'TLS - Truststore' - Path to the Truststore that is used when connecting to LDAP using LDAPS or START_TLS.
'TLS - Truststore Password' - Password for the Truststore that is used when connecting to
LDAP using LDAPS or START_TLS.
'TLS - Truststore Type' - Type of the Truststore that is used when connecting to LDAP using
LDAPS or START_TLS (i.e. JKS or PKCS12).
'TLS - Client Auth' - Client authentication policy when connecting to LDAP using LDAPS or START_TLS.
Possible values are REQUIRED, WANT, NONE.
'TLS - Protocol' - Protocol to use when connecting to LDAP using LDAPS or START_TLS. (i.e. TLS,
TLSv1.1, TLSv1.2, etc).
'TLS - Shutdown Gracefully' - Specifies whether the TLS should be shut down gracefully
before the target context is closed. Defaults to false.
'Referral Strategy' - Strategy for handling referrals. Possible values are FOLLOW, IGNORE, THROW.
'Connect Timeout' - Duration of connect timeout. (i.e. 10 secs).
'Read Timeout' - Duration of read timeout. (i.e. 10 secs).
'Url' - Space-separated list of URLs of the LDAP servers (i.e. ldap://<hostname>:<port>).
'Page Size' - Sets the page size when retrieving users and groups. If not specified, no paging is performed.
'Sync Interval' - Duration of time between syncing users and groups. (i.e. 30 mins).
'User Search Base' - Base DN for searching for users (i.e. ou=users,o=nifi). Required to search users.
'User Object Class' - Object class for identifying users (i.e. person). Required if searching users.
'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.
'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.
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities.
Group names are not mapped.
-->
<!-- To enable the ldap-user-group-provider remove 2 lines. This is 1 of 2.
<userGroupProvider>
<identifier>ldap-user-group-provider</identifier>
<class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
<property name="Authentication Strategy">START_TLS</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"></property>
<property name="Page Size"></property>
<property name="Sync Interval">30 mins</property>
<property name="User Search Base"></property>
<property name="User Object Class">person</property>
<property name="User Search Scope">ONE_LEVEL</property>
<property name="User Search Filter"></property>
<property name="User Identity Attribute"></property>
<property name="User Group Name Attribute"></property>
<property name="Group Search Base"></property>
<property name="Group Object Class">group</property>
<property name="Group Search Scope">ONE_LEVEL</property>
<property name="Group Search Filter"></property>
<property name="Group Name Attribute"></property>
<property name="Group Member Attribute"></property>
</userGroupProvider>
To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. -->
<!-- <!--
The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file
on the local file system. on the local file system.

View File

@ -26,6 +26,10 @@
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-security</artifactId> <artifactId>nifi-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-administration</artifactId> <artifactId>nifi-administration</artifactId>

View File

@ -39,6 +39,10 @@
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-security-utils</artifactId> <artifactId>nifi-security-utils</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-ldap</artifactId> <artifactId>spring-security-ldap</artifactId>
@ -59,6 +63,17 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-all</artifactId>
<version>2.0.0-M20</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-expression-language</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<name>nifi-ldap-iaa-providers</name> <name>nifi-ldap-iaa-providers</name>
</project> </project>

View File

@ -0,0 +1,748 @@
/*
* 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.ldap.tenants;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authentication.exception.ProviderDestructionException;
import org.apache.nifi.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.User;
import org.apache.nifi.authorization.UserAndGroups;
import org.apache.nifi.authorization.UserGroupProvider;
import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.authorization.annotation.AuthorizerContext;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.util.IdentityMapping;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.ldap.LdapAuthenticationStrategy;
import org.apache.nifi.ldap.LdapsSocketFactory;
import org.apache.nifi.ldap.ReferralStrategy;
import org.apache.nifi.security.util.SslContextFactory;
import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
import org.apache.nifi.util.FormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.control.PagedResultsDirContextProcessor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextProcessor;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.LdapTemplate.NullDirContextProcessor;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.core.support.AbstractTlsDirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.DefaultTlsDirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.ldap.core.support.SimpleDirContextAuthenticationStrategy;
import org.springframework.ldap.core.support.SingleContextSource;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.HardcodedFilter;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.SearchControls;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* Abstract LDAP based implementation of a login identity provider.
*/
public class LdapUserGroupProvider implements UserGroupProvider {
private static final Logger logger = LoggerFactory.getLogger(LdapUserGroupProvider.class);
public static final String PROP_CONNECT_TIMEOUT = "Connect Timeout";
public static final String PROP_READ_TIMEOUT = "Read Timeout";
public static final String PROP_AUTHENTICATION_STRATEGY = "Authentication Strategy";
public static final String PROP_MANAGER_DN = "Manager DN";
public static final String PROP_MANAGER_PASSWORD = "Manager Password";
public static final String PROP_REFERRAL_STRATEGY = "Referral Strategy";
public static final String PROP_URL = "Url";
public static final String PROP_PAGE_SIZE = "Page Size";
public static final String PROP_USER_SEARCH_BASE = "User Search Base";
public static final String PROP_USER_OBJECT_CLASS = "User Object Class";
public static final String PROP_USER_SEARCH_SCOPE = "User Search Scope";
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_GROUP_SEARCH_BASE = "Group Search Base";
public static final String PROP_GROUP_OBJECT_CLASS = "Group Object Class";
public static final String PROP_GROUP_SEARCH_SCOPE = "Group Search Scope";
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_SYNC_INTERVAL = "Sync Interval";
private List<IdentityMapping> identityMappings;
private NiFiProperties properties;
private ScheduledExecutorService ldapSync;
private AtomicReference<TenantHolder> tenants = new AtomicReference<>(null);
private String userSearchBase;
private SearchScope userSearchScope;
private String userSearchFilter;
private String userIdentityAttribute;
private String userObjectClass;
private String userGroupNameAttribute;
private boolean useDnForUserIdentity;
private boolean performUserSearch;
private String groupSearchBase;
private SearchScope groupSearchScope;
private String groupSearchFilter;
private String groupMemberAttribute;
private String groupNameAttribute;
private String groupObjectClass;
private boolean useDnForGroupName;
private boolean performGroupSearch;
private Integer pageSize;
@Override
public void initialize(final UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
ldapSync = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
final ThreadFactory factory = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
final Thread thread = factory.newThread(r);
thread.setName(String.format("%s (%s) - background sync thread", getClass().getSimpleName(), initializationContext.getIdentifier()));
return thread;
}
});
}
@Override
public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final LdapContextSource context = new LdapContextSource();
final Map<String, Object> baseEnvironment = new HashMap<>();
// connect/read time out
setTimeout(configurationContext, baseEnvironment, PROP_CONNECT_TIMEOUT, "com.sun.jndi.ldap.connect.timeout");
setTimeout(configurationContext, baseEnvironment, PROP_READ_TIMEOUT, "com.sun.jndi.ldap.read.timeout");
// authentication strategy
final PropertyValue rawAuthenticationStrategy = configurationContext.getProperty(PROP_AUTHENTICATION_STRATEGY);
final LdapAuthenticationStrategy authenticationStrategy;
try {
authenticationStrategy = LdapAuthenticationStrategy.valueOf(rawAuthenticationStrategy.getValue());
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("Unrecognized authentication strategy '%s'. Possible values are [%s]",
rawAuthenticationStrategy.getValue(), StringUtils.join(LdapAuthenticationStrategy.values(), ", ")));
}
switch (authenticationStrategy) {
case ANONYMOUS:
context.setAnonymousReadOnly(true);
break;
default:
final String userDn = configurationContext.getProperty(PROP_MANAGER_DN).getValue();
final String password = configurationContext.getProperty(PROP_MANAGER_PASSWORD).getValue();
context.setUserDn(userDn);
context.setPassword(password);
switch (authenticationStrategy) {
case SIMPLE:
context.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy());
break;
case LDAPS:
context.setAuthenticationStrategy(new SimpleDirContextAuthenticationStrategy());
// indicate a secure connection
baseEnvironment.put(Context.SECURITY_PROTOCOL, "ssl");
// get the configured ssl context
final SSLContext ldapsSslContext = getConfiguredSslContext(configurationContext);
if (ldapsSslContext != null) {
// initialize the ldaps socket factory prior to use
LdapsSocketFactory.initialize(ldapsSslContext.getSocketFactory());
baseEnvironment.put("java.naming.ldap.factory.socket", LdapsSocketFactory.class.getName());
}
break;
case START_TLS:
final AbstractTlsDirContextAuthenticationStrategy tlsAuthenticationStrategy = new DefaultTlsDirContextAuthenticationStrategy();
// shutdown gracefully
final String rawShutdownGracefully = configurationContext.getProperty("TLS - Shutdown Gracefully").getValue();
if (StringUtils.isNotBlank(rawShutdownGracefully)) {
final boolean shutdownGracefully = Boolean.TRUE.toString().equalsIgnoreCase(rawShutdownGracefully);
tlsAuthenticationStrategy.setShutdownTlsGracefully(shutdownGracefully);
}
// get the configured ssl context
final SSLContext startTlsSslContext = getConfiguredSslContext(configurationContext);
if (startTlsSslContext != null) {
tlsAuthenticationStrategy.setSslSocketFactory(startTlsSslContext.getSocketFactory());
}
// set the authentication strategy
context.setAuthenticationStrategy(tlsAuthenticationStrategy);
break;
}
break;
}
// referrals
final String rawReferralStrategy = configurationContext.getProperty(PROP_REFERRAL_STRATEGY).getValue();
final ReferralStrategy referralStrategy;
try {
referralStrategy = ReferralStrategy.valueOf(rawReferralStrategy);
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("Unrecognized referral strategy '%s'. Possible values are [%s]",
rawReferralStrategy, StringUtils.join(ReferralStrategy.values(), ", ")));
}
// using the value as this needs to be the lowercase version while the value is configured with the enum constant
context.setReferral(referralStrategy.getValue());
// url
final String urls = configurationContext.getProperty(PROP_URL).getValue();
if (StringUtils.isBlank(urls)) {
throw new AuthorizerCreationException("LDAP identity provider 'Url' must be specified.");
}
// connection
context.setUrls(StringUtils.split(urls));
// raw user search base
final PropertyValue rawUserSearchBase = configurationContext.getProperty(PROP_USER_SEARCH_BASE);
final PropertyValue rawUserObjectClass = configurationContext.getProperty(PROP_USER_OBJECT_CLASS);
final PropertyValue rawUserSearchScope = configurationContext.getProperty(PROP_USER_SEARCH_SCOPE);
// if loading the users, ensure the object class set
if (rawUserSearchBase.isSet() && !rawUserObjectClass.isSet()) {
throw new AuthorizerCreationException("LDAP user group provider 'User Object Class' must be specified when 'User Search Base' is set.");
}
// if loading the users, ensure the search scope is set
if (rawUserSearchBase.isSet() && !rawUserSearchScope.isSet()) {
throw new AuthorizerCreationException("LDAP user group provider 'User Search Scope' must be specified when 'User Search Base' is set.");
}
// user search criteria
userSearchBase = rawUserSearchBase.getValue();
userObjectClass = rawUserObjectClass.getValue();
userSearchFilter = configurationContext.getProperty(PROP_USER_SEARCH_FILTER).getValue();
userIdentityAttribute = configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE).getValue();
userGroupNameAttribute = configurationContext.getProperty(PROP_USER_GROUP_ATTRIBUTE).getValue();
try {
userSearchScope = SearchScope.valueOf(rawUserSearchScope.getValue());
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("Unrecognized user search scope '%s'. Possible values are [%s]",
rawUserSearchScope.getValue(), StringUtils.join(SearchScope.values(), ", ")));
}
// determine user behavior
useDnForUserIdentity = StringUtils.isBlank(userIdentityAttribute);
performUserSearch = StringUtils.isNotBlank(userSearchBase);
// raw group search criteria
final PropertyValue rawGroupSearchBase = configurationContext.getProperty(PROP_GROUP_SEARCH_BASE);
final PropertyValue rawGroupObjectClass = configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS);
final PropertyValue rawGroupSearchScope = configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE);
// if loading the groups, ensure the object class is set
if (rawGroupSearchBase.isSet() && !rawGroupObjectClass.isSet()) {
throw new AuthorizerCreationException("LDAP user group provider 'Group Object Class' must be specified when 'Group Search Base' is set.");
}
// if loading the groups, ensure the search scope is set
if (rawGroupSearchBase.isSet() && !rawGroupSearchScope.isSet()) {
throw new AuthorizerCreationException("LDAP user group provider 'Group Search Scope' must be specified when 'Group Search Base' is set.");
}
// group search criteria
groupSearchBase = rawGroupSearchBase.getValue();
groupObjectClass = rawGroupObjectClass.getValue();
groupSearchFilter = configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER).getValue();
groupNameAttribute = configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE).getValue();
groupMemberAttribute = configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE).getValue();
try {
groupSearchScope = SearchScope.valueOf(rawGroupSearchScope.getValue());
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("Unrecognized group search scope '%s'. Possible values are [%s]",
rawGroupSearchScope.getValue(), StringUtils.join(SearchScope.values(), ", ")));
}
// determine group behavior
useDnForGroupName = StringUtils.isBlank(groupNameAttribute);
performGroupSearch = StringUtils.isNotBlank(groupSearchBase);
// ensure we are either searching users or groups (at least one must be specified)
if (!performUserSearch && !performGroupSearch) {
throw new AuthorizerCreationException("LDAP user group provider 'User Search Base' or 'Group Search Base' must be specified.");
}
// ensure group member attribute is set if searching groups but not users
if (performGroupSearch && !performUserSearch && StringUtils.isBlank(groupMemberAttribute)) {
throw new AuthorizerCreationException("'Group Member Attribute' is required when searching groups but not users.");
}
// get the page size if configured
final PropertyValue rawPageSize = configurationContext.getProperty(PROP_PAGE_SIZE);
if (rawPageSize.isSet() && StringUtils.isNotBlank(rawPageSize.getValue())) {
pageSize = rawPageSize.asInteger();
}
// extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
// set the base environment is necessary
if (!baseEnvironment.isEmpty()) {
context.setBaseEnvironmentProperties(baseEnvironment);
}
try {
// handling initializing beans
context.afterPropertiesSet();
} catch (final Exception e) {
throw new AuthorizerCreationException(e.getMessage(), e);
}
final PropertyValue rawSyncInterval = configurationContext.getProperty(PROP_SYNC_INTERVAL);
final long syncInterval;
if (rawSyncInterval.isSet()) {
try {
syncInterval = FormatUtils.getTimeDuration(rawSyncInterval.getValue(), TimeUnit.MILLISECONDS);
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("The %s '%s' is not a valid time duration", PROP_SYNC_INTERVAL, rawSyncInterval.getValue()));
}
} else {
throw new AuthorizerCreationException("The 'Sync Interval' must be specified.");
}
try {
// perform the initial load, tenants must be loaded as the configured UserGroupProvider is supplied
// to the AccessPolicyProvider for granting initial permissions
load(context);
// ensure the tenants were successfully synced
if (tenants.get() == null) {
throw new AuthorizerCreationException("Unable to sync users and groups.");
}
// schedule the background thread to load the users/groups
ldapSync.scheduleWithFixedDelay(() -> load(context), syncInterval, syncInterval, TimeUnit.SECONDS);
} catch (final AuthorizationAccessException e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return tenants.get().getAllUsers();
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return tenants.get().getUsersById().get(identifier);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return tenants.get().getUser(identity);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return tenants.get().getAllGroups();
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return tenants.get().getGroupsById().get(identifier);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
final TenantHolder holder = tenants.get();
return new UserAndGroups() {
@Override
public User getUser() {
return holder.getUser(identity);
}
@Override
public Set<Group> getGroups() {
return holder.getGroups(identity);
}
};
}
/**
* Reloads the tenants.
*/
private void load(final ContextSource contextSource) {
// create the ldapTemplate based on the context source. use a single source context to use the same connection
// to support paging when configured
final SingleContextSource singleContextSource = new SingleContextSource(contextSource.getReadOnlyContext());
final LdapTemplate ldapTemplate = new LdapTemplate(singleContextSource);
try {
final List<User> userList = new ArrayList<>();
final List<Group> groupList = new ArrayList<>();
// group dn -> user identifiers lookup
final Map<String, Set<String>> groupDnToUserIdentifierMappings = new HashMap<>();
// user dn -> user lookup
final Map<String, User> userDnLookup = new HashMap<>();
if (performUserSearch) {
// search controls
final SearchControls userControls = new SearchControls();
userControls.setSearchScope(userSearchScope.ordinal());
// consider paging support for users
final DirContextProcessor userProcessor;
if (pageSize == null) {
userProcessor = new NullDirContextProcessor();
} else {
userProcessor = new PagedResultsDirContextProcessor(pageSize);
}
// looking for objects matching the user object class
final AndFilter userFilter = new AndFilter();
userFilter.and(new EqualsFilter("objectClass", userObjectClass));
// if a filter has been provided by the user, we add it to the filter
if (StringUtils.isNotBlank(userSearchFilter)) {
userFilter.and(new HardcodedFilter(userSearchFilter));
}
do {
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);
if (StringUtils.isNotBlank(userGroupNameAttribute)) {
final Attribute attributeGroups = ctx.getAttributes().get(userGroupNameAttribute);
if (attributeGroups == null) {
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());
}
} catch (NamingException e) {
throw new AuthorizationAccessException("Error while retrieving user group name attribute [" + userIdentityAttribute + "].");
}
}
}
return user;
}
}, userProcessor));
} while (hasMorePages(userProcessor));
}
if (performGroupSearch) {
final SearchControls groupControls = new SearchControls();
groupControls.setSearchScope(groupSearchScope.ordinal());
// consider paging support for groups
final DirContextProcessor groupProcessor;
if (pageSize == null) {
groupProcessor = new NullDirContextProcessor();
} else {
groupProcessor = new PagedResultsDirContextProcessor(pageSize);
}
// looking for objects matching the group object class
AndFilter groupFilter = new AndFilter();
groupFilter.and(new EqualsFilter("objectClass", groupObjectClass));
// if a filter has been provided by the user, we add it to the filter
if(StringUtils.isNotBlank(groupSearchFilter)) {
groupFilter.and(new HardcodedFilter(groupSearchFilter));
}
do {
groupList.addAll(ldapTemplate.search(groupSearchBase, groupFilter.encode(), groupControls, new AbstractContextMapper<Group>() {
@Override
protected Group doMapFromContext(DirContextOperations ctx) {
final String dn = ctx.getDn().toString();
// get the group identity
final String name = getGroupName(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();
if (performUserSearch) {
// find the user by dn add the identifier to this group
final User user = userDnLookup.get(userDn);
// ensure the user is known
if (user != null) {
groupDnToUserIdentifierMappings.computeIfAbsent(dn, 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));
}
} else {
final String userIdentity;
if (useDnForUserIdentity) {
// use the dn to avoid the unnecessary look up
userIdentity = userDn;
} else {
// lookup the user to extract the user identity
userIdentity = getUserIdentity((DirContextAdapter) ldapTemplate.lookup(userDn));
}
// build the user
final User user = new User.Builder().identifierGenerateFromSeed(userIdentity).identity(userIdentity).build();
// add this user
userList.add(user);
groupDnToUserIdentifierMappings.computeIfAbsent(dn, g -> new HashSet<>()).add(user.getIdentifier());
}
}
} catch (NamingException e) {
throw new AuthorizationAccessException("Error while retrieving group name attribute [" + groupNameAttribute + "].");
}
}
}
// 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));
}
return groupBuilder.build();
}
}, groupProcessor));
} while (hasMorePages(groupProcessor));
// any remaining groupDn's were referenced by a user but not found while searching groups
groupDnToUserIdentifierMappings.forEach((groupDn, 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));
});
} else {
// groups are not being searched so lookup any groups identified while searching users
groupDnToUserIdentifierMappings.forEach((groupDn, userIdentifiers) -> {
final String groupName;
if (useDnForGroupName) {
// use the dn to avoid the unnecessary look up
groupName = groupDn;
} else {
groupName = getGroupName((DirContextAdapter) ldapTemplate.lookup(groupDn));
}
// define the group
final Group.Builder groupBuilder = new Group.Builder().identifierGenerateFromSeed(groupName).name(groupName);
// add each user
userIdentifiers.forEach(userIdentifier -> groupBuilder.addUser(userIdentifier));
// build the group
groupList.add(groupBuilder.build());
});
}
// record the updated tenants
tenants.set(new TenantHolder(new HashSet<>(userList), new HashSet<>(groupList)));
} finally {
singleContextSource.destroy();
}
}
private boolean hasMorePages(final DirContextProcessor processor ) {
return processor instanceof PagedResultsDirContextProcessor && ((PagedResultsDirContextProcessor) processor).hasMore();
}
private String getUserIdentity(final DirContextOperations ctx) {
final String identity;
if (useDnForUserIdentity) {
identity = ctx.getDn().toString();
} else {
final Attribute attributeName = ctx.getAttributes().get(userIdentityAttribute);
if (attributeName == null) {
throw new AuthorizationAccessException("User identity attribute [" + userIdentityAttribute + "] does not exist.");
}
try {
identity = (String) attributeName.get();
} catch (NamingException e) {
throw new AuthorizationAccessException("Error while retrieving user name attribute [" + userIdentityAttribute + "].");
}
}
return IdentityMappingUtil.mapIdentity(identity, identityMappings);
}
private String getGroupName(final DirContextOperations ctx) {
final String name;
if (useDnForGroupName) {
name = ctx.getDn().toString();
} else {
final Attribute attributeName = ctx.getAttributes().get(groupNameAttribute);
if (attributeName == null) {
throw new AuthorizationAccessException("Group identity attribute [" + groupNameAttribute + "] does not exist.");
}
try {
name = (String) attributeName.get();
} catch (NamingException e) {
throw new AuthorizationAccessException("Error while retrieving group name attribute [" + groupNameAttribute + "].");
}
}
return name;
}
@AuthorizerContext
public void setNiFiProperties(NiFiProperties properties) {
this.properties = properties;
}
@Override
public final void preDestruction() throws ProviderDestructionException {
ldapSync.shutdown();
try {
if (!ldapSync.awaitTermination(10000, TimeUnit.MILLISECONDS)) {
logger.info("Failed to stop ldap sync thread in 10 sec. Terminating");
ldapSync.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private void setTimeout(final AuthorizerConfigurationContext configurationContext,
final Map<String, Object> baseEnvironment,
final String configurationProperty,
final String environmentKey) {
final PropertyValue rawTimeout = configurationContext.getProperty(configurationProperty);
if (rawTimeout.isSet()) {
try {
final Long timeout = FormatUtils.getTimeDuration(rawTimeout.getValue(), TimeUnit.MILLISECONDS);
baseEnvironment.put(environmentKey, timeout.toString());
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("The %s '%s' is not a valid time duration", configurationProperty, rawTimeout));
}
}
}
private SSLContext getConfiguredSslContext(final AuthorizerConfigurationContext configurationContext) {
final String rawKeystore = configurationContext.getProperty("TLS - Keystore").getValue();
final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password").getValue();
final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type").getValue();
final String rawTruststore = configurationContext.getProperty("TLS - Truststore").getValue();
final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password").getValue();
final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type").getValue();
final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth").getValue();
final String rawProtocol = configurationContext.getProperty("TLS - Protocol").getValue();
// create the ssl context
final SSLContext sslContext;
try {
if (StringUtils.isBlank(rawKeystore) && StringUtils.isBlank(rawTruststore)) {
sslContext = null;
} else {
// ensure the protocol is specified
if (StringUtils.isBlank(rawProtocol)) {
throw new AuthorizerCreationException("TLS - Protocol must be specified.");
}
if (StringUtils.isBlank(rawKeystore)) {
sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol);
} else if (StringUtils.isBlank(rawTruststore)) {
sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol);
} else {
// determine the client auth if specified
final ClientAuth clientAuth;
if (StringUtils.isBlank(rawClientAuth)) {
clientAuth = ClientAuth.NONE;
} else {
try {
clientAuth = ClientAuth.valueOf(rawClientAuth);
} catch (final IllegalArgumentException iae) {
throw new AuthorizerCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
rawClientAuth, StringUtils.join(ClientAuth.values(), ", ")));
}
}
sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType,
rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol);
}
}
} catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) {
throw new AuthorizerCreationException(e.getMessage(), e);
}
return sslContext;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.ldap.tenants;
/**
* Scope for searching a directory server.
*/
public enum SearchScope {
OBJECT,
ONE_LEVEL,
SUBTREE;
}

View File

@ -0,0 +1,165 @@
/*
* 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.ldap.tenants;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.User;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A holder to provide atomic access to user group data structures.
*/
public class TenantHolder {
private final Set<User> allUsers;
private final Map<String,User> usersById;
private final Map<String,User> usersByIdentity;
private final Set<Group> allGroups;
private final Map<String,Group> groupsById;
private final Map<String, Set<Group>> groupsByUserIdentity;
/**
* Creates a new holder and populates all convenience data structures.
*/
public TenantHolder(final Set<User> allUsers, final Set<Group> allGroups) {
// create a convenience map to retrieve a user by id
final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
// create a convenience map to retrieve a user by identity
final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
// create a convenience map to retrieve a group by id
final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
// create a convenience map to retrieve the groups for a user identity
final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
// set all the holders
this.allUsers = allUsers;
this.allGroups = allGroups;
this.usersById = userByIdMap;
this.usersByIdentity = userByIdentityMap;
this.groupsById = groupByIdMap;
this.groupsByUserIdentity = groupsByUserIdentityMap;
}
/**
* Creates a Map from user identifier to User.
*
* @param users the set of all users
* @return the Map from user identifier to User
*/
private Map<String,User> createUserByIdMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentifier(), user);
}
return usersMap;
}
/**
* Creates a Map from user identity to User.
*
* @param users the set of all users
* @return the Map from user identity to User
*/
private Map<String,User> createUserByIdentityMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentity(), user);
}
return usersMap;
}
/**
* Creates a Map from group identifier to Group.
*
* @param groups the set of all groups
* @return the Map from group identifier to Group
*/
private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
Map<String,Group> groupsMap = new HashMap<>();
for (Group group : groups) {
groupsMap.put(group.getIdentifier(), group);
}
return groupsMap;
}
/**
* Creates a Map from user identity to the set of Groups for that identity.
*
* @param groups all groups
* @param users all users
* @return a Map from User identity to the set of Groups for that identity
*/
private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
for (User user : users) {
Set<Group> userGroups = new HashSet<>();
for (Group group : groups) {
for (String groupUser : group.getUsers()) {
if (groupUser.equals(user.getIdentifier())) {
userGroups.add(group);
}
}
}
groupsByUserIdentity.put(user.getIdentity(), userGroups);
}
return groupsByUserIdentity;
}
public Set<User> getAllUsers() {
return allUsers;
}
public Map<String, User> getUsersById() {
return usersById;
}
public Set<Group> getAllGroups() {
return allGroups;
}
public Map<String, Group> getGroupsById() {
return groupsById;
}
public User getUser(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Identity cannot be null");
}
return usersByIdentity.get(identity);
}
public Set<Group> getGroups(String userIdentity) {
if (userIdentity == null) {
throw new IllegalArgumentException("User Identity cannot be null");
}
return groupsByUserIdentity.get(userIdentity);
}
}

View File

@ -0,0 +1,15 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
org.apache.nifi.ldap.tenants.LdapUserGroupProvider

View File

@ -0,0 +1,552 @@
/*
* 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.ldap.tenants;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.annotations.ApplyLdifFiles;
import org.apache.directory.server.core.annotations.CreateDS;
import org.apache.directory.server.core.annotations.CreatePartition;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.UserAndGroups;
import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.ldap.LdapAuthenticationStrategy;
import org.apache.nifi.ldap.ReferralStrategy;
import org.apache.nifi.util.NiFiProperties;
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_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;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_SEARCH_BASE;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_SEARCH_FILTER;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_GROUP_SEARCH_SCOPE;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_MANAGER_DN;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_MANAGER_PASSWORD;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_PAGE_SIZE;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_READ_TIMEOUT;
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_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;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_SEARCH_BASE;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_SEARCH_FILTER;
import static org.apache.nifi.ldap.tenants.LdapUserGroupProvider.PROP_USER_SEARCH_SCOPE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = {@CreateTransport(protocol = "LDAP")})
@CreateDS(name = "nifi-example", partitions = {@CreatePartition(name = "example", suffix = "o=nifi")})
@ApplyLdifFiles("nifi-example.ldif")
public class LdapUserGroupProviderTest extends AbstractLdapTestUnit {
private static final String USER_SEARCH_BASE = "ou=users,o=nifi";
private static final String GROUP_SEARCH_BASE = "ou=groups,o=nifi";
private LdapUserGroupProvider ldapUserGroupProvider;
@Before
public void setup() {
final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
when(initializationContext.getIdentifier()).thenReturn("identifier");
ldapUserGroupProvider = new LdapUserGroupProvider();
ldapUserGroupProvider.setNiFiProperties(getNiFiProperties(new Properties()));
ldapUserGroupProvider.initialize(initializationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testNoSearchBasesSpecified() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, null);
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testUserSearchBaseSpecifiedButNoUserObjectClass() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_OBJECT_CLASS)).thenReturn(new StandardPropertyValue(null, null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testUserSearchBaseSpecifiedButNoUserSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(null, null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testInvalidUserSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue("not-valid", null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test
public void testSearchUsersWithNoIdentityAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("cn=User 1,ou=users,o=nifi"));
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersWithUidIdentityAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("user1"));
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersWithCnIdentityAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1"));
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersObjectSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.OBJECT.name(), null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertTrue(ldapUserGroupProvider.getUsers().isEmpty());
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersSubtreeSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("o=nifi", null);
when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.SUBTREE.name(), null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersWithFilter() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null));
when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(1, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("user1"));
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersWithPaging() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_PAGE_SIZE)).thenReturn(new StandardPropertyValue("1", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchUsersWithGroupingNoGroupName() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
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 memberof
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertEquals(2, ldapUserGroupProvider.getGroups().size());
final UserAndGroups userAndGroups = ldapUserGroupProvider.getUserAndGroups("user4");
assertNotNull(userAndGroups.getUser());
assertEquals(1, userAndGroups.getGroups().size());
assertEquals("cn=team1,ou=groups,o=nifi", userAndGroups.getGroups().iterator().next().getName());
}
@Test
public void testSearchUsersWithGroupingAndGroupName() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
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 memberof
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
assertEquals(2, ldapUserGroupProvider.getGroups().size());
final UserAndGroups userAndGroups = ldapUserGroupProvider.getUserAndGroups("user4");
assertNotNull(userAndGroups.getUser());
assertEquals(1, userAndGroups.getGroups().size());
assertEquals("team1", userAndGroups.getGroups().iterator().next().getName());
}
@Test(expected = AuthorizerCreationException.class)
public void testSearchGroupsWithoutMemberAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testGroupSearchBaseSpecifiedButNoGroupObjectClass() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue(null, null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testUserSearchBaseSpecifiedButNoGroupSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(null, null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testInvalidGroupSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue("not-valid", null));
ldapUserGroupProvider.onConfigured(configurationContext);
}
@Test
public void testSearchGroupsWithNoNameAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
ldapUserGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
assertEquals(1, groups.stream().filter(group -> "cn=admins,ou=groups,o=nifi".equals(group.getName())).count());
}
@Test
public void testSearchGroupsWithPaging() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_PAGE_SIZE)).thenReturn(new StandardPropertyValue("1", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(4, ldapUserGroupProvider.getGroups().size());
}
@Test
public void testSearchGroupsObjectSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.OBJECT.name(), null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertTrue(ldapUserGroupProvider.getUsers().isEmpty());
assertTrue(ldapUserGroupProvider.getGroups().isEmpty());
}
@Test
public void testSearchGroupsSubtreeSearchScope() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, "o=nifi");
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.SUBTREE.name(), null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(4, ldapUserGroupProvider.getGroups().size());
}
@Test
public void testSearchGroupsWithNameAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group admins = groups.stream().filter(group -> "admins".equals(group.getName())).findFirst().orElse(null);
assertNotNull(admins);
assertFalse(admins.getUsers().isEmpty());
assertEquals(1, admins.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "cn=User 1,ou=users,o=nifi".equals(user.getIdentity())).count());
}
@Test
public void testSearchGroupsWithNoNameAndUserIdentityUidAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null));
ldapUserGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group admins = groups.stream().filter(group -> "cn=admins,ou=groups,o=nifi".equals(group.getName())).findFirst().orElse(null);
assertNotNull(admins);
assertFalse(admins.getUsers().isEmpty());
assertEquals(1, admins.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity())).count());
}
@Test
public void testSearchGroupsWithNameAndUserIdentityCnAttribute() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group admins = groups.stream().filter(group -> "admins".equals(group.getName())).findFirst().orElse(null);
assertNotNull(admins);
assertFalse(admins.getUsers().isEmpty());
assertEquals(1, admins.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "User 1".equals(user.getIdentity())).count());
}
@Test
public void testSearchGroupsWithFilter() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(null, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(cn=admins)", null));
ldapUserGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(1, groups.size());
assertEquals(1, groups.stream().filter(group -> "cn=admins,ou=groups,o=nifi".equals(group.getName())).count());
}
@Test
public void testSearchUsersAndGroupsNoMembership() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
groups.forEach(group -> assertTrue(group.getUsers().isEmpty()));
}
@Test
public void testSearchUsersAndGroupsMembershipThroughUsers() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
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 memberof
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group team1 = groups.stream().filter(group -> "team1".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team1);
assertEquals(2, team1.getUsers().size());
assertEquals(2, team1.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user4".equals(user.getIdentity()) || "user5".equals(user.getIdentity())).count());
final Group team2 = groups.stream().filter(group -> "team2".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team2);
assertEquals(2, team2.getUsers().size());
assertEquals(2, team2.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user6".equals(user.getIdentity()) || "user7".equals(user.getIdentity())).count());
}
@Test
public void testSearchUsersAndGroupsMembershipThroughGroups() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
when(configurationContext.getProperty(PROP_USER_IDENTITY_ATTRIBUTE)).thenReturn(new StandardPropertyValue("uid", null));
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group admins = groups.stream().filter(group -> "admins".equals(group.getName())).findFirst().orElse(null);
assertNotNull(admins);
assertEquals(2, admins.getUsers().size());
assertEquals(2, admins.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity()) || "user3".equals(user.getIdentity())).count());
final Group readOnly = groups.stream().filter(group -> "read-only".equals(group.getName())).findFirst().orElse(null);
assertNotNull(readOnly);
assertEquals(1, readOnly.getUsers().size());
assertEquals(1, readOnly.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user2".equals(user.getIdentity())).count());
final Group team1 = groups.stream().filter(group -> "team1".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team1);
assertEquals(1, team1.getUsers().size());
assertEquals(1, team1.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity())).count());
final Group team2 = groups.stream().filter(group -> "team2".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team2);
assertEquals(1, team2.getUsers().size());
assertEquals(1, team2.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity())).count());
}
@Test
public void testSearchUsersAndGroupsMembershipThroughUsersAndGroups() throws Exception {
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE);
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 memberof
when(configurationContext.getProperty(PROP_GROUP_MEMBER_ATTRIBUTE)).thenReturn(new StandardPropertyValue("member", null));
when(configurationContext.getProperty(PROP_GROUP_NAME_ATTRIBUTE)).thenReturn(new StandardPropertyValue("cn", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(8, ldapUserGroupProvider.getUsers().size());
final Set<Group> groups = ldapUserGroupProvider.getGroups();
assertEquals(4, groups.size());
final Group admins = groups.stream().filter(group -> "admins".equals(group.getName())).findFirst().orElse(null);
assertNotNull(admins);
assertEquals(2, admins.getUsers().size());
assertEquals(2, admins.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity()) || "user3".equals(user.getIdentity())).count());
final Group readOnly = groups.stream().filter(group -> "read-only".equals(group.getName())).findFirst().orElse(null);
assertNotNull(readOnly);
assertEquals(1, readOnly.getUsers().size());
assertEquals(1, readOnly.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user2".equals(user.getIdentity())).count());
final Group team1 = groups.stream().filter(group -> "team1".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team1);
assertEquals(3, team1.getUsers().size());
assertEquals(3, team1.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity()) || "user4".equals(user.getIdentity()) || "user5".equals(user.getIdentity())).count());
final Group team2 = groups.stream().filter(group -> "team2".equals(group.getName())).findFirst().orElse(null);
assertNotNull(team2);
assertEquals(3, team2.getUsers().size());
assertEquals(3, team2.getUsers().stream().map(
userIdentifier -> ldapUserGroupProvider.getUser(userIdentifier)).filter(
user -> "user1".equals(user.getIdentity()) || "user6".equals(user.getIdentity()) || "user7".equals(user.getIdentity())).count());
}
@Test
public void testUserIdentityMapping() throws Exception {
final Properties props = new Properties();
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),o=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
final NiFiProperties properties = getNiFiProperties(props);
ldapUserGroupProvider.setNiFiProperties(properties);
final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null);
when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null));
ldapUserGroupProvider.onConfigured(configurationContext);
assertEquals(1, ldapUserGroupProvider.getUsers().size());
assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1,ou=users"));
}
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));
when(configurationContext.getProperty(PROP_CONNECT_TIMEOUT)).thenReturn(new StandardPropertyValue("30 secs", null));
when(configurationContext.getProperty(PROP_READ_TIMEOUT)).thenReturn(new StandardPropertyValue("30 secs", null));
when(configurationContext.getProperty(PROP_REFERRAL_STRATEGY)).thenReturn(new StandardPropertyValue(ReferralStrategy.FOLLOW.name(), null));
when(configurationContext.getProperty(PROP_PAGE_SIZE)).thenReturn(new StandardPropertyValue(null, null));
when(configurationContext.getProperty(PROP_SYNC_INTERVAL)).thenReturn(new StandardPropertyValue("30 mins", null));
when(configurationContext.getProperty(PROP_AUTHENTICATION_STRATEGY)).thenReturn(new StandardPropertyValue(LdapAuthenticationStrategy.SIMPLE.name(), null));
when(configurationContext.getProperty(PROP_MANAGER_DN)).thenReturn(new StandardPropertyValue("uid=admin,ou=system", null));
when(configurationContext.getProperty(PROP_MANAGER_PASSWORD)).thenReturn(new StandardPropertyValue("secret", null));
when(configurationContext.getProperty(PROP_USER_SEARCH_BASE)).thenReturn(new StandardPropertyValue(userSearchBase, null));
when(configurationContext.getProperty(PROP_USER_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("person", null));
when(configurationContext.getProperty(PROP_USER_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.ONE_LEVEL.name(), null));
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_GROUP_SEARCH_BASE)).thenReturn(new StandardPropertyValue(groupSearchBase, null));
when(configurationContext.getProperty(PROP_GROUP_OBJECT_CLASS)).thenReturn(new StandardPropertyValue("groupOfNames", null));
when(configurationContext.getProperty(PROP_GROUP_SEARCH_SCOPE)).thenReturn(new StandardPropertyValue(SearchScope.ONE_LEVEL.name(), null));
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));
return configurationContext;
}
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]);
}
});
return nifiProperties;
}
}

View File

@ -0,0 +1,136 @@
## ---------------------------------------------------------------------------
## 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.
## ---------------------------------------------------------------------------
version: 1
dn: o=nifi
objectclass: extensibleObject
objectclass: top
objectclass: domain
dc: nifi
o: nifi
dn: ou=users,o=nifi
objectClass: organizationalUnit
objectClass: top
ou: users
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=User 3,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 3
sn: User3
uid: user3
dn: cn=User 4,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 4
sn: User4
description: cn=team1,ou=groups,o=nifi
uid: user4
dn: cn=User 5,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 5
sn: User5
description: cn=team1,ou=groups,o=nifi
uid: user5
dn: cn=User 6,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 6
sn: User6
description: cn=team2,ou=groups,o=nifi
uid: user6
dn: cn=User 7,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 7
sn: User7
description: cn=team2,ou=groups,o=nifi
uid: user7
dn: cn=User 8,ou=users,o=nifi
objectClass: organizationalPerson
objectClass: person
objectClass: inetOrgPerson
objectClass: top
cn: User 8
sn: User8
uid: user8
dn: ou=groups,o=nifi
objectClass: organizationalUnit
objectClass: top
ou: groups
dn: cn=admins,ou=groups,o=nifi
objectClass: groupOfNames
objectClass: top
cn: admins
member: cn=User 1,ou=users,o=nifi
member: cn=User 3,ou=users,o=nifi
dn: cn=read-only,ou=groups,o=nifi
objectClass: groupOfNames
objectClass: top
cn: read-only
member: cn=User 2,ou=users,o=nifi
dn: cn=team1,ou=groups,o=nifi
objectClass: groupOfNames
objectClass: top
cn: team1
member: cn=User 1,ou=users,o=nifi
dn: cn=team2,ou=groups,o=nifi
objectClass: groupOfNames
objectClass: top
cn: team2
member: cn=User 1,ou=users,o=nifi