diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts index 3dfcec073b084f34687dd7fc412d2cb2161be97c..c89ba0dca77e9eee35e14e1c149df2b593fa3532 100644 --- a/smp-angular/src/app/app.module.ts +++ b/smp-angular/src/app/app.module.ts @@ -146,6 +146,7 @@ import {SubresourceDocumentPanelComponent} from "./edit/edit-resources/subresour import {SubresourceDocumentWizardComponent} from "./edit/edit-resources/subresource-document-wizard-dialog/subresource-document-wizard.component"; import {SmpWarningPanelComponent} from "./common/components/smp-warning-panel/smp-warning-panel.component"; import {ManageMembersDialogComponent} from "./common/dialogs/manage-members-dialog/manage-members-dialog.component"; +import {HttpErrorHandlerService} from "./common/error/http-error-handler.service"; @NgModule({ @@ -293,6 +294,7 @@ import {ManageMembersDialogComponent} from "./common/dialogs/manage-members-dial EditDomainService, EditGroupService, EditResourceService, + HttpErrorHandlerService, ExtensionService, GlobalLookups, HttpEventService, diff --git a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts index 02add5ab85be54354c60342fcf93dc4e2ac17350..c8dfdb244ea2e35b0e17352a0c547fcc32a11d4f 100644 --- a/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts +++ b/smp-angular/src/app/common/dialogs/credential-dialog/credential-dialog.component.ts @@ -1,4 +1,4 @@ -import {Component, Inject, Output} from '@angular/core'; +import {Component, Inject} from '@angular/core'; import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; import {FormBuilder, FormControl, FormGroup} from "@angular/forms"; import {SmpConstants} from "../../../smp.constants"; @@ -7,6 +7,7 @@ import {UserService} from "../../../system-settings/user/user.service"; import {CredentialRo} from "../../../security/credential.model"; import {CertificateRo} from "../../../system-settings/user/certificate-ro.model"; import {CertificateService} from "../../../system-settings/user/certificate.service"; +import {HttpErrorHandlerService} from "../../error/http-error-handler.service"; @Component({ @@ -34,6 +35,7 @@ export class CredentialDialogComponent { constructor(@Inject(MAT_DIALOG_DATA) public data: any, private userService: UserService, + private httpErrorHandlerService: HttpErrorHandlerService, private certificateService: CertificateService, public dialogRef: MatDialogRef<CredentialDialogComponent>, private formBuilder: FormBuilder @@ -138,6 +140,7 @@ export class CredentialDialogComponent { }); if (res.invalid) { this.showErrorMessage(res.invalidReason); + } else { this.clearAlert() } @@ -154,6 +157,10 @@ export class CredentialDialogComponent { }, err => { this.clearCertificateData() + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)){ + this.closeDialog(); + return; + } this.showErrorMessage("Error uploading certificate file [" + file.name + "]." + err.error?.errorDescription) } ); @@ -175,6 +182,10 @@ export class CredentialDialogComponent { this.userService.notifyAccessTokenUpdated(response.credential); this.setDisabled(true); }, (err) => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(err)){ + this.closeDialog(); + return; + } this.showErrorMessage(err.error.errorDescription); }); } @@ -208,16 +219,17 @@ export class CredentialDialogComponent { } return null; } + get minSelectableDate(): Date { - return this.credentialType == CredentialDialogComponent.ACCESS_TOKEN_TYPE? new Date():null; + return this.credentialType == CredentialDialogComponent.ACCESS_TOKEN_TYPE ? new Date() : null; } - showSuccessMessage(value:string) { + showSuccessMessage(value: string) { this.message = value; this.messageType = "success"; } - showErrorMessage(value:string) { + showErrorMessage(value: string) { this.message = value; this.messageType = "error"; } diff --git a/smp-angular/src/app/common/error/http-error-handler.service.ts b/smp-angular/src/app/common/error/http-error-handler.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..217cbe8e4797a82bbe020d004a9696f4d8594ade --- /dev/null +++ b/smp-angular/src/app/common/error/http-error-handler.service.ts @@ -0,0 +1,26 @@ +import {Injectable} from '@angular/core'; +import {Router, NavigationStart, NavigationEnd} from '@angular/router'; +import {Observable, Subject} from 'rxjs'; +import {HttpErrorResponse} from "@angular/common/http"; +import {NavigationService} from "../../window/sidenav/navigation-model.service"; +import {AlertMessageService} from "../alert-message/alert-message.service"; + +@Injectable() +export class HttpErrorHandlerService { + + constructor (private navigationService: NavigationService, + private alertMessageService: AlertMessageService,) { + + } + + public logoutOnInvalidSessionError(err: any): boolean { + if (err instanceof HttpErrorResponse) { + if (err.status === 401) { + this.navigationService.navigateToLogin(); + this.alertMessageService.error(err.error?.errorDescription) + return true; + } + } + return false; + } +} diff --git a/smp-angular/src/app/guards/authentication.guard.ts b/smp-angular/src/app/guards/authentication.guard.ts index d9bfd87699fabbe19e2b84901de60814948afc0f..08ac0dc0dbf40b164a8acbcb86c91175a3556b60 100644 --- a/smp-angular/src/app/guards/authentication.guard.ts +++ b/smp-angular/src/app/guards/authentication.guard.ts @@ -8,7 +8,6 @@ export const authenticationGuard = () => { const navigationService = inject(NavigationService); const securityService = inject(SecurityService); const alertService = inject(AlertMessageService); - const router = inject(Router); // test if logged in securityService.isAuthenticated(true).subscribe((isAuthenticated: boolean) => { @@ -17,9 +16,7 @@ export const authenticationGuard = () => { } else { alertService.error('You have been logged out because of inactivity or missing access permissions.', true); // Redirect to the login page - navigationService.reset(); - router.navigate(['/login'], {queryParams: {returnUrl: router.url}}); - router.parseUrl('/login'); + navigationService.navigateToLogin(); } }); }; diff --git a/smp-angular/src/app/security/security.service.ts b/smp-angular/src/app/security/security.service.ts index 2a14888d999a9beca6ccdbcfa3b24e58c3e2fc7d..464a3ecfcd6ed06d1e949379239b1a8424767d2d 100644 --- a/smp-angular/src/app/security/security.service.ts +++ b/smp-angular/src/app/security/security.service.ts @@ -2,7 +2,7 @@ import {Observable, ReplaySubject} from 'rxjs'; import {User} from './user.model'; import {SecurityEventService} from './security-event.service'; -import {HttpClient, HttpHeaders} from '@angular/common/http'; +import {HttpClient, HttpErrorResponse, HttpHeaders} from '@angular/common/http'; import {SmpConstants} from "../smp.constants"; import {Authority} from "./authority.model"; import {AlertMessageService} from "../common/alert-message/alert-message.service"; @@ -169,4 +169,5 @@ export class SecurityService { private clearLocalStorage() { localStorage.removeItem(this.LOCAL_STORAGE_KEY_CURRENT_USER); } + } diff --git a/smp-angular/src/app/system-settings/admin-users/admin-user.component.ts b/smp-angular/src/app/system-settings/admin-users/admin-user.component.ts index 5ccabc9a19a0f451a435a935e197a52664b42c0f..e7cf2b36668112f48cb2e44e99f6403dd0374e88 100644 --- a/smp-angular/src/app/system-settings/admin-users/admin-user.component.ts +++ b/smp-angular/src/app/system-settings/admin-users/admin-user.component.ts @@ -16,6 +16,7 @@ import { } from "../../common/dialogs/password-change-dialog/password-change-dialog.component"; import {UserDetailsDialogMode} from "../user/user-details-dialog/user-details-dialog.component"; import {ApplicationRoleEnum} from "../../common/enums/application-role.enum"; +import {HttpErrorHandlerService} from "../../common/error/http-error-handler.service"; @Component({ @@ -39,6 +40,7 @@ export class AdminUserComponent implements AfterViewInit, BeforeLeaveGuard { @ViewChild(MatPaginator) paginator: MatPaginator; constructor(private adminUserService: AdminUserService, + private httpErrorHandlerService: HttpErrorHandlerService, private securityService: SecurityService, private alertService: AlertMessageService, private dialog: MatDialog) { @@ -127,6 +129,9 @@ export class AdminUserComponent implements AfterViewInit, BeforeLeaveGuard { this.selected = selectUser; } }, (error) => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)) { + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -149,6 +154,9 @@ export class AdminUserComponent implements AfterViewInit, BeforeLeaveGuard { this.alertService.success("User [" + user.username + "] updated!"); } }, (error) => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)) { + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -163,6 +171,9 @@ export class AdminUserComponent implements AfterViewInit, BeforeLeaveGuard { this.alertService.success("User [" + user.username + "] created!"); } }, (error) => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)) { + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -192,6 +203,9 @@ export class AdminUserComponent implements AfterViewInit, BeforeLeaveGuard { this.alertService.success("User [" + user.username + "] deleted!"); } }, (error) => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)) { + return; + } this.alertService.error(error.error?.errorDescription) }); diff --git a/smp-angular/src/app/system-settings/user/user.service.ts b/smp-angular/src/app/system-settings/user/user.service.ts index c18c5d3457bbbe3b5413cc301c13507e0b28165a..e5716537364eb1a5146c9ae53b79cd7798b788fd 100644 --- a/smp-angular/src/app/system-settings/user/user.service.ts +++ b/smp-angular/src/app/system-settings/user/user.service.ts @@ -7,6 +7,7 @@ import {SecurityService} from "../../security/security.service"; import {Observable, Subject} from "rxjs"; import {CredentialRo} from "../../security/credential.model"; import {AccessTokenRo} from "../../common/dialogs/access-token-generation-dialog/access-token-ro.model"; +import {HttpErrorHandlerService} from "../../common/error/http-error-handler.service"; /** * Class handle current user settings such-as profile, credentials, DomiSMP settings... , @@ -27,6 +28,7 @@ export class UserService { constructor( private http: HttpClient, + private httpErrorHandlerService: HttpErrorHandlerService, private securityService: SecurityService, private alertService: AlertMessageService, ) { @@ -53,6 +55,9 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyPwdStatusUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -67,6 +72,9 @@ export class UserService { .subscribe((response: CredentialRo[]) => { this.notifyAccessTokensUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -82,6 +90,9 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyAccessTokenUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -97,6 +108,9 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyAccessTokenUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -112,6 +126,9 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyCertificateUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -127,6 +144,9 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyCertificateUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } @@ -153,7 +173,11 @@ export class UserService { .subscribe((response: CredentialRo) => { this.notifyCertificateUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) + }); } @@ -170,6 +194,9 @@ export class UserService { .subscribe((response: CredentialRo[]) => { this.notifyCertificatesUpdated(response) }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)){ + return; + } this.alertService.error(error.error?.errorDescription) }); } diff --git a/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts b/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts index 8cf924c27f64cb98dd135a5a4c3e75f479d729a8..bd09fd9e6215eafe125b18c3ad242fb5f091ceff 100644 --- a/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts +++ b/smp-angular/src/app/user-settings/user-certificates/user-certificates.component.ts @@ -9,6 +9,7 @@ import {CertificateDialogComponent} from "../../common/dialogs/certificate-dialo import {CredentialDialogComponent} from "../../common/dialogs/credential-dialog/credential-dialog.component"; import {BeforeLeaveGuard} from "../../window/sidenav/navigation-on-leave-guard"; import {UserCertificatePanelComponent} from "./user-certificate-panel/user-certificate-panel.component"; +import {HttpErrorHandlerService} from "../../common/error/http-error-handler.service"; @Component({ @@ -23,6 +24,7 @@ export class UserCertificatesComponent implements BeforeLeaveGuard { userCertificateCredentialComponents: QueryList<UserCertificatePanelComponent>; constructor(private securityService: SecurityService, + private httpErrorHandlerService: HttpErrorHandlerService, private userService: UserService, public dialog: MatDialog) { @@ -78,21 +80,27 @@ export class UserCertificatesComponent implements BeforeLeaveGuard { } public createNew() { - this.dialog.open(CredentialDialogComponent,{ - data:{ + this.dialog.open(CredentialDialogComponent, { + data: { credentialType: CredentialDialogComponent.CERTIFICATE_TYPE, formTitle: "Import certificate dialog" } - } ).afterClosed(); + }).afterClosed(); } + public onShowItemClicked(credential: CredentialRo) { - this.userService.getUserCertificateCredentialObservable(credential).subscribe((response: CredentialRo) => { - this.dialog.open(CertificateDialogComponent, { - data: {row: response.certificate} + this.userService.getUserCertificateCredentialObservable(credential) + .subscribe((response: CredentialRo) => { + this.dialog.open(CertificateDialogComponent, { + data: {row: response.certificate} + }); + + }, error => { + if (this.httpErrorHandlerService.logoutOnInvalidSessionError(error)) { + return; + } }); - - }); } @@ -119,7 +127,7 @@ export class UserCertificatesComponent implements BeforeLeaveGuard { } isDirty(): boolean { - let dirtyComp = !this.userCertificateCredentialComponents?null: this.userCertificateCredentialComponents.find(cmp => cmp.isDirty()) + let dirtyComp = !this.userCertificateCredentialComponents ? null : this.userCertificateCredentialComponents.find(cmp => cmp.isDirty()) return !!dirtyComp; } } diff --git a/smp-angular/src/app/window/sidenav/navigation-model.service.ts b/smp-angular/src/app/window/sidenav/navigation-model.service.ts index a249df5d82ada8018aa709c68b629605dbf80348..4ed4f2dd3ddceafc41e6860d7afa3c0173ff1f3c 100644 --- a/smp-angular/src/app/window/sidenav/navigation-model.service.ts +++ b/smp-angular/src/app/window/sidenav/navigation-model.service.ts @@ -317,4 +317,9 @@ export class NavigationService extends MatTreeNestedDataSource<NavigationNode> { return false; } + public navigateToLogin(): void { + this.reset(); + this.router.navigate(['/login'], {queryParams: {returnUrl: this.router.url}}); + this.router.parseUrl('/login'); + } } diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreController.java index a54f28b1856e0efe605e48a2fe9ad6773b3796ba..7d442e03ba7b8f879740f7dde32cc662456d24e2 100644 --- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreController.java +++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/ui/external/TruststoreController.java @@ -30,7 +30,7 @@ public class TruststoreController { this.payloadValidatorService = payloadValidatorService; } - @PreAuthorize("@smpAuthorizationService.systemAdministrator || @smpAuthorizationService.isCurrentlyLoggedIn(#userId)") + @PreAuthorize("@smpAuthorizationService.isCurrentlyLoggedIn(#userId)") @PostMapping(path = "/{user-id}/validate-certificate", consumes = MimeTypeUtils.APPLICATION_OCTET_STREAM_VALUE, produces = MimeTypeUtils.APPLICATION_JSON_VALUE) public CertificateRO validateCertificate(@PathVariable("user-id") String userId, @RequestBody byte[] data) { LOG.info("Got certificate data size: {}", data.length);