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 5f87664a0d5..75e73a1f7a9 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 66e8f30a1a4..080630a60be 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 80ad60dda06..5236f4f2a8b 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 921d15fe755..1ff8a118468 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 a31a0d49a16..e9afc18afd2 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 100007d4292..ba918a78c93 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 8538b3ca6b1..5f26b44c33d 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 0145ed9a277..d6f44bdfc53 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 1bd44395a4f..9e86daaaa31 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 ca6d0bf955d..85fc489bad3 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 adeb27ef6e1..860148f3805 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 33280b48224..45c521d7880 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 5875b441e66..c4c123695b8 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 261aa119596..f66d5b5185b 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 47b4d09237e..5a577511963 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 7ba6b4537d6..34d289efa16 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 f88e0df9cd2..fbcdd494864 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 d0d446c3190..0ae340fc73e 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 0934993984c..9ee79aa5f39 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,