From c3eb88972c68311128b4bd1157395d0484f07228 Mon Sep 17 00:00:00 2001 From: Orlando M Guerreiro Date: Mon, 23 Jun 2025 11:04:25 +0100 Subject: [PATCH] =?UTF-8?q?Configurar=20a=20possibilidade=20de=20inativar?= =?UTF-8?q?=20autentica=C3=A7=C3=A3o=20por=20user+password=20fixes=20#13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/SecurityConfiguration.java | 1 + .../resilient/web/rest/AccountResource.java | 32 +++++++++++- src/main/resources/config/application-dev.yml | 10 ++-- .../resources/config/application-prod.yml | 8 +-- .../webapp/app/core/auth/account.service.ts | 10 +++- .../webapp/app/login/login.component.html | 52 ++++++++++--------- src/main/webapp/app/login/login.component.ts | 10 ++++ 7 files changed, 89 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/oguerreiro/resilient/config/SecurityConfiguration.java b/src/main/java/com/oguerreiro/resilient/config/SecurityConfiguration.java index ce677fb..eb2f037 100644 --- a/src/main/java/com/oguerreiro/resilient/config/SecurityConfiguration.java +++ b/src/main/java/com/oguerreiro/resilient/config/SecurityConfiguration.java @@ -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() diff --git a/src/main/java/com/oguerreiro/resilient/web/rest/AccountResource.java b/src/main/java/com/oguerreiro/resilient/web/rest/AccountResource.java index d4eb475..3aab272 100644 --- a/src/main/java/com/oguerreiro/resilient/web/rest/AccountResource.java +++ b/src/main/java/com/oguerreiro/resilient/web/rest/AccountResource.java @@ -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 = 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); diff --git a/src/main/resources/config/application-dev.yml b/src/main/resources/config/application-dev.yml index 0108b50..3c04c8d 100644 --- a/src/main/resources/config/application-dev.yml +++ b/src/main/resources/config/application-dev.yml @@ -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 diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml index 3695f21..f0650bf 100644 --- a/src/main/resources/config/application-prod.yml +++ b/src/main/resources/config/application-prod.yml @@ -18,7 +18,9 @@ logging: ROOT: INFO tech.jhipster: INFO com.oguerreiro.resilient: INFO - + org.hibernate.proxy: TRACE + org.hibernate.bytecode: TRACE + management: prometheus: metrics: @@ -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 diff --git a/src/main/webapp/app/core/auth/account.service.ts b/src/main/webapp/app/core/auth/account.service.ts index 522a3e9..e3fcc06 100644 --- a/src/main/webapp/app/core/auth/account.service.ts +++ b/src/main/webapp/app/core/auth/account.service.ts @@ -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 { + return this.http.get( + this.applicationConfigService.getEndpointFor('api/account/has-basic-auth'), { responseType: 'text' as 'text' } + ).pipe( + map(response => response === 'true') + );; + } + private fetch(): Observable { return this.http.get(this.applicationConfigService.getEndpointFor('api/account')); } diff --git a/src/main/webapp/app/login/login.component.html b/src/main/webapp/app/login/login.component.html index d06f9b4..6dc2af5 100644 --- a/src/main/webapp/app/login/login.component.html +++ b/src/main/webapp/app/login/login.component.html @@ -12,33 +12,35 @@ }
-
- +
+
+ +
+
+ +
+

Esqueci-me da palavra passe

+
-
- -
-

Esqueci-me da palavra passe

- +

ou

-

ou

diff --git a/src/main/webapp/app/login/login.component.ts b/src/main/webapp/app/login/login.component.ts index cf30f76..e459c8a 100644 --- a/src/main/webapp/app/login/login.component.ts +++ b/src/main/webapp/app/login/login.component.ts @@ -17,6 +17,7 @@ export default class LoginComponent implements OnInit, AfterViewInit { username = viewChild.required('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 {