74612: 403 page + redirect to login on unauthorized

This commit is contained in:
Kristof De Langhe
2020-11-18 14:05:36 +01:00
parent 12da991630
commit ab755d3fd9
27 changed files with 221 additions and 35 deletions

View File

@@ -17,13 +17,14 @@ import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
import { Item } from '../core/shared/item.model'; import { Item } from '../core/shared/item.model';
import { import {
getSucceededRemoteData, getSucceededRemoteData,
redirectOn404Or401, redirectOn4xx,
toDSpaceObjectListRD toDSpaceObjectListRD
} from '../core/shared/operators'; } from '../core/shared/operators';
import { fadeIn, fadeInOut } from '../shared/animations/fade'; import { fadeIn, fadeInOut } from '../shared/animations/fade';
import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util'; import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { AuthService } from '../core/auth/auth.service';
@Component({ @Component({
selector: 'ds-collection-page', selector: 'ds-collection-page',
@@ -51,7 +52,8 @@ export class CollectionPageComponent implements OnInit {
private searchService: SearchService, private searchService: SearchService,
private metadata: MetadataService, private metadata: MetadataService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router,
private authService: AuthService,
) { ) {
this.paginationConfig = new PaginationComponentOptions(); this.paginationConfig = new PaginationComponentOptions();
this.paginationConfig.id = 'collection-page-pagination'; this.paginationConfig.id = 'collection-page-pagination';
@@ -63,7 +65,7 @@ export class CollectionPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.collectionRD$ = this.route.data.pipe( this.collectionRD$ = this.route.data.pipe(
map((data) => data.dso as RemoteData<Collection>), map((data) => data.dso as RemoteData<Collection>),
redirectOn404Or401(this.router), redirectOn4xx(this.router, this.authService),
take(1) take(1)
); );
this.logoRD$ = this.collectionRD$.pipe( this.logoRD$ = this.collectionRD$.pipe(

View File

@@ -13,7 +13,8 @@ import { MetadataService } from '../core/metadata/metadata.service';
import { fadeInOut } from '../shared/animations/fade'; import { fadeInOut } from '../shared/animations/fade';
import { hasValue } from '../shared/empty.util'; import { hasValue } from '../shared/empty.util';
import { redirectOn404Or401 } from '../core/shared/operators'; import { redirectOn4xx } from '../core/shared/operators';
import { AuthService } from '../core/auth/auth.service';
@Component({ @Component({
selector: 'ds-community-page', selector: 'ds-community-page',
@@ -39,7 +40,8 @@ export class CommunityPageComponent implements OnInit {
private communityDataService: CommunityDataService, private communityDataService: CommunityDataService,
private metadata: MetadataService, private metadata: MetadataService,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router,
private authService: AuthService,
) { ) {
} }
@@ -47,7 +49,7 @@ export class CommunityPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.communityRD$ = this.route.data.pipe( this.communityRD$ = this.route.data.pipe(
map((data) => data.dso as RemoteData<Community>), map((data) => data.dso as RemoteData<Community>),
redirectOn404Or401(this.router) redirectOn4xx(this.router, this.authService)
); );
this.logoRD$ = this.communityRD$.pipe( this.logoRD$ = this.communityRD$.pipe(
map((rd: RemoteData<Community>) => rd.payload), map((rd: RemoteData<Community>) => rd.payload),

View File

@@ -21,6 +21,7 @@ import {
createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$ createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils'; } from '../../shared/remote-data.utils';
import { AuthService } from '../../core/auth/auth.service';
const mockItem: Item = Object.assign(new Item(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
@@ -46,7 +47,14 @@ describe('FullItemPageComponent', () => {
let comp: FullItemPageComponent; let comp: FullItemPageComponent;
let fixture: ComponentFixture<FullItemPageComponent>; let fixture: ComponentFixture<FullItemPageComponent>;
let authService: AuthService;
beforeEach(async(() => { beforeEach(async(() => {
authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
loader: { loader: {
@@ -58,7 +66,8 @@ describe('FullItemPageComponent', () => {
providers: [ providers: [
{provide: ActivatedRoute, useValue: routeStub}, {provide: ActivatedRoute, useValue: routeStub},
{provide: ItemDataService, useValue: {}}, {provide: ItemDataService, useValue: {}},
{provide: MetadataService, useValue: metadataServiceStub} {provide: MetadataService, useValue: metadataServiceStub},
{ provide: AuthService, useValue: authService },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -15,6 +15,7 @@ import { MetadataService } from '../../core/metadata/metadata.service';
import { fadeInOut } from '../../shared/animations/fade'; import { fadeInOut } from '../../shared/animations/fade';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -35,8 +36,8 @@ export class FullItemPageComponent extends ItemPageComponent implements OnInit {
metadata$: Observable<MetadataMap>; metadata$: Observable<MetadataMap>;
constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService) { constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService, authService: AuthService) {
super(route, router, items, metadataService); super(route, router, items, metadataService, authService);
} }
/*** AoT inheritance fix, will hopefully be resolved in the near future **/ /*** AoT inheritance fix, will hopefully be resolved in the near future **/

View File

@@ -20,6 +20,7 @@ import {
createFailedRemoteDataObject$, createPendingRemoteDataObject$, createSuccessfulRemoteDataObject, createFailedRemoteDataObject$, createPendingRemoteDataObject$, createSuccessfulRemoteDataObject,
createSuccessfulRemoteDataObject$ createSuccessfulRemoteDataObject$
} from '../../shared/remote-data.utils'; } from '../../shared/remote-data.utils';
import { AuthService } from '../../core/auth/auth.service';
const mockItem: Item = Object.assign(new Item(), { const mockItem: Item = Object.assign(new Item(), {
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])), bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
@@ -30,6 +31,7 @@ const mockItem: Item = Object.assign(new Item(), {
describe('ItemPageComponent', () => { describe('ItemPageComponent', () => {
let comp: ItemPageComponent; let comp: ItemPageComponent;
let fixture: ComponentFixture<ItemPageComponent>; let fixture: ComponentFixture<ItemPageComponent>;
let authService: AuthService;
const mockMetadataService = { const mockMetadataService = {
/* tslint:disable:no-empty */ /* tslint:disable:no-empty */
@@ -41,6 +43,11 @@ describe('ItemPageComponent', () => {
}); });
beforeEach(async(() => { beforeEach(async(() => {
authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({ imports: [TranslateModule.forRoot({
loader: { loader: {
@@ -53,7 +60,8 @@ describe('ItemPageComponent', () => {
{provide: ActivatedRoute, useValue: mockRoute}, {provide: ActivatedRoute, useValue: mockRoute},
{provide: ItemDataService, useValue: {}}, {provide: ItemDataService, useValue: {}},
{provide: MetadataService, useValue: mockMetadataService}, {provide: MetadataService, useValue: mockMetadataService},
{provide: Router, useValue: {}} {provide: Router, useValue: {}},
{ provide: AuthService, useValue: authService },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]

View File

@@ -11,8 +11,9 @@ import { Item } from '../../core/shared/item.model';
import { MetadataService } from '../../core/metadata/metadata.service'; import { MetadataService } from '../../core/metadata/metadata.service';
import { fadeInOut } from '../../shared/animations/fade'; import { fadeInOut } from '../../shared/animations/fade';
import { redirectOn404Or401 } from '../../core/shared/operators'; import { redirectOn4xx } from '../../core/shared/operators';
import { ViewMode } from '../../core/shared/view-mode.model'; import { ViewMode } from '../../core/shared/view-mode.model';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* This component renders a simple item page. * This component renders a simple item page.
@@ -48,6 +49,7 @@ export class ItemPageComponent implements OnInit {
private router: Router, private router: Router,
private items: ItemDataService, private items: ItemDataService,
private metadataService: MetadataService, private metadataService: MetadataService,
private authService: AuthService,
) { } ) { }
/** /**
@@ -56,7 +58,7 @@ export class ItemPageComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.itemRD$ = this.route.data.pipe( this.itemRD$ = this.route.data.pipe(
map((data) => data.item as RemoteData<Item>), map((data) => data.item as RemoteData<Item>),
redirectOn404Or401(this.router) redirectOn4xx(this.router, this.authService)
); );
this.metadataService.processRemoteData(this.itemRD$); this.metadataService.processRemoteData(this.itemRD$);
} }

View File

@@ -61,6 +61,12 @@ export function getUnauthorizedRoute() {
return `/${UNAUTHORIZED_PATH}`; return `/${UNAUTHORIZED_PATH}`;
} }
export const FORBIDDEN_PATH = '403';
export function getForbiddenRoute() {
return `/${FORBIDDEN_PATH}`;
}
export const PAGE_NOT_FOUND_PATH = '404'; export const PAGE_NOT_FOUND_PATH = '404';
export function getPageNotFoundRoute() { export function getPageNotFoundRoute() {

View File

@@ -14,7 +14,7 @@ import {
PROFILE_MODULE_PATH, PROFILE_MODULE_PATH,
ADMIN_MODULE_PATH, ADMIN_MODULE_PATH,
BITSTREAM_MODULE_PATH, BITSTREAM_MODULE_PATH,
INFO_MODULE_PATH INFO_MODULE_PATH, FORBIDDEN_PATH
} from './app-routing-paths'; } from './app-routing-paths';
import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths'; import { COLLECTION_MODULE_PATH } from './+collection-page/collection-page-routing-paths';
import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths'; import { COMMUNITY_MODULE_PATH } from './+community-page/community-page-routing-paths';
@@ -22,6 +22,7 @@ import { ITEM_MODULE_PATH } from './+item-page/item-page-routing-paths';
import { ReloadGuard } from './core/reload/reload.guard'; import { ReloadGuard } from './core/reload/reload.guard';
import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard'; import { EndUserAgreementCurrentUserGuard } from './core/end-user-agreement/end-user-agreement-current-user.guard';
import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard'; import { SiteRegisterGuard } from './core/data/feature-authorization/feature-authorization-guard/site-register.guard';
import { ForbiddenComponent } from './forbidden/forbidden.component';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -69,6 +70,7 @@ import { SiteRegisterGuard } from './core/data/feature-authorization/feature-aut
{ path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] }, { path: 'processes', loadChildren: './process-page/process-page.module#ProcessPageModule', canActivate: [AuthenticatedGuard, EndUserAgreementCurrentUserGuard] },
{ path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' }, { path: INFO_MODULE_PATH, loadChildren: './info/info.module#InfoModule' },
{ path: UNAUTHORIZED_PATH, component: UnauthorizedComponent }, { path: UNAUTHORIZED_PATH, component: UnauthorizedComponent },
{ path: FORBIDDEN_PATH, component: ForbiddenComponent },
{ {
path: 'statistics', path: 'statistics',
loadChildren: './statistics-page/statistics-page-routing.module#StatisticsPageRoutingModule', loadChildren: './statistics-page/statistics-page-routing.module#StatisticsPageRoutingModule',

View File

@@ -42,6 +42,7 @@ import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; import { UnauthorizedComponent } from './unauthorized/unauthorized.component';
import { ForbiddenComponent } from './forbidden/forbidden.component';
export function getBase() { export function getBase() {
return environment.ui.nameSpace; return environment.ui.nameSpace;
@@ -116,6 +117,9 @@ const DECLARATIONS = [
NotificationComponent, NotificationComponent,
NotificationsBoardComponent, NotificationsBoardComponent,
SearchNavbarComponent, SearchNavbarComponent,
BreadcrumbsComponent,
UnauthorizedComponent,
ForbiddenComponent,
]; ];
const EXPORTS = [ const EXPORTS = [
@@ -133,8 +137,6 @@ const EXPORTS = [
], ],
declarations: [ declarations: [
...DECLARATIONS, ...DECLARATIONS,
BreadcrumbsComponent,
UnauthorizedComponent,
], ],
exports: [ exports: [
...EXPORTS ...EXPORTS

View File

@@ -24,6 +24,10 @@ export class ServerResponseService {
return this.setStatus(401, message) return this.setStatus(401, message)
} }
setForbidden(message = 'Forbidden'): this {
return this.setStatus(403, message)
}
setNotFound(message = 'Not found'): this { setNotFound(message = 'Not found'): this {
return this.setStatus(404, message) return this.setStatus(404, message)
} }

View File

@@ -14,7 +14,7 @@ import {
getResourceLinksFromResponse, getResourceLinksFromResponse,
getResponseFromEntry, getResponseFromEntry,
getSucceededRemoteData, getSucceededRemoteData,
redirectOn404Or401 redirectOn4xx
} from './operators'; } from './operators';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { RemoteDataError } from '../data/remote-data-error'; import { RemoteDataError } from '../data/remote-data-error';
@@ -200,39 +200,74 @@ describe('Core Module - RxJS Operators', () => {
}); });
}); });
describe('redirectOn404Or401', () => { describe('redirectOn4xx', () => {
let router; let router;
let authService;
beforeEach(() => { beforeEach(() => {
router = jasmine.createSpyObj('router', ['navigateByUrl']); router = jasmine.createSpyObj('router', ['navigateByUrl']);
authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
}); });
it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => { it('should call navigateByUrl to a 404 page, when the remote data contains a 404 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found')); const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(404, 'Not Found', 'Object was not found'));
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe(); observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true }); expect(router.navigateByUrl).toHaveBeenCalledWith('/404', { skipLocationChange: true });
}); });
it('should call navigateByUrl to a 403 page, when the remote data contains a 403 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(403, 'Forbidden', 'Forbidden access'));
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(router.navigateByUrl).toHaveBeenCalledWith('/403', { skipLocationChange: true });
});
it('should call navigateByUrl to a 401 page, when the remote data contains a 401 error', () => { it('should call navigateByUrl to a 401 page, when the remote data contains a 401 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized')); const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized'));
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe(); observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(router.navigateByUrl).toHaveBeenCalledWith('/401', { skipLocationChange: true }); expect(router.navigateByUrl).toHaveBeenCalledWith('/401', { skipLocationChange: true });
}); });
it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains another error than a 404 or 401', () => { it('should not call navigateByUrl to a 404, 403 or 401 page, when the remote data contains another error than a 404, 403 or 401', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong')); const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(500, 'Server Error', 'Something went wrong'));
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe(); observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(router.navigateByUrl).not.toHaveBeenCalled(); expect(router.navigateByUrl).not.toHaveBeenCalled();
}); });
it('should not call navigateByUrl to a 404 or 401 page, when the remote data contains no error', () => { it('should not call navigateByUrl to a 404, 403 or 401 page, when the remote data contains no error', () => {
const testRD = createSuccessfulRemoteDataObject(undefined); const testRD = createSuccessfulRemoteDataObject(undefined);
observableOf(testRD).pipe(redirectOn404Or401(router)).subscribe(); observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(router.navigateByUrl).not.toHaveBeenCalled(); expect(router.navigateByUrl).not.toHaveBeenCalled();
}); });
describe('when the user is not authenticated', () => {
beforeEach(() => {
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
});
it('should set the redirect url and navigate to login when the remote data contains a 401 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(401, 'Unauthorized', 'The current user is unauthorized'));
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(authService.setRedirectUrl).toHaveBeenCalled();
expect(router.navigateByUrl).toHaveBeenCalledWith('login');
});
it('should set the redirect url and navigate to login when the remote data contains a 403 error', () => {
const testRD = createFailedRemoteDataObject(undefined, new RemoteDataError(403, 'Forbidden', 'Forbidden access'));
observableOf(testRD).pipe(redirectOn4xx(router, authService)).subscribe();
expect(authService.setRedirectUrl).toHaveBeenCalled();
expect(router.navigateByUrl).toHaveBeenCalledWith('login');
});
});
}); });
describe('getResponseFromEntry', () => { describe('getResponseFromEntry', () => {

View File

@@ -13,8 +13,9 @@ import { MetadataField } from '../metadata/metadata-field.model';
import { MetadataSchema } from '../metadata/metadata-schema.model'; import { MetadataSchema } from '../metadata/metadata-schema.model';
import { BrowseDefinition } from './browse-definition.model'; import { BrowseDefinition } from './browse-definition.model';
import { DSpaceObject } from './dspace-object.model'; import { DSpaceObject } from './dspace-object.model';
import { getPageNotFoundRoute, getUnauthorizedRoute } from '../../app-routing-paths'; import { getForbiddenRoute, getPageNotFoundRoute, getUnauthorizedRoute } from '../../app-routing-paths';
import { getEndUserAgreementPath } from '../../info/info-routing-paths'; import { getEndUserAgreementPath } from '../../info/info-routing-paths';
import { AuthService } from '../auth/auth.service';
/** /**
* This file contains custom RxJS operators that can be used in multiple places * This file contains custom RxJS operators that can be used in multiple places
@@ -174,18 +175,29 @@ export const getAllSucceededRemoteListPayload = () =>
* Operator that checks if a remote data object returned a 401 or 404 error * Operator that checks if a remote data object returned a 401 or 404 error
* When it does contain such an error, it will redirect the user to the related error page, without altering the current URL * When it does contain such an error, it will redirect the user to the related error page, without altering the current URL
* @param router The router used to navigate to a new page * @param router The router used to navigate to a new page
* @param authService Service to check if the user is authenticated
*/ */
export const redirectOn404Or401 = (router: Router) => export const redirectOn4xx = (router: Router, authService: AuthService) =>
<T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> => <T>(source: Observable<RemoteData<T>>): Observable<RemoteData<T>> =>
source.pipe( observableCombineLatest(source, authService.isAuthenticated()).pipe(
tap((rd: RemoteData<T>) => { map(([rd, isAuthenticated]: [RemoteData<T>, boolean]) => {
if (rd.hasFailed) { if (rd.hasFailed) {
if (rd.error.statusCode === 404) { if (rd.error.statusCode === 404) {
router.navigateByUrl(getPageNotFoundRoute(), {skipLocationChange: true}); router.navigateByUrl(getPageNotFoundRoute(), {skipLocationChange: true});
} else if (rd.error.statusCode === 401) { } else if (rd.error.statusCode === 403 || rd.error.statusCode === 401) {
router.navigateByUrl(getUnauthorizedRoute(), {skipLocationChange: true}); if (isAuthenticated) {
if (rd.error.statusCode === 403) {
router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true});
} else if (rd.error.statusCode === 401) {
router.navigateByUrl(getUnauthorizedRoute(), {skipLocationChange: true});
}
} else {
authService.setRedirectUrl(router.url);
router.navigateByUrl('login');
}
} }
} }
return rd;
})); }));
/** /**

View File

@@ -0,0 +1,10 @@
<div class="forbidden container">
<h1>403</h1>
<h2><small>{{"403.forbidden" | translate}}</small></h2>
<br/>
<p>{{"403.help" | translate}}</p>
<br/>
<p class="text-center">
<a routerLink="/home" class="btn btn-primary">{{"403.link.home-page" | translate}}</a>
</p>
</div>

View File

@@ -0,0 +1,32 @@
import { Component, OnInit } from '@angular/core';
import { AuthService } from '../core/auth/auth.service';
import { ServerResponseService } from '../core/services/server-response.service';
/**
* This component representing the `Forbidden` DSpace page.
*/
@Component({
selector: 'ds-forbidden',
templateUrl: './forbidden.component.html',
styleUrls: ['./forbidden.component.scss']
})
export class ForbiddenComponent implements OnInit {
/**
* Initialize instance variables
*
* @param {AuthService} authService
* @param {ServerResponseService} responseService
*/
constructor(private authService: AuthService, private responseService: ServerResponseService) {
this.responseService.setForbidden();
}
/**
* Remove redirect url from the state
*/
ngOnInit(): void {
this.authService.clearRedirectUrl();
}
}

View File

@@ -15,6 +15,7 @@ import { ProcessDataService } from '../../core/data/processes/process-data.servi
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
import { createPaginatedList } from '../../shared/testing/utils.test'; import { createPaginatedList } from '../../shared/testing/utils.test';
import { AuthService } from '../../core/auth/auth.service';
describe('ProcessDetailComponent', () => { describe('ProcessDetailComponent', () => {
let component: ProcessDetailComponent; let component: ProcessDetailComponent;
@@ -22,6 +23,7 @@ describe('ProcessDetailComponent', () => {
let processService: ProcessDataService; let processService: ProcessDataService;
let nameService: DSONameService; let nameService: DSONameService;
let authService: AuthService;
let process: Process; let process: Process;
let fileName: string; let fileName: string;
@@ -65,6 +67,10 @@ describe('ProcessDetailComponent', () => {
nameService = jasmine.createSpyObj('nameService', { nameService = jasmine.createSpyObj('nameService', {
getName: fileName getName: fileName
}); });
authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
} }
beforeEach(async(() => { beforeEach(async(() => {
@@ -75,7 +81,8 @@ describe('ProcessDetailComponent', () => {
providers: [ providers: [
{ provide: ActivatedRoute, useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } }, { provide: ActivatedRoute, useValue: { data: observableOf({ process: createSuccessfulRemoteDataObject(process) }) } },
{ provide: ProcessDataService, useValue: processService }, { provide: ProcessDataService, useValue: processService },
{ provide: DSONameService, useValue: nameService } { provide: DSONameService, useValue: nameService },
{ provide: AuthService, useValue: authService },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}).compileComponents(); }).compileComponents();

View File

@@ -4,12 +4,13 @@ import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { Process } from '../processes/process.model'; import { Process } from '../processes/process.model';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { getFirstSucceededRemoteDataPayload, redirectOn404Or401 } from '../../core/shared/operators'; import { getFirstSucceededRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators';
import { AlertType } from '../../shared/alert/aletr-type'; import { AlertType } from '../../shared/alert/aletr-type';
import { ProcessDataService } from '../../core/data/processes/process-data.service'; import { ProcessDataService } from '../../core/data/processes/process-data.service';
import { PaginatedList } from '../../core/data/paginated-list'; import { PaginatedList } from '../../core/data/paginated-list';
import { Bitstream } from '../../core/shared/bitstream.model'; import { Bitstream } from '../../core/shared/bitstream.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { AuthService } from '../../core/auth/auth.service';
@Component({ @Component({
selector: 'ds-process-detail', selector: 'ds-process-detail',
@@ -39,7 +40,8 @@ export class ProcessDetailComponent implements OnInit {
constructor(protected route: ActivatedRoute, constructor(protected route: ActivatedRoute,
protected router: Router, protected router: Router,
protected processService: ProcessDataService, protected processService: ProcessDataService,
protected nameService: DSONameService) { protected nameService: DSONameService,
protected authService: AuthService) {
} }
/** /**
@@ -49,7 +51,7 @@ export class ProcessDetailComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.processRD$ = this.route.data.pipe( this.processRD$ = this.route.data.pipe(
map((data) => data.process as RemoteData<Process>), map((data) => data.process as RemoteData<Process>),
redirectOn404Or401(this.router) redirectOn4xx(this.router, this.authService)
); );
this.filesRD$ = this.processRD$.pipe( this.filesRD$ = this.processRD$.pipe(

View File

@@ -14,6 +14,7 @@ import { SharedModule } from '../../shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { AuthService } from '../../core/auth/auth.service';
describe('CollectionStatisticsPageComponent', () => { describe('CollectionStatisticsPageComponent', () => {
@@ -59,6 +60,11 @@ describe('CollectionStatisticsPageComponent', () => {
getName: () => observableOf('test dso name'), getName: () => observableOf('test dso name'),
}; };
const authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
@@ -75,6 +81,7 @@ describe('CollectionStatisticsPageComponent', () => {
{ provide: UsageReportService, useValue: usageReportService }, { provide: UsageReportService, useValue: usageReportService },
{ provide: DSpaceObjectDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} },
{ provide: DSONameService, useValue: nameService }, { provide: DSONameService, useValue: nameService },
{ provide: AuthService, useValue: authService },
], ],
}) })
.compileComponents(); .compileComponents();

View File

@@ -4,6 +4,7 @@ import { UsageReportService } from '../../core/statistics/usage-report-data.serv
import { ActivatedRoute , Router} from '@angular/router'; import { ActivatedRoute , Router} from '@angular/router';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* Component representing the statistics page for a collection. * Component representing the statistics page for a collection.
@@ -30,12 +31,14 @@ export class CollectionStatisticsPageComponent extends StatisticsPageComponent<C
protected router: Router, protected router: Router,
protected usageReportService: UsageReportService, protected usageReportService: UsageReportService,
protected nameService: DSONameService, protected nameService: DSONameService,
protected authService: AuthService
) { ) {
super( super(
route, route,
router, router,
usageReportService, usageReportService,
nameService, nameService,
authService,
); );
} }
} }

View File

@@ -14,6 +14,7 @@ import { SharedModule } from '../../shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { AuthService } from '../../core/auth/auth.service';
describe('CommunityStatisticsPageComponent', () => { describe('CommunityStatisticsPageComponent', () => {
@@ -59,6 +60,11 @@ describe('CommunityStatisticsPageComponent', () => {
getName: () => observableOf('test dso name'), getName: () => observableOf('test dso name'),
}; };
const authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
@@ -75,6 +81,7 @@ describe('CommunityStatisticsPageComponent', () => {
{ provide: UsageReportService, useValue: usageReportService }, { provide: UsageReportService, useValue: usageReportService },
{ provide: DSpaceObjectDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} },
{ provide: DSONameService, useValue: nameService }, { provide: DSONameService, useValue: nameService },
{ provide: AuthService, useValue: authService },
], ],
}) })
.compileComponents(); .compileComponents();

View File

@@ -4,6 +4,7 @@ import { UsageReportService } from '../../core/statistics/usage-report-data.serv
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* Component representing the statistics page for a community. * Component representing the statistics page for a community.
@@ -30,12 +31,14 @@ export class CommunityStatisticsPageComponent extends StatisticsPageComponent<Co
protected router: Router, protected router: Router,
protected usageReportService: UsageReportService, protected usageReportService: UsageReportService,
protected nameService: DSONameService, protected nameService: DSONameService,
protected authService: AuthService,
) { ) {
super( super(
route, route,
router, router,
usageReportService, usageReportService,
nameService, nameService,
authService,
); );
} }
} }

View File

@@ -14,6 +14,7 @@ import { SharedModule } from '../../shared/shared.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { AuthService } from '../../core/auth/auth.service';
describe('ItemStatisticsPageComponent', () => { describe('ItemStatisticsPageComponent', () => {
@@ -59,6 +60,11 @@ describe('ItemStatisticsPageComponent', () => {
getName: () => observableOf('test dso name'), getName: () => observableOf('test dso name'),
}; };
const authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
@@ -75,6 +81,7 @@ describe('ItemStatisticsPageComponent', () => {
{ provide: UsageReportService, useValue: usageReportService }, { provide: UsageReportService, useValue: usageReportService },
{ provide: DSpaceObjectDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} },
{ provide: DSONameService, useValue: nameService }, { provide: DSONameService, useValue: nameService },
{ provide: AuthService, useValue: authService },
], ],
}) })
.compileComponents(); .compileComponents();

View File

@@ -4,6 +4,7 @@ import { UsageReportService } from '../../core/statistics/usage-report-data.serv
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { Item } from '../../core/shared/item.model'; import { Item } from '../../core/shared/item.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* Component representing the statistics page for an item. * Component representing the statistics page for an item.
@@ -31,12 +32,14 @@ export class ItemStatisticsPageComponent extends StatisticsPageComponent<Item> {
protected router: Router, protected router: Router,
protected usageReportService: UsageReportService, protected usageReportService: UsageReportService,
protected nameService: DSONameService, protected nameService: DSONameService,
protected authService: AuthService
) { ) {
super( super(
route, route,
router, router,
usageReportService, usageReportService,
nameService, nameService,
authService,
); );
} }
} }

View File

@@ -14,6 +14,7 @@ import { CommonModule } from '@angular/common';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service'; import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
import { SiteDataService } from '../../core/data/site-data.service'; import { SiteDataService } from '../../core/data/site-data.service';
import { AuthService } from '../../core/auth/auth.service';
describe('SiteStatisticsPageComponent', () => { describe('SiteStatisticsPageComponent', () => {
@@ -55,6 +56,11 @@ describe('SiteStatisticsPageComponent', () => {
})) }))
}; };
const authService = jasmine.createSpyObj('authService', {
isAuthenticated: observableOf(true),
setRedirectUrl: {}
});
TestBed.configureTestingModule({ TestBed.configureTestingModule({
imports: [ imports: [
TranslateModule.forRoot(), TranslateModule.forRoot(),
@@ -72,6 +78,7 @@ describe('SiteStatisticsPageComponent', () => {
{ provide: DSpaceObjectDataService, useValue: {} }, { provide: DSpaceObjectDataService, useValue: {} },
{ provide: DSONameService, useValue: nameService }, { provide: DSONameService, useValue: nameService },
{ provide: SiteDataService, useValue: siteService }, { provide: SiteDataService, useValue: siteService },
{ provide: AuthService, useValue: authService },
], ],
}) })
.compileComponents(); .compileComponents();

View File

@@ -6,6 +6,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { Site } from '../../core/shared/site.model'; import { Site } from '../../core/shared/site.model';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* Component representing the site-wide statistics page. * Component representing the site-wide statistics page.
@@ -30,12 +31,14 @@ export class SiteStatisticsPageComponent extends StatisticsPageComponent<Site> {
protected usageReportService: UsageReportService, protected usageReportService: UsageReportService,
protected nameService: DSONameService, protected nameService: DSONameService,
protected siteService: SiteDataService, protected siteService: SiteDataService,
protected authService: AuthService,
) { ) {
super( super(
route, route,
router, router,
usageReportService, usageReportService,
nameService, nameService,
authService,
); );
} }

View File

@@ -4,10 +4,11 @@ import { UsageReportService } from '../../core/statistics/usage-report-data.serv
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { UsageReport } from '../../core/statistics/models/usage-report.model'; import { UsageReport } from '../../core/statistics/models/usage-report.model';
import { RemoteData } from '../../core/data/remote-data'; import { RemoteData } from '../../core/data/remote-data';
import { getRemoteDataPayload, getSucceededRemoteData, redirectOn404Or401 } from '../../core/shared/operators'; import { getRemoteDataPayload, getSucceededRemoteData, redirectOn4xx } from '../../core/shared/operators';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { AuthService } from '../../core/auth/auth.service';
/** /**
* Class representing an abstract statistics page component. * Class representing an abstract statistics page component.
@@ -36,6 +37,7 @@ export abstract class StatisticsPageComponent<T extends DSpaceObject> implements
protected router: Router, protected router: Router,
protected usageReportService: UsageReportService, protected usageReportService: UsageReportService,
protected nameService: DSONameService, protected nameService: DSONameService,
protected authService: AuthService,
) { ) {
} }
@@ -55,7 +57,7 @@ export abstract class StatisticsPageComponent<T extends DSpaceObject> implements
protected getScope$(): Observable<DSpaceObject> { protected getScope$(): Observable<DSpaceObject> {
return this.route.data.pipe( return this.route.data.pipe(
map((data) => data.scope as RemoteData<T>), map((data) => data.scope as RemoteData<T>),
redirectOn404Or401(this.router), redirectOn4xx(this.router, this.authService),
getSucceededRemoteData(), getSucceededRemoteData(),
getRemoteDataPayload(), getRemoteDataPayload(),
); );

View File

@@ -8,6 +8,14 @@
"403.help": "You don't have permission to access this page. You can use the button below to get back to the home page.",
"403.link.home-page": "Take me to the home page",
"403.forbidden": "forbidden",
"404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ", "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
"404.link.home-page": "Take me to the home page", "404.link.home-page": "Take me to the home page",