Skip to content

Commit f78896e

Browse files
committed
feat(admin): allow deletion of users according to config property
* There is new config property 'user_deletion_forced' which defines if users on instance can be deleted or anonymized (this is the default option). * This task is related to new backend implementation where deletion of user is now connected with blocking of user's logins. * The new configuration property 'user_deletion_forced' (default false) was added. When this property is not set, still the default anonymization will be applied. BREAKING-CHANGE: new configuration property 'user_deletion_forced'
1 parent 9531ddb commit f78896e

File tree

12 files changed

+129
-18
lines changed

12 files changed

+129
-18
lines changed

apps/admin-gui/src/app/admin/pages/admin-user-detail-page/admin-user-detail-page.component.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,15 @@
2222
data-cy="edit-user-button">
2323
<mat-icon>edit</mat-icon>
2424
</button>
25-
<button *ngIf="!anonymized" (click)="anonymizeUser()" mat-icon-button>
25+
<button
26+
*ngIf="!anonymized && !userDeletionForced"
27+
(click)="anonymizeUser()"
28+
mat-icon-button>
2629
<mat-icon>no_accounts</mat-icon>
2730
</button>
31+
<button *ngIf="userDeletionForced" (click)="deleteUser()" mat-icon-button>
32+
<mat-icon>delete_forever</mat-icon>
33+
</button>
2834
<mat-icon
2935
*ngIf="anonymized"
3036
matTooltip="{{'USER_DETAIL.DASHBOARD.ANONYMIZED' | translate}}"

apps/admin-gui/src/app/admin/pages/admin-user-detail-page/admin-user-detail-page.component.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ import { AttributesManagerService, User, UsersManagerService } from '@perun-web-
66
import { getDefaultDialogConfig } from '@perun-web-apps/perun/utils';
77
import { MatDialog } from '@angular/material/dialog';
88
import { EditUserDialogComponent } from '../../../shared/components/dialogs/edit-user-dialog/edit-user-dialog.component';
9-
import { EntityStorageService, GuiAuthResolver } from '@perun-web-apps/perun/services';
10-
import { AnonymizeUserDialogComponent } from '@perun-web-apps/perun/dialogs';
9+
import {
10+
EntityStorageService,
11+
GuiAuthResolver,
12+
StoreService,
13+
} from '@perun-web-apps/perun/services';
14+
import {
15+
AnonymizeUserDialogComponent,
16+
DeleteUserDialogComponent,
17+
} from '@perun-web-apps/perun/dialogs';
18+
import { ComponentType } from '@angular/cdk/overlay';
1119

