Configurar a possibilidade de inativar autenticação por user+password
Some checks failed
Release / release (push) Failing after 10m3s

fixes #13
This commit is contained in:
Orlando M Guerreiro 2025-06-23 11:04:25 +01:00
parent 93193c6165
commit c3eb88972c
7 changed files with 89 additions and 34 deletions

View file

@ -219,6 +219,7 @@ public class SecurityConfiguration {
.requestMatchers(mvc.pattern("/api/account/reset-password/init")).permitAll()
.requestMatchers(mvc.pattern("/api/account/reset-password/finish")).permitAll()
.requestMatchers(mvc.pattern("/api/account/saml2-endpoint")).permitAll()
.requestMatchers(mvc.pattern("/api/account/has-basic-auth")).permitAll()
.requestMatchers(mvc.pattern("/api/home/page")).permitAll()
.requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN)
.requestMatchers(mvc.pattern("/api/**")).authenticated()

View file

@ -31,6 +31,7 @@ import com.oguerreiro.resilient.domain.User;
import com.oguerreiro.resilient.repository.PersistentTokenRepository;
import com.oguerreiro.resilient.repository.UserRepository;
import com.oguerreiro.resilient.security.SecurityUtils;
import com.oguerreiro.resilient.security.basic.ResilientBasicProperties;
import com.oguerreiro.resilient.security.custom.ResilientUserDetails;
import com.oguerreiro.resilient.service.MailService;
import com.oguerreiro.resilient.service.UserService;
@ -71,14 +72,18 @@ public class AccountResource {
private final RelyingPartyRegistrationRepository registrationRepository;
private ResilientBasicProperties resilientBasicProperties;
public AccountResource(UserRepository userRepository, UserService userService, MailService mailService,
PersistentTokenRepository persistentTokenRepository,
@Autowired(required = false) RelyingPartyRegistrationRepository registrationRepository) {
@Autowired(required = false) RelyingPartyRegistrationRepository registrationRepository,
ResilientBasicProperties resilientBasicProperties) {
this.userRepository = userRepository;
this.userService = userService;
this.mailService = mailService;
this.persistentTokenRepository = persistentTokenRepository;
this.registrationRepository = registrationRepository;
this.resilientBasicProperties = resilientBasicProperties;
}
/**
@ -192,6 +197,10 @@ public class AccountResource {
*/
@PostMapping(path = "/account/change-password")
public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) {
if (!this.isBasicAuthActive()) {
throw new AccountResourceException("Invalid or not allowed request");
}
if (isPasswordLengthInvalid(passwordChangeDto.getNewPassword())) {
throw new InvalidPasswordException();
}
@ -244,6 +253,10 @@ public class AccountResource {
*/
@PostMapping(path = "/account/reset-password/init")
public void requestPasswordReset(@RequestBody String mail) {
if (!this.isBasicAuthActive()) {
throw new AccountResourceException("Invalid or not allowed request");
}
Optional<User> user = userService.requestPasswordReset(mail);
if (user.isPresent()) {
mailService.sendPasswordResetMail(user.orElseThrow());
@ -263,6 +276,10 @@ public class AccountResource {
*/
@PostMapping(path = "/account/reset-password/finish")
public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) {
if (!this.isBasicAuthActive()) {
throw new AccountResourceException("Invalid or not allowed request");
}
if (isPasswordLengthInvalid(keyAndPassword.getNewPassword())) {
throw new InvalidPasswordException();
}
@ -294,6 +311,19 @@ public class AccountResource {
return ids.get(0);
}
@GetMapping(path = "/account/has-basic-auth")
public Boolean hasBasicAuthentication() {
return this.isBasicAuthActive();
}
private boolean isBasicAuthActive() {
if (this.resilientBasicProperties != null && this.resilientBasicProperties.isEnabled()) {
return this.resilientBasicProperties.isEnabled();
}
return Boolean.FALSE;
}
private static boolean isPasswordLengthInvalid(String password) {
return (StringUtils.isEmpty(password) || password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH
|| password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH);

View file

@ -144,9 +144,11 @@ resilient:
enabled: false
port: 8081
mock-idp:
enabled: false
enabled: true
path: classpath:mock-idp/idp.js
security:
basic: # ADDED to config the formLogin (user+password). This allows for the DISABLE of basic authentication
enabled: true
saml2: # ADDED to support SAMLv2 authentication to IDP.
# Metadata endpoint ${base-url}/saml2/service-provider-metadata/mock-idp
enabled: true
@ -172,9 +174,9 @@ resilient:
name: name # the user display name [OPTIONAL]
username: urn:mace:dir:attribute-def:mail # the username, typically for authentication. Fallsback to email. [MANDATORY]
email: email # the user email [MANDATORY]
organization-code: organization_code # organization unit code [OPTIONAL]
security-group-code: security_group # security group code [OPTIONAL]
role: roles # a single role is expected [OPTIONAL]
organization-code: # organization unit code [OPTIONAL]. Eg. "organization_code"
security-group-code: # security group code [OPTIONAL]. Eg. "security_group"
role: # a single role is expected [OPTIONAL]. Eg. "roles"
defaults: # For some attributes defaults can be given. This will be used if SAML2 response doesn't have them
organization-code: NOVA # default organization unit code
security-group-code: GRP_USER # default security group code

View file

@ -18,6 +18,8 @@ logging:
ROOT: INFO
tech.jhipster: INFO
com.oguerreiro.resilient: INFO
org.hibernate.proxy: TRACE
org.hibernate.bytecode: TRACE
management:
prometheus:
@ -33,9 +35,9 @@ spring:
enabled: false
datasource:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:mysql://localhost:3306/resilient?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true
url: jdbc:mysql://localhost:3306/resilient_resilient?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true
username: root
password:
password: root
hikari:
poolName: Hikari
auto-commit: false

View file

@ -3,7 +3,7 @@ import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';
import { TranslateService } from '@ngx-translate/core';
import { Observable, ReplaySubject, of } from 'rxjs';
import { shareReplay, tap, catchError } from 'rxjs/operators';
import { shareReplay, tap, catchError, map } from 'rxjs/operators';
import { StateStorageService } from 'app/core/auth/state-storage.service';
import { Account } from 'app/core/auth/account.model';
@ -95,6 +95,14 @@ export class AccountService {
return this.http.get(this.applicationConfigService.getEndpointFor('api/account/saml2-endpoint'), { responseType: 'text' as 'text' });
}
hasBasicAuth(): Observable<boolean> {
return this.http.get(
this.applicationConfigService.getEndpointFor('api/account/has-basic-auth'), { responseType: 'text' as 'text' }
).pipe(
map(response => response === 'true')
);;
}
private fetch(): Observable<Account> {
return this.http.get<Account>(this.applicationConfigService.getEndpointFor('api/account'));
}

View file

@ -12,6 +12,7 @@
</div>
}
<form class="form" (ngSubmit)="login()" [formGroup]="loginForm">
<div *ngIf="hasBasicAuth">
<div class="form-group">
<input
type="text"
@ -37,8 +38,9 @@
</div>
<p class="text-right"><a routerLink="/account/reset/request" data-cy="forgetYourPasswordSelector" jhiTranslate="login.password.forgot">Esqueci-me da palavra passe</a></p>
<button type="submit" class="btn btn-primary btn-block btn-lg mb-3" data-cy="submit" jhiTranslate="login.form.button">Entrar</button>
</div>
<p *ngIf="hasBasicAuth && saml2Endpoint">ou</p>
<div *ngIf="saml2Endpoint">
<p>ou</p>
<button (click)="samlLogin()" class="btn btn-default btn-block btn-lg mb-5">Login NOVA</button>
</div>
</form>

View file

@ -17,6 +17,7 @@ export default class LoginComponent implements OnInit, AfterViewInit {
username = viewChild.required<ElementRef>('username');
saml2Endpoint : string | null = null;
authenticationError = signal(false);
hasBasicAuth: boolean = false;
loginForm = new FormGroup({
username: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
@ -44,6 +45,15 @@ export default class LoginComponent implements OnInit, AfterViewInit {
console.error('Failed to fetch SAML2 endpoint', err);
}
});
this.accountService.hasBasicAuth().subscribe({
next: (response: boolean) => {
this.hasBasicAuth = response;
},
error: (err) => {
console.error('Failed to fetch hasBasicAuth', err);
}
});
}
ngAfterViewInit(): void {