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