From 638793ca5e438d226f8fd2666c157f855ffc087c Mon Sep 17 00:00:00 2001 From: Kristof De Langhe Date: Fri, 10 Apr 2020 17:41:39 +0200 Subject: [PATCH] 70373: Login as EPerson intermediate commit --- resources/i18n/en.json5 | 8 ++++ .../eperson-form/eperson-form.component.html | 12 +++++ .../eperson-form/eperson-form.component.ts | 46 ++++++++++++++++++- src/app/core/auth/auth.interceptor.ts | 12 ++++- src/app/core/auth/auth.service.ts | 43 ++++++++++++++++- src/app/shared/form/form.component.html | 2 + 6 files changed, 118 insertions(+), 5 deletions(-) diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5 index ae3176d8b1..7f9c41c366 100644 --- a/resources/i18n/en.json5 +++ b/resources/i18n/en.json5 @@ -170,6 +170,14 @@ + "admin.access-control.epeople.actions.delete": "Delete EPerson", + + "admin.access-control.epeople.actions.impersonate": "Impersonate EPerson", + + "admin.access-control.epeople.actions.reset": "Reset password", + + "admin.access-control.epeople.actions.stop-impersonating": "Stop impersonating EPerson", + "admin.access-control.epeople.title": "DSpace Angular :: EPeople", "admin.access-control.epeople.head": "EPeople", diff --git a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html index 578862b561..b87b3e0848 100644 --- a/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html +++ b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.html @@ -14,6 +14,18 @@ [formLayout]="formLayout" (cancel)="onCancel()" (submitForm)="onSubmit()"> + + + +
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 cbcaef78dc..f886514ded 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 @@ -7,7 +7,7 @@ import { DynamicInputModel } from '@ng-dynamic-forms/core'; import { TranslateService } from '@ngx-translate/core'; -import { Subscription, combineLatest } from 'rxjs'; +import { Subscription, combineLatest, of } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; import { take } from 'rxjs/operators'; import { RestResponse } from '../../../../core/cache/response.models'; @@ -22,6 +22,7 @@ import { hasValue } from '../../../../shared/empty.util'; import { FormBuilderService } from '../../../../shared/form/builder/form-builder.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model'; +import { AuthService } from '../../../../core/auth/auth.service'; @Component({ selector: 'ds-eperson-form', @@ -105,6 +106,24 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ @Output() cancelForm: EventEmitter = new EventEmitter(); + /** + * Observable whether or not the admin is allowed to reset the EPerson's password + * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false) + */ + canReset$: Observable = of(false); + + /** + * Observable whether or not the admin is allowed to delete the EPerson + * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return false) + */ + canDelete$: Observable = of(false); + + /** + * Observable whether or not the admin is allowed to impersonate the EPerson + * TODO: Initialize the observable once the REST API supports this (currently hardcoded to return true) + */ + canImpersonate$: Observable = of(true); + /** * List of subscriptions */ @@ -129,13 +148,20 @@ export class EPersonFormComponent implements OnInit, OnDestroy { */ epersonInitial: EPerson; + /** + * Whether or not this EPerson is currently being impersonated + */ + isImpersonated = false; + constructor(public epersonService: EPersonDataService, public groupsDataService: GroupDataService, private formBuilderService: FormBuilderService, private translateService: TranslateService, - private notificationsService: NotificationsService,) { + private notificationsService: NotificationsService, + private authService: AuthService) { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.epersonInitial = eperson; + this.isImpersonated = this.authService.isImpersonatingUser(eperson.id); })); } @@ -364,6 +390,22 @@ export class EPersonFormComponent implements OnInit, OnDestroy { })); } + /** + * Start impersonating the EPerson + */ + impersonate() { + this.authService.impersonate(this.epersonInitial.id); + this.isImpersonated = true; + } + + /** + * Stop impersonating the EPerson + */ + stopImpersonating() { + this.authService.stopImpersonating(); + this.isImpersonated = false; + } + /** * Cancel the current edit when component is destroyed & unsub all subscriptions */ diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts index 6d609a4ea3..bf11d00ccd 100644 --- a/src/app/core/auth/auth.interceptor.ts +++ b/src/app/core/auth/auth.interceptor.ts @@ -18,7 +18,7 @@ import { AppState } from '../../app.reducer'; import { AuthService } from './auth.service'; import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo } from './models/auth-token-info.model'; -import { isNotEmpty, isNotNull, isUndefined } from '../../shared/empty.util'; +import { hasValue, isNotEmpty, isNotNull, isUndefined } from '../../shared/empty.util'; import { RedirectWhenTokenExpiredAction, RefreshTokenAction } from './auth.actions'; import { Store } from '@ngrx/store'; import { Router } from '@angular/router'; @@ -235,8 +235,16 @@ export class AuthInterceptor implements HttpInterceptor { }); // Get the auth header from the service. authorization = authService.buildAuthHeader(token); + let newHeaders = req.headers.set('authorization', authorization); + + // When present, add the ID of the EPerson we're impersonating to the headers + const impersonatingID = authService.getImpersonateID(); + if (hasValue(impersonatingID)) { + newHeaders = newHeaders.set('X-On-Behalf-Of', impersonatingID); + } + // Clone the request to add the new header. - newReq = req.clone({ headers: req.headers.set('authorization', authorization) }); + newReq = req.clone({ headers: newHeaders }); } else { newReq = req.clone(); } diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts index 0f5c06bbc9..46d02a03cf 100644 --- a/src/app/core/auth/auth.service.ts +++ b/src/app/core/auth/auth.service.ts @@ -14,7 +14,7 @@ import { AuthRequestService } from './auth-request.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { AuthStatus } from './models/auth-status.model'; import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model'; -import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util'; import { CookieService } from '../services/cookie.service'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { AppState, routerStateSelector } from '../../app.reducer'; @@ -33,6 +33,7 @@ import { AuthMethod } from './models/auth.method'; export const LOGIN_ROUTE = '/login'; export const LOGOUT_ROUTE = '/logout'; export const REDIRECT_COOKIE = 'dsRedirectUrl'; +export const IMPERSONATING_COOKIE = 'dsImpersonatingEPerson'; /** * The auth service. @@ -469,4 +470,44 @@ export class AuthService { this.storage.remove(REDIRECT_COOKIE); } + /** + * Start impersonating EPerson + * @param epersonId ID of the EPerson to impersonate + */ + impersonate(epersonId: string) { + this.storage.set(IMPERSONATING_COOKIE, epersonId); + this.refreshAfterLogout(); + } + + /** + * Stop impersonating EPerson + */ + stopImpersonating() { + this.storage.remove(IMPERSONATING_COOKIE); + this.refreshAfterLogout(); + } + + /** + * Get the ID of the EPerson we're currently impersonating + * Returns undefined if we're not impersonating anyone + */ + getImpersonateID(): string { + return this.storage.get(IMPERSONATING_COOKIE); + } + + /** + * Whether or not we are currently impersonating an EPerson + */ + isImpersonating(): boolean { + return hasValue(this.getImpersonateID()); + } + + /** + * Whether or not we are currently impersonating a specific EPerson + * @param epersonId ID of the EPerson to check + */ + isImpersonatingUser(epersonId: string): boolean { + return this.getImpersonateID() === epersonId; + } + } diff --git a/src/app/shared/form/form.component.html b/src/app/shared/form/form.component.html index 24948680c7..20fb942380 100644 --- a/src/app/shared/form/form.component.html +++ b/src/app/shared/form/form.component.html @@ -45,6 +45,8 @@ + +