root/resilient#3 Melhorias no registo de utilizadores

This commit is contained in:
Orlando M Guerreiro 2025-06-04 21:38:42 +01:00
parent 2aa75f0e13
commit af63377a1e
11 changed files with 186 additions and 15 deletions

View file

@ -76,6 +76,12 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
@Column(nullable = false) @Column(nullable = false)
private boolean activated = 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) @Size(min = 2, max = 10)
@Column(name = "lang_key", length = 10) @Column(name = "lang_key", length = 10)
private String langKey; private String langKey;
@ -183,6 +189,22 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
return activationKey; 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) { public void setActivationKey(String activationKey) {
this.activationKey = activationKey; this.activationKey = activationKey;
} }
@ -260,8 +282,19 @@ public class User extends AbstractAuditingEntity<Long> implements Serializable {
// prettier-ignore // prettier-ignore
@Override @Override
public String toString() { public String toString() {
return "User{" + "login='" + login + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' //@formatter:off
+ ", email='" + email + '\'' + ", imageUrl='" + imageUrl + '\'' + ", activated='" + activated + '\'' return "User{"
+ ", langKey='" + langKey + '\'' + ", activationKey='" + activationKey + '\'' + "}"; + "login='" + login + '\''
+ ", firstName='" + firstName + '\''
+ ", lastName='" + lastName + '\''
+ ", email='" + email + '\''
+ ", imageUrl='" + imageUrl + '\''
+ ", activated='" + activated + '\''
+ ", allowSamlAuthentication='" + allowSamlAuthentication + '\''
+ ", allowUserPwdAuthentication='" + allowUserPwdAuthentication + '\''
+ ", langKey='" + langKey + '\''
+ ", activationKey='" + activationKey + '\''
+ "}";
//@formatter:on
} }
} }

View file

