content: /*
   * 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 static java.lang.Boolean.parseBoolean;
  import static org.easymock.EasyMock.anyObject;
  import static org.easymock.EasyMock.anyString;
  import static org.easymock.EasyMock.eq;
  import static org.easymock.EasyMock.expect;
  import static org.easymock.EasyMock.expectLastCall;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.fail;
  
  import javax.naming.NamingEnumeration;
  import javax.naming.directory.Attributes;
  import javax.naming.directory.BasicAttributes;
  import javax.naming.directory.SearchControls;
  import javax.naming.directory.SearchResult;
  import javax.naming.ldap.LdapName;
  
  import org.apache.ambari.server.configuration.AmbariServerConfigurationKey;
  import org.apache.ambari.server.ldap.domain.AmbariLdapConfiguration;
  import org.apache.commons.lang.StringUtils;
  import org.easymock.EasyMockSupport;
  import org.junit.Before;
  import org.junit.Test;
  import org.springframework.ldap.core.DirContextOperations;
  import org.springframework.ldap.core.support.LdapContextSource;
  import org.springframework.ldap.support.LdapUtils;
  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  import org.springframework.security.ldap.search.FilterBasedLdapUserSearch;
  import org.springframework.web.context.request.RequestAttributes;
  import org.springframework.web.context.request.RequestContextHolder;
  import org.springframework.web.context.request.ServletRequestAttributes;
  
  import com.google.inject.AbstractModule;
  import com.google.inject.Guice;
  import com.google.inject.Injector;
  
  public class AmbariLdapBindAuthenticatorTest extends EasyMockSupport {
    
    private Injector injector;
    private AmbariLdapConfiguration ldapConfiguration;
    
    @Before
    public void init() throws Exception {
      injector = createInjector();
      ldapConfiguration = injector.getInstance(AmbariLdapConfiguration.class);
    }
  
    @Test
    public void testAuthenticateWithoutLogin() throws Exception {
      testAuthenticate("username", "username", false);
    }
  
    @Test
    public void testAuthenticateWithNullLDAPUsername() throws Exception {
      testAuthenticate("username", null, false);
    }
  
    @Test
    public void testAuthenticateWithLoginAliasDefault() throws Exception {
      testAuthenticate("username", "ldapUsername", false);
    }
  
    @Test
    public void testAuthenticateWithLoginAliasForceToLower() throws Exception {
      testAuthenticate("username", "ldapUsername", true);
    }
  
    @Test
    public void testAuthenticateBadPassword() throws Exception {
      String basePathString = "dc=apache,dc=org";
      String ldapUserRelativeDNString = String.format("uid=%s,ou=people,ou=dev", "ldapUsername");
      LdapName ldapUserRelativeDN = new LdapName(ldapUserRelativeDNString);
      String ldapUserDNString = String.format("%s,%s", ldapUserRelativeDNString, basePathString);
      LdapName basePath = LdapUtils.newLdapName(basePathString);
  
      LdapContextSource ldapCtxSource = createMock(LdapContextSource.class);
      expect(ldapCtxSource.getBaseLdapName())
          .andReturn(basePath)
          .atLeastOnce();
      expect(ldapCtxSource.getContext(ldapUserDNString, "password"))
          .andThrow(new org.springframework.ldap.AuthenticationException(null))
          .once();
  
      DirContextOperations searchedUserContext = createMock(DirContextOperations.class);
      expect(searchedUserContext.getDn())
          .andReturn(ldapUserRelativeDN)
          .atLeastOnce();
  
      FilterBasedLdapUserSearch userSearch = createMock(FilterBasedLdapUserSearch.class);
      expect(userSearch.searchForUser(anyString())).andReturn(searchedUserContext).once();
  
      setupDatabaseConfigurationExpectations(false, false);
  
      replayAll();
  
  
      AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, ldapConfiguration);
      bindAuthenticator.setUserSearch(userSearch);
  
      try {
        bindAuthenticator.authenticate(new UsernamePasswordAuthenticationToken("username", "password"));
        fail("Expected thrown exception: org.springframework.security.authentication.BadCredentialsException");
      } catch (org.springframework.security.authentication.BadCredentialsException e) {
        // expected
      } catch (Throwable t) {
        fail("Expected thrown exception: org.springframework.security.authentication.BadCredentialsException\nEncountered thrown exception " + t.getClass().getName());
      }
  
      verifyAll();
    }
  
    private void testAuthenticate(String ambariUsername, String ldapUsername, boolean forceUsernameToLower) throws Exception {
      String basePathString = "dc=apache,dc=org";
      String ldapUserRelativeDNString = String.format("uid=%s,ou=people,ou=dev", ldapUsername);
      LdapName ldapUserRelativeDN = new LdapName(ldapUserRelativeDNString);
      String ldapUserDNString = String.format("%s,%s", ldapUserRelativeDNString, basePathString);
      LdapName basePath = LdapUtils.newLdapName(basePathString);
  
      @SuppressWarnings("unchecked")
      NamingEnumeration<SearchResult> adminGroups = createMock(NamingEnumeration.class);
      expect(adminGroups.hasMore())
          .andReturn(false)
          .atLeastOnce();
      adminGroups.close();
      expectLastCall().atLeastOnce();
  
      DirContextOperations boundUserContext = createMock(DirContextOperations.class);
      System.out.println(ldapUserDNString);
      expect(boundUserContext.search(eq("ou=groups"), eq("(&(member=" + ldapUserDNString + ")(objectclass=group)(|(cn=Ambari Administrators)))"), anyObject(SearchControls.class)))
          .andReturn(adminGroups)
          .atLeastOnce();
      boundUserContext.close();
      expectLastCall().atLeastOnce();
      
      LdapContextSource ldapCtxSource = createMock(LdapContextSource.class);
      expect(ldapCtxSource.getBaseLdapName())
          .andReturn(basePath)
          .atLeastOnce();
      expect(ldapCtxSource.getContext(ldapUserDNString, "password"))
          .andReturn(boundUserContext)
          .once();
      expect(ldapCtxSource.getReadOnlyContext())
          .andReturn(boundUserContext)
          .once();
  
      Attributes searchedAttributes = new BasicAttributes("uid", ldapUsername);
  
      DirContextOperations searchedUserContext = createMock(DirContextOperations.class);
      expect(searchedUserContext.getDn())
          .andReturn(ldapUserRelativeDN)
          .atLeastOnce();
      expect(searchedUserContext.getAttributes())
          .andReturn(searchedAttributes)
          .atLeastOnce();
  
      FilterBasedLdapUserSearch userSearch = createMock(FilterBasedLdapUserSearch.class);
      expect(userSearch.searchForUser(ambariUsername)).andReturn(searchedUserContext).once();
  
      ServletRequestAttributes servletRequestAttributes = createMock(ServletRequestAttributes.class);
  
      if (!StringUtils.isEmpty(ldapUsername) && !ambariUsername.equals(ldapUsername)) {
        servletRequestAttributes.setAttribute(eq(ambariUsername), eq(forceUsernameToLower ? ldapUsername.toLowerCase() : ldapUsername), eq(RequestAttributes.SCOPE_SESSION));
        expectLastCall().once();
        servletRequestAttributes.setAttribute(eq(forceUsernameToLower ? ldapUsername.toLowerCase() : ldapUsername),eq(ambariUsername), eq(RequestAttributes.SCOPE_SESSION));
        expectLastCall().once();
      }
  
      setupDatabaseConfigurationExpectations(true, forceUsernameToLower);
  
      replayAll();
  
      RequestContextHolder.setRequestAttributes(servletRequestAttributes);
  //    servletRequestAttributes.setAttribute(ambariUsername,ldapUsername, RequestAttributes.SCOPE_SESSION);
  //    expectLastCall().anyTimes();
  
      AmbariLdapBindAuthenticator bindAuthenticator = new AmbariLdapBindAuthenticator(ldapCtxSource, ldapConfiguration);
      bindAuthenticator.setUserSearch(userSearch);
      DirContextOperations user = bindAuthenticator.authenticate(new UsernamePasswordAuthenticationToken(ambariUsername, "password"));
  
      verifyAll();
  
      String ldapUserNameAttribute = ldapConfiguration.getLdapServerProperties().getUsernameAttribute();
      assertEquals(ldapUsername, user.getStringAttribute(ldapUserNameAttribute));
    }
    
    private Injector createInjector() throws Exception {
      return Guice.createInjector(new AbstractModule() {
  
        @Override
        protected void configure() {
          bind(AmbariLdapConfiguration.class).toInstance(createNiceMock(AmbariLdapConfiguration.class));
        }
      });
    }
    
    private void setupDatabaseConfigurationExpectations(boolean expectedDatabaseConfigCall, boolean forceUsernameToLowerCase) {
      final LdapServerProperties ldapServerProperties = getDefaultLdapServerProperties(forceUsernameToLowerCase);
      ldapServerProperties.setGroupObjectClass("group");
      if (expectedDatabaseConfigCall) {
        expect(ldapConfiguration.getLdapServerProperties()).andReturn(ldapServerProperties).anyTimes();
      }
    }
    
    private static LdapServerProperties getDefaultLdapServerProperties(boolean forceUsernameToLowerCase) {
      final LdapServerProperties ldapServerProperties = new LdapServerProperties();
      ldapServerProperties.setPrimaryUrl(AmbariServerConfigurationKey.SERVER_HOST.getDefaultValue() + ":" + AmbariServerConfigurationKey.SERVER_PORT.getDefaultValue());
      ldapServerProperties.setSecondaryUrl(AmbariServerConfigurationKey.SECONDARY_SERVER_HOST.getDefaultValue() + ":" + AmbariServerConfigurationKey.SECONDARY_SERVER_PORT.getDefaultValue());
      ldapServerProperties.setUseSsl(parseBoolean(AmbariServerConfigurationKey.USE_SSL.getDefaultValue()));
      ldapServerProperties.setAnonymousBind(parseBoolean(AmbariServerConfigurationKey.ANONYMOUS_BIND.getDefaultValue()));
      ldapServerProperties.setManagerDn(AmbariServerConfigurationKey.BIND_DN.getDefaultValue());
      ldapServerProperties.setManagerPassword(AmbariServerConfigurationKey.BIND_PASSWORD.getDefaultValue());
      ldapServerProperties.setBaseDN(AmbariServerConfigurationKey.USER_SEARCH_BASE.getDefaultValue());
      ldapServerProperties.setUsernameAttribute(AmbariServerConfigurationKey.USER_NAME_ATTRIBUTE.getDefaultValue());
      ldapServerProperties.setForceUsernameToLowercase(forceUsernameToLowerCase);
      ldapServerProperties.setUserBase(AmbariServerConfigurationKey.USER_BASE.getDefaultValue());
      ldapServerProperties.setUserObjectClass(AmbariServerConfigurationKey.USER_OBJECT_CLASS.getDefaultValue());
      ldapServerProperties.setDnAttribute(AmbariServerConfigurationKey.DN_ATTRIBUTE.getDefaultValue());
      ldapServerProperties.setGroupBase(AmbariServerConfigurationKey.GROUP_BASE.getDefaultValue());
      ldapServerProperties.setGroupObjectClass(AmbariServerConfigurationKey.GROUP_OBJECT_CLASS.getDefaultValue());
      ldapServerProperties.setGroupMembershipAttr(AmbariServerConfigurationKey.GROUP_MEMBER_ATTRIBUTE.getDefaultValue());
      ldapServerProperties.setGroupNamingAttr(AmbariServerConfigurationKey.GROUP_NAME_ATTRIBUTE.getDefaultValue());
      ldapServerProperties.setAdminGroupMappingRules(AmbariServerConfigurationKey.GROUP_MAPPING_RULES.getDefaultValue());
      ldapServerProperties.setAdminGroupMappingMemberAttr("");
      ldapServerProperties.setUserSearchFilter(AmbariServerConfigurationKey.USER_SEARCH_FILTER.getDefaultValue());
      ldapServerProperties.setAlternateUserSearchFilter(AmbariServerConfigurationKey.ALTERNATE_USER_SEARCH_FILTER.getDefaultValue());
      ldapServerProperties.setGroupSearchFilter(AmbariServerConfigurationKey.GROUP_SEARCH_FILTER.getDefaultValue());
      ldapServerProperties.setReferralMethod(AmbariServerConfigurationKey.REFERRAL_HANDLING.getDefaultValue());
      ldapServerProperties.setSyncUserMemberReplacePattern(AmbariServerConfigurationKey.USER_MEMBER_REPLACE_PATTERN.getDefaultValue());
      ldapServerProperties.setSyncGroupMemberReplacePattern(AmbariServerConfigurationKey.GROUP_MEMBER_REPLACE_PATTERN.getDefaultValue());
      ldapServerProperties.setSyncUserMemberFilter(AmbariServerConfigurationKey.USER_MEMBER_FILTER.getDefaultValue());
      ldapServerProperties.setSyncGroupMemberFilter(AmbariServerConfigurationKey.GROUP_MEMBER_FILTER.getDefaultValue());
      ldapServerProperties.setPaginationEnabled(parseBoolean(AmbariServerConfigurationKey.PAGINATION_ENABLED.getDefaultValue()));
      return ldapServerProperties;
    }
  }
  