diff --git a/src/app/access-control/access-control-routes.ts b/src/app/access-control/access-control-routes.ts index a7cce461ef..07b6f6c4ff 100644 --- a/src/app/access-control/access-control-routes.ts +++ b/src/app/access-control/access-control-routes.ts @@ -1,16 +1,13 @@ import { AbstractControl } from '@angular/forms'; -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { DYNAMIC_ERROR_MESSAGES_MATCHER, DynamicErrorMessagesMatcher, } from '@ng-dynamic-forms/core'; import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { GroupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { groupAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { siteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { EPERSON_PATH, GROUP_PATH, @@ -20,7 +17,7 @@ import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.co import { EPersonFormComponent } from './epeople-registry/eperson-form/eperson-form.component'; import { EPersonResolver } from './epeople-registry/eperson-resolver.service'; import { GroupFormComponent } from './group-registry/group-form/group-form.component'; -import { GroupPageGuard } from './group-registry/group-page.guard'; +import { groupPageGuard } from './group-registry/group-page.guard'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; /** @@ -28,7 +25,7 @@ import { GroupsRegistryComponent } from './group-registry/groups-registry.compon */ export const ValidateEmailErrorStateMatcher: DynamicErrorMessagesMatcher = (control: AbstractControl, model: any, hasFocus: boolean) => { - return (control.touched && !hasFocus) || (control.errors?.emailTaken && hasFocus); + return ( control.touched && !hasFocus ) || ( control.errors?.emailTaken && hasFocus ); }; const providers = [ @@ -46,7 +43,7 @@ export const ROUTES: Route[] = [ }, providers, data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }, - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], }, { path: `${EPERSON_PATH}/create`, @@ -56,7 +53,7 @@ export const ROUTES: Route[] = [ }, providers, data: { title: 'admin.access-control.epeople.add.title', breadcrumbKey: 'admin.access-control.epeople.add' }, - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], }, { path: `${EPERSON_PATH}/:id/edit`, @@ -67,7 +64,7 @@ export const ROUTES: Route[] = [ }, providers, data: { title: 'admin.access-control.epeople.edit.title', breadcrumbKey: 'admin.access-control.epeople.edit' }, - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], }, { path: GROUP_PATH, @@ -77,7 +74,7 @@ export const ROUTES: Route[] = [ }, providers, data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }, - canActivate: mapToCanActivate([GroupAdministratorGuard]), + canActivate: [groupAdministratorGuard], }, { path: `${GROUP_PATH}/create`, @@ -90,7 +87,7 @@ export const ROUTES: Route[] = [ title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup', }, - canActivate: mapToCanActivate([GroupAdministratorGuard]), + canActivate: [groupAdministratorGuard], }, { path: `${GROUP_PATH}/:groupId/edit`, @@ -103,7 +100,7 @@ export const ROUTES: Route[] = [ title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup', }, - canActivate: mapToCanActivate([GroupPageGuard]), + canActivate: [groupPageGuard], }, { path: 'bulk-access', @@ -112,6 +109,6 @@ export const ROUTES: Route[] = [ breadcrumb: i18nBreadcrumbResolver, }, data: { title: 'admin.access-control.bulk-access.title', breadcrumbKey: 'admin.access-control.bulk-access' }, - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], }, ]; diff --git a/src/app/access-control/group-registry/group-page.guard.spec.ts b/src/app/access-control/group-registry/group-page.guard.spec.ts index b1648f59ec..3024e42d64 100644 --- a/src/app/access-control/group-registry/group-page.guard.spec.ts +++ b/src/app/access-control/group-registry/group-page.guard.spec.ts @@ -1,14 +1,24 @@ +import { + TestBed, + waitForAsync, +} from '@angular/core/testing'; import { ActivatedRouteSnapshot, Router, + UrlTree, } from '@angular/router'; -import { of as observableOf } from 'rxjs'; +import { + Observable, + of as observableOf, +} from 'rxjs'; 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'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; -import { GroupPageGuard } from './group-page.guard'; +import { groupPageGuard } from './group-page.guard'; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; // Increase timeout to 10 seconds describe('GroupPageGuard', () => { const groupsEndpointUrl = 'https://test.org/api/eperson/groups'; @@ -20,42 +30,54 @@ describe('GroupPageGuard', () => { }, } as unknown as ActivatedRouteSnapshot; - let guard: GroupPageGuard; let halEndpointService: HALEndpointService; let authorizationService: AuthorizationDataService; let router: Router; let authService: AuthService; - beforeEach(() => { + function init() { halEndpointService = jasmine.createSpyObj(['getEndpoint']); - (halEndpointService as any).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl)); + ( halEndpointService as any ).getEndpoint.and.returnValue(observableOf(groupsEndpointUrl)); authorizationService = jasmine.createSpyObj(['isAuthorized']); // NOTE: value is set in beforeEach router = jasmine.createSpyObj(['parseUrl']); - (router as any).parseUrl.and.returnValue = {}; + ( router as any ).parseUrl.and.returnValue = {}; authService = jasmine.createSpyObj(['isAuthenticated']); - (authService as any).isAuthenticated.and.returnValue(observableOf(true)); + ( authService as any ).isAuthenticated.and.returnValue(observableOf(true)); - guard = new GroupPageGuard(halEndpointService, authorizationService, router, authService); - }); + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: HALEndpointService, useValue: halEndpointService }, + ], + }); + } + + beforeEach(waitForAsync(() => { + init(); + })); it('should be created', () => { - expect(guard).toBeTruthy(); + expect(groupPageGuard).toBeTruthy(); }); describe('canActivate', () => { describe('when the current user can manage the group', () => { beforeEach(() => { - (authorizationService as any).isAuthorized.and.returnValue(observableOf(true)); + ( authorizationService as any ).isAuthorized.and.returnValue(observableOf(true)); }); it('should return true', (done) => { - guard.canActivate( - routeSnapshotWithGroupId, { url: 'current-url' } as any, - ).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return groupPageGuard()(routeSnapshotWithGroupId, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { expect(authorizationService.isAuthorized).toHaveBeenCalledWith( FeatureID.CanManageGroup, groupEndpointUrl, undefined, ); @@ -71,15 +93,18 @@ describe('GroupPageGuard', () => { }); it('should not return true', (done) => { - guard.canActivate( - routeSnapshotWithGroupId, { url: 'current-url' } as any, - ).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return groupPageGuard()(routeSnapshotWithGroupId, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { expect(authorizationService.isAuthorized).toHaveBeenCalledWith( FeatureID.CanManageGroup, groupEndpointUrl, undefined, ); expect(result).not.toBeTrue(); done(); }); + }); }); }); diff --git a/src/app/access-control/group-registry/group-page.guard.ts b/src/app/access-control/group-registry/group-page.guard.ts index 928271887c..c52bed9c48 100644 --- a/src/app/access-control/group-registry/group-page.guard.ts +++ b/src/app/access-control/group-registry/group-page.guard.ts @@ -1,7 +1,7 @@ -import { Injectable } from '@angular/core'; +import { inject } from '@angular/core'; import { ActivatedRouteSnapshot, - Router, + CanActivateFn, RouterStateSnapshot, } from '@angular/router'; import { @@ -10,34 +10,29 @@ import { } from 'rxjs'; import { map } from 'rxjs/operators'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { SomeFeatureAuthorizationGuard } from '../../core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard'; +import { + someFeatureAuthorizationGuard, + StringGuardParamFn, +} from '../../core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { HALEndpointService } from '../../core/shared/hal-endpoint.service'; -@Injectable({ - providedIn: 'root', -}) -export class GroupPageGuard extends SomeFeatureAuthorizationGuard { +const defaultGroupPageGetObjectUrl: StringGuardParamFn = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, +): Observable => { + const halEndpointService = inject(HALEndpointService); + const groupsEndpoint = 'groups'; - protected groupsEndpoint = 'groups'; + return halEndpointService.getEndpoint(groupsEndpoint).pipe( + map(groupsUrl => `${groupsUrl}/${route?.params?.groupId}`), + ); +}; - constructor(protected halEndpointService: HALEndpointService, - protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf([FeatureID.CanManageGroup]); - } - - getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.halEndpointService.getEndpoint(this.groupsEndpoint).pipe( - map(groupsUrl => `${groupsUrl}/${route?.params?.groupId}`), - ); - } - -} +export const groupPageGuard = ( + getObjectUrl = defaultGroupPageGetObjectUrl, + getEPersonUuid?: StringGuardParamFn, +): CanActivateFn => someFeatureAuthorizationGuard( + () => observableOf([FeatureID.CanManageGroup]), + getObjectUrl, + getEPersonUuid); diff --git a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts index c193148cc4..0316913cf6 100644 --- a/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts +++ b/src/app/admin/admin-notify-dashboard/admin-notify-dashboard-routes.ts @@ -1,18 +1,15 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; import { notifyInfoGuard } from '../../core/coar-notify/notify-info/notify-info.guard'; -import { SiteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { siteAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { AdminNotifyDashboardComponent } from './admin-notify-dashboard.component'; import { AdminNotifyIncomingComponent } from './admin-notify-logs/admin-notify-incoming/admin-notify-incoming.component'; import { AdminNotifyOutgoingComponent } from './admin-notify-logs/admin-notify-outgoing/admin-notify-outgoing.component'; export const ROUTES: Route[] = [ { - canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + canActivate: [siteAdministratorGuard, notifyInfoGuard], path: '', resolve: { breadcrumb: i18nBreadcrumbResolver, @@ -30,7 +27,7 @@ export const ROUTES: Route[] = [ breadcrumb: i18nBreadcrumbResolver, }, component: AdminNotifyIncomingComponent, - canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + canActivate: [siteAdministratorGuard, notifyInfoGuard], data: { title: 'admin.notify.dashboard.page.title', breadcrumbKey: 'admin.notify.dashboard', @@ -42,7 +39,7 @@ export const ROUTES: Route[] = [ breadcrumb: i18nBreadcrumbResolver, }, component: AdminNotifyOutgoingComponent, - canActivate: [...mapToCanActivate([SiteAdministratorGuard]), notifyInfoGuard], + canActivate: [siteAdministratorGuard, notifyInfoGuard], data: { title: 'admin.notify.dashboard.page.title', breadcrumbKey: 'admin.notify.dashboard', diff --git a/src/app/app-routes.ts b/src/app/app-routes.ts index 8b7f6acd47..29a78364b5 100644 --- a/src/app/app-routes.ts +++ b/src/app/app-routes.ts @@ -1,6 +1,5 @@ import { InMemoryScrollingOptions, - mapToCanActivate, Route, RouterConfigOptions, } from '@angular/router'; @@ -26,12 +25,12 @@ import { COLLECTION_MODULE_PATH } from './collection-page/collection-page-routin import { COMMUNITY_MODULE_PATH } from './community-page/community-page-routing-paths'; import { authBlockingGuard } from './core/auth/auth-blocking.guard'; import { authenticatedGuard } from './core/auth/authenticated.guard'; -import { GroupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; -import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; -import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard'; -import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard'; +import { groupAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/group-administrator.guard'; +import { siteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { siteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard'; +import { endUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard'; import { reloadGuard } from './core/reload/reload.guard'; -import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard'; +import { forgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard'; import { ServerCheckGuard } from './core/server-check/server-check.guard'; import { ThemedForbiddenComponent } from './forbidden/themed-forbidden.component'; import { ITEM_MODULE_PATH } from './item-page/item-page-routing-paths'; @@ -66,105 +65,105 @@ export const APP_ROUTES: Route[] = [ .then((m) => m.ROUTES), data: { showBreadcrumbs: false }, providers: [provideSuggestionNotificationsState()], - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'community-list', loadChildren: () => import('./community-list-page/community-list-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'id', loadChildren: () => import('./lookup-by-id/lookup-by-id-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'handle', loadChildren: () => import('./lookup-by-id/lookup-by-id-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: REGISTER_PATH, loadChildren: () => import('./register-page/register-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([SiteRegisterGuard]), + canActivate: [siteRegisterGuard], }, { path: FORGOT_PASSWORD_PATH, loadChildren: () => import('./forgot-password/forgot-password-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard, ForgotPasswordCheckGuard]), + canActivate: [endUserAgreementCurrentUserGuard, forgotPasswordCheckGuard], }, { path: COMMUNITY_MODULE_PATH, loadChildren: () => import('./community-page/community-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: COLLECTION_MODULE_PATH, loadChildren: () => import('./collection-page/collection-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: ITEM_MODULE_PATH, loadChildren: () => import('./item-page/item-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'entities/:entity-type', loadChildren: () => import('./item-page/item-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: LEGACY_BITSTREAM_MODULE_PATH, loadChildren: () => import('./bitstream-page/bitstream-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: BITSTREAM_MODULE_PATH, loadChildren: () => import('./bitstream-page/bitstream-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'mydspace', loadChildren: () => import('./my-dspace-page/my-dspace-page-routes') .then((m) => m.ROUTES), providers: [provideSuggestionNotificationsState()], - canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])], + canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard], }, { path: 'search', loadChildren: () => import('./search-page/search-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'browse', loadChildren: () => import('./browse-by/browse-by-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: ADMIN_MODULE_PATH, loadChildren: () => import('./admin/admin-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([SiteAdministratorGuard, EndUserAgreementCurrentUserGuard]), + canActivate: [siteAdministratorGuard, endUserAgreementCurrentUserGuard], }, { path: NOTIFICATIONS_MODULE_PATH, loadChildren: () => import('./quality-assurance-notifications-pages/notifications-pages-routes') .then((m) => m.ROUTES), providers: [provideSuggestionNotificationsState()], - canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])], + canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard], }, { path: 'login', @@ -181,47 +180,47 @@ export const APP_ROUTES: Route[] = [ loadChildren: () => import('./submit-page/submit-page-routes') .then((m) => m.ROUTES), providers: [provideSubmissionState()], - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'import-external', loadChildren: () => import('./import-external-page/import-external-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: 'workspaceitems', loadChildren: () => import('./workspaceitems-edit-page/workspaceitems-edit-page-routes') .then((m) => m.ROUTES), providers: [provideSubmissionState()], - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: WORKFLOW_ITEM_MODULE_PATH, providers: [provideSubmissionState()], loadChildren: () => import('./workflowitems-edit-page/workflowitems-edit-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: PROFILE_MODULE_PATH, loadChildren: () => import('./profile-page/profile-page-routes') .then((m) => m.ROUTES), providers: [provideSuggestionNotificationsState()], - canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])], + canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard], }, { path: PROCESS_MODULE_PATH, loadChildren: () => import('./process-page/process-page-routes') .then((m) => m.ROUTES), - canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])], + canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard], }, { path: SUGGESTION_MODULE_PATH, loadChildren: () => import('./suggestions-page/suggestions-page-routes') .then((m) => m.ROUTES), providers: [provideSuggestionNotificationsState()], - canActivate: [authenticatedGuard, ...mapToCanActivate([EndUserAgreementCurrentUserGuard])], + canActivate: [authenticatedGuard, endUserAgreementCurrentUserGuard], }, { path: INFO_MODULE_PATH, @@ -230,7 +229,7 @@ export const APP_ROUTES: Route[] = [ { path: REQUEST_COPY_MODULE_PATH, loadChildren: () => import('./request-copy/request-copy-routes').then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: FORBIDDEN_PATH, @@ -240,7 +239,7 @@ export const APP_ROUTES: Route[] = [ path: 'statistics', loadChildren: () => import('./statistics-page/statistics-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([EndUserAgreementCurrentUserGuard]), + canActivate: [endUserAgreementCurrentUserGuard], }, { path: HEALTH_PAGE_PATH, @@ -250,7 +249,7 @@ export const APP_ROUTES: Route[] = [ { path: ACCESS_CONTROL_MODULE_PATH, loadChildren: () => import('./access-control/access-control-routes').then((m) => m.ROUTES), - canActivate: mapToCanActivate([GroupAdministratorGuard, EndUserAgreementCurrentUserGuard]), + canActivate: [groupAdministratorGuard, endUserAgreementCurrentUserGuard], }, { path: 'subscriptions', diff --git a/src/app/bitstream-page/bitstream-page-authorizations.guard.spec.ts b/src/app/bitstream-page/bitstream-page-authorizations.guard.spec.ts new file mode 100644 index 0000000000..9b4d019058 --- /dev/null +++ b/src/app/bitstream-page/bitstream-page-authorizations.guard.spec.ts @@ -0,0 +1,81 @@ +import { TestBed } from '@angular/core/testing'; +import { + Router, + UrlTree, +} from '@angular/router'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; + +import { BitstreamDataService } from '../core/data/bitstream-data.service'; +import { Bitstream } from '../core/shared/bitstream.model'; +import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils'; +import { bitstreamPageAuthorizationsGuard } from './bitstream-page-authorizations.guard'; + +describe('bitstreamPageAuthorizationsGuard', () => { + let authorizationService: AuthorizationDataService; + let authService: AuthService; + let router: Router; + let route; + let parentRoute; + let bitstreamService: BitstreamDataService; + let bitstream: Bitstream; + let uuid = '1234-abcdef-54321-fedcba'; + let bitstreamSelfLink = 'test.url/1234-abcdef-54321-fedcba'; + + beforeEach(() => { + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + router = jasmine.createSpyObj('router', { + parseUrl: {}, + navigateByUrl: undefined, + }); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + }); + + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0', + }, + }; + route = { + params: {}, + parent: parentRoute, + }; + bitstream = new Bitstream(); + bitstream.uuid = uuid; + bitstream._links = { self: { href: bitstreamSelfLink } } as any; + bitstreamService = jasmine.createSpyObj('bitstreamService', { findById: createSuccessfulRemoteDataObject$(bitstream) }); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: BitstreamDataService, useValue: bitstreamService }, + ], + }); + }); + + it('should call authorizationService.isAuthorized with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return bitstreamPageAuthorizationsGuard(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith( + FeatureID.CanManagePolicies, + bitstreamSelfLink, + undefined, + ); + done(); + }); + + }); +}); diff --git a/src/app/bitstream-page/bitstream-page-authorizations.guard.ts b/src/app/bitstream-page/bitstream-page-authorizations.guard.ts new file mode 100644 index 0000000000..134693977f --- /dev/null +++ b/src/app/bitstream-page/bitstream-page-authorizations.guard.ts @@ -0,0 +1,16 @@ +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +import { dsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../core/data/feature-authorization/feature-id'; +import { bitstreamPageResolver } from './bitstream-page.resolver'; + +/** + * Guard for preventing unauthorized access to certain {@link Bitstream} pages requiring specific authorizations. + * Checks authorization rights for managing policies. + */ +export const bitstreamPageAuthorizationsGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => bitstreamPageResolver, + () => observableOf(FeatureID.CanManagePolicies), + ); diff --git a/src/app/bitstream-page/bitstream-page-routes.ts b/src/app/bitstream-page/bitstream-page-routes.ts index 1a6e3ebc3d..6d5e312db5 100644 --- a/src/app/bitstream-page/bitstream-page-routes.ts +++ b/src/app/bitstream-page/bitstream-page-routes.ts @@ -10,6 +10,7 @@ import { resourcePolicyTargetResolver } from '../shared/resource-policies/resolv import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component'; import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; import { bitstreamPageResolver } from './bitstream-page.resolver'; +import { bitstreamPageAuthorizationsGuard } from './bitstream-page-authorizations.guard'; import { ThemedEditBitstreamPageComponent } from './edit-bitstream-page/themed-edit-bitstream-page.component'; import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard'; @@ -49,6 +50,7 @@ export const ROUTES: Route[] = [ }, { path: EDIT_BITSTREAM_AUTHORIZATIONS_PATH, + canActivate: [bitstreamPageAuthorizationsGuard], children: [ { path: 'create', diff --git a/src/app/collection-page/collection-page-administrator.guard.ts b/src/app/collection-page/collection-page-administrator.guard.ts index 63e969b7c8..30edc72fc6 100644 --- a/src/app/collection-page/collection-page-administrator.guard.ts +++ b/src/app/collection-page/collection-page-administrator.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../core/auth/auth.service'; -import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../core/data/remote-data'; -import { Collection } from '../core/shared/collection.model'; import { collectionPageResolver } from './collection-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Collection} pages requiring administrator rights + * Check administrator authorization rights */ -export class CollectionPageAdministratorGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = collectionPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const collectionPageAdministratorGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => collectionPageResolver, + () => observableOf(FeatureID.AdministratorOf), + ); diff --git a/src/app/collection-page/collection-page-routes.ts b/src/app/collection-page/collection-page-routes.ts index 889b910d6a..f2dadc3fbe 100644 --- a/src/app/collection-page/collection-page-routes.ts +++ b/src/app/collection-page/collection-page-routes.ts @@ -1,7 +1,4 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { browseByGuard } from '../browse-by/browse-by-guard'; import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver'; @@ -15,7 +12,7 @@ import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { collectionPageResolver } from './collection-page.resolver'; -import { CollectionPageAdministratorGuard } from './collection-page-administrator.guard'; +import { collectionPageAdministratorGuard } from './collection-page-administrator.guard'; import { COLLECTION_CREATE_PATH, COLLECTION_EDIT_PATH, @@ -65,7 +62,7 @@ export const ROUTES: Route[] = [ path: COLLECTION_EDIT_PATH, loadChildren: () => import('./edit-collection-page/edit-collection-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([CollectionPageAdministratorGuard]), + canActivate: [collectionPageAdministratorGuard], }, { path: 'delete', diff --git a/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts b/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts index cf550c3223..19dbaa616b 100644 --- a/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts +++ b/src/app/collection-page/edit-collection-page/edit-collection-page-routes.ts @@ -1,10 +1,7 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { CollectionAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard'; +import { collectionAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard'; import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { resourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; @@ -30,7 +27,7 @@ export const ROUTES: Route[] = [ }, data: { breadcrumbKey: 'collection.edit' }, component: EditCollectionPageComponent, - canActivate: mapToCanActivate([CollectionAdministratorGuard]), + canActivate: [collectionAdministratorGuard], children: [ { path: '', diff --git a/src/app/community-page/community-page-administrator.guard.ts b/src/app/community-page/community-page-administrator.guard.ts index 23d429b484..ecbc9b86c0 100644 --- a/src/app/community-page/community-page-administrator.guard.ts +++ b/src/app/community-page/community-page-administrator.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../core/auth/auth.service'; -import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../core/data/remote-data'; -import { Community } from '../core/shared/community.model'; import { communityPageResolver } from './community-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Community} pages requiring administrator rights + * Check administrator authorization rights */ -export class CommunityPageAdministratorGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = communityPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const communityPageAdministratorGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => communityPageResolver, + () => observableOf(FeatureID.AdministratorOf), + ); diff --git a/src/app/community-page/community-page-routes.ts b/src/app/community-page/community-page-routes.ts index 656b96c311..d9505c53b1 100644 --- a/src/app/community-page/community-page-routes.ts +++ b/src/app/community-page/community-page-routes.ts @@ -1,7 +1,4 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { browseByGuard } from '../browse-by/browse-by-guard'; import { browseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver'; @@ -14,7 +11,7 @@ import { dsoEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { MenuItemType } from '../shared/menu/menu-item-type.model'; import { communityPageResolver } from './community-page.resolver'; -import { CommunityPageAdministratorGuard } from './community-page-administrator.guard'; +import { communityPageAdministratorGuard } from './community-page-administrator.guard'; import { COMMUNITY_CREATE_PATH, COMMUNITY_EDIT_PATH, @@ -62,7 +59,7 @@ export const ROUTES: Route[] = [ path: COMMUNITY_EDIT_PATH, loadChildren: () => import('./edit-community-page/edit-community-page-routes') .then((m) => m.ROUTES), - canActivate: mapToCanActivate([CommunityPageAdministratorGuard]), + canActivate: [communityPageAdministratorGuard], }, { path: 'delete', diff --git a/src/app/community-page/edit-community-page/edit-community-page-routes.ts b/src/app/community-page/edit-community-page/edit-community-page-routes.ts index a15312a216..2402c2037d 100644 --- a/src/app/community-page/edit-community-page/edit-community-page-routes.ts +++ b/src/app/community-page/edit-community-page/edit-community-page-routes.ts @@ -1,10 +1,7 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { CommunityAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/community-administrator.guard'; +import { communityAdministratorGuard } from '../../core/data/feature-authorization/feature-authorization-guard/community-administrator.guard'; import { ResourcePolicyCreateComponent } from '../../shared/resource-policies/create/resource-policy-create.component'; import { ResourcePolicyEditComponent } from '../../shared/resource-policies/edit/resource-policy-edit.component'; import { resourcePolicyResolver } from '../../shared/resource-policies/resolvers/resource-policy.resolver'; @@ -28,7 +25,7 @@ export const ROUTES: Route[] = [ }, data: { breadcrumbKey: 'community.edit' }, component: EditCommunityPageComponent, - canActivate: mapToCanActivate([CommunityAdministratorGuard]), + canActivate: [communityAdministratorGuard], children: [ { path: '', diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts index 5af9f18b33..1b1b4a9d6c 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/collection-administrator.guard.ts @@ -1,35 +1,13 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user * isn't a Collection administrator + * Check group management rights */ -@Injectable({ - providedIn: 'root', -}) -export class CollectionAdministratorGuard extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check group management rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.IsCollectionAdmin); - } -} +export const collectionAdministratorGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.IsCollectionAdmin)); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts index 2092fce110..6d7dac314e 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/community-administrator.guard.ts @@ -1,35 +1,13 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user * isn't a Community administrator + * Check group management rights */ -@Injectable({ - providedIn: 'root', -}) -export class CommunityAdministratorGuard extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check group management rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.IsCommunityAdmin); - } -} +export const communityAdministratorGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.IsCommunityAdmin)); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.spec.ts index 2cd9fefa93..18292bb943 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.spec.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.spec.ts @@ -1,8 +1,8 @@ +import { TestBed } from '@angular/core/testing'; import { - ActivatedRouteSnapshot, ResolveFn, Router, - RouterStateSnapshot, + UrlTree, } from '@angular/router'; import { Observable, @@ -12,52 +12,39 @@ import { import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; import { AuthService } from '../../../auth/auth.service'; import { DSpaceObject } from '../../../shared/dspace-object.model'; -import { Item } from '../../../shared/item.model'; import { RemoteData } from '../../remote-data'; import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { DsoPageSingleFeatureGuard } from './dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from './dso-page-single-feature.guard'; +import { + defaultDSOGetObjectUrl, + getRouteWithDSOId, +} from './dso-page-some-feature.guard'; -const object = { - self: 'test-selflink', -} as DSpaceObject; - -const testResolver: ResolveFn> = () => createSuccessfulRemoteDataObject$(object); - -/** - * Test implementation of abstract class DsoPageSingleFeatureGuard - */ -class DsoPageSingleFeatureGuardImpl extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = testResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService, - protected featureID: FeatureID) { - super(authorizationService, router, authService); - } - - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.featureID); - } -} describe('DsoPageSingleFeatureGuard', () => { - let guard: DsoPageSingleFeatureGuard; let authorizationService: AuthorizationDataService; let router: Router; let authService: AuthService; + let resolver: ResolveFn>; + let object: DSpaceObject; let route; let parentRoute; + let featureId: FeatureID; + function init() { + object = { + self: 'test-selflink', + } as DSpaceObject; + authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: observableOf(true), }); router = jasmine.createSpyObj('router', { parseUrl: {}, }); + resolver = () => createSuccessfulRemoteDataObject$(object); authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true), }); @@ -71,16 +58,25 @@ describe('DsoPageSingleFeatureGuard', () => { }, parent: parentRoute, }; - guard = new DsoPageSingleFeatureGuardImpl(authorizationService, router, authService, undefined); + + featureId = FeatureID.LoginOnBehalfOf; + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + ], + }); } beforeEach(() => { init(); }); - describe('getObjectUrl', () => { + describe('defaultDSOGetObjectUrl', () => { it('should return the resolved object\'s selflink', (done) => { - guard.getObjectUrl(route, undefined).subscribe((selflink) => { + defaultDSOGetObjectUrl(resolver)(route, undefined).subscribe((selflink) => { expect(selflink).toEqual(object.self); done(); }); @@ -89,8 +85,23 @@ describe('DsoPageSingleFeatureGuard', () => { describe('getRouteWithDSOId', () => { it('should return the route that has the UUID of the DSO', () => { - const foundRoute = (guard as any).getRouteWithDSOId(route); + const foundRoute = getRouteWithDSOId(route); expect(foundRoute).toBe(parentRoute); }); }); + + describe('dsoPageSingleFeatureGuard', () => { + it('should call authorizationService.isAuthenticated with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return dsoPageSingleFeatureGuard( + () => resolver, () => observableOf(featureId), + )(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe(() => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, object.self, undefined); + done(); + }); + }); + }); }); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.ts index 1f75df846b..5073a38653 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard.ts @@ -1,31 +1,27 @@ import { ActivatedRouteSnapshot, + CanActivateFn, + ResolveFn, RouterStateSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { DSpaceObject } from '../../../shared/dspace-object.model'; +import { RemoteData } from '../../remote-data'; import { FeatureID } from '../feature-id'; -import { DsoPageSomeFeatureGuard } from './dso-page-some-feature.guard'; +import { dsoPageSomeFeatureGuard } from './dso-page-some-feature.guard'; +import { SingleFeatureGuardParamFn } from './single-feature-authorization.guard'; /** * Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature * This guard utilizes a resolver to retrieve the relevant object to check authorizations for */ -export abstract class DsoPageSingleFeatureGuard extends DsoPageSomeFeatureGuard { - /** - * The features to check authorization for - */ - getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.getFeatureID(route, state).pipe( - map((featureID) => [featureID]), - ); - } - - /** - * The type of feature to check authorization for - * Override this method to define a feature - */ - abstract getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable; -} +export const dsoPageSingleFeatureGuard = ( + getResolveFn: () => ResolveFn>, + getFeatureID: SingleFeatureGuardParamFn, +): CanActivateFn => dsoPageSomeFeatureGuard( + getResolveFn, + (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => getFeatureID(route, state).pipe( + map((featureID: FeatureID) => [featureID]), + )); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.spec.ts index 4e741b5b71..08f1c96b29 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.spec.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.spec.ts @@ -1,8 +1,8 @@ +import { TestBed } from '@angular/core/testing'; import { - ActivatedRouteSnapshot, ResolveFn, Router, - RouterStateSnapshot, + UrlTree, } from '@angular/router'; import { Observable, @@ -12,53 +12,39 @@ import { import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils'; import { AuthService } from '../../../auth/auth.service'; import { DSpaceObject } from '../../../shared/dspace-object.model'; -import { Item } from '../../../shared/item.model'; import { RemoteData } from '../../remote-data'; import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { DsoPageSomeFeatureGuard } from './dso-page-some-feature.guard'; +import { + defaultDSOGetObjectUrl, + dsoPageSomeFeatureGuard, + getRouteWithDSOId, +} from './dso-page-some-feature.guard'; -const object = { - self: 'test-selflink', -} as DSpaceObject; -const testResolver: ResolveFn> = () => createSuccessfulRemoteDataObject$(object); - -/** - * Test implementation of abstract class DsoPageSomeFeatureGuard - */ -class DsoPageSomeFeatureGuardImpl extends DsoPageSomeFeatureGuard { - - protected resolver: ResolveFn> = testResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService, - protected featureIDs: FeatureID[]) { - super(authorizationService, router, authService); - } - - getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.featureIDs); - } -} - -describe('DsoPageSomeFeatureGuard', () => { - let guard: DsoPageSomeFeatureGuard; +describe('dsoPageSomeFeatureGuard and its functions', () => { let authorizationService: AuthorizationDataService; let router: Router; let authService: AuthService; - + let resolver: ResolveFn>; + let object: DSpaceObject; let route; let parentRoute; + let featureIds: FeatureID[]; + function init() { + object = { + self: 'test-selflink', + } as DSpaceObject; + featureIds = [FeatureID.LoginOnBehalfOf, FeatureID.CanDelete]; authorizationService = jasmine.createSpyObj('authorizationService', { isAuthorized: observableOf(true), }); router = jasmine.createSpyObj('router', { parseUrl: {}, }); + resolver = () => createSuccessfulRemoteDataObject$(object); authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true), }); @@ -72,16 +58,25 @@ describe('DsoPageSomeFeatureGuard', () => { }, parent: parentRoute, }; - guard = new DsoPageSomeFeatureGuardImpl(authorizationService, router, authService, []); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + ], + }); + } beforeEach(() => { init(); }); - describe('getObjectUrl', () => { + + describe('defaultDSOGetObjectUrl', () => { it('should return the resolved object\'s selflink', (done) => { - guard.getObjectUrl(route, undefined).subscribe((selflink) => { + defaultDSOGetObjectUrl(resolver)(route, undefined).subscribe((selflink) => { expect(selflink).toEqual(object.self); done(); }); @@ -90,8 +85,26 @@ describe('DsoPageSomeFeatureGuard', () => { describe('getRouteWithDSOId', () => { it('should return the route that has the UUID of the DSO', () => { - const foundRoute = (guard as any).getRouteWithDSOId(route); + const foundRoute = getRouteWithDSOId(route); expect(foundRoute).toBe(parentRoute); }); }); + + + describe('dsoPageSomeFeatureGuard', () => { + it('should call authorizationService.isAuthenticated with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return dsoPageSomeFeatureGuard( + () => resolver, () => observableOf(featureIds), + )(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe(() => { + featureIds.forEach((featureId: FeatureID) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, object.self, undefined); + }); + done(); + }); + }); + }); }); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.ts index c887b8ae2a..7469f113b4 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard.ts @@ -1,7 +1,7 @@ import { ActivatedRouteSnapshot, + CanActivateFn, ResolveFn, - Router, RouterStateSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; @@ -11,47 +11,50 @@ import { hasNoValue, hasValue, } from '../../../../shared/empty.util'; -import { AuthService } from '../../../auth/auth.service'; import { DSpaceObject } from '../../../shared/dspace-object.model'; import { getAllSucceededRemoteDataPayload } from '../../../shared/operators'; import { RemoteData } from '../../remote-data'; -import { AuthorizationDataService } from '../authorization-data.service'; -import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard'; +import { FeatureID } from '../feature-id'; +import { + someFeatureAuthorizationGuard, + SomeFeatureGuardParamFn, + StringGuardParamFn, +} from './some-feature-authorization.guard'; + +export declare type DSOGetObjectURlFn = (resolve: ResolveFn>) => StringGuardParamFn; + /** - * Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for any specific feature in a list - * This guard utilizes a resolver to retrieve the relevant object to check authorizations for + * Method to resolve resolve (parent) route that contains the UUID of the DSO + * @param route The current route */ -export abstract class DsoPageSomeFeatureGuard extends SomeFeatureAuthorizationGuard { - - protected abstract resolver: ResolveFn>; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); +export const getRouteWithDSOId = (route: ActivatedRouteSnapshot): ActivatedRouteSnapshot => { + let routeWithDSOId = route; + while (hasNoValue(routeWithDSOId.params.id) && hasValue(routeWithDSOId.parent)) { + routeWithDSOId = routeWithDSOId.parent; } + return routeWithDSOId; +}; - /** - * Check authorization rights for the object resolved using the provided resolver - */ - getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - const routeWithObjectID = this.getRouteWithDSOId(route); - return (this.resolver(routeWithObjectID, state) as Observable>).pipe( + + +export const defaultDSOGetObjectUrl: DSOGetObjectURlFn = (resolve: ResolveFn>): StringGuardParamFn => { + return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + const routeWithObjectID = getRouteWithDSOId(route); + return (resolve(routeWithObjectID, state) as Observable>).pipe( getAllSucceededRemoteDataPayload(), map((dso) => dso.self), ); - } + }; +}; - /** - * Method to resolve (parent) route that contains the UUID of the DSO - * @param route The current route - */ - protected getRouteWithDSOId(route: ActivatedRouteSnapshot): ActivatedRouteSnapshot { - let routeWithDSOId = route; - while (hasNoValue(routeWithDSOId.params.id) && hasValue(routeWithDSOId.parent)) { - routeWithDSOId = routeWithDSOId.parent; - } - return routeWithDSOId; - } -} +/** + * Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for any specific feature in a list + * This guard utilizes a resolver to retrieve the relevant object to check authorizations for + */ +export const dsoPageSomeFeatureGuard = ( + getResolveFn: () => ResolveFn>, + getFeatureIDs: SomeFeatureGuardParamFn, + getObjectUrl: DSOGetObjectURlFn = defaultDSOGetObjectUrl, + getEPersonUuid?: StringGuardParamFn, +): CanActivateFn => someFeatureAuthorizationGuard((route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => getFeatureIDs(route, state), getObjectUrl(getResolveFn()), getEPersonUuid); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts index 5f32e26851..9641d0aace 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/group-administrator.guard.ts @@ -1,35 +1,12 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group * management rights */ -@Injectable({ - providedIn: 'root', -}) -export class GroupAdministratorGuard extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check group management rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanManageGroups); - } -} +export const groupAdministratorGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.CanManageGroups)); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.spec.ts index e789f8c473..7c15fa4cdf 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.spec.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.spec.ts @@ -1,7 +1,10 @@ import { - ActivatedRouteSnapshot, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { Router, - RouterStateSnapshot, + UrlTree, } from '@angular/router'; import { Observable, @@ -11,37 +14,9 @@ import { import { AuthService } from '../../../auth/auth.service'; import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; -/** - * Test implementation of abstract class SingleFeatureAuthorizationGuard - * Provide the return values of the overwritten getters as constructor arguments - */ -class SingleFeatureAuthorizationGuardImpl extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService, - protected featureId: FeatureID, - protected objectUrl: string, - protected ePersonUuid: string) { - super(authorizationService, router, authService); - } - - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.featureId); - } - - getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.objectUrl); - } - - getEPersonUuid(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.ePersonUuid); - } -} - -describe('SingleFeatureAuthorizationGuard', () => { - let guard: SingleFeatureAuthorizationGuard; +describe('singleFeatureAuthorizationGuard', () => { let authorizationService: AuthorizationDataService; let router: Router; let authService: AuthService; @@ -64,17 +39,36 @@ describe('SingleFeatureAuthorizationGuard', () => { authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true), }); - guard = new SingleFeatureAuthorizationGuardImpl(authorizationService, router, authService, featureId, objectUrl, ePersonUuid); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + ], + }); } - beforeEach(() => { + beforeEach(waitForAsync(() => { init(); - }); + })); describe('canActivate', () => { - it('should call authorizationService.isAuthenticated with the appropriate arguments', () => { - guard.canActivate(undefined, { url: 'current-url' } as any).subscribe(); - expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, objectUrl, ePersonUuid); + it('should call authorizationService.isAuthenticated with the appropriate arguments', (done: DoneFn) => { + const result$ = TestBed.runInInjectionContext(() => { + return singleFeatureAuthorizationGuard( + () => observableOf(featureId), + () => observableOf(objectUrl), + () => observableOf(ePersonUuid), + )(undefined, { url: 'current-url' } as any); + }) as Observable; + + + result$.subscribe(() => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith(featureId, objectUrl, ePersonUuid); + done(); + }); }); + }); }); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.ts index cd9f615aa7..995dcb6f5c 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard.ts @@ -1,31 +1,35 @@ import { ActivatedRouteSnapshot, + CanActivateFn, RouterStateSnapshot, } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { FeatureID } from '../feature-id'; -import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard'; +import { + someFeatureAuthorizationGuard, + StringGuardParamFn, +} from './some-feature-authorization.guard'; + +export declare type SingleFeatureGuardParamFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable; /** - * Abstract Guard for preventing unauthorized activating and loading of routes when a user - * doesn't have authorized rights on a specific feature and/or object. - * Override the desired getters in the parent class for checking specific authorization on a feature and/or object. + * Guard for preventing unauthorized activating and loading of routes when a user doesn't have + * authorized rights on a specific feature and/or object. + * + * @param getFeatureID The feature to check authorization for + * @param getObjectUrl The URL of the object to check if the user has authorized rights for, + * Optional, if not provided, the {@link Site}'s URL will be assumed + * @param getEPersonUuid The UUID of the user to check authorization rights for. + * Optional, if not provided, the authenticated user's UUID will be assumed. */ -export abstract class SingleFeatureAuthorizationGuard extends SomeFeatureAuthorizationGuard { - /** - * The features to check authorization for - */ - getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.getFeatureID(route, state).pipe( - map((featureID) => [featureID]), - ); - } - /** - * The type of feature to check authorization for - * Override this method to define a feature - */ - abstract getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable; -} +export const singleFeatureAuthorizationGuard = ( + getFeatureID: SingleFeatureGuardParamFn, + getObjectUrl?: StringGuardParamFn, + getEPersonUuid?: StringGuardParamFn, +): CanActivateFn => someFeatureAuthorizationGuard( + (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => getFeatureID(route, state).pipe( + map((featureID: FeatureID) => [featureID]), + ), getObjectUrl, getEPersonUuid); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/site-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/site-administrator.guard.ts index e4f0705def..4caa1f806d 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/site-administrator.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/site-administrator.guard.ts @@ -1,33 +1,12 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * 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 extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const siteAdministratorGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.AdministratorOf)); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/site-register.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/site-register.guard.ts index a1a78dc67d..ee08532d38 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/site-register.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/site-register.guard.ts @@ -1,33 +1,12 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have registration * rights to the {@link Site} */ -@Injectable({ providedIn: 'root' }) -export class SiteRegisterGuard extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check registration authorization rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.EPersonRegistration); - } -} +export const siteRegisterGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.EPersonRegistration)); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.spec.ts index 53d77cadad..79e023bdd0 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.spec.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.spec.ts @@ -1,7 +1,10 @@ import { - ActivatedRouteSnapshot, + TestBed, + waitForAsync, +} from '@angular/core/testing'; +import { Router, - RouterStateSnapshot, + UrlTree, } from '@angular/router'; import { Observable, @@ -11,37 +14,9 @@ import { import { AuthService } from '../../../auth/auth.service'; import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard'; - -/** - * Test implementation of abstract class SomeFeatureAuthorizationGuard - * Provide the return values of the overwritten getters as constructor arguments - */ -class SomeFeatureAuthorizationGuardImpl extends SomeFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService, - protected featureIds: FeatureID[], - protected objectUrl: string, - protected ePersonUuid: string) { - super(authorizationService, router, authService); - } - - getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.featureIds); - } - - getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.objectUrl); - } - - getEPersonUuid(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(this.ePersonUuid); - } -} +import { someFeatureAuthorizationGuard } from './some-feature-authorization.guard'; describe('SomeFeatureAuthorizationGuard', () => { - let guard: SomeFeatureAuthorizationGuard; let authorizationService: AuthorizationDataService; let router: Router; let authService: AuthService; @@ -62,18 +37,27 @@ describe('SomeFeatureAuthorizationGuard', () => { return observableOf(authorizedFeatureIds.indexOf(featureId) > -1); }, }); + router = jasmine.createSpyObj('router', { parseUrl: {}, }); + authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true), }); - guard = new SomeFeatureAuthorizationGuardImpl(authorizationService, router, authService, featureIds, objectUrl, ePersonUuid); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + ], + }); } - beforeEach(() => { + beforeEach(waitForAsync(() => { init(); - }); + })); describe('canActivate', () => { describe('when the user isn\'t authorized', () => { @@ -82,7 +66,16 @@ describe('SomeFeatureAuthorizationGuard', () => { }); it('should not return true', (done) => { - guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => { + + const result$ = TestBed.runInInjectionContext(() => { + return someFeatureAuthorizationGuard( + () => observableOf(featureIds), + () => observableOf(objectUrl), + () => observableOf(ePersonUuid), + )(undefined, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { expect(result).not.toEqual(true); done(); }); @@ -95,7 +88,16 @@ describe('SomeFeatureAuthorizationGuard', () => { }); it('should return true', (done) => { - guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => { + + const result$ = TestBed.runInInjectionContext(() => { + return someFeatureAuthorizationGuard( + () => observableOf(featureIds), + () => observableOf(objectUrl), + () => observableOf(ePersonUuid), + )(undefined, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -108,7 +110,16 @@ describe('SomeFeatureAuthorizationGuard', () => { }); it('should return true', (done) => { - guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => { + + const result$ = TestBed.runInInjectionContext(() => { + return someFeatureAuthorizationGuard( + () => observableOf(featureIds), + () => observableOf(objectUrl), + () => observableOf(ePersonUuid), + )(undefined, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(true); done(); }); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.ts index 229321452f..53e5e582eb 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/some-feature-authorization.guard.ts @@ -1,5 +1,7 @@ +import { inject } from '@angular/core'; import { ActivatedRouteSnapshot, + CanActivateFn, Router, RouterStateSnapshot, UrlTree, @@ -16,49 +18,39 @@ import { returnForbiddenUrlTreeOrLoginOnAllFalse } from '../../../shared/authori import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; +export declare type SomeFeatureGuardParamFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable; +export declare type StringGuardParamFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => Observable; +export const defaultStringGuardParamFn = () => observableOf(undefined); + /** - * Abstract Guard for preventing unauthorized activating and loading of routes when a user - * doesn't have authorized rights on any of the specified features and/or object. - * Override the desired getters in the parent class for checking specific authorization on a list of features and/or object. + * Guard for preventing unauthorized activating and loading of routes when a user doesn't have + * authorized rights on any of the specified features and/or object. + + * @param getFeatureIDs The features to check authorization for + * @param getObjectUrl The URL of the object to check if the user has authorized rights for, + * Optional, if not provided, the {@link Site}'s URL will be assumed + * @param getEPersonUuid The UUID of the user to check authorization rights for. + * Optional, if not provided, the authenticated user's UUID will be assumed. */ -export abstract class SomeFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - } - - /** - * True when user has authorization rights for the feature and object provided - * Redirect the user to the unauthorized page when they are not authorized for the given feature - */ - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableCombineLatest(this.getFeatureIDs(route, state), this.getObjectUrl(route, state), this.getEPersonUuid(route, state)).pipe( - switchMap(([featureIDs, objectUrl, ePersonUuid]) => - observableCombineLatest(...featureIDs.map((featureID) => this.authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid))), +export const someFeatureAuthorizationGuard = ( + getFeatureIDs: SomeFeatureGuardParamFn, + getObjectUrl: StringGuardParamFn = defaultStringGuardParamFn, + getEPersonUuid: StringGuardParamFn = defaultStringGuardParamFn, +): CanActivateFn => { + return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + const authorizationService = inject(AuthorizationDataService); + const router = inject(Router); + const authService = inject(AuthService); + return observableCombineLatest([ + getFeatureIDs(route, state), + getObjectUrl(route, state), + getEPersonUuid(route, state), + ]).pipe( + switchMap(([featureIDs, objectUrl, ePersonUuid]: [FeatureID[], string, string]) => + observableCombineLatest(featureIDs.map((featureID) => authorizationService.isAuthorized(featureID, objectUrl, ePersonUuid))), ), - returnForbiddenUrlTreeOrLoginOnAllFalse(this.router, this.authService, state.url), + returnForbiddenUrlTreeOrLoginOnAllFalse(router, authService, state.url), ); - } + }; +}; - /** - * The features to check authorization for - * Override this method to define a list of features - */ - abstract getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable; - - /** - * The URL of the object to check if the user has authorized rights for - * Override this method to define an object URL. If not provided, the {@link Site}'s URL will be used - */ - getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(undefined); - } - - /** - * The UUID of the user to check authorization rights for - * Override this method to define an {@link EPerson} UUID. If not provided, the authenticated user's UUID will be used. - */ - getEPersonUuid(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(undefined); - } -} diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts index b301d550a1..21cafeaba3 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard.ts @@ -1,35 +1,12 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../../auth/auth.service'; -import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; -import { SingleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from './single-feature-authorization.guard'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have group * management rights */ -@Injectable({ - providedIn: 'root', -}) -export class StatisticsAdministratorGuard extends SingleFeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService, protected router: Router, protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check group management rights - */ - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanViewUsageStatistics); - } -} +export const statisticsAdministratorGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.CanViewUsageStatistics)); diff --git a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts b/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts deleted file mode 100644 index 2937011a38..0000000000 --- a/src/app/core/end-user-agreement/abstract-end-user-agreement.guard.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, - UrlTree, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; - -import { environment } from '../../../environments/environment'; -import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/authorized.operators'; - -/** - * An abstract guard for redirecting users to the user agreement page if a certain condition is met - * That condition is defined by abstract method hasAccepted - */ -export abstract class AbstractEndUserAgreementGuard { - - constructor(protected router: Router) { - } - - /** - * True when the user agreement has been accepted - * The user will be redirected to the End User Agreement page if they haven't accepted it before - * A redirect URL will be provided with the navigation so the component can redirect the user back to the blocked route - * when they're finished accepting the agreement - */ - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - if (!environment.info.enableEndUserAgreement) { - return observableOf(true); - } - return this.hasAccepted().pipe( - returnEndUserAgreementUrlTreeOnFalse(this.router, state.url), - ); - } - - /** - * This abstract method determines how the User Agreement has to be accepted before the user is allowed to visit - * the desired route - */ - abstract hasAccepted(): Observable; - -} diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts index a801fb7a6f..640859c7a4 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.spec.ts @@ -1,13 +1,14 @@ +import { TestBed } from '@angular/core/testing'; import { Router, UrlTree, } from '@angular/router'; +import { Observable } from 'rxjs'; import { EndUserAgreementService } from './end-user-agreement.service'; -import { EndUserAgreementCookieGuard } from './end-user-agreement-cookie.guard'; +import { endUserAgreementCookieGuard } from './end-user-agreement-cookie.guard'; -describe('EndUserAgreementCookieGuard', () => { - let guard: EndUserAgreementCookieGuard; +describe('endUserAgreementCookieGuard', () => { let endUserAgreementService: EndUserAgreementService; let router: Router; @@ -21,14 +22,22 @@ describe('EndUserAgreementCookieGuard', () => { parseUrl: new UrlTree(), createUrlTree: new UrlTree(), }); - - guard = new EndUserAgreementCookieGuard(endUserAgreementService, router); + TestBed.configureTestingModule({ + providers: [ + { provide: Router, useValue: router }, + { provide: EndUserAgreementService, useValue: endUserAgreementService }, + ], + }); }); describe('canActivate', () => { describe('when the cookie has been accepted', () => { it('should return true', (done) => { - guard.canActivate(undefined, { url: Object.assign({ url: 'redirect' }) } as any).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCookieGuard(undefined, { url: Object.assign({ url: 'redirect' }) } as any); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -41,7 +50,11 @@ describe('EndUserAgreementCookieGuard', () => { }); it('should return a UrlTree', (done) => { - guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCookieGuard(undefined, { url: Object.assign({ url: 'redirect' }) } as any); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(jasmine.any(UrlTree)); done(); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts index 3eccae3ca9..2da38fb22d 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-cookie.guard.ts @@ -1,29 +1,19 @@ -import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { inject } from '@angular/core'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard'; +import { endUserAgreementGuard } from './end-user-agreement.guard'; import { EndUserAgreementService } from './end-user-agreement.service'; + /** - * A guard redirecting users to the end agreement page when the user agreement cookie hasn't been accepted + * Guard for preventing unauthorized access to certain pages + * requiring the end user agreement to have been accepted in a cookie */ -@Injectable({ providedIn: 'root' }) -export class EndUserAgreementCookieGuard extends AbstractEndUserAgreementGuard { - - constructor(protected endUserAgreementService: EndUserAgreementService, - protected router: Router) { - super(router); - } - - /** - * True when the user agreement cookie has been accepted - */ - hasAccepted(): Observable { - return observableOf(this.endUserAgreementService.isCookieAccepted()); - } - -} +export const endUserAgreementCookieGuard: CanActivateFn = + endUserAgreementGuard( + () => { + const endUserAgreementService = inject(EndUserAgreementService); + return observableOf(endUserAgreementService.isCookieAccepted()); + }, + ); diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts index 88722bd799..ae1f63884f 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.spec.ts @@ -1,16 +1,18 @@ +import { TestBed } from '@angular/core/testing'; import { Router, UrlTree, } from '@angular/router'; -import { of as observableOf } from 'rxjs'; +import { + Observable, + of as observableOf, +} from 'rxjs'; import { environment } from '../../../environments/environment.test'; import { EndUserAgreementService } from './end-user-agreement.service'; -import { EndUserAgreementCurrentUserGuard } from './end-user-agreement-current-user.guard'; - -describe('EndUserAgreementGuard', () => { - let guard: EndUserAgreementCurrentUserGuard; +import { endUserAgreementCurrentUserGuard } from './end-user-agreement-current-user.guard'; +describe('endUserAgreementGuard', () => { let endUserAgreementService: EndUserAgreementService; let router: Router; @@ -18,19 +20,30 @@ describe('EndUserAgreementGuard', () => { endUserAgreementService = jasmine.createSpyObj('endUserAgreementService', { hasCurrentUserAcceptedAgreement: observableOf(true), }); + router = jasmine.createSpyObj('router', { navigateByUrl: {}, parseUrl: new UrlTree(), createUrlTree: new UrlTree(), }); - guard = new EndUserAgreementCurrentUserGuard(endUserAgreementService, router); + TestBed.configureTestingModule({ + providers: [ + { provide: Router, useValue: router }, + { provide: EndUserAgreementService, useValue: endUserAgreementService }, + ], + }); + }); describe('canActivate', () => { describe('when the user has accepted the agreement', () => { it('should return true', (done) => { - guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCurrentUserGuard(undefined, Object.assign({ url: 'redirect' })); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -43,7 +56,11 @@ describe('EndUserAgreementGuard', () => { }); it('should return a UrlTree', (done) => { - guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCurrentUserGuard(undefined, Object.assign({ url: 'redirect' })); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(jasmine.any(UrlTree)); done(); }); @@ -53,7 +70,12 @@ describe('EndUserAgreementGuard', () => { describe('when the end user agreement is disabled', () => { it('should return true', (done) => { environment.info.enableEndUserAgreement = false; - guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCurrentUserGuard(undefined, Object.assign({ url: 'redirect' })); + }) as Observable; + + result$.subscribe((result) => { expect(result).toEqual(true); done(); }); @@ -61,7 +83,11 @@ describe('EndUserAgreementGuard', () => { it('should not resolve to the end user agreement page', (done) => { environment.info.enableEndUserAgreement = false; - guard.canActivate(undefined, Object.assign({ url: 'redirect' })).subscribe((result) => { + const result$ = TestBed.runInInjectionContext(() => { + return endUserAgreementCurrentUserGuard(undefined, Object.assign({ url: 'redirect' })); + }) as Observable; + + result$.subscribe((result) => { expect(router.navigateByUrl).not.toHaveBeenCalled(); done(); }); diff --git a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts index 566f049450..7c190a08b3 100644 --- a/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts +++ b/src/app/core/end-user-agreement/end-user-agreement-current-user.guard.ts @@ -1,34 +1,25 @@ -import { Injectable } from '@angular/core'; -import { Router } from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { inject } from '@angular/core'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; import { environment } from '../../../environments/environment'; -import { AbstractEndUserAgreementGuard } from './abstract-end-user-agreement.guard'; +import { endUserAgreementGuard } from './end-user-agreement.guard'; import { EndUserAgreementService } from './end-user-agreement.service'; + /** - * A guard redirecting logged in users to the end agreement page when they haven't accepted the latest user agreement + * Guard for preventing unauthorized access to certain pages + * requiring the end user agreement to have been accepted by the current user + */ -@Injectable({ providedIn: 'root' }) -export class EndUserAgreementCurrentUserGuard extends AbstractEndUserAgreementGuard { +export const endUserAgreementCurrentUserGuard: CanActivateFn = + endUserAgreementGuard( + () => { + const endUserAgreementService = inject(EndUserAgreementService); + if (!environment.info.enableEndUserAgreement) { + return observableOf(true); + } - constructor(protected endUserAgreementService: EndUserAgreementService, - protected router: Router) { - super(router); - } - - /** - * True when the currently logged in user has accepted the agreements or when the user is not currently authenticated - */ - hasAccepted(): Observable { - if (!environment.info.enableEndUserAgreement) { - return observableOf(true); - } - - return this.endUserAgreementService.hasCurrentUserAcceptedAgreement(true); - } - -} + return endUserAgreementService.hasCurrentUserAcceptedAgreement(true); + }, + ); diff --git a/src/app/core/end-user-agreement/end-user-agreement.guard.ts b/src/app/core/end-user-agreement/end-user-agreement.guard.ts new file mode 100644 index 0000000000..911dd54e25 --- /dev/null +++ b/src/app/core/end-user-agreement/end-user-agreement.guard.ts @@ -0,0 +1,34 @@ +import { inject } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivateFn, + Router, + RouterStateSnapshot, + UrlTree, +} from '@angular/router'; +import { + Observable, + of as observableOf, +} from 'rxjs'; + +import { environment } from '../../../environments/environment'; +import { returnEndUserAgreementUrlTreeOnFalse } from '../shared/authorized.operators'; + +export declare type HasAcceptedGuardParamFn = () => Observable; +/** + * Guard for preventing activating when the user has not accepted the EndUserAgreement + * @param hasAccepted Function determining if the EndUserAgreement has been accepted + */ +export const endUserAgreementGuard = ( + hasAccepted: HasAcceptedGuardParamFn, +): CanActivateFn => { + return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable => { + const router = inject(Router); + if (!environment.info.enableEndUserAgreement) { + return observableOf(true); + } + return hasAccepted().pipe( + returnEndUserAgreementUrlTreeOnFalse(router, state.url), + ); + }; +}; diff --git a/src/app/core/rest-property/forgot-password-check-guard.guard.ts b/src/app/core/rest-property/forgot-password-check-guard.guard.ts index cc74e8039f..49727dda11 100644 --- a/src/app/core/rest-property/forgot-password-check-guard.guard.ts +++ b/src/app/core/rest-property/forgot-password-check-guard.guard.ts @@ -1,37 +1,11 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../auth/auth.service'; -import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service'; -import { SingleFeatureAuthorizationGuard } from '../data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard'; +import { singleFeatureAuthorizationGuard } from '../data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard'; import { FeatureID } from '../data/feature-authorization/feature-id'; -@Injectable({ - providedIn: 'root', -}) /** * Guard that checks if the forgot-password feature is enabled */ -export class ForgotPasswordCheckGuard extends SingleFeatureAuthorizationGuard { - - constructor( - protected readonly authorizationService: AuthorizationDataService, - protected readonly router: Router, - protected readonly authService: AuthService, - ) { - super(authorizationService, router, authService); - } - - getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return of(FeatureID.EPersonForgotPassword); - } - -} +export const forgotPasswordCheckGuard: CanActivateFn = + singleFeatureAuthorizationGuard(() => observableOf(FeatureID.EPersonForgotPassword)); diff --git a/src/app/health-page/health-page-routes.ts b/src/app/health-page/health-page-routes.ts index 4c02bc548f..f87cf8e3d3 100644 --- a/src/app/health-page/health-page-routes.ts +++ b/src/app/health-page/health-page-routes.ts @@ -1,10 +1,7 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { siteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { HealthPageComponent } from './health-page.component'; export const ROUTES: Route[] = [ @@ -15,7 +12,7 @@ export const ROUTES: Route[] = [ breadcrumbKey: 'health', title: 'health-page.title', }, - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], component: HealthPageComponent, }, ]; diff --git a/src/app/item-page/edit-item-page/edit-item-page-routes.ts b/src/app/item-page/edit-item-page/edit-item-page-routes.ts index 1b1e43a883..a7189f9888 100644 --- a/src/app/item-page/edit-item-page/edit-item-page-routes.ts +++ b/src/app/item-page/edit-item-page/edit-item-page-routes.ts @@ -1,7 +1,4 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { i18nBreadcrumbResolver } from '../../core/breadcrumbs/i18n-breadcrumb.resolver'; import { ThemedDsoEditMetadataComponent } from '../../dso-shared/dso-edit-metadata/themed-dso-edit-metadata.component'; @@ -27,17 +24,21 @@ import { ItemCollectionMapperComponent } from './item-collection-mapper/item-col import { ItemCurateComponent } from './item-curate/item-curate.component'; import { ItemDeleteComponent } from './item-delete/item-delete.component'; import { ItemMoveComponent } from './item-move/item-move.component'; -import { ItemPageAccessControlGuard } from './item-page-access-control.guard'; -import { ItemPageBitstreamsGuard } from './item-page-bitstreams.guard'; -import { ItemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; -import { ItemPageCurateGuard } from './item-page-curate.guard'; -import { ItemPageMetadataGuard } from './item-page-metadata.guard'; -import { ItemPageRegisterDoiGuard } from './item-page-register-doi.guard'; -import { ItemPageReinstateGuard } from './item-page-reinstate.guard'; -import { ItemPageRelationshipsGuard } from './item-page-relationships.guard'; -import { ItemPageStatusGuard } from './item-page-status.guard'; -import { ItemPageVersionHistoryGuard } from './item-page-version-history.guard'; -import { ItemPageWithdrawGuard } from './item-page-withdraw.guard'; +import { itemPageAccessControlGuard } from './item-page-access-control.guard'; +import { itemPageBitstreamsGuard } from './item-page-bitstreams.guard'; +import { itemPageCollectionMapperGuard } from './item-page-collection-mapper.guard'; +import { itemPageCurateGuard } from './item-page-curate.guard'; +import { itemPageDeleteGuard } from './item-page-delete.guard'; +import { itemPageEditAuthorizationsGuard } from './item-page-edit-authorizations.guard'; +import { itemPageMetadataGuard } from './item-page-metadata.guard'; +import { itemPageMoveGuard } from './item-page-move.guard'; +import { itemPagePrivateGuard } from './item-page-private.guard'; +import { itemPageRegisterDoiGuard } from './item-page-register-doi.guard'; +import { itemPageReinstateGuard } from './item-page-reinstate.guard'; +import { itemPageRelationshipsGuard } from './item-page-relationships.guard'; +import { itemPageStatusGuard } from './item-page-status.guard'; +import { itemPageVersionHistoryGuard } from './item-page-version-history.guard'; +import { itemPageWithdrawGuard } from './item-page-withdraw.guard'; import { ItemPrivateComponent } from './item-private/item-private.component'; import { ItemPublicComponent } from './item-public/item-public.component'; import { ItemRegisterDoiComponent } from './item-register-doi/item-register-doi.component'; @@ -72,31 +73,31 @@ export const ROUTES: Route[] = [ path: 'status', component: ThemedItemStatusComponent, data: { title: 'item.edit.tabs.status.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageStatusGuard]), + canActivate: [itemPageStatusGuard], }, { path: 'bitstreams', component: ItemBitstreamsComponent, data: { title: 'item.edit.tabs.bitstreams.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageBitstreamsGuard]), + canActivate: [itemPageBitstreamsGuard], }, { path: 'metadata', component: ThemedDsoEditMetadataComponent, data: { title: 'item.edit.tabs.metadata.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageMetadataGuard]), + canActivate: [itemPageMetadataGuard], }, { path: 'curate', component: ItemCurateComponent, data: { title: 'item.edit.tabs.curate.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageCurateGuard]), + canActivate: [itemPageCurateGuard], }, { path: 'relationships', component: ItemRelationshipsComponent, data: { title: 'item.edit.tabs.relationships.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageRelationshipsGuard]), + canActivate: [itemPageRelationshipsGuard], }, /* TODO - uncomment & fix when view page exists { @@ -114,19 +115,19 @@ export const ROUTES: Route[] = [ path: 'versionhistory', component: ItemVersionHistoryComponent, data: { title: 'item.edit.tabs.versionhistory.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageVersionHistoryGuard]), + canActivate: [itemPageVersionHistoryGuard], }, { path: 'access-control', component: ItemAccessControlComponent, data: { title: 'item.edit.tabs.access-control.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageAccessControlGuard]), + canActivate: [itemPageAccessControlGuard], }, { path: 'mapper', component: ItemCollectionMapperComponent, data: { title: 'item.edit.tabs.item-mapper.title', showBreadcrumbs: true }, - canActivate: mapToCanActivate([ItemPageCollectionMapperGuard]), + canActivate: [itemPageCollectionMapperGuard], }, ], }, @@ -137,16 +138,17 @@ export const ROUTES: Route[] = [ { path: ITEM_EDIT_WITHDRAW_PATH, component: ItemWithdrawComponent, - canActivate: mapToCanActivate([ItemPageWithdrawGuard]), + canActivate: [itemPageWithdrawGuard], }, { path: ITEM_EDIT_REINSTATE_PATH, component: ItemReinstateComponent, - canActivate: mapToCanActivate([ItemPageReinstateGuard]), + canActivate: [itemPageReinstateGuard], }, { path: ITEM_EDIT_PRIVATE_PATH, component: ItemPrivateComponent, + canActivate: [itemPagePrivateGuard], }, { path: ITEM_EDIT_PUBLIC_PATH, @@ -155,16 +157,18 @@ export const ROUTES: Route[] = [ { path: ITEM_EDIT_DELETE_PATH, component: ItemDeleteComponent, + canActivate: [itemPageDeleteGuard], }, { path: ITEM_EDIT_MOVE_PATH, component: ItemMoveComponent, data: { title: 'item.edit.move.title' }, + canActivate: [itemPageMoveGuard], }, { path: ITEM_EDIT_REGISTER_DOI_PATH, component: ItemRegisterDoiComponent, - canActivate: mapToCanActivate([ItemPageRegisterDoiGuard]), + canActivate: [itemPageRegisterDoiGuard], data: { title: 'item.edit.register-doi.title' }, }, { @@ -192,6 +196,7 @@ export const ROUTES: Route[] = [ data: { title: 'item.edit.authorizations.title' }, }, ], + canActivate: [itemPageEditAuthorizationsGuard], }, ], }, diff --git a/src/app/item-page/edit-item-page/item-page-access-control.guard.ts b/src/app/item-page/edit-item-page/item-page-access-control.guard.ts index 42ee1f3d15..bcf5ea9a13 100644 --- a/src/app/item-page/edit-item-page/item-page-access-control.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-access-control.guard.ts @@ -1,43 +1,15 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights */ -export class ItemPageAccessControlGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const itemPageAccessControlGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.AdministratorOf), + ); diff --git a/src/app/item-page/edit-item-page/item-page-bitstreams.guard.ts b/src/app/item-page/edit-item-page/item-page-bitstreams.guard.ts index 396064b524..32af7e603e 100644 --- a/src/app/item-page/edit-item-page/item-page-bitstreams.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-bitstreams.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring manage bitstreams rights + * Check manage bitstreams authorization rights */ -export class ItemPageBitstreamsGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super( authorizationService, router, authService); - } - - /** - * Check manage bitstreams authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanManageBitstreamBundles); - } -} +export const itemPageBitstreamsGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanManageBitstreamBundles), + ); diff --git a/src/app/item-page/edit-item-page/item-page-collection-mapper.guard.ts b/src/app/item-page/edit-item-page/item-page-collection-mapper.guard.ts index aee0a60371..56d9675d92 100644 --- a/src/app/item-page/edit-item-page/item-page-collection-mapper.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-collection-mapper.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring manage mappings rights + * Check manage mappings authorization rights */ -export class ItemPageCollectionMapperGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check manage mappings authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanManageMappings); - } -} +export const itemPageCollectionMapperGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanManageMappings), + ); diff --git a/src/app/item-page/edit-item-page/item-page-curate.guard.ts b/src/app/item-page/edit-item-page/item-page-curate.guard.ts index 392bc5c523..40cfe00c2f 100644 --- a/src/app/item-page/edit-item-page/item-page-curate.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-curate.guard.ts @@ -1,43 +1,15 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights */ -export class ItemPageCurateGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const itemPageCurateGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.AdministratorOf), + ); diff --git a/src/app/item-page/edit-item-page/item-page-delete.guard.spec.ts b/src/app/item-page/edit-item-page/item-page-delete.guard.spec.ts new file mode 100644 index 0000000000..d2d7954c6f --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-delete.guard.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { + Router, + UrlTree, +} from '@angular/router'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; + +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { Item } from '../../core/shared/item.model'; +import { getMockTranslateService } from '../../shared/mocks/translate.service.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { itemPageDeleteGuard } from './item-page-delete.guard'; + +describe('itemPageDeleteGuard', () => { + let authorizationService: AuthorizationDataService; + let authService: AuthService; + let router: Router; + let route; + let parentRoute; + let store: Store; + let itemService: ItemDataService; + let item: Item; + let uuid = '1234-abcdef-54321-fedcba'; + let itemSelfLink = 'test.url/1234-abcdef-54321-fedcba'; + + beforeEach(() => { + + store = jasmine.createSpyObj('store', { + dispatch: {}, + pipe: observableOf(true), + }); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + router = jasmine.createSpyObj('router', { + parseUrl: {}, + navigateByUrl: undefined, + }); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + }); + + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0', + }, + }; + route = { + params: {}, + parent: parentRoute, + }; + item = new Item(); + item.uuid = uuid; + item._links = { self: { href: itemSelfLink } } as any; + itemService = jasmine.createSpyObj('itemService', { findById: createSuccessfulRemoteDataObject$(item) }); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: Store, useValue: store }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: ItemDataService, useValue: itemService }, + ], + }); + }); + + it('should call authorizationService.isAuthorized with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return itemPageDeleteGuard(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith( + FeatureID.CanDelete, + itemSelfLink, // This value is retrieved from the itemDataService.findById's return item's self link + undefined, // dsoPageSingleFeatureGuard never provides a function to retrieve a person ID + ); + done(); + }); + + }); +}); diff --git a/src/app/item-page/edit-item-page/item-page-delete.guard.ts b/src/app/item-page/edit-item-page/item-page-delete.guard.ts new file mode 100644 index 0000000000..99d79ca68f --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-delete.guard.ts @@ -0,0 +1,16 @@ +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { itemPageResolver } from '../item-page.resolver'; + +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring specific authorizations. + * Checks authorization rights for deleting items. + */ +export const itemPageDeleteGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanDelete), + ); diff --git a/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.spec.ts b/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.spec.ts new file mode 100644 index 0000000000..f03e79ad36 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { + Router, + UrlTree, +} from '@angular/router'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; + +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { Item } from '../../core/shared/item.model'; +import { getMockTranslateService } from '../../shared/mocks/translate.service.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { itemPageEditAuthorizationsGuard } from './item-page-edit-authorizations.guard'; + +describe('itemPageEditAuthorizationsGuard', () => { + let authorizationService: AuthorizationDataService; + let authService: AuthService; + let router: Router; + let route; + let parentRoute; + let store: Store; + let itemService: ItemDataService; + let item: Item; + let uuid = '1234-abcdef-54321-fedcba'; + let itemSelfLink = 'test.url/1234-abcdef-54321-fedcba'; + + beforeEach(() => { + + store = jasmine.createSpyObj('store', { + dispatch: {}, + pipe: observableOf(true), + }); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + router = jasmine.createSpyObj('router', { + parseUrl: {}, + navigateByUrl: undefined, + }); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + }); + + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0', + }, + }; + route = { + params: {}, + parent: parentRoute, + }; + item = new Item(); + item.uuid = uuid; + item._links = { self: { href: itemSelfLink } } as any; + itemService = jasmine.createSpyObj('itemService', { findById: createSuccessfulRemoteDataObject$(item) }); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: Store, useValue: store }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: ItemDataService, useValue: itemService }, + ], + }); + }); + + it('should call authorizationService.isAuthorized with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return itemPageEditAuthorizationsGuard(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith( + FeatureID.CanManagePolicies, + itemSelfLink, // This value is retrieved from the itemDataService.findById's return item's self link + undefined, // dsoPageSingleFeatureGuard never provides a function to retrieve a person ID + ); + done(); + }); + + }); +}); diff --git a/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.ts b/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.ts new file mode 100644 index 0000000000..c5032ac604 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-edit-authorizations.guard.ts @@ -0,0 +1,16 @@ +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { itemPageResolver } from '../item-page.resolver'; + +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring specific authorizations. + * Checks authorization rights for managing policies. + */ +export const itemPageEditAuthorizationsGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanManagePolicies), + ); diff --git a/src/app/item-page/edit-item-page/item-page-metadata.guard.ts b/src/app/item-page/edit-item-page/item-page-metadata.guard.ts index 1d6d75df2a..f058eb7359 100644 --- a/src/app/item-page/edit-item-page/item-page-metadata.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-metadata.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring edit metadata rights + * Check edit metadata authorization rights */ -export class ItemPageMetadataGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check edit metadata authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanEditMetadata); - } -} +export const itemPageMetadataGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanEditMetadata), + ); diff --git a/src/app/item-page/edit-item-page/item-page-move.guard.spec.ts b/src/app/item-page/edit-item-page/item-page-move.guard.spec.ts new file mode 100644 index 0000000000..d30f23817a --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-move.guard.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { + Router, + UrlTree, +} from '@angular/router'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; + +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { Item } from '../../core/shared/item.model'; +import { getMockTranslateService } from '../../shared/mocks/translate.service.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { itemPageMoveGuard } from './item-page-move.guard'; + +describe('itemPageMoveGuard', () => { + let authorizationService: AuthorizationDataService; + let authService: AuthService; + let router: Router; + let route; + let parentRoute; + let store: Store; + let itemService: ItemDataService; + let item: Item; + let uuid = '1234-abcdef-54321-fedcba'; + let itemSelfLink = 'test.url/1234-abcdef-54321-fedcba'; + + beforeEach(() => { + + store = jasmine.createSpyObj('store', { + dispatch: {}, + pipe: observableOf(true), + }); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + router = jasmine.createSpyObj('router', { + parseUrl: {}, + navigateByUrl: undefined, + }); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + }); + + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0', + }, + }; + route = { + params: {}, + parent: parentRoute, + }; + item = new Item(); + item.uuid = uuid; + item._links = { self: { href: itemSelfLink } } as any; + itemService = jasmine.createSpyObj('itemService', { findById: createSuccessfulRemoteDataObject$(item) }); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: Store, useValue: store }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: ItemDataService, useValue: itemService }, + ], + }); + }); + + it('should call authorizationService.isAuthorized with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return itemPageMoveGuard(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith( + FeatureID.CanMove, + itemSelfLink, // This value is retrieved from the itemDataService.findById's return item's self link + undefined, // dsoPageSingleFeatureGuard never provides a function to retrieve a person ID + ); + done(); + }); + + }); +}); diff --git a/src/app/item-page/edit-item-page/item-page-move.guard.ts b/src/app/item-page/edit-item-page/item-page-move.guard.ts new file mode 100644 index 0000000000..307201ef90 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-move.guard.ts @@ -0,0 +1,16 @@ +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { itemPageResolver } from '../item-page.resolver'; + +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring specific authorizations. + * Checks authorization rights for moving items. + */ +export const itemPageMoveGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanMove), + ); diff --git a/src/app/item-page/edit-item-page/item-page-private.guard.spec.ts b/src/app/item-page/edit-item-page/item-page-private.guard.spec.ts new file mode 100644 index 0000000000..3e573923d8 --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-private.guard.spec.ts @@ -0,0 +1,94 @@ +import { TestBed } from '@angular/core/testing'; +import { + Router, + UrlTree, +} from '@angular/router'; +import { Store } from '@ngrx/store'; +import { TranslateService } from '@ngx-translate/core'; +import { + Observable, + of as observableOf, +} from 'rxjs'; +import { AuthService } from 'src/app/core/auth/auth.service'; +import { AuthorizationDataService } from 'src/app/core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from 'src/app/core/data/feature-authorization/feature-id'; + +import { APP_DATA_SERVICES_MAP } from '../../../config/app-config.interface'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { Item } from '../../core/shared/item.model'; +import { getMockTranslateService } from '../../shared/mocks/translate.service.mock'; +import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; +import { itemPagePrivateGuard } from './item-page-private.guard'; + +describe('itemPagePrivateGuard', () => { + let authorizationService: AuthorizationDataService; + let authService: AuthService; + let router: Router; + let route; + let parentRoute; + let store: Store; + let itemService: ItemDataService; + let item: Item; + let uuid = '1234-abcdef-54321-fedcba'; + let itemSelfLink = 'test.url/1234-abcdef-54321-fedcba'; + + beforeEach(() => { + + store = jasmine.createSpyObj('store', { + dispatch: {}, + pipe: observableOf(true), + }); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthorized: observableOf(true), + }); + router = jasmine.createSpyObj('router', { + parseUrl: {}, + navigateByUrl: undefined, + }); + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + }); + + parentRoute = { + params: { + id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0', + }, + }; + route = { + params: {}, + parent: parentRoute, + }; + item = new Item(); + item.uuid = uuid; + item._links = { self: { href: itemSelfLink } } as any; + itemService = jasmine.createSpyObj('itemService', { findById: createSuccessfulRemoteDataObject$(item) }); + + TestBed.configureTestingModule({ + providers: [ + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: Router, useValue: router }, + { provide: AuthService, useValue: authService }, + { provide: Store, useValue: store }, + { provide: APP_DATA_SERVICES_MAP, useValue: {} }, + { provide: TranslateService, useValue: getMockTranslateService() }, + { provide: ItemDataService, useValue: itemService }, + ], + }); + }); + + it('should call authorizationService.isAuthorized with the appropriate arguments', (done) => { + const result$ = TestBed.runInInjectionContext(() => { + return itemPagePrivateGuard(route, { url: 'current-url' } as any); + }) as Observable; + + result$.subscribe((result) => { + expect(authorizationService.isAuthorized).toHaveBeenCalledWith( + FeatureID.CanMakePrivate, + itemSelfLink, // This value is retrieved from the itemDataService.findById's return item's self link + undefined, // dsoPageSingleFeatureGuard never provides a function to retrieve a person ID + ); + done(); + }); + + }); +}); diff --git a/src/app/item-page/edit-item-page/item-page-private.guard.ts b/src/app/item-page/edit-item-page/item-page-private.guard.ts new file mode 100644 index 0000000000..64626542ff --- /dev/null +++ b/src/app/item-page/edit-item-page/item-page-private.guard.ts @@ -0,0 +1,16 @@ +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; + +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { itemPageResolver } from '../item-page.resolver'; + +/** + * Guard for preventing unauthorized access to certain {@link Item} pages requiring specific authorizations. + * Checks authorization rights for making items private. + */ +export const itemPagePrivateGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanMakePrivate), + ); diff --git a/src/app/item-page/edit-item-page/item-page-register-doi.guard.ts b/src/app/item-page/edit-item-page/item-page-register-doi.guard.ts index 18c7939852..9bedca518b 100644 --- a/src/app/item-page/edit-item-page/item-page-register-doi.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-register-doi.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring DOI registration rights + * Check DOI registration authorization rights */ -export class ItemPageRegisterDoiGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check DOI registration authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanRegisterDOI); - } -} +export const itemPageRegisterDoiGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanRegisterDOI), + ); diff --git a/src/app/item-page/edit-item-page/item-page-reinstate.guard.ts b/src/app/item-page/edit-item-page/item-page-reinstate.guard.ts index aacf2f3e7d..3e60158d0a 100644 --- a/src/app/item-page/edit-item-page/item-page-reinstate.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-reinstate.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring reinstate rights + * Check reinstate authorization rights */ -export class ItemPageReinstateGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check reinstate authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.ReinstateItem); - } -} +export const itemPageReinstateGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.ReinstateItem), + ); diff --git a/src/app/item-page/edit-item-page/item-page-relationships.guard.ts b/src/app/item-page/edit-item-page/item-page-relationships.guard.ts index 5d16ae8751..fe107977db 100644 --- a/src/app/item-page/edit-item-page/item-page-relationships.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-relationships.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring manage relationships rights + * Check manage relationships authorization rights */ -export class ItemPageRelationshipsGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check manage relationships authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanManageRelationships); - } -} +export const itemPageRelationshipsGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanManageRelationships), + ); diff --git a/src/app/item-page/edit-item-page/item-page-status.guard.ts b/src/app/item-page/edit-item-page/item-page-status.guard.ts index 372b072e19..deeb2dbb5e 100644 --- a/src/app/item-page/edit-item-page/item-page-status.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-status.guard.ts @@ -1,44 +1,17 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSomeFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard'; +import { dsoPageSomeFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-some-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring any of the rights required for * the status page + * Check authorization rights */ -export class ItemPageStatusGuard extends DsoPageSomeFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check authorization rights - */ - getFeatureIDs(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf([FeatureID.CanManageMappings, FeatureID.WithdrawItem, FeatureID.ReinstateItem, FeatureID.CanManagePolicies, FeatureID.CanMakePrivate, FeatureID.CanDelete, FeatureID.CanMove, FeatureID.CanRegisterDOI]); - } -} +export const itemPageStatusGuard: CanActivateFn = + dsoPageSomeFeatureGuard( + () => itemPageResolver, + () => observableOf([FeatureID.CanManageMappings, FeatureID.WithdrawItem, FeatureID.ReinstateItem, FeatureID.CanManagePolicies, FeatureID.CanMakePrivate, FeatureID.CanDelete, FeatureID.CanMove, FeatureID.CanRegisterDOI]), + ); diff --git a/src/app/item-page/edit-item-page/item-page-version-history.guard.ts b/src/app/item-page/edit-item-page/item-page-version-history.guard.ts index dc512527ad..99d581dce6 100644 --- a/src/app/item-page/edit-item-page/item-page-version-history.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-version-history.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring manage versions rights + * Check manage versions authorization rights */ -export class ItemPageVersionHistoryGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check manage versions authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanManageVersions); - } -} +export const itemPageVersionHistoryGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanManageVersions), + ); diff --git a/src/app/item-page/edit-item-page/item-page-withdraw.guard.ts b/src/app/item-page/edit-item-page/item-page-withdraw.guard.ts index 464386edfc..8e41b1c653 100644 --- a/src/app/item-page/edit-item-page/item-page-withdraw.guard.ts +++ b/src/app/item-page/edit-item-page/item-page-withdraw.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring withdraw rights + * Check withdraw authorization rights */ -export class ItemPageWithdrawGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check withdraw authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.WithdrawItem); - } -} +export const itemPageWithdrawGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.WithdrawItem), + ); diff --git a/src/app/item-page/item-page-administrator.guard.ts b/src/app/item-page/item-page-administrator.guard.ts index 117ebf5899..411ffa1e37 100644 --- a/src/app/item-page/item-page-administrator.guard.ts +++ b/src/app/item-page/item-page-administrator.guard.ts @@ -1,43 +1,15 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../core/auth/auth.service'; -import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../core/data/remote-data'; -import { Item } from '../core/shared/item.model'; import { itemPageResolver } from './item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights */ -export class ItemPageAdministratorGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.AdministratorOf); - } -} +export const itemPageAdministratorGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.AdministratorOf), + ); diff --git a/src/app/item-page/item-page-routes.ts b/src/app/item-page/item-page-routes.ts index 57aa70336d..684ea56459 100644 --- a/src/app/item-page/item-page-routes.ts +++ b/src/app/item-page/item-page-routes.ts @@ -1,7 +1,4 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { REQUEST_COPY_MODULE_PATH } from '../app-routing-paths'; import { authenticatedGuard } from '../core/auth/authenticated.guard'; @@ -19,7 +16,7 @@ import { UPLOAD_BITSTREAM_PATH, } from './item-page-routing-paths'; import { OrcidPageComponent } from './orcid-page/orcid-page.component'; -import { OrcidPageGuard } from './orcid-page/orcid-page.guard'; +import { orcidPageGuard } from './orcid-page/orcid-page.guard'; import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { versionResolver } from './version-page/version.resolver'; import { VersionPageComponent } from './version-page/version-page/version-page.component'; @@ -60,7 +57,7 @@ export const ROUTES: Route[] = [ { path: ORCID_PATH, component: OrcidPageComponent, - canActivate: [authenticatedGuard, ...mapToCanActivate([OrcidPageGuard])], + canActivate: [authenticatedGuard, orcidPageGuard], }, ], data: { diff --git a/src/app/item-page/orcid-page/orcid-page.guard.ts b/src/app/item-page/orcid-page/orcid-page.guard.ts index 44c74c684e..c06ab7d97d 100644 --- a/src/app/item-page/orcid-page/orcid-page.guard.ts +++ b/src/app/item-page/orcid-page/orcid-page.guard.ts @@ -1,43 +1,16 @@ -import { Injectable } from '@angular/core'; -import { - ActivatedRouteSnapshot, - ResolveFn, - Router, - RouterStateSnapshot, -} from '@angular/router'; -import { - Observable, - of as observableOf, -} from 'rxjs'; +import { CanActivateFn } from '@angular/router'; +import { of as observableOf } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; -import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; -import { DsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; +import { dsoPageSingleFeatureGuard } from '../../core/data/feature-authorization/feature-authorization-guard/dso-page-single-feature.guard'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; -import { RemoteData } from '../../core/data/remote-data'; -import { Item } from '../../core/shared/item.model'; import { itemPageResolver } from '../item-page.resolver'; -@Injectable({ - providedIn: 'root', -}) /** * Guard for preventing unauthorized access to certain {@link Item} pages requiring administrator rights + * Check administrator authorization rights */ -export class OrcidPageGuard extends DsoPageSingleFeatureGuard { - - protected resolver: ResolveFn> = itemPageResolver; - - constructor(protected authorizationService: AuthorizationDataService, - protected router: Router, - protected authService: AuthService) { - super(authorizationService, router, authService); - } - - /** - * Check administrator authorization rights - */ - getFeatureID(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return observableOf(FeatureID.CanSynchronizeWithORCID); - } -} +export const orcidPageGuard: CanActivateFn = + dsoPageSingleFeatureGuard( + () => itemPageResolver, + () => observableOf(FeatureID.CanSynchronizeWithORCID), + ); diff --git a/src/app/register-page/register-page-routes.ts b/src/app/register-page/register-page-routes.ts index 109ed24c6d..e7ca386aac 100644 --- a/src/app/register-page/register-page-routes.ts +++ b/src/app/register-page/register-page-routes.ts @@ -1,9 +1,6 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; -import { EndUserAgreementCookieGuard } from '../core/end-user-agreement/end-user-agreement-cookie.guard'; +import { endUserAgreementCookieGuard } from '../core/end-user-agreement/end-user-agreement-cookie.guard'; import { ThemedCreateProfileComponent } from './create-profile/themed-create-profile.component'; import { ThemedRegisterEmailComponent } from './register-email/themed-register-email.component'; import { registrationGuard } from './registration.guard'; @@ -20,7 +17,7 @@ export const ROUTES: Route[] = [ component: ThemedCreateProfileComponent, canActivate: [ registrationGuard, - ...mapToCanActivate([EndUserAgreementCookieGuard]), + endUserAgreementCookieGuard, ], }, ]; diff --git a/src/app/statistics-page/statistics-page-routes.ts b/src/app/statistics-page/statistics-page-routes.ts index 41a91ee3aa..69bcc6b41c 100644 --- a/src/app/statistics-page/statistics-page-routes.ts +++ b/src/app/statistics-page/statistics-page-routes.ts @@ -1,12 +1,9 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; import { collectionPageResolver } from '../collection-page/collection-page.resolver'; import { communityPageResolver } from '../community-page/community-page.resolver'; import { i18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver'; -import { StatisticsAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard'; +import { statisticsAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/statistics-administrator.guard'; import { itemResolver } from '../item-page/item.resolver'; import { ThemedCollectionStatisticsPageComponent } from './collection-statistics-page/themed-collection-statistics-page.component'; import { ThemedCommunityStatisticsPageComponent } from './community-statistics-page/themed-community-statistics-page.component'; @@ -29,7 +26,7 @@ export const ROUTES: Route[] = [ component: ThemedSiteStatisticsPageComponent, }, ], - canActivate: mapToCanActivate([StatisticsAdministratorGuard]), + canActivate: [statisticsAdministratorGuard], }, { path: `items/:id`, @@ -42,7 +39,7 @@ export const ROUTES: Route[] = [ breadcrumbKey: 'statistics', }, component: ThemedItemStatisticsPageComponent, - canActivate: mapToCanActivate([StatisticsAdministratorGuard]), + canActivate: [statisticsAdministratorGuard], }, { path: `collections/:id`, @@ -55,7 +52,7 @@ export const ROUTES: Route[] = [ breadcrumbKey: 'statistics', }, component: ThemedCollectionStatisticsPageComponent, - canActivate: mapToCanActivate([StatisticsAdministratorGuard]), + canActivate: [statisticsAdministratorGuard], }, { path: `communities/:id`, @@ -68,6 +65,6 @@ export const ROUTES: Route[] = [ breadcrumbKey: 'statistics', }, component: ThemedCommunityStatisticsPageComponent, - canActivate: mapToCanActivate([StatisticsAdministratorGuard]), + canActivate: [statisticsAdministratorGuard], }, ]; diff --git a/src/app/system-wide-alert/system-wide-alert-routes.ts b/src/app/system-wide-alert/system-wide-alert-routes.ts index 2f3596825b..a71007b6b3 100644 --- a/src/app/system-wide-alert/system-wide-alert-routes.ts +++ b/src/app/system-wide-alert/system-wide-alert-routes.ts @@ -1,15 +1,12 @@ -import { - mapToCanActivate, - Route, -} from '@angular/router'; +import { Route } from '@angular/router'; -import { SiteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { siteAdministratorGuard } from '../core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; import { SystemWideAlertFormComponent } from './alert-form/system-wide-alert-form.component'; export const ROUTES: Route[] = [ { path: '', - canActivate: mapToCanActivate([SiteAdministratorGuard]), + canActivate: [siteAdministratorGuard], component: SystemWideAlertFormComponent, },