Skip to content

Commit

Permalink
Refactor: Make sure PasswordChangeEndpoint,
Browse files Browse the repository at this point in the history
ChangePasswordController and ChangePasswordService do not use
ResetPassword's controllers.

[#95916214] https://www.pivotaltracker.com/story/show/95916214

Signed-off-by: Xuebin He <xuebin.he@emc.com>
  • Loading branch information
Chris Dutra authored and mbhave committed Jul 1, 2015
1 parent ecb307b commit a6593dd
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 104 deletions.
Expand Up @@ -13,5 +13,5 @@
package org.cloudfoundry.identity.uaa.login; package org.cloudfoundry.identity.uaa.login;


public interface ChangePasswordService { public interface ChangePasswordService {
public Void changePassword(String username, String currentPassword, String newPassword); void changePassword(String username, String currentPassword, String newPassword);
} }
Expand Up @@ -12,31 +12,79 @@
*******************************************************************************/ *******************************************************************************/
package org.cloudfoundry.identity.uaa.login; package org.cloudfoundry.identity.uaa.login;


import org.cloudfoundry.identity.uaa.scim.endpoints.PasswordResetEndpoints; import org.cloudfoundry.identity.uaa.password.event.PasswordChangeEvent;
import org.springframework.http.ResponseEntity; import org.cloudfoundry.identity.uaa.password.event.PasswordChangeFailureEvent;
import org.cloudfoundry.identity.uaa.scim.ScimUser;
import org.cloudfoundry.identity.uaa.scim.ScimUserProvisioning;
import org.cloudfoundry.identity.uaa.scim.exception.InvalidPasswordException;
import org.cloudfoundry.identity.uaa.scim.exception.ScimResourceNotFoundException;
import org.cloudfoundry.identity.uaa.scim.validate.PasswordValidator;
import org.cloudfoundry.identity.uaa.user.UaaUser;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.context.SecurityContextHolder;


import java.util.Map; import java.util.Date;
import java.util.List;


public class UaaChangePasswordService implements ChangePasswordService { public class UaaChangePasswordService implements ChangePasswordService, ApplicationEventPublisherAware {


private final PasswordResetEndpoints passwordResetEndpoints; private final ScimUserProvisioning scimUserProvisioning;
private final PasswordValidator passwordValidator;
private ApplicationEventPublisher publisher;


public UaaChangePasswordService(PasswordResetEndpoints passwordResetEndpoints) { public UaaChangePasswordService(ScimUserProvisioning scimUserProvisioning, PasswordValidator passwordValidator) {
this.passwordResetEndpoints= passwordResetEndpoints; this.scimUserProvisioning = scimUserProvisioning;
this.passwordValidator = passwordValidator;
} }


@Override @Override
public Void changePassword(String username, String currentPassword, String newPassword) { public void changePassword(String username, String currentPassword, String newPassword) {
PasswordResetEndpoints.PasswordChange change = new PasswordResetEndpoints.PasswordChange(); if (username == null || currentPassword == null) {
change.setUsername(username);
change.setCurrentPassword(currentPassword);
change.setNewPassword(newPassword);
ResponseEntity<Map<String,String>> response = passwordResetEndpoints.changePassword(change);
if (! response.getStatusCode().is2xxSuccessful()) {
//throw an error
throw new BadCredentialsException(username); throw new BadCredentialsException(username);
} }
return null; passwordValidator.validate(newPassword);
changePasswordUsernamePasswordAuthenticated(username, currentPassword, newPassword);
}

private void changePasswordUsernamePasswordAuthenticated(String username, String currentPassword, String newPassword) {
List<ScimUser> results = scimUserProvisioning.query("userName eq \"" + username + "\"");
if (results.isEmpty()) {
throw new ScimResourceNotFoundException("User not found");
}
ScimUser user = results.get(0);
UaaUser uaaUser = getUaaUser(user);
try {
if (scimUserProvisioning.checkPasswordMatches(user.getId(), newPassword)) {
throw new InvalidPasswordException("Your new password cannot be the same as the old password");
}
scimUserProvisioning.changePassword(user.getId(), currentPassword, newPassword);
publish(new PasswordChangeEvent("Password changed", uaaUser, SecurityContextHolder.getContext().getAuthentication()));
} catch (Exception e) {
publish(new PasswordChangeFailureEvent(e.getMessage(), uaaUser, SecurityContextHolder.getContext().getAuthentication()));
throw e;
}
}

private UaaUser getUaaUser(ScimUser scimUser) {
Date today = new Date();
return new UaaUser(scimUser.getId(), scimUser.getUserName(), "N/A", scimUser.getPrimaryEmail(), null,
scimUser.getGivenName(),
scimUser.getFamilyName(), today, today,
scimUser.getOrigin(), scimUser.getExternalId(), scimUser.isVerified(), scimUser.getZoneId(), scimUser.getSalt(),
scimUser.getPasswordLastModified());
}

@Override
public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {
this.publisher = publisher;
}

protected void publish(ApplicationEvent event) {
if (publisher!=null) {
publisher.publishEvent(event);
}
} }
} }
3 changes: 2 additions & 1 deletion login/src/main/resources/login-ui.xml
Expand Up @@ -436,7 +436,8 @@
</bean> </bean>


<bean id="changePasswordService" class="org.cloudfoundry.identity.uaa.login.UaaChangePasswordService"> <bean id="changePasswordService" class="org.cloudfoundry.identity.uaa.login.UaaChangePasswordService">
<constructor-arg ref="passwordResetEndpoints"/> <constructor-arg ref="scimUserProvisioning"/>
<constructor-arg ref="uaaPasswordValidator"/>
</bean> </bean>


<bean id="changePasswordController" class="org.cloudfoundry.identity.uaa.login.ChangePasswordController"> <bean id="changePasswordController" class="org.cloudfoundry.identity.uaa.login.ChangePasswordController">
Expand Down
@@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
* Cloud Foundry * Cloud Foundry
* Copyright (c) [2009-2014] Pivotal Software, Inc. All Rights Reserved. * Copyright (c) [2009-2015] Pivotal Software, Inc. All Rights Reserved.
* *
* This product is licensed to you under the Apache License, Version 2.0 (the "License"). * 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. * You may not use this product except in compliance with the License.
Expand All @@ -18,7 +18,6 @@
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
Expand All @@ -31,7 +30,8 @@
import java.util.Arrays; import java.util.Arrays;


import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Lists.newArrayList;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.doThrow;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED; import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
Expand All @@ -47,7 +47,7 @@ public class ChangePasswordControllerTest extends TestClassNullifier {
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
changePasswordService = Mockito.mock(ChangePasswordService.class); changePasswordService = mock(ChangePasswordService.class);
ChangePasswordController controller = new ChangePasswordController(changePasswordService); ChangePasswordController controller = new ChangePasswordController(changePasswordService);


InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
Expand All @@ -57,6 +57,14 @@ public void setUp() throws Exception {
.standaloneSetup(controller) .standaloneSetup(controller)
.setViewResolvers(viewResolver) .setViewResolvers(viewResolver)
.build(); .build();

Authentication authentication = new UsernamePasswordAuthenticationToken(
"bob",
"secret",
Arrays.asList(UaaAuthority.UAA_USER)
);

SecurityContextHolder.getContext().setAuthentication(authentication);
} }


@After @After
Expand All @@ -65,88 +73,71 @@ public void tearDown() {
} }


