From af63377a1e7339f5c8f5680064dbea6f9ac46d6a Mon Sep 17 00:00:00 2001 From: Orlando M Guerreiro Date: Wed, 4 Jun 2025 21:38:42 +0100 Subject: [PATCH] root/resilient#3 Melhorias no registo de utilizadores --- .../com/oguerreiro/resilient/domain/User.java | 39 ++++++++++++++-- .../security/DomainUserDetailsService.java | 43 +++++++++++++++--- .../saml2/Saml2AuthenticationHandler.java | 7 +++ .../resilient/service/UserService.java | 2 + .../resilient/service/dto/AdminUserDTO.java | 44 ++++++++++++++++--- .../20250604123000_altered_entity_User.xml | 31 +++++++++++++ .../resources/config/liquibase/master.xml | 1 + .../user-management-update.component.html | 28 ++++++++++++ .../user-management-update.component.ts | 2 + .../user-management/user-management.model.ts | 2 + .../webapp/i18n/pt-pt/user-management.json | 2 + 11 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 src/main/resources/config/liquibase/changelog/20250604123000_altered_entity_User.xml diff --git a/src/main/java/com/oguerreiro/resilient/domain/User.java b/src/main/java/com/oguerreiro/resilient/domain/User.java index 3b80242..f49190a 100644 --- a/src/main/java/com/oguerreiro/resilient/domain/User.java +++ b/src/main/java/com/oguerreiro/resilient/domain/User.java @@ -76,6 +76,12 @@ public class User extends AbstractAuditingEntity implements Serializable { @Column(nullable = false) private boolean activated = false; + @Column(name = "allow_user_pwd_auth") + private boolean allowUserPwdAuthentication = false; + + @Column(name = "allow_saml_auth") + private boolean allowSamlAuthentication = false; + @Size(min = 2, max = 10) @Column(name = "lang_key", length = 10) private String langKey; @@ -183,6 +189,22 @@ public class User extends AbstractAuditingEntity implements Serializable { return activationKey; } + public void setAllowSamlAuthentication(boolean allowSamlAuthentication) { + this.allowSamlAuthentication = allowSamlAuthentication; + } + + public boolean getAllowSamlAuthentication() { + return allowSamlAuthentication; + } + + public void setAllowUserPwdAuthentication(boolean allowUserPwdAuthentication) { + this.allowUserPwdAuthentication = allowUserPwdAuthentication; + } + + public boolean getAllowUserPwdAuthentication() { + return allowUserPwdAuthentication; + } + public void setActivationKey(String activationKey) { this.activationKey = activationKey; } @@ -260,8 +282,19 @@ public class User extends AbstractAuditingEntity implements Serializable { // prettier-ignore @Override public String toString() { - return "User{" + "login='" + login + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' - + ", email='" + email + '\'' + ", imageUrl='" + imageUrl + '\'' + ", activated='" + activated + '\'' - + ", langKey='" + langKey + '\'' + ", activationKey='" + activationKey + '\'' + "}"; + //@formatter:off + return "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", allowSamlAuthentication='" + allowSamlAuthentication + '\'' + + ", allowUserPwdAuthentication='" + allowUserPwdAuthentication + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}"; + //@formatter:on } } diff --git a/src/main/java/com/oguerreiro/resilient/security/DomainUserDetailsService.java b/src/main/java/com/oguerreiro/resilient/security/DomainUserDetailsService.java index daf631d..949ea18 100644 --- a/src/main/java/com/oguerreiro/resilient/security/DomainUserDetailsService.java +++ b/src/main/java/com/oguerreiro/resilient/security/DomainUserDetailsService.java @@ -2,6 +2,7 @@ package com.oguerreiro.resilient.security; import java.util.List; import java.util.Locale; +import java.util.concurrent.atomic.AtomicBoolean; import org.hibernate.Hibernate; import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; @@ -66,16 +67,44 @@ public class DomainUserDetailsService implements UserDetailsService { public UserDetails loadUserByUsername(final String login) { log.debug("Authenticating {}", login); + UserDetails userDetails = null; + AtomicBoolean allowUserPwdAuthentication = new AtomicBoolean(false); + if (new EmailValidator().isValid(login, null)) { - return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login).map( - user -> createResilientSecurityUser(login, user)).orElseThrow( - () -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); + //@formatter:off + userDetails = userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login) + .map(user -> { + allowUserPwdAuthentication.set(user.getAllowUserPwdAuthentication()); + return createResilientSecurityUser(login, user); + }) + .orElseThrow( + () -> new UsernameNotFoundException("User with email " + login + " was not found in the database") + ); + //@formatter:on + } else { + String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); + //@formatter:off + userDetails = userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin) + .map(user -> { + allowUserPwdAuthentication.set(user.getAllowUserPwdAuthentication()); + return createResilientSecurityUser(lowercaseLogin, user); + }) + .orElseThrow( + () -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database") + ); + //@formatter:on } - String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); - return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin).map( - user -> createResilientSecurityUser(lowercaseLogin, user)).orElseThrow( - () -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")); + if (userDetails == null) { + throw new UsernameNotFoundException("User " + login + " was not found in the database"); + } + + if (!allowUserPwdAuthentication.get()) { + // The user can't login with User + Password + throw new UsernameNotFoundException("User " + login + " not allowed for login with User + Password."); + } + + return userDetails; } private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, diff --git a/src/main/java/com/oguerreiro/resilient/security/saml2/Saml2AuthenticationHandler.java b/src/main/java/com/oguerreiro/resilient/security/saml2/Saml2AuthenticationHandler.java index 6de36c4..3d2b3db 100644 --- a/src/main/java/com/oguerreiro/resilient/security/saml2/Saml2AuthenticationHandler.java +++ b/src/main/java/com/oguerreiro/resilient/security/saml2/Saml2AuthenticationHandler.java @@ -84,6 +84,13 @@ public class Saml2AuthenticationHandler implements AuthenticationSuccessHandler, User user = userRepository.findOneByLogin(username).orElse(null); if (user != null) { + // This user can login with SAML2 ? + if (!user.getAllowSamlAuthentication()) { + log.error("The user '" + username + "' it's not allowed to login with SAML2 authentication."); + this.invalidateLogin(request, response); + return; + } + //@formatter:off // User found. Setup permissions from resilient config authorities = user.getAuthorities().stream() diff --git a/src/main/java/com/oguerreiro/resilient/service/UserService.java b/src/main/java/com/oguerreiro/resilient/service/UserService.java index bd8fda4..c2f26b8 100644 --- a/src/main/java/com/oguerreiro/resilient/service/UserService.java +++ b/src/main/java/com/oguerreiro/resilient/service/UserService.java @@ -201,6 +201,8 @@ public class UserService { } user.setImageUrl(userDTO.getImageUrl()); user.setActivated(userDTO.isActivated()); + user.setAllowSamlAuthentication(userDTO.isAllowSamlAuthentication()); + user.setAllowUserPwdAuthentication(userDTO.isAllowUserPwdAuthentication()); user.setLangKey(userDTO.getLangKey()); Set managedAuthorities = user.getAuthorities(); managedAuthorities.clear(); diff --git a/src/main/java/com/oguerreiro/resilient/service/dto/AdminUserDTO.java b/src/main/java/com/oguerreiro/resilient/service/dto/AdminUserDTO.java index 7b2248b..6cd5b5b 100644 --- a/src/main/java/com/oguerreiro/resilient/service/dto/AdminUserDTO.java +++ b/src/main/java/com/oguerreiro/resilient/service/dto/AdminUserDTO.java @@ -47,6 +47,8 @@ public class AdminUserDTO implements Serializable { private String imageUrl; private boolean activated = false; + private boolean allowUserPwdAuthentication = false; + private boolean allowSamlAuthentication = false; @Size(min = 2, max = 10) private String langKey; @@ -76,6 +78,8 @@ public class AdminUserDTO implements Serializable { this.lastName = user.getLastName(); this.email = user.getEmail(); this.activated = user.isActivated(); + this.allowSamlAuthentication = user.getAllowSamlAuthentication(); + this.allowUserPwdAuthentication = user.getAllowUserPwdAuthentication(); this.imageUrl = user.getImageUrl(); this.langKey = user.getLangKey(); this.createdBy = user.getCreatedBy(); @@ -221,13 +225,43 @@ public class AdminUserDTO implements Serializable { this.parentOrganization = parentOrganization; } + public boolean isAllowUserPwdAuthentication() { + return allowUserPwdAuthentication; + } + + public void setAllowUserPwdAuthentication(boolean allowUserPwdAuthentication) { + this.allowUserPwdAuthentication = allowUserPwdAuthentication; + } + + public boolean isAllowSamlAuthentication() { + return allowSamlAuthentication; + } + + public void setAllowSamlAuthentication(boolean allowSamlAuthentication) { + this.allowSamlAuthentication = allowSamlAuthentication; + } + // prettier-ignore @Override public String toString() { - return "AdminUserDTO{" + "login='" + login + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName - + '\'' + ", email='" + email + '\'' + ", imageUrl='" + imageUrl + '\'' + ", activated=" + activated - + ", langKey='" + langKey + '\'' + ", createdBy=" + createdBy + ", createdDate=" + createdDate - + ", lastModifiedBy='" + lastModifiedBy + '\'' + ", lastModifiedDate=" + lastModifiedDate + ", authorities=" - + authorities + "}"; + //@formatter:off + return "AdminUserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", allowSamlAuthentication=" + allowSamlAuthentication + + ", allowUserPwdAuthentication=" + allowUserPwdAuthentication + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + //@formatter: + } } diff --git a/src/main/resources/config/liquibase/changelog/20250604123000_altered_entity_User.xml b/src/main/resources/config/liquibase/changelog/20250604123000_altered_entity_User.xml new file mode 100644 index 0000000..664680d --- /dev/null +++ b/src/main/resources/config/liquibase/changelog/20250604123000_altered_entity_User.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml index 4909c76..bdb2aaf 100644 --- a/src/main/resources/config/liquibase/master.xml +++ b/src/main/resources/config/liquibase/master.xml @@ -104,5 +104,6 @@ + diff --git a/src/main/webapp/app/admin/user-management/update/user-management-update.component.html b/src/main/webapp/app/admin/user-management/update/user-management-update.component.html index 59ce362..9f024e1 100644 --- a/src/main/webapp/app/admin/user-management/update/user-management-update.component.html +++ b/src/main/webapp/app/admin/user-management/update/user-management-update.component.html @@ -137,6 +137,34 @@ +
+ +
+ +
+ +
+ diff --git a/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts b/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts index b9cf9cc..10e5000 100644 --- a/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts +++ b/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts @@ -52,6 +52,8 @@ export default class UserManagementUpdateComponent implements OnInit { langKey: new FormControl(userTemplate.langKey, { nonNullable: true }), authorities: new FormControl(userTemplate.authorities, { nonNullable: true }), securityGroup: new FormControl(userTemplate.securityGroup, { nonNullable: true }), + allowSamlAuthentication: new FormControl(userTemplate.allowSamlAuthentication, { nonNullable: true }), + allowUserPwdAuthentication: new FormControl(userTemplate.allowUserPwdAuthentication, { nonNullable: true }), }); private userService = inject(UserManagementService); diff --git a/src/main/webapp/app/admin/user-management/user-management.model.ts b/src/main/webapp/app/admin/user-management/user-management.model.ts index 0cffa49..b179f11 100644 --- a/src/main/webapp/app/admin/user-management/user-management.model.ts +++ b/src/main/webapp/app/admin/user-management/user-management.model.ts @@ -14,6 +14,8 @@ export interface IUser { lastModifiedBy?: string; lastModifiedDate?: Date; securityGroup?: ISecurityGroup; + allowUserPwdAuthentication?: boolean; + allowSamlAuthentication?: boolean; } export class User implements IUser { diff --git a/src/main/webapp/i18n/pt-pt/user-management.json b/src/main/webapp/i18n/pt-pt/user-management.json index c253534..53a84d6 100644 --- a/src/main/webapp/i18n/pt-pt/user-management.json +++ b/src/main/webapp/i18n/pt-pt/user-management.json @@ -20,6 +20,8 @@ "lastName": "Último nome", "email": "Email", "activated": "Ativo", + "allowSamlAuthentication": "Permite autenticação por SAML2 ?", + "allowUserPwdAuthentication": "Permite autenticação por Utlizador/Password ?", "deactivated": "Inativo", "profiles": "Perfis", "langKey": "Linguagem",