diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractUser.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractUser.java index 99094418b5..f9efc661c5 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractUser.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/AbstractUser.java @@ -49,7 +49,7 @@ public abstract class AbstractUser extends AbstractActor { protected String email; /** - * URL or identifier of user's avatar image + * URL or identifier of the user's avatar image */ protected String avatar; @@ -141,7 +141,7 @@ public void activateMFA(String type, String secret) { } /** - * Activates Multi-Factor Authentication with enabled state. + * Activates Multi-Factor Authentication with the enabled state. * * @param type MFA type identifier * @param secret MFA secret key @@ -154,7 +154,7 @@ public void activateMFA(String type, String secret, boolean enabled) { * Checks if a credential is enabled. * * @param type credential type to check - * @return true if credential is enabled, false otherwise + * @return true if the credential is enabled, false otherwise */ public boolean isCredentialEnabled(String type) { return false; @@ -200,7 +200,7 @@ public Object getCredentialProperty(String type, String key) { } /** - * Returns user's first and last name concatenated. + * Returns a user's first and last name concatenated. */ @Override public String getName() { @@ -208,7 +208,7 @@ public String getName() { } /** - * Returns user's full name including middle name if present. + * Returns the user's full name including middle name if present. */ @Override public String getFullName() { diff --git a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java index 6c49d9cef4..be637542de 100644 --- a/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java +++ b/nae-object-library/src/main/java/com/netgrif/application/engine/objects/auth/domain/User.java @@ -1,8 +1,6 @@ package com.netgrif.application.engine.objects.auth.domain; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; -import com.netgrif.application.engine.objects.annotations.Indexed; -import com.netgrif.application.engine.objects.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.objects.utils.DateUtils; import com.querydsl.core.annotations.QueryEntity; import lombok.AllArgsConstructor; @@ -30,19 +28,29 @@ @EqualsAndHashCode(callSuper = false) public class User extends AbstractUser implements Serializable { - /** Flag indicating whether the user's email has been verified */ + /** + * Flag indicating whether the user's email has been verified + */ private boolean emailVerified; - /** Current state of the user (e.g., ACTIVE, INACTIVE) */ + /** + * Current state of the user (e.g., ACTIVE, INACTIVE) + */ private UserState state; - /** Timestamp when the user was created */ + /** + * Timestamp when the user was created + */ private LocalDateTime createdAt = LocalDateTime.now(); - /** Timestamp of the last modification to the user */ + /** + * Timestamp of the last modification to the user + */ private LocalDateTime modifiedAt = LocalDateTime.now(); - /** Map containing user's credentials with credential type as key */ + /** + * Map containing user's credentials with credential type as key + */ private Map> credentials = new HashMap<>(); /** @@ -57,6 +65,7 @@ public User() { /** * Constructor creating a User with a specified ObjectId + * * @param id The ObjectId to be assigned to the user */ public User(ObjectId id) { @@ -66,6 +75,7 @@ public User(ObjectId id) { /** * Retrieves the user's authentication token from credentials + * * @return The token string if present, null otherwise */ public String getToken() { @@ -75,6 +85,7 @@ public String getToken() { /** * Sets the user's authentication token + * * @param token The token string to be stored */ public void setToken(String token) { @@ -84,6 +95,7 @@ public void setToken(String token) { /** * Retrieves the encoded password from credentials + * * @return The encoded password string if present, null otherwise */ @Override @@ -97,6 +109,7 @@ public String getPassword() { /** * Sets the user's password (should be already encoded) + * * @param password The encoded password string to store */ @Override @@ -107,6 +120,7 @@ public void setPassword(String password) { /** * Sets the expiration date for the user's token + * * @param expirationDate LocalDateTime when the token should expire */ public void setExpirationDate(LocalDateTime expirationDate) { @@ -122,6 +136,7 @@ public void setExpirationDate(LocalDateTime expirationDate) { /** * Retrieves the token expiration date + * * @return LocalDateTime of token expiration if present, null otherwise */ public LocalDateTime getExpirationDate() { @@ -138,6 +153,7 @@ public LocalDateTime getExpirationDate() { /** * Checks if a specific credential type is enabled + * * @param type The credential type to check * @return true if the credential exists and is enabled, false otherwise */ @@ -149,6 +165,7 @@ public boolean isCredentialEnabled(String type) { /** * Returns a set of enabled MFA method names + * * @return Set of strings representing enabled MFA methods */ public Set getEnabledMFAMethods() { @@ -166,6 +183,7 @@ public Set getEnabledMFAMethods() { /** * Disables a specific credential type + * * @param type The credential type to disable */ @Override @@ -177,7 +195,8 @@ public void disableCredential(String type) { /** * Activates an MFA method with the given secret (enabled by default) - * @param type The MFA type to activate + * + * @param type The MFA type to activate * @param secret The secret key for the MFA method */ @Override @@ -187,8 +206,9 @@ public void activateMFA(String type, String secret) { /** * Activates an MFA method with the given secret and enabled state - * @param type The MFA type to activate - * @param secret The secret key for the MFA method + * + * @param type The MFA type to activate + * @param secret The secret key for the MFA method * @param enabled Whether the MFA method should be enabled * @throws IllegalArgumentException if type or secret is null or empty */ @@ -206,8 +226,9 @@ public void activateMFA(String type, String secret, boolean enabled) { /** * Sets a property for a specific credential type - * @param type The credential type - * @param key The property key + * + * @param type The credential type + * @param key The property key * @param value The property value (must be Serializable) */ @Override @@ -227,8 +248,9 @@ public void setCredentialProperty(String type, String key, Object value) { /** * Retrieves a property value for a specific credential type + * * @param type The credential type - * @param key The property key + * @param key The property key * @return The property value if found, null otherwise */ @Override @@ -239,6 +261,7 @@ public Object getCredentialProperty(String type, String key) { /** * Checks if a specific credential type exists + * * @param type The credential type to check * @return true if the credential exists, false otherwise */ @@ -248,6 +271,7 @@ public boolean hasCredential(String type) { /** * Retrieves a credential by its type + * * @param type The credential type * @return The Credential object if found, null otherwise */ @@ -258,7 +282,8 @@ public Credential getCredential(String type) { /** * Sets the entire credentials map - * @param credentials The new credentials map (null creates empty map) + * + * @param credentials The new credentials map (null creates an empty map) */ public void setCredentials(Map> credentials) { this.credentials = credentials == null ? new HashMap<>() : new HashMap<>(credentials); @@ -266,9 +291,10 @@ public void setCredentials(Map> credentials) { /** * Creates and sets a new string credential - * @param type The credential type - * @param value The credential value - * @param order The credential order + * + * @param type The credential type + * @param value The credential value + * @param order The credential order * @param enabled Whether the credential should be enabled */ @Override @@ -279,7 +305,8 @@ public void setCredential(String type, String value, int order, boolean enabled) /** * Sets a credential with the given key - * @param key The credential key + * + * @param key The credential key * @param credential The credential object to set */ @Override @@ -292,6 +319,7 @@ public void setCredential(String key, Credential credential) { /** * Gets the value of a credential by its key + * * @param key The credential key * @return The credential value if found, null otherwise */ @@ -305,6 +333,7 @@ public Object getCredentialValue(String key) { /** * Removes a credential by its key + * * @param key The credential key to remove */ public void removeCredential(String key) { @@ -317,8 +346,9 @@ public void removeCredential(String key) { /** * Checks if a credential is set and has a value + * * @param key The credential key to check - * @return true if credential exists and has a value, false otherwise + * @return true if a credential exists and has a value, false otherwise */ public boolean isCredentialSet(String key) { if (this.credentials == null) { @@ -330,6 +360,7 @@ public boolean isCredentialSet(String key) { /** * Gets all credential keys + * * @return Set of credential keys, empty set if no credentials exist */ public Set getCredentialsKeys() { @@ -340,10 +371,11 @@ public Set getCredentialsKeys() { } /** - * Checks if the user is in ACTIVE state - * @return true if user state is ACTIVE, false otherwise + * Checks if the user is in an ACTIVE state + * + * @return true if the user state is ACTIVE, false otherwise */ public boolean isActive() { - return this.state.equals(UserState.ACTIVE); + return this.state == UserState.ACTIVE; } -} \ No newline at end of file +} diff --git a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java index 892c6b3295..c11f6cb3ac 100644 --- a/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java +++ b/nae-user-ce/src/main/java/com/netgrif/application/engine/auth/service/UserServiceImpl.java @@ -261,6 +261,19 @@ public List getUserGroups(AbstractActor actor) { return groupService.findAllByIds(actor.getGroupIds(), Pageable.unpaged()).stream().toList(); } + @Override + public AbstractUser changePassword(AbstractUser user, String newPassword, String oldPassword) { + canUpdatePassword(user, newPassword); + + if (!verifyPasswords(user, oldPassword)) { + throw new IllegalArgumentException("Old password does not match."); + } + + log.debug("Setting password for user [{}]", user.getUsername()); + user.setPassword(passwordEncoder.encode(newPassword)); + return saveUser(user); + } + @Override public void addDefaultAuthorities(AbstractUser user) { log.trace("Assigning default authorities to user [{}]", user.getUsername()); @@ -636,4 +649,22 @@ private void resolveRelatedProcessRoles(AbstractUser user) { user.getAuthoritySet().addAll(getUserGroups(user).stream().map(Group::getAuthoritySet).flatMap(Set::stream).collect(Collectors.toSet())); } + private boolean verifyPasswords(AbstractUser user, String password) { + if (password == null) { + throw new IllegalArgumentException("confirmation password is not set"); + } + + log.trace("Verifying password for user [{}]", user.getUsername()); + return passwordEncoder.matches(password, user.getPassword()); + } + + protected void canUpdatePassword(AbstractUser user, String password) { + if (!user.isCredentialEnabled("password")) { + throw new RuntimeException("Password does not exists or authorization is not enabled"); + } + + if (password == null) { + throw new IllegalArgumentException("Password is not set"); + } + } } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java index 359ae6f7b3..5654992c6d 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/service/UserService.java @@ -407,4 +407,14 @@ Page searchAllCoMembers(String query, Collection roles); + + /** + * Resets password for user. + * + * @param user user + * @param newPassword new password + * @param oldPassword old password + * @return the updated user + */ + AbstractUser changePassword(AbstractUser user, String newPassword, String oldPassword); } diff --git a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java index fef0c959fc..42605f8591 100644 --- a/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java +++ b/nae-user-common/src/main/java/com/netgrif/application/engine/auth/web/responsebodies/User.java @@ -4,15 +4,18 @@ import com.netgrif.application.engine.objects.auth.domain.AbstractUser; import com.netgrif.application.engine.objects.auth.domain.Attribute; import com.netgrif.application.engine.objects.auth.domain.Authority; +import com.netgrif.application.engine.objects.auth.domain.Credential; import com.netgrif.application.engine.objects.auth.domain.enums.UserState; import lombok.Data; import java.time.LocalDateTime; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; @Data public class User { + public static final String ATTR_ENABLED_CREDENTIALS = "enabledCredentials"; private String id; private String username; @@ -34,6 +37,20 @@ public class User { protected UserState state; public User(AbstractUser user) { + Attribute> enabledCredentialsAttribute = new Attribute<>(); + if (user instanceof com.netgrif.application.engine.objects.auth.domain.User domainUser) { + Map> credentials = domainUser.getCredentials(); + enabledCredentialsAttribute.setValue( + (credentials == null ? java.util.Map.>of() : credentials) + .values().stream() + .filter(java.util.Objects::nonNull) + .filter(Credential::isEnabled) + .map(Credential::getType) + .filter(java.util.Objects::nonNull) + .collect(Collectors.toSet())); + enabledCredentialsAttribute.setRequired(true); + } + id = user.getStringId(); username = user.getUsername(); realmId = user.getRealmId(); @@ -41,8 +58,13 @@ public User(AbstractUser user) { avatar = user.getAvatar(); firstName = user.getFirstName(); lastName = user.getLastName(); - fullName = user.getName(); - attributes = user.getAttributes(); + fullName = user.getFullName(); + attributes = user.getAttributes() != null + ? new java.util.HashMap<>(user.getAttributes()) + : new java.util.HashMap<>(); + if (enabledCredentialsAttribute.getValue() != null && !enabledCredentialsAttribute.getValue().isEmpty()) { + attributes.put(ATTR_ENABLED_CREDENTIALS, enabledCredentialsAttribute); + } if (user instanceof com.netgrif.application.engine.objects.auth.domain.User u) { createdAt = u.getCreatedAt(); enabled = u.isActive();