/*
* ******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
* ******************************************************************************
*/
package org.cloudfoundry.identity.uaa.authentication.manager;
import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.LdapIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.provider.ldap.extension.ExtendedLdapUserImpl;
import org.cloudfoundry.identity.uaa.provider.ldap.extension.LdapAuthority;
import org.cloudfoundry.identity.uaa.user.UaaAuthority;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
import org.cloudfoundry.identity.uaa.user.UaaUserPrototype;
import org.cloudfoundry.identity.uaa.user.UserInfo;
import org.cloudfoundry.identity.uaa.zone.IdentityZoneHolder;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Matchers;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.ldap.userdetails.LdapUserDetails;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static java.util.Collections.EMPTY_LIST;
import static org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition.USER_ATTRIBUTE_PREFIX;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class LdapLoginAuthenticationManagerTests {
public static final String DN = "cn=marissa,ou=Users,dc=test,dc=com";
public static final String LDAP_EMAIL = "test@ldap.org";
public static final String TEST_EMAIL = "email@email.org";
public static final String USERNAME = "username";
private static final String EMAIL_ATTRIBUTE = "email";
private final String GIVEN_NAME_ATTRIBUTE = "firstname";
private final String FAMILY_NAME_ATTRIBUTE = "surname";
private final String PHONE_NUMBER_ATTTRIBUTE = "digits";
private static LdapUserDetails userDetails;
final String DENVER_CO = "Denver,CO";
final String COST_CENTER = "costCenter";
final String COST_CENTERS = "costCenters";
final String JOHN_THE_SLOTH = "John the Sloth";
final String KARI_THE_ANT_EATER = "Kari the Ant Eater";
final String UAA_MANAGER = "uaaManager";
final String MANAGERS = "managers";
private static LdapUserDetails mockLdapUserDetails() {
userDetails = mock(LdapUserDetails.class);
setupGeneralExpectations(userDetails);
when(userDetails.getDn()).thenReturn(DN);
return userDetails;
}
private static UserDetails mockNonLdapUserDetails() {
UserDetails userDetails = mock(UserDetails.class);
setupGeneralExpectations(userDetails);
return userDetails;
}
private static void setupGeneralExpectations(UserDetails userDetails) {
when(userDetails.getUsername()).thenReturn(USERNAME);
when(userDetails.getPassword()).thenReturn("koala");
when(userDetails.getAuthorities()).thenReturn(null);
when(userDetails.isAccountNonExpired()).thenReturn(true);
when(userDetails.isAccountNonLocked()).thenReturn(true);
when(userDetails.isCredentialsNonExpired()).thenReturn(true);
when(userDetails.isEnabled()).thenReturn(true);
}
LdapLoginAuthenticationManager am;
ApplicationEventPublisher publisher;
String origin = "test";
Map<String, String[]> info = new HashMap<>();
UaaUser dbUser = getUaaUser();
Authentication auth;
ExtendedLdapUserImpl authUserDetail;
IdentityProviderProvisioning provisioning;
IdentityProvider provider;
LdapIdentityProviderDefinition definition;
@Before
public void setUp() {
provisioning = mock(IdentityProviderProvisioning.class);
am = new LdapLoginAuthenticationManager(provisioning);
publisher = mock(ApplicationEventPublisher.class);
am.setApplicationEventPublisher(publisher);
am.setOrigin(origin);
authUserDetail = getAuthDetails(LDAP_EMAIL, "Marissa", "Bloggs", "8675309");
auth = mock(Authentication.class);
when(auth.getPrincipal()).thenReturn(authUserDetail);
UaaUserDatabase db = mock(UaaUserDatabase.class);
when(db.retrieveUserById(anyString())).thenReturn(dbUser);
am.setUserDatabase(db);
when(auth.getAuthorities()).thenReturn(null);
provider = mock(IdentityProvider.class);
when(provisioning.retrieveByOrigin(anyString(),anyString())).thenReturn(provider);
Map attributeMappings = new HashMap<>();
definition = LdapIdentityProviderDefinition.searchAndBindMapGroupToScopes(
"baseUrl",
"bindUserDn",
"bindPassword",
"userSearchBase",
"userSearchFilter",
"grouSearchBase",
"groupSearchFilter",
"mailAttributeName",
"mailSubstitute",
false,
false,
false,
1,
false
);
definition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+MANAGERS, UAA_MANAGER);
definition.addAttributeMapping(USER_ATTRIBUTE_PREFIX+COST_CENTERS, COST_CENTER);
when(provider.getConfig()).thenReturn(definition);
}
@Test
public void testGetUserWithExtendedLdapInfo() throws Exception {
UaaUser user = am.getUser(auth, null);
assertEquals(DN, user.getExternalId());
assertEquals(LDAP_EMAIL, user.getEmail());
assertEquals(origin, user.getOrigin());
}
@Test
public void testGetUserWithNonLdapInfo() throws Exception {
UserDetails mockNonLdapUserDetails = mockNonLdapUserDetails();
when(mockNonLdapUserDetails.getUsername()).thenReturn(TEST_EMAIL);
when(auth.getPrincipal()).thenReturn(mockNonLdapUserDetails);
UaaUser user = am.getUser(auth, null);
assertEquals(TEST_EMAIL, user.getExternalId());
assertEquals(TEST_EMAIL, user.getEmail());
assertEquals(origin, user.getOrigin());
}
@Test
public void testUserAuthenticated() throws Exception {
UaaUser user = getUaaUser();
UaaUser userFromRequest = am.getUser(auth, null);
definition.setAutoAddGroups(true);
UaaUser result = am.userAuthenticated(auth, user, userFromRequest);
assertSame(dbUser, result);
verify(publisher, times(1)).publishEvent(Matchers.<ApplicationEvent>anyObject());
definition.setAutoAddGroups(false);
result = am.userAuthenticated(auth, userFromRequest, user);
assertSame(dbUser, result);
verify(publisher, times(2)).publishEvent(Matchers.<ApplicationEvent>anyObject());
}
@Test
public void shadowUserCreationDisabledWillNotAddShadowUser() throws Exception {
definition.setAddShadowUserOnLogin(false);
assertFalse(am.isAddNewShadowUser());
}
@Test
public void update_existingUser_if_attributes_different() throws Exception {
ExtendedLdapUserImpl authDetails = getAuthDetails(LDAP_EMAIL, "MarissaChanged", "BloggsChanged", "8675309");
when(auth.getPrincipal()).thenReturn(authDetails);
UaaUser user = getUaaUser();
UaaUser userFromRequest = am.getUser(auth, null);
am.userAuthenticated(auth, userFromRequest, user);
ArgumentCaptor<ExternalGroupAuthorizationEvent> captor = ArgumentCaptor.forClass(ExternalGroupAuthorizationEvent.class);
verify(publisher, times(1)).publishEvent(captor.capture());
assertEquals(LDAP_EMAIL, captor.getValue().getUser().getEmail());
assertEquals("MarissaChanged", captor.getValue().getUser().getGivenName());
assertEquals("BloggsChanged", captor.getValue().getUser().getFamilyName());
}
@Test
public void dontUpdate_existingUser_if_attributes_same() throws Exception {
UaaUser user = getUaaUser();
ExtendedLdapUserImpl authDetails = getAuthDetails(user.getEmail(), user.getGivenName(), user.getFamilyName(), user.getPhoneNumber());
when(auth.getPrincipal()).thenReturn(authDetails);
UaaUser userFromRequest = am.getUser(auth, null);
am.userAuthenticated(auth, userFromRequest, user);
ArgumentCaptor<ExternalGroupAuthorizationEvent> captor = ArgumentCaptor.forClass(ExternalGroupAuthorizationEvent.class);
verify(publisher, times(1)).publishEvent(captor.capture());
assertEquals(user.getModified(), captor.getValue().getUser().getModified());
}
@Test
public void test_authentication_attributes() throws Exception {
test_authentication_attributes(false);
}
@Test
public void test_authentication_attributes_store_custom_attributes() throws Exception {
test_authentication_attributes(true);
}
@Test
public void test_group_white_list_with_wildcard() {
UaaUser user = getUaaUser();
ExtendedLdapUserImpl authDetails =
getAuthDetails(
user.getEmail(),
user.getGivenName(),
user.getFamilyName(),
user.getPhoneNumber(),
new AttributeInfo(UAA_MANAGER, new String[] {KARI_THE_ANT_EATER, JOHN_THE_SLOTH}),
new AttributeInfo(COST_CENTER, new String[] {DENVER_CO})
);
Map<String, String[]> role1 = new HashMap<>();
role1.put("cn", new String[] {"ldap.role.1.a", "ldap.role.1.b", "ldap.role.1"});
Map<String, String[]> role2 = new HashMap<>();
role2.put("cn", new String[] {"ldap.role.2.a", "ldap.role.2.b", "ldap.role.2"});
authDetails.setAuthorities(
Arrays.asList(
new LdapAuthority("role1", "cn=role1,ou=test,ou=com", role1),
new LdapAuthority("role2", "cn=role2,ou=test,ou=com", role2)
)
);
definition.setExternalGroupsWhitelist(EMPTY_LIST);
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder()
);
definition.setExternalGroupsWhitelist(null);
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder()
);
definition.setExternalGroupsWhitelist(Arrays.asList("ldap.role.1.a"));
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder("ldap.role.1.a")
);
definition.setExternalGroupsWhitelist(Arrays.asList("ldap.role.1.a", "ldap.role.2.*"));
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder("ldap.role.1.a", "ldap.role.2.a", "ldap.role.2.b")
);
definition.setExternalGroupsWhitelist(Arrays.asList("ldap.role.*.*"));
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder("ldap.role.1.a", "ldap.role.1.b", "ldap.role.2.a", "ldap.role.2.b")
);
definition.setExternalGroupsWhitelist(Arrays.asList("ldap.role.*.*", "ldap.role.*"));
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder("ldap.role.1.a", "ldap.role.1.b", "ldap.role.1", "ldap.role.2.a", "ldap.role.2.b", "ldap.role.2")
);
definition.setExternalGroupsWhitelist(Arrays.asList("ldap*"));
assertThat(am.getExternalUserAuthorities(authDetails),
containsInAnyOrder("ldap.role.1.a", "ldap.role.1.b", "ldap.role.1", "ldap.role.2.a", "ldap.role.2.b", "ldap.role.2")
);
}
public void test_authentication_attributes(boolean storeUserInfo) throws Exception {
UaaUser user = getUaaUser();
ExtendedLdapUserImpl authDetails =
getAuthDetails(
user.getEmail(),
user.getGivenName(),
user.getFamilyName(),
user.getPhoneNumber(),
new AttributeInfo(UAA_MANAGER, new String[] {KARI_THE_ANT_EATER, JOHN_THE_SLOTH}),
new AttributeInfo(COST_CENTER, new String[] {DENVER_CO})
);
Map<String, String[]> role1 = new HashMap<>();
role1.put("cn", new String[] {"ldap.role.1.a", "ldap.role.1.b", "ldap.role.1"});
Map<String, String[]> role2 = new HashMap<>();
role2.put("cn", new String[] {"ldap.role.2.a", "ldap.role.2.b", "ldap.role.2"});
authDetails.setAuthorities(
Arrays.asList(
new LdapAuthority("role1", "cn=role1,ou=test,ou=com", role1),
new LdapAuthority("role2", "cn=role2,ou=test,ou=com", role2)
)
);
definition.setExternalGroupsWhitelist(Arrays.asList("*"));
when(auth.getPrincipal()).thenReturn(authDetails);
UaaUserDatabase db = mock(UaaUserDatabase.class);
when(db.retrieveUserByName(anyString(), eq(OriginKeys.LDAP))).thenReturn(user);
when(db.retrieveUserById(anyString())).thenReturn(user);
am.setOrigin(OriginKeys.LDAP);
am.setUserDatabase(db);
//set the config flag
definition.setStoreCustomAttributes(storeUserInfo);
UaaAuthentication authentication = (UaaAuthentication)am.authenticate(auth);
UserInfo info = new UserInfo()
.setUserAttributes(authentication.getUserAttributes())
.setRoles(Arrays.asList("ldap.role.1.a", "ldap.role.1.b", "ldap.role.1", "ldap.role.2.a", "ldap.role.2.b", "ldap.role.2"));
if (storeUserInfo) {
verify(db, times(1)).storeUserInfo(anyString(), eq(info));
} else {
verify(db, never()).storeUserInfo(anyString(), eq(info));
}
assertEquals("Expected two user attributes", 2, authentication.getUserAttributes().size());
assertNotNull("Expected cost center attribute", authentication.getUserAttributes().get(COST_CENTERS));
assertEquals(DENVER_CO, authentication.getUserAttributes().getFirst(COST_CENTERS));
assertNotNull("Expected manager attribute", authentication.getUserAttributes().get(MANAGERS));
assertEquals("Expected 2 manager attribute values", 2, authentication.getUserAttributes().get(MANAGERS).size());
assertThat(authentication.getUserAttributes().get(MANAGERS), containsInAnyOrder(JOHN_THE_SLOTH, KARI_THE_ANT_EATER));
assertThat(authentication.getAuthenticationMethods(), containsInAnyOrder("ext", "pwd"));
}
private ExtendedLdapUserImpl getAuthDetails(String email, String givenName, String familyName, String phoneNumber, AttributeInfo... attributes) {
String[] emails = {email};
String[] given_names = {givenName};
String[] family_names = {familyName};
String[] phone_numbers = {phoneNumber};
info.put(EMAIL_ATTRIBUTE, emails);
info.put(GIVEN_NAME_ATTRIBUTE, given_names);
info.put(FAMILY_NAME_ATTRIBUTE, family_names);
info.put(PHONE_NUMBER_ATTTRIBUTE, phone_numbers);
for (AttributeInfo i : attributes) {
info.put(i.getName(), i.getValues());
}
authUserDetail = new ExtendedLdapUserImpl(mockLdapUserDetails(), info);
authUserDetail.setMailAttributeName(EMAIL_ATTRIBUTE);
authUserDetail.setGivenNameAttributeName(GIVEN_NAME_ATTRIBUTE);
authUserDetail.setFamilyNameAttributeName(FAMILY_NAME_ATTRIBUTE);
authUserDetail.setPhoneNumberAttributeName(PHONE_NUMBER_ATTTRIBUTE);
return authUserDetail;
}
protected UaaUser getUaaUser() {
return new UaaUser(new UaaUserPrototype()
.withId("id")
.withUsername(USERNAME)
.withPassword("password")
.withEmail(TEST_EMAIL)
.withAuthorities(UaaAuthority.USER_AUTHORITIES)
.withGivenName("givenname")
.withFamilyName("familyname")
.withPhoneNumber("8675309")
.withCreated(new Date())
.withModified(new Date())
.withOrigin(OriginKeys.ORIGIN)
.withExternalId(DN)
.withVerified(false)
.withZoneId(IdentityZoneHolder.get().getId())
.withSalt(null)
.withPasswordLastModified(null));
}
public static class AttributeInfo {
final String name;
final String[] values;
public AttributeInfo(String name, String[] values) {
this.name = name;
this.values = values;
}
public String getName() {
return name;
}
public String[] getValues() {
return values;
}
}
}