70373: Login as EPerson intermediate commit

This commit is contained in:
Kristof De Langhe
2020-04-10 17:41:39 +02:00
parent ac84b3f9c0
commit 638793ca5e
6 changed files with 118 additions and 5 deletions

View File

@@ -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.title": "DSpace Angular :: EPeople",
"admin.access-control.epeople.head": "EPeople", "admin.access-control.epeople.head": "EPeople",

View File

@@ -14,6 +14,18 @@
[formLayout]="formLayout" [formLayout]="formLayout"
(cancel)="onCancel()" (cancel)="onCancel()"
(submitForm)="onSubmit()"> (submitForm)="onSubmit()">
<button class="btn btn-light" [disabled]="!(canReset$ | async)">
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
</button>
<button class="btn btn-light" [disabled]="!(canDelete$ | async)">
<i class="fa fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
</button>
<button *ngIf="!isImpersonated" class="btn btn-light" [disabled]="!(canImpersonate$ | async)" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
</button>
<button *ngIf="isImpersonated" class="btn btn-light" (click)="stopImpersonating()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
</button>
</ds-form> </ds-form>
<div *ngIf="epersonService.getActiveEPerson() | async"> <div *ngIf="epersonService.getActiveEPerson() | async">

View File

@@ -7,7 +7,7 @@ import {
DynamicInputModel DynamicInputModel
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { TranslateService } from '@ngx-translate/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 { Observable } from 'rxjs/internal/Observable';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { RestResponse } from '../../../../core/cache/response.models'; 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 { FormBuilderService } from '../../../../shared/form/builder/form-builder.service';
import { NotificationsService } from '../../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../../shared/notifications/notifications.service';
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-eperson-form', selector: 'ds-eperson-form',
@@ -105,6 +106,24 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/ */
@Output() cancelForm: EventEmitter<any> = new EventEmitter(); @Output() cancelForm: EventEmitter<any> = 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<boolean> = 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<boolean> = 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<boolean> = of(true);
/** /**
* List of subscriptions * List of subscriptions
*/ */
@@ -129,13 +148,20 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
*/ */
epersonInitial: EPerson; epersonInitial: EPerson;
/**
* Whether or not this EPerson is currently being impersonated
*/
isImpersonated = false;
constructor(public epersonService: EPersonDataService, constructor(public epersonService: EPersonDataService,
public groupsDataService: GroupDataService, public groupsDataService: GroupDataService,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private translateService: TranslateService, private translateService: TranslateService,
private notificationsService: NotificationsService,) { private notificationsService: NotificationsService,
private authService: AuthService) {
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
this.epersonInitial = 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 * Cancel the current edit when component is destroyed & unsub all subscriptions
*/ */

View File

@@ -18,7 +18,7 @@ import { AppState } from '../../app.reducer';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo } from './models/auth-token-info.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 { RedirectWhenTokenExpiredAction, RefreshTokenAction } from './auth.actions';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
@@ -235,8 +235,16 @@ export class AuthInterceptor implements HttpInterceptor {
}); });
// Get the auth header from the service. // Get the auth header from the service.
authorization = authService.buildAuthHeader(token); 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. // Clone the request to add the new header.
newReq = req.clone({ headers: req.headers.set('authorization', authorization) }); newReq = req.clone({ headers: newHeaders });
} else { } else {
newReq = req.clone(); newReq = req.clone();
} }

View File

@@ -14,7 +14,7 @@ import { AuthRequestService } from './auth-request.service';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service'; import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from './models/auth-status.model'; import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.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 { CookieService } from '../services/cookie.service';
import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors'; import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer'; import { AppState, routerStateSelector } from '../../app.reducer';
@@ -33,6 +33,7 @@ import { AuthMethod } from './models/auth.method';
export const LOGIN_ROUTE = '/login'; export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout'; export const LOGOUT_ROUTE = '/logout';
export const REDIRECT_COOKIE = 'dsRedirectUrl'; export const REDIRECT_COOKIE = 'dsRedirectUrl';
export const IMPERSONATING_COOKIE = 'dsImpersonatingEPerson';
/** /**
* The auth service. * The auth service.
@@ -469,4 +470,44 @@ export class AuthService {
this.storage.remove(REDIRECT_COOKIE); 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;
}
} }

View File

@@ -45,6 +45,8 @@
</ds-dynamic-form> </ds-dynamic-form>
<ng-content></ng-content>
<div *ngIf="displaySubmit"> <div *ngIf="displaySubmit">
<hr> <hr>
<div class="form-group row"> <div class="form-group row">