From 5c141eee26b9c69d8847cc15b6e52154de63153c Mon Sep 17 00:00:00 2001
From: Florent Poittevin <florent.poittevin@unige.ch>
Date: Wed, 30 Oct 2019 14:16:20 +0100
Subject: [PATCH] feat: 830 Update token when user edit is own token
 information and display warning before

---
 src/app/features/admin/admin.module.ts        |  6 +-
 ...in-user-edit-role-current-user.dialog.html | 41 ++++++++
 ...in-user-edit-role-current-user.dialog.scss | 25 +++++
 ...dmin-user-edit-role-current-user.dialog.ts | 46 +++++++++
 .../admin-user-form.presentational.html       |  4 +
 .../admin-user-form.presentational.ts         |  4 +
 .../admin-user-edit.routable.html             |  4 +
 .../admin-user-edit.routable.scss             |  9 ++
 .../admin-user-edit.routable.ts               | 99 ++++++++++++++++++-
 src/app/features/admin/user/user.module.ts    |  2 +
 .../shared-abstract-edit.routable.ts          |  4 +-
 src/assets/i18n/de.json                       | 13 +++
 src/assets/i18n/en.json                       | 13 +++
 src/assets/i18n/fr.json                       | 13 +++
 14 files changed, 273 insertions(+), 10 deletions(-)
 create mode 100644 src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.html
 create mode 100644 src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.scss
 create mode 100644 src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.ts
 create mode 100644 src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.scss

diff --git a/src/app/features/admin/admin.module.ts b/src/app/features/admin/admin.module.ts
index ea7c54457..a368e8d98 100644
--- a/src/app/features/admin/admin.module.ts
+++ b/src/app/features/admin/admin.module.ts
@@ -8,9 +8,7 @@ import {AdminOrganizationalUnitFundingAgencyState} from "@admin/orgunit/stores/f
 import {AdminOrganizationalUnitInstitutionState} from "@admin/orgunit/stores/institution/admin-organizational-unit-institution.state";
 import {AdminOrganizationalUnitPersonRoleState} from "@admin/orgunit/stores/person-role/admin-organizational-unit-person-role.state";
 import {AdminPersonState} from "@admin/person/stores/admin-person.state";
-import {
-  AdminPersonInstitutionsState,
-} from "@admin/person/stores/institutions/admin-people-institutions.state";
+import {AdminPersonInstitutionsState} from "@admin/person/stores/institutions/admin-people-institutions.state";
 import {AdminPersonOrgUnitRoleState} from "@admin/person/stores/person-role/admin-person-orgunit-role.state";
 import {AdminPreservationPolicyState} from "@admin/preservation-policy/stores/admin-preservation-policy.state";
 import {AdminRoleState} from "@admin/role/stores/admin-role.state";
@@ -63,7 +61,7 @@ const presentationals = [];
       AdminRoleState,
       AdminFundingAgenciesState,
       AdminFundingAgenciesOrganizationalUnitState,
