Browse Source

Refactor impersonation mode into guard

pull/7597/head
Thomas Kaul 3 weeks ago
parent
commit
a9bffe5a50
  1. 2
      apps/api/src/app/portfolio/portfolio.controller.ts
  2. 5
      apps/api/src/app/user/user.controller.ts
  3. 8
      apps/api/src/decorators/allow-during-impersonation.decorator.ts
  4. 6
      apps/api/src/decorators/impersonation.decorator.ts
  5. 10
      apps/api/src/guards/impersonation-write.guard.ts
  6. 2
      apps/api/src/services/impersonation/impersonation.service.ts
  7. 8
      libs/common/src/lib/types/impersonation-context.type.ts

2
apps/api/src/app/portfolio/portfolio.controller.ts

@ -652,8 +652,6 @@ export class PortfolioController {
const report = await this.portfolioService.getReport({ userId }); const report = await this.portfolioService.getReport({ userId });
if ( if (
// The evaluations of the rules interpolate absolute values, hence they
// are withheld from a restricted view
hasReadRestrictedAccessPermission({ hasReadRestrictedAccessPermission({
accesses: this.request.user?.accessesGet, accesses: this.request.user?.accessesGet,
impersonationId: accessId impersonationId: accessId

5
apps/api/src/app/user/user.controller.ts

@ -181,8 +181,9 @@ export class UserController {
return !isUserSettingOfAuthenticatedUser(key); return !isUserSettingOfAuthenticatedUser(key);
}) })
) { ) {
// While impersonating, the presented settings of the impersonated user // While impersonating, only the settings which stay with the
// must not be written back to the authenticated user // authenticated user can be changed, as the update is always written
// back to the authenticated user
throw new HttpException( throw new HttpException(
getReasonPhrase(StatusCodes.FORBIDDEN), getReasonPhrase(StatusCodes.FORBIDDEN),
StatusCodes.FORBIDDEN StatusCodes.FORBIDDEN

8
apps/api/src/decorators/allow-during-impersonation.decorator.ts

@ -2,9 +2,11 @@ import { SetMetadata } from '@nestjs/common';
export const ALLOW_DURING_IMPERSONATION_KEY = 'allow_during_impersonation'; export const ALLOW_DURING_IMPERSONATION_KEY = 'allow_during_impersonation';
// Marks a controller or a route which modifies data of the authenticated user /**
// instead of data of the impersonated user, hence it stays available while an * Marks a controller or a route which modifies data of the authenticated user
// impersonation is active * instead of data of the impersonated user, hence it stays available while an
* impersonation is active
*/
export function AllowDuringImpersonation() { export function AllowDuringImpersonation() {
return SetMetadata(ALLOW_DURING_IMPERSONATION_KEY, true); return SetMetadata(ALLOW_DURING_IMPERSONATION_KEY, true);
} }

6
apps/api/src/decorators/impersonation.decorator.ts

@ -5,8 +5,10 @@ import type {
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { createParamDecorator, ExecutionContext } from '@nestjs/common';
// Provides the impersonation context of the request, which requires the /**
// ImpersonationGuard to be applied to the route * Provides the impersonation context of the request, which requires the
* ImpersonationGuard to be applied to the route
*/
export const Impersonation = createParamDecorator( export const Impersonation = createParamDecorator(
(_data: unknown, context: ExecutionContext): ImpersonationContext => { (_data: unknown, context: ExecutionContext): ImpersonationContext => {
const { impersonation, user } = context const { impersonation, user } = context

10
apps/api/src/guards/impersonation-write.guard.ts

@ -10,10 +10,12 @@ import {
import { Reflector } from '@nestjs/core'; import { Reflector } from '@nestjs/core';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
// Denies modifying requests while an impersonation is active, so that data of /**
// the authenticated user cannot be changed from a view presenting data of the * Blocks write requests while an impersonation is active, so that data of the
// impersonated user. The header is evaluated instead of the resolved context to * authenticated user cannot be changed from a view presenting data of the
// fail closed, also for an identifier which cannot be resolved. * impersonated user. The header is evaluated instead of the resolved context to
* fail closed, also for an identifier which cannot be resolved.
*/
@Injectable() @Injectable()
export class ImpersonationWriteGuard implements CanActivate { export class ImpersonationWriteGuard implements CanActivate {
public constructor(private readonly reflector: Reflector) {} public constructor(private readonly reflector: Reflector) {}

2
apps/api/src/services/impersonation/impersonation.service.ts

@ -38,8 +38,8 @@ export class ImpersonationService {
}); });
return { return {
isActive: true,
accessId: impersonationId, accessId: impersonationId,
isActive: true,
userId: impersonatedUserId, userId: impersonatedUserId,
userSettings: { userSettings: {
...((settings?.settings ?? {}) as UserSettings), ...((settings?.settings ?? {}) as UserSettings),

8
libs/common/src/lib/types/impersonation-context.type.ts

@ -1,8 +1,10 @@
import { UserSettings } from '@ghostfolio/common/interfaces'; import { UserSettings } from '@ghostfolio/common/interfaces';
// Describes whose data a request presents. The user id and the settings belong /**
// to the impersonated user while an impersonation is active and to the * Describes whose data a request presents. The user id and the settings belong
// authenticated user otherwise, so a handler can use them unconditionally. * to the impersonated user while an impersonation is active and to the
* authenticated user otherwise, so a handler can use them unconditionally.
*/
export interface ImpersonationContext { export interface ImpersonationContext {
accessId?: string; accessId?: string;
isActive: boolean; isActive: boolean;

Loading…
Cancel
Save