Skip to content

Commit d694b24

Browse files
committed
feat(admin): user roles are split by source
* user-detail page splits his roles by source (directly assigned or obtained as a member of authorized group) * tabs added to change what source we want to see * (authorized) group based roles cannot be managed, only viewed
1 parent 2f4f97c commit d694b24

File tree

15 files changed

+570
-44
lines changed

15 files changed

+570
-44
lines changed

apps/admin-gui/src/app/shared/components/roles-list/roles-page.component.html

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
<h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
21
<button
3-
*ngIf="assignableRules.length !== 0"
2+
*ngIf="assignableRules.length !== 0 && editable"
43
mat-flat-button
54
color="accent"
6-
class="mb-3 mr-2"
5+
class="mb-3 mr-2 mt-2"
76
(click)="addRole()">
87
{{'ROLES.ADD'| translate}}
98
</button>
@@ -22,7 +21,7 @@ <h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
2221
</mat-expansion-panel-header>
2322
<ng-template matExpansionPanelContent>
2423
<span
25-
*ngIf="role.roleName !== 'SELF' && role.roleName !== 'MEMBERSHIP' && role.roleName !== 'SPONSORSHIP'"
24+
*ngIf="editable && role.roleName !== 'SELF' && role.roleName !== 'MEMBERSHIP' && role.roleName !== 'SPONSORSHIP'"
2625
matTooltip="{{'ROLES.REMOVE_DISABLED_TOOLTIP'| translate}}"
2726
matTooltipPosition="left"
2827
[matTooltipDisabled]="(selection.selected.length === 0 && selectedFacilities.selected.length === 0) || !disableRemove">
@@ -43,9 +42,11 @@ <h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
4342
{{('ROLES.' + role.roleName + '_VOS_' + entityType) | translate}}
4443
</div>
4544
<perun-web-apps-vos-list
46-
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'shortName'] : ['checkbox', 'id', 'name', 'shortName']"
45+
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'shortName'] : (editable ? ['checkbox', 'id', 'name', 'shortName'] : ['id', 'name', 'authzGroup', 'shortName'])"
4746
[vos]="vos | async"
48-
[selection]="selection">
47+
[selection]="selection"
48+
[authzVoNames]="voNames"
49+
[voWithAuthzGroupPairs]="_complementaryObjectsWithAuthzGroups?.get(role.roleName)?.get('vo')">
4950
</perun-web-apps-vos-list>
5051
</div>
5152
<div
@@ -55,9 +56,11 @@ <h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
5556
{{('ROLES.' + role.roleName + '_GROUPS_' + entityType) | translate}}
5657
</div>
5758
<perun-web-apps-groups-list
58-
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'vo', 'name', 'description'] : ['select', 'id', 'vo', 'name', 'description']"
59+
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'vo', 'name', 'description'] : (editable ? ['select', 'id', 'vo', 'name', 'description'] : ['id', 'vo', 'name', 'authzGroup', 'description'])"
5960
[groups]="groups | async"
60-
[selection]="selection">
61+
[selection]="selection"
62+
[authzVoNames]="voNames"
63+
[groupWithAuthzGroupPairs]="_complementaryObjectsWithAuthzGroups?.get(role.roleName)?.get('group')">
6164
</perun-web-apps-groups-list>
6265
</div>
6366
<div
@@ -68,9 +71,11 @@ <h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
6871
</div>
6972
<perun-web-apps-resources-list
7073
[resources]="resources | async"
71-
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'vo', 'facility', 'description'] : ['select', 'id', 'name', 'vo', 'facility', 'description']"
74+
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'vo', 'facility', 'description'] : (editable ? ['select', 'id', 'name', 'vo', 'facility', 'description'] : ['id', 'name', 'authzGroup', 'vo', 'facility', 'description'])"
7275
[routingVo]="true"
73-
[selection]="selection">
76+
[selection]="selection"
77+
[authzVoNames]="voNames"
78+
[resourceWithAuthzGroupPairs]="_complementaryObjectsWithAuthzGroups?.get(role.roleName)?.get('resource')">
7479
</perun-web-apps-resources-list>
7580
</div>
7681
<div
@@ -80,9 +85,11 @@ <h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
8085
{{('ROLES.' + role.roleName + '_FACILITIES_' + entityType) | translate}}
8186
</div>
8287
<perun-web-apps-facilities-list
83-
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'description'] : ['select', 'id', 'name', 'description']"
88+
[displayedColumns]="role.roleName === 'MEMBERSHIP' ? ['id', 'name', 'description'] : (editable ? ['select', 'id', 'name', 'description'] : ['id', 'name', 'authzGroup', 'description'])"
8489
[facilities]="facilities | async"
85-
[selection]="selectedFacilities">
90+
[selection]="selectedFacilities"
91+
[authzVoNames]="voNames"
92+
[facilityWithAuthzGroupPairs]="_complementaryObjectsWithAuthzGroups?.get(role.roleName)?.get('facility')">
8693
</perun-web-apps-facilities-list>
8794
</div>
8895
<div *ngIf="role.roleName === 'SPONSORSHIP'" class="mb-3">

