mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
78243: edit item page fine-grained permission checks - mocks + tests
This commit is contained in:
@@ -28,19 +28,19 @@ describe('ItemOperationComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render operation row', () => {
|
it('should render operation row', () => {
|
||||||
const span = fixture.debugElement.query(By.css('span')).nativeElement;
|
const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement;
|
||||||
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
||||||
const link = fixture.debugElement.query(By.css('a')).nativeElement;
|
const button = fixture.debugElement.query(By.css('button')).nativeElement;
|
||||||
expect(link.href).toContain('url1');
|
expect(button.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
||||||
expect(link.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
|
||||||
});
|
});
|
||||||
it('should render disabled operation row', () => {
|
it('should render disabled operation row', () => {
|
||||||
itemOperation.setDisabled(true);
|
itemOperation.setDisabled(true);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const span = fixture.debugElement.query(By.css('span')).nativeElement;
|
const span = fixture.debugElement.query(By.css('.action-label span')).nativeElement;
|
||||||
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
expect(span.textContent).toContain('item.edit.tabs.status.buttons.key1.label');
|
||||||
const span2 = fixture.debugElement.query(By.css('span.btn-danger')).nativeElement;
|
const button = fixture.debugElement.query(By.css('button')).nativeElement;
|
||||||
expect(span2.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
expect(button.disabled).toBeTrue();
|
||||||
|
expect(button.textContent).toContain('item.edit.tabs.status.buttons.key1.button');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -9,9 +9,9 @@ import { FeatureID } from '../feature-id';
|
|||||||
import { AuthService } from '../../../auth/auth.service';
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test implementation of abstract class DsoPageAdministratorGuard
|
* Test implementation of abstract class DsoPageSingleFeatureGuard
|
||||||
*/
|
*/
|
||||||
class DsoPageFeatureGuardImpl extends DsoPageSingleFeatureGuard<any> {
|
class DsoPageSingleFeatureGuardImpl extends DsoPageSingleFeatureGuard<any> {
|
||||||
constructor(protected resolver: Resolve<RemoteData<any>>,
|
constructor(protected resolver: Resolve<RemoteData<any>>,
|
||||||
protected authorizationService: AuthorizationDataService,
|
protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@@ -25,7 +25,7 @@ class DsoPageFeatureGuardImpl extends DsoPageSingleFeatureGuard<any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('DsoPageAdministratorGuard', () => {
|
describe('DsoPageSingleFeatureGuard', () => {
|
||||||
let guard: DsoPageSingleFeatureGuard<any>;
|
let guard: DsoPageSingleFeatureGuard<any>;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
@@ -62,7 +62,7 @@ describe('DsoPageAdministratorGuard', () => {
|
|||||||
},
|
},
|
||||||
parent: parentRoute
|
parent: parentRoute
|
||||||
};
|
};
|
||||||
guard = new DsoPageFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
guard = new DsoPageSingleFeatureGuardImpl(resolver, authorizationService, router, authService, undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -0,0 +1,87 @@
|
|||||||
|
import { AuthorizationDataService } from '../authorization-data.service';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { RemoteData } from '../../remote-data';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
|
import { DSpaceObject } from '../../../shared/dspace-object.model';
|
||||||
|
import { FeatureID } from '../feature-id';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
import { DsoPageSomeFeatureGuard } from './dso-page-some-feature.guard';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test implementation of abstract class DsoPageSomeFeatureGuard
|
||||||
|
*/
|
||||||
|
class DsoPageSomeFeatureGuardImpl extends DsoPageSomeFeatureGuard<any> {
|
||||||
|
constructor(protected resolver: Resolve<RemoteData<any>>,
|
||||||
|
protected authorizationService: AuthorizationDataService,
|
||||||
|
protected router: Router,
|
||||||
|
protected authService: AuthService,
|
||||||
|
protected featureIDs: FeatureID[]) {
|
||||||
|
super(resolver, authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureIDs(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID[]> {
|
||||||
|
return observableOf(this.featureIDs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('DsoPageSomeFeatureGuard', () => {
|
||||||
|
let guard: DsoPageSomeFeatureGuard<any>;
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
let router: Router;
|
||||||
|
let authService: AuthService;
|
||||||
|
let resolver: Resolve<RemoteData<any>>;
|
||||||
|
let object: DSpaceObject;
|
||||||
|
let route;
|
||||||
|
let parentRoute;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
object = {
|
||||||
|
self: 'test-selflink'
|
||||||
|
} as DSpaceObject;
|
||||||
|
|
||||||
|
authorizationService = jasmine.createSpyObj('authorizationService', {
|
||||||
|
isAuthorized: observableOf(true)
|
||||||
|
});
|
||||||
|
router = jasmine.createSpyObj('router', {
|
||||||
|
parseUrl: {}
|
||||||
|
});
|
||||||
|
resolver = jasmine.createSpyObj('resolver', {
|
||||||
|
resolve: createSuccessfulRemoteDataObject$(object)
|
||||||
|
});
|
||||||
|
authService = jasmine.createSpyObj('authService', {
|
||||||
|
isAuthenticated: observableOf(true)
|
||||||
|
});
|
||||||
|
parentRoute = {
|
||||||
|
params: {
|
||||||
|
id: '3e1a5327-dabb-41ff-af93-e6cab9d032f0'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
route = {
|
||||||
|
params: {
|
||||||
|
},
|
||||||
|
parent: parentRoute
|
||||||
|
};
|
||||||
|
guard = new DsoPageSomeFeatureGuardImpl(resolver, authorizationService, router, authService, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getObjectUrl', () => {
|
||||||
|
it('should return the resolved object\'s selflink', (done) => {
|
||||||
|
guard.getObjectUrl(route, undefined).subscribe((selflink) => {
|
||||||
|
expect(selflink).toEqual(object.self);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getRouteWithDSOId', () => {
|
||||||
|
it('should return the route that has the UUID of the DSO', () => {
|
||||||
|
const foundRoute = (guard as any).getRouteWithDSOId(route);
|
||||||
|
expect(foundRoute).toBe(parentRoute);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -10,7 +10,7 @@ import { hasNoValue, hasValue } from '../../../../shared/empty.util';
|
|||||||
import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard';
|
import { SomeFeatureAuthorizationGuard } from './some-feature-authorization.guard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Guard for preventing unauthorized access to {@link DSpaceObject} pages that require rights for a specific feature
|
* 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
|
* This guard utilizes a resolver to retrieve the relevant object to check authorizations for
|
||||||
*/
|
*/
|
||||||
export abstract class DsoPageSomeFeatureGuard<T extends DSpaceObject> extends SomeFeatureAuthorizationGuard {
|
export abstract class DsoPageSomeFeatureGuard<T extends DSpaceObject> extends SomeFeatureAuthorizationGuard {
|
||||||
|
@@ -6,10 +6,10 @@ import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/ro
|
|||||||
import { AuthService } from '../../../auth/auth.service';
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test implementation of abstract class FeatureAuthorizationGuard
|
* Test implementation of abstract class SingleFeatureAuthorizationGuard
|
||||||
* Provide the return values of the overwritten getters as constructor arguments
|
* Provide the return values of the overwritten getters as constructor arguments
|
||||||
*/
|
*/
|
||||||
class FeatureAuthorizationGuardImpl extends SingleFeatureAuthorizationGuard {
|
class SingleFeatureAuthorizationGuardImpl extends SingleFeatureAuthorizationGuard {
|
||||||
constructor(protected authorizationService: AuthorizationDataService,
|
constructor(protected authorizationService: AuthorizationDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
@@ -32,7 +32,7 @@ class FeatureAuthorizationGuardImpl extends SingleFeatureAuthorizationGuard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('FeatureAuthorizationGuard', () => {
|
describe('SingleFeatureAuthorizationGuard', () => {
|
||||||
let guard: SingleFeatureAuthorizationGuard;
|
let guard: SingleFeatureAuthorizationGuard;
|
||||||
let authorizationService: AuthorizationDataService;
|
let authorizationService: AuthorizationDataService;
|
||||||
let router: Router;
|
let router: Router;
|
||||||
@@ -56,7 +56,7 @@ describe('FeatureAuthorizationGuard', () => {
|
|||||||
authService = jasmine.createSpyObj('authService', {
|
authService = jasmine.createSpyObj('authService', {
|
||||||
isAuthenticated: observableOf(true)
|
isAuthenticated: observableOf(true)
|
||||||
});
|
});
|
||||||
guard = new FeatureAuthorizationGuardImpl(authorizationService, router, authService, featureId, objectUrl, ePersonUuid);
|
guard = new SingleFeatureAuthorizationGuardImpl(authorizationService, router, authService, featureId, objectUrl, ePersonUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@@ -0,0 +1,110 @@
|
|||||||
|
import { AuthorizationDataService } from '../authorization-data.service';
|
||||||
|
import { FeatureID } from '../feature-id';
|
||||||
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { AuthService } from '../../../auth/auth.service';
|
||||||
|
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<FeatureID[]> {
|
||||||
|
return observableOf(this.featureIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
getObjectUrl(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
||||||
|
return observableOf(this.objectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
getEPersonUuid(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> {
|
||||||
|
return observableOf(this.ePersonUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('SomeFeatureAuthorizationGuard', () => {
|
||||||
|
let guard: SomeFeatureAuthorizationGuard;
|
||||||
|
let authorizationService: AuthorizationDataService;
|
||||||
|
let router: Router;
|
||||||
|
let authService: AuthService;
|
||||||
|
|
||||||
|
let featureIds: FeatureID[];
|
||||||
|
let authorizedFeatureIds: FeatureID[];
|
||||||
|
let objectUrl: string;
|
||||||
|
let ePersonUuid: string;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
featureIds = [FeatureID.LoginOnBehalfOf, FeatureID.CanDelete];
|
||||||
|
authorizedFeatureIds = [];
|
||||||
|
objectUrl = 'fake-object-url';
|
||||||
|
ePersonUuid = 'fake-eperson-uuid';
|
||||||
|
|
||||||
|
authorizationService = Object.assign({
|
||||||
|
isAuthorized(featureId?: FeatureID): Observable<boolean> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('canActivate', () => {
|
||||||
|
describe('when the user isn\'t authorized', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorizedFeatureIds = [];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not return true', (done) => {
|
||||||
|
guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => {
|
||||||
|
expect(result).not.toEqual(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is authorized for at least one of the guard\'s features', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorizedFeatureIds = [featureIds[0]];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (done) => {
|
||||||
|
guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => {
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the user is authorized for all of the guard\'s features', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
authorizedFeatureIds = featureIds;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true', (done) => {
|
||||||
|
guard.canActivate(undefined, { url: 'current-url' } as any).subscribe((result) => {
|
||||||
|
expect(result).toEqual(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canDelete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"size": 20,
|
||||||
|
"totalElements": 0,
|
||||||
|
"totalPages": 1,
|
||||||
|
"number": 0
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"_embedded": {
|
||||||
|
"authorizations": [
|
||||||
|
{
|
||||||
|
"id": "cd824a61-95be-4e16-bccd-51fea26707d0_canMove_core.item_96715576-3748-4761-ad45-001646632963",
|
||||||
|
"type": "authorization",
|
||||||
|
"_links": {
|
||||||
|
"eperson": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canMove_core.item_96715576-3748-4761-ad45-001646632963/eperson"
|
||||||
|
},
|
||||||
|
"feature": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canMove_core.item_96715576-3748-4761-ad45-001646632963/feature"
|
||||||
|
},
|
||||||
|
"object": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canMove_core.item_96715576-3748-4761-ad45-001646632963/object"
|
||||||
|
},
|
||||||
|
"self": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/cd824a61-95be-4e16-bccd-51fea26707d0_canMove_core.item_96715576-3748-4761-ad45-001646632963"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_embedded": {
|
||||||
|
"feature": {
|
||||||
|
"id": "canMove",
|
||||||
|
"description": "It can be used to verify if the user is allowed to move items",
|
||||||
|
"type": "feature",
|
||||||
|
"resourcetypes": [
|
||||||
|
"core.item"
|
||||||
|
],
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/features/canMove"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canMove"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page": {
|
||||||
|
"size": 20,
|
||||||
|
"totalElements": 1,
|
||||||
|
"totalPages": 1,
|
||||||
|
"number": 0
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,8 @@ import { InjectionToken } from '@angular/core';
|
|||||||
// import mockPublicationResponse from './mock-publication-response.json';
|
// import mockPublicationResponse from './mock-publication-response.json';
|
||||||
// import mockUntypedItemResponse from './mock-untyped-item-response.json';
|
// import mockUntypedItemResponse from './mock-untyped-item-response.json';
|
||||||
import mockFeatureItemCanManageBitstreamsResponse from './mock-feature-item-can-manage-bitstreams-response.json';
|
import mockFeatureItemCanManageBitstreamsResponse from './mock-feature-item-can-manage-bitstreams-response.json';
|
||||||
|
import mockFeatureItemCanMoveResponse from './mock-feature-item-can-move-response.json';
|
||||||
|
import mockFeatureItemCanDeleteNoneResponse from './mock-feature-item-can-delete-none-response.json';
|
||||||
|
|
||||||
export class ResponseMapMock extends Map<string, any> {}
|
export class ResponseMapMock extends Map<string, any> {}
|
||||||
|
|
||||||
@@ -18,4 +20,6 @@ export const mockResponseMap: ResponseMapMock = new Map([
|
|||||||
// [ '/api/pid/find', mockPublicationResponse ],
|
// [ '/api/pid/find', mockPublicationResponse ],
|
||||||
// [ '/api/pid/find', mockUntypedItemResponse ],
|
// [ '/api/pid/find', mockUntypedItemResponse ],
|
||||||
[ 'https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canManageBitstreams&embed=feature', mockFeatureItemCanManageBitstreamsResponse ],
|
[ 'https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canManageBitstreams&embed=feature', mockFeatureItemCanManageBitstreamsResponse ],
|
||||||
|
[ 'https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canMove&embed=feature', mockFeatureItemCanMoveResponse ],
|
||||||
|
[ 'https://api7.dspace.org/server/api/authz/authorizations/search/object?uri=https://api7.dspace.org/server/api/core/items/96715576-3748-4761-ad45-001646632963&feature=canDelete&embed=feature', mockFeatureItemCanDeleteNoneResponse ],
|
||||||
]);
|
]);
|
||||||
|
Reference in New Issue
Block a user