package org.cloudfoundry.identity.uaa.authentication.manager; import org.cloudfoundry.identity.uaa.authentication.AccountNotPreCreatedException; import org.cloudfoundry.identity.uaa.authentication.UaaAuthentication; import org.cloudfoundry.identity.uaa.authentication.UaaAuthenticationDetails; import org.cloudfoundry.identity.uaa.authentication.UaaPrincipal; import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent; import org.cloudfoundry.identity.uaa.provider.ExternalIdentityProviderDefinition; import org.cloudfoundry.identity.uaa.provider.IdentityProvider; import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning; import org.cloudfoundry.identity.uaa.provider.ldap.ExtendedLdapUserDetails; import org.cloudfoundry.identity.uaa.provider.ldap.extension.ExtendedLdapUserImpl; import org.cloudfoundry.identity.uaa.user.Mailable; 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.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEventPublisher; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.ldap.userdetails.LdapUserDetails; import org.springframework.security.oauth2.common.util.RandomValueStringGenerator; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import static org.cloudfoundry.identity.uaa.constants.OriginKeys.LDAP; import static org.cloudfoundry.identity.uaa.oauth.token.ClaimConstants.ROLES; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyObject; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; public class ExternalLoginAuthenticationManagerTest { private ApplicationEventPublisher applicationEventPublisher; private UaaUserDatabase uaaUserDatabase; private Authentication inputAuth; private ExternalLoginAuthenticationManager manager; private String origin = "test"; private String beanName = "ExternalLoginAuthenticationManagerTestBean"; private UserDetails userDetails; private String userName="testUserName"; private String password = ""; private UaaUser user; private String userId = new RandomValueStringGenerator().generate(); private ArgumentCaptor<ApplicationEvent> userArgumentCaptor; private IdentityProviderProvisioning providerProvisioning; private MultiValueMap<String, String> userAttributes; private List<String> externalGroups; private void mockUserDetails(UserDetails userDetails) { when(userDetails.getUsername()).thenReturn(userName); when(userDetails.getPassword()).thenReturn(password); 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); } @Before public void setUp() throws Exception { userDetails = mock(UserDetails.class); mockUserDetails(userDetails); mockUaaWithUser(); userAttributes = new LinkedMultiValueMap<>(); userAttributes.put("1", Arrays.asList("1")); userAttributes.put("2", Arrays.asList("2", "3")); externalGroups = Arrays.asList("role1", "role2", "role3"); } private void mockUaaWithUser() { applicationEventPublisher = mock(ApplicationEventPublisher.class); uaaUserDatabase = mock(UaaUserDatabase.class); user = addUserToDb(userName, userId, origin, "test@email.org"); inputAuth = mock(Authentication.class); when(inputAuth.getPrincipal()).thenReturn(userDetails); manager = new ExternalLoginAuthenticationManager(null); setupManager(); } private UaaUser addUserToDb(String userName, String userId, String origin, String email) { UaaUser user = mock(UaaUser.class); when(user.getUsername()).thenReturn(userName); when(user.getId()).thenReturn(userId); when(user.getOrigin()).thenReturn(origin); when(user.getEmail()).thenReturn(email); when(this.uaaUserDatabase.retrieveUserById(eq(userId))).thenReturn(user); when(this.uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))).thenReturn(user); return user; } private void setupManager() { manager.setOrigin(origin); manager.setBeanName(beanName); manager.setApplicationEventPublisher(applicationEventPublisher); manager.setUserDatabase(uaaUserDatabase); providerProvisioning = mock(IdentityProviderProvisioning.class); manager.setProviderProvisioning(providerProvisioning); } @Test public void testAuthenticateNullPrincipal() throws Exception { Authentication auth = mock(Authentication.class); when(auth.getPrincipal()).thenReturn(null); Authentication result = manager.authenticate(auth); assertNull(result); } @Test public void testAuthenticateUnknownPrincipal() throws Exception { Authentication auth = mock(Authentication.class); when(auth.getPrincipal()).thenReturn(userName); Authentication result = manager.authenticate(auth); assertNull(result); } @Test public void testAuthenticateUsernamePasswordToken() throws Exception { UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(userName,password); Authentication result = manager.authenticate(auth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test public void testAuthenticateUserDetailsPrincipal() throws Exception { Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test public void testAuthenticateWithAuthDetails() throws Exception { UaaAuthenticationDetails uaaAuthenticationDetails = mock(UaaAuthenticationDetails.class); when(uaaAuthenticationDetails.getOrigin()).thenReturn(origin); when(uaaAuthenticationDetails.getClientId()).thenReturn(null); when(uaaAuthenticationDetails.getSessionId()).thenReturn(new RandomValueStringGenerator().generate()); when(inputAuth.getDetails()).thenReturn(uaaAuthenticationDetails); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test public void testNoUsernameOnlyEmail() throws Exception { String email = "joe@test.org"; userDetails = mock(UserDetails.class, withSettings().extraInterfaces(Mailable.class)); when(((Mailable)userDetails).getEmailAddress()).thenReturn(email); mockUserDetails(userDetails); mockUaaWithUser(); UaaAuthenticationDetails uaaAuthenticationDetails = mock(UaaAuthenticationDetails.class); when(uaaAuthenticationDetails.getOrigin()).thenReturn(origin); when(uaaAuthenticationDetails.getClientId()).thenReturn(null); when(uaaAuthenticationDetails.getSessionId()).thenReturn(new RandomValueStringGenerator().generate()); when(inputAuth.getDetails()).thenReturn(uaaAuthenticationDetails); when(user.getUsername()).thenReturn(email); when(uaaUserDatabase.retrieveUserByName(email, origin)) .thenReturn(user); when(userDetails.getUsername()).thenReturn(null); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(email,uaaAuthentication.getPrincipal().getName()); assertEquals(origin, uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test(expected = BadCredentialsException.class) public void testNoUsernameNoEmail() throws Exception { UaaAuthenticationDetails uaaAuthenticationDetails = mock(UaaAuthenticationDetails.class); when(uaaAuthenticationDetails.getOrigin()).thenReturn(origin); when(uaaAuthenticationDetails.getClientId()).thenReturn(null); when(uaaAuthenticationDetails.getSessionId()).thenReturn(new RandomValueStringGenerator().generate()); when(inputAuth.getDetails()).thenReturn(uaaAuthenticationDetails); when(uaaUserDatabase.retrieveUserByName(anyString(), eq(origin))).thenReturn(null); when(userDetails.getUsername()).thenReturn(null); manager.authenticate(inputAuth); } @Test public void testAmpersandInName() throws Exception { String name = "filip@hanik"; when(userDetails.getUsername()).thenReturn(name); when(user.getUsername()).thenReturn(name); when(uaaUserDatabase.retrieveUserByName(eq(name),eq(origin))) .thenReturn(null) .thenReturn(user); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(name,uaaAuthentication.getPrincipal().getName()); assertEquals(origin, uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test public void testAmpersandInEndOfName() throws Exception { String name = "filip@hanik@"; String actual = name.replaceAll("@","") + "@user.from."+origin+".cf"; when(userDetails.getUsername()).thenReturn(name); when(user.getUsername()).thenReturn(name); when(uaaUserDatabase.retrieveUserByName(eq(name),eq(origin))) .thenReturn(null) .thenReturn(user); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(name,uaaAuthentication.getPrincipal().getName()); assertEquals(origin, uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(2)).publishEvent(userArgumentCaptor.capture()); assertEquals(2,userArgumentCaptor.getAllValues().size()); NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, event.getUser().getOrigin()); assertEquals(actual, event.getUser().getEmail()); } @Test(expected = BadCredentialsException.class) public void testAuthenticateUserInsertFails() throws Exception { when(uaaUserDatabase.retrieveUserByName(anyString(),anyString())).thenThrow(new UsernameNotFoundException("")); manager.authenticate(inputAuth); } @Test public void testAuthenticateLdapUserDetailsPrincipal() throws Exception { String dn = "cn="+userName+",ou=Users,dc=test,dc=com"; String origin = LDAP; LdapUserDetails ldapUserDetails = mock(LdapUserDetails.class); mockUserDetails(ldapUserDetails); when(ldapUserDetails.getDn()).thenReturn(dn); manager = new LdapLoginAuthenticationManager(null); setupManager(); manager.setProviderProvisioning(null); manager.setOrigin(origin); when(user.getOrigin()).thenReturn(origin); when(uaaUserDatabase.retrieveUserByName(eq(userName), eq(origin))).thenReturn(user); when(inputAuth.getPrincipal()).thenReturn(ldapUserDetails); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); } @Test public void testShadowUserCreationDisabled() throws Exception { String dn = "cn="+userName+",ou=Users,dc=test,dc=com"; String origin = LDAP; LdapUserDetails ldapUserDetails = mock(LdapUserDetails.class); mockUserDetails(ldapUserDetails); when(ldapUserDetails.getDn()).thenReturn(dn); manager = new LdapLoginAuthenticationManager(null) { @Override protected boolean isAddNewShadowUser() { return false; } }; setupManager(); manager.setOrigin(origin); when(uaaUserDatabase.retrieveUserByName(eq(userName), eq(origin))).thenReturn(null); when(inputAuth.getPrincipal()).thenReturn(ldapUserDetails); try { manager.authenticate(inputAuth); fail("Expected authentication to fail with an exception."); } catch (AccountNotPreCreatedException ex) { assertThat(ex.getMessage(), containsString("user account must be pre-created")); } verify(applicationEventPublisher, times(0)).publishEvent(any()); } @Test public void testAuthenticateCreateUserWithLdapUserDetailsPrincipal() throws Exception { String dn = "cn="+userName+",ou=Users,dc=test,dc=com"; String origin = LDAP; String email = "joe@test.org"; LdapUserDetails baseLdapUserDetails = mock(LdapUserDetails.class); mockUserDetails(baseLdapUserDetails); when(baseLdapUserDetails.getDn()).thenReturn(dn); HashMap<String, String[]> ldapAttrs = new HashMap<>(); String ldapMailAttrName = "email"; ldapAttrs.put(ldapMailAttrName, new String[]{email}); ExtendedLdapUserImpl ldapUserDetails = new ExtendedLdapUserImpl(baseLdapUserDetails, ldapAttrs); ldapUserDetails.setMailAttributeName(ldapMailAttrName); manager = new LdapLoginAuthenticationManager(null); setupManager(); manager.setProviderProvisioning(null); manager.setOrigin(origin); when(user.getEmail()).thenReturn(email); when(user.getOrigin()).thenReturn(origin); when(uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))) .thenReturn(null) .thenReturn(user); when(inputAuth.getPrincipal()).thenReturn(ldapUserDetails); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(3)).publishEvent(userArgumentCaptor.capture()); assertEquals(3,userArgumentCaptor.getAllValues().size()); NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, event.getUser().getOrigin()); assertEquals(dn, event.getUser().getExternalId()); } @Test public void testAuthenticateCreateUserWithUserDetailsPrincipal() throws Exception { String origin = LDAP; manager = new LdapLoginAuthenticationManager(null); setupManager(); manager.setOrigin(origin); manager.setProviderProvisioning(null); when(user.getOrigin()).thenReturn(origin); when(uaaUserDatabase.retrieveUserByName(eq(userName),eq(origin))) .thenReturn(null) .thenReturn(user); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(origin,uaaAuthentication.getPrincipal().getOrigin()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(3)).publishEvent(userArgumentCaptor.capture()); assertEquals(3,userArgumentCaptor.getAllValues().size()); NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, event.getUser().getOrigin()); //incorrect user details - we wont be able to get the correct external ID assertEquals(userName, event.getUser().getExternalId()); } @Test public void testAuthenticateInvitedUserWithoutAcceptance() throws Exception { String username = "guyWhoDoesNotAcceptInvites"; String origin = LDAP; String email = "guy@ldap.org"; UserDetails ldapUserDetails = mock(ExtendedLdapUserDetails.class, withSettings().extraInterfaces(Mailable.class)); when(ldapUserDetails.getUsername()).thenReturn(username); when(ldapUserDetails.getPassword()).thenReturn(password); when(ldapUserDetails.getAuthorities()).thenReturn(null); when(ldapUserDetails.isAccountNonExpired()).thenReturn(true); when(ldapUserDetails.isAccountNonLocked()).thenReturn(true); when(ldapUserDetails.isCredentialsNonExpired()).thenReturn(true); when(ldapUserDetails.isEnabled()).thenReturn(true); when(((Mailable) ldapUserDetails).getEmailAddress()).thenReturn(email); // Invited users are created with their email as their username. UaaUser invitedUser = addUserToDb(email, userId, origin, email); when(invitedUser.modifyAttributes(anyString(), anyString(), anyString(), anyString())).thenReturn(invitedUser); UaaUser updatedUser = new UaaUser(new UaaUserPrototype().withUsername(username).withId(userId).withOrigin(origin).withEmail(email)); when(invitedUser.modifyUsername(username)).thenReturn(updatedUser); manager = new LdapLoginAuthenticationManager(null); setupManager(); manager.setProviderProvisioning(null); manager.setOrigin(origin); when(uaaUserDatabase.retrieveUserByName(eq(username),eq(origin))) .thenThrow(new UsernameNotFoundException("")); when(uaaUserDatabase.retrieveUserByEmail(eq(email), eq(origin))) .thenReturn(invitedUser); Authentication ldapAuth = mock(Authentication.class); when(ldapAuth.getPrincipal()).thenReturn(ldapUserDetails); manager.authenticate(ldapAuth); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher, atLeastOnce()).publishEvent(userArgumentCaptor.capture()); for(ApplicationEvent event : userArgumentCaptor.getAllValues()) { assertNotEquals(event.getClass(), NewUserAuthenticatedEvent.class); } } @Test public void testPopulateAttributesStoresCustomAttributesAndRoles() { manager = new LdapLoginAuthenticationManager(null); setupManager(); manager.setOrigin(origin); IdentityProvider provider = mock(IdentityProvider.class); ExternalIdentityProviderDefinition providerDefinition = new ExternalIdentityProviderDefinition(); when(provider.getConfig()).thenReturn(providerDefinition); when(providerProvisioning.retrieveByOrigin(eq(origin), anyString())).thenReturn(provider); UaaAuthentication uaaAuthentication = mock(UaaAuthentication.class); UaaPrincipal uaaPrincipal = mock(UaaPrincipal.class); when(uaaPrincipal.getId()).thenReturn("id"); when(uaaAuthentication.getPrincipal()).thenReturn(uaaPrincipal); when(uaaAuthentication.getUserAttributes()).thenReturn(userAttributes); when(uaaAuthentication.getExternalGroups()).thenReturn(new HashSet<>(externalGroups)); manager.populateAuthenticationAttributes(uaaAuthentication, mock(Authentication.class), null); verify(manager.getUserDatabase(), never()).storeUserInfo(anyString(), anyObject()); providerDefinition.setStoreCustomAttributes(true); manager.populateAuthenticationAttributes(uaaAuthentication, mock(Authentication.class), null); UserInfo userInfo = new UserInfo() .setUserAttributes(userAttributes) .setRoles(externalGroups); verify(manager.getUserDatabase(), times(1)).storeUserInfo(eq("id"), eq(userInfo)); //null provider does not store it reset(manager.getUserDatabase()); manager.setProviderProvisioning(null); manager.populateAuthenticationAttributes(uaaAuthentication, mock(Authentication.class), null); verify(manager.getUserDatabase(), never()).storeUserInfo(anyString(), anyObject()); manager.setProviderProvisioning(providerProvisioning); //empty attributes does not store it reset(manager.getUserDatabase()); userAttributes.clear(); manager.populateAuthenticationAttributes(uaaAuthentication, mock(Authentication.class), null); verify(manager.getUserDatabase(), never()).storeUserInfo(anyString(), anyObject()); } @Test public void testAuthenticateUserExists() throws Exception { Authentication result = manager.authenticate(inputAuth); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(1)).publishEvent(userArgumentCaptor.capture()); assertEquals(1,userArgumentCaptor.getAllValues().size()); UserAuthenticationSuccessEvent event = (UserAuthenticationSuccessEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, event.getUser().getOrigin()); assertEquals(userName, event.getUser().getUsername()); } @Test public void testAuthenticateUserDoesNotExists() throws Exception { String origin = "external"; manager.setOrigin(origin); when(uaaUserDatabase.retrieveUserByName(eq(userName), eq(origin))) .thenReturn(null) .thenReturn(user); Authentication result = manager.authenticate(inputAuth); assertNotNull(result); assertEquals(UaaAuthentication.class, result.getClass()); UaaAuthentication uaaAuthentication = (UaaAuthentication)result; assertEquals(userName,uaaAuthentication.getPrincipal().getName()); assertEquals(userId, uaaAuthentication.getPrincipal().getId()); userArgumentCaptor = ArgumentCaptor.forClass(ApplicationEvent.class); verify(applicationEventPublisher,times(2)).publishEvent(userArgumentCaptor.capture()); assertEquals(2,userArgumentCaptor.getAllValues().size()); NewUserAuthenticatedEvent event = (NewUserAuthenticatedEvent)userArgumentCaptor.getAllValues().get(0); assertEquals(origin, event.getUser().getOrigin()); } }