diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts index 9d7b35eb68..2748254dbb 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.ts @@ -1,6 +1,6 @@ import { Component, Injector, OnInit } from '@angular/core'; 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 { AuthService } from '../../core/auth/auth.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 */ createMenu() { - const menuList = [ - /* News */ - { - id: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.new' - } as TextMenuItemModel, - icon: 'plus-circle', - index: 0 - }, - { - id: 'new_community', - parentID: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.new_community', - function: () => { - this.modalService.open(CreateCommunityParentSelectorComponent); - } - } as OnClickMenuItemModel, - }, - { - id: 'new_collection', - parentID: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.new_collection', - function: () => { - this.modalService.open(CreateCollectionParentSelectorComponent); - } - } as OnClickMenuItemModel, - }, - { - id: 'new_item', - parentID: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.new_item', - function: () => { - this.modalService.open(CreateItemParentSelectorComponent); - } - } as OnClickMenuItemModel, - }, - { - id: 'new_process', - parentID: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.new_process', - link: '/processes/new' - } as LinkMenuItemModel, - }, - { - id: 'new_item_version', - parentID: 'new', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.new_item_version', - link: '' - } as LinkMenuItemModel, - }, + combineLatest([ + this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin), + this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin), + ]).subscribe(([isCollectionAdmin, isCommunityAdmin]) => { + const menuList = [ + /* News */ + { + id: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.new' + } as TextMenuItemModel, + icon: 'plus-circle', + index: 0 + }, + { + id: 'new_community', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.new_community', + function: () => { + this.modalService.open(CreateCommunityParentSelectorComponent); + } + } as OnClickMenuItemModel, + }, + { + id: 'new_collection', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.new_collection', + function: () => { + this.modalService.open(CreateCollectionParentSelectorComponent); + } + } as OnClickMenuItemModel, + }, + { + id: 'new_item', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.new_item', + function: () => { + this.modalService.open(CreateItemParentSelectorComponent); + } + } as OnClickMenuItemModel, + }, + { + id: 'new_process', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.new_process', + link: '/processes/new' + } as LinkMenuItemModel, + }, + { + id: 'new_item_version', + parentID: 'new', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.new_item_version', + link: '' + } as LinkMenuItemModel, + }, - /* Edit */ - { - id: 'edit', - active: false, - visible: true, - model: { - type: MenuItemType.TEXT, - text: 'menu.section.edit' - } as TextMenuItemModel, - icon: 'pencil-alt', - index: 1 - }, - { - id: 'edit_community', - parentID: 'edit', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.edit_community', - function: () => { - this.modalService.open(EditCommunitySelectorComponent); - } - } as OnClickMenuItemModel, - }, - { - id: 'edit_collection', - parentID: 'edit', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.edit_collection', - function: () => { - this.modalService.open(EditCollectionSelectorComponent); - } - } as OnClickMenuItemModel, - }, - { - id: 'edit_item', - parentID: 'edit', - active: false, - visible: true, - model: { - type: MenuItemType.ONCLICK, - text: 'menu.section.edit_item', - function: () => { - this.modalService.open(EditItemSelectorComponent); - } - } as OnClickMenuItemModel, - }, + /* Edit */ + { + id: 'edit', + active: false, + visible: true, + model: { + type: MenuItemType.TEXT, + text: 'menu.section.edit' + } as TextMenuItemModel, + icon: 'pencil-alt', + index: 1 + }, + { + id: 'edit_community', + parentID: 'edit', + active: false, + visible: isCommunityAdmin, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.edit_community', + function: () => { + this.modalService.open(EditCommunitySelectorComponent); + } + } as OnClickMenuItemModel, + }, + { + id: 'edit_collection', + parentID: 'edit', + active: false, + visible: isCollectionAdmin, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.edit_collection', + function: () => { + this.modalService.open(EditCollectionSelectorComponent); + } + } as OnClickMenuItemModel, + }, + { + id: 'edit_item', + parentID: 'edit', + active: false, + visible: true, + model: { + type: MenuItemType.ONCLICK, + text: 'menu.section.edit_item', + function: () => { + this.modalService.open(EditItemSelectorComponent); + } + } as OnClickMenuItemModel, + }, - /* Curation tasks */ - { - id: 'curation_tasks', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.curation_task', - link: '' - } as LinkMenuItemModel, - icon: 'filter', - index: 7 - }, + /* Curation tasks */ + { + id: 'curation_tasks', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.curation_task', + link: '' + } as LinkMenuItemModel, + icon: 'filter', + index: 7 + }, - /* Statistics */ - { - id: 'statistics_task', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.statistics_task', - link: '' - } as LinkMenuItemModel, - icon: 'chart-bar', - index: 8 - }, + /* Statistics */ + { + id: 'statistics_task', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.statistics_task', + link: '' + } as LinkMenuItemModel, + icon: 'chart-bar', + index: 8 + }, - /* Control Panel */ - { - id: 'control_panel', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.control_panel', - link: '' - } as LinkMenuItemModel, - icon: 'cogs', - index: 9 - }, + /* Control Panel */ + { + id: 'control_panel', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.control_panel', + link: '' + } as LinkMenuItemModel, + icon: 'cogs', + index: 9 + }, - /* Processes */ - { - id: 'processes', - active: false, - visible: true, - model: { - type: MenuItemType.LINK, - text: 'menu.section.processes', - link: '/processes' - } as LinkMenuItemModel, - icon: 'terminal', - index: 10 - }, - ]; - menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { - shouldPersistOnRouteChange: true - }))); + /* Processes */ + { + id: 'processes', + active: false, + visible: true, + model: { + type: MenuItemType.LINK, + text: 'menu.section.processes', + link: '/processes' + } as LinkMenuItemModel, + icon: 'terminal', + index: 10 + }, + ]; + menuList.forEach((menuSection) => this.menuService.addSection(this.menuID, Object.assign(menuSection, { + shouldPersistOnRouteChange: true + }))); + }) } /** diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html index beb7413415..a9dd99068e 100644 --- a/src/app/+collection-page/collection-page.component.html +++ b/src/app/+collection-page/collection-page.component.html @@ -35,7 +35,7 @@
- +
diff --git a/src/app/+collection-page/collection-page.component.ts b/src/app/+collection-page/collection-page.component.ts index 8065480604..72e217a7ec 100644 --- a/src/app/+collection-page/collection-page.component.ts +++ b/src/app/+collection-page/collection-page.component.ts @@ -22,6 +22,8 @@ import { hasValue, isNotEmpty } from '../shared/empty.util'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { AuthService } from '../core/auth/auth.service'; 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({ selector: 'ds-collection-page', @@ -44,6 +46,11 @@ export class CollectionPageComponent implements OnInit { sortConfig: SortOptions }>; + /** + * Whether the current user is a Community admin + */ + isCollectionAdmin$: Observable; + constructor( private collectionDataService: CollectionDataService, private searchService: SearchService, @@ -51,6 +58,7 @@ export class CollectionPageComponent implements OnInit { private route: ActivatedRoute, private router: Router, private authService: AuthService, + private authorizationDataService: AuthorizationDataService, ) { this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig.id = 'collection-page-pagination'; @@ -70,6 +78,7 @@ export class CollectionPageComponent implements OnInit { filter((collection: Collection) => hasValue(collection)), mergeMap((collection: Collection) => collection.logo) ); + this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin); this.paginationChanges$ = new BehaviorSubject({ paginationConfig: this.paginationConfig, diff --git a/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts b/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts index e41f0ebda4..e4dea5e63c 100644 --- a/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts +++ b/src/app/+collection-page/edit-collection-page/edit-collection-page.routing.module.ts @@ -12,6 +12,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; 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 @@ -26,6 +27,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit }, data: { breadcrumbKey: 'collection.edit' }, component: EditCollectionPageComponent, + canActivate: [IsCollectionAdminGuard], children: [ { path: '', diff --git a/src/app/+community-page/community-page.component.html b/src/app/+community-page/community-page.component.html index 418e69ed10..c1d88c9ab8 100644 --- a/src/app/+community-page/community-page.component.html +++ b/src/app/+community-page/community-page.component.html @@ -21,7 +21,7 @@
- +
diff --git a/src/app/+community-page/community-page.component.ts b/src/app/+community-page/community-page.component.ts index e4812e6514..32886b3f01 100644 --- a/src/app/+community-page/community-page.component.ts +++ b/src/app/+community-page/community-page.component.ts @@ -15,6 +15,8 @@ import { fadeInOut } from '../shared/animations/fade'; import { hasValue } from '../shared/empty.util'; import { redirectOn4xx } from '../core/shared/operators'; 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({ selector: 'ds-community-page', @@ -32,6 +34,11 @@ export class CommunityPageComponent implements OnInit { */ communityRD$: Observable>; + /** + * Whether the current user is a Community admin + */ + isCommunityAdmin$: Observable; + /** * The logo of this community */ @@ -42,6 +49,7 @@ export class CommunityPageComponent implements OnInit { private route: ActivatedRoute, private router: Router, private authService: AuthService, + private authorizationDataService: AuthorizationDataService ) { } @@ -54,7 +62,8 @@ export class CommunityPageComponent implements OnInit { this.logoRD$ = this.communityRD$.pipe( map((rd: RemoteData) => rd.payload), filter((community: Community) => hasValue(community)), - mergeMap((community: Community) => community.logo)); + mergeMap((community: Community) => community.logo) + ); + this.isCommunityAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCommunityAdmin); } - } diff --git a/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts b/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts index 440fa01a30..b5aa20f252 100644 --- a/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts +++ b/src/app/+community-page/edit-community-page/edit-community-page.routing.module.ts @@ -10,6 +10,7 @@ import { ResourcePolicyTargetResolver } from '../../shared/resource-policies/res import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; 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 @@ -24,6 +25,7 @@ import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit }, data: { breadcrumbKey: 'community.edit' }, component: EditCommunityPageComponent, + canActivate: [IsCommunityAdminGuard], children: [ { path: '', diff --git a/src/app/access-control/guards/is-collection-admin.guard.spec.ts b/src/app/access-control/guards/is-collection-admin.guard.spec.ts new file mode 100644 index 0000000000..9a60980c01 --- /dev/null +++ b/src/app/access-control/guards/is-collection-admin.guard.spec.ts @@ -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(); + }); +}); diff --git a/src/app/access-control/guards/is-collection-admin.guard.ts b/src/app/access-control/guards/is-collection-admin.guard.ts new file mode 100644 index 0000000000..140ea46e68 --- /dev/null +++ b/src/app/access-control/guards/is-collection-admin.guard.ts @@ -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 { + return this.authorizationService.isAuthorized(FeatureID.IsCollectionAdmin); + } +} diff --git a/src/app/access-control/guards/is-community-admin.guard.spec.ts b/src/app/access-control/guards/is-community-admin.guard.spec.ts new file mode 100644 index 0000000000..52dc7ec33c --- /dev/null +++ b/src/app/access-control/guards/is-community-admin.guard.spec.ts @@ -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(); + }); +}); diff --git a/src/app/access-control/guards/is-community-admin.guard.ts b/src/app/access-control/guards/is-community-admin.guard.ts new file mode 100644 index 0000000000..fcc4f6520a --- /dev/null +++ b/src/app/access-control/guards/is-community-admin.guard.ts @@ -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 { + return this.authorizationService.isAuthorized(FeatureID.IsCommunityAdmin); + } +} diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index 0af2399b7c..e3473a895e 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -10,4 +10,6 @@ export enum FeatureID { ReinstateItem = 'reinstateItem', EPersonRegistration = 'epersonRegistration', CanManageGroups = 'canManageGroups', + IsCollectionAdmin = 'isCollectionAdmin', + IsCommunityAdmin = 'isCommunityAdmin', }