1220
@Component({
1321
selector: 'app-admin-user-detail-page',
@@ -19,6 +27,7 @@ export class AdminUserDetailPageComponent implements OnInit {
1927
loading = false;
2028
svgIcon = 'perun-user-dark';
2129
anonymized: boolean;
30+
userDeletionForced: boolean;
2231
private path: string;
2332
private regex: string;
2433

@@ -31,11 +40,13 @@ export class AdminUserDetailPageComponent implements OnInit {
3140
private dialog: MatDialog,
3241
public authResolver: GuiAuthResolver,
3342
private entityStorageService: EntityStorageService,
34-
private router: Router
43+
private router: Router,
44+
private store: StoreService
3545
) {}
3646

3747
ngOnInit(): void {
3848
this.loading = true;
49+
this.userDeletionForced = this.store.getProperty('user_deletion_forced');
3950
this.route.params.subscribe((params) => {
4051
const userId = Number(params['userId']);
4152
this.entityStorageService.setEntity({ id: Number(userId), beanName: 'User' });
@@ -91,14 +102,23 @@ export class AdminUserDetailPageComponent implements OnInit {
91102
}
92103

93104
anonymizeUser(): void {
105+
this.openDialog(AnonymizeUserDialogComponent);
106+
}
107+
108+
deleteUser(): void {
109+
this.openDialog(DeleteUserDialogComponent);
110+
}
111+
112+
private openDialog(
113+
dialogComponent: ComponentType<AnonymizeUserDialogComponent | DeleteUserDialogComponent>
114+
): void {
94115
const config = getDefaultDialogConfig();
95116
config.width = '550px';
96117
config.data = {
97118
theme: 'admin-theme',
98119
user: this.user,
99120
};
100-
101-
const dialogRef = this.dialog.open(AnonymizeUserDialogComponent, config);
121+
const dialogRef = this.dialog.open(dialogComponent, config);
102122

103123
dialogRef.afterClosed().subscribe((result) => {
104124
if (result) {

apps/admin-gui/src/assets/config/defaultConfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
},
5656
"group_name_secondary_regex": "^[-a-zA-Z.0-9_ ]+$",
5757
"group_name_error_message": "Name cannot be empty and can contain only characters a-z, A-Z, numbers, spaces, dots, '_' and '-'",
58+
"user_deletion_forced": false,
5859
"enforce_consents": false,
5960
"footer": {
6061
"columns": [

apps/admin-gui/src/assets/i18n/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2208,6 +2208,11 @@
22082208
"GROUP_RELATION": "User's membership in all groups",
22092209
"SUCCESS_NOTIFICATION": "User was successfully anonymized"
22102210
},
2211+
"DELETE_USER": {
2212+
"VO_RELATION": "User's membership in all organizations",
2213+
"GROUP_RELATION": "User's membership in all groups",
2214+
"SUCCESS_NOTIFICATION": "User was successfully deleted"
2215+
},
22112216
"CHANGE_VO_EXPIRATION": {
22122217
"TITLE": "Change member expiration"
22132218
},

libs/perun/dialogs/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ export * from './lib/add-ssh-dialog/add-ssh-dialog.component';
2727
export * from './lib/remove-string-value-dialog/remove-string-value-dialog.component';
2828
export * from './lib/show-ssh-dialog/show-ssh-dialog.component';
2929
export * from './lib/show-notification-history-dialog/show-notification-history-dialog.component';
30+
export * from './lib/delete-user-dialog/delete-user-dialog.component';

libs/perun/dialogs/src/lib/anonymize-user-dialog/anonymize-user-dialog.component.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import { Component, Inject, OnInit } from '@angular/core';
22
import { MatTableDataSource } from '@angular/material/table';
33
import { User, UsersManagerService } from '@perun-web-apps/perun/openapi';
44
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
5-
import { NotificatorService } from '@perun-web-apps/perun/services';
6-
import { TranslateService } from '@ngx-translate/core';
5+
import { NotificatorService, PerunTranslateService } from '@perun-web-apps/perun/services';
76
import { DeleteDialogResult } from '../delete-entity-dialog/delete-entity-dialog.component';
87

98
export interface AnonymizeUserDialogComponentData {
@@ -29,14 +28,14 @@ export class AnonymizeUserDialogComponent implements OnInit {
2928
@Inject(MAT_DIALOG_DATA) private data: AnonymizeUserDialogComponentData,
3029
private notificator: NotificatorService,
3130
private usersService: UsersManagerService,
32-
private translate: TranslateService
31+
private translate: PerunTranslateService
3332
) {}
3433

3534
ngOnInit(): void {
3635
this.theme = this.data.theme;
3736
this.dataSource = new MatTableDataSource<User>([this.data.user]);
38-
this.relations.push(this.translate.instant('DIALOGS.ANONYMIZE_USER.GROUP_RELATION') as string);
39-
this.relations.push(this.translate.instant('DIALOGS.ANONYMIZE_USER.VO_RELATION') as string);
37+
this.relations.push(this.translate.instant('DIALOGS.ANONYMIZE_USER.GROUP_RELATION'));
38+
this.relations.push(this.translate.instant('DIALOGS.ANONYMIZE_USER.VO_RELATION'));
4039
}
4140

4241
onCancel(): void {
@@ -45,16 +44,16 @@ export class AnonymizeUserDialogComponent implements OnInit {
4544

4645
anonymizeUser(): void {
4746
this.loading = true;
48-
this.usersService.anonymizeUser(this.data.user.id, this.force).subscribe(
49-
() => {
47+
this.usersService.anonymizeUser(this.data.user.id, this.force).subscribe({
48+
next: () => {
5049
this.notificator.showSuccess(
51-
this.translate.instant('DIALOGS.ANONYMIZE_USER.SUCCESS_NOTIFICATION') as string
50+
this.translate.instant('DIALOGS.ANONYMIZE_USER.SUCCESS_NOTIFICATION')
5251
);
5352
this.loading = false;
5453
this.dialogRef.close(true);
5554
},
56-
() => (this.loading = false)
57-
);
55+
error: () => (this.loading = false),
56+
});
5857
}
5958

6059
onSubmit(result: DeleteDialogResult): void {

libs/perun/dialogs/src/lib/delete-entity-dialog/delete-entity-dialog.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ <h1 mat-dialog-title>
2626
<ng-container matColumnDef="name">
2727
<th *matHeaderCellDef mat-header-cell></th>
2828
<td *matCellDef="let entity" mat-cell>
29-
{{anonymize ? (entity | userFullName) : entity.name }}
29+
{{ entityType === 'user' ? (entity | userFullName) : entity.name }}
3030
</td>
3131
</ng-container>
3232

@@ -87,7 +87,7 @@ <h1 mat-dialog-title>
8787
<ng-container matColumnDef="name">
8888
<th *matHeaderCellDef mat-header-cell></th>
8989
<td *matCellDef="let entity" mat-cell>
90-
{{anonymize ? (entity | userFullName) : entity.name }}
90+
{{ entityType === 'user' ? (entity | userFullName) : entity.name }}
9191
</td>
9292
</ng-container>
9393

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<div class="{{theme}}">
2+
<perun-web-apps-delete-entity-dialog
3+
[entityNames]="dataSource"
4+
[entityType]="'user'"
5+
[relations]="relations"
6+
[loading]="loading"
7+
(deleted)="onSubmit($event)">
8+
</perun-web-apps-delete-entity-dialog>
9+
</div>

libs/perun/dialogs/src/lib/delete-user-dialog/delete-user-dialog.component.scss

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Component, Inject, OnInit } from '@angular/core';
2+
import { MatTableDataSource } from '@angular/material/table';
3+
import { User, UsersManagerService } from '@perun-web-apps/perun/openapi';
4+
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
5+
import { NotificatorService, PerunTranslateService } from '@perun-web-apps/perun/services';
6+
import { DeleteDialogResult } from '../delete-entity-dialog/delete-entity-dialog.component';
7+
8+
export interface DeleteUserDialogComponentData {
9+
user: User;
10+
theme: string;
11+
}
12+
13+
@Component({
14+
selector: 'perun-web-apps-delete-user-dialog',
15+
templateUrl: './delete-user-dialog.component.html',
16+
styleUrls: ['./delete-user-dialog.component.scss'],
17+
})
18+
export class DeleteUserDialogComponent implements OnInit {
19+
theme: string;
20+
force = false;
21+
loading: boolean;
22+
dataSource: MatTableDataSource<User>;
23+
relations: string[] = [];
24+
25+
constructor(
26+
private dialogRef: MatDialogRef<DeleteUserDialogComponentData>,
27+
@Inject(MAT_DIALOG_DATA) private data: DeleteUserDialogComponentData,
28+
private notificator: NotificatorService,
29+
private usersService: UsersManagerService,
30+
private translate: PerunTranslateService
31+
) {}
32+
33+
ngOnInit(): void {
34+
this.theme = this.data.theme;
35+
this.dataSource = new MatTableDataSource<User>([this.data.user]);
36+
this.relations.push(this.translate.instant('DIALOGS.DELETE_USER.GROUP_RELATION'));
37+
this.relations.push(this.translate.instant('DIALOGS.DELETE_USER.VO_RELATION'));
38+
}
39+
40+
onCancel(): void {
41+
this.dialogRef.close(false);
42+
}
43+
44+
deleteUser(): void {
45+
this.loading = true;
46+
this.usersService.deleteUser(this.data.user.id, this.force).subscribe({
47+
next: () => {
48+
this.notificator.showSuccess(
49+
this.translate.instant('DIALOGS.DELETE_USER.SUCCESS_NOTIFICATION')
50+
);
51+
this.loading = false;
52+
this.dialogRef.close(true);
53+
},
54+
error: () => (this.loading = false),
55+
});
56+
}
57+
58+
onSubmit(result: DeleteDialogResult): void {
59+
this.force = result.force;
60+
if (result.deleted) {
61+
this.deleteUser();
62+
} else {
63+
this.onCancel();
64+
}
65+
}
66+
}

libs/perun/dialogs/src/lib/perun-dialogs.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { UiMaterialModule } from '@perun-web-apps/ui/material';
4747
import { ExportDataDialogComponent } from './exporting-data-dialog/export-data-dialog.component';
4848
import { RequestChangeDataQuotaDialogComponent } from './request-change-data-quota-dialog/request-change-data-quota-dialog.component';
4949
import { ExpirationSelectComponent } from './expiration-select/expiration-select.component';
50+
import { DeleteUserDialogComponent } from './delete-user-dialog/delete-user-dialog.component';
5051

5152
@NgModule({
5253
imports: [
@@ -101,6 +102,7 @@ import { ExpirationSelectComponent } from './expiration-select/expiration-select
101102
ExportDataDialogComponent,
102103
RequestChangeDataQuotaDialogComponent,
103104
ExpirationSelectComponent,
105+
DeleteUserDialogComponent,
104106
],
105107
exports: [
106108
ChangeExpirationDialogComponent,
@@ -125,6 +127,7 @@ import { ExpirationSelectComponent } from './expiration-select/expiration-select
125127
ExportDataDialogComponent,
126128
RequestChangeDataQuotaDialogComponent,
127129
ExpirationSelectComponent,
130+
DeleteUserDialogComponent,
128131
],
129132
})
130133
export class PerunDialogsModule {}

libs/perun/models/src/lib/ConfigProperties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ export interface PerunConfig {
167167
// Admin gui specific
168168
// Required
169169
config: string;
170+
user_deletion_forced?: boolean;
170171
// Optional
171172
login_namespace_attributes?: string[];
172173
profile_label_en?: string;

0 commit comments

Comments
 (0)