diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts index 693f3cf916..51eb9d0690 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts @@ -33,6 +33,9 @@ import { NotificationsServiceStub } from '../../../../shared/testing/notificatio import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock'; import { AuthService } from '../../../../core/auth/auth.service'; import { AuthServiceStub } from '../../../../shared/testing/auth-service.stub'; +import { AuthorizationDataService } from '../../../../core/data/feature-authorization/authorization-data.service'; +import { GroupDataService } from '../../../../core/eperson/group-data.service'; +import { createPaginatedList } from '../../../../shared/testing/utils.test'; describe('EPersonFormComponent', () => { let component: EPersonFormComponent; @@ -43,6 +46,8 @@ describe('EPersonFormComponent', () => { let mockEPeople; let ePersonDataServiceStub: any; let authService: AuthServiceStub; + let authorizationService: AuthorizationDataService; + let groupsDataService: GroupDataService; beforeEach(async(() => { mockEPeople = [EPersonMock, EPersonMock2]; @@ -108,6 +113,13 @@ describe('EPersonFormComponent', () => { builderService = getMockFormBuilderService(); translateService = getMockTranslateService(); authService = new AuthServiceStub(); + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthenticated: observableOf(true) + }); + groupsDataService = jasmine.createSpyObj('groupsDataService', { + findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])), + getGroupRegistryRouterLink: '' + }); TestBed.configureTestingModule({ imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule, TranslateModule.forRoot({ @@ -130,6 +142,8 @@ describe('EPersonFormComponent', () => { { provide: RemoteDataBuildService, useValue: {} }, { provide: HALEndpointService, useValue: {} }, { provide: AuthService, useValue: authService }, + { provide: AuthorizationDataService, useValue: authorizationService }, + { provide: GroupDataService, useValue: groupsDataService }, EPeopleRegistryComponent ], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts index 6d1efd956c..4a52125957 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.ts @@ -245,7 +245,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy { }); })); this.canImpersonate$ = this.epersonService.getActiveEPerson().pipe( - switchMap((eperson) => this.authorizationService.isAuthenticated(FeatureType.LoginOnBehalfOf, eperson.self)) + switchMap((eperson) => this.authorizationService.isAuthenticated(FeatureType.LoginOnBehalfOf, hasValue(eperson) ? eperson.self : undefined)) ); }); } diff --git a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts index e3e8d08753..fd76f0293e 100644 --- a/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts +++ b/src/app/+admin/admin-sidebar/admin-sidebar.component.spec.ts @@ -15,13 +15,18 @@ import { By } from '@angular/platform-browser'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { RouterTestingModule } from '@angular/router/testing'; import { ActivatedRoute } from '@angular/router'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; describe('AdminSidebarComponent', () => { let comp: AdminSidebarComponent; let fixture: ComponentFixture; const menuService = new MenuServiceStub(); + let authorizationService: AuthorizationDataService; beforeEach(async(() => { + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthenticated: observableOf(true) + }); TestBed.configureTestingModule({ imports: [TranslateModule.forRoot(), NoopAnimationsModule, RouterTestingModule], declarations: [AdminSidebarComponent], @@ -31,6 +36,7 @@ describe('AdminSidebarComponent', () => { { provide: CSSVariableService, useClass: CSSVariableServiceStub }, { provide: AuthService, useClass: AuthServiceStub }, { provide: ActivatedRoute, useValue: {} }, + { provide: AuthorizationDataService, useValue: authorizationService }, { provide: NgbModal, useValue: { open: () => {/*comment*/} diff --git a/src/app/core/data/feature-authorization/authorization-data.service.spec.ts b/src/app/core/data/feature-authorization/authorization-data.service.spec.ts new file mode 100644 index 0000000000..bd1fb20ac3 --- /dev/null +++ b/src/app/core/data/feature-authorization/authorization-data.service.spec.ts @@ -0,0 +1,162 @@ +import { AuthorizationDataService } from './authorization-data.service'; +import { SiteDataService } from '../site-data.service'; +import { AuthService } from '../../auth/auth.service'; +import { Site } from '../../shared/site.model'; +import { EPerson } from '../../eperson/models/eperson.model'; +import { of as observableOf } from 'rxjs'; +import { FindListOptions } from '../request.models'; +import { FeatureType } from './feature-type'; +import { hasValue } from '../../../shared/empty.util'; +import { RequestParam } from '../../cache/models/request-param.model'; +import { Authorization } from '../../shared/authorization.model'; +import { RemoteData } from '../remote-data'; +import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; +import { createPaginatedList } from '../../../shared/testing/utils.test'; + +describe('AuthorizationDataService', () => { + let service: AuthorizationDataService; + let siteService: SiteDataService; + let authService: AuthService; + + let site: Site; + let ePerson: EPerson; + + function init() { + site = Object.assign(new Site(), { + id: 'test-site', + _links: { + self: { href: 'test-site-href' } + } + }); + ePerson = Object.assign(new EPerson(), { + id: 'test-eperson', + uuid: 'test-eperson' + }); + siteService = jasmine.createSpyObj('siteService', { + find: observableOf(site) + }); + authService = { + isAuthenticated: () => observableOf(true), + getAuthenticatedUserFromStore: () => observableOf(ePerson) + } as AuthService; + service = new AuthorizationDataService(undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, authService, siteService); + } + + beforeEach(() => { + init(); + spyOn(service, 'searchBy').and.returnValue(observableOf(undefined)); + }); + + describe('searchByObject', () => { + const objectUrl = 'fake-object-url'; + const ePersonUuid = 'fake-eperson-uuid'; + + function createExpected(objectUrl: string, ePersonUuid?: string, featureId?: FeatureType): FindListOptions { + const searchParams = [new RequestParam('uri', objectUrl)]; + if (hasValue(featureId)) { + searchParams.push(new RequestParam('feature', featureId)); + } + if (hasValue(ePersonUuid)) { + searchParams.push(new RequestParam('eperson', ePersonUuid)); + } + return Object.assign(new FindListOptions(), { searchParams }); + } + + describe('when no arguments are provided and a user is authenticated', () => { + beforeEach(() => { + service.searchByObject().subscribe(); + }); + + it('should call searchBy with the site\'s url and authenticated user\'s uuid', () => { + expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, ePerson.uuid)); + }); + }); + + describe('when no arguments except for a feature are provided and a user is authenticated', () => { + beforeEach(() => { + service.searchByObject(FeatureType.LoginOnBehalfOf).subscribe(); + }); + + it('should call searchBy with the site\'s url, authenticated user\'s uuid and the feature', () => { + expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self, ePerson.uuid, FeatureType.LoginOnBehalfOf)); + }); + }); + + describe('when a feature and object url are provided, but no user uuid and a user is authenticated', () => { + beforeEach(() => { + service.searchByObject(FeatureType.LoginOnBehalfOf, objectUrl).subscribe(); + }); + + it('should call searchBy with the object\'s url, authenticated user\'s uuid and the feature', () => { + expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePerson.uuid, FeatureType.LoginOnBehalfOf)); + }); + }); + + describe('when all arguments are provided', () => { + beforeEach(() => { + service.searchByObject(FeatureType.LoginOnBehalfOf, objectUrl, ePersonUuid).subscribe(); + }); + + it('should call searchBy with the object\'s url, user\'s uuid and the feature', () => { + expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(objectUrl, ePersonUuid, FeatureType.LoginOnBehalfOf)); + }); + }); + + describe('when no arguments are provided and no user is authenticated', () => { + beforeEach(() => { + spyOn(authService, 'isAuthenticated').and.returnValue(observableOf(false)); + service.searchByObject().subscribe(); + }); + + it('should call searchBy with the site\'s url', () => { + expect(service.searchBy).toHaveBeenCalledWith('object', createExpected(site.self)); + }); + }); + }); + + describe('isAuthenticated', () => { + const validPayload = [ + Object.assign(new Authorization()) + ] + const emptyPayload = []; + + describe('when searchByObject returns a 401', () => { + beforeEach(() => { + spyOn(service, 'searchByObject').and.returnValue(observableOf(new RemoteData(false, false, true, undefined, undefined, 401))); + }); + + it('should return false', (done) => { + service.isAuthenticated().subscribe((result) => { + expect(result).toEqual(false); + done(); + }); + }); + }); + + describe('when searchByObject returns an empty list', () => { + beforeEach(() => { + spyOn(service, 'searchByObject').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList(emptyPayload))); + }); + + it('should return false', (done) => { + service.isAuthenticated().subscribe((result) => { + expect(result).toEqual(false); + done(); + }); + }); + }); + + describe('when searchByObject returns a valid list', () => { + beforeEach(() => { + spyOn(service, 'searchByObject').and.returnValue(createSuccessfulRemoteDataObject$(createPaginatedList(validPayload))); + }); + + it('should return true', (done) => { + service.isAuthenticated().subscribe((result) => { + expect(result).toEqual(true); + done(); + }); + }); + }); + }); +}); diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.spec.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.spec.ts new file mode 100644 index 0000000000..5af0f42bf7 --- /dev/null +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.spec.ts @@ -0,0 +1,67 @@ +import { FeatureAuthorizationGuard } from './feature-authorization.guard'; +import { AuthorizationDataService } from '../authorization-data.service'; +import { FeatureType } from '../feature-type'; +import { of as observableOf } from 'rxjs'; + +/** + * Test implementation of abstract class FeatureAuthorizationGuard + * Provide the return values of the overwritten getters as constructor arguments + */ +class FeatureAuthorizationGuardImpl extends FeatureAuthorizationGuard { + constructor(protected authorizationService: AuthorizationDataService, + protected featureType: FeatureType, + protected objectUrl: string, + protected ePersonUuid: string) { + super(authorizationService); + } + + getFeatureType(): FeatureType { + return this.featureType; + } + + getObjectUrl(): string { + return this.objectUrl; + } + + getEPersonUuid(): string { + return this.ePersonUuid; + } +} + +describe('FeatureAuthorizationGuard', () => { + let guard: FeatureAuthorizationGuard; + let authorizationService: AuthorizationDataService; + + let featureType: FeatureType; + let objectUrl: string; + let ePersonUuid: string; + + function init() { + featureType = FeatureType.LoginOnBehalfOf; + objectUrl = 'fake-object-url'; + ePersonUuid = 'fake-eperson-uuid'; + + authorizationService = jasmine.createSpyObj('authorizationService', { + isAuthenticated: observableOf(true) + }); + guard = new FeatureAuthorizationGuardImpl(authorizationService, featureType, objectUrl, ePersonUuid); + } + + beforeEach(() => { + init(); + }); + + describe('canActivate', () => { + it('should call authorizationService.isAuthenticated with the appropriate arguments', () => { + guard.canActivate(undefined, undefined).subscribe(); + expect(authorizationService.isAuthenticated).toHaveBeenCalledWith(featureType, objectUrl, ePersonUuid); + }); + }); + + describe('canLoad', () => { + it('should call authorizationService.isAuthenticated with the appropriate arguments', () => { + guard.canLoad(undefined, undefined).subscribe(); + expect(authorizationService.isAuthenticated).toHaveBeenCalledWith(featureType, objectUrl, ePersonUuid); + }); + }); +});