From f46d5ac3d280496e5937e8884ca694797d4b3a61 Mon Sep 17 00:00:00 2001 From: daveotengo <30934250+daveotengo@users.noreply.github.com> Date: Fri, 1 Nov 2024 12:52:19 +0000 Subject: [PATCH] updated with moble reset password using keycloak --- .../de/symeda/sormas/api/ConfigFacade.java | 2 - .../sormas/api/feature/FeatureType.java | 2 +- .../api/feature/FeatureTypeProperty.java | 3 +- .../de/symeda/sormas/api/user/UserFacade.java | 4 +- .../api/user/UserPasswordChangeDto.java | 33 +++++++ .../app/backend/config/ConfigProvider.java | 1 - .../app/backend/user/UserDtoHelper.java | 6 +- .../app/login/ChangePasswordActivity.java | 2 +- .../sormas/app/rest/UserFacadeRetro.java | 5 +- .../sormas/app/settings/SettingsFragment.java | 2 +- .../backend/common/ConfigFacadeEjb.java | 23 ----- .../sormas/backend/user/KeycloakService.java | 87 +++++++++++++++++++ .../sormas/backend/user/UserFacadeEjb.java | 20 +++-- .../sormas/backend/user/UserService.java | 2 + .../user/event/PasswordChangeEvent.java | 40 +++++++++ .../backend/user/UserFacadeEjbTest.java | 8 +- .../sormas/rest/resources/UserResource.java | 12 ++- .../symeda/sormas/ui/user/UserController.java | 45 ++-------- 18 files changed, 207 insertions(+), 90 deletions(-) create mode 100644 sormas-api/src/main/java/de/symeda/sormas/api/user/UserPasswordChangeDto.java create mode 100644 sormas-backend/src/main/java/de/symeda/sormas/backend/user/event/PasswordChangeEvent.java diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/ConfigFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/ConfigFacade.java index 329f4647975..496c696ef02 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/ConfigFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/ConfigFacade.java @@ -126,8 +126,6 @@ public interface ConfigFacade { String getAuthenticationProvider(); - KeycloakClientConfig getKeycloakCredentials(); - boolean isAuthenticationProviderUserSyncAtStartupEnabled(); String getAuthenticationProviderSyncedNewUserRole(); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java index bf489f2c309..c66348bce71 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureType.java @@ -332,7 +332,7 @@ public enum FeatureType { CONTACT_TRACING }, null, ImmutableMap.of(FeatureTypeProperty.S2S_SHARING, Boolean.FALSE)), - SELF_PASSWORD_RESET(true, false, null, null, ImmutableMap.of(FeatureTypeProperty.AUTHENTICATION_PROVIDER, "SORMAS")); + SELF_PASSWORD_RESET(true, false, null, null,null); public static final FeatureType[] SURVEILLANCE_FEATURE_TYPES = { FeatureType.CASE_SURVEILANCE, diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureTypeProperty.java b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureTypeProperty.java index 546f5caa994..f4c0878e202 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureTypeProperty.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/feature/FeatureTypeProperty.java @@ -30,8 +30,7 @@ public enum FeatureTypeProperty { SHARE_IMMUNIZATIONS(Boolean.class), SHARE_REPORTS(Boolean.class), FETCH_MODE(Boolean.class), - FORCE_AUTOMATIC_PROCESSING(Boolean.class), - AUTHENTICATION_PROVIDER(String.class); + FORCE_AUTOMATIC_PROCESSING(Boolean.class); private final Class returnType; diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserFacade.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserFacade.java index 193b75457ad..85dc1c60d70 100644 --- a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserFacade.java +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserFacade.java @@ -52,9 +52,9 @@ public interface UserFacade { String resetPassword(String uuid); - String updateUserPassword(String uuid, String newPassword, String currentPassword); + String updateUserPassword(String uuid, String newPassword); - boolean validatePassword(String uuid, String password); + boolean validateCurrentPassword(String password); String checkPasswordStrength(String password); diff --git a/sormas-api/src/main/java/de/symeda/sormas/api/user/UserPasswordChangeDto.java b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserPasswordChangeDto.java new file mode 100644 index 00000000000..29d0984e05a --- /dev/null +++ b/sormas-api/src/main/java/de/symeda/sormas/api/user/UserPasswordChangeDto.java @@ -0,0 +1,33 @@ +package de.symeda.sormas.api.user; + +import java.io.Serializable; + +public class UserPasswordChangeDto implements Serializable { + private static final long serialVersionUID = 6269655187128160377L; + + private String uuid; + private String newPassword; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } + + public UserPasswordChangeDto(String uuid, String newPassword) { + this.uuid = uuid; + this.newPassword = newPassword; + } + + public UserPasswordChangeDto(){} +} diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/config/ConfigProvider.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/config/ConfigProvider.java index afd3ae0fd54..935467aeeb4 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/config/ConfigProvider.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/config/ConfigProvider.java @@ -320,7 +320,6 @@ public static void setNewPassword(String password) { instance.password = password; DatabaseHelper.getConfigDao().createOrUpdate(new Config(KEY_PASSWORD, password)); - } public static void setPin(String pin) { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/user/UserDtoHelper.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/user/UserDtoHelper.java index 93e5191a292..48072ab7984 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/user/UserDtoHelper.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/backend/user/UserDtoHelper.java @@ -24,6 +24,7 @@ import de.symeda.sormas.api.PostResponse; import de.symeda.sormas.api.user.UserDto; +import de.symeda.sormas.api.user.UserPasswordChangeDto; import de.symeda.sormas.api.user.UserReferenceDto; import de.symeda.sormas.app.backend.common.AdoDtoHelper; import de.symeda.sormas.app.backend.common.DatabaseHelper; @@ -133,8 +134,9 @@ public static UserReferenceDto toReferenceDto(User ado) { return dto; } - public static Call saveNewPassword(String uuid, String newPassword, String currentPassword) throws NoConnectionException { - return RetroProvider.getUserFacade().saveNewPassword(uuid, newPassword, currentPassword); + public static Call saveNewPassword(String uuid, String newPassword) throws NoConnectionException { + System.out.println(uuid+" pw: "+newPassword); + return RetroProvider.getUserFacade().saveNewPassword(new UserPasswordChangeDto(uuid,newPassword)); } public static Call generatePassword() throws NoConnectionException { diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/login/ChangePasswordActivity.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/login/ChangePasswordActivity.java index 01e5a4de4c6..db601c92b4f 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/login/ChangePasswordActivity.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/login/ChangePasswordActivity.java @@ -309,7 +309,7 @@ public void savePassword(View view) { RetroProvider.connectAsyncHandled(this, true, true, result -> { if (Boolean.TRUE.equals(result)) { try { - executeSaveNewPasswordCall(UserDtoHelper.saveNewPassword(ConfigProvider.getUser().getUuid(), newPassword, currentPassword), this); + executeSaveNewPasswordCall(UserDtoHelper.saveNewPassword(ConfigProvider.getUser().getUuid(), newPassword), this); setNewPassword(newPassword); } catch (Exception e) { binding.actionPasswordStrength.setVisibility(View.VISIBLE); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/rest/UserFacadeRetro.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/rest/UserFacadeRetro.java index e9ed0663c57..95a4b59d658 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/rest/UserFacadeRetro.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/rest/UserFacadeRetro.java @@ -23,6 +23,7 @@ import retrofit2.http.GET; import retrofit2.http.POST; import retrofit2.http.Path; +import de.symeda.sormas.api.user.UserPasswordChangeDto; /** * Created by Martin Wahnschaffe on 07.06.2016. @@ -38,8 +39,8 @@ public interface UserFacadeRetro { @GET("users/uuids") Call> pullUuids(); - @POST("users/saveNewPassword/{uuid}/{newPassword}/{currentPassword}") - Call saveNewPassword(@Path("uuid") String uuid, @Path("newPassword") String newPassword, @Path("currentPassword") String currentPassword); + @POST("users/saveNewPassword") + Call saveNewPassword(@Body UserPasswordChangeDto userPasswordChangeDto); @GET("users/generatePassword") Call generatePassword(); diff --git a/sormas-app/app/src/main/java/de/symeda/sormas/app/settings/SettingsFragment.java b/sormas-app/app/src/main/java/de/symeda/sormas/app/settings/SettingsFragment.java index f2cae4cee20..f2dd70648c5 100644 --- a/sormas-app/app/src/main/java/de/symeda/sormas/app/settings/SettingsFragment.java +++ b/sormas-app/app/src/main/java/de/symeda/sormas/app/settings/SettingsFragment.java @@ -162,7 +162,7 @@ public void onResume() { binding.settingsServerUrl.setVisibility(!hasServerUrl() || isShowDevOptions() ? View.VISIBLE : View.GONE); binding.changePin.setVisibility(hasUser ? View.VISIBLE : View.GONE); binding.changePassword.setVisibility( - hasUser && DatabaseHelper.getFeatureConfigurationDao().getStringPropertyValue(FeatureType.SELF_PASSWORD_RESET, FeatureTypeProperty.AUTHENTICATION_PROVIDER).equalsIgnoreCase("SORMAS") && !DatabaseHelper.getFeatureConfigurationDao().isFeatureDisabled(FeatureType.SELF_PASSWORD_RESET) ? View.VISIBLE : View.GONE); + hasUser && !DatabaseHelper.getFeatureConfigurationDao().isFeatureDisabled(FeatureType.SELF_PASSWORD_RESET) ? View.VISIBLE : View.GONE); binding.resynchronizeData.setVisibility(hasUser ? View.VISIBLE : View.GONE); binding.showSyncLog.setVisibility(hasUser ? View.VISIBLE : View.GONE); binding.logout.setVisibility(hasUser && isShowDevOptions() ? View.VISIBLE : View.GONE); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/common/ConfigFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/common/ConfigFacadeEjb.java index 0bd53127629..082182554ce 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/common/ConfigFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/common/ConfigFacadeEjb.java @@ -53,10 +53,6 @@ import de.symeda.sormas.api.utils.DataHelper; import de.symeda.sormas.api.utils.InfoProvider; import de.symeda.sormas.api.utils.VersionHelper; -import com.jayway.jsonpath.JsonPath; -import de.symeda.sormas.api.KeycloakClientConfig; -import org.eclipse.microprofile.config.ConfigProvider; -import java.util.Optional; /** * Provides the application configuration settings @@ -696,25 +692,6 @@ public String getAuthenticationProvider() { return getProperty(AUTHENTICATION_PROVIDER, "SORMAS"); } - public KeycloakClientConfig getKeycloakCredentials() { - Optional oidcJson = ConfigProvider.getConfig().getOptionalValue("sormas.backend.security.oidc.json", String.class); - if (oidcJson.isEmpty()) { - logger.warn( - "Undefined KEYCLOAK configuration for sormas.backend.security.oidc.json. Configure the property or disable the KEYCLOAK authentication provider."); - return null; - } - - String OIDC_REALM = "realm"; - String OIDC_SERVER_URL = "auth-server-url"; - - String keycloakJsonConfig = oidcJson.get(); - String keycloakUrl = JsonPath.read(keycloakJsonConfig, OIDC_SERVER_URL); - String realm = JsonPath.read(keycloakJsonConfig, OIDC_REALM); - String clientId = "sormas-ui"; - - return new KeycloakClientConfig(keycloakUrl,realm,clientId); - } - @Override public boolean isAuthenticationProviderUserSyncAtStartupEnabled() { return getBoolean(AUTHENTICATION_PROVIDER_USER_SYNC_AT_STARTUP, false); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/KeycloakService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/KeycloakService.java index 80cc8aa939d..107a9dc4f88 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/KeycloakService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/KeycloakService.java @@ -65,6 +65,7 @@ import de.symeda.sormas.backend.user.event.SyncUsersFromProviderEvent; import de.symeda.sormas.backend.user.event.UserCreateEvent; import de.symeda.sormas.backend.user.event.UserUpdateEvent; +import de.symeda.sormas.backend.user.event.PasswordChangeEvent; /** * @author Alex Vidrean @@ -233,6 +234,74 @@ public void handlePasswordResetEvent(@Observes PasswordResetEvent passwordResetE } } + public boolean validateCurrentPassword(String username, String password){ + Optional oidcJson = ConfigProvider.getConfig().getOptionalValue("sormas.backend.security.oidc.json", String.class); + + if (oidcJson.isEmpty()) { + logger.warn( + "Undefined KEYCLOAK configuration for sormas.backend.security.oidc.json. Configure the property or disable the KEYCLOAK authentication provider."); + return false; + } + + String keycloakJsonConfig = oidcJson.get(); + String secret=JsonPath.read(keycloakJsonConfig, OIDC_SECRET); + String realm=JsonPath.read(keycloakJsonConfig, OIDC_REALM); + String clientId="sormas-backend"; + String serverUrl=JsonPath.read(keycloakJsonConfig, OIDC_SERVER_URL); + + try { + + Keycloak keycloak = KeycloakBuilder.builder() + .serverUrl(serverUrl) + .realm(realm) + .clientId(clientId) + .clientSecret(secret) + .username(username) + .password(password) + .grantType("password") + .build(); + + try { + keycloak.tokenManager().getAccessToken(); + logger.info("Token retrieved Successfully"); + keycloak.tokenManager().logout(); + return true; + } catch (Exception e) { + logger.error("Token retrieval failed...Kindly Check if you have turned on Direct access grant" + + " on 'sormas-backend' client on the Keycloak console", e); + throw e; + } + } catch (Exception e) { + logger.error("Authentication attempt failed", e); + } + return false; + } + public void handlePasswordChangeEvent(User user) { + Optional oidcJson = ConfigProvider.getConfig().getOptionalValue("sormas.backend.security.oidc.json", String.class); + + if (oidcJson.isEmpty()) { + logger.warn( + "Undefined KEYCLOAK configuration for sormas.backend.security.oidc.json. Configure the property or disable the KEYCLOAK authentication provider."); + return; + } + + String password = user.getPassword(); + String username=user.getUserName(); + + Optional keycloak =Optional.of(keycloakInstance); + Optional userRepresentation = getUserByUsername(keycloak.get(), username); + if (userRepresentation.isEmpty()) { + logger.warn("Cannot find user or email for user with username {} to reset the password", user.getUserName()); + return; + } + + Optional adminClient = getKeycloakInstance(); + adminClient.ifPresent(value -> userRepresentation.ifPresent(existing -> { + setCredentialsForChangePassword(existing, password, user.getSeed()); + value.realm(REALM_NAME).users().get(existing.getId()).update(existing); + })); + } + public void handleSyncUsersFromProviderEvent(@Observes SyncUsersFromProviderEvent syncUsersFromProviderEvent) { Optional keycloak = getKeycloakInstance(); if (keycloak.isEmpty()) { @@ -465,6 +534,24 @@ private void setCredentials(UserRepresentation userRepresentation, String passwo userRepresentation.setCredentials(singletonList(credential)); } + private void setCredentialsForChangePassword(UserRepresentation userRepresentation, String password, String salt) { + JsonObjectBuilder secretData = Json.createObjectBuilder(); + secretData.add("value", password); + secretData.add("salt", Base64.getEncoder().encodeToString(salt.getBytes())); + + JsonObjectBuilder credentialData = Json.createObjectBuilder(); + credentialData.add("hashIterations", 1); + credentialData.add("algorithm", "sormas-sha256"); + + CredentialRepresentation credential = new CredentialRepresentation(); + credential.setType(CredentialRepresentation.PASSWORD); + // let user use the password he set + credential.setTemporary(false); + credential.setSecretData(secretData.build().toString()); + credential.setCredentialData(credentialData.build().toString()); + userRepresentation.setCredentials(singletonList(credential)); + } + private Optional getKeycloakInstance() { return Optional.ofNullable(keycloakInstance); } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserFacadeEjb.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserFacadeEjb.java index 3718aa26000..8323ad93e50 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserFacadeEjb.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserFacadeEjb.java @@ -152,6 +152,7 @@ import de.symeda.sormas.backend.util.QueryHelper; import de.symeda.sormas.backend.util.RightsAllowed; import de.symeda.sormas.backend.util.PasswordValidator; +import de.symeda.sormas.backend.user.event.PasswordChangeEvent; @Stateless(name = "UserFacade") public class UserFacadeEjb implements UserFacade { @@ -166,6 +167,8 @@ public class UserFacadeEjb implements UserFacade { @EJB private UserService userService; @EJB + private KeycloakService keycloakService; + @EJB private LocationFacadeEjbLocal locationFacade; @EJB private RegionService regionService; @@ -202,6 +205,8 @@ public class UserFacadeEjb implements UserFacade { @Inject private Event passwordResetEvent; @Inject + private Event passwordChangeEvent; + @Inject private Event syncUsersFromProviderEventEvent; public static UserDto toDto(User source) { @@ -1193,13 +1198,15 @@ public List getUserRights(String userUuid) { @Override @PermitAll - public boolean validatePassword(String uuid, String password) { + public boolean validateCurrentPassword(String password) { User user = userService.getCurrentUser(); - if (user != null) { + AuthProvider authProvider = AuthProvider.getProvider(configFacade); + if(KEYCLOAK.equalsIgnoreCase(authProvider.getName())) { + return keycloakService.validateCurrentPassword(user.getUserName(),password); + } return DataHelper.equal(user.getPassword(), PasswordHelper.encodePassword(password, user.getSeed())); } - return false; } @@ -1216,9 +1223,12 @@ public boolean isPasswordWeak(String password) { @PermitAll @Override - public String updateUserPassword(String uuid, String password, String currentPassword) { + public String updateUserPassword(String uuid, String password) { String updatePassword = userService.updatePassword(uuid, password); - passwordResetEvent.fire(new PasswordResetEvent(userService.getByUuid(uuid))); + AuthProvider authProvider = AuthProvider.getProvider(configFacade); + if(KEYCLOAK.equalsIgnoreCase(authProvider.getName())) { + passwordChangeEvent.fire(new PasswordChangeEvent(userService.getByUuid(uuid))); + } return updatePassword; } diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserService.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserService.java index 8b352835109..2b103168727 100644 --- a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserService.java +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserService.java @@ -667,6 +667,8 @@ public String updatePassword(String userUuid, String password) { if (user == null) { throw new IllegalArgumentException("User not found for UUID: " + userUuid); } + logger.info(user.getUserName()); + user.setPassword(PasswordHelper.encodePassword(password, user.getSeed())); ensurePersisted(user); diff --git a/sormas-backend/src/main/java/de/symeda/sormas/backend/user/event/PasswordChangeEvent.java b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/event/PasswordChangeEvent.java new file mode 100644 index 00000000000..af4af75c0fa --- /dev/null +++ b/sormas-backend/src/main/java/de/symeda/sormas/backend/user/event/PasswordChangeEvent.java @@ -0,0 +1,40 @@ +/* + * SORMAS® - Surveillance Outbreak Response Management & Analysis System + * Copyright © 2016-2020 Helmholtz-Zentrum für Infektionsforschung GmbH (HZI) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.symeda.sormas.backend.user.event; + +import de.symeda.sormas.backend.user.User; + +/** + * Event fired whenever a user's password is changed. + * + * @author David Oteng + * @since 31-October-2024 + */ +public class PasswordChangeEvent { + + private final User user; + public PasswordChangeEvent(User user) { + this.user = user; + } + + public User getUser() { + return user; + } + +} diff --git a/sormas-backend/src/test/java/de/symeda/sormas/backend/user/UserFacadeEjbTest.java b/sormas-backend/src/test/java/de/symeda/sormas/backend/user/UserFacadeEjbTest.java index da4daa93812..f65544d04df 100644 --- a/sormas-backend/src/test/java/de/symeda/sormas/backend/user/UserFacadeEjbTest.java +++ b/sormas-backend/src/test/java/de/symeda/sormas/backend/user/UserFacadeEjbTest.java @@ -490,7 +490,7 @@ public void testUpdateUserPassword() { String newPassword = "newPassword"; String currentPassword = testUser.getPassword(); String uuid = testUser.getUuid(); - String result = getUserFacade().updateUserPassword(uuid, newPassword, currentPassword); + String result = getUserFacade().updateUserPassword(uuid, newPassword); // Assert assertEquals(newPassword, result); @@ -502,7 +502,7 @@ public void testUpdateUserPassword() { UserDto updatedUserDto = getUserFacade().getByUuid(uuid); loginWith(updatedUserDto); // Login again with changed password - assertTrue(getUserFacade().validatePassword(uuid, newPassword)); // Validate the new password + assertTrue(getUserFacade().validateCurrentPassword(newPassword)); // Validate the new password } @@ -524,7 +524,7 @@ void testValidatePasswordValid() { // Act UserDto loggedInUser = getUserFacade().getByUuid(user.getUuid()); loginWith(loggedInUser); - boolean result = getUserFacade().validatePassword(loggedInUser.getUuid(), "password"); // Use plain password for validation + boolean result = getUserFacade().validateCurrentPassword("password"); // Use plain password for validation // Assert assertTrue(result); @@ -533,7 +533,7 @@ void testValidatePasswordValid() { @Test void testValidatePasswordInvalid() { // Act - boolean result = getUserFacade().validatePassword("uuid", "password"); + boolean result = getUserFacade().validateCurrentPassword("password"); // Assert assertFalse(result); diff --git a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/UserResource.java b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/UserResource.java index 13d43a99383..d3a317c15a7 100644 --- a/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/UserResource.java +++ b/sormas-rest/src/main/java/de/symeda/sormas/rest/resources/UserResource.java @@ -18,6 +18,7 @@ import java.util.Date; import java.util.List; +import javax.validation.Valid; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -36,6 +37,7 @@ import de.symeda.sormas.api.user.UserReferenceWithTaskNumbersDto; import de.symeda.sormas.api.user.UserRight; import io.swagger.v3.oas.annotations.parameters.RequestBody; +import de.symeda.sormas.api.user.UserPasswordChangeDto; /** * @see Jersey documentation @@ -99,12 +101,9 @@ public List getUserRights(@PathParam("uuid") String uuid) { } @POST - @Path("/saveNewPassword/{uuid}/{newPassword}/{currentPassword}") - public String saveNewPassword( - @PathParam("uuid") String uuid, - @PathParam("newPassword") String newPassword, - @PathParam("currentPassword") String currentPassword) { - return FacadeProvider.getUserFacade().updateUserPassword(uuid, newPassword, currentPassword); + @Path("/saveNewPassword") + public String saveNewPassword(UserPasswordChangeDto userPasswordChangeDto) { + return FacadeProvider.getUserFacade().updateUserPassword(userPasswordChangeDto.getUuid(), userPasswordChangeDto.getNewPassword()); } @GET @@ -112,5 +111,4 @@ public String saveNewPassword( public String generatePassword() { return FacadeProvider.getUserFacade().generatePassword(); } - } diff --git a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserController.java b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserController.java index cf5738e618b..97777a68c52 100644 --- a/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserController.java +++ b/sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserController.java @@ -17,7 +17,6 @@ *******************************************************************************/ package de.symeda.sormas.ui.user; -import de.symeda.sormas.api.feature.FeatureTypeProperty; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -70,8 +69,6 @@ import de.symeda.sormas.ui.UserProvider; import java.util.Objects; import de.symeda.sormas.ui.ControllerProvider; -import org.slf4j.LoggerFactory; -import de.symeda.sormas.api.KeycloakClientConfig; public class UserController { @@ -303,7 +300,7 @@ public void buttonClick(ClickEvent event) { } }, ValoTheme.BUTTON_LINK); } - + public ConfirmationComponent getResetPasswordConfirmationComponent( String userUuid, String userEmail, @@ -466,8 +463,8 @@ public void disableAllSelectedItems(Collection selectedRows, UserGrid u } } - public void showUpdatePassword(String userUuid, String userEmail, String password, String currentPassword) { - FacadeProvider.getUserFacade().updateUserPassword(userUuid, password, currentPassword); + public void showUpdatePassword(String userUuid, String userEmail, String password) { + FacadeProvider.getUserFacade().updateUserPassword(userUuid, password); if (isGeneratePassword) { if (StringUtils.isBlank(userEmail) || AuthProvider.getProvider(FacadeProvider.getConfigFacade()).isDefaultProvider()) { showPasswordResetInternalSuccessPopup(password); @@ -503,44 +500,18 @@ private void showPopupWindow(String header, String message, Integer width, Boole } public Button createUpdatePasswordButton() { - return ButtonHelper.createIconButton(Captions.userResetPassword, VaadinIcons.UNLOCK, new ClickListener() { + return ButtonHelper.createIconButton(Captions.userResetPassword, VaadinIcons.UNLOCK, new ClickListener() { private static final long serialVersionUID = 1L; @Override public void buttonClick(ClickEvent event) { - - String authenticationProviderFromFeatureConfiguration= FacadeProvider.getFeatureConfigurationFacade() - .getProperty(FeatureType.SELF_PASSWORD_RESET, null, - FeatureTypeProperty.AUTHENTICATION_PROVIDER, String.class); - if (authenticationProviderFromFeatureConfiguration.equalsIgnoreCase(AuthProvider.SORMAS)) { - popUpWindow = VaadinUiUtil.showPopupWindow(getUpdatePasswordComponent()); - popUpWindow.setCaption(I18nProperties.getString(Strings.headingChangePassword)); - } - else if (authenticationProviderFromFeatureConfiguration.equalsIgnoreCase(AuthProvider.KEYCLOAK)) { - String resetUrl = buildKeycloakForgotPasswordUrl(); - Page.getCurrent().setLocation(resetUrl); - } + popUpWindow = VaadinUiUtil.showPopupWindow(getUpdatePasswordComponent()); + popUpWindow.setCaption(I18nProperties.getString(Strings.headingChangePassword)); } }, ValoTheme.BUTTON_LINK); } - private String buildKeycloakForgotPasswordUrl() { - try { - KeycloakClientConfig keycloakClientConfig = FacadeProvider.getConfigFacade().getKeycloakCredentials(); - - return keycloakClientConfig.getKeycloakUrl() + - "/realms/" + - keycloakClientConfig.getRealm() + - "/login-actions/reset-credentials" + - "?client_id=" + - keycloakClientConfig.getClientId(); - } catch (Exception e) { - LoggerFactory.getLogger(getClass()).error("Could not build Keycloak reset password URL", e); - throw new RuntimeException("Could not build Keycloak reset password URL", e); - } - } - public Button generatePasswordButton() { return ButtonHelper.createIconButton(Captions.userGeneratePassword, VaadinIcons.UNLOCK, new ClickListener() { @@ -599,12 +570,12 @@ public CommitDiscardWrapperComponent getUpdatePasswordCompon UserDto changedUser = form.getValue(); if (!Objects.equals(changedUser.getConfirmPassword(), changedUser.getNewPassword())) { Notification.show(I18nProperties.getString(Strings.messageNewPasswordDoesNotMatchFailed), Notification.Type.ERROR_MESSAGE); - } else if (!FacadeProvider.getUserFacade().validatePassword(user.getUuid(), changedUser.getCurrentPassword())) { + } else if (!FacadeProvider.getUserFacade().validateCurrentPassword(changedUser.getCurrentPassword())) { Notification.show(I18nProperties.getString(Strings.messageWrongCurrentPassword), Notification.Type.ERROR_MESSAGE); } else if (FacadeProvider.getUserFacade().isPasswordWeak(changedUser.getNewPassword())) { Notification.show(I18nProperties.getString(Strings.messageNewPasswordFailed), Notification.Type.ERROR_MESSAGE); } else { - showUpdatePassword(user.getUuid(), user.getUserEmail(), changedUser.getNewPassword(), changedUser.getCurrentPassword()); + showUpdatePassword(user.getUuid(), user.getUserEmail(), changedUser.getNewPassword()); } } });