Skip to content

Commit

Permalink
SIDM-3397 Expired Reset Password link. (#288)
Browse files Browse the repository at this point in the history
* SIDM-3397 Expired Reset Password link.

* SIDM-3397 Add test.

* SIDM-3397 Fix after merge.

* Code review adjustements.

* Don't show the hyperlink when the data is not provided.
  • Loading branch information
dfourn authored Dec 2, 2019
1 parent 79eb660 commit ac5566a
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 19 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ allprojects {
sourceCompatibility = 1.8
targetCompatibility = 1.8

def idamBomVersion = '1.9.1'
def idamBomVersion = '1.9.4'
ext['tomcat.version'] = '9.0.22'

dependencyManagement {
Expand Down
43 changes: 36 additions & 7 deletions src/main/java/uk/gov/hmcts/reform/idam/web/AppController.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
Expand Down Expand Up @@ -124,6 +125,9 @@ public class AppController {
@Autowired
private ConfigurationProperties configurationProperties;

@Autowired
private ObjectMapper mapper;

@Value("${authentication.secureCookie}")
private Boolean useSecureCookie;

Expand Down Expand Up @@ -254,25 +258,50 @@ public RedirectView logout(final Map<String, Object> model) {
* @should redirect to passwordReset view
*/
@GetMapping(value = "/passwordReset")
public String getPasswordReset(@RequestParam("token") String token, @RequestParam("code") String code) {
return this.passwordReset(token, code);
public String getPasswordReset(@RequestParam("token") String token, @RequestParam("code") String code, Model model) {
return this.passwordReset(token, code, model);
}

/**
* @should redirect to reset password page if token is valid
* @should redirect to token expired page if token is invalid
* @should redirect to token expired page if token is expired
*/
@PostMapping(value = "/passwordReset")
public String passwordReset(@RequestParam("token") String token, @RequestParam("code") String code) {
String nextPage = RESETPASSWORD_VIEW;
public String passwordReset(@RequestParam("token") String token, @RequestParam("code") String code, Model model) {
try {
spiService.validateResetPasswordToken(token, code);
} catch (Exception e) {
nextPage = EXPIRED_PASSWORD_RESET_LINK_VIEW;
return RESETPASSWORD_VIEW;
} catch (HttpClientErrorException e) {
if (HttpStatus.NOT_FOUND.equals(e.getStatusCode())) {
model.addAttribute("forgotPasswordLink", buildUrlFromTheBody(e.getResponseBodyAsString()));
}
}
return nextPage;
return EXPIRED_PASSWORD_RESET_LINK_VIEW;
}

private String buildUrlFromTheBody(String responseBodyAsString) {
try {
uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordDetails request = mapper.readValue(
responseBodyAsString, uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordDetails.class);
if (Strings.isNotEmpty(request.getRedirectUri())) {
return "/reset/forgotpassword?redirectUri=" + request.getRedirectUri() +
"&clientId=" + nullToEmpty(request.getClientId()) +
"&state=" + nullToEmpty(request.getState()) +
"&scope=" + nullToEmpty(request.getScope());
}
} catch (IOException ex) {
log.error("Failed to read returned ForgotPasswordDetails", ex);

}
return "";
}

private String nullToEmpty(Object obj) {
return Objects.toString(obj, "");
}


/**
* @should put in model correct data and return forgot password view
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.springframework.web.util.UriComponentsBuilder;
import uk.gov.hmcts.reform.idam.api.internal.model.ActivationResult;
import uk.gov.hmcts.reform.idam.api.internal.model.ArrayOfServices;
import uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordRequest;
import uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordDetails;
import uk.gov.hmcts.reform.idam.api.internal.model.ResetPasswordRequest;
import uk.gov.hmcts.reform.idam.api.internal.model.ValidateRequest;
import uk.gov.hmcts.reform.idam.api.shared.model.User;
Expand Down Expand Up @@ -306,8 +306,8 @@ public ResponseEntity<String> forgetPassword(final String email, final String re
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);

HttpEntity<ForgotPasswordRequest> entity = new HttpEntity<>(
new ForgotPasswordRequest()
HttpEntity<ForgotPasswordDetails> entity = new HttpEntity<>(
new ForgotPasswordDetails()
.email(email)
.redirectUri(redirectUri)
.clientId(clientId),
Expand All @@ -320,7 +320,7 @@ public ResponseEntity<String> forgetPassword(final String email, final String re
/**
* @should call api with the correct data
*/
public ResponseEntity<String> validateResetPasswordToken(final String token, final String code) throws IOException {
public ResponseEntity<String> validateResetPasswordToken(final String token, final String code) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.add("token", token); //NOSONAR
Expand Down
11 changes: 11 additions & 0 deletions src/main/webapp/WEB-INF/jsp/expiredPasswordResetLink.jsp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
<p class="lede">
<spring:message code="public.user.password.reset.expired.text"/>
</p>
<p>
<spring:message code="public.user.link.expired.text"/>
<c:choose>
<c:when test="${empty forgotPasswordLink}">
<spring:message code="public.user.password.reset.expired.link.caption"/>.
</c:when>
<c:otherwise>
<a href="${forgotPasswordLink}"><spring:message code="public.user.password.reset.expired.link.caption"/></a>.
</c:otherwise>
</c:choose>
</p>
</article>
<script>
sendEvent('Expired Token', 'Expired', 'Password Reset token has expired');
Expand Down
28 changes: 26 additions & 2 deletions src/test/java/uk/gov/hmcts/reform/idam/web/AppControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uk.gov.hmcts.reform.idam.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
Expand All @@ -21,6 +22,7 @@
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import uk.gov.hmcts.reform.idam.api.internal.model.ErrorResponse;
import uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordDetails;
import uk.gov.hmcts.reform.idam.api.internal.model.Service;
import uk.gov.hmcts.reform.idam.web.helper.MvcKeys;
import uk.gov.hmcts.reform.idam.web.model.AuthorizeRequest;
Expand Down Expand Up @@ -801,7 +803,7 @@ public void loginView_shouldReturnErrorPageViewIfOAuth2DetailsAreMissing() throw

/**
* @verifies redirect to reset password page if token is valid
* @see AppController#passwordReset(String, String)
* @see AppController#passwordReset(String, String, Model)
*/
@Test public void passwordReset_shouldRedirectToResetPasswordPageIfTokenIsValid() throws Exception {

Expand All @@ -819,7 +821,7 @@ public void loginView_shouldReturnErrorPageViewIfOAuth2DetailsAreMissing() throw

/**
* @verifies redirect to token expired page if token is invalid
* @see AppController#passwordReset(String, String)
* @see AppController#passwordReset(String, String, Model)
*/
@Test public void passwordReset_shouldRedirectToTokenExpiredPageIfTokenIsInvalid() throws Exception {

Expand All @@ -835,6 +837,28 @@ public void loginView_shouldReturnErrorPageViewIfOAuth2DetailsAreMissing() throw
verify(spiService).validateResetPasswordToken(RESET_PASSWORD_TOKEN, RESET_PASSWORD_CODE);
}

/**
* @verifies redirect to token expired page if token is expired
* @see AppController#passwordReset(String, String, Model)
*/
@Test
public void passwordReset_shouldRedirectToTokenExpiredPageIfTokenIsExpired() throws Exception {
byte[] body = new ObjectMapper().writeValueAsBytes(new ForgotPasswordDetails().redirectUri("1234"));
given(spiService.validateResetPasswordToken(RESET_PASSWORD_TOKEN, RESET_PASSWORD_CODE))
.willThrow(HttpClientErrorException.create(HttpStatus.NOT_FOUND, "", null, body, StandardCharsets.UTF_8));

mockMvc.perform(post(PASSWORD_RESET_ENDPOINT).with(csrf())
.param(ACTION_PARAMETER, UNUSED)
.param(TOKEN_PARAMETER, RESET_PASSWORD_TOKEN)
.param(CODE_PARAMETER, RESET_PASSWORD_CODE))
.andExpect(status().isOk())
.andExpect(view().name(EXPIRED_PASSWORD_RESET_TOKEN_VIEW_NAME))
.andExpect(model().attribute("forgotPasswordLink",
"/reset/forgotpassword?redirectUri=1234&clientId=&state=&scope="));

verify(spiService).validateResetPasswordToken(RESET_PASSWORD_TOKEN, RESET_PASSWORD_CODE);
}

/**
* @verifies put in model redirect uri if service returns http 200 and redirect uri is present in response then return reset password success view
* @see AppController#resetPassword(String, String, String, String, String, Map)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import org.springframework.web.client.RestTemplate;
import uk.gov.hmcts.reform.idam.api.internal.model.ActivationResult;
import uk.gov.hmcts.reform.idam.api.internal.model.ArrayOfServices;
import uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordRequest;
import uk.gov.hmcts.reform.idam.api.internal.model.ForgotPasswordDetails;
import uk.gov.hmcts.reform.idam.api.internal.model.ResetPasswordRequest;
import uk.gov.hmcts.reform.idam.api.internal.model.Service;
import uk.gov.hmcts.reform.idam.api.internal.model.ValidateRequest;
Expand All @@ -34,7 +34,6 @@
import uk.gov.hmcts.reform.idam.web.health.HealthCheckStatus;
import uk.gov.hmcts.reform.idam.web.model.RegisterUserRequest;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
Expand All @@ -53,7 +52,60 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.verify;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.*;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.ACTIVATE_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.ACTIVATE_USER_REQUEST;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.API_LOGIN_UPLIFT_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.API_URL;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.AUTHENTICATE_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.AUTHORIZATION_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.AUTHORIZATION_TOKEN;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.CLIENTID_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.CLIENT_ID;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.CLIENT_ID_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.CODE_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.CUSTOM_SCOPE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.DETAILS_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.FORGOT_PASSWORD_SPI_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.FORGOT_PASSWORD_URI;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.GOOGLE_WEB_ADDRESS;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.HEALTH_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.JWT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.JWT_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.MISSING;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.OAUTH2_AUTHORIZE_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.PASSWORD_ONE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.PASSWORD_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.REDIRECTURI;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.REDIRECT_URI;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.RESET_PASSWORD_CODE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.RESET_PASSWORD_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.RESET_PASSWORD_TOKEN;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.RESET_PASSWORD_URI;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SCOPE_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SELF_REGISTRATION_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SELF_REGISTRATION_RESPONSE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SELF_REGISTRATION_URL;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SERVICES_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SERVICE_CLIENT_ID;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SERVICE_LABEL;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SERVICE_OAUTH2_CLIENT_ID;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SERVICE_OAUTH2_REDIRECT_URI;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.SLASH;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.STATE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.STATE_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.TOKEN_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USERNAME_PARAMETER;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USERS_SELF_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_ACTIVATION_CODE;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_ACTIVATION_TOKEN;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_EMAIL;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_FIRST_NAME;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_IP_ADDRESS;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_LAST_NAME;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_NAME;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.USER_PASSWORD;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.VALIDATE_RESET_PASSWORD_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestConstants.VALIDATE_TOKEN_API_ENDPOINT;
import static uk.gov.hmcts.reform.idam.web.util.TestHelper.anAuthorizedUser;
import static uk.gov.hmcts.reform.idam.web.util.TestHelper.getFoundResponseEntity;
import static uk.gov.hmcts.reform.idam.web.util.TestHelper.getSelfRegisterRequest;
Expand Down Expand Up @@ -234,10 +286,10 @@ public void forgetPassword_shouldCallApiWithTheCorrectParameters() throws Except
spiService.forgetPassword(USER_EMAIL, SERVICE_OAUTH2_REDIRECT_URI, CLIENT_ID);
Thread.sleep(1000); // hack to get around CompletableFuture.supplyAsync()

ArgumentCaptor<HttpEntity<ForgotPasswordRequest>> captor = ArgumentCaptor.forClass(HttpEntity.class);
ArgumentCaptor<HttpEntity<ForgotPasswordDetails>> captor = ArgumentCaptor.forClass(HttpEntity.class);
verify(restTemplate).exchange(eq(FORGOT_PASSWORD_URI), eq(HttpMethod.POST), captor.capture(), eq(String.class));

HttpEntity<ForgotPasswordRequest> actualRequest = captor.getValue();
HttpEntity<ForgotPasswordDetails> actualRequest = captor.getValue();

assertEquals(USER_EMAIL, actualRequest.getBody().getEmail());
assertEquals(SERVICE_OAUTH2_REDIRECT_URI, actualRequest.getBody().getRedirectUri());
Expand Down

0 comments on commit ac5566a

Please sign in to comment.