NIFI-3020:

- Introducing a strategy for identifying users.
- Fixing issue with the referral strategy error message.
- Adding code to shutdown the application when the authorizer or login identity provider are not initialized successfully.

NIFI-3020:
- Updating the admin guide to document the identity strategy.

NIFI-3020:
- Ensuring the request replicator attempts to shutdown regardless of whether the flow service properly terminates.

This closes #1236
This commit is contained in:
Matt Gilman 2016-11-16 16:29:14 -05:00 committed by Oleg Zhurakousky
parent 1be0871473
commit c8830742ee
6 changed files with 102 additions and 31 deletions

View File

@ -295,6 +295,7 @@ Below is an example and description of configuring a Login Identity Provider tha
<property name="User Search Base"></property> <property name="User Search Base"></property>
<property name="User Search Filter"></property> <property name="User Search Filter"></property>
<property name="Identity Strategy">USE_DN</property>
<property name="Authentication Expiration">12 hours</property> <property name="Authentication Expiration">12 hours</property>
</provider> </provider>
---- ----
@ -326,6 +327,8 @@ nifi.security.user.login.identity.provider=ldap-provider
|`Url` | Url of the LDAP servier (i.e. ldap://<hostname>:<port>). |`Url` | Url of the LDAP servier (i.e. ldap://<hostname>:<port>).
|`User Search Base` | Base DN for searching for users (i.e. CN=Users,DC=example,DC=com). |`User Search Base` | Base DN for searching for users (i.e. CN=Users,DC=example,DC=com).
|`User Search Filter` | Filter for searching for users against the 'User Search Base'. (i.e. sAMAccountName={0}). The user specified name is inserted into '{0}'. |`User Search Filter` | Filter for searching for users against the 'User Search Base'. (i.e. sAMAccountName={0}). The user specified name is inserted into '{0}'.
|`Identity Strategy` | Strategy to identify users. Possible values are USE_DN and USE_USERNAME. The default functionality if this property is missing is USE_DN in order to retain backward
compatibility. USE_DN will use the full DN of the user entry if possible. USE_USERNAME will use the username the user logged in with.
|`Authentication Expiration` | The duration of how long the user authentication is valid for. If the user never logs out, they will be required to log back in following this duration. |`Authentication Expiration` | The duration of how long the user authentication is valid for. If the user never logs out, they will be required to log back in following this duration.
|================================================================================================================================================== |==================================================================================================================================================

View File

@ -55,6 +55,10 @@
'User Search Filter' - Filter for searching for users against the 'User Search Base'. 'User Search Filter' - Filter for searching for users against the 'User Search Base'.
(i.e. sAMAccountName={0}). The user specified name is inserted into '{0}'. (i.e. sAMAccountName={0}). The user specified name is inserted into '{0}'.
'Identity Strategy' - Strategy to identify users. Possible values are USE_DN and USE_USERNAME.
The default functionality if this property is missing is USE_DN in order to retain
backward compatibility. USE_DN will use the full DN of the user entry if possible.
USE_USERNAME will use the username the user logged in with.
'Authentication Expiration' - The duration of how long the user authentication is valid 'Authentication Expiration' - The duration of how long the user authentication is valid
for. If the user never logs out, they will be required to log back in following for. If the user never logs out, they will be required to log back in following
this duration. this duration.
@ -86,6 +90,7 @@
<property name="User Search Base"></property> <property name="User Search Base"></property>
<property name="User Search Filter"></property> <property name="User Search Filter"></property>
<property name="Identity Strategy">USE_DN</property>
<property name="Authentication Expiration">12 hours</property> <property name="Authentication Expiration">12 hours</property>
</provider> </provider>
To enable the ldap-provider remove 2 lines. This is 2 of 2. --> To enable the ldap-provider remove 2 lines. This is 2 of 2. -->

View File

@ -16,11 +16,8 @@
*/ */
package org.apache.nifi.web.contextlistener; package org.apache.nifi.web.contextlistener;
import java.io.IOException; import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.Authorizer;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator; import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
import org.apache.nifi.controller.FlowController; import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.repository.RepositoryPurgeException; import org.apache.nifi.controller.repository.RepositoryPurgeException;
@ -33,6 +30,10 @@ import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils; import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.io.IOException;
/** /**
* Application context listener for starting the application. If the application is configured for a standalone environment or the application is a node in a clustered environment then a flow * Application context listener for starting the application. If the application is configured for a standalone environment or the application is a node in a clustered environment then a flow
* controller is created and managed. Otherwise, we assume the application is running as the cluster manager in a clustered environment. In this case, the cluster manager is created and managed. * controller is created and managed. Otherwise, we assume the application is running as the cluster manager in a clustered environment. In this case, the cluster manager is created and managed.
@ -80,16 +81,16 @@ public class ApplicationStartupContextListener implements ServletContextListener
logger.info("Flow Controller started successfully."); logger.info("Flow Controller started successfully.");
} }
} catch (BeansException | RepositoryPurgeException | IOException e) { } catch (BeansException | RepositoryPurgeException | IOException e) {
// ensure the flow service is terminated shutdown(flowService, ctx.getBean("requestReplicator", RequestReplicator.class));
if (flowService != null && flowService.isRunning()) { throw new NiFiCoreException("Unable to start Flow Controller.", e);
flowService.stop(false); }
}
final RequestReplicator requestReplicator = ctx.getBean("requestReplicator", RequestReplicator.class);
if (requestReplicator != null) {
requestReplicator.shutdown();
}
try {
// attempt to get a few beans that we want to to ensure properly created since they are lazily initialized
ctx.getBean("loginIdentityProvider", LoginIdentityProvider.class);
ctx.getBean("authorizer", Authorizer.class);
} catch (final BeansException e) {
shutdown(flowService, ctx.getBean("requestReplicator", RequestReplicator.class));
throw new NiFiCoreException("Unable to start Flow Controller.", e); throw new NiFiCoreException("Unable to start Flow Controller.", e);
} }
} }
@ -97,23 +98,34 @@ public class ApplicationStartupContextListener implements ServletContextListener
@Override @Override
public void contextDestroyed(ServletContextEvent sce) { public void contextDestroyed(ServletContextEvent sce) {
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext()); ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(sce.getServletContext());
try {
logger.info("Initiating shutdown of flow service...");
FlowService flowService = ctx.getBean("flowService", FlowService.class); logger.info("Initiating shutdown of flow service...");
if (flowService.isRunning()) { shutdown(ctx.getBean("flowService", FlowService.class), ctx.getBean("requestReplicator", RequestReplicator.class));
logger.info("Flow service termination completed.");
}
private void shutdown(final FlowService flowService, final RequestReplicator requestReplicator) {
try {
// ensure the flow service is terminated
if (flowService != null && flowService.isRunning()) {
flowService.stop(false); flowService.stop(false);
} }
} catch (final Exception e) {
final String msg = "Problem occurred ensuring flow controller or repository was properly terminated due to " + e;
if (logger.isDebugEnabled()) {
logger.warn(msg, e);
} else {
logger.warn(msg);
}
}
final RequestReplicator requestReplicator = ctx.getBean("requestReplicator", RequestReplicator.class); try {
// ensure the request replicator is shutdown
if (requestReplicator != null) { if (requestReplicator != null) {
requestReplicator.shutdown(); requestReplicator.shutdown();
} }
logger.info("Flow service termination completed.");
} catch (final Exception e) { } catch (final Exception e) {
String msg = "Problem occurred ensuring flow controller or repository was properly terminated due to " + e; final String msg = "Problem occurred ensuring request replicator was properly terminated due to " + e;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.warn(msg, e); logger.warn(msg, e);
} else { } else {

View File

@ -0,0 +1,26 @@
/*
* 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;
/**
*
*/
public enum IdentityStrategy {
USE_DN,
USE_USERNAME;
}

View File

@ -68,6 +68,7 @@ public class LdapProvider implements LoginIdentityProvider {
private AbstractLdapAuthenticationProvider provider; private AbstractLdapAuthenticationProvider provider;
private String issuer; private String issuer;
private long expiration; private long expiration;
private IdentityStrategy identityStrategy;
@Override @Override
public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException { public final void initialize(final LoginIdentityProviderInitializationContext initializationContext) throws ProviderCreationException {
@ -195,7 +196,8 @@ public class LdapProvider implements LoginIdentityProvider {
rawReferralStrategy, StringUtils.join(ReferralStrategy.values(), ", "))); rawReferralStrategy, StringUtils.join(ReferralStrategy.values(), ", ")));
} }
context.setReferral(referralStrategy.toString()); // 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 // url
final String url = configurationContext.getProperty("Url"); final String url = configurationContext.getProperty("Url");
@ -221,6 +223,24 @@ public class LdapProvider implements LoginIdentityProvider {
final BindAuthenticator authenticator = new BindAuthenticator(context); final BindAuthenticator authenticator = new BindAuthenticator(context);
authenticator.setUserSearch(userSearch); authenticator.setUserSearch(userSearch);
// identity strategy
final String rawIdentityStrategy = configurationContext.getProperty("Identity Strategy");
if (StringUtils.isBlank(rawIdentityStrategy)) {
logger.info(String.format("Identity Strategy is not configured, defaulting strategy to %s.", IdentityStrategy.USE_DN));
// if this value is not configured, default to use dn which was the previous implementation
identityStrategy = IdentityStrategy.USE_DN;
} else {
try {
// attempt to get the configured identity strategy
identityStrategy = IdentityStrategy.valueOf(rawIdentityStrategy);
} catch (final IllegalArgumentException iae) {
throw new ProviderCreationException(String.format("Unrecognized identity strategy '%s'. Possible values are [%s]",
rawIdentityStrategy, StringUtils.join(IdentityStrategy.values(), ", ")));
}
}
try { try {
// handling initializing beans // handling initializing beans
context.afterPropertiesSet(); context.afterPropertiesSet();
@ -260,10 +280,16 @@ public class LdapProvider implements LoginIdentityProvider {
final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword()); final UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
final Authentication authentication = provider.authenticate(token); final Authentication authentication = provider.authenticate(token);
// attempt to get the ldap user details to get the DN // use dn if configured
if (authentication.getPrincipal() instanceof LdapUserDetails) { if (IdentityStrategy.USE_DN.equals(identityStrategy)) {
final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal(); // attempt to get the ldap user details to get the DN
return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration, issuer); if (authentication.getPrincipal() instanceof LdapUserDetails) {
final LdapUserDetails userDetails = (LdapUserDetails) authentication.getPrincipal();
return new AuthenticationResponse(userDetails.getDn(), credentials.getUsername(), expiration, issuer);
} else {
logger.warn(String.format("Unable to determine user DN for %s, using username.", authentication.getName()));
return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration, issuer);
}
} else { } else {
return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration, issuer); return new AuthenticationResponse(authentication.getName(), credentials.getUsername(), expiration, issuer);
} }

View File

@ -31,8 +31,7 @@ public enum ReferralStrategy {
this.value = value; this.value = value;
} }
@Override public String getValue() {
public String toString() {
return value; return value;
} }