1
0

77205: Add Comm/Coll admin guards & authorization checks

This commit is contained in:
Yura Bondarenko
2021-02-25 14:55:41 +01:00
parent d401a92a17
commit 3f4e032bb5
12 changed files with 287 additions and 186 deletions

View File

@@ -1,6 +1,6 @@
import { Component, Injector, OnInit } from '@angular/core'; import { Component, Injector, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; import { combineLatest, combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { first, map, take } from 'rxjs/operators'; import { first, map, take } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service'; import { AuthService } from '../../core/auth/auth.service';
import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ScriptDataService } from '../../core/data/processes/script-data.service';
@@ -103,192 +103,197 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
* Initialize all menu sections and items for this menu * Initialize all menu sections and items for this menu
*/ */
createMenu() { createMenu() {
const menuList = [ combineLatest([
/* News */ this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin),
{ this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin),
id: 'new', ]).subscribe(([isCollectionAdmin, isCommunityAdmin]) => {
active: false, const menuList = [
visible: true, /* News */
model: { {
type: MenuItemType.TEXT, id: 'new',
text: 'menu.section.new' active: false,
} as TextMenuItemModel, visible: true,
icon: 'plus-circle', model: {
index: 0 type: MenuItemType.TEXT,
}, text: 'menu.section.new'
{ } as TextMenuItemModel,
id: 'new_community', icon: 'plus-circle',
parentID: 'new', index: 0
active: false, },
visible: true, {
model: { id: 'new_community',
type: MenuItemType.ONCLICK, parentID: 'new',
text: 'menu.section.new_community', active: false,
function: () => { visible: true,
this.modalService.open(CreateCommunityParentSelectorComponent); model: {
} type: MenuItemType.ONCLICK,
} as OnClickMenuItemModel, text: 'menu.section.new_community',
}, function: () => {
{ this.modalService.open(CreateCommunityParentSelectorComponent);
id: 'new_collection', }
parentID: 'new', } as OnClickMenuItemModel,
active: false, },
visible: true, {
model: { id: 'new_collection',
type: MenuItemType.ONCLICK, parentID: 'new',
text: 'menu.section.new_collection', active: false,
function: () => { visible: true,
this.modalService.open(CreateCollectionParentSelectorComponent); model: {
} type: MenuItemType.ONCLICK,
} as OnClickMenuItemModel, text: 'menu.section.new_collection',
}, function: () => {
{ this.modalService.open(CreateCollectionParentSelectorComponent);
id: 'new_item', }
parentID: 'new', } as OnClickMenuItemModel,
active: false, },
visible: true, {
model: { id: 'new_item',
type: MenuItemType.ONCLICK, parentID: 'new',
text: 'menu.section.new_item', active: false,
function: () => { visible: true,
this.modalService.open(CreateItemParentSelectorComponent); model: {
} type: MenuItemType.ONCLICK,
} as OnClickMenuItemModel, text: 'menu.section.new_item',
}, function: () => {
{ this.modalService.open(CreateItemParentSelectorComponent);
id: 'new_process', }
parentID: 'new', } as OnClickMenuItemModel,
active: false, },
visible: true, {
model: { id: 'new_process',
type: MenuItemType.LINK, parentID: 'new',
text: 'menu.section.new_process', active: false,
link: '/processes/new' visible: true,
} as LinkMenuItemModel, model: {
}, type: MenuItemType.LINK,
{ text: 'menu.section.new_process',
id: 'new_item_version', link: '/processes/new'
parentID: 'new', } as LinkMenuItemModel,
active: false, },
visible: true, {
model: { id: 'new_item_version',
type: MenuItemType.LINK, parentID: 'new',
text: 'menu.section.new_item_version', active: false,
link: '' visible: true,
} as LinkMenuItemModel, model: {
}, type: MenuItemType.LINK,
text: 'menu.section.new_item_version',
link: ''
} as LinkMenuItemModel,
},
/* Edit */ /* Edit */
{ {
id: 'edit', id: 'edit',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.TEXT, type: MenuItemType.TEXT,
text: 'menu.section.edit' text: 'menu.section.edit'
} as TextMenuItemModel, } as TextMenuItemModel,
icon: 'pencil-alt', icon: 'pencil-alt',
index: 1 index: 1
}, },
{ {
id: 'edit_community', id: 'edit_community',
parentID: 'edit', parentID: 'edit',
active: false, active: false,
visible: true, visible: isCommunityAdmin,
model: { model: {
type: MenuItemType.ONCLICK, type: MenuItemType.ONCLICK,
text: 'menu.section.edit_community', text: 'menu.section.edit_community',
function: () => { function: () => {
this.modalService.open(EditCommunitySelectorComponent); this.modalService.open(EditCommunitySelectorComponent);
} }
} as OnClickMenuItemModel, } as OnClickMenuItemModel,
}, },
{ {
id: 'edit_collection', id: 'edit_collection',
parentID: 'edit', parentID: 'edit',
active: false, active: false,
visible: true, visible: isCollectionAdmin,
model: { model: {
type: MenuItemType.ONCLICK, type: MenuItemType.ONCLICK,
text: 'menu.section.edit_collection', text: 'menu.section.edit_collection',
function: () => { function: () => {
this.modalService.open(EditCollectionSelectorComponent); this.modalService.open(EditCollectionSelectorComponent);
} }
} as OnClickMenuItemModel, } as OnClickMenuItemModel,
}, },
{ {
id: 'edit_item', id: 'edit_item',
parentID: 'edit', parentID: 'edit',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.ONCLICK, type: MenuItemType.ONCLICK,
text: 'menu.section.edit_item', text: 'menu.section.edit_item',
function: () => { function: () => {
this.modalService.open(EditItemSelectorComponent); this.modalService.open(EditItemSelectorComponent);
} }
} as OnClickMenuItemModel, } as OnClickMenuItemModel,
}, },
/* Curation tasks */ /* Curation tasks */
{ {
id: 'curation_tasks', id: 'curation_tasks',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.LINK, type: MenuItemType.LINK,
text: 'menu.section.curation_task', text: 'menu.section.curation_task',
link: '' link: ''
} as LinkMenuItemModel, } as LinkMenuItemModel,
icon: 'filter', icon: 'filter',
index: 7 index: 7
}, },
/* Statistics */ /* Statistics */
{ {
id: 'statistics_task', id: 'statistics_task',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.LINK, type: MenuItemType.LINK,
text: 'menu.section.statistics_task', text: 'menu.section.statistics_task',
link: '' link: ''
} as LinkMenuItemModel, } as LinkMenuItemModel,
icon: 'chart-bar', icon: 'chart-bar',
index: 8 index: 8
}, },
/* Control Panel */ /* Control Panel */
{ {
id: 'control_panel', id: 'control_panel',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.LINK, type: MenuItemType.LINK,
text: 'menu.section.control_panel', text: 'menu.section.control_panel',
link: '' link: ''
} as LinkMenuItemModel, } as LinkMenuItemModel,
icon: 'cogs', icon: 'cogs',
index: 9 index: 9
}, },
/* Processes */ /* Processes */
{ {
id: 'processes', id: 'processes',
active: false, active: false,
visible: true, visible: true,
model: { model: {
type: MenuItemType.LINK, type: MenuItemType.LINK,
text: 'menu.section.processes', text: 'menu.section.processes',
link: '/processes' link: '/processes'
} as LinkMenuItemModel, } as LinkMenuItemModel,
icon: 'terminal', icon: 'terminal',
index: 10 index: 10
}, },
]; ];
menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, {
shouldPersistOnRouteChange: true shouldPersistOnRouteChange: true
}))); })));
})
} }
/** /**

View File

@@ -35,7 +35,7 @@
</ds-comcol-page-content> </ds-comcol-page-content>
</header> </header>
<div class="pl-2"> <div class="pl-2">
<ds-dso-page-edit-button [pageRoutePrefix]="'collections'" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button> <ds-dso-page-edit-button *ngIf="isCollectionAdmin$ | async" [pageRoutePrefix]="'collections'" [dso]="collection" [tooltipMsg]="'collection.page.edit'"></ds-dso-page-edit-button>
</div> </div>
</div> </div>
<section class="comcol-page-browse-section"> <section class="comcol-page-browse-section">

View File

@@ -22,6 +22,8 @@ import { hasValue, isNotEmpty } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
import {PaginationChangeEvent} from '../shared/pagination/paginationChangeEvent.interface'; import {PaginationChangeEvent} from '../shared/pagination/paginationChangeEvent.interface';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
@Component({ @Component({
selector: 'ds-collection-page', selector: 'ds-collection-page',
@@ -44,6 +46,11 @@ export class CollectionPageComponent implements OnInit {
sortConfig: SortOptions sortConfig: SortOptions
}>; }>;
/**
* Whether the current user is a Community admin
*/
isCollectionAdmin$: Observable<boolean>;
constructor( constructor(
private collectionDataService: CollectionDataService, private collectionDataService: CollectionDataService,
private searchService: SearchService, private searchService: SearchService,
@@ -51,6 +58,7 @@ export class CollectionPageComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private authorizationDataService: AuthorizationDataService,
) { ) {
this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig = new PaginationComponentOptions();
this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.id = 'collection-page-pagination';
@@ -70,6 +78,7 @@ export class CollectionPageComponent implements OnInit {
filter((collection: Collection) => hasValue(collection)), filter((collection: Collection) => hasValue(collection)),
mergeMap((collection: Collection) => collection.logo) mergeMap((collection: Collection) => collection.logo)
); );
this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
this.paginationChanges$ = new BehaviorSubject({ this.paginationChanges$ = new BehaviorSubject({
paginationConfig: this.paginationConfig, paginationConfig: this.paginationConfig,

View File

@@ -12,6 +12,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
import { IsCollectionAdminGuard } from '../../access-control/guards/is-collection-admin.guard';
/** /**
* Routing module that handles the routing for the Edit Collection page administrator functionality * Routing module that handles the routing for the Edit Collection page administrator functionality
@@ -26,6 +27,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
}, },
data: { breadcrumbKey: 'collection.edit' }, data: { breadcrumbKey: 'collection.edit' },
component: EditCollectionPageComponent, component: EditCollectionPageComponent,
canActivate: [IsCollectionAdminGuard],
children: [ children: [
{ {
path: '', path: '',

View File

@@ -21,7 +21,7 @@
</ds-comcol-page-content> </ds-comcol-page-content>
</header> </header>
<div class="pl-2"> <div class="pl-2">
<ds-dso-page-edit-button [pageRoutePrefix]="'communities'" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button> <ds-dso-page-edit-button *ngIf="isCommunityAdmin$ | async" [pageRoutePrefix]="'communities'" [dso]="communityPayload" [tooltipMsg]="'community.page.edit'"></ds-dso-page-edit-button>
</div> </div>
</div> </div>
<section class="comcol-page-browse-section"> <section class="comcol-page-browse-section">

View File

@@ -15,6 +15,8 @@ import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { redirectOn4xx } from '../core/shared/operators'; import { redirectOn4xx } from '../core/shared/operators';
import { AuthService } from '../core/auth/auth.service'; import { AuthService } from '../core/auth/auth.service';
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
import { FeatureID } from '../core/data/feature-authorization/feature-id';
@Component({ @Component({
selector: 'ds-community-page', selector: 'ds-community-page',
@@ -32,6 +34,11 @@ export class CommunityPageComponent implements OnInit {
*/ */
communityRD$: Observable<RemoteData<Community>>; communityRD$: Observable<RemoteData<Community>>;
/**
* Whether the current user is a Community admin
*/
isCommunityAdmin$: Observable<boolean>;
/** /**
* The logo of this community * The logo of this community
*/ */
@@ -42,6 +49,7 @@ export class CommunityPageComponent implements OnInit {
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router, private router: Router,
private authService: AuthService, private authService: AuthService,
private authorizationDataService: AuthorizationDataService
) { ) {
} }
@@ -54,7 +62,8 @@ export class CommunityPageComponent implements OnInit {
this.logoRD$ = this.communityRD$.pipe( this.logoRD$ = this.communityRD$.pipe(
map((rd: RemoteData<Community>) => rd.payload), map((rd: RemoteData<Community>) => rd.payload),
filter((community: Community) => hasValue(community)), filter((community: Community) => hasValue(community)),
mergeMap((community: Community) => community.logo)); mergeMap((community: Community) => community.logo)
);
this.isCommunityAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCommunityAdmin);
} }
} }

