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)
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<Long> 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<Long> 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
}
}

View file

@ -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,

View file

@ -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()

View file

@ -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<Authority> managedAuthorities = user.getAuthorities();
managedAuthorities.clear();

View file

@ -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:
}
}

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/20250502222301_added_entity_DashboardComponentOrganization.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>

View file

@ -137,6 +137,34 @@
</select>
</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()">
<fa-icon icon="ban"></fa-icon>&nbsp;<span jhiTranslate="entity.action.cancel">Cancelar</span>
</button>

View file

@ -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);

View file

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

View file

@ -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",