diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b10ae4df55..9694ed42b0 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -11,6 +11,7 @@ import { Item } from './core/shared/item.model'; import { getItemPageRoute } from './+item-page/item-page-routing.module'; import { getCollectionPageRoute } from './+collection-page/collection-page-routing.module'; import { SiteAdministratorGuard } from './core/data/feature-authorization/feature-authorization-guard/site-administrator.guard'; +import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; const ITEM_MODULE_PATH = 'items'; @@ -99,6 +100,7 @@ export function getDSOPath(dso: DSpaceObject): string { path: PROFILE_MODULE_PATH, loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard] }, + { path: '401', component: UnauthorizedComponent }, { path: '**', pathMatch: 'full', component: PageNotFoundComponent }, ], { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index ced019a6f9..33454ed6c5 100755 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -40,6 +40,7 @@ import { SharedModule } from './shared/shared.module'; import { BreadcrumbsComponent } from './breadcrumbs/breadcrumbs.component'; import { environment } from '../environments/environment'; import { BrowserModule } from '@angular/platform-browser'; +import { UnauthorizedComponent } from './unauthorized/unauthorized.component'; export function getBase() { return environment.ui.nameSpace; @@ -123,6 +124,7 @@ const EXPORTS = [ declarations: [ ...DECLARATIONS, BreadcrumbsComponent, + UnauthorizedComponent, ], exports: [ ...EXPORTS diff --git a/src/app/core/data/feature-authorization/authorization-data.service.ts b/src/app/core/data/feature-authorization/authorization-data.service.ts index f997ee250d..92a56e7bcf 100644 --- a/src/app/core/data/feature-authorization/authorization-data.service.ts +++ b/src/app/core/data/feature-authorization/authorization-data.service.ts @@ -26,6 +26,7 @@ import { RequestParam } from '../../cache/models/request-param.model'; import { AuthorizationSearchParams } from './authorization-search-params'; import { addAuthenticatedUserUuidIfEmpty, addSiteObjectUrlIfEmpty } from './authorization-utils'; import { FeatureID } from './feature-id'; +import { of } from 'rxjs/internal/observable/of'; /** * A service to retrieve {@link Authorization}s from the REST API diff --git a/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.ts b/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.ts index bbee3abff2..708b197441 100644 --- a/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.ts +++ b/src/app/core/data/feature-authorization/feature-authorization-guard/feature-authorization.guard.ts @@ -1,7 +1,16 @@ -import { ActivatedRouteSnapshot, CanActivate, CanLoad, Route, RouterStateSnapshot, UrlSegment } from '@angular/router'; +import { + ActivatedRouteSnapshot, + CanActivate, + CanLoad, + Route, + Router, + RouterStateSnapshot, + UrlSegment +} from '@angular/router'; import { AuthorizationDataService } from '../authorization-data.service'; import { FeatureID } from '../feature-id'; import { Observable } from 'rxjs/internal/Observable'; +import { redirectToUnauthorizedOnFalse } from '../../../shared/operators'; /** * Abstract Guard for preventing unauthorized activating and loading of routes when a user @@ -9,21 +18,24 @@ import { Observable } from 'rxjs/internal/Observable'; * Override the desired getters in the parent class for checking specific authorization on a feature and/or object. */ export abstract class FeatureAuthorizationGuard implements CanActivate, CanLoad { - constructor(protected authorizationService: AuthorizationDataService) { + constructor(protected authorizationService: AuthorizationDataService, + protected router: Router) { } /** * True when user has authorization rights for the feature and object provided + * Redirect the user to the unauthorized page when he/she's not authorized for the given feature */ canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.authorizationService.isAuthenticated(this.getFeatureID(), this.getObjectUrl(), this.getEPersonUuid()); + return this.authorizationService.isAuthenticated(this.getFeatureID(), this.getObjectUrl(), this.getEPersonUuid()).pipe(redirectToUnauthorizedOnFalse(this.router)); } /** * True when user has authorization rights for the feature and object provided + * Redirect the user to the unauthorized page when he/she's not authorized for the given feature */ canLoad(route: Route, segments: UrlSegment[]): Observable { - return this.authorizationService.isAuthenticated(this.getFeatureID(), this.getObjectUrl(), this.getEPersonUuid()); + return this.authorizationService.isAuthenticated(this.getFeatureID(), this.getObjectUrl(), this.getEPersonUuid()).pipe(redirectToUnauthorizedOnFalse(this.router)); } /** 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 91f47fa19c..a64e40468d 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 @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'; import { FeatureAuthorizationGuard } from './feature-authorization.guard'; import { FeatureID } from '../feature-id'; import { AuthorizationDataService } from '../authorization-data.service'; +import { Router } from '@angular/router'; /** * Prevent unauthorized activating and loading of routes when the current authenticated user doesn't have administrator @@ -11,8 +12,8 @@ import { AuthorizationDataService } from '../authorization-data.service'; providedIn: 'root' }) export class SiteAdministratorGuard extends FeatureAuthorizationGuard { - constructor(protected authorizationService: AuthorizationDataService) { - super(authorizationService); + constructor(protected authorizationService: AuthorizationDataService, protected router: Router) { + super(authorizationService, router); } /** diff --git a/src/app/core/services/server-response.service.ts b/src/app/core/services/server-response.service.ts index f143c56ffb..10da2a3379 100644 --- a/src/app/core/services/server-response.service.ts +++ b/src/app/core/services/server-response.service.ts @@ -20,6 +20,10 @@ export class ServerResponseService { return this; } + setUnauthorized(message = 'Unauthorized'): this { + return this.setStatus(401, message) + } + setNotFound(message = 'Not found'): this { return this.setStatus(404, message) } diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index a307b144d2..ca1394005e 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -180,6 +180,19 @@ export const redirectToPageNotFoundOn404 = (router: Router) => } })); +/** + * Operator that redirects the user to the unauthorized page when the boolean received is false + * @param router + */ +export const redirectToUnauthorizedOnFalse = (router: Router) => + (source: Observable): Observable => + source.pipe( + tap((authorized: boolean) => { + if (!authorized) { + router.navigateByUrl('/401', { skipLocationChange: true }); + } + })); + export const getFinishedRemoteData = () => (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => !rd.isLoading)); diff --git a/src/app/unauthorized/unauthorized.component.html b/src/app/unauthorized/unauthorized.component.html new file mode 100644 index 0000000000..a25e271b77 --- /dev/null +++ b/src/app/unauthorized/unauthorized.component.html @@ -0,0 +1,10 @@ +
+

401

+

{{"401.unauthorized" | translate}}

+
+

{{"401.help" | translate}}

+
+

+ {{"401.link.home-page" | translate}} +

+
diff --git a/src/app/unauthorized/unauthorized.component.scss b/src/app/unauthorized/unauthorized.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/unauthorized/unauthorized.component.ts b/src/app/unauthorized/unauthorized.component.ts new file mode 100644 index 0000000000..280a1ae947 --- /dev/null +++ b/src/app/unauthorized/unauthorized.component.ts @@ -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 `Unauthorized` DSpace page. + */ +@Component({ + selector: 'ds-unauthorized', + templateUrl: './unauthorized.component.html', + styleUrls: ['./unauthorized.component.scss'] +}) +export class UnauthorizedComponent implements OnInit { + + /** + * Initialize instance variables + * + * @param {AuthService} authservice + * @param {ServerResponseService} responseService + */ + constructor(private authservice: AuthService, private responseService: ServerResponseService) { + this.responseService.setUnauthorized(); + } + + /** + * Remove redirect url from the state + */ + ngOnInit(): void { + this.authservice.clearRedirectUrl(); + } + +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 4173fa1cf2..08a411fcf8 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1,5 +1,13 @@ { + "401.help": "You're not authorized to access this page. You can use the button below to get back to the home page.", + + "401.link.home-page": "Take me to the home page", + + "401.unauthorized": "unauthorized", + + + "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",