@ -2,6 +2,7 @@ package com.oguerreiro.resilient.security;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicBoolean;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator;
@ -66,16 +67,44 @@ public class DomainUserDetailsService implements UserDetailsService {
public UserDetails loadUserByUsername(final String login) { public UserDetails loadUserByUsername(final String login) {
log.debug("Authenticating {}", login); log.debug("Authenticating {}", login);
UserDetails userDetails = null;
AtomicBoolean allowUserPwdAuthentication = new AtomicBoolean(false);
if (new EmailValidator().isValid(login, null)) { if (new EmailValidator().isValid(login, null)) {
return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login).map( //@formatter:off
user -> createResilientSecurityUser(login, user)).orElseThrow( userDetails = userRepository.findOneWithAuthoritiesByEmailIgnoreCase(login)
() -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); .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); if (userDetails == null) {
return userRepository.findOneWithAuthoritiesByLogin(lowercaseLogin).map( throw new UsernameNotFoundException("User " + login + " was not found in the database");
user -> createResilientSecurityUser(lowercaseLogin, user)).orElseThrow( }
() -> new UsernameNotFoundException("User " + lowercaseLogin + " 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, private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin,

View file

@ -84,6 +84,13 @@ public class Saml2AuthenticationHandler implements AuthenticationSuccessHandler,
User user = userRepository.findOneByLogin(username).orElse(null); User user = userRepository.findOneByLogin(username).orElse(null);
if (user != 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 //@formatter:off
// User found. Setup permissions from resilient config // User found. Setup permissions from resilient config
authorities = user.getAuthorities().stream() authorities = user.getAuthorities().stream()

View file

@ -201,6 +201,8 @@ public class UserService {
} }
user.setImageUrl(userDTO.getImageUrl()); user.setImageUrl(userDTO.getImageUrl());
user.setActivated(userDTO.isActivated()); user.setActivated(userDTO.isActivated());
user.setAllowSamlAuthentication(userDTO.isAllowSamlAuthentication());
user.setAllowUserPwdAuthentication(userDTO.isAllowUserPwdAuthentication());
user.setLangKey(userDTO.getLangKey()); user.setLangKey(userDTO.getLangKey());
Set<Authority> managedAuthorities = user.getAuthorities(); Set<Authority> managedAuthorities = user.getAuthorities();
managedAuthorities.clear(); managedAuthorities.clear();

View file

@ -47,6 +47,8 @@ public class AdminUserDTO implements Serializable {
private String imageUrl; private String imageUrl;
private boolean activated = false; private boolean activated = false;
private boolean allowUserPwdAuthentication = false;
private boolean allowSamlAuthentication = false;
@Size(min = 2, max = 10) @Size(min = 2, max = 10)
private String langKey; private String langKey;
@ -76,6 +78,8 @@ public class AdminUserDTO implements Serializable {
this.lastName = user.getLastName(); this.lastName = user.getLastName();
this.email = user.getEmail(); this.email = user.getEmail();
this.activated = user.isActivated(); this.activated = user.isActivated();
this.allowSamlAuthentication = user.getAllowSamlAuthentication();
this.allowUserPwdAuthentication = user.getAllowUserPwdAuthentication();
this.imageUrl = user.getImageUrl(); this.imageUrl = user.getImageUrl();
this.langKey = user.getLangKey(); this.langKey = user.getLangKey();
this.createdBy = user.getCreatedBy(); this.createdBy = user.getCreatedBy();
@ -221,13 +225,43 @@ public class AdminUserDTO implements Serializable {
this.parentOrganization = parentOrganization; 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 // prettier-ignore
@Override @Override
public String toString() { public String toString() {
return "AdminUserDTO{" + "login='" + login + '\'' + ", firstName='" + firstName + '\'' + ", lastName='" + lastName //@formatter:off
+ '\'' + ", email='" + email + '\'' + ", imageUrl='" + imageUrl + '\'' + ", activated=" + activated return "AdminUserDTO{"
+ ", langKey='" + langKey + '\'' + ", createdBy=" + createdBy + ", createdDate=" + createdDate + "login='" + login + '\''
+ ", lastModifiedBy='" + lastModifiedBy + '\'' + ", lastModifiedDate=" + lastModifiedDate + ", authorities=" + ", firstName='" + firstName + '\''
+ authorities + "}"; + ", 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:
} }
} }

View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<!--
Alter the entity User.
Add flags for allowUserPwdAuth & allowSamlAuth
-->
<changeSet id="20250604123000-1" author="omg">
<!-- User can authenticate with USER+PWD -->
<addColumn tableName="jhi_user">
<column name="allow_user_pwd_auth" type="boolean" valueBoolean="false"/>
</addColumn>
<!-- User can authenticate with IDP (SAML2) -->
<addColumn tableName="jhi_user">
<column name="allow_saml_auth" type="boolean" valueBoolean="false"/>
</addColumn>
</changeSet>
<changeSet id="20250604123000-2" author="omg">
<!-- Update allow_user_pwd_auth to TRUE -->
<update tableName="jhi_users">
<column name="allow_user_pwd" valueBoolean="true"/>
</update>
</changeSet>
</databaseChangeLog>

View file

@ -104,5 +104,6 @@
<include file="config/liquibase/changelog/20252104104000_added_entity_ResilientLog.xml" relativeToChangelogFile="false"/> <include file="config/liquibase/changelog/20252104104000_added_entity_ResilientLog.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20250502222301_added_entity_DashboardComponentOrganization.xml" relativeToChangelogFile="false"/> <include file="config/liquibase/changelog/20250502222301_added_entity_DashboardComponentOrganization.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20250506120000_altered_entity_ResilientLog.xml" relativeToChangelogFile="false"/> <include file="config/liquibase/changelog/20250506120000_altered_entity_ResilientLog.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20250604123000_altered_entity_User.xml" relativeToChangelogFile="false"/>
</databaseChangeLog> </databaseChangeLog>

View file

@ -137,6 +137,34 @@
</select> </select>
</div> </div>
<div class="form-check">
<label class="form-check-label" for="allowUserPwdAuthentication">
<input
class="form-check-input"
[attr.disabled]="editForm.value.id === undefined ? 'disabled' : null"
type="checkbox"
id="allowUserPwdAuthentication"
name="allowUserPwdAuthentication"
formControlName="allowUserPwdAuthentication"
/>
<span jhiTranslate="userManagement.allowUserPwdAuthentication">Permite autenticação Utilizador+Password</span>
</label>
</div>
<div class="form-check">
<label class="form-check-label" for="allowSamlAuthentication">
<input
class="form-check-input"
[attr.disabled]="editForm.value.id === undefined ? 'disabled' : null"
type="checkbox"
id="allowSamlAuthentication"
name="allowSamlAuthentication"
formControlName="allowSamlAuthentication"
/>
<span jhiTranslate="userManagement.allowSamlAuthentication">Permite autenticação SAML2</span>
</label>
</div>
<button type="button" class="btn btn-secondary" (click)="previousState()"> <button type="button" class="btn btn-secondary" (click)="previousState()">
<fa-icon icon="ban"></fa-icon>&nbsp;<span jhiTranslate="entity.action.cancel">Cancelar</span> <fa-icon icon="ban"></fa-icon>&nbsp;<span jhiTranslate="entity.action.cancel">Cancelar</span>
</button> </button>

View file

@ -52,6 +52,8 @@ export default class UserManagementUpdateComponent implements OnInit {
langKey: new FormControl(userTemplate.langKey, { nonNullable: true }), langKey: new FormControl(userTemplate.langKey, { nonNullable: true }),
authorities: new FormControl(userTemplate.authorities, { nonNullable: true }), authorities: new FormControl(userTemplate.authorities, { nonNullable: true }),
securityGroup: new FormControl(userTemplate.securityGroup, { 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); private userService = inject(UserManagementService);

View file

@ -14,6 +14,8 @@ export interface IUser {
lastModifiedBy?: string; lastModifiedBy?: string;
lastModifiedDate?: Date; lastModifiedDate?: Date;
securityGroup?: ISecurityGroup; securityGroup?: ISecurityGroup;
allowUserPwdAuthentication?: boolean;
allowSamlAuthentication?: boolean;
} }
export class User implements IUser { export class User implements IUser {

View file

@ -20,6 +20,8 @@
"lastName": "Último nome", "lastName": "Último nome",
"email": "Email", "email": "Email",
"activated": "Ativo", "activated": "Ativo",
"allowSamlAuthentication": "Permite autenticação por SAML2 ?",
"allowUserPwdAuthentication": "Permite autenticação por Utlizador/Password ?",
"deactivated": "Inativo", "deactivated": "Inativo",
"profiles": "Perfis", "profiles": "Perfis",
"langKey": "Linguagem", "langKey": "Linguagem",