Browse Source

Restrict support for filtering to public access

pull/7753/head
Thomas Kaul 14 hours ago
parent
commit
613efb319d
  1. 10
      apps/api/src/app/access/access.controller.ts
  2. 13
      apps/api/src/app/access/access.service.ts
  3. 61
      apps/api/src/app/endpoints/mcp/mcp.controller.ts
  4. 4
      apps/api/src/services/impersonation/impersonation.service.ts
  5. 3
      libs/common/src/lib/types/impersonation-context.type.ts

10
apps/api/src/app/access/access.controller.ts

@ -116,7 +116,10 @@ export class AccessController {
type,
scopes: data.scopes
}),
settings: this.accessService.buildSettings(data.filters),
settings: this.accessService.buildSettings({
type,
filters: data.filters
}),
user: { connect: { id: this.request.user.id } }
});
} catch {
@ -204,7 +207,10 @@ export class AccessController {
scopes: data.scopes ?? originalAccess.scopes,
type: originalAccess.type
}),
settings: this.accessService.buildSettings(data.filters)
settings: this.accessService.buildSettings({
filters: data.filters,
type: originalAccess.type
})
},
where: { id }
});

13
apps/api/src/app/access/access.service.ts

@ -3,7 +3,7 @@ import { AccessSettings, Filter } from '@ghostfolio/common/interfaces';
import { AccessWithGranteeUser } from '@ghostfolio/common/types';
import { Injectable } from '@nestjs/common';
import { Access, Prisma } from '@prisma/client';
import { Access, AccessType, Prisma } from '@prisma/client';
import { isBefore, isToday, isValid } from 'date-fns';
@Injectable()
@ -41,8 +41,15 @@ export class AccessService {
});
}
public buildSettings(filters?: Filter[]) {
const settings: AccessSettings = filters?.length ? { filters } : {};
public buildSettings({
filters,
type
}: {
filters?: Filter[];
type: AccessType;
}) {
const settings: AccessSettings =
type === 'PUBLIC' && filters?.length ? { filters } : {};
return settings as Prisma.InputJsonValue;
}

61
apps/api/src/app/endpoints/mcp/mcp.controller.ts

@ -88,12 +88,7 @@ export class GhostfolioMcpController {
})
public async getActivities(
@Impersonation()
{
filters,
scopes: scopesOfAccess,
userId,
userSettings
}: ImpersonationContext,
{ scopes: scopesOfAccess, userId, userSettings }: ImpersonationContext,
@Payload()
{
activityTypes,
@ -113,65 +108,18 @@ export class GhostfolioMcpController {
}));
}
const filtersOfAccess = filters ?? [];
const filtersOfTool = this.apiService.buildFiltersFromQueryParams({
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAssetClasses: assetClasses?.join(','),
filterByDataSource: holding?.dataSource,
filterBySymbol: holding?.symbol
});
// A tool must never widen the access, hence a filter of the tool which
// the access does not permit gives no activity
const filtersOfToolOutsideAccess = filtersOfTool.filter(({ id, type }) => {
const filtersOfAccessOfType = filtersOfAccess.filter((filter) => {
return filter.type === type;
});
return (
filtersOfAccessOfType.length > 0 &&
!filtersOfAccessOfType.some((filter) => {
return filter.id === id;
})
);
});
if (filtersOfToolOutsideAccess.length > 0) {
const valuesOutsideAccess = filtersOfToolOutsideAccess
.map(({ id }) => {
return id;
})
.join(', ');
return {
content: [
{
text: `No activities found. The access does not permit these values of the parameters: ${valuesOutsideAccess}.`,
type: 'text' as const
}
]
};
}
const typesOfFiltersOfTool = new Set(
filtersOfTool.map(({ type }) => {
return type;
})
);
// The filters of a type are combined with a logical or, hence a filter of
// the tool replaces the filters of the access of the same type instead of
// joining them
const filtersOfAccessOutsideTool = filtersOfAccess.filter(({ type }) => {
return !typesOfFiltersOfTool.has(type);
});
const table = await this.aiService.getActivitiesTable({
endDate,
filters,
skip,
startDate,
userId,
filters: [...filtersOfAccessOutsideTool, ...filtersOfTool],
take: take ?? MCP_MAX_ACTIVITIES,
types: activityTypes,
userCurrency: userSettings.baseCurrency,
@ -194,10 +142,9 @@ export class GhostfolioMcpController {
name: 'get-portfolio'
})
public async getPortfolio(
@Impersonation() { filters, userId, userSettings }: ImpersonationContext
@Impersonation() { userId, userSettings }: ImpersonationContext
) {
const prompt = await this.aiService.getPrompt({
filters,
userId,
languageCode: DEFAULT_LANGUAGE_CODE,
mode: 'portfolio',

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

@ -3,7 +3,7 @@ import { SubscriptionService } from '@ghostfolio/api/app/subscription/subscripti
import { ConfigurationService } from '@ghostfolio/api/services/configuration/configuration.service';
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
import { AccessSettings, UserSettings } from '@ghostfolio/common/interfaces';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import {
getScopesOfAccess,
@ -65,11 +65,9 @@ export class ImpersonationService {
where: { id: impersonatedUserId }
});
const { filters } = (access?.settings ?? {}) as AccessSettings;
const settings = impersonatedUser?.settings?.settings as UserSettings;
return {
filters,
accessId: impersonationId,
authenticatedUserSubscription: user?.subscription,
isActive: true,

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

@ -1,4 +1,4 @@
import { Filter, UserSettings } from '@ghostfolio/common/interfaces';
import { UserSettings } from '@ghostfolio/common/interfaces';
import { UserWithSettings } from '@ghostfolio/common/types';
/**
@ -10,7 +10,6 @@ import { UserWithSettings } from '@ghostfolio/common/types';
export interface ImpersonationContext {
accessId?: string;
authenticatedUserSubscription?: UserWithSettings['subscription'];
filters?: Filter[];
isActive: boolean;
scopes: string[];
userId: string;

Loading…
Cancel
Save