You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by st...@apache.org on 2016/03/28 13:04:42 UTC
[2/2] ambari git commit: AMBARI-15554. Ambari LDAP integration cannot
handle LDAP directories with multiple entries for the same user. (stoader)
AMBARI-15554. Ambari LDAP integration cannot handle LDAP directories with multiple entries for the same user. (stoader)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/71b4c624
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/71b4c624
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/71b4c624
Branch: refs/heads/trunk
Commit: 71b4c624fb219bb1626c238322bda6c2e5589f72
Parents: d0672c2
Author: Toader, Sebastian <st...@hortonworks.com>
Authored: Mon Mar 28 13:04:17 2016 +0200
Committer: Toader, Sebastian <st...@hortonworks.com>
Committed: Mon Mar 28 13:04:17 2016 +0200
----------------------------------------------------------------------
ambari-server/conf/unix/log4j.properties | 3 +
ambari-server/pom.xml | 1 +
.../server/api/UserNameOverrideFilter.java | 137 ++++++++
.../server/configuration/Configuration.java | 47 +++
.../ambari/server/controller/AmbariServer.java | 2 +
.../authorization/AmbariAuthentication.java | 214 ++++++++++++
.../AmbariLdapAuthenticationProvider.java | 47 ++-
.../AmbariLdapAuthoritiesPopulator.java | 2 +
.../AmbariLdapBindAuthenticator.java | 29 +-
.../security/authorization/AmbariLdapUtils.java | 43 +++
.../authorization/AuthorizationHelper.java | 38 ++-
...ateLdapUserFoundAuthenticationException.java | 51 +++
.../authorization/LdapServerProperties.java | 44 ++-
.../webapp/WEB-INF/spring-security.xml | 1 +
.../server/api/UserNameOverrideFilterTest.java | 196 +++++++++++
.../server/configuration/ConfigurationTest.java | 56 ++++
.../server/security/AmbariLdapUtilsTest.java | 87 +++++
.../authorization/AmbariAuthenticationTest.java | 333 +++++++++++++++++++
...henticationProviderForDuplicateUserTest.java | 100 ++++++
.../AmbariLdapAuthenticationProviderTest.java | 50 ++-
.../AmbariLdapBindAuthenticatorTest.java | 136 ++++++++
.../authorization/AuthorizationHelperTest.java | 116 ++++++-
.../authorization/LdapServerPropertiesTest.java | 23 +-
.../TestAmbariLdapAuthoritiesPopulator.java | 42 +++
ambari-server/src/test/resources/users.ldif | 1 +
.../resources/users_with_duplicate_uid.ldif | 20 ++
ambari-web/app/controllers/login_controller.js | 8 +-
ambari-web/app/router.js | 4 +-
.../test/controllers/login_controller_test.js | 26 +-
29 files changed, 1816 insertions(+), 41 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/conf/unix/log4j.properties
----------------------------------------------------------------------
diff --git a/ambari-server/conf/unix/log4j.properties b/ambari-server/conf/unix/log4j.properties
index 2ee32d4..ab3ea0b 100644
--- a/ambari-server/conf/unix/log4j.properties
+++ b/ambari-server/conf/unix/log4j.properties
@@ -73,3 +73,6 @@ log4j.appender.eclipselink.layout.ConversionPattern=%m%n
log4j.logger.org.apache.hadoop.yarn.client=WARN
log4j.logger.org.apache.slider.common.tools.SliderUtils=WARN
log4j.logger.org.apache.ambari.server.security.authorization=WARN
+
+log4j.logger.org.apache.ambari.server.security.authorization.AuthorizationHelper=INFO
+log4j.logger.org.apache.ambari.server.security.authorization.AmbariLdapBindAuthenticator=INFO
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index 1e44517..857e554 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -293,6 +293,7 @@
<exclude>src/test/resources/TestAmbaryServer.samples/**</exclude>
<exclude>src/test/resources/*.txt</exclude>
<exclude>src/test/resources/users_for_dn_with_space.ldif</exclude>
+ <exclude>src/test/resources/users_with_duplicate_uid.ldif</exclude>
<!--Velocity log -->
<exclude>**/velocity.log*</exclude>
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/api/UserNameOverrideFilter.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/UserNameOverrideFilter.java b/ambari-server/src/main/java/org/apache/ambari/server/api/UserNameOverrideFilter.java
new file mode 100644
index 0000000..04de12c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/api/UserNameOverrideFilter.java
@@ -0,0 +1,137 @@
+/**
+ * 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.ambari.server.api;
+
+import java.io.IOException;
+import java.net.URLDecoder;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.apache.ambari.server.security.authorization.AuthorizationHelper;
+
+/**
+ * This filter overrides usernames found in request url.
+ */
+public class UserNameOverrideFilter implements Filter {
+
+ // Regex for extracting user name component from the user related api request Uris
+ private final static Pattern USER_NAME_IN_URI_REGEXP = Pattern.compile("(?<pre>.*/users/)(?<username>[^/]+)(?<post>(/.*)?)");
+
+ /**
+ * Called by the web container to indicate to a filter that it is
+ * being placed into service.
+ *
+ * <p>The servlet container calls the init
+ * method exactly once after instantiating the filter. The init
+ * method must complete successfully before the filter is asked to do any
+ * filtering work.
+ *
+ * <p>The web container cannot place the filter into service if the init
+ * method either
+ * <ol>
+ * <li>Throws a ServletException
+ * <li>Does not return within a time period defined by the web container
+ * </ol>
+ *
+ * @param filterConfig
+ */
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+
+ }
+
+ /**
+ * The <code>doFilter</code> method of the Filter is called by the
+ * container each time a request/response pair is passed through the
+ * chain due to a client request for a resource at the end of the chain.
+ * The FilterChain passed in to this method allows the Filter to pass
+ * on the request and response to the next entity in the chain.
+ *
+ * Verify if this a user related request by checking that the Uri of the request contains
+ * username and resolves the username to actual ambari user name if username
+ * is a login alias.
+ *
+ * @param request
+ * @param response
+ * @param chain
+ */
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+ if (request instanceof HttpServletRequest) {
+ final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+ Matcher userNameMatcher = getUserNameMatcher(httpServletRequest.getRequestURI());
+
+ if (userNameMatcher.find()) {
+ String userNameFromUri = URLDecoder.decode(userNameMatcher.group("username"), "UTF-8");
+ final String userName = AuthorizationHelper.resolveLoginAliasToUserName(userNameFromUri);
+
+ if (!userNameFromUri.equals(userName)) {
+ final String requestUriOverride = String.format("%s%s%s", userNameMatcher.group("pre"), userName, userNameMatcher.group("post"));
+
+ request = new HttpServletRequestWrapper(httpServletRequest) {
+ @Override
+ public String getRequestURI() {
+ return requestUriOverride;
+ }
+ };
+
+ }
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ /**
+ * Returns a {@link Matcher} created from {@link #USER_NAME_IN_URI_REGEXP} for the
+ * provided requestUri.
+ * @param requestUri the Uri the Matcher is created for.
+ * @return the matcher
+ */
+ protected Matcher getUserNameMatcher(String requestUri) {
+ return USER_NAME_IN_URI_REGEXP.matcher(requestUri);
+ }
+
+ /**
+ * Called by the web container to indicate to a filter that it is being
+ * taken out of service.
+ *
+ * <p>This method is only called once all threads within the filter's
+ * doFilter method have exited or after a timeout period has passed.
+ * After the web container calls this method, it will not call the
+ * doFilter method again on this instance of the filter.
+ *
+ * <p>This method gives the filter an opportunity to clean up any
+ * resources that are being held (for example, memory, file handles,
+ * threads) and make sure that any persistent state is synchronized
+ * with the filter's current state in memory.
+ */
+ @Override
+ public void destroy() {
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
index bf18325..1d30f1c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java
@@ -188,6 +188,30 @@ public class Configuration {
public static final String LDAP_GROUP_NAMING_ATTR_KEY = "authentication.ldap.groupNamingAttr";
public static final String LDAP_GROUP_MEMEBERSHIP_ATTR_KEY = "authentication.ldap.groupMembershipAttr";
public static final String LDAP_ADMIN_GROUP_MAPPING_RULES_KEY = "authorization.ldap.adminGroupMappingRules";
+ /**
+ * When authentication through LDAP is enabled then Ambari Server uses this filter to lookup
+ * the user in LDAP based on the provided ambari user name.
+ *
+ * If it is not set then the default {@link #LDAP_USER_SEARCH_FILTER_DEFAULT} is used.
+ */
+ public static final String LDAP_USER_SEARCH_FILTER_KEY = "authentication.ldap.userSearchFilter";
+
+ /**
+ * When authentication through LDAP is enabled there might be cases when {@link #LDAP_USER_SEARCH_FILTER_KEY}
+ * may match multiple users in LDAP. In such cases the user is prompted to provide additional info, e.g. the domain
+ * he or she wants ot log in upon login beside the username. This filter will be used by Ambari Server to lookup
+ * users in LDAP if the login name the user logs in contains additional information beside ambari user name.
+ *
+ * If it is not not set then the default {@link #LDAP_ALT_USER_SEARCH_FILTER_DEFAULT} is used.
+ *
+ * <p>
+ * Note: Currently this filter will only be used by Ambari Server if the user login name
+ * is in the username@domain format (e.g. user1@x.y.com) which is the userPrincipalName
+ * format used in AD.
+ * </p>
+ */
+ public static final String LDAP_ALT_USER_SEARCH_FILTER_KEY = "authentication.ldap.alternateUserSearchFilter"; //TODO: we'll need a more generic solution to support any login name format
+
public static final String LDAP_GROUP_SEARCH_FILTER_KEY = "authorization.ldap.groupSearchFilter";
public static final String LDAP_REFERRAL_KEY = "authentication.ldap.referral";
public static final String LDAP_PAGINATION_ENABLED_KEY = "authentication.ldap.pagination.enabled";
@@ -459,6 +483,25 @@ public class Configuration {
private static final String LDAP_GROUP_NAMING_ATTR_DEFAULT = "cn";
private static final String LDAP_GROUP_MEMBERSHIP_ATTR_DEFAULT = "member";
private static final String LDAP_ADMIN_GROUP_MAPPING_RULES_DEFAULT = "Ambari Administrators";
+ /**
+ * When authentication through LDAP is enabled then Ambari Server uses this filter by default to lookup
+ * the user in LDAP if one not provided in the config via {@link #LDAP_USER_SEARCH_FILTER_KEY}.
+ */
+ protected static final String LDAP_USER_SEARCH_FILTER_DEFAULT = "(&({usernameAttribute}={0})(objectClass={userObjectClass}))";
+
+ /**
+ * When authentication through LDAP is enabled Ambari Server uses this filter by default to lookup
+ * the user in LDAP when the user provides beside user name additional information.
+ * This filter can be overridden through {@link #LDAP_ALT_USER_SEARCH_FILTER_KEY}.
+ *
+ * <p>
+ * Note: Currently the use of alternate user search filter is triggered only if the user login name
+ * is in the username@domain format (e.g. user1@x.y.com) which is the userPrincipalName
+ * format used in AD.
+ * </p>
+ */
+ protected static final String LDAP_ALT_USER_SEARCH_FILTER_DEFAULT = "(&(userPrincipalName={0})(objectClass={userObjectClass}))"; //TODO: we'll need a more generic solution to support any login name format
+
private static final String LDAP_GROUP_SEARCH_FILTER_DEFAULT = "";
private static final String LDAP_REFERRAL_DEFAULT = "follow";
@@ -1661,6 +1704,10 @@ public class Configuration {
getProperty(LDAP_GROUP_NAMING_ATTR_KEY, LDAP_GROUP_NAMING_ATTR_DEFAULT));
ldapServerProperties.setAdminGroupMappingRules(properties.getProperty(
LDAP_ADMIN_GROUP_MAPPING_RULES_KEY, LDAP_ADMIN_GROUP_MAPPING_RULES_DEFAULT));
+ ldapServerProperties.setUserSearchFilter(properties.getProperty(
+ LDAP_USER_SEARCH_FILTER_KEY, LDAP_USER_SEARCH_FILTER_DEFAULT));
+ ldapServerProperties.setAlternateUserSearchFilter(properties.getProperty(
+ LDAP_ALT_USER_SEARCH_FILTER_KEY, LDAP_ALT_USER_SEARCH_FILTER_DEFAULT));
ldapServerProperties.setGroupSearchFilter(properties.getProperty(
LDAP_GROUP_SEARCH_FILTER_KEY, LDAP_GROUP_SEARCH_FILTER_DEFAULT));
ldapServerProperties.setReferralMethod(properties.getProperty(
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
index 076f850..4b769e3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariServer.java
@@ -41,6 +41,7 @@ import org.apache.ambari.server.agent.rest.AgentResource;
import org.apache.ambari.server.api.AmbariErrorHandler;
import org.apache.ambari.server.api.AmbariPersistFilter;
import org.apache.ambari.server.api.MethodOverrideFilter;
+import org.apache.ambari.server.api.UserNameOverrideFilter;
import org.apache.ambari.server.api.rest.BootStrapResource;
import org.apache.ambari.server.api.services.AmbariMetaInfo;
import org.apache.ambari.server.api.services.KeyService;
@@ -363,6 +364,7 @@ public class AmbariServer {
root.addEventListener(new RequestContextListener());
root.addFilter(new FilterHolder(springSecurityFilter), "/api/*", DISPATCHER_TYPES);
+ root.addFilter(new FilterHolder(new UserNameOverrideFilter()), "/api/v1/users/*", DISPATCHER_TYPES);
// session-per-request strategy for agents
agentroot.addFilter(new FilterHolder(injector.getInstance(AmbariPersistFilter.class)), "/agent/*", DISPATCHER_TYPES);
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthentication.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthentication.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthentication.java
new file mode 100644
index 0000000..98b97b2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariAuthentication.java
@@ -0,0 +1,214 @@
+/**
+ * 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.ambari.server.security.authorization;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Objects;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.User;
+
+/**
+ * This class is a wrapper for authentication objects to
+ * provide functionality for resolving login aliases to
+ * ambari user names.
+ */
+public final class AmbariAuthentication implements Authentication {
+ private final Authentication authentication;
+ private final Object principalOverride;
+
+ public AmbariAuthentication(Authentication authentication) {
+ this.authentication = authentication;
+ this.principalOverride = getPrincipalOverride();
+ }
+
+
+
+ /**
+ * Set by an <code>AuthenticationManager</code> to indicate the authorities that the principal has been
+ * granted. Note that classes should not rely on this value as being valid unless it has been set by a trusted
+ * <code>AuthenticationManager</code>.
+ * <p>
+ * Implementations should ensure that modifications to the returned collection
+ * array do not affect the state of the Authentication object, or use an unmodifiable instance.
+ * </p>
+ *
+ * @return the authorities granted to the principal, or an empty collection if the token has not been authenticated.
+ * Never null.
+ */
+ @Override
+ public Collection<? extends GrantedAuthority> getAuthorities() {
+ return authentication.getAuthorities();
+ }
+
+ /**
+ * The credentials that prove the principal is correct. This is usually a password, but could be anything
+ * relevant to the <code>AuthenticationManager</code>. Callers are expected to populate the credentials.
+ *
+ * @return the credentials that prove the identity of the <code>Principal</code>
+ */
+ @Override
+ public Object getCredentials() {
+ return authentication.getCredentials();
+ }
+
+ /**
+ * Stores additional details about the authentication request. These might be an IP address, certificate
+ * serial number etc.
+ *
+ * @return additional details about the authentication request, or <code>null</code> if not used
+ */
+ @Override
+ public Object getDetails() {
+ return authentication.getDetails();
+ }
+
+ /**
+ * The identity of the principal being authenticated. In the case of an authentication request with username and
+ * password, this would be the username. Callers are expected to populate the principal for an authentication
+ * request.
+ * <p>
+ * The <tt>AuthenticationManager</tt> implementation will often return an <tt>Authentication</tt> containing
+ * richer information as the principal for use by the application. Many of the authentication providers will
+ * create a {@code UserDetails} object as the principal.
+ *
+ * @return the <code>Principal</code> being authenticated or the authenticated principal after authentication.
+ */
+ @Override
+ public Object getPrincipal() {
+ if (principalOverride != null) {
+ return principalOverride;
+ }
+
+ return authentication.getPrincipal();
+ }
+
+ /**
+ * Used to indicate to {@code AbstractSecurityInterceptor} whether it should present the
+ * authentication token to the <code>AuthenticationManager</code>. Typically an <code>AuthenticationManager</code>
+ * (or, more often, one of its <code>AuthenticationProvider</code>s) will return an immutable authentication token
+ * after successful authentication, in which case that token can safely return <code>true</code> to this method.
+ * Returning <code>true</code> will improve performance, as calling the <code>AuthenticationManager</code> for
+ * every request will no longer be necessary.
+ * <p>
+ * For security reasons, implementations of this interface should be very careful about returning
+ * <code>true</code> from this method unless they are either immutable, or have some way of ensuring the properties
+ * have not been changed since original creation.
+ *
+ * @return true if the token has been authenticated and the <code>AbstractSecurityInterceptor</code> does not need
+ * to present the token to the <code>AuthenticationManager</code> again for re-authentication.
+ */
+ @Override
+ public boolean isAuthenticated() {
+ return authentication.isAuthenticated();
+ }
+
+ /**
+ * See {@link #isAuthenticated()} for a full description.
+ * <p>
+ * Implementations should <b>always</b> allow this method to be called with a <code>false</code> parameter,
+ * as this is used by various classes to specify the authentication token should not be trusted.
+ * If an implementation wishes to reject an invocation with a <code>true</code> parameter (which would indicate
+ * the authentication token is trusted - a potential security risk) the implementation should throw an
+ * {@link IllegalArgumentException}.
+ *
+ * @param isAuthenticated <code>true</code> if the token should be trusted (which may result in an exception) or
+ * <code>false</code> if the token should not be trusted
+ * @throws IllegalArgumentException if an attempt to make the authentication token trusted (by passing
+ * <code>true</code> as the argument) is rejected due to the implementation being immutable or
+ * implementing its own alternative approach to {@link #isAuthenticated()}
+ */
+ @Override
+ public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+ authentication.setAuthenticated(isAuthenticated);
+ }
+
+ /**
+ * Returns the name of this principal.
+ *
+ * @return the name of this principal.
+ */
+ @Override
+ public String getName() {
+ if (principalOverride != null)
+ {
+ if (principalOverride instanceof UserDetails) {
+ return ((UserDetails) principalOverride).getUsername();
+ }
+
+ return principalOverride.toString();
+ }
+
+ return authentication.getName();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ AmbariAuthentication that = (AmbariAuthentication) o;
+ return Objects.equals(authentication, that.authentication) &&
+ Objects.equals(principalOverride, that.principalOverride);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(authentication, principalOverride);
+ }
+
+ /**
+ * Returns a principal object that is to be used
+ * to override the original principal object
+ * returned by the inner {@link #authentication} object.
+ *
+ * <p>The purpose of overriding the origin principal is to provide
+ * and object that resolves the contained user name to ambari user name in case
+ * the original user name is a login alias.</p>
+ *
+ * @return principal override of the original one is of type {@link UserDetails},
+ * if the original one is a login alias name than the user name the login alias resolves to
+ * otherwise <code>null</code>
+ */
+ private Object getPrincipalOverride() {
+ Object principal = authentication.getPrincipal();
+
+ if (principal instanceof UserDetails) {
+ UserDetails user = (UserDetails)principal;
+
+ principal =
+ new User(
+ AuthorizationHelper.resolveLoginAliasToUserName(user.getUsername()),
+ user.getPassword(),
+ user.isEnabled(),
+ user.isAccountNonExpired(),
+ user.isCredentialsNonExpired(),
+ user.isAccountNonLocked(),
+ user.getAuthorities());
+ } else if ( !(principal instanceof Principal) && principal != null ){
+ String username = principal.toString();
+ principal = AuthorizationHelper.resolveLoginAliasToUserName(username);
+ } else {
+ principal = null;
+ }
+
+ return principal;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
index 20cf2fd..7b2a95c 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProvider.java
@@ -19,10 +19,12 @@ package org.apache.ambari.server.security.authorization;
import com.google.inject.Inject;
import java.util.List;
+
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.security.ClientSecurityType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.ldap.core.support.LdapContextSource;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -45,6 +47,7 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
private ThreadLocal<LdapServerProperties> ldapServerProperties = new ThreadLocal<LdapServerProperties>();
private ThreadLocal<LdapAuthenticationProvider> providerThreadLocal = new ThreadLocal<LdapAuthenticationProvider>();
+ private ThreadLocal<String> ldapUserSearchFilterThreadLocal = new ThreadLocal<>();
@Inject
public AmbariLdapAuthenticationProvider(Configuration configuration, AmbariLdapAuthoritiesPopulator authoritiesPopulator) {
@@ -54,10 +57,13 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-
if (isLdapEnabled()) {
+ String username = getUserName(authentication);
+
try {
- return loadLdapAuthenticationProvider().authenticate(authentication);
+ Authentication auth = loadLdapAuthenticationProvider(username).authenticate(authentication);
+
+ return new AmbariAuthentication(auth);
} catch (AuthenticationException e) {
LOG.debug("Got exception during LDAP authentification attempt", e);
// Try to help in troubleshooting
@@ -73,6 +79,8 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
}
}
throw e;
+ } catch (IncorrectResultSizeDataAccessException multipleUsersFound) {
+ throw new DuplicateLdapUserFoundAuthenticationException(String.format("Login Failed: Please append your domain to your username and try again. Example: %s@domain", username));
}
} else {
return null;
@@ -89,9 +97,14 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
* Reloads LDAP Context Source and depending objects if properties were changed
* @return corresponding LDAP authentication provider
*/
- LdapAuthenticationProvider loadLdapAuthenticationProvider() {
- if (reloadLdapServerProperties()) {
- LOG.info("LDAP Properties changed - rebuilding Context");
+ LdapAuthenticationProvider loadLdapAuthenticationProvider(String userName) {
+ boolean ldapConfigPropertiesChanged = reloadLdapServerProperties();
+
+ String ldapUserSearchFilter = getLdapUserSearchFilter(userName);
+
+ if (ldapConfigPropertiesChanged|| !ldapUserSearchFilter.equals(ldapUserSearchFilterThreadLocal.get())) {
+
+ LOG.info("Either LDAP Properties or user search filter changed - rebuilding Context");
LdapContextSource springSecurityContextSource = new LdapContextSource();
List<String> ldapUrls = ldapServerProperties.get().getLdapUrls();
springSecurityContextSource.setUrls(ldapUrls.toArray(new String[ldapUrls.size()]));
@@ -111,18 +124,17 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
//TODO change properties
String userSearchBase = ldapServerProperties.get().getUserSearchBase();
- String userSearchFilter = ldapServerProperties.get().getUserSearchFilter();
-
- FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(userSearchBase, userSearchFilter, springSecurityContextSource);
+ FilterBasedLdapUserSearch userSearch = new FilterBasedLdapUserSearch(userSearchBase, ldapUserSearchFilter, springSecurityContextSource);
AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(springSecurityContextSource, configuration);
bindAuthenticator.setUserSearch(userSearch);
LdapAuthenticationProvider authenticationProvider = new LdapAuthenticationProvider(bindAuthenticator, authoritiesPopulator);
-
providerThreadLocal.set(authenticationProvider);
}
+ ldapUserSearchFilterThreadLocal.set(ldapUserSearchFilter);
+
return providerThreadLocal.get();
}
@@ -136,6 +148,16 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
}
/**
+ * Extracts the user name from the passed authentication object.
+ * @param authentication
+ * @return
+ */
+ protected String getUserName(Authentication authentication) {
+ UsernamePasswordAuthenticationToken userToken = (UsernamePasswordAuthenticationToken)authentication;
+ return userToken.getName();
+ }
+
+ /**
* Reloads LDAP Server properties from configuration
*
* @return true if properties were reloaded
@@ -149,4 +171,11 @@ public class AmbariLdapAuthenticationProvider implements AuthenticationProvider
}
return false;
}
+
+
+ private String getLdapUserSearchFilter(String userName) {
+ return ldapServerProperties.get()
+ .getUserSearchFilter(AmbariLdapUtils.isUserPrincipalNameFormat(userName));
+ }
+
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthoritiesPopulator.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthoritiesPopulator.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthoritiesPopulator.java
index fc7f73a..7df8dc3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthoritiesPopulator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthoritiesPopulator.java
@@ -60,6 +60,8 @@ public class AmbariLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator
@Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
+ username = AuthorizationHelper.resolveLoginAliasToUserName(username);
+
log.info("Get authorities for user " + username + " from local DB");
UserEntity user;
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
index ed68c01..c63ea92 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticator.java
@@ -18,7 +18,14 @@
package org.apache.ambari.server.security.authorization;
+import java.util.List;
+
+import javax.naming.NamingException;
+import javax.naming.directory.Attributes;
+
import org.apache.ambari.server.configuration.Configuration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.LdapTemplate;
@@ -26,16 +33,13 @@ import org.springframework.ldap.core.support.BaseLdapPathContextSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.ldap.authentication.BindAuthenticator;
-import java.util.*;
-import javax.naming.*;
-import javax.naming.directory.Attributes;
-
/**
* An authenticator which binds as a user and checks if user should get ambari
* admin authorities according to LDAP group membership
*/
public class AmbariLdapBindAuthenticator extends BindAuthenticator {
+ private static final Logger LOG = LoggerFactory.getLogger(AmbariLdapBindAuthenticator.class);
private Configuration configuration;
@@ -51,8 +55,23 @@ public class AmbariLdapBindAuthenticator extends BindAuthenticator {
public DirContextOperations authenticate(Authentication authentication) {
DirContextOperations user = super.authenticate(authentication);
+ setAmbariAdminAttr(user);
- return setAmbariAdminAttr(user);
+ // Users stored locally in ambari are matched against LDAP users by the ldap attribute configured to be used as user name.
+ // (e.g. uid, sAMAccount -> ambari user name )
+ String ldapUserName = user.getStringAttribute(configuration.getLdapServerProperties().getUsernameAttribute());
+ String loginName = authentication.getName(); // user login name the user has logged in
+
+ if (!ldapUserName.equals(loginName)) {
+ // if authenticated user name is different from ldap user name than user has logged in
+ // with a login name that is different (e.g. user principal name) from the ambari user name stored in
+ // ambari db. In this case add the user login name as login alias for ambari user name.
+ LOG.info("User with {}='{}' logged in with login alias '{}'", ldapUserName, loginName);
+
+ AuthorizationHelper.addLoginNameAlias(ldapUserName, loginName);
+ }
+
+ return user;
}
/**
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapUtils.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapUtils.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapUtils.java
new file mode 100644
index 0000000..ffebd45
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AmbariLdapUtils.java
@@ -0,0 +1,43 @@
+/**
+ * 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.ambari.server.security.authorization;
+
+import java.util.regex.Pattern;
+
+/**
+ * Provides utility methods for LDAP related functionality
+ */
+public class AmbariLdapUtils {
+
+ /**
+ * Regexp to verify if user login name beside user contains domain information as well (User principal name format).
+ */
+ private static final Pattern UPN_FORMAT = Pattern.compile(".+@\\w+(\\.\\w+)*");
+
+ /**
+ * Returns true if the given user name contains domain name as well (e.g. username@domain)
+ * @param loginName the login name to verify if it contains domain information.
+ * @return
+ */
+ public static boolean isUserPrincipalNameFormat(String loginName) {
+ return UPN_FORMAT.matcher(loginName).matches();
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
index 0c675b8..15ef8d0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/AuthorizationHelper.java
@@ -28,6 +28,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.*;
@@ -42,7 +45,7 @@ public class AuthorizationHelper {
* Converts collection of RoleEntities to collection of GrantedAuthorities
*/
public Collection<GrantedAuthority> convertPrivilegesToAuthorities(Collection<PrivilegeEntity> privilegeEntities) {
- Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(privilegeEntities.size());
+ Set<GrantedAuthority> authorities = new HashSet<>(privilegeEntities.size());
for (PrivilegeEntity privilegeEntity : privilegeEntities) {
authorities.add(new AmbariGrantedAuthority(privilegeEntity));
@@ -247,4 +250,37 @@ public class AuthorizationHelper {
SecurityContext context = SecurityContextHolder.getContext();
return (context == null) ? null : context.getAuthentication();
}
+
+ /**
+ * There are cases when users log-in with a login name that is
+ * define in LDAP and which do not correspond to the user name stored
+ * locally in ambari. These external login names act as an alias to
+ * ambari users name. This method stores in the current http session a mapping
+ * of alias user name to local ambari user name to make possible resolving
+ * login alias to ambari user name.
+ * @param ambariUserName ambari user name for which the alias is to be stored in the session
+ * @param loginAlias the alias for the ambari user name.
+ */
+ public static void addLoginNameAlias(String ambariUserName, String loginAlias) {
+ ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (attr != null) {
+ LOG.info("Adding login alias '{}' for user name '{}'", loginAlias, ambariUserName);
+ attr.setAttribute(loginAlias, ambariUserName, RequestAttributes.SCOPE_SESSION);
+ }
+ }
+
+ /**
+ * Looks up the provided loginAlias in the current http session and return the ambari
+ * user name that the alias is defined for.
+ * @param loginAlias the login alias to resolve to ambari user name
+ * @return the ambari user name if the alias is found otherwise returns the passed in loginAlias
+ */
+ public static String resolveLoginAliasToUserName(String loginAlias) {
+ ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+ if (attr != null && attr.getAttribute(loginAlias, RequestAttributes.SCOPE_SESSION) != null) {
+ return (String)attr.getAttribute(loginAlias, RequestAttributes.SCOPE_SESSION);
+ }
+
+ return loginAlias;
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/DuplicateLdapUserFoundAuthenticationException.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/DuplicateLdapUserFoundAuthenticationException.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/DuplicateLdapUserFoundAuthenticationException.java
new file mode 100644
index 0000000..dd5c754
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/DuplicateLdapUserFoundAuthenticationException.java
@@ -0,0 +1,51 @@
+/**
+ * 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.ambari.server.security.authorization;
+
+
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * This exception signals that duplicate user entries were found in LDAP during authentication.
+ * The filter used to match user entry in LDAP that corresponds to the user being authenticated
+ * should be refined to match only one entry.
+ */
+public class DuplicateLdapUserFoundAuthenticationException extends AuthenticationException {
+
+ /**
+ * Constructs an {@code DuplicateLdapUserFoundAuthenticationException} with the specified message.
+ *
+ * @param msg the detail message
+ */
+ public DuplicateLdapUserFoundAuthenticationException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an {@code DuplicateLdapUserFoundAuthenticationException} with the specified message and root cause.
+ *
+ * @param msg the detail message
+ * @param t the root cause
+ */
+ public DuplicateLdapUserFoundAuthenticationException(String msg, Throwable t) {
+ super(msg, t);
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/LdapServerProperties.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/LdapServerProperties.java b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/LdapServerProperties.java
index 8eeaf35..17432d0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/LdapServerProperties.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/authorization/LdapServerProperties.java
@@ -53,7 +53,8 @@ public class LdapServerProperties {
private String userSearchBase = "";
private String groupSearchFilter;
- private static final String userSearchFilter = "(&({attribute}={0})(objectClass={userObjectClass}))";
+ private String userSearchFilter;
+ private String alternateUserSearchFilter; // alternate user search filter to be used when users use their alternate login id (e.g. User Principal Name)
//LDAP pagination properties
private boolean paginationEnabled = true;
@@ -137,10 +138,17 @@ public class LdapServerProperties {
this.userSearchBase = userSearchBase;
}
- public String getUserSearchFilter() {
- return userSearchFilter
- .replace("{attribute}", usernameAttribute)
- .replace("{userObjectClass}", userObjectClass);
+ /**
+ * Returns the LDAP filter to search users by.
+ * @param useAlternateUserSearchFilter if true than return LDAP filter that expects user name in
+ * User Principal Name format to filter users constructed from {@value org.apache.ambari.server.configuration.Configuration#LDAP_ALT_USER_SEARCH_FILTER_KEY}.
+ * Otherwise the filter is constructed from {@value org.apache.ambari.server.configuration.Configuration#LDAP_USER_SEARCH_FILTER_KEY}
+ * @return the LDAP filter string
+ */
+ public String getUserSearchFilter(boolean useAlternateUserSearchFilter) {
+ String filter = useAlternateUserSearchFilter ? alternateUserSearchFilter : userSearchFilter;
+
+ return resolveUserSearchFilterPlaceHolders(filter);
}
public String getUsernameAttribute() {
@@ -199,6 +207,15 @@ public class LdapServerProperties {
this.groupSearchFilter = groupSearchFilter;
}
+
+ public void setUserSearchFilter(String userSearchFilter) {
+ this.userSearchFilter = userSearchFilter;
+ }
+
+ public void setAlternateUserSearchFilter(String alternateUserSearchFilter) {
+ this.alternateUserSearchFilter = alternateUserSearchFilter;
+ }
+
public boolean isGroupMappingEnabled() {
return groupMappingEnabled;
}
@@ -288,6 +305,10 @@ public class LdapServerProperties {
if (paginationEnabled != that.isPaginationEnabled()) return false;
+ if (userSearchFilter != null ? !userSearchFilter.equals(that.userSearchFilter) : that.userSearchFilter != null) return false;
+ if (alternateUserSearchFilter != null ? !alternateUserSearchFilter.equals(that.alternateUserSearchFilter) : that.alternateUserSearchFilter != null) return false;
+
+
return true;
}
@@ -311,7 +332,20 @@ public class LdapServerProperties {
result = 31 * result + (groupSearchFilter != null ? groupSearchFilter.hashCode() : 0);
result = 31 * result + (dnAttribute != null ? dnAttribute.hashCode() : 0);
result = 31 * result + (referralMethod != null ? referralMethod.hashCode() : 0);
+ result = 31 * result + (userSearchFilter != null ? userSearchFilter.hashCode() : 0);
+ result = 31 * result + (alternateUserSearchFilter != null ? alternateUserSearchFilter.hashCode() : 0);
return result;
}
+ /**
+ * Resolves known placeholders found within the given ldap user search ldap filter
+ * @param filter
+ * @return returns the filter with the resolved placeholders.
+ */
+ protected String resolveUserSearchFilterPlaceHolders(String filter) {
+ return filter
+ .replace("{usernameAttribute}", usernameAttribute)
+ .replace("{userObjectClass}", userObjectClass);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
index 3bbc785..8b44b94 100644
--- a/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
+++ b/ambari-server/src/main/resources/webapp/WEB-INF/spring-security.xml
@@ -49,5 +49,6 @@
<beans:bean id="basicFilter" class="org.springframework.security.web.authentication.www.BasicAuthenticationFilter">
<beans:constructor-arg ref="authenticationManager"/>
+ <beans:constructor-arg ref="ambariEntryPoint"/>
</beans:bean>
</beans:beans>
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/api/UserNameOverrideFilterTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/UserNameOverrideFilterTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/UserNameOverrideFilterTest.java
new file mode 100644
index 0000000..1e0fe90
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/UserNameOverrideFilterTest.java
@@ -0,0 +1,196 @@
+/**
+ * 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.ambari.server.api;
+
+import java.net.URLEncoder;
+import java.util.regex.Matcher;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ambari.server.security.authorization.AuthorizationHelper;
+import org.easymock.Capture;
+import org.easymock.EasyMockRule;
+import org.easymock.EasyMockSupport;
+import org.easymock.Mock;
+import org.easymock.MockType;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.api.easymock.PowerMock;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.same;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(PowerMockRunner.class) // Allow mocking static methods
+@PrepareForTest(AuthorizationHelper.class) // This class has a static method that will be mocked
+public class UserNameOverrideFilterTest extends EasyMockSupport {
+
+ @Rule
+ public EasyMockRule mocks = new EasyMockRule(this);
+
+ @Mock(type = MockType.NICE)
+ private HttpServletRequest userRelatedRequest;
+
+ @Mock(type = MockType.NICE)
+ private ServletResponse response;
+
+ @Mock(type = MockType.NICE)
+ private FilterChain filterChain;
+
+ private UserNameOverrideFilter filter = new UserNameOverrideFilter();
+
+
+ @Test
+ public void testGetUserNameMatcherNoUserNameInUri() throws Exception {
+ // Given
+ String uri = "/aaa/bbb";
+
+ // When
+ Matcher m = filter.getUserNameMatcher(uri);
+ boolean isMatch = m.matches();
+
+ // Then
+ assertFalse(isMatch);
+ }
+
+ @Test
+ public void testGetUserNameMatcherNoPostInUri() throws Exception {
+ // Given
+ String uri = "/aaa/users/user1@domain";
+
+ // When
+ Matcher m = filter.getUserNameMatcher(uri);
+ boolean isMatch = m.find();
+
+ String pre = isMatch ? m.group("pre") : null;
+ String userName = isMatch ? m.group("username") : null;
+ String post = isMatch ? m.group("post") : null;
+
+
+ // Then
+ assertTrue(isMatch);
+
+ assertEquals("/aaa/users/", pre);
+ assertEquals("user1@domain", userName);
+ assertEquals("", post);
+ }
+
+
+
+ @Test
+ public void testGetUserNameMatcherPostInUri() throws Exception {
+ // Given
+ String uri = "/aaa/users/user1@domain/privileges";
+
+ // When
+ Matcher m = filter.getUserNameMatcher(uri);
+ boolean isMatch = m.find();
+
+ String pre = isMatch ? m.group("pre") : null;
+ String userName = isMatch ? m.group("username") : null;
+ String post = isMatch ? m.group("post") : null;
+
+
+ // Then
+ assertTrue(isMatch);
+
+ assertEquals("/aaa/users/", pre);
+ assertEquals("user1@domain", userName);
+ assertEquals("/privileges", post);
+ }
+
+ @Test
+ public void testDoFilterNoUserNameInUri() throws Exception {
+ // Given
+ expect(userRelatedRequest.getRequestURI()).andReturn("/test/test1").anyTimes();
+ filterChain.doFilter(same(userRelatedRequest), same(response));
+ expectLastCall();
+
+ replayAll();
+
+ // When
+ filter.doFilter(userRelatedRequest, response, filterChain);
+
+ // Then
+
+ verifyAll();
+ }
+
+ @Test
+ public void testDoFilterWithUserNameInUri() throws Exception {
+ // Given
+ expect(userRelatedRequest.getRequestURI()).andReturn("/test/users/testUserName/test1").anyTimes();
+
+ // filterChain should be invoked with the same req and resp as the OverrideUserName filter doesn't change these
+ filterChain.doFilter(same(userRelatedRequest), same(response));
+ expectLastCall();
+
+ replayAll();
+
+ // When
+ filter.doFilter(userRelatedRequest, response, filterChain);
+
+ // Then
+
+ verifyAll();
+ }
+
+ @Test
+ public void testDoFilterWithLoginAliasInUri() throws Exception {
+ // Given
+ expect(userRelatedRequest.getRequestURI()).andReturn(String.format("/test/users/%s/test1", URLEncoder.encode("testLoginAlias@testdomain.com", "UTF-8"))).anyTimes();
+
+ Capture<ServletRequest> requestCapture = Capture.newInstance();
+ filterChain.doFilter(capture(requestCapture), same(response));
+ expectLastCall();
+
+ PowerMock.mockStatic(AuthorizationHelper.class);
+ expect(AuthorizationHelper.resolveLoginAliasToUserName(eq("testLoginAlias@testdomain.com"))).andReturn("testuser1");
+
+ PowerMock.replay(AuthorizationHelper.class);
+ replayAll();
+
+ // When
+ filter.doFilter(userRelatedRequest, response, filterChain);
+
+ // Then
+ HttpServletRequest updatedRequest = (HttpServletRequest)requestCapture.getValue();
+ assertEquals("testLoginAlias@testdomain.com login alias in the request Uri should be resolved to testuser1 user name !", "/test/users/testuser1/test1", updatedRequest.getRequestURI());
+
+ PowerMock.verify(AuthorizationHelper.class);
+ verifyAll();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ resetAll();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java b/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java
index 3ecb5aa..99ec786 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/configuration/ConfigurationTest.java
@@ -658,4 +658,60 @@ public class ConfigurationTest {
Assert.assertEquals(actualCacheEnabledConfig, Configuration.SERVER_HRC_STATUS_SUMMARY_CACHE_ENABLED_DEFAULT);
}
+ @Test
+ public void testLdapUserSearchFilterDefault() throws Exception {
+ // Given
+ final Properties ambariProperties = new Properties();
+ final Configuration configuration = new Configuration(ambariProperties);
+
+ // When
+ String actualLdapUserSearchFilter = configuration.getLdapServerProperties().getUserSearchFilter(false);
+
+ // Then
+ Assert.assertEquals("(&(uid={0})(objectClass=person))", actualLdapUserSearchFilter);
+ }
+
+ @Test
+ public void testLdapUserSearchFilter() throws Exception {
+ // Given
+ final Properties ambariProperties = new Properties();
+ final Configuration configuration = new Configuration(ambariProperties);
+ ambariProperties.setProperty(Configuration.LDAP_USERNAME_ATTRIBUTE_KEY, "test_uid");
+ ambariProperties.setProperty(Configuration.LDAP_USER_SEARCH_FILTER_KEY, "{usernameAttribute}={0}");
+
+ // When
+ String actualLdapUserSearchFilter = configuration.getLdapServerProperties().getUserSearchFilter(false);
+
+ // Then
+ Assert.assertEquals("test_uid={0}", actualLdapUserSearchFilter);
+ }
+
+ @Test
+ public void testAlternateLdapUserSearchFilterDefault() throws Exception {
+ // Given
+ final Properties ambariProperties = new Properties();
+ final Configuration configuration = new Configuration(ambariProperties);
+
+ // When
+ String actualLdapUserSearchFilter = configuration.getLdapServerProperties().getUserSearchFilter(true);
+
+ // Then
+ Assert.assertEquals("(&(userPrincipalName={0})(objectClass=person))", actualLdapUserSearchFilter);
+ }
+
+ @Test
+ public void testAlternatLdapUserSearchFilter() throws Exception {
+ // Given
+ final Properties ambariProperties = new Properties();
+ final Configuration configuration = new Configuration(ambariProperties);
+ ambariProperties.setProperty(Configuration.LDAP_USERNAME_ATTRIBUTE_KEY, "test_uid");
+ ambariProperties.setProperty(Configuration.LDAP_ALT_USER_SEARCH_FILTER_KEY, "{usernameAttribute}={5}");
+
+ // When
+ String actualLdapUserSearchFilter = configuration.getLdapServerProperties().getUserSearchFilter(true);
+
+ // Then
+ Assert.assertEquals("test_uid={5}", actualLdapUserSearchFilter);
+ }
+
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/security/AmbariLdapUtilsTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/AmbariLdapUtilsTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/AmbariLdapUtilsTest.java
new file mode 100644
index 0000000..b2778ae
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/AmbariLdapUtilsTest.java
@@ -0,0 +1,87 @@
+/**
+ * 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.ambari.server.security;
+
+import org.apache.ambari.server.security.authorization.AmbariLdapUtils;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+public class AmbariLdapUtilsTest {
+
+ @Test
+ public void testIsUserPrincipalNameFormat_True() throws Exception {
+ // Given
+ String testLoginName = "testuser@domain1.d_1.com";
+
+ // When
+ boolean isUserPrincipalNameFormat = AmbariLdapUtils.isUserPrincipalNameFormat(testLoginName);
+
+ // Then
+ assertTrue(isUserPrincipalNameFormat);
+ }
+
+ @Test
+ public void testIsUserPrincipalNameFormatMultipleAtSign_True() throws Exception {
+ // Given
+ String testLoginName = "@testuser@domain1.d_1.com";
+
+ // When
+ boolean isUserPrincipalNameFormat = AmbariLdapUtils.isUserPrincipalNameFormat(testLoginName);
+
+ // Then
+ assertTrue(isUserPrincipalNameFormat);
+ }
+
+ @Test
+ public void testIsUserPrincipalNameFormat_False() throws Exception {
+ // Given
+ String testLoginName = "testuser";
+
+ // When
+ boolean isUserPrincipalNameFormat = AmbariLdapUtils.isUserPrincipalNameFormat(testLoginName);
+
+ // Then
+ assertFalse(isUserPrincipalNameFormat);
+ }
+
+ @Test
+ public void testIsUserPrincipalNameFormatWithAtSign_False() throws Exception {
+ // Given
+ String testLoginName = "@testuser";
+
+ // When
+ boolean isUserPrincipalNameFormat = AmbariLdapUtils.isUserPrincipalNameFormat(testLoginName);
+
+ // Then
+ assertFalse(isUserPrincipalNameFormat);
+ }
+
+ @Test
+ public void testIsUserPrincipalNameFormatWithAtSign1_False() throws Exception {
+ // Given
+ String testLoginName = "testuser@";
+
+ // When
+ boolean isUserPrincipalNameFormat = AmbariLdapUtils.isUserPrincipalNameFormat(testLoginName);
+
+ // Then
+ assertFalse(isUserPrincipalNameFormat);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariAuthenticationTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariAuthenticationTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariAuthenticationTest.java
new file mode 100644
index 0000000..19656b1
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariAuthenticationTest.java
@@ -0,0 +1,333 @@
+/**
+ * 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.ambari.server.security.authorization;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.easymock.EasyMockRule;
+import org.easymock.EasyMockSupport;
+import org.easymock.Mock;
+import org.easymock.MockType;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertSame;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.verify;
+
+public class AmbariAuthenticationTest extends EasyMockSupport {
+
+ @Rule
+ public EasyMockRule mocks = new EasyMockRule(this);
+
+ @Mock(type = MockType.NICE)
+ private ServletRequestAttributes servletRequestAttributes;
+
+ @Mock(type = MockType.NICE)
+ private Authentication testAuthentication;
+
+ @Before
+ public void setUp() {
+ resetAll();
+
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ }
+
+ @Test
+ public void testGetPrincipalNoOverride() throws Exception {
+ // Given
+ Principal origPrincipal = new Principal() {
+ @Override
+ public String getName() {
+ return "user";
+ }
+ };
+
+ Authentication authentication = new TestingAuthenticationToken(origPrincipal, "password");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object principal = ambariAuthentication.getPrincipal();
+
+ // Then
+ assertSame(origPrincipal, principal);
+ }
+
+
+ @Test
+ public void testGetPrincipal() throws Exception {
+ // Given
+ Authentication authentication = new TestingAuthenticationToken("user", "password");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object principal = ambariAuthentication.getPrincipal();
+
+ // Then
+ assertEquals("user", principal);
+ }
+
+ @Test
+ public void testGetPrincipalWithLoginAlias() throws Exception {
+ // Given
+ Authentication authentication = new TestingAuthenticationToken("loginAlias", "password");
+ expect(servletRequestAttributes.getAttribute(eq("loginAlias"), eq(RequestAttributes.SCOPE_SESSION)))
+ .andReturn("user").atLeastOnce();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ verifyAll();
+ Object principal = ambariAuthentication.getPrincipal();
+
+ // Then
+ assertEquals("user", principal);
+ }
+
+ @Test
+ public void testGetUserDetailPrincipal() throws Exception {
+ // Given
+ UserDetails userDetails = new User("user", "password", Collections.<GrantedAuthority>emptyList());
+ Authentication authentication = new TestingAuthenticationToken(userDetails, userDetails.getPassword());
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object principal = ambariAuthentication.getPrincipal();
+
+ // Then
+ assertEquals(userDetails, principal);
+ }
+
+ @Test
+ public void testGetUserDetailPrincipalWithLoginAlias() throws Exception {
+ // Given
+ UserDetails userDetails = new User("loginAlias", "password", Collections.<GrantedAuthority>emptyList());
+ Authentication authentication = new TestingAuthenticationToken(userDetails, userDetails.getPassword());
+
+ expect(servletRequestAttributes.getAttribute(eq("loginAlias"), eq(RequestAttributes.SCOPE_SESSION)))
+ .andReturn("user").atLeastOnce();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object principal = ambariAuthentication.getPrincipal();
+
+ // Then
+ verify();
+ UserDetails expectedUserDetails = new User("user", "password", Collections.<GrantedAuthority>emptyList()); // user detail with login alias resolved
+
+ assertEquals(expectedUserDetails, principal);
+ }
+
+
+
+ @Test
+ public void testGetNameNoOverride () throws Exception {
+ // Given
+ Principal origPrincipal = new Principal() {
+ @Override
+ public String getName() {
+ return "user1";
+ }
+ };
+ Authentication authentication = new TestingAuthenticationToken(origPrincipal, "password");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ String name = ambariAuthentication.getName();
+
+ // Then
+ assertEquals("user1", name);
+ }
+
+ @Test
+ public void testGetName() throws Exception {
+ // Given
+ Authentication authentication = new TestingAuthenticationToken("user", "password");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ String name = ambariAuthentication.getName();
+
+ // Then
+ assertEquals("user", name);
+ }
+
+ @Test
+ public void testGetNameWithLoginAlias() throws Exception {
+ // Given
+ Authentication authentication = new TestingAuthenticationToken("loginAlias", "password");
+ expect(servletRequestAttributes.getAttribute(eq("loginAlias"), eq(RequestAttributes.SCOPE_SESSION)))
+ .andReturn("user").atLeastOnce();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ String name = ambariAuthentication.getName();
+
+ // Then
+ verifyAll();
+ assertEquals("user", name);
+ }
+
+ @Test
+ public void testGetNameWithUserDetailsPrincipal() throws Exception {
+ // Given
+ UserDetails userDetails = new User("user", "password", Collections.<GrantedAuthority>emptyList());
+ Authentication authentication = new TestingAuthenticationToken(userDetails, userDetails.getPassword());
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ String name = ambariAuthentication.getName();
+
+ // Then
+ assertEquals("user", name);
+ }
+
+ @Test
+ public void testGetNameWithUserDetailsPrincipalWithLoginAlias() throws Exception {
+ // Given
+ UserDetails userDetails = new User("loginAlias", "password", Collections.<GrantedAuthority>emptyList());
+ Authentication authentication = new TestingAuthenticationToken(userDetails, userDetails.getPassword());
+
+ expect(servletRequestAttributes.getAttribute(eq("loginAlias"), eq(RequestAttributes.SCOPE_SESSION)))
+ .andReturn("user").atLeastOnce();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ String name = ambariAuthentication.getName();
+
+ // Then
+ verifyAll();
+ assertEquals("user", name);
+ }
+
+ @Test
+ public void testGetAuthorities() throws Exception {
+ // Given
+ Authentication authentication = new TestingAuthenticationToken("user", "password", "test_role");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Collection<?> grantedAuthorities = ambariAuthentication.getAuthorities();
+
+ // Then
+ Collection<?> expectedAuthorities = authentication.getAuthorities();
+
+ assertSame(expectedAuthorities, grantedAuthorities);
+ }
+
+ @Test
+ public void testGetCredentials() throws Exception {
+ // Given
+ String passord = "password";
+ Authentication authentication = new TestingAuthenticationToken("user", passord);
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object credentials = ambariAuthentication.getCredentials();
+
+ // Then
+ assertSame(passord, credentials);
+ }
+
+ @Test
+ public void testGetDetails() throws Exception {
+ // Given
+ TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
+ authentication.setDetails("test auth details");
+ Authentication ambariAuthentication = new AmbariAuthentication(authentication);
+
+ // When
+ Object authDetails = ambariAuthentication.getDetails();
+
+ // Then
+ Object expecteAuthDetails = authentication.getDetails();
+
+ assertSame(expecteAuthDetails, authDetails);
+ }
+
+ @Test
+ public void testIsAuthenticated() throws Exception {
+ // Given
+ expect(testAuthentication.isAuthenticated()).andReturn(false).once();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(testAuthentication);
+
+ // When
+ ambariAuthentication.isAuthenticated();
+
+ // Then
+ verifyAll();
+ }
+
+ @Test
+ public void setTestAuthentication() throws Exception {
+ // Given
+ testAuthentication.setAuthenticated(true);
+ expectLastCall().once();
+
+ replayAll();
+
+ Authentication ambariAuthentication = new AmbariAuthentication(testAuthentication);
+
+ // When
+ ambariAuthentication.setAuthenticated(true);
+
+ // Then
+ verifyAll();
+ }
+
+ @Test
+ public void testEquals() throws Exception {
+ EqualsVerifier.forClass(AmbariAuthentication.class)
+ .verify();
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderForDuplicateUserTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderForDuplicateUserTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderForDuplicateUserTest.java
new file mode 100644
index 0000000..f5d1412
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderForDuplicateUserTest.java
@@ -0,0 +1,100 @@
+/**
+ * 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.ambari.server.security.authorization;
+
+import java.util.Properties;
+
+import org.apache.ambari.server.configuration.Configuration;
+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.ContextEntry;
+import org.apache.directory.server.core.annotations.CreateDS;
+import org.apache.directory.server.core.annotations.CreatePartition;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+import org.easymock.EasyMockRule;
+import org.easymock.Mock;
+import org.easymock.MockType;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+
+import com.google.inject.Inject;
+
+@RunWith(FrameworkRunner.class)
+@CreateDS(allowAnonAccess = true,
+ name = "Test",
+ partitions = {
+ @CreatePartition(name = "Root",
+ suffix = "dc=apache,dc=org",
+ contextEntry = @ContextEntry(
+ entryLdif =
+ "dn: dc=apache,dc=org\n" +
+ "dc: apache\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n" +
+ "dn: dc=ambari,dc=apache,dc=org\n" +
+ "dc: ambari\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n"))
+ })
+@CreateLdapServer(allowAnonymousAccess = true,
+ transports = {@CreateTransport(protocol = "LDAP", port = 33389)})
+@ApplyLdifFiles("users_with_duplicate_uid.ldif")
+public class AmbariLdapAuthenticationProviderForDuplicateUserTest extends AmbariLdapAuthenticationProviderBaseTest {
+
+ @Rule
+ public EasyMockRule mocks = new EasyMockRule(this);
+
+ @Mock(type = MockType.NICE)
+ private AmbariLdapAuthoritiesPopulator authoritiesPopulator;
+
+ private AmbariLdapAuthenticationProvider authenticationProvider;
+
+ @Before
+ public void setUp() {
+ Properties properties = new Properties();
+ properties.setProperty(Configuration.CLIENT_SECURITY_KEY, "ldap");
+ properties.setProperty(Configuration.SERVER_PERSISTENCE_TYPE_KEY, "in-memory");
+ properties.setProperty(Configuration.METADATA_DIR_PATH,"src/test/resources/stacks");
+ properties.setProperty(Configuration.SERVER_VERSION_FILE,"src/test/resources/version");
+ properties.setProperty(Configuration.OS_VERSION_KEY,"centos5");
+ properties.setProperty(Configuration.SHARED_RESOURCES_DIR_KEY, "src/test/resources/");
+ properties.setProperty(Configuration.LDAP_BASE_DN_KEY, "dc=apache,dc=org");
+
+ Configuration configuration = new Configuration(properties);
+
+ authenticationProvider = new AmbariLdapAuthenticationProvider(configuration, authoritiesPopulator);
+ }
+
+ @Test(expected = DuplicateLdapUserFoundAuthenticationException.class)
+ public void testAuthenticateDuplicateUser() throws Exception {
+ // Given
+ Authentication authentication = new UsernamePasswordAuthenticationToken("user_dup", "password");
+
+ // When
+ authenticationProvider.authenticate(authentication);
+
+ // Then
+ // DuplicateLdapUserFoundAuthenticationException should be thrown
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderTest.java
index d48be85..b26494c 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapAuthenticationProviderTest.java
@@ -49,6 +49,8 @@ import org.slf4j.Logger;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.ldap.authentication.LdapAuthenticationProvider;
+
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
@@ -90,6 +92,7 @@ public class AmbariLdapAuthenticationProviderTest extends AmbariLdapAuthenticati
injector.injectMembers(this);
injector.getInstance(GuiceJpaInitializer.class);
configuration.setClientSecurityType(ClientSecurityType.LDAP);
+ configuration.setProperty(Configuration.LDAP_ALT_USER_SEARCH_FILTER_KEY, "(&(mail={0})(objectClass={userObjectClass}))");
}
@After
@@ -116,7 +119,7 @@ public class AmbariLdapAuthenticationProviderTest extends AmbariLdapAuthenticati
expect(exception.getCause()).andReturn(exception).atLeastOnce();
expect(provider.isLdapEnabled()).andReturn(true);
- expect(provider.loadLdapAuthenticationProvider()).andThrow(exception);
+ expect(provider.loadLdapAuthenticationProvider("notFound")).andThrow(exception);
// Logging call
Logger log = createNiceMock(Logger.class);
provider.LOG = log;
@@ -155,7 +158,7 @@ public class AmbariLdapAuthenticationProviderTest extends AmbariLdapAuthenticati
expect(exception.getCause()).andReturn(cause).atLeastOnce();
expect(provider.isLdapEnabled()).andReturn(true);
- expect(provider.loadLdapAuthenticationProvider()).andThrow(exception);
+ expect(provider.loadLdapAuthenticationProvider("notFound")).andThrow(exception);
// Logging call
Logger log = createNiceMock(Logger.class);
provider.LOG = log;
@@ -189,4 +192,47 @@ public class AmbariLdapAuthenticationProviderTest extends AmbariLdapAuthenticati
Authentication auth = authenticationProvider.authenticate(authentication);
Assert.assertTrue(auth == null);
}
+
+ @Test
+ public void testAuthenticateLoginAlias() throws Exception {
+ // Given
+ assertNull("User alread exists in DB", userDAO.findLdapUserByName("allowedUser"));
+ Authentication authentication = new UsernamePasswordAuthenticationToken("allowedUser@ambari.apache.org", "password");
+
+
+ // When
+ Authentication result = authenticationProvider.authenticate(authentication);
+
+ // Then
+ assertTrue(result.isAuthenticated());
+ }
+
+ @Test(expected = BadCredentialsException.class)
+ public void testBadCredentialsForMissingLoginAlias() throws Exception {
+ // Given
+ assertNull("User alread exists in DB", userDAO.findLdapUserByName("allowedUser"));
+ Authentication authentication = new UsernamePasswordAuthenticationToken("missingloginalias@ambari.apache.org", "password");
+
+
+ // When
+ authenticationProvider.authenticate(authentication);
+
+ // Then
+ // BadCredentialsException should be thrown due to no user with 'missingloginalias@ambari.apache.org' is found in ldap
+ }
+
+
+ @Test(expected = BadCredentialsException.class)
+ public void testBadCredentialsBadPasswordForLoginAlias() throws Exception {
+ // Given
+ assertNull("User alread exists in DB", userDAO.findLdapUserByName("allowedUser"));
+ Authentication authentication = new UsernamePasswordAuthenticationToken("allowedUser@ambari.apache.org", "bad_password");
+
+
+ // When
+ authenticationProvider.authenticate(authentication);
+
+ // Then
+ // BadCredentialsException should be thrown due to wrong password
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/71b4c624/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
new file mode 100644
index 0000000..27e62e2
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/authorization/AmbariLdapBindAuthenticatorTest.java
@@ -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.
+ */
+package org.apache.ambari.server.security.authorization;
+
+import java.util.Properties;
+
+import org.apache.ambari.server.configuration.Configuration;
+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.ContextEntry;
+import org.apache.directory.server.core.annotations.CreateDS;
+import org.apache.directory.server.core.annotations.CreatePartition;
+import org.apache.directory.server.core.integ.FrameworkRunner;
+import org.easymock.EasyMockRule;
+import org.easymock.Mock;
+import org.easymock.MockType;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.ldap.core.DirContextOperations;
+import org.springframework.ldap.core.support.LdapContextSource;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
+import org.springframework.security.ldap.search.LdapUserSearch;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.junit.Assert.assertEquals;
+
+
+@RunWith(FrameworkRunner.class)
+@CreateDS(allowAnonAccess = true,
+ name = "Test",
+ partitions = {
+ @CreatePartition(name = "Root",
+ suffix = "dc=apache,dc=org",
+ contextEntry = @ContextEntry(
+ entryLdif =
+ "dn: dc=apache,dc=org\n" +
+ "dc: apache\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n" +
+ "dn: dc=ambari,dc=apache,dc=org\n" +
+ "dc: ambari\n" +
+ "objectClass: top\n" +
+ "objectClass: domain\n\n"))
+ })
+@CreateLdapServer(allowAnonymousAccess = true,
+ transports = {@CreateTransport(protocol = "LDAP", port = 33389)})
+@ApplyLdifFiles("users.ldif")
+public class AmbariLdapBindAuthenticatorTest extends AmbariLdapAuthenticationProviderBaseTest {
+
+ @Rule
+ public EasyMockRule mocks = new EasyMockRule(this);
+
+ @Mock(type = MockType.NICE)
+ private ServletRequestAttributes servletRequestAttributes;
+
+ @Before
+ public void setUp() {
+ resetAll();
+ }
+
+ @Test
+ public void testAuthenticateWithLoginAlias() throws Exception {
+ // Given
+
+ LdapContextSource ldapCtxSource = new LdapContextSource();
+ ldapCtxSource.setUrls(new String[] {"ldap://localhost:33389"});
+ ldapCtxSource.setBase("dc=ambari,dc=apache,dc=org");
+ ldapCtxSource.afterPropertiesSet();
+
+ Properties properties = new Properties();
+ properties.setProperty(Configuration.CLIENT_SECURITY_KEY, "ldap");
+ properties.setProperty(Configuration.SERVER_PERSISTENCE_TYPE_KEY, "in-memory");
+ properties.setProperty(Configuration.METADATA_DIR_PATH,"src/test/resources/stacks");
+ properties.setProperty(Configuration.SERVER_VERSION_FILE,"src/test/resources/version");
+ properties.setProperty(Configuration.OS_VERSION_KEY,"centos5");
+ properties.setProperty(Configuration.SHARED_RESOURCES_DIR_KEY, "src/test/resources/");
+ properties.setProperty(Configuration.LDAP_BASE_DN_KEY, "dc=ambari,dc=apache,dc=org");
+
+ Configuration configuration = new Configuration(properties);
+
+ AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, configuration);
+
+ LdapUserSearch userSearch = new FilterBasedLdapUserSearch("", "(&(cn={0})(objectClass=person))", ldapCtxSource);
+ bindAuthenticator.setUserSearch(userSearch);
+
+ // JohnSmith is a login alias for deniedUser username
+ String loginAlias = "JohnSmith";
+ String userName = "deniedUser";
+
+ Authentication authentication = new UsernamePasswordAuthenticationToken(loginAlias, "password");
+
+ RequestContextHolder.setRequestAttributes(servletRequestAttributes);
+
+ servletRequestAttributes.setAttribute(eq(loginAlias), eq(userName), eq(RequestAttributes.SCOPE_SESSION));
+ expectLastCall().once();
+
+ replayAll();
+
+ // When
+
+ DirContextOperations user = bindAuthenticator.authenticate(authentication);
+
+ // Then
+
+ verifyAll();
+
+ String ldapUserNameAttribute = configuration.getLdapServerProperties().getUsernameAttribute();
+
+ assertEquals(userName, user.getStringAttribute(ldapUserNameAttribute));
+ }
+}