apps/admin-gui/src/app/shared/components/roles-list/roles-page.component.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class RolesPageComponent implements OnInit {
5252
@Input() showDescription: boolean;
5353
@Input() entityId: number;
5454
@Input() entityType: 'SELF' | 'USER' | 'GROUP';
55+
@Input() editable = true;
5556
@Output() reload = new EventEmitter<void>();
5657
@Output() startLoading = new EventEmitter<void>();
5758

@@ -62,6 +63,8 @@ export class RolesPageComponent implements OnInit {
6263
allRules: RoleManagementRules[] = [];
6364
disableRemove = false;
6465

66+
voNames: Map<number, string> = new Map<number, string>();
67+
6568
selectedRole = new BehaviorSubject<RoleManagementRules>(null);
6669

6770
groups: Observable<Group[]> = this.selectedRole.pipe(
@@ -139,6 +142,7 @@ export class RolesPageComponent implements OnInit {
139142
startWith([])
140143
);
141144

145+
_complementaryObjectsWithAuthzGroups = new Map<string, Map<string, Map<number, Group[]>>>();
142146
private _roles = new Map<string, Map<string, number[]>>();
143147

144148
constructor(
@@ -169,6 +173,13 @@ export class RolesPageComponent implements OnInit {
169173
.filter((rule) => this._roles.has(rule.roleName));
170174
}
171175

176+
@Input() set complementaryObjectsWithAuthzGroups(
177+
compObjects: Map<string, Map<string, Map<number, Group[]>>>
178+
) {
179+
this._complementaryObjectsWithAuthzGroups = compObjects;
180+
this.updateVoNames();
181+
}
182+
172183
ngOnInit(): void {
173184
this.assignableRules = this.guiAuthResolver.getAssignableRoleRules(
174185
this.entityType === 'GROUP' ? 'GROUP' : 'USER'
@@ -369,4 +380,27 @@ export class RolesPageComponent implements OnInit {
369380
modifiedByUid: resource.modifiedByUid,
370381
};
371382
}
383+
384+
private updateVoNames(): void {
385+
const voIds = new Set<number>();
386+
this._complementaryObjectsWithAuthzGroups.forEach((beanNamesMap) => {
387+
beanNamesMap.forEach((beanIdsToGroupsMap) => {
388+
beanIdsToGroupsMap.forEach((groups) => {
389+
groups.forEach((group) => {
390+
if (!voIds.has(group.voId) && !this.voNames.has(group.voId)) {
391+
voIds.add(group.voId);
392+
}
393+
});
394+
});
395+
});
396+
});
397+
398+
if (voIds.size > 0) {
399+
this.vosService.getVosByIds([...voIds]).subscribe((vos) => {
400+
vos.forEach((vo) => {
401+
this.voNames.set(vo.id, vo.name);
402+
});
403+
});
404+
}
405+
}
372406
}
Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
1-
<app-perun-web-apps-roles-page
2-
[outerLoading]="this.outerLoading"
3-
[roles]="this.roles"
4-
[entityId]="this.userId"
5-
[showDescription]="this.showDescription"
6-
[entityType]="this.entityType"
7-
(reload)="getData()"
8-
(startLoading)="outerLoading = true;">
9-
</app-perun-web-apps-roles-page>
1+
<h1 class="page-subtitle d-flex">{{'ROLES.TITLE'| translate}}</h1>
2+
<mat-tab-group>
3+
<mat-tab>
4+
<ng-template matTabLabel>
5+
{{'USER_DETAIL.DASHBOARD.DIRECT_ROLES' | translate}}
6+
</ng-template>
7+
<ng-template matTabContent>
8+
<app-perun-web-apps-roles-page
9+
[outerLoading]="this.outerLoading"
10+
[roles]="this.roles"
11+
[entityId]="this.userId"
12+
[showDescription]="this.showDescription"
13+
[entityType]="this.entityType"
14+
[editable]="true"
15+
(reload)="getData()"
16+
(startLoading)="outerLoading = true;">
17+
</app-perun-web-apps-roles-page>
18+
</ng-template>
19+
</mat-tab>
20+
21+
<mat-tab>
22+
<ng-template matTabLabel>
23+
{{'USER_DETAIL.DASHBOARD.AUTHORIZED_GROUP_BASED_ROLES' | translate}}
24+
</ng-template>
25+
<ng-template matTabContent>
26+
<app-perun-web-apps-roles-page
27+
[outerLoading]="this.outerLoading"
28+
[roles]="this.indirectRoles"
29+
[entityId]="this.userId"
30+
[showDescription]="this.showDescription"
31+
[entityType]="this.entityType"
32+
[editable]="false"
33+
[complementaryObjectsWithAuthzGroups]="this.rolesComplementaryObjectsWithAuthzGroups"
34+
(reload)="getData()"
35+
(startLoading)="outerLoading = true;">
36+
</app-perun-web-apps-roles-page>
37+
</ng-template>
38+
</mat-tab>
39+
</mat-tab-group>

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

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { Component, HostBinding, OnInit } from '@angular/core';
2-
import { AuthzResolverService } from '@perun-web-apps/perun/openapi';
2+
import { AuthzResolverService, Group } from '@perun-web-apps/perun/openapi';
33
import { ActivatedRoute } from '@angular/router';
44
import { StoreService, RoleService } from '@perun-web-apps/perun/services';
5-
import { iif, of } from 'rxjs';
6-
import { mergeMap } from 'rxjs/operators';
75

86
@Component({
97
selector: 'app-user-roles',
@@ -16,6 +14,8 @@ export class UserRolesComponent implements OnInit {
1614
userId: number;
1715

1816
roles = new Map<string, Map<string, number[]>>();
17+
indirectRoles = new Map<string, Map<string, number[]>>();
18+
rolesComplementaryObjectsWithAuthzGroups = new Map<string, Map<string, Map<number, Group[]>>>();
1919
outerLoading: boolean;
2020
showDescription = true;
2121
entityType: 'SELF' | 'USER';
@@ -44,21 +44,51 @@ export class UserRolesComponent implements OnInit {
4444

4545
getData(): void {
4646
this.outerLoading = true;
47+
48+
// get user based roles
4749
this.roles.clear();
48-
of(this.entityType)
49-
.pipe(
50-
mergeMap((type) =>
51-
iif(
52-
() => type === 'SELF',
53-
of(this.store.getPerunPrincipal().roles),
54-
this.authzResolverService.getUserRoles(this.userId)
55-
)
56-
)
57-
)
58-
.subscribe((roles) => {
50+
this.authzResolverService.getUserDirectRoles(this.userId).subscribe({
51+
next: (roles) => {
52+
// prepare direct roles
5953
const roleNames = Object.keys(roles).map((role) => role.toUpperCase());
6054
this.roles = this.roleService.prepareRoles(roles, roleNames);
61-
this.outerLoading = false;
62-
});
55+
56+
// get group based roles
57+
this.indirectRoles.clear();
58+
this.authzResolverService
59+
.getUserRolesObtainedFromAuthorizedGroupMemberships(this.userId)
60+
.subscribe({
61+
next: (groupBasedRoles) => {
62+
// prepare group based roles
63+
const groupBasedRoleNames = Object.keys(groupBasedRoles).map((role) =>
64+
role.toUpperCase()
65+
);
66+
this.indirectRoles = this.roleService.prepareRoles(
67+
groupBasedRoles,
68+
groupBasedRoleNames
69+
);
70+
71+
// get map of roles and complementary objects with authorized groups
72+
this.rolesComplementaryObjectsWithAuthzGroups.clear();
73+
this.authzResolverService
74+
.getRoleComplementaryObjectsWithAuthorizedGroups(this.userId)
75+
.subscribe({
76+
next: (obtainedComplementaryObjects) => {
77+
// prepare map
78+
this.rolesComplementaryObjectsWithAuthzGroups =
79+
this.roleService.prepareComplementaryObjects(
80+
Object.keys(obtainedComplementaryObjects),
81+
obtainedComplementaryObjects
82+
);
83+
this.outerLoading = false;
84+
},
85+
error: () => (this.outerLoading = false),
86+
});
87+
},
88+
error: () => (this.outerLoading = false),
89+
});
90+
},
91+
error: () => (this.outerLoading = false),
92+
});
6393
}
6494
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2512,7 +2512,9 @@
25122512
"SHOW_RECENTLY_VIEWED": "Show Recently viewed",
25132513
"SHOW_ROLES": "Show roles",
25142514
"MAIL_CHANGE_SUCCESS": "Preferred mail has been changed",
2515-
"ANONYMIZED": "User anonymized"
2515+
"ANONYMIZED": "User anonymized",
2516+
"DIRECT_ROLES": "User based",
2517+
"AUTHORIZED_GROUP_BASED_ROLES": "Group based"
25162518
},
25172519
"OVERVIEW": {
25182520
"GENERAL_SETTINGS": "General settings"
@@ -2691,7 +2693,8 @@
26912693
"TECHNICAL_OWNERS": "Technical owners",
26922694
"DESTINATIONS": "Destinations",
26932695
"HOSTS": "Hosts",
2694-
"NO_FACILITIES_WARNING": "No facilities found"
2696+
"NO_FACILITIES_WARNING": "No facilities found",
2697+
"AUTHZ_GROUP": "Authorized group"
26952698
},
26962699
"CONSENT_HUBS_LIST": {
26972700
"ID": "Id",
@@ -2845,7 +2848,8 @@
28452848
"CREATE_RELATION_AUTH_TOOLTIP": "You don't have permission to create relation with this group.",
28462849
"SYNCHRONIZED_GROUP": "Group is filled with members from an external source",
28472850
"INDIRECT_GROUP": "This group is assigned as a part of a groups tree.",
2848-
"MULTIPLE_ASSIGNMENTS": "This group is assigned manually and also as a part of a groups tree. You can remove only manual assignment."
2851+
"MULTIPLE_ASSIGNMENTS": "This group is assigned manually and also as a part of a groups tree. You can remove only manual assignment.",
2852+
"AUTHZ_GROUP": "Authorized group"
28492853
},
28502854
"GROUP_MENU": {
28512855
"MOVE": "Move group",
@@ -2860,7 +2864,8 @@
28602864
"VOS_LIST": {
28612865
"ID": "Id",
28622866
"NAME": "Name",
2863-
"SHORTNAME": "Short name"
2867+
"SHORTNAME": "Short name",
2868+
"AUTHZ_GROUP": "Authorized group"
28642869
},
28652870
"VO_SEARCH_SELECT": {
28662871
"SELECT_VO": "Select organization",
@@ -2891,7 +2896,8 @@
28912896
"TABLE_RESOURCE_DESCRIPTION": "Description",
28922897
"TABLE_GROUP_RESOURCE_STATUS": "Status",
28932898
"NO_RESOURCES_WARNING": "No resources assigned.",
2894-
"INDIRECT_RESOURCE": "This resource is assigned indirectly."
2899+
"INDIRECT_RESOURCE": "This resource is assigned indirectly.",
2900+
"AUTHZ_GROUP": "Authorized group"
28952901
},
28962902
"RESOURCE_SEARCH_SELECT": {
28972903
"SELECT_RESOURCE": "Select resource",

libs/perun/components/src/lib/facilities-list/facilities-list.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,16 @@
5151
{{facility.facility.name}}
5252
</td>
5353
</ng-container>
54+
<ng-container matColumnDef="authzGroup">
55+
<th mat-header-cell *matHeaderCellDef mat-sort-header>
56+
{{'SHARED.COMPONENTS.FACILITIES_LIST.AUTHZ_GROUP' | translate}}
57+
</th>
58+
<td mat-cell *matCellDef="let facility">
59+
{{ facilityWithAuthzGroupPairs.has(facility.facility.id)
60+
? authzVoNames.get(facilityWithAuthzGroupPairs.get(facility.facility.id)[0].voId) + ":" + facilityWithAuthzGroupPairs.get(facility.facility.id)[0].name
61+
: "" }}
62+
</td>
63+
</ng-container>
5464
<ng-container matColumnDef="description">
5565
<th *matHeaderCellDef mat-header-cell>
5666
{{'SHARED.COMPONENTS.FACILITIES_LIST.DESCRIPTION' | translate}}

libs/perun/components/src/lib/facilities-list/facilities-list.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Component, Input, OnChanges, ViewChild } from '@angular/core';
22
import { MatSort } from '@angular/material/sort';
33
import { MatTableDataSource } from '@angular/material/table';
4-
import { EnrichedFacility } from '@perun-web-apps/perun/openapi';
4+
import { EnrichedFacility, Group } from '@perun-web-apps/perun/openapi';
55
import {
66
customDataSourceFilterPredicate,
77
customDataSourceSort,
@@ -21,6 +21,8 @@ import { TableWrapperComponent } from '@perun-web-apps/perun/utils';
2121
})
2222
export class FacilitiesListComponent implements OnChanges {
2323
@Input() facilities: EnrichedFacility[];
24+
@Input() facilityWithAuthzGroupPairs: Map<number, Group[]>;
25+
@Input() authzVoNames: Map<number, string>;
2426
@Input() recentIds: number[];
2527
@Input() filterValue: string;
2628
@Input() tableId: string;

libs/perun/components/src/lib/groups-list/groups-list.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@
9090
{{group.name}}
9191
</td>
9292
</ng-container>
93+
<ng-container matColumnDef="authzGroup">
94+
<th *matHeaderCellDef mat-header-cell mat-sort-header>
95+
{{'SHARED_LIB.PERUN.COMPONENTS.GROUPS_LIST.AUTHZ_GROUP' | translate}}
96+
</th>
97+
<td *matCellDef="let group" mat-cell>
98+
{{ groupWithAuthzGroupPairs.has(group.id)
99+
? authzVoNames.get(groupWithAuthzGroupPairs.get(group.id)[0].voId) + ":" + groupWithAuthzGroupPairs.get(group.id)[0].name
100+
: ""}}
101+
</td>
102+
</ng-container>
93103
<ng-container matColumnDef="status">
94104
<th *matHeaderCellDef mat-header-cell mat-sort-header>
95105
{{'SHARED_LIB.PERUN.COMPONENTS.GROUPS_LIST.TABLE_GROUP_STATUS' | translate}}

libs/perun/components/src/lib/groups-list/groups-list.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ import { GroupUtilsService } from '@perun-web-apps/perun/services';
4545
export class GroupsListComponent {
4646
@Input() theme = 'group-theme';
4747
@Input() selection = new SelectionModel<GroupWithStatus>(true, []);
48+
@Input() groupWithAuthzGroupPairs: Map<number, Group[]>;
49+
@Input() authzVoNames: Map<number, string>;
4850
@Input() disableMembers: boolean;
4951
@Input() disableGroups: boolean;
5052
@Input() groupsToDisableCheckbox: Set<number> = new Set<number>();

0 commit comments

Comments
 (0)