-      AdminPersonInstitutionsState
+      AdminPersonInstitutionsState,
     ]),
   ],
   entryComponents: [
diff --git a/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.html b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.html
new file mode 100644
index 000000000..0c58c39be
--- /dev/null
+++ b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.html
@@ -0,0 +1,41 @@
+<h1 mat-dialog-title>{{"admin.user.dialog.editOwnRoles.title" | translate}}</h1>
+<div mat-dialog-content>
+  <p class="warning">{{"admin.user.dialog.editOwnRoles.warning" | translate}}</p>
+
+  <p *ngIf="data.newRolesAfterUpdate.length === 0"
+     class="message-alert"
+  >{{"admin.user.dialog.editOwnRoles.noMoreRight" | translate}}</p>
+  <p *ngIf="data.newRolesAfterUpdate.length > 0
+    && !data.newRolesAfterUpdate.includes(applicationRoleEnum.admin)
+    && !data.newRolesAfterUpdate.includes(applicationRoleEnum.root)"
+     class="message-alert"
+  >{{"admin.user.dialog.editOwnRoles.looseAdminRole" | translate}}</p>
+
+  <div *ngIf="data.removedRoles.length > 0"
+       class="summary"
+  >
+    <label>{{"admin.user.dialog.editOwnRoles.summary.removedRoles" | translate}}</label>
+    <ul>
+      <li *ngFor="let role of data.removedRoles">{{role}}</li>
+    </ul>
+  </div>
+
+  <div *ngIf="data.addedRoles.length > 0"
+       class="summary"
+  >
+    <label>{{"admin.user.dialog.editOwnRoles.summary.addedRoles" | translate}}</label>
+    <ul>
+      <li *ngFor="let role of data.addedRoles">{{role}}</li>
+    </ul>
+  </div>
+</div>
+<div mat-dialog-actions>
+  <button mat-flat-button
+          color="warn"
+          (click)="confirm()"
+  >{{"admin.user.dialog.editOwnRoles.confirm"  | translate}}</button>
+  <button mat-button
+          [mat-dialog-close]=""
+          cdkFocusInitial
+  >{{"admin.user.dialog.editOwnRoles.cancel" | translate}}</button>
+</div>
diff --git a/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.scss b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.scss
new file mode 100644
index 000000000..711b3bfd4
--- /dev/null
+++ b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.scss
@@ -0,0 +1,25 @@
+@import "../sass/abstracts/mixins";
+@import "../sass/abstracts/variables";
+
+:host {
+  .warning {
+    white-space: pre;
+  }
+
+  .message-alert {
+    color: $error;
+  }
+
+  .summary {
+    ul {
+      padding-top: 10px;
+
+      li {
+        list-style-type: initial;
+        margin-left: 15px;
+      }
+    }
+
+    padding: 10px 0;
+  }
+}
diff --git a/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.ts b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.ts
new file mode 100644
index 000000000..ea6acbec2
--- /dev/null
+++ b/src/app/features/admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog.ts
@@ -0,0 +1,46 @@
+import {
+  ChangeDetectionStrategy,
+  Component,
+  Inject,
+  OnInit,
+} from "@angular/core";
+import {
+  MAT_DIALOG_DATA,
+  MatDialogRef,
+} from "@angular/material";
+import {Store} from "@ngxs/store";
+import {SharedAbstractContainer} from "@shared/components/containers/shared-abstract/shared-abstract.container";
+import {ApplicationRoleEnum} from "@shared/enums/application-role.enum";
+
+@Component({
+  selector: "dlcm-admin-user-edit-role-current-user-dialog",
+  templateUrl: "./admin-user-edit-role-current-user.dialog.html",
+  styleUrls: ["./admin-user-edit-role-current-user.dialog.scss"],
+  changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class AdminUserEditRoleCurrentUserDialog extends SharedAbstractContainer implements OnInit {
+  get applicationRoleEnum(): typeof ApplicationRoleEnum {
+    return ApplicationRoleEnum;
+  }
+
+  constructor(protected store: Store,
+              protected dialogRef: MatDialogRef<AdminUserEditRoleCurrentUserDialog>,
+              @Inject(MAT_DIALOG_DATA) public data: AdminUserEditRoleCurrentUserDialogData) {
+    super();
+  }
+
+  ngOnInit(): void {
+    super.ngOnInit();
+  }
+
+  confirm(): void {
+    this.dialogRef.close(true);
+  }
+}
+
+export interface AdminUserEditRoleCurrentUserDialogData {
+  removedRoles: string[];
+  addedRoles: string[];
+  newRolesAfterUpdate: string[];
+  oldRolesBeforeUpdate: string[];
+}
diff --git a/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.html b/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.html
index 5a453799d..148f4b1d5 100644
--- a/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.html
+++ b/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.html
@@ -111,6 +111,10 @@
     </div>
   </div>
 
+  <!--  TODO DEBUG CHECKBOX NOT CONNECTED TO FORM WHEN EDIT NOT IS IN EDIT MODE-->
+  <!--  TODO REMOVE USELESS MAT ERROR-->
+  <!--  {{form.get(formDefinition.applicationRoles).value | json}}-->
+
   <div class="submit-button">
     <button *ngIf="!readonly"
             mat-flat-button
diff --git a/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.ts b/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.ts
index 786a2a3a5..2364a7a46 100644
--- a/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.ts
+++ b/src/app/features/admin/user/components/presentationals/admin-user-form/admin-user-form.presentational.ts
@@ -13,6 +13,7 @@ import {
   AccessOrganizationalUnit,
   ApplicationRole,
   Person,
+  User,
 } from "@app/generated-api";
 import {SharedAbstractFormPresentational} from "@shared/components/presentationals/shared-abstract-form/shared-abstract-form.presentational";
 import {UserApplicationRoleEnum} from "@shared/enums/user-application-role.enum";
@@ -41,6 +42,9 @@ export class AdminUserFormPresentational extends SharedAbstractFormPresentationa
   @Input()
   listOrganizationalUnit: AccessOrganizationalUnit[];
 
+  @Input()
+  currentUser: User;
+
   constructor(protected readonly _changeDetectorRef: ChangeDetectorRef,
               private readonly _fb: FormBuilder) {
     super(_changeDetectorRef);
diff --git a/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.html b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.html
index 4bf291cd4..f7e09a4c8 100644
--- a/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.html
+++ b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.html
@@ -11,8 +11,12 @@
 <div class="wrapper"
      [dlcmSpinner]="isLoadingWithDependencyObs | async"
 >
+  <label *ngIf="isCurrentUser(currentUser | async)"
+         class="current-user-admin"
+  >{{"admin.user.editCurrentUser" | translate}}</label>
   <dlcm-admin-user-form #formPresentational
                         *ngIf="isReadyToBeDisplayedObs | async"
+                        [currentUser]="currentUser | async"
                         [model]="currentObs | async"
                         [listPersons]="listPersonObs | async"
                         [listOrganizationalUnit]="listOrgUnitObs | async"
diff --git a/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.scss b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.scss
new file mode 100644
index 000000000..d5375b1c7
--- /dev/null
+++ b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.scss
@@ -0,0 +1,9 @@
+@import "../../../../../../shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.scss";
+@import "../sass/abstracts/variables";
+
+.current-user-admin {
+  text-align: center;
+  padding: 0 10px 20px;
+  color: $warning;
+  display: block;
+}
diff --git a/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.ts b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.ts
index e28ef75c8..8c934ecf1 100644
--- a/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.ts
+++ b/src/app/features/admin/user/components/routables/admin-user-edit/admin-user-edit.routable.ts
@@ -1,4 +1,11 @@
-import {adminUserActionNameSpace} from "@admin/user/stores/admin-user.action";
+import {
+  AdminUserEditRoleCurrentUserDialog,
+  AdminUserEditRoleCurrentUserDialogData,
+} from "@admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog";
+import {
+  AdminUserAction,
+  adminUserActionNameSpace,
+} from "@admin/user/stores/admin-user.action";
 import {
   AdminUserState,
   AdminUserStateModel,
@@ -7,12 +14,17 @@ import {
   ChangeDetectionStrategy,
   Component,
 } from "@angular/core";
+import {MatDialog} from "@angular/material/dialog";
 import {ActivatedRoute} from "@angular/router";
 import {
   AccessOrganizationalUnit,
   Person,
 } from "@app/generated-api";
+import {AppAction} from "@app/stores/app.action";
+import {AppState} from "@app/stores/app.state";
 import {
+  Actions,
+  ofActionCompleted,
   Select,
   Store,
 } from "@ngxs/store";
@@ -20,25 +32,104 @@ import {SharedAbstractEditRoutable} from "@shared/components/routables/shared-ab
 import {LocalStateEnum} from "@shared/enums/local-state.enum";
 import {UserExtended} from "@shared/models/business/user-extended.model";
 import {LocalStateModel} from "@shared/models/local-state.model";
-import {Observable} from "rxjs";
+import {
+  Observable,
+  of,
+} from "rxjs";
+import {
+  take,
+  tap,
+} from "rxjs/operators";
+import {
+  isNullOrUndefined,
+  isTrue,
+  ModelFormControlEvent,
+} from "solidify-frontend";
 
 @Component({
   selector: "dlcm-admin-user-edit-routable",
   templateUrl: "./admin-user-edit.routable.html",
-  styleUrls: ["../../../../../../shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.scss"],
+  styleUrls: ["./admin-user-edit.routable.scss"],
   changeDetection: ChangeDetectionStrategy.OnPush,
 })
 export class AdminUserEditRoutable extends SharedAbstractEditRoutable<UserExtended, AdminUserStateModel> {
   @Select(AdminUserState.isLoadingWithDependency) isLoadingWithDependencyObs: Observable<boolean>;
   @Select(AdminUserState.isReadyToBeDisplayed) isReadyToBeDisplayedObs: Observable<boolean>;
+  @Select(AppState.currentUser) currentUser: Observable<UserExtended>;
   @Select((state: LocalStateModel) => state.shared.shared_person.list) listPersonObs: Observable<Person[]>;
   @Select((state: LocalStateModel) => state.shared.shared_organizationalUnit.list) listOrgUnitObs: Observable<AccessOrganizationalUnit[]>;
 
   constructor(protected store: Store,
-              protected route: ActivatedRoute) {
+              protected route: ActivatedRoute,
+              private readonly _dialog: MatDialog,
+              private readonly _actions$: Actions) {
     super(store, route, LocalStateEnum.admin_user, adminUserActionNameSpace, LocalStateEnum.admin);
   }
 
   getSubResourceWithParentId(id: string): void {
   }
+
+  update(model: ModelFormControlEvent<UserExtended>): Observable<any> {
+    this.subscribe(this.currentUser.pipe(
+      take(1),
+      tap(currentUser => {
+        if (!this.isCurrentUser(currentUser)) {
+          super.update(model);
+          return;
+        }
+
+        const newApplicationRoles = model.model.applicationRoles.map(a => a.resId);
+        const oldApplicationRoles = currentUser.applicationRoles.map(a => a.resId);
+        const removedRoles = oldApplicationRoles.filter(item => newApplicationRoles.indexOf(item) < 0);
+        const addedRoles = newApplicationRoles.filter(item => oldApplicationRoles.indexOf(item) < 0);
+
+        if (removedRoles.length === 0 && addedRoles.length === 0) {
+          super.update(model);
+          return;
+        }
+
+        this.openModalConfirmEditRole({
+          newRolesAfterUpdate: newApplicationRoles,
+          oldRolesBeforeUpdate: oldApplicationRoles,
+          addedRoles: addedRoles,
+          removedRoles: removedRoles,
+        } as AdminUserEditRoleCurrentUserDialogData, model);
+      }),
+    ));
+    return of();
+  }
+
+  private openModalConfirmEditRole(data: AdminUserEditRoleCurrentUserDialogData, model: ModelFormControlEvent<UserExtended>): void {
+    this.subscribe(this._dialog.open(AdminUserEditRoleCurrentUserDialog, {
+      data: data,
+    }).afterClosed().pipe(
+      tap((isConfirmed: boolean | undefined) => {
+        if (isNullOrUndefined(isConfirmed)) {
+          return;
+        }
+        if (isTrue(isConfirmed)) {
+          this.updateAndLogout(model);
+        }
+      }),
+    ));
+  }
+
+  private updateAndLogout(model: ModelFormControlEvent<UserExtended>): void {
+    super.update(model);
+    this.subscribe(this._actions$.pipe(
+      ofActionCompleted(AdminUserAction.UpdateSuccess),
+      tap((result) => {
+        if (isTrue(result.result.successful)) {
+          this.store.dispatch(new AppAction.Logout());
+        }
+      }),
+    ));
+  }
+
+  isCurrentUser(userExtended: UserExtended): boolean {
+    if (isNullOrUndefined(userExtended)) {
+      return false;
+    }
+    return userExtended.resId === this._resId;
+  }
 }
diff --git a/src/app/features/admin/user/user.module.ts b/src/app/features/admin/user/user.module.ts
index e1203e1ae..8d432a069 100644
--- a/src/app/features/admin/user/user.module.ts
+++ b/src/app/features/admin/user/user.module.ts
@@ -1,4 +1,5 @@
 import {AdminUserDeleteDialog} from "@admin/user/components/dialogs/admin-user-delete/admin-user-delete.dialog";
+import {AdminUserEditRoleCurrentUserDialog} from "@admin/user/components/dialogs/admin-user-edit-role-current-user/admin-user-edit-role-current-user.dialog";
 import {AdminUserFormPresentational} from "@admin/user/components/presentationals/admin-user-form/admin-user-form.presentational";
 import {AdminUserCreateRoutable} from "@admin/user/components/routables/admin-user-create/admin-user-create.routable";
 import {AdminUserDetailRoutable} from "@admin/user/components/routables/admin-user-detail/admin-user-detail.routable";
@@ -20,6 +21,7 @@ const routables = [
 const containers = [];
 const dialogs = [
   AdminUserDeleteDialog,
+  AdminUserEditRoleCurrentUserDialog,
 ];
 const presentationals = [
   AdminUserFormPresentational,
diff --git a/src/app/shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.ts b/src/app/shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.ts
index 3e9ac4d51..e882cf135 100644
--- a/src/app/shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.ts
+++ b/src/app/shared/components/routables/shared-abstract-edit/shared-abstract-edit.routable.ts
@@ -75,9 +75,9 @@ export abstract class SharedAbstractEditRoutable<TResourceModel, UResourceStateM
     return this._resId;
   }
 
-  update(model: ModelFormControlEvent<TResourceModel>): void {
+  update(model: ModelFormControlEvent<TResourceModel>): Observable<any> {
     super.saveInProgress();
-    this.store.dispatch(ResourceActionHelper.update<TResourceModel>(this.resourceNameSpace, model));
+    return this.store.dispatch(ResourceActionHelper.update<TResourceModel>(this.resourceNameSpace, model));
   }
 
   backToDetail(): void {
diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json
index 75fec7fd1..434656ebe 100644
--- a/src/assets/i18n/de.json
+++ b/src/assets/i18n/de.json
@@ -431,8 +431,21 @@
           "confirm": "Yes",
           "message": "Are you sure you want to delete the User '{{name}}'?",
           "title": "Confirm deletion"
+        },
+        "editOwnRoles": {
+          "cancel": "Cancel",
+          "confirm": "Yes, I'm sure!",
+          "looseAdminRole": "You will lose access to the administration features.",
+          "noMoreRight": "You have removed all your rights which will prevent you from accessing the application!",
+          "summary": {
+            "addedRoles": "Added roles :",
+            "removedRoles": "Removed roles :"
+          },
+          "title": "Be careful, you will modify your roles!",
+          "warning": "This will result in a change of rights within the application.\nYou're going to be dislodged."
         }
       },
+      "editCurrentUser": "Warning, you will edit your own informations !",
       "form": {
         "accessToken": "Access Token",
         "email": "Email",
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 75fec7fd1..434656ebe 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -431,8 +431,21 @@
           "confirm": "Yes",
           "message": "Are you sure you want to delete the User '{{name}}'?",
           "title": "Confirm deletion"
+        },
+        "editOwnRoles": {
+          "cancel": "Cancel",
+          "confirm": "Yes, I'm sure!",
+          "looseAdminRole": "You will lose access to the administration features.",
+          "noMoreRight": "You have removed all your rights which will prevent you from accessing the application!",
+          "summary": {
+            "addedRoles": "Added roles :",
+            "removedRoles": "Removed roles :"
+          },
+          "title": "Be careful, you will modify your roles!",
+          "warning": "This will result in a change of rights within the application.\nYou're going to be dislodged."
         }
       },
+      "editCurrentUser": "Warning, you will edit your own informations !",
       "form": {
         "accessToken": "Access Token",
         "email": "Email",
diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json
index 2033c54c1..870064e26 100644
--- a/src/assets/i18n/fr.json
+++ b/src/assets/i18n/fr.json
@@ -431,8 +431,21 @@
           "confirm": "Oui",
           "message": "Êtes-vous sûr de vouloir supprimer l'utilisateur '{{name}}'?",
           "title": "Confirmer suppresion"
+        },
+        "editOwnRoles": {
+          "cancel": "Annuler",
+          "confirm": "Oui, je suis sûr !",
+          "looseAdminRole": "Vous allez perdre l'accès aux fonctionnalités d'administration.",
+          "noMoreRight": "Vous avez retiré l'intégralité de vos droits ce qui vous empéchera d'accéder à l'application !",
+          "summary": {
+            "addedRoles": "Rôles ajoutés :",
+            "removedRoles": "Rôles supprimés :"
+          },
+          "title": "Attention, vous allez modifier vos rôles !",
+          "warning": "Cela va engendrer un changement de droits au sein de l'application.\nVous allez être déloggué."
         }
       },
+      "editCurrentUser": "Attention, vous allez éditer vos propres informations !",
       "form": {
         "accessToken": "Jeton d'accès OAuth",
         "email": "Email",
-- 
GitLab