@Test @Test
public void testChangePasswordPage() throws Exception { public void changePasswordPage_RendersChangePasswordPage() throws Exception {
mockMvc.perform(get("/change_password")) mockMvc.perform(get("/change_password"))
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(view().name("change_password")); .andExpect(view().name("change_password"));
} }


@Test @Test
public void testChangePassword() throws Exception { public void changePassword_Returns302Found_SuccessfullyChangedPassword() throws Exception {
setupSecurityContext(); MockHttpServletRequestBuilder post = createRequest("secret", "new secret", "new secret");

MockHttpServletRequestBuilder post = post("/change_password.do")
.contentType(APPLICATION_FORM_URLENCODED)
.param("current_password", "secret")
.param("new_password", "new secret")
.param("confirm_password", "new secret");

mockMvc.perform(post) mockMvc.perform(post)
.andExpect(status().isFound()) .andExpect(status().isFound())
.andExpect(redirectedUrl("profile")); .andExpect(redirectedUrl("profile"));


Mockito.verify(changePasswordService).changePassword("bob", "secret", "new secret"); verify(changePasswordService).changePassword("bob", "secret", "new secret");
} }


@Test @Test
public void testChangePasswordValidation() throws Exception { public void changePassword_ConfirmationPasswordDoesNotMatch() throws Exception {
setupSecurityContext(); MockHttpServletRequestBuilder post = createRequest("secret", "new secret", "newsecret");

MockHttpServletRequestBuilder post = post("/change_password.do")
.contentType(APPLICATION_FORM_URLENCODED)
.param("current_password", "secret")
.param("new_password", "new secret")
.param("confirm_password", "newsecret");

mockMvc.perform(post) mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity()) .andExpect(status().isUnprocessableEntity())
.andExpect(view().name("change_password")) .andExpect(view().name("change_password"))
.andExpect(model().attribute("message_code", "form_error")); .andExpect(model().attribute("message_code", "form_error"));


Mockito.verifyZeroInteractions(changePasswordService); verifyZeroInteractions(changePasswordService);
} }


