From d6501ffa05f0a909bc0d52db9a8cf5ac00e5ad35 Mon Sep 17 00:00:00 2001 From: strehle Date: Tue, 21 Jul 2026 22:58:38 +0200 Subject: [PATCH 1/8] Refactor password change --- .../uaa/account/ResetPasswordController.java | 6 +++ .../uaa/account/UaaResetPasswordService.java | 4 ++ .../account/PasswordResetEndpointTest.java | 50 +++++++++++++++++-- .../login/ResetPasswordControllerTest.java | 22 ++++++-- 4 files changed, 75 insertions(+), 7 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index 360dc613fc5..a6c466bb5ea 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -38,6 +38,7 @@ import java.sql.Timestamp; import java.util.Map; +import static org.cloudfoundry.identity.uaa.account.UaaResetPasswordService.FORGOT_PASSWORD_INTENT_PREFIX; import static org.springframework.util.StringUtils.hasText; @Controller @@ -197,6 +198,11 @@ private ExpiringCode checkIfUserExists(ExpiringCode code) { logger.debug("reset_password ExpiringCode object is null. Aborting."); return null; } + String intent = code.getIntent(); + if (intent == null || !intent.startsWith(FORGOT_PASSWORD_INTENT_PREFIX)) { + logger.debug("reset_password ExpiringCode[{}] intent is not a forgot-password intent. Aborting.", code.getCode()); + return null; + } if (!hasText(code.getData())) { logger.debug("reset_password ExpiringCode[{}] data string is null or empty. Aborting.", code.getCode()); return null; diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java index b0256990e4f..a99bc7ed2ba 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java @@ -88,6 +88,10 @@ public void resetUserPassword(String userId, String password) { } private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expiringCode, String newPassword) { + String intent = expiringCode.getIntent(); + if (intent == null || !intent.startsWith(FORGOT_PASSWORD_INTENT_PREFIX)) { + throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); + } String userId; String userName; Date passwordLastModified; diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java index 956354f892c..012df0d78f7 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java @@ -36,7 +36,9 @@ import java.util.Date; import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.account.UaaResetPasswordService.FORGOT_PASSWORD_INTENT_PREFIX; import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.AUTOLOGIN; +import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.INVITATION; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -275,7 +277,8 @@ void creatingAPasswordResetWithAUsernameContainingSpecialCharacters() throws Exc @ValueSource(strings = {"/password_change", "/password_change/"}) void changingAPasswordWithAValidCode(String url) throws Exception { ExpiringCode code = new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), - "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", null); + "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", + FORGOT_PASSWORD_INTENT_PREFIX + "eyedee"); when(mockExpiringCodeStore.retrieveCode("secret_code", currentZoneId)).thenReturn(code); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); @@ -323,7 +326,8 @@ void changingPasswordWithInvalidCode() throws Exception { @Test void changingAPasswordForUnverifiedUser() throws Exception { ExpiringCode code = new ExpiringCode("secret_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), - "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", null); + "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", + FORGOT_PASSWORD_INTENT_PREFIX + "eyedee"); when(mockExpiringCodeStore.retrieveCode("secret_code", currentZoneId)).thenReturn(code); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); @@ -369,7 +373,7 @@ void passwordsMustSatisfyPolicy() throws Exception { when(mockExpiringCodeStore.retrieveCode("emailed_code", currentZoneId)) .thenReturn(new ExpiringCode("emailed_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", - null)); + FORGOT_PASSWORD_INTENT_PREFIX + "eyedee")); MockHttpServletRequestBuilder post = post("/password_change") .contentType(APPLICATION_JSON) @@ -391,7 +395,7 @@ void changePassword_Returns422UnprocessableEntity_NewPasswordSameAsOld() throws when(mockExpiringCodeStore.retrieveCode("emailed_code", currentZoneId)) .thenReturn(new ExpiringCode("emailed_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", - null)); + FORGOT_PASSWORD_INTENT_PREFIX + "eyedee")); ScimUser scimUser = new ScimUser("eyedee", "user@example.com", "User", "Man"); scimUser.setMeta(new ScimMeta(new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24)), 0)); @@ -414,4 +418,42 @@ void changePassword_Returns422UnprocessableEntity_NewPasswordSameAsOld() throws assertThat(JsonUtils.readTree(result.getResponse().getContentAsString())) .isEqualTo(JsonUtils.readTree(new JSONObject().put("error_description", "Your new password cannot be the same as the old password.").put("message", "Your new password cannot be the same as the old password.").put("error", "invalid_password").toString())); } + + @Test + void changePassword_withInvitationCode_returns422() throws Exception { + String inviteData = "{\"user_id\":\"eyedee\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}"; + ExpiringCode inviteCode = new ExpiringCode("invite_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), + inviteData, INVITATION.name()); + when(mockExpiringCodeStore.retrieveCode("invite_code", currentZoneId)).thenReturn(inviteCode); + + MockHttpServletRequestBuilder post = post("/password_change") + .contentType(APPLICATION_JSON) + .content("{\"code\":\"invite_code\",\"new_password\":\"new_secret\"}") + .accept(APPLICATION_JSON); + + MvcResult result = mockMvc.perform(post) + .andExpect(status().isUnprocessableEntity()) + .andReturn(); + assertThat(JsonUtils.readTree(result.getResponse().getContentAsString())) + .isEqualTo(JsonUtils.readTree(new JSONObject() + .put("error_description", "Sorry, your reset password link is no longer valid. Please request a new one") + .put("message", "Sorry, your reset password link is no longer valid. Please request a new one") + .put("error", "invalid_code") + .toString())); + } + + @Test + void changePassword_withNullIntentCode_returns422() throws Exception { + ExpiringCode nullIntentCode = new ExpiringCode("null_intent_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), + "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", null); + when(mockExpiringCodeStore.retrieveCode("null_intent_code", currentZoneId)).thenReturn(nullIntentCode); + + MockHttpServletRequestBuilder post = post("/password_change") + .contentType(APPLICATION_JSON) + .content("{\"code\":\"null_intent_code\",\"new_password\":\"new_secret\"}") + .accept(APPLICATION_JSON); + + mockMvc.perform(post) + .andExpect(status().isUnprocessableEntity()); + } } diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index 31dd93cbdbd..863d76fb1c4 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -51,6 +51,8 @@ import java.sql.Timestamp; import static org.assertj.core.api.Assertions.assertThat; +import static org.cloudfoundry.identity.uaa.account.UaaResetPasswordService.FORGOT_PASSWORD_INTENT_PREFIX; +import static org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType.INVITATION; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.contains; import static org.mockito.Mockito.eq; @@ -290,7 +292,7 @@ void instructions() throws Exception { @Test void resetPasswordPage() throws Exception { - ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), FORGOT_PASSWORD_INTENT_PREFIX + "some-user-id", IdentityZoneHolder.get().getId()); MvcResult result = mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", code.getCode())) .andExpect(status().isOk()) .andDo(print()) @@ -305,7 +307,7 @@ void resetPasswordPage() throws Exception { @Test void resetPasswordPageWithPriorHeadRequest() throws Exception { - ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), FORGOT_PASSWORD_INTENT_PREFIX + "some-user-id", IdentityZoneHolder.get().getId()); mockMvc.perform(head("/reset_password").param("email", "user@example.com").param("code", code.getCode())) .andExpect(status().isOk()); MvcResult result = mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", code.getCode())) @@ -322,7 +324,7 @@ void resetPasswordPageWithPriorHeadRequest() throws Exception { @Test void resetPasswordPageDuplicate() throws Exception { - ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode("{\"user_id\" : \"some-user-id\"}", new Timestamp(System.currentTimeMillis() + 1000000), FORGOT_PASSWORD_INTENT_PREFIX + "some-user-id", IdentityZoneHolder.get().getId()); mockMvc.perform(get("/reset_password").param("email", "user@example.com").param("code", code.getCode())) .andExpect(status().isOk()) .andExpect(view().name("reset_password")); @@ -339,6 +341,20 @@ void resetPasswordPageWhenExpiringCodeNull() throws Exception { .andExpect(model().attribute("message_code", "bad_code")); } + @Test + void resetPasswordPage_withInvitationCode_returns422() throws Exception { + ExpiringCode inviteCode = codeStore.generateCode( + "{\"user_id\":\"some-user-id\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}", + new Timestamp(System.currentTimeMillis() + 1000000), + INVITATION.name(), + IdentityZoneHolder.get().getId()); + + mockMvc.perform(get("/reset_password").param("code", inviteCode.getCode())) + .andExpect(status().isUnprocessableEntity()) + .andExpect(view().name("forgot_password")) + .andExpect(model().attribute("message_code", "bad_code")); + } + @EnableWebMvc @Import(ThymeleafConfig.class) static class ContextConfiguration implements WebMvcConfigurer { From d6a02138199715d52701f0fc9570fd58c96d43f4 Mon Sep 17 00:00:00 2001 From: strehle Date: Tue, 21 Jul 2026 23:21:02 +0200 Subject: [PATCH 2/8] test fix --- .../login/UaaResetPasswordServiceTests.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java index 1240f6d8da0..d91db552a39 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java @@ -203,7 +203,8 @@ void resetPassword_InvalidPasswordException_NewPasswordSameAsOld() { user.setMeta(new ScimMeta(new Date(), new Date(), 0)); user.setPrimaryEmail("foo@example.com"); ExpiringCode expiringCode = new ExpiringCode("good_code", - new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "{\"user_id\":\"user-id\",\"username\":\"username\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", null); + new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "{\"user_id\":\"user-id\",\"username\":\"username\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", + UaaResetPasswordService.FORGOT_PASSWORD_INTENT_PREFIX + "user-id"); when(codeStore.retrieveCode("good_code", currentZoneId)).thenReturn(expiringCode); when(scimUserProvisioning.retrieve("user-id", currentZoneId)).thenReturn(user); when(scimUserProvisioning.checkPasswordMatches("user-id", "Passwo3dAsOld", currentZoneId)) @@ -236,6 +237,18 @@ void resetPassword_InvalidCodeData() { } } + @Test + void resetPassword_rejectsNonForgotPasswordIntent() { + ExpiringCode inviteCode = new ExpiringCode("good_code", + new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), + "{\"user_id\":\"user-id\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}", "INVITATION"); + + assertThatThrownBy(() -> uaaResetPasswordService.resetPassword(inviteCode, "new_secret")) + .isInstanceOf(InvalidCodeException.class) + .hasMessage("Sorry, your reset password link is no longer valid. Please request a new one"); + verify(scimUserProvisioning, times(0)).changePassword(anyString(), any(), anyString(), anyString()); + } + @Test void resetPassword_WithInvalidClientId() { ExpiringCode code = setupResetPassword("invalid_client", "redirect.example.com"); @@ -320,7 +333,8 @@ private ExpiringCode setupResetPassword(String clientId, String redirectUri) { String zoneId = currentZoneId; when(scimUserProvisioning.retrieve(eq("usermans-id"), eq(zoneId))).thenReturn(user); ExpiringCode code = new ExpiringCode("code", new Timestamp(System.currentTimeMillis()), - "{\"user_id\":\"usermans-id\",\"username\":\"userman\",\"passwordModifiedTime\":null,\"client_id\":\"" + clientId + "\",\"redirect_uri\":\"" + redirectUri + "\"}", null); + "{\"user_id\":\"usermans-id\",\"username\":\"userman\",\"passwordModifiedTime\":null,\"client_id\":\"" + clientId + "\",\"redirect_uri\":\"" + redirectUri + "\"}", + UaaResetPasswordService.FORGOT_PASSWORD_INTENT_PREFIX + "usermans-id"); when(codeStore.retrieveCode(eq("secret_code"), anyString())).thenReturn(code); SecurityContext securityContext = mock(SecurityContext.class); when(securityContext.getAuthentication()).thenReturn(new MockAuthentication()); From da19d3b5c8933bc79e7cd8c210634a35790e3b80 Mon Sep 17 00:00:00 2001 From: strehle Date: Wed, 22 Jul 2026 00:04:03 +0200 Subject: [PATCH 3/8] test fix --- .../ResetPasswordControllerMockMvcTests.java | 14 +++++++------- ...PasswordControllerMockMvcZonePathTests.java | 18 +++++++++--------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java index dba525ad069..8946a4acf6b 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -83,7 +83,7 @@ void resettingAPasswordUsingUsernameToEnsureNoModification() throws Exception { .hasSize(1); PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, true)) .andExpect(status().isFound()) @@ -99,7 +99,7 @@ void resettingPasswordDoesNotUpdateLastLogonTime() throws Exception { Long lastLogonBeforeReset = users.getFirst().getLastLogonTime(); PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, true)) .andExpect(status().isFound()) @@ -124,7 +124,7 @@ void resettingAPasswordFailsWhenUsernameChanged() throws Exception { ScimUser user = users.getFirst(); PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); String formerUsername = user.getUserName(); user.setUserName("newusername"); @@ -319,7 +319,7 @@ void resettingAPasswordFailsWhenPasswordChanged() throws Exception { ScimUserProvisioning userProvisioning = webApplicationContext.getBean(ScimUserProvisioning.class); Thread.sleep(1000 - (System.currentTimeMillis() % 1000) + 10); //because password last modified is second only PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); userProvisioning.changePassword(user.getId(), "secret", "secr3t", IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true)) @@ -344,7 +344,7 @@ void resettingAPasswordUsingTimestampForUserModification() throws Exception { assertThat(users) .hasSize(1); PasswordChange passwordChange = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); MockHttpServletRequestBuilder post = createChangePasswordRequest(users.getFirst(), code, true, "newpassw0rD", "newpassw0rD"); @@ -362,10 +362,10 @@ void resetPassword_ReturnsUnprocessableEntity_NewPasswordSameAsOld() throws Exce .hasSize(1); ScimUser user = users.getFirst(); PasswordChange passwordChange = new PasswordChange(user.getId(), user.getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")); - code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")) .andExpect(status().isUnprocessableEntity()) .andExpect(request().attribute("message", "Your new password cannot be the same as the old password.")) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java index d69de878c46..3e9a53dcc85 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java @@ -97,7 +97,7 @@ void resettingAPasswordUsingUsernameToEnsureNoModification() throws Exception { .hasSize(1); PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, true)) .andExpect(status().isFound()) @@ -113,7 +113,7 @@ void resettingPasswordDoesNotUpdateLastLogonTime() throws Exception { Long lastLogonBeforeReset = users.getFirst().getLastLogonTime(); PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, true)) .andExpect(status().isFound()) @@ -138,7 +138,7 @@ void resettingAPasswordFailsWhenUsernameChanged() throws Exception { ScimUser user = users.getFirst(); PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); String formerUsername = user.getUserName(); user.setUserName("newusername"); @@ -333,7 +333,7 @@ void resettingAPasswordFailsWhenPasswordChanged() throws Exception { ScimUserProvisioning userProvisioning = webApplicationContext.getBean(ScimUserProvisioning.class); Thread.sleep(1000 - (System.currentTimeMillis() % 1000) + 10); //because password last modified is second only PasswordChange change = new PasswordChange(user.getId(), user.getUserName(), user.getPasswordLastModified(), "", ""); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + 50000), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); userProvisioning.changePassword(user.getId(), "secret", "secr3t", IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true)) @@ -358,7 +358,7 @@ void resettingAPasswordUsingTimestampForUserModification() throws Exception { assertThat(users) .hasSize(1); PasswordChange passwordChange = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); MockHttpServletRequestBuilder post = createChangePasswordRequest(users.getFirst(), code, true, "newpassw0rD", "newpassw0rD"); @@ -376,10 +376,10 @@ void resetPassword_ReturnsUnprocessableEntity_NewPasswordSameAsOld() throws Exce .hasSize(1); ScimUser user = users.getFirst(); PasswordChange passwordChange = new PasswordChange(user.getId(), user.getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")); - code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")) .andExpect(status().isUnprocessableEntity()) .andExpect(request().attribute("message", "Your new password cannot be the same as the old password.")) @@ -403,10 +403,10 @@ void resetPassword_ReturnsUnprocessableEntity_NewPasswordNotAccordingToPolicy() .hasSize(1); ScimUser user = users.getFirst(); PasswordChange passwordChange = new PasswordChange(user.getId(), user.getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")); - code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "a", "a")) .andExpect(status().isUnprocessableEntity()) .andExpect(request().attribute("message", "Password must be at least 3 characters in length.")) From 2642cb33d15b1dbbf412586dc27b18bcfe23f20d Mon Sep 17 00:00:00 2001 From: strehle Date: Thu, 23 Jul 2026 23:03:46 +0200 Subject: [PATCH 4/8] review --- .../identity/uaa/account/ResetPasswordController.java | 2 +- .../uaa/account/PasswordResetEndpointTest.java | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index a6c466bb5ea..5050dba0518 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -200,7 +200,7 @@ private ExpiringCode checkIfUserExists(ExpiringCode code) { } String intent = code.getIntent(); if (intent == null || !intent.startsWith(FORGOT_PASSWORD_INTENT_PREFIX)) { - logger.debug("reset_password ExpiringCode[{}] intent is not a forgot-password intent. Aborting.", code.getCode()); + logger.debug("reset_password ExpiringCode intent is not a forgot-password intent. Aborting."); return null; } if (!hasText(code.getData())) { diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java index 012df0d78f7..0edfe7f2b73 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java @@ -453,7 +453,14 @@ void changePassword_withNullIntentCode_returns422() throws Exception { .content("{\"code\":\"null_intent_code\",\"new_password\":\"new_secret\"}") .accept(APPLICATION_JSON); - mockMvc.perform(post) - .andExpect(status().isUnprocessableEntity()); + MvcResult result = mockMvc.perform(post) + .andExpect(status().isUnprocessableEntity()) + .andReturn(); + assertThat(JsonUtils.readTree(result.getResponse().getContentAsString())) + .isEqualTo(JsonUtils.readTree(new JSONObject() + .put("error_description", "Sorry, your reset password link is no longer valid. Please request a new one") + .put("message", "Sorry, your reset password link is no longer valid. Please request a new one") + .put("error", "invalid_code") + .toString())); } } From 2ccad8d49ffb2c5a398b8ebdb3f62a237417565f Mon Sep 17 00:00:00 2001 From: strehle Date: Thu, 23 Jul 2026 23:06:22 +0200 Subject: [PATCH 5/8] review --- .../identity/uaa/account/ResetPasswordController.java | 4 ++++ .../identity/uaa/account/UaaResetPasswordService.java | 3 +++ .../identity/uaa/login/ResetPasswordControllerTest.java | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java index 5050dba0518..b10f38d66b7 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/ResetPasswordController.java @@ -214,6 +214,10 @@ private ExpiringCode checkIfUserExists(ExpiringCode code) { return null; } String userId = data.get("user_id"); + if (!intent.equals(FORGOT_PASSWORD_INTENT_PREFIX + userId)) { + logger.debug("reset_password ExpiringCode intent does not match user_id. Aborting."); + return null; + } try { userDatabase.retrieveUserById(userId); } catch (UsernameNotFoundException _) { diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java index a99bc7ed2ba..f9d68b60969 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java @@ -104,6 +104,9 @@ private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expir throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); } userId = change.getUserId(); + if (!intent.equals(FORGOT_PASSWORD_INTENT_PREFIX + userId)) { + throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); + } userName = change.getUsername(); passwordLastModified = change.getPasswordModifiedTime(); clientId = change.getClientId(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java index 863d76fb1c4..72e63ad0263 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerTest.java @@ -342,7 +342,7 @@ void resetPasswordPageWhenExpiringCodeNull() throws Exception { } @Test - void resetPasswordPage_withInvitationCode_returns422() throws Exception { + void resetPasswordPageWithInvitationCodeReturns422() throws Exception { ExpiringCode inviteCode = codeStore.generateCode( "{\"user_id\":\"some-user-id\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}", new Timestamp(System.currentTimeMillis() + 1000000), From c259579800a5ecc8a0784a0e1f00bfa9e3ca24c4 Mon Sep 17 00:00:00 2001 From: strehle Date: Fri, 24 Jul 2026 07:31:46 +0200 Subject: [PATCH 6/8] review --- .../identity/uaa/account/PasswordResetEndpointTest.java | 4 ++-- .../uaa/login/ResetPasswordControllerMockMvcTests.java | 7 ++++--- .../login/ResetPasswordControllerMockMvcZonePathTests.java | 3 ++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java index 0edfe7f2b73..03e0e1c65af 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/account/PasswordResetEndpointTest.java @@ -420,7 +420,7 @@ void changePassword_Returns422UnprocessableEntity_NewPasswordSameAsOld() throws } @Test - void changePassword_withInvitationCode_returns422() throws Exception { + void changingPasswordWithInvitationCodeReturns422() throws Exception { String inviteData = "{\"user_id\":\"eyedee\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}"; ExpiringCode inviteCode = new ExpiringCode("invite_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), inviteData, INVITATION.name()); @@ -443,7 +443,7 @@ void changePassword_withInvitationCode_returns422() throws Exception { } @Test - void changePassword_withNullIntentCode_returns422() throws Exception { + void changingPasswordWithNullIntentCodeReturns422() throws Exception { ExpiringCode nullIntentCode = new ExpiringCode("null_intent_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), "{\"user_id\":\"eyedee\",\"username\":\"user@example.com\",\"passwordModifiedTime\":null,\"client_id\":\"\",\"redirect_uri\":\"\"}", null); when(mockExpiringCodeStore.retrieveCode("null_intent_code", currentZoneId)).thenReturn(nullIntentCode); diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java index 8946a4acf6b..9aac6697162 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcTests.java @@ -331,7 +331,8 @@ void resettingAPasswordNoCsrfParameter() throws Exception { List users = webApplicationContext.getBean(ScimUserProvisioning.class).query("username eq \"marissa\"", IdentityZoneHolder.get().getId()); assertThat(users) .hasSize(1); - ExpiringCode code = codeStore.generateCode(users.getFirst().getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, false)) .andExpect(status().isFound()) @@ -389,10 +390,10 @@ void resetPassword_ReturnsUnprocessableEntity_NewPasswordNotAccordingToPolicy() .hasSize(1); ScimUser user = users.getFirst(); PasswordChange passwordChange = new PasswordChange(user.getId(), user.getUserName(), null, null, null); - ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "d3faultPasswd", "d3faultPasswd")); - code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + code = codeStore.generateCode(JsonUtils.writeValueAsString(passwordChange), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + user.getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(user, code, true, "a", "a")) .andExpect(status().isUnprocessableEntity()) .andExpect(request().attribute("message", "Password must be at least 3 characters in length.")) diff --git a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java index 3e9a53dcc85..3a7c42c245f 100644 --- a/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java +++ b/uaa/src/test/java/org/cloudfoundry/identity/uaa/login/ResetPasswordControllerMockMvcZonePathTests.java @@ -345,7 +345,8 @@ void resettingAPasswordNoCsrfParameter() throws Exception { List users = webApplicationContext.getBean(ScimUserProvisioning.class).query("username eq \"marissa\"", IdentityZoneHolder.get().getId()); assertThat(users) .hasSize(1); - ExpiringCode code = codeStore.generateCode(users.getFirst().getId(), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), null, IdentityZoneHolder.get().getId()); + PasswordChange change = new PasswordChange(users.getFirst().getId(), users.getFirst().getUserName(), users.getFirst().getPasswordLastModified(), "", ""); + ExpiringCode code = codeStore.generateCode(JsonUtils.writeValueAsString(change), new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), FORGOT_PASSWORD_INTENT_PREFIX + users.getFirst().getId(), IdentityZoneHolder.get().getId()); mockMvc.perform(createChangePasswordRequest(users.getFirst(), code, false)) .andExpect(status().isFound()) From b6f24bcf8d91956b8652ecb0c9e228e347191783 Mon Sep 17 00:00:00 2001 From: strehle Date: Fri, 24 Jul 2026 07:52:35 +0200 Subject: [PATCH 7/8] review --- .../identity/uaa/account/UaaResetPasswordService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java index f9d68b60969..912b9a5d304 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java @@ -90,7 +90,7 @@ public void resetUserPassword(String userId, String password) { private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expiringCode, String newPassword) { String intent = expiringCode.getIntent(); if (intent == null || !intent.startsWith(FORGOT_PASSWORD_INTENT_PREFIX)) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); + throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); } String userId; String userName; @@ -101,11 +101,11 @@ private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expir try { change = JsonUtils.readValue(expiringCode.getData(), PasswordChange.class); } catch (JsonUtils.JsonUtilException _) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); + throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); } userId = change.getUserId(); if (!intent.equals(FORGOT_PASSWORD_INTENT_PREFIX + userId)) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", 422); + throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); } userName = change.getUsername(); passwordLastModified = change.getPasswordModifiedTime(); From 28ebc9f8ae6c202cf1861232c6925914d7657493 Mon Sep 17 00:00:00 2001 From: strehle Date: Fri, 24 Jul 2026 08:43:40 +0200 Subject: [PATCH 8/8] review --- .../identity/uaa/account/UaaResetPasswordService.java | 7 ++++--- .../identity/uaa/login/UaaResetPasswordServiceTests.java | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java index 912b9a5d304..a7c51da5b32 100644 --- a/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java +++ b/server/src/main/java/org/cloudfoundry/identity/uaa/account/UaaResetPasswordService.java @@ -88,9 +88,10 @@ public void resetUserPassword(String userId, String password) { } private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expiringCode, String newPassword) { + final String invalidCodeMessage = "Sorry, your reset password link is no longer valid. Please request a new one"; String intent = expiringCode.getIntent(); if (intent == null || !intent.startsWith(FORGOT_PASSWORD_INTENT_PREFIX)) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); + throw new InvalidCodeException("invalid_code", invalidCodeMessage, UNPROCESSABLE_ENTITY.value()); } String userId; String userName; @@ -101,11 +102,11 @@ private ResetPasswordResponse changePasswordCodeAuthenticated(ExpiringCode expir try { change = JsonUtils.readValue(expiringCode.getData(), PasswordChange.class); } catch (JsonUtils.JsonUtilException _) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); + throw new InvalidCodeException("invalid_code", invalidCodeMessage, UNPROCESSABLE_ENTITY.value()); } userId = change.getUserId(); if (!intent.equals(FORGOT_PASSWORD_INTENT_PREFIX + userId)) { - throw new InvalidCodeException("invalid_code", "Sorry, your reset password link is no longer valid. Please request a new one", UNPROCESSABLE_ENTITY.value()); + throw new InvalidCodeException("invalid_code", invalidCodeMessage, UNPROCESSABLE_ENTITY.value()); } userName = change.getUsername(); passwordLastModified = change.getPasswordModifiedTime(); diff --git a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java index d91db552a39..82f94df1aaf 100644 --- a/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java +++ b/server/src/test/java/org/cloudfoundry/identity/uaa/login/UaaResetPasswordServiceTests.java @@ -11,6 +11,7 @@ import org.cloudfoundry.identity.uaa.client.UaaClientDetails; import org.cloudfoundry.identity.uaa.codestore.ExpiringCode; import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeStore; +import org.cloudfoundry.identity.uaa.codestore.ExpiringCodeType; import org.cloudfoundry.identity.uaa.oauth.common.util.RandomValueStringGenerator; import org.cloudfoundry.identity.uaa.provider.NoSuchClientException; import org.cloudfoundry.identity.uaa.scim.ScimMeta; @@ -241,7 +242,7 @@ void resetPassword_InvalidCodeData() { void resetPassword_rejectsNonForgotPasswordIntent() { ExpiringCode inviteCode = new ExpiringCode("good_code", new Timestamp(System.currentTimeMillis() + UaaResetPasswordService.PASSWORD_RESET_LIFETIME), - "{\"user_id\":\"user-id\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}", "INVITATION"); + "{\"user_id\":\"user-id\",\"client_id\":\"invite-client\",\"created_new_user\":\"false\"}", ExpiringCodeType.INVITATION.name()); assertThatThrownBy(() -> uaaResetPasswordService.resetPassword(inviteCode, "new_secret")) .isInstanceOf(InvalidCodeException.class)