mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
71429: SiteAdministratorGuard on admin routes + authorization check on visibility of admin sidebar sections
This commit is contained in:
@@ -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))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -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,30 +73,32 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
* Set and calculate all initial values of the instance variables
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.createMenu();
|
||||
super.ngOnInit();
|
||||
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
|
||||
this.authService.isAuthenticated()
|
||||
.subscribe((loggedIn: boolean) => {
|
||||
if (loggedIn) {
|
||||
this.menuService.showMenu(this.menuID);
|
||||
}
|
||||
});
|
||||
this.menuCollapsed.pipe(first())
|
||||
.subscribe((collapsed: boolean) => {
|
||||
this.sidebarOpen = !collapsed;
|
||||
this.sidebarClosed = collapsed;
|
||||
});
|
||||
this.sidebarExpanded = combineLatestObservable(this.menuCollapsed, this.menuPreviewCollapsed)
|
||||
.pipe(
|
||||
map(([collapsed, previewCollapsed]) => (!collapsed || !previewCollapsed))
|
||||
);
|
||||
this.authorizationService.isAuthenticated(FeatureType.AdministratorOf).pipe(take(1)).subscribe((authorized) => {
|
||||
this.createMenu(authorized);
|
||||
super.ngOnInit();
|
||||
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
|
||||
this.authService.isAuthenticated()
|
||||
.subscribe((loggedIn: boolean) => {
|
||||
if (loggedIn) {
|
||||
this.menuService.showMenu(this.menuID);
|
||||
}
|
||||
});
|
||||
this.menuCollapsed.pipe(first())
|
||||
.subscribe((collapsed: boolean) => {
|
||||
this.sidebarOpen = !collapsed;
|
||||
this.sidebarClosed = collapsed;
|
||||
});
|
||||
this.sidebarExpanded = combineLatestObservable(this.menuCollapsed, this.menuPreviewCollapsed)
|
||||
.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',
|
||||
|
@@ -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' },
|
||||
|
@@ -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,
|
||||
|
@@ -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),
|
||||
|
@@ -42,7 +42,7 @@ export const addAuthenticatedUserUuidIfEmpty = (authService: AuthService) =>
|
||||
map((ePerson) => Object.assign({}, params, { ePersonUuid: ePerson.uuid }))
|
||||
);
|
||||
} else {
|
||||
observableOf(params)
|
||||
return observableOf(params)
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@@ -2,5 +2,6 @@
|
||||
* Enum object for all possible {@link Feature} types
|
||||
*/
|
||||
export enum FeatureType {
|
||||
LoginOnBehalfOf = 'loginOnBehalfOf'
|
||||
LoginOnBehalfOf = 'loginOnBehalfOf',
|
||||
AdministratorOf = 'administratorOf'
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user