From 97fb0987a05ea71dd016d7535414c3eac90e15bd Mon Sep 17 00:00:00 2001
From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu>
Date: Mon, 22 May 2023 08:13:07 +0200
Subject: [PATCH] Fix logout issue when session is expired on user settings
 pages.

---
 smp-angular/src/app/app.module.ts             |  2 ++
 .../credential-dialog.component.ts            | 20 +++++++++++---
 .../error/http-error-handler.service.ts       | 26 ++++++++++++++++++
 .../src/app/guards/authentication.guard.ts    |  5 +---
 .../src/app/security/security.service.ts      |  3 ++-
 .../admin-users/admin-user.component.ts       | 14 ++++++++++
 .../app/system-settings/user/user.service.ts  | 27 +++++++++++++++++++
 .../user-certificates.component.ts            | 26 +++++++++++-------
 .../sidenav/navigation-model.service.ts       |  5 ++++
 .../smp/ui/external/TruststoreController.java |  2 +-
 10 files changed, 111 insertions(+), 19 deletions(-)
 create mode 100644 smp-angular/src/app/common/error/http-error-handler.service.ts

diff --git a/smp-angular/src/app/app.module.ts b/smp-angular/src/app/app.module.ts
index 3dfcec073..c89ba0dca 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 02add5ab8..c8dfdb244 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 000000000..217cbe8e4
--- /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 d9bfd8769..08ac0dc0d 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 2a14888d9..464a3ecfc 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 5ccabc9a1..e7cf2b366 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 c18c5d345..e57165373 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 8cf924c27..bd09fd9e6 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 a249df5d8..4ed4f2dd3 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 a54f28b18..7d442e03b 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);
-- 
GitLab