/*******************************************************************************
* 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.AccountNotVerifiedException;
import org.cloudfoundry.identity.uaa.authentication.AuthenticationPolicyRejectionException;
import org.cloudfoundry.identity.uaa.authentication.AuthzAuthenticationRequest;
import org.cloudfoundry.identity.uaa.authentication.PasswordChangeRequiredException;
import org.cloudfoundry.identity.uaa.authentication.PasswordExpiredException;
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.UnverifiedUserAuthenticationEvent;
import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationFailureEvent;
import org.cloudfoundry.identity.uaa.authentication.event.UserAuthenticationSuccessEvent;
import org.cloudfoundry.identity.uaa.authentication.event.UserNotFoundEvent;
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.PasswordPolicy;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
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.zone.IdentityZoneHolder;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
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.event.AuthenticationFailureLockedEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import javax.servlet.http.HttpServletRequest;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Luke Taylor
*/
public class AuthzAuthenticationManagerTests {
private AuthzAuthenticationManager mgr;
private UaaUserDatabase db;
private ApplicationEventPublisher publisher;
private static final String PASSWORD = "$2a$10$HoWPAUn9zqmmb0b.2TBZWe6cjQcxyo8TDwTX.5G46PBL347N3/0zO"; // "password"
private UaaUser user = null;
private BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
private String loginServerUserName="loginServerUser".toLowerCase();
private IdentityProviderProvisioning providerProvisioning;
@Rule
public final ExpectedException exception = ExpectedException.none();
private ArgumentCaptor<ApplicationEvent> eventCaptor;
@Before
public void setUp() throws Exception {
user = new UaaUser(getPrototype());
providerProvisioning = mock(IdentityProviderProvisioning.class);
db = mock(UaaUserDatabase.class);
publisher = mock(ApplicationEventPublisher.class);
eventCaptor = ArgumentCaptor.forClass(ApplicationEvent.class);
doNothing().when(publisher).publishEvent(eventCaptor.capture());
mgr = new AuthzAuthenticationManager(db, encoder, providerProvisioning);
mgr.setApplicationEventPublisher(publisher);
mgr.setOrigin(OriginKeys.UAA);
}
private UaaUserPrototype getPrototype() {
String id = new RandomValueStringGenerator().generate();
return new UaaUserPrototype()
.withId(id)
.withUsername("auser")
.withPassword(PASSWORD)
.withEmail("auser@blah.com")
.withAuthorities(UaaAuthority.USER_AUTHORITIES)
.withGivenName("A")
.withFamilyName("User")
.withOrigin(OriginKeys.UAA)
.withZoneId(IdentityZoneHolder.get().getId())
.withExternalId(id)
.withPasswordLastModified(new Date(System.currentTimeMillis()))
.withVerified(true);
}
@Test
public void successfulAuthentication() throws Exception {
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
Authentication result = mgr.authenticate(createAuthRequest("auser", "password"));
assertNotNull(result);
assertEquals("auser", result.getName());
assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName());
assertThat(((UaaAuthentication)result).getAuthenticationMethods(), containsInAnyOrder("pwd"));
ApplicationEvent event = eventCaptor.getValue();
assertThat(event, instanceOf(UserAuthenticationSuccessEvent.class));
assertEquals("auser", ((UserAuthenticationSuccessEvent)event).getUser().getUsername());
}
@Test(expected = PasswordExpiredException.class)
public void unsuccessfulPasswordExpired() throws Exception {
IdentityProvider<UaaIdentityProviderDefinition> provider = new IdentityProvider<>();
UaaIdentityProviderDefinition idpDefinition = new UaaIdentityProviderDefinition(new PasswordPolicy(6, 128, 1, 1, 1, 1, 6), null);
provider.setConfig(idpDefinition);
when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider);
Calendar oneYearAgoCal = Calendar.getInstance();
oneYearAgoCal.add(Calendar.YEAR, -1);
Date oneYearAgo = new Date(oneYearAgoCal.getTimeInMillis());
user = new UaaUser(
user.getId(),
user.getUsername(),
PASSWORD,
user.getPassword(),
user.getAuthorities(),
user.getGivenName(),
user.getFamilyName(),
oneYearAgo,
oneYearAgo,
OriginKeys.UAA,
null,
true,
IdentityZoneHolder.get().getId(),
user.getSalt(),
oneYearAgo);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
mgr.authenticate(createAuthRequest("auser", "password"));
}
@Test(expected = BadCredentialsException.class)
public void unsuccessfulLoginServerUserAuthentication() throws Exception {
when(db.retrieveUserByName(loginServerUserName, OriginKeys.UAA)).thenReturn(null);
mgr.authenticate(createAuthRequest(loginServerUserName, ""));
verify(db, times(0)).updateLastLogonTime(anyString());
}
@Test(expected = BadCredentialsException.class)
public void unsuccessfulLoginServerUserWithPasswordAuthentication() throws Exception {
when(db.retrieveUserByName(loginServerUserName, OriginKeys.UAA)).thenReturn(null);
mgr.authenticate(createAuthRequest(loginServerUserName, "dadas"));
}
@Test
public void successfulAuthenticationReturnsTokenAndPublishesEvent() throws Exception {
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
Authentication result = mgr.authenticate(createAuthRequest("auser", "password"));
assertNotNull(result);
assertEquals("auser", result.getName());
assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName());
verify(publisher).publishEvent(isA(UserAuthenticationSuccessEvent.class));
}
@Test
public void invalidPasswordPublishesAuthenticationFailureEvent() {
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
try {
mgr.authenticate(createAuthRequest("auser", "wrongpassword"));
fail();
} catch (BadCredentialsException expected) {
}
verify(publisher).publishEvent(isA(UserAuthenticationFailureEvent.class));
verify(db, times(0)).updateLastLogonTime(anyString());
}
@Test(expected = AuthenticationPolicyRejectionException.class)
public void authenticationIsDeniedIfRejectedByLoginPolicy() throws Exception {
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
AccountLoginPolicy lp = mock(AccountLoginPolicy.class);
when(lp.isAllowed(any(UaaUser.class), any(Authentication.class))).thenReturn(false);
mgr.setAccountLoginPolicy(lp);
mgr.authenticate(createAuthRequest("auser", "password"));
verify(db, times(0)).updateLastLogonTime(anyString());
}
@Test
public void missingUserPublishesNotFoundEvent() {
when(db.retrieveUserByName(eq("aguess"),eq(OriginKeys.UAA))).thenThrow(new UsernameNotFoundException("mocked"));
try {
mgr.authenticate(createAuthRequest("aguess", "password"));
fail();
} catch (BadCredentialsException expected) {
}
verify(publisher).publishEvent(isA(UserNotFoundEvent.class));
}
@Test
public void successfulVerifyOriginAuthentication1() throws Exception {
mgr.setOrigin("test");
user = user.modifySource("test",null);
when(db.retrieveUserByName("auser","test")).thenReturn(user);
Authentication result = mgr.authenticate(createAuthRequest("auser", "password"));
assertNotNull(result);
assertEquals("auser", result.getName());
assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName());
}
@Test(expected = BadCredentialsException.class)
public void originAuthenticationFail() throws Exception {
when(db.retrieveUserByName("auser", "not UAA")).thenReturn(user);
mgr.authenticate(createAuthRequest("auser", "password"));
}
@Test
public void unverifiedAuthenticationForOldUserSucceedsWhenAllowed() throws Exception {
mgr.setAllowUnverifiedUsers(true);
user = new UaaUser(getPrototype().withLegacyVerificationBehavior(true));
user.setVerified(false);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
Authentication result = mgr.authenticate(createAuthRequest("auser", "password"));
assertEquals("auser", result.getName());
assertEquals("auser", ((UaaPrincipal) result.getPrincipal()).getName());
}
@Test
public void unverifiedAuthenticationForNewUserFailsEvenWhenAllowed() throws Exception {
mgr.setAllowUnverifiedUsers(true);
user.setVerified(false);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
try {
mgr.authenticate(createAuthRequest("auser", "password"));
fail("Expected AccountNotVerifiedException");
} catch(AccountNotVerifiedException e) {
verify(publisher).publishEvent(isA(UnverifiedUserAuthenticationEvent.class));
}
}
@Test
public void authenticationWhenUserPasswordChangeRequired() throws Exception {
exception.expectMessage("User password needs to be changed");
exception.expect(PasswordChangeRequiredException.class);
mgr.setAllowUnverifiedUsers(false);
user.setPasswordChangeRequired(true);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
mgr.authenticate(createAuthRequest("auser", "password"));
}
@Test
public void unverifiedAuthenticationFailsWhenNotAllowed() throws Exception {
mgr.setAllowUnverifiedUsers(false);
user.setVerified(false);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
try {
mgr.authenticate(createAuthRequest("auser", "password"));
fail("Expected AccountNotVerifiedException");
} catch(AccountNotVerifiedException e) {
verify(publisher).publishEvent(isA(UnverifiedUserAuthenticationEvent.class));
}
}
@Test (expected = PasswordChangeRequiredException.class)
public void testSystemWidePasswordExpiry() {
IdentityProvider<UaaIdentityProviderDefinition> provider = new IdentityProvider<>();
UaaIdentityProviderDefinition idpDefinition = mock(UaaIdentityProviderDefinition.class);
provider.setConfig(idpDefinition);
when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider);
PasswordPolicy policy = new PasswordPolicy();
policy.setPasswordNewerThan(new Date(System.currentTimeMillis() + 1000));
when(idpDefinition.getPasswordPolicy()).thenReturn(policy);
when(db.retrieveUserByName("auser",OriginKeys.UAA)).thenReturn(user);
mgr.authenticate(createAuthRequest("auser", "password"));
}
@Test
public void testSystemWidePasswordExpiryWithPastDate() {
IdentityProvider<UaaIdentityProviderDefinition> provider = new IdentityProvider<>();
UaaIdentityProviderDefinition idpDefinition = mock(UaaIdentityProviderDefinition.class);
provider.setConfig(idpDefinition);
when(providerProvisioning.retrieveByOrigin(anyString(), anyString())).thenReturn(provider);
PasswordPolicy policy = new PasswordPolicy();
Date past = new Date(System.currentTimeMillis() - 10000000);
policy.setPasswordNewerThan(past);
when(idpDefinition.getPasswordPolicy()).thenReturn(policy);
when(db.retrieveUserByName("auser",OriginKeys.UAA)).thenReturn(user);
mgr.authenticate(createAuthRequest("auser", "password"));
}
@Test
public void userIsLockedOutAfterNumberOfFailedTriesIsExceeded() throws Exception {
AccountLoginPolicy lockoutPolicy = mock(PeriodLockoutPolicy.class);
mgr.setAccountLoginPolicy(lockoutPolicy);
when(db.retrieveUserByName("auser", OriginKeys.UAA)).thenReturn(user);
Authentication authentication = createAuthRequest("auser", "password");
when(lockoutPolicy.isAllowed(any(UaaUser.class), eq(authentication))).thenReturn(false);
try {
mgr.authenticate(authentication);
} catch (AuthenticationPolicyRejectionException e) {
// woo hoo
}
assertFalse(authentication.isAuthenticated());
verify(publisher).publishEvent(isA(AuthenticationFailureLockedEvent.class));
}
AuthzAuthenticationRequest createAuthRequest(String username, String password) {
Map<String, String> userdata = new HashMap<String, String>();
userdata.put("username", username);
userdata.put("password", password);
return new AuthzAuthenticationRequest(userdata, new UaaAuthenticationDetails(mock(HttpServletRequest.class)));
}
}