@Test @Test
public void testPasswordPolicyViolationIsReported() throws Exception { public void changePassword_PasswordPolicyViolationReported() throws Exception {
setupSecurityContext(); doThrow(new InvalidPasswordException(newArrayList("Msg 2b", "Msg 1b"))).when(changePasswordService).changePassword("bob", "secret", "new secret");
MockHttpServletRequestBuilder post = post("/change_password.do")
.contentType(APPLICATION_FORM_URLENCODED) MockHttpServletRequestBuilder post = createRequest("secret", "new secret", "new secret");
.param("current_password", "secret")
.param("new_password", "new secret")
.param("confirm_password", "new secret");

when(changePasswordService.changePassword("bob", "secret", "new secret")).thenThrow(new InvalidPasswordException(newArrayList("Msg 2b", "Msg 1b")));
mockMvc.perform(post) mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity()) .andExpect(status().isUnprocessableEntity())
.andExpect(view().name("change_password")) .andExpect(view().name("change_password"))
.andExpect(model().attribute("message", "Msg 1b Msg 2b")); .andExpect(model().attribute("message", "Msg 1b Msg 2b"));
} }


@Test @Test
public void testChangePasswordWrongPassword() throws Exception { public void changePassword_Returns401Unauthorized_WrongCurrentPassword() throws Exception {
setupSecurityContext(); doThrow(new RestClientException("401 Unauthorized")).when(changePasswordService).changePassword("bob", "wrong", "new secret");

Mockito.doThrow(new RestClientException("401 Unauthorized")).when(changePasswordService).changePassword("bob", "wrong", "new secret");

MockHttpServletRequestBuilder post = post("/change_password.do")
.contentType(APPLICATION_FORM_URLENCODED)
.param("current_password", "wrong")
.param("new_password", "new secret")
.param("confirm_password", "new secret");


MockHttpServletRequestBuilder post = createRequest("wrong", "new secret", "new secret");
mockMvc.perform(post) mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity()) .andExpect(status().isUnprocessableEntity())
.andExpect(view().name("change_password")) .andExpect(view().name("change_password"))
.andExpect(model().attribute("message_code", "unauthorized")); .andExpect(model().attribute("message_code", "unauthorized"));
} }


private void setupSecurityContext() { @Test
Authentication authentication = new UsernamePasswordAuthenticationToken( public void changePassword_PasswordNoveltyViolationReported_NewPasswordSameAsCurrentPassword() throws Exception {
"bob", doThrow(new InvalidPasswordException("Your new password cannot be the same as the old password.")).when(changePasswordService).changePassword("bob", "secret", "new secret");
"secret",
Arrays.asList(UaaAuthority.UAA_USER)
);


SecurityContextHolder.getContext().setAuthentication(authentication); MockHttpServletRequestBuilder post = createRequest("secret", "new secret", "new secret");
mockMvc.perform(post)
.andExpect(status().isUnprocessableEntity())
.andExpect(view().name("change_password"))
.andExpect(model().attribute("message", "Your new password cannot be the same as the old password."));
}

private MockHttpServletRequestBuilder createRequest(String currentPassword, String newPassword, String confirmPassword) {
return post("/change_password.do")
.contentType(APPLICATION_FORM_URLENCODED)
.param("current_password", currentPassword)
.param("new_password", newPassword)
.param("confirm_password", confirmPassword);
} }
} }

0 comments on commit a6593dd

Please sign in to comment.