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 5f87664a0..75e73a1f7 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 66e8f30a1..080630a60 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 80ad60dda..5236f4f2a 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 921d15fe7..1ff8a1184 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 a31a0d49a..e9afc18af 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 100007d42..ba918a78c 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 8538b3ca6..5f26b44c3 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 0145ed9a2..d6f44bdfc 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 1bd44395a..9e86daaaa 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 ca6d0bf95..85fc489ba 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 adeb27ef6..860148f38 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 33280b482..45c521d78 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 5875b441e..c4c123695 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 261aa1195..f66d5b518 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 47b4d0923..5a5775119 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 7ba6b4537..34d289efa 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 f88e0df9c..fbcdd4948 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 d0d446c31..0ae340fc7 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 093499398..9ee79aa5f 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,