package org.cloudfoundry.identity.uaa.login;
import org.cloudfoundry.identity.uaa.account.UserAccountStatus;
import org.cloudfoundry.identity.uaa.constants.OriginKeys;
import org.cloudfoundry.identity.uaa.mock.InjectedMockContextTest;
import org.cloudfoundry.identity.uaa.mock.util.MockMvcUtils;
import org.cloudfoundry.identity.uaa.provider.IdentityProvider;
import org.cloudfoundry.identity.uaa.provider.IdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.JdbcIdentityProviderProvisioning;
import org.cloudfoundry.identity.uaa.provider.PasswordPolicy;
import org.cloudfoundry.identity.uaa.provider.UaaIdentityProviderDefinition;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.security.web.CookieBasedCsrfTokenRepository;
import org.cloudfoundry.identity.uaa.util.JsonUtils;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Matchers;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.oauth2.common.util.RandomValueStringGenerator;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import javax.servlet.http.Cookie;
import java.net.URLEncoder;
import java.util.Date;
import static org.mockito.Matchers.contains;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.cookie;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
public class ForcePasswordChangeControllerMockMvcTest extends InjectedMockContextTest {
private ScimUser user;
private String token;
private IdentityProviderProvisioning identityProviderProvisioning;
@Before
public void setup() throws Exception {
String username = new RandomValueStringGenerator().generate() + "@test.org";
user = new ScimUser(null, username, "givenname","familyname");
user.setPrimaryEmail(username);
user.setPassword("secret");
identityProviderProvisioning = getWebApplicationContext().getBean(JdbcIdentityProviderProvisioning.class);
token = MockMvcUtils.utils().getClientCredentialsOAuthAccessToken(getMockMvc(), "admin", "adminsecret", null, null);
user = MockMvcUtils.utils().createUser(getMockMvc(), token, user);
}
@Test
public void testHandleChangePasswordForUser() throws Exception {
forcePasswordChangeForUser();
MockHttpSession session = new MockHttpSession();
Cookie cookie = new Cookie(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
MockHttpServletRequestBuilder invalidPost = post("/login.do")
.param("username", user.getUserName())
.param("password", "secret")
.session(session)
.cookie(cookie)
.param(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
getMockMvc().perform(invalidPost)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/force_password_change"));
MockHttpServletRequestBuilder validPost = post("/force_password_change")
.param("password", "test")
.param("password_confirmation", "test")
.session(session)
.cookie(cookie);
validPost.with(csrf());
getMockMvc().perform(validPost)
.andExpect(status().isFound())
.andExpect(redirectedUrl(("/")))
.andExpect(currentUserCookie(user.getId()));
}
@Test
public void testHandleChangePasswordForUserWithInvalidPassword() throws Exception {
forcePasswordChangeForUser();
MockHttpSession session = new MockHttpSession();
Cookie cookie = new Cookie(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId());
UaaIdentityProviderDefinition currentConfig = ((UaaIdentityProviderDefinition) identityProvider.getConfig());
PasswordPolicy passwordPolicy = new PasswordPolicy(15,20,0,0,0,0,0);
identityProvider.setConfig(new UaaIdentityProviderDefinition(passwordPolicy, null));
try {
identityProviderProvisioning.update(identityProvider);
MockHttpServletRequestBuilder invalidPost = post("/login.do")
.param("username", user.getUserName())
.param("password", "secret")
.session(session)
.cookie(cookie)
.param(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
getMockMvc().perform(invalidPost)
.andExpect(status().isFound());
MockHttpServletRequestBuilder validPost = post("/force_password_change")
.param("password", "test")
.param("password_confirmation", "test")
.session(session)
.cookie(cookie)
.with(csrf());
getMockMvc().perform(validPost)
.andExpect(view().name("force_password_change"))
.andExpect(model().attribute("message", "Password must be at least 15 characters in length."))
.andExpect(model().attribute("email", user.getPrimaryEmail()));
} finally {
identityProvider.setConfig(currentConfig);
identityProviderProvisioning.update(identityProvider);
}
}
@Test
public void testHandleChangePasswordForSystemWideChange() throws Exception {
IdentityProvider identityProvider = identityProviderProvisioning.retrieveByOrigin(OriginKeys.UAA, IdentityZone.getUaa().getId());
UaaIdentityProviderDefinition currentConfig = ((UaaIdentityProviderDefinition) identityProvider.getConfig());
PasswordPolicy passwordPolicy = new PasswordPolicy(4,20,0,0,0,0,0);
passwordPolicy.setPasswordNewerThan(new Date(System.currentTimeMillis()));
identityProvider.setConfig(new UaaIdentityProviderDefinition(passwordPolicy, null));
try {
identityProviderProvisioning.update(identityProvider);
MockHttpSession session = new MockHttpSession();
Cookie cookie = new Cookie(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
MockHttpServletRequestBuilder invalidPost = post("/login.do")
.param("username", user.getUserName())
.param("password", "secret")
.session(session).cookie(cookie)
.param(CookieBasedCsrfTokenRepository.DEFAULT_CSRF_COOKIE_NAME, "csrf1");
getMockMvc().perform(invalidPost)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/force_password_change"));
MockHttpServletRequestBuilder validPost = post("/force_password_change")
.param("password", "test")
.param("password_confirmation", "test")
.session(session).cookie(cookie);
validPost.with(csrf());
getMockMvc().perform(validPost)
.andExpect(status().isFound())
.andExpect(redirectedUrl(("/")))
.andExpect(currentUserCookie(user.getId()));
} finally {
identityProvider.setConfig(currentConfig);
identityProviderProvisioning.update(identityProvider);
}
}
@Test
public void testHandleChangePasswordForUserInvalid() throws Exception {
forcePasswordChangeForUser();
MockHttpServletRequestBuilder validPost = post("/force_password_change")
.param("password", "test")
.param("password_confirmation", "test");
validPost.with(csrf());
getMockMvc().perform(validPost)
.andExpect(status().isFound())
.andExpect(redirectedUrl(("/login")));
}
private void forcePasswordChangeForUser() throws Exception {
UserAccountStatus userAccountStatus = new UserAccountStatus();
userAccountStatus.setPasswordChangeRequired(true);
String jsonStatus = JsonUtils.writeValueAsString(userAccountStatus);
getMockMvc().perform(
patch("/Users/"+user.getId()+"/status")
.header("Authorization", "Bearer "+token)
.accept(APPLICATION_JSON)
.contentType(APPLICATION_JSON)
.content(jsonStatus))
.andExpect(status().isOk());
}
private static ResultMatcher currentUserCookie(String userId) {
return result -> {
cookie().value("Current-User", URLEncoder.encode("{\"userId\":\"" + userId + "\"}", "UTF-8")).match(result);
cookie().maxAge("Current-User", 365*24*60*60);
cookie().path("Current-User", "").match(result);
};
}
}