Feature/extend support for impersonation mode (#1898)

* Support impersonation of all users for local development

* Update changelog
pull/1902/head
Thomas Kaul 1 year ago committed by GitHub
parent 672d8dfab2
commit f4c748f67a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Extended the support of the impersonation mode for local development
### Fixed
- Improved the holdings table by showing the cash position also when the filter contains the accounts, so that we can see the total allocation for that account

@ -87,10 +87,7 @@ export class AccountController {
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId
): Promise<Accounts> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
await this.impersonationService.validateImpersonationId(impersonationId);
return this.portfolioService.getAccountsWithAggregations({
userId: impersonationUserId || this.request.user.id,
@ -106,10 +103,7 @@ export class AccountController {
@Param('id') id: string
): Promise<AccountWithValue> {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
await this.impersonationService.validateImpersonationId(impersonationId);
const accountsWithAggregations =
await this.portfolioService.getAccountsWithAggregations({

@ -96,10 +96,7 @@ export class OrderController {
});
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
impersonationId,
this.request.user.id
);
await this.impersonationService.validateImpersonationId(impersonationId);
const userCurrency = this.request.user.Settings.settings.baseCurrency;
const activities = await this.orderService.getOrders({

@ -1827,10 +1827,7 @@ export class PortfolioService {
private async getUserId(aImpersonationId: string, aUserId: string) {
const impersonationUserId =
await this.impersonationService.validateImpersonationId(
aImpersonationId,
aUserId
);
await this.impersonationService.validateImpersonationId(aImpersonationId);
return impersonationUserId || aUserId;
}

@ -1,4 +1,5 @@
import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscription.service';
import { environment } from '@ghostfolio/api/environments/environment';
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
@ -196,6 +197,10 @@ export class UserService {
}
}
if (!environment.production && role === 'ADMIN') {
currentPermissions.push(permissions.impersonateAllUsers);
}
user.Account = sortBy(user.Account, (account) => {
return account.name;
});

@ -1,15 +1,35 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { Injectable } from '@nestjs/common';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { RequestWithUser } from '@ghostfolio/common/types';
import { Inject, Injectable } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
@Injectable()
export class ImpersonationService {
public constructor(private readonly prismaService: PrismaService) {}
public constructor(
private readonly prismaService: PrismaService,
@Inject(REQUEST) private readonly request: RequestWithUser
) {}
public async validateImpersonationId(aId = '', aUserId: string) {
public async validateImpersonationId(aId = '') {
const accessObject = await this.prismaService.access.findFirst({
where: { GranteeUser: { id: aUserId }, id: aId }
where: {
GranteeUser: { id: this.request.user.id },
id: aId
}
});
return accessObject?.userId;
if (accessObject?.userId) {
return accessObject?.userId;
} else if (
hasPermission(
this.request.user.permissions,
permissions.impersonateAllUsers
)
) {
return aId;
}
return null;
}
}

@ -1,5 +1,6 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { getDateFormatString, getEmojiFlag } from '@ghostfolio/common/helper';
import { AdminData, InfoItem, User } from '@ghostfolio/common/interfaces';
@ -21,6 +22,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
public defaultDateFormat: string;
public getEmojiFlag = getEmojiFlag;
public hasPermissionForSubscription: boolean;
public hasPermissionToImpersonateAllUsers: boolean;
public info: InfoItem;
public user: User;
public users: AdminData['users'];
@ -30,6 +32,7 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
) {
this.info = this.dataService.fetchInfo();
@ -48,6 +51,11 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
this.defaultDateFormat = getDateFormatString(
this.user.settings.locale
);
this.hasPermissionToImpersonateAllUsers = hasPermission(
this.user.permissions,
permissions.impersonateAllUsers
);
}
});
}
@ -88,6 +96,16 @@ export class AdminUsersComponent implements OnDestroy, OnInit {
}
}
public onImpersonateUser(aId: string) {
if (aId) {
this.impersonationStorageService.setId(aId);
} else {
this.impersonationStorageService.removeId();
}
window.location.reload();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();

@ -106,12 +106,20 @@
<button
class="mx-1 no-min-width px-2"
mat-button
[matMenuTriggerFor]="accountMenu"
[matMenuTriggerFor]="userMenu"
(click)="$event.stopPropagation()"
>
<ion-icon name="ellipsis-vertical"></ion-icon>
</button>
<mat-menu #accountMenu="matMenu" xPosition="before">
<mat-menu #userMenu="matMenu" xPosition="before">
<button
*ngIf="hasPermissionToImpersonateAllUsers"
mat-menu-item
(click)="onImpersonateUser(userItem.id)"
>
<ion-icon class="mr-2" name="contract-outline"></ion-icon>
<span i18n>Impersonate</span>
</button>
<button
mat-menu-item
[disabled]="userItem.id === user?.id"

@ -20,6 +20,7 @@ export const permissions = {
enableSubscription: 'enableSubscription',
enableSubscriptionInterstitial: 'enableSubscriptionInterstitial',
enableSystemMessage: 'enableSystemMessage',
impersonateAllUsers: 'impersonateAllUsers',
reportDataGlitch: 'reportDataGlitch',
toggleReadOnlyMode: 'toggleReadOnlyMode',
updateAccount: 'updateAccount',

Loading…
Cancel
Save