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.spec.ts b/src/app/+admin/admin-access-control/epeople-registry/eperson-form/eperson-form.component.spec.ts
index 292b49ac6b..693f3cf916 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
@@ -31,6 +31,8 @@ import { getMockFormBuilderService } from '../../../../shared/mocks/form-builder
import { getMockTranslateService } from '../../../../shared/mocks/translate.service.mock';
import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service.stub';
import { TranslateLoaderMock } from '../../../../shared/mocks/translate-loader.mock';
+import { AuthService } from '../../../../core/auth/auth.service';
+import { AuthServiceStub } from '../../../../shared/testing/auth-service.stub';
describe('EPersonFormComponent', () => {
let component: EPersonFormComponent;
@@ -40,6 +42,7 @@ describe('EPersonFormComponent', () => {
let mockEPeople;
let ePersonDataServiceStub: any;
+ let authService: AuthServiceStub;
beforeEach(async(() => {
mockEPeople = [EPersonMock, EPersonMock2];
@@ -104,6 +107,7 @@ describe('EPersonFormComponent', () => {
};
builderService = getMockFormBuilderService();
translateService = getMockTranslateService();
+ authService = new AuthServiceStub();
TestBed.configureTestingModule({
imports: [CommonModule, NgbModule, FormsModule, ReactiveFormsModule, BrowserModule,
TranslateModule.forRoot({
@@ -125,6 +129,7 @@ describe('EPersonFormComponent', () => {
{ provide: Store, useValue: {} },
{ provide: RemoteDataBuildService, useValue: {} },
{ provide: HALEndpointService, useValue: {} },
+ { provide: AuthService, useValue: authService },
EPeopleRegistryComponent
],
schemas: [NO_ERRORS_SCHEMA]
@@ -228,4 +233,40 @@ describe('EPersonFormComponent', () => {
});
});
+ describe('impersonate', () => {
+ let ePersonId;
+
+ beforeEach(() => {
+ spyOn(authService, 'impersonate').and.callThrough();
+ ePersonId = 'testEPersonId';
+ component.epersonInitial = Object.assign(new EPerson(), {
+ id: ePersonId
+ });
+ component.impersonate();
+ });
+
+ it('should call authService.impersonate', () => {
+ expect(authService.impersonate).toHaveBeenCalledWith(ePersonId);
+ });
+
+ it('should set isImpersonated to true', () => {
+ expect(component.isImpersonated).toBe(true);
+ });
+ });
+
+ describe('stopImpersonating', () => {
+ beforeEach(() => {
+ spyOn(authService, 'stopImpersonatingAndRefresh').and.callThrough();
+ component.stopImpersonating();
+ });
+
+ it('should call authService.stopImpersonatingAndRefresh', () => {
+ expect(authService.stopImpersonatingAndRefresh).toHaveBeenCalled();
+ });
+
+ it('should set isImpersonated to false', () => {
+ expect(component.isImpersonated).toBe(false);
+ });
+ });
+
});
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..9e3bcc88c0 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,22 @@ 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;
+ if (hasValue(eperson)) {
+ this.isImpersonated = this.authService.isImpersonatingUser(eperson.id);
+ }
}));
}
@@ -364,6 +392,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.stopImpersonatingAndRefresh();
+ this.isImpersonated = false;
+ }
+
/**
* Cancel the current edit when component is destroyed & unsub all subscriptions
*/
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index ead1157df1..0ba0851e4e 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -66,6 +66,7 @@ export function getDSOPath(dso: DSpaceObject): string {
imports: [
RouterModule.forRoot([
{ path: '', redirectTo: '/home', pathMatch: 'full' },
+ { path: 'reload/:rnd', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule', data: { showBreadcrumbs: false } },
{ path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' },
{ path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
diff --git a/src/app/core/auth/auth.actions.ts b/src/app/core/auth/auth.actions.ts
index 2c2224e878..9237c30db9 100644
--- a/src/app/core/auth/auth.actions.ts
+++ b/src/app/core/auth/auth.actions.ts
@@ -402,10 +402,10 @@ export class RetrieveAuthenticatedEpersonAction implements Action {
*/
export class RetrieveAuthenticatedEpersonSuccessAction implements Action {
public type: string = AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_SUCCESS;
- payload: EPerson;
+ payload: string;
- constructor(user: EPerson) {
- this.payload = user ;
+ constructor(userId: string) {
+ this.payload = userId ;
}
}
diff --git a/src/app/core/auth/auth.effects.spec.ts b/src/app/core/auth/auth.effects.spec.ts
index e231857159..5aaced609e 100644
--- a/src/app/core/auth/auth.effects.spec.ts
+++ b/src/app/core/auth/auth.effects.spec.ts
@@ -235,7 +235,7 @@ describe('AuthEffects', () => {
}
});
- const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock) });
+ const expected = cold('--b-', { b: new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock.id) });
expect(authEffects.retrieveAuthenticatedEperson$).toBeObservable(expected);
});
diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts
index d153748fb9..717aaff01e 100644
--- a/src/app/core/auth/auth.effects.ts
+++ b/src/app/core/auth/auth.effects.ts
@@ -43,6 +43,7 @@ import {
RetrieveAuthMethodsSuccessAction,
RetrieveTokenAction
} from './auth.actions';
+import { hasValue } from '../../shared/empty.util';
@Injectable()
export class AuthEffects {
@@ -97,8 +98,15 @@ export class AuthEffects {
public retrieveAuthenticatedEperson$: Observable = this.actions$.pipe(
ofType(AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON),
switchMap((action: RetrieveAuthenticatedEpersonAction) => {
- return this.authService.retrieveAuthenticatedUserByHref(action.payload).pipe(
- map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user)),
+ const impersonatedUserID = this.authService.getImpersonateID();
+ let user$: Observable;
+ if (hasValue(impersonatedUserID)) {
+ user$ = this.authService.retrieveAuthenticatedUserById(impersonatedUserID);
+ } else {
+ user$ = this.authService.retrieveAuthenticatedUserByHref(action.payload);
+ }
+ return user$.pipe(
+ map((user: EPerson) => new RetrieveAuthenticatedEpersonSuccessAction(user.id)),
catchError((error) => observableOf(new RetrieveAuthenticatedEpersonErrorAction(error))));
})
);
@@ -193,6 +201,7 @@ export class AuthEffects {
.pipe(
ofType(AuthActionTypes.LOG_OUT),
switchMap(() => {
+ this.authService.stopImpersonating();
return this.authService.logout().pipe(
map((value) => new LogOutSuccessAction()),
catchError((error) => observableOf(new LogOutErrorAction(error)))
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.reducer.spec.ts b/src/app/core/auth/auth.reducer.spec.ts
index 1606ed9185..cf934a7f47 100644
--- a/src/app/core/auth/auth.reducer.spec.ts
+++ b/src/app/core/auth/auth.reducer.spec.ts
@@ -189,7 +189,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
const action = new LogOutAction();
@@ -206,7 +206,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
const action = new LogOutSuccessAction();
@@ -219,7 +219,7 @@ describe('authReducer', () => {
loading: false,
info: undefined,
refreshing: false,
- user: undefined
+ userId: undefined
};
expect(newState).toEqual(state);
});
@@ -232,7 +232,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
const action = new LogOutErrorAction(mockError);
@@ -244,7 +244,7 @@ describe('authReducer', () => {
error: 'Test error message',
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
expect(newState).toEqual(state);
});
@@ -258,7 +258,7 @@ describe('authReducer', () => {
loading: true,
info: undefined
};
- const action = new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock);
+ const action = new RetrieveAuthenticatedEpersonSuccessAction(EPersonMock.id);
const newState = authReducer(initialState, action);
state = {
authenticated: true,
@@ -267,7 +267,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
expect(newState).toEqual(state);
});
@@ -301,7 +301,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
const newTokenInfo = new AuthTokenInfo('Refreshed token');
const action = new RefreshTokenAction(newTokenInfo);
@@ -313,7 +313,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock,
+ userId: EPersonMock.id,
refreshing: true
};
expect(newState).toEqual(state);
@@ -327,7 +327,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock,
+ userId: EPersonMock.id,
refreshing: true
};
const newTokenInfo = new AuthTokenInfo('Refreshed token');
@@ -340,7 +340,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock,
+ userId: EPersonMock.id,
refreshing: false
};
expect(newState).toEqual(state);
@@ -354,7 +354,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock,
+ userId: EPersonMock.id,
refreshing: true
};
const action = new RefreshTokenErrorAction();
@@ -367,7 +367,7 @@ describe('authReducer', () => {
loading: false,
info: undefined,
refreshing: false,
- user: undefined
+ userId: undefined
};
expect(newState).toEqual(state);
});
@@ -380,7 +380,7 @@ describe('authReducer', () => {
error: undefined,
loading: false,
info: undefined,
- user: EPersonMock
+ userId: EPersonMock.id
};
state = {
@@ -390,7 +390,7 @@ describe('authReducer', () => {
loading: false,
error: undefined,
info: 'Message',
- user: undefined
+ userId: undefined
};
});
diff --git a/src/app/core/auth/auth.reducer.ts b/src/app/core/auth/auth.reducer.ts
index 19fd162d3f..16990b35a8 100644
--- a/src/app/core/auth/auth.reducer.ts
+++ b/src/app/core/auth/auth.reducer.ts
@@ -14,7 +14,6 @@ import {
SetRedirectUrlAction
} from './auth.actions';
// import models
-import { EPerson } from '../eperson/models/eperson.model';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { AuthMethod } from './models/auth.method';
import { AuthMethodType } from './models/auth.method-type';
@@ -49,8 +48,8 @@ export interface AuthState {
// true when refreshing token
refreshing?: boolean;
- // the authenticated user
- user?: EPerson;
+ // the authenticated user's id
+ userId?: string;
// all authentication Methods enabled at the backend
authMethods?: AuthMethod[];
@@ -112,7 +111,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
error: undefined,
loading: false,
info: undefined,
- user: (action as RetrieveAuthenticatedEpersonSuccessAction).payload
+ userId: (action as RetrieveAuthenticatedEpersonSuccessAction).payload
});
case AuthActionTypes.AUTHENTICATE_ERROR:
@@ -144,7 +143,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loading: false,
info: undefined,
refreshing: false,
- user: undefined
+ userId: undefined
});
case AuthActionTypes.REDIRECT_AUTHENTICATION_REQUIRED:
@@ -155,7 +154,7 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
loaded: false,
loading: false,
info: (action as RedirectWhenTokenExpiredAction as RedirectWhenAuthenticationIsRequiredAction).payload,
- user: undefined
+ userId: undefined
});
case AuthActionTypes.REGISTRATION:
diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts
index 89d8cdce4e..3b6fae4dd1 100644
--- a/src/app/core/auth/auth.service.spec.ts
+++ b/src/app/core/auth/auth.service.spec.ts
@@ -8,7 +8,7 @@ import { of as observableOf } from 'rxjs';
import { authReducer, AuthState } from './auth.reducer';
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
-import { AuthService } from './auth.service';
+import { AuthService, IMPERSONATING_COOKIE } from './auth.service';
import { RouterStub } from '../../shared/testing/router.stub';
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
@@ -332,5 +332,120 @@ describe('AuthService test', () => {
expect(routeServiceMock.getHistory).toHaveBeenCalled();
expect(routerStub.navigateByUrl).toHaveBeenCalledWith('/');
});
+
+ describe('impersonate', () => {
+ const userId = 'testUserId';
+
+ beforeEach(() => {
+ spyOn(authService, 'refreshAfterLogout');
+ authService.impersonate(userId);
+ });
+
+ it('should impersonate user', () => {
+ expect(storage.set).toHaveBeenCalledWith(IMPERSONATING_COOKIE, userId);
+ });
+
+ it('should call refreshAfterLogout', () => {
+ expect(authService.refreshAfterLogout).toHaveBeenCalled();
+ });
+ });
+
+ describe('stopImpersonating', () => {
+ beforeEach(() => {
+ authService.stopImpersonating();
+ });
+
+ it('should impersonate user', () => {
+ expect(storage.remove).toHaveBeenCalledWith(IMPERSONATING_COOKIE);
+ });
+ });
+
+ describe('stopImpersonatingAndRefresh', () => {
+ beforeEach(() => {
+ spyOn(authService, 'refreshAfterLogout');
+ authService.stopImpersonatingAndRefresh();
+ });
+
+ it('should impersonate user', () => {
+ expect(storage.remove).toHaveBeenCalledWith(IMPERSONATING_COOKIE);
+ });
+
+ it('should call refreshAfterLogout', () => {
+ expect(authService.refreshAfterLogout).toHaveBeenCalled();
+ });
+ });
+
+ describe('getImpersonateID', () => {
+ beforeEach(() => {
+ authService.getImpersonateID();
+ });
+
+ it('should impersonate user', () => {
+ expect(storage.get).toHaveBeenCalledWith(IMPERSONATING_COOKIE);
+ });
+ });
+
+ describe('isImpersonating', () => {
+ const userId = 'testUserId';
+ let result: boolean;
+
+ describe('when the cookie doesn\'t contain a value', () => {
+ beforeEach(() => {
+ result = authService.isImpersonating();
+ });
+
+ it('should return false', () => {
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('when the cookie contains a value', () => {
+ beforeEach(() => {
+ storage.get = jasmine.createSpy().and.returnValue(userId);
+ result = authService.isImpersonating();
+ });
+
+ it('should return true', () => {
+ expect(result).toBe(true);
+ });
+ });
+ });
+
+ describe('isImpersonatingUser', () => {
+ const userId = 'testUserId';
+ let result: boolean;
+
+ describe('when the cookie doesn\'t contain a value', () => {
+ beforeEach(() => {
+ result = authService.isImpersonatingUser(userId);
+ });
+
+ it('should return false', () => {
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('when the cookie contains the right value', () => {
+ beforeEach(() => {
+ storage.get = jasmine.createSpy().and.returnValue(userId);
+ result = authService.isImpersonatingUser(userId);
+ });
+
+ it('should return true', () => {
+ expect(result).toBe(true);
+ });
+ });
+
+ describe('when the cookie contains the wrong value', () => {
+ beforeEach(() => {
+ storage.get = jasmine.createSpy().and.returnValue('wrongValue');
+ result = authService.isImpersonatingUser(userId);
+ });
+
+ it('should return false', () => {
+ expect(result).toBe(false);
+ });
+ });
+ });
});
});
diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts
index 0f5c06bbc9..588d9e2675 100644
--- a/src/app/core/auth/auth.service.ts
+++ b/src/app/core/auth/auth.service.ts
@@ -4,7 +4,7 @@ import { HttpHeaders } from '@angular/common/http';
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
import { Observable, of as observableOf } from 'rxjs';
-import { distinctUntilChanged, filter, map, startWith, take, withLatestFrom } from 'rxjs/operators';
+import { distinctUntilChanged, filter, map, startWith, switchMap, take, withLatestFrom } from 'rxjs/operators';
import { RouterReducerState } from '@ngrx/router-store';
import { select, Store } from '@ngrx/store';
import { CookieAttributes } from 'js-cookie';
@@ -14,9 +14,15 @@ 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, hasValueOperator, isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
import { CookieService } from '../services/cookie.service';
-import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
+import {
+ getAuthenticatedUserId,
+ getAuthenticationToken,
+ getRedirectUrl,
+ isAuthenticated,
+ isTokenRefreshing
+} from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer';
import {
CheckAuthenticationTokenAction,
@@ -33,6 +39,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.
@@ -163,7 +170,7 @@ export class AuthService {
}
/**
- * Returns the authenticated user
+ * Returns the authenticated user by href
* @returns {User}
*/
public retrieveAuthenticatedUserByHref(userHref: string): Observable {
@@ -172,6 +179,29 @@ export class AuthService {
)
}
+ /**
+ * Returns the authenticated user by id
+ * @returns {User}
+ */
+ public retrieveAuthenticatedUserById(userId: string): Observable {
+ return this.epersonService.findById(userId).pipe(
+ getAllSucceededRemoteDataPayload()
+ )
+ }
+
+ /**
+ * Returns the authenticated user from the store
+ * @returns {User}
+ */
+ public getAuthenticatedUserFromStore(): Observable {
+ return this.store.pipe(
+ select(getAuthenticatedUserId),
+ hasValueOperator(),
+ switchMap((id: string) => this.epersonService.findById(id)),
+ getAllSucceededRemoteDataPayload()
+ )
+ }
+
/**
* Checks if token is present into browser storage and is valid.
*/
@@ -430,9 +460,9 @@ export class AuthService {
* Refresh route navigated
*/
public refreshAfterLogout() {
- this.router.navigate(['/home']);
- // Hard redirect to home page, so that all state is definitely lost
- this._window.nativeWindow.location.href = '/home';
+ // Hard redirect to the reload page with a unique number behind it
+ // so that all state is definitely lost
+ this._window.nativeWindow.location.href = `/reload/${new Date().getTime()}`;
}
/**
@@ -469,4 +499,51 @@ 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);
+ }
+
+ /**
+ * Stop impersonating EPerson and refresh the store/ui
+ */
+ stopImpersonatingAndRefresh() {
+ this.stopImpersonating();
+ 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/core/auth/selectors.ts b/src/app/core/auth/selectors.ts
index 4e51bc1fc9..173f82e810 100644
--- a/src/app/core/auth/selectors.ts
+++ b/src/app/core/auth/selectors.ts
@@ -8,7 +8,6 @@ import { createSelector } from '@ngrx/store';
*/
import { AuthState } from './auth.reducer';
import { AppState } from '../../app.reducer';
-import { EPerson } from '../eperson/models/eperson.model';
/**
* Returns the user state.
@@ -36,12 +35,11 @@ const _isAuthenticatedLoaded = (state: AuthState) => state.loaded;
/**
* Return the users state
- * NOTE: when state is REHYDRATED user object lose prototype so return always a new EPerson object
- * @function _getAuthenticatedUser
+ * @function _getAuthenticatedUserId
* @param {State} state
- * @returns {EPerson}
+ * @returns {string} User ID
*/
-const _getAuthenticatedUser = (state: AuthState) => Object.assign(new EPerson(), state.user);
+const _getAuthenticatedUserId = (state: AuthState) => state.userId;
/**
* Returns the authentication error.
@@ -119,13 +117,13 @@ const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
export const getAuthenticationMethods = createSelector(getAuthState, _getAuthenticationMethods);
/**
- * Returns the authenticated user
- * @function getAuthenticatedUser
+ * Returns the authenticated user id
+ * @function getAuthenticatedUserId
* @param {AuthState} state
* @param {any} props
- * @return {User}
+ * @return {string} User ID
*/
-export const getAuthenticatedUser = createSelector(getAuthState, _getAuthenticatedUser);
+export const getAuthenticatedUserId = createSelector(getAuthState, _getAuthenticatedUserId);
/**
* Returns the authentication error.
diff --git a/src/app/header/header.component.html b/src/app/header/header.component.html
index 58f7cb1ecf..5ce0cdb410 100644
--- a/src/app/header/header.component.html
+++ b/src/app/header/header.component.html
@@ -8,6 +8,7 @@
+