From e1a7fa3e5d01eafec2e32cb934a48513896246b6 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:37:23 +0200 Subject: [PATCH] [permissions] Override workspaceDatasource.createQueryBuilder (#12415) In the frame of https://github.com/twentyhq/core-team-issues/issues/924 - Rename dataSource -> workspaceDataSource when relevant to ease understandability - override workspaceDataSource.createQueryBuilder, because we don't want developers to use it directly since it does not run permission checks at this level. Indeed, we cannot do so because 1) datasources are shared between roles so we would need to re-think its implementation to make that possible, while for now we never call workspaceDatasource.createQueryBuilder in our codebase 2) workspaceEntityManager.createQueryBuilder, that we have overriden with permission checks, then performs a call to workspaceDataSource.createQueryBuilder so that would make two permission checks. --- .../process-nested-relations-v2.helper.ts | 14 ++-- .../process-nested-relations.helper.ts | 8 +-- .../interfaces/base-resolver-service.ts | 4 +- ...phql-query-create-many-resolver.service.ts | 2 +- ...aphql-query-create-one-resolver.service.ts | 2 +- ...phql-query-delete-many-resolver.service.ts | 2 +- ...aphql-query-delete-one-resolver.service.ts | 2 +- ...hql-query-destroy-many-resolver.service.ts | 2 +- ...phql-query-destroy-one-resolver.service.ts | 2 +- ...raphql-query-find-many-resolver.service.ts | 2 +- ...graphql-query-find-one-resolver.service.ts | 2 +- ...hql-query-restore-many-resolver.service.ts | 2 +- ...phql-query-restore-one-resolver.service.ts | 2 +- ...phql-query-update-many-resolver.service.ts | 2 +- ...aphql-query-update-one-resolver.service.ts | 2 +- .../core/interfaces/rest-api-base.handler.ts | 8 +-- .../datasource/workspace.datasource.ts | 70 +++++++++++++++++++ .../workspace-entity-manager.ts | 20 ++++-- .../workspace-select-query-builder.ts | 18 ++++- 19 files changed, 129 insertions(+), 37 deletions(-) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts index 5f87664a0d..75e73a1f7a 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper.ts @@ -37,7 +37,7 @@ export class ProcessNestedRelationsV2Helper { aggregate = {}, limit, authContext, - dataSource, + workspaceDataSource, roleId, shouldBypassPermissionChecks, }: { @@ -50,7 +50,7 @@ export class ProcessNestedRelationsV2Helper { aggregate?: Record; limit: number; authContext: AuthContext; - dataSource: WorkspaceDataSource; + workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; roleId?: string; }): Promise { @@ -66,7 +66,7 @@ export class ProcessNestedRelationsV2Helper { aggregate, limit, authContext, - dataSource, + workspaceDataSource, shouldBypassPermissionChecks, roleId, }), @@ -85,7 +85,7 @@ export class ProcessNestedRelationsV2Helper { aggregate, limit, authContext, - dataSource, + workspaceDataSource, shouldBypassPermissionChecks, roleId, }: { @@ -99,7 +99,7 @@ export class ProcessNestedRelationsV2Helper { aggregate: Record; limit: number; authContext: AuthContext; - dataSource: WorkspaceDataSource; + workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; roleId?: string; }): Promise { @@ -131,7 +131,7 @@ export class ProcessNestedRelationsV2Helper { sourceFieldName, }); - const targetObjectRepository = dataSource.getRepository( + const targetObjectRepository = workspaceDataSource.getRepository( targetObjectMetadata.nameSingular, shouldBypassPermissionChecks, roleId, @@ -203,7 +203,7 @@ export class ProcessNestedRelationsV2Helper { aggregate, limit, authContext, - dataSource, + workspaceDataSource, shouldBypassPermissionChecks, roleId, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts index 66e8f30a1a..080630a60b 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper.ts @@ -4,7 +4,6 @@ import { FindOptionsRelations, ObjectLiteral } from 'typeorm'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper'; import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper'; import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; @@ -16,7 +15,6 @@ import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace. export class ProcessNestedRelationsHelper { constructor( private readonly processNestedRelationsV2Helper: ProcessNestedRelationsV2Helper, - private readonly processAggregateHelper: ProcessAggregateHelper, ) {} public async processNestedRelations({ @@ -28,7 +26,7 @@ export class ProcessNestedRelationsHelper { aggregate = {}, limit, authContext, - dataSource, + workspaceDataSource, shouldBypassPermissionChecks, roleId, }: { @@ -41,7 +39,7 @@ export class ProcessNestedRelationsHelper { aggregate?: Record; limit: number; authContext: AuthContext; - dataSource: WorkspaceDataSource; + workspaceDataSource: WorkspaceDataSource; shouldBypassPermissionChecks: boolean; roleId?: string; }): Promise { @@ -54,7 +52,7 @@ export class ProcessNestedRelationsHelper { aggregate, limit, authContext, - dataSource, + workspaceDataSource, shouldBypassPermissionChecks, roleId, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts index 80ad60dda0..5236f4f2a8 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -41,7 +41,7 @@ import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global. export type GraphqlQueryResolverExecutionArgs = { args: Input; options: WorkspaceQueryRunnerOptions; - dataSource: WorkspaceDataSource; + workspaceDataSource: WorkspaceDataSource; repository: WorkspaceRepository; graphqlQueryParser: GraphqlQueryParser; graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult; @@ -152,7 +152,7 @@ export abstract class GraphqlQueryBaseResolverService< const graphqlQueryResolverExecutionArgs = { args: computedArgs, options, - dataSource: workspaceDataSource, + workspaceDataSource, repository, graphqlQueryParser, graphqlQuerySelectedFieldsResult, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts index 921d15fe75..1ff8a11846 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts @@ -415,7 +415,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext: executionArgs.options.authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts index a31a0d49a1..e9afc18afd 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-one-resolver.service.ts @@ -67,7 +67,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts index 100007d429..ba918a78c9 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-many-resolver.service.ts @@ -69,7 +69,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts index 8538b3ca6b..5f26b44c33 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-delete-one-resolver.service.ts @@ -71,7 +71,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts index 0145ed9a27..d6f44bdfc5 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-many-resolver.service.ts @@ -67,7 +67,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 1bd44395a4..9e86daaaa3 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -67,7 +67,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts index ca6d0bf955..85fc489bad 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-many-resolver.service.ts @@ -153,7 +153,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts index adeb27ef6e..860148f380 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service.ts @@ -77,7 +77,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts index 33280b4822..45c521d788 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service.ts @@ -69,7 +69,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts index 5875b441e6..c4c123695b 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service.ts @@ -71,7 +71,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 261aa11959..f66d5b5185 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -108,7 +108,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts index 47b4d09237..5a57751196 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service.ts @@ -100,7 +100,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv relations: executionArgs.graphqlQuerySelectedFieldsResult.relations, limit: QUERY_MAX_RECORDS, authContext, - dataSource: executionArgs.dataSource, + workspaceDataSource: executionArgs.workspaceDataSource, roleId, shouldBypassPermissionChecks: executionArgs.isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts b/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts index 7ba6b4537d..34d289efa1 100644 --- a/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts +++ b/packages/twenty-server/src/engine/api/rest/core/interfaces/rest-api-base.handler.ts @@ -104,7 +104,7 @@ export abstract class RestApiBaseHandler { throw new BadRequestException('Workspace not found'); } - const dataSource = + const workspaceDataSource = await this.twentyORMGlobalManager.getDataSourceForWorkspace({ workspaceId: workspace.id, shouldFailIfMetadataNotFound: false, @@ -125,7 +125,7 @@ export abstract class RestApiBaseHandler { ); } - const shouldBypassPermissionChecks = !!apiKey; + const shouldBypassPermissionChecks = isDefined(apiKey); const roleId = await this.workspacePermissionsCacheService.getRoleIdFromUserWorkspaceId({ @@ -133,7 +133,7 @@ export abstract class RestApiBaseHandler { userWorkspaceId, }); - const repository = dataSource.getRepository( + const repository = workspaceDataSource.getRepository( objectMetadataNameSingular, shouldBypassPermissionChecks, roleId, @@ -142,7 +142,7 @@ export abstract class RestApiBaseHandler { return { objectMetadata, repository, - dataSource, + workspaceDataSource, objectMetadataItemWithFieldsMaps, }; } diff --git a/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts b/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts index f88e0df9cd..fbcdd49486 100644 --- a/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts +++ b/packages/twenty-server/src/engine/twenty-orm/datasource/workspace.datasource.ts @@ -1,3 +1,4 @@ +import { isDefined } from 'class-validator'; import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types'; import { DataSource, @@ -6,11 +7,13 @@ import { ObjectLiteral, QueryRunner, ReplicationMode, + SelectQueryBuilder, } from 'typeorm'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { PermissionsException, PermissionsExceptionCode, @@ -19,6 +22,10 @@ import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/wor import { WorkspaceQueryRunner } from 'src/engine/twenty-orm/query-runner/workspace-query-runner'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; +type CreateQueryBuilderOptions = { + calledByWorkspaceEntityManager?: boolean; +}; + export class WorkspaceDataSource extends DataSource { readonly internalContext: WorkspaceInternalContext; readonly manager: WorkspaceEntityManager; @@ -83,6 +90,69 @@ export class WorkspaceDataSource extends DataSource { return queryRunner as any as WorkspaceQueryRunner; } + override createQueryBuilder( + entityClass: EntityTarget, + alias: string, + queryRunner?: QueryRunner, + options?: CreateQueryBuilderOptions, + ): SelectQueryBuilder; + + override createQueryBuilder( + queryRunner?: QueryRunner, + options?: CreateQueryBuilderOptions, // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): SelectQueryBuilder; + + // Only callable from workspaceEntityManager to guarantee a permission check was run + override createQueryBuilder( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + queryRunnerOrEntityClass?: QueryRunner | EntityTarget, + aliasOrOptions?: string | CreateQueryBuilderOptions, + queryRunner?: QueryRunner, + options?: CreateQueryBuilderOptions, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): SelectQueryBuilder { + let calledByWorkspaceEntityManager; + + const isPermissionsV2Enabled = + this.featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; + + const isCalledWithEntityTarget = + isDefined(aliasOrOptions) && typeof aliasOrOptions === 'string'; + + if (isPermissionsV2Enabled) { + if (isCalledWithEntityTarget) { + calledByWorkspaceEntityManager = + options?.calledByWorkspaceEntityManager; + } else { + calledByWorkspaceEntityManager = ( + aliasOrOptions as CreateQueryBuilderOptions + )?.calledByWorkspaceEntityManager; + } + + if (!(calledByWorkspaceEntityManager === true)) { + throw new PermissionsException( + 'Method not allowed because permissions are not implemented at datasource level.', + PermissionsExceptionCode.METHOD_NOT_ALLOWED, + ); + } + } + + if (isCalledWithEntityTarget) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const entityClass = queryRunnerOrEntityClass as EntityTarget; + + return super.createQueryBuilder( + entityClass, + aliasOrOptions as string, + queryRunner, + ); + } else { + const queryRunner = queryRunnerOrEntityClass as QueryRunner; + + return super.createQueryBuilder(queryRunner); + } + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any override query( query: string, diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts index d0d446c319..0ae340fc73 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts @@ -145,14 +145,20 @@ export class WorkspaceEntityManager extends EntityManager { let queryBuilder: SelectQueryBuilder; if (alias) { - queryBuilder = super.createQueryBuilder( + queryBuilder = this.connection.createQueryBuilder( entityClassOrQueryRunner as EntityTarget, alias as string, queryRunner as QueryRunner | undefined, + { + calledByWorkspaceEntityManager: true, + }, ); } else { - queryBuilder = super.createQueryBuilder( + queryBuilder = this.connection.createQueryBuilder( entityClassOrQueryRunner as QueryRunner, + { + calledByWorkspaceEntityManager: true, + }, ); } @@ -912,7 +918,10 @@ export class WorkspaceEntityManager extends EntityManager { permissionOptions, ) .setFindOptions(options || {}) - .getExists(); + .select('1') + .limit(1) + .getRawOne() + .then((result) => isDefined(result)); } override existsBy( @@ -929,7 +938,10 @@ export class WorkspaceEntityManager extends EntityManager { permissionOptions, ) .setFindOptions({ where }) - .getExists(); + .select('1') + .limit(1) + .getRawOne() + .then((result) => isDefined(result)); } override count( diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts index 0934993984..9ee79aa5f3 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts @@ -4,6 +4,10 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; +import { + PermissionsException, + PermissionsExceptionCode, +} from 'src/engine/metadata-modules/permissions/permissions.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; @@ -83,9 +87,10 @@ export class WorkspaceSelectQueryBuilder< } override getExists(): Promise { - this.validatePermissions(); - - return super.getExists(); + throw new PermissionsException( + 'getExists is not supported because it calls dataSource.createQueryBuilder()', + PermissionsExceptionCode.METHOD_NOT_ALLOWED, + ); } override getManyAndCount(): Promise<[T[], number]> { @@ -148,6 +153,13 @@ export class WorkspaceSelectQueryBuilder< ); } + override executeExistsQuery(): Promise { + throw new PermissionsException( + 'executeExistsQuery is not supported because it calls dataSource.createQueryBuilder()', + PermissionsExceptionCode.METHOD_NOT_ALLOWED, + ); + } + private validatePermissions(): void { validateQueryIsPermittedOrThrow( this.expressionMap,