Skip to content

Commit

Permalink
Merge pull request DSpace#3049 from atmire/w2p-114858_refactor_guards…
Browse files Browse the repository at this point in the history
…_as_functions-8.0

Fix: Item edit pages not hidden/redirect for anon or non-authorised users
  • Loading branch information
tdonohue authored May 31, 2024
2 parents ebf9469 + 553bada commit b9a1045
Show file tree
Hide file tree
Showing 62 changed files with 1,281 additions and 1,284 deletions.
27 changes: 12 additions & 15 deletions src/app/access-control/access-control-routes.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -20,15 +17,15 @@ 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';

/**
* Condition for displaying error messages on email form field
*/
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 = [
Expand All @@ -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`,
Expand All @@ -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`,
Expand All @@ -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,
Expand All @@ -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`,
Expand All @@ -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`,
Expand All @@ -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',
Expand All @@ -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],
},
];
59 changes: 42 additions & 17 deletions src/app/access-control/group-registry/group-page.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<boolean | UrlTree>;

result$.subscribe((result) => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(
FeatureID.CanManageGroup, groupEndpointUrl, undefined,
);
Expand All @@ -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<boolean | UrlTree>;

result$.subscribe((result) => {
expect(authorizationService.isAuthorized).toHaveBeenCalledWith(
FeatureID.CanManageGroup, groupEndpointUrl, undefined,
);
expect(result).not.toBeTrue();
done();
});

});
});
});
Expand Down
51 changes: 23 additions & 28 deletions src/app/access-control/group-registry/group-page.guard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { inject } from '@angular/core';
import {
ActivatedRouteSnapshot,
Router,
CanActivateFn,
RouterStateSnapshot,
} from '@angular/router';
import {
Expand All @@ -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 {

protected groupsEndpoint = 'groups';

constructor(protected halEndpointService: HALEndpointService,
protected authorizationService: AuthorizationDataService,
protected router: Router,
protected authService: AuthService) {
super(authorizationService, router, authService);
}

getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
return observableOf([FeatureID.CanManageGroup]);
}
const defaultGroupPageGetObjectUrl: StringGuardParamFn = (
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<string> => {
const halEndpointService = inject(HALEndpointService);
const groupsEndpoint = 'groups';

getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
return this.halEndpointService.getEndpoint(this.groupsEndpoint).pipe(
map(groupsUrl => `${groupsUrl}/${route?.params?.groupId}`),
);
}
return halEndpointService.getEndpoint(groupsEndpoint).pipe(
map(groupsUrl => `${groupsUrl}/${route?.params?.groupId}`),
);
};

}
export const groupPageGuard = (
getObjectUrl = defaultGroupPageGetObjectUrl,
getEPersonUuid?: StringGuardParamFn,
): CanActivateFn => someFeatureAuthorizationGuard(
() => observableOf([FeatureID.CanManageGroup]),
getObjectUrl,
getEPersonUuid);
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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',
Expand All @@ -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',
Expand Down
Loading

0 comments on commit b9a1045

Please sign in to comment.