View File

@@ -10,6 +10,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res
import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component';
import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver';
import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component';
import { IsCommunityAdminGuard } from '../../access-control/guards/is-community-admin.guard';
/** /**
* Routing module that handles the routing for the Edit Community page administrator functionality * Routing module that handles the routing for the Edit Community page administrator functionality
@@ -24,6 +25,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit
}, },
data: { breadcrumbKey: 'community.edit' }, data: { breadcrumbKey: 'community.edit' },
component: EditCommunityPageComponent, component: EditCommunityPageComponent,
canActivate: [IsCommunityAdminGuard],
children: [ children: [
{ {
path: '', path: '',

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { IsCollectionAdminGuard } from './is-collection-admin.guard';
describe('IsCollectionAdminGuard', () => {
let guard: IsCollectionAdminGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(IsCollectionAdminGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});

View File

@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
/**
* Guard for preventing unauthorized editing of Communities
*/
@Injectable({
providedIn: 'root'
})
export class IsCollectionAdminGuard implements CanActivate {
constructor(private authorizationService: AuthorizationDataService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin);
}
}

View File

@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { IsCommunityAdminGuard } from './is-community-admin.guard';
describe('IsCommunityAdminGuard', () => {
let guard: IsCommunityAdminGuard;
beforeEach(() => {
TestBed.configureTestingModule({});
guard = TestBed.inject(IsCommunityAdminGuard);
});
it('should be created', () => {
expect(guard).toBeTruthy();
});
});

View File

@@ -0,0 +1,20 @@
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
/**
* Guard for preventing unauthorized editing of Communities
*/
@Injectable({
providedIn: 'root'
})
export class IsCommunityAdminGuard implements CanActivate {
constructor(private authorizationService: AuthorizationDataService) {
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin);
}
}

View File

@@ -10,4 +10,6 @@ export enum FeatureID {
ReinstateItem = 'reinstateItem', ReinstateItem = 'reinstateItem',
EPersonRegistration = 'epersonRegistration', EPersonRegistration = 'epersonRegistration',
CanManageGroups = 'canManageGroups', CanManageGroups = 'canManageGroups',
IsCollectionAdmin = 'isCollectionAdmin',
IsCommunityAdmin = 'isCommunityAdmin',
} }