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 @@
+
+