Skip to content

Commit

Permalink
updated with moble reset password using keycloak
Browse files Browse the repository at this point in the history
  • Loading branch information
daveotengo committed Nov 1, 2024
1 parent a2e5b05 commit f46d5ac
Show file tree
Hide file tree
Showing 18 changed files with 207 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,6 @@ public interface ConfigFacade {

String getAuthenticationProvider();

KeycloakClientConfig getKeycloakCredentials();

boolean isAuthenticationProviderUserSyncAtStartupEnabled();

String getAuthenticationProviderSyncedNewUserRole();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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(){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -133,8 +134,9 @@ public static UserReferenceDto toReferenceDto(User ado) {
return dto;
}

public static Call<String> saveNewPassword(String uuid, String newPassword, String currentPassword) throws NoConnectionException {
return RetroProvider.getUserFacade().saveNewPassword(uuid, newPassword, currentPassword);
public static Call<String> saveNewPassword(String uuid, String newPassword) throws NoConnectionException {
System.out.println(uuid+" pw: "+newPassword);
return RetroProvider.getUserFacade().saveNewPassword(new UserPasswordChangeDto(uuid,newPassword));
}

public static Call<String> generatePassword() throws NoConnectionException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -38,8 +39,8 @@ public interface UserFacadeRetro {
@GET("users/uuids")
Call<List<String>> pullUuids();

@POST("users/saveNewPassword/{uuid}/{newPassword}/{currentPassword}")
Call<String> saveNewPassword(@Path("uuid") String uuid, @Path("newPassword") String newPassword, @Path("currentPassword") String currentPassword);
@POST("users/saveNewPassword")
Call<String> saveNewPassword(@Body UserPasswordChangeDto userPasswordChangeDto);

@GET("users/generatePassword")
Call<String> generatePassword();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -696,25 +692,6 @@ public String getAuthenticationProvider() {
return getProperty(AUTHENTICATION_PROVIDER, "SORMAS");
}

public KeycloakClientConfig getKeycloakCredentials() {
Optional<String> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -233,6 +234,74 @@ public void handlePasswordResetEvent(@Observes PasswordResetEvent passwordResetE
}
}

public boolean validateCurrentPassword(String username, String password){
Optional<String> 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<String> 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> keycloak =Optional.of(keycloakInstance);
Optional<UserRepresentation> 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<Keycloak> 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> keycloak = getKeycloakInstance();
if (keycloak.isEmpty()) {
Expand Down Expand Up @@ -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<Keycloak> getKeycloakInstance() {
return Optional.ofNullable(keycloakInstance);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down Expand Up @@ -202,6 +205,8 @@ public class UserFacadeEjb implements UserFacade {
@Inject
private Event<PasswordResetEvent> passwordResetEvent;
@Inject
private Event<PasswordChangeEvent> passwordChangeEvent;
@Inject
private Event<SyncUsersFromProviderEvent> syncUsersFromProviderEventEvent;

public static UserDto toDto(User source) {
Expand Down Expand Up @@ -1193,13 +1198,15 @@ public List<UserRight> 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;
}

Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <https://www.gnu.org/licenses/>.
*/

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;
}

}
Loading

0 comments on commit f46d5ac

Please sign in to comment.