71429: SiteAdministratorGuard on admin routes + authorization check on visibility of admin sidebar sections

This commit is contained in:
Kristof De Langhe
2020-06-18 17:13:37 +02:00
parent 8350833b61
commit bbbeddc875
8 changed files with 77 additions and 37 deletions

View File

@@ -245,7 +245,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
});
}));
this.canImpersonate$ = this.epersonService.getActiveEPerson().pipe(
switchMap((eperson) => this.authorizationService.isAuthenticated(eperson.self, undefined, FeatureType.LoginOnBehalfOf))
switchMap((eperson) => this.authorizationService.isAuthenticated(FeatureType.LoginOnBehalfOf, eperson.self))
);
});
}

View File

@@ -2,7 +2,7 @@ import { Component, Injector, OnInit } from '@angular/core';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { combineLatest as combineLatestObservable } from 'rxjs';
import { Observable } from 'rxjs/internal/Observable';
import { first, map } from 'rxjs/operators';
import { first, map, take } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
@@ -18,6 +18,8 @@ import { TextMenuItemModel } from '../../shared/menu/menu-item/models/text.model
import { MenuComponent } from '../../shared/menu/menu.component';
import { MenuService } from '../../shared/menu/menu.service';
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
import { FeatureType } from '../../core/data/feature-authorization/feature-type';
/**
* Component representing the admin sidebar
@@ -61,7 +63,8 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
protected injector: Injector,
private variableService: CSSVariableService,
private authService: AuthService,
private modalService: NgbModal
private modalService: NgbModal,
private authorizationService: AuthorizationDataService
) {
super(menuService, injector);
}
@@ -70,7 +73,8 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
* Set and calculate all initial values of the instance variables
*/
ngOnInit(): void {
this.createMenu();
this.authorizationService.isAuthenticated(FeatureType.AdministratorOf).pipe(take(1)).subscribe((authorized) => {
this.createMenu(authorized);
super.ngOnInit();
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
this.authService.isAuthenticated()
@@ -88,12 +92,13 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
.pipe(
map(([collapsed, previewCollapsed]) => (!collapsed || !previewCollapsed))
);
});
}
/**
* Initialize all menu sections and items for this menu
*/
createMenu() {
createMenu(isAdministratorOfSite: boolean) {
const menuList = [
/* News */
{
@@ -309,7 +314,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
{
id: 'access_control',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.TEXT,
text: 'menu.section.access_control'
@@ -321,7 +326,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
id: 'access_control_people',
parentID: 'access_control',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.access_control_people',
@@ -332,7 +337,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
id: 'access_control_groups',
parentID: 'access_control',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.access_control_groups',
@@ -343,7 +348,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
id: 'access_control_authorizations',
parentID: 'access_control',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.access_control_authorizations',
@@ -354,7 +359,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
{
id: 'admin_search',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.admin_search',
@@ -367,7 +372,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
{
id: 'registries',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.TEXT,
text: 'menu.section.registries'
@@ -379,7 +384,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
id: 'registries_metadata',
parentID: 'registries',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.registries_metadata',
@@ -390,7 +395,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
id: 'registries_format',
parentID: 'registries',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.registries_format',
@@ -443,7 +448,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
{
id: 'workflow',
active: false,
visible: true,
visible: isAdministratorOfSite,
model: {
type: MenuItemType.LINK,
text: 'menu.section.workflow',

View File

@@ -10,6 +10,7 @@ import { Collection } from './core/shared/collection.model';
import { Item } from './core/shared/item.model';
import { getItemPageRoute } from './+item-page/item-page-routing.module';
import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module';
import { SiteAdministratorGuard } from './core/data/feature-authorization/site-administrator.guard';
const ITEM_MODULE_PATH = 'items';
@@ -82,7 +83,7 @@ export function getDSOPath(dso: DSpaceObject): string {
},
{ path: 'search', loadChildren: './+search-page/search-page-routing.module#SearchPageRoutingModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule'},
{ path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
{ path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [SiteAdministratorGuard] },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },

View File

@@ -149,6 +149,7 @@ import { Feature } from './shared/feature.model';
import { Authorization } from './shared/authorization.model';
import { FeatureDataService } from './data/feature-authorization/feature-data.service';
import { AuthorizationDataService } from './data/feature-authorization/authorization-data.service';
import { SiteAdministratorGuard } from './data/feature-authorization/site-administrator.guard';
/**
* When not in production, endpoint responses can be mocked for testing purposes
@@ -270,6 +271,7 @@ const PROVIDERS = [
WorkflowActionDataService,
FeatureDataService,
AuthorizationDataService,
SiteAdministratorGuard,
// register AuthInterceptor as HttpInterceptor
{
provide: HTTP_INTERCEPTORS,

View File

@@ -59,8 +59,8 @@ export class AuthorizationDataService extends DataService<Authorization> {
* If not provided, the UUID of the currently authenticated {@link EPerson} will be used.
* @param featureId ID of the {@link Feature} to check {@link Authorization} for
*/
isAuthenticated(objectUrl?: string, ePersonUuid?: string, featureId?: FeatureType): Observable<boolean> {
return this.searchByObject(objectUrl, ePersonUuid, featureId).pipe(
isAuthenticated(featureId?: FeatureType, objectUrl?: string, ePersonUuid?: string): Observable<boolean> {
return this.searchByObject(featureId, objectUrl, ePersonUuid).pipe(
map((authorizationRD) => (authorizationRD.statusCode !== 401 && hasValue(authorizationRD.payload) && isNotEmpty(authorizationRD.payload.page)))
);
}
@@ -76,7 +76,7 @@ export class AuthorizationDataService extends DataService<Authorization> {
* @param options {@link FindListOptions} to provide pagination and/or additional arguments
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
*/
searchByObject(objectUrl?: string, ePersonUuid?: string, featureId?: FeatureType, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Authorization>>): Observable<RemoteData<PaginatedList<Authorization>>> {
searchByObject(featureId?: FeatureType, objectUrl?: string, ePersonUuid?: string, options: FindListOptions = {}, ...linksToFollow: Array<FollowLinkConfig<Authorization>>): Observable<RemoteData<PaginatedList<Authorization>>> {
return observableOf(new AuthorizationSearchParams(objectUrl, ePersonUuid, featureId)).pipe(
addSiteObjectUrlIfEmpty(this.siteService),
addAuthenticatedUserUuidIfEmpty(this.authService),

View File

@@ -42,7 +42,7 @@ export const addAuthenticatedUserUuidIfEmpty = (authService: AuthService) =>
map((ePerson) => Object.assign({}, params, { ePersonUuid: ePerson.uuid }))
);
} else {
observableOf(params)
return observableOf(params)
}
})
);

View File

@@ -2,5 +2,6 @@
* Enum object for all possible {@link Feature} types
*/
export enum FeatureType {
LoginOnBehalfOf = 'loginOnBehalfOf'
LoginOnBehalfOf = 'loginOnBehalfOf',
AdministratorOf = 'administratorOf'
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, RouterStateSnapshot, UrlSegment } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthorizationDataService } from './authorization-data.service';
import { FeatureType } from './feature-type';
/**
* Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have administrator
* rights to the {@link Site}
*/
@Injectable({
providedIn: 'root'
})
export class SiteAdministratorGuard implements CanActivate, CanLoad {
constructor(private authorizationService: AuthorizationDataService) {
}
/**
* True when user has administrator rights to the {@link Site}
*/
canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
return this.authorizationService.isAuthenticated(FeatureType.AdministratorOf);
}
/**
* True when user has administrator rights to the {@link Site}
*/
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> {
return this.authorizationService.isAuthenticated(FeatureType.AdministratorOf);
}
}