From 4ae375308fc87f6276e55f16928b62d2195a2cb4 Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 9 Feb 2026 16:52:58 +0100 Subject: [PATCH] Revert "Releasing RLS" This reverts commit c01853c349627f6524052d7e15b05553a69ba3f9. --- .../src/generated-metadata/graphql.ts | 1 + .../twenty-front/src/generated/graphql.ts | 1 + ...gsRolePermissionsObjectLevelObjectForm.tsx | 18 ++++++++++----- .../constants/public-feature-flag.const.ts | 9 ++++++++ .../enums/feature-flag-key.enum.ts | 1 + .../workspace-entity-manager.spec.ts | 2 ++ .../workspace-insert-query-builder.ts | 9 ++++++++ .../workspace-select-query-builder.ts | 23 +++++++++++++------ .../workspace-update-query-builder.ts | 17 ++++++++++++++ ...ly-row-level-permission-predicates.util.ts | 10 ++++++++ .../core/utils/seed-feature-flags.util.ts | 5 ++++ ...ssion-predicate-upsert.integration-spec.ts | 14 +++++++++++ ...ssion-predicate-upsert.integration-spec.ts | 14 +++++++++++ 13 files changed, 111 insertions(+), 13 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 19e95e9607c..1901f460762 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1501,6 +1501,7 @@ export enum FeatureFlagKey { IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED', + IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED', IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED', IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED', IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index e95f7527b95..c319fac5475 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1473,6 +1473,7 @@ export enum FeatureFlagKey { IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED', IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED', IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED', + IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED', IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED', IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED', IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx index 87bc4f2744c..360dbd01a65 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/object-level-permissions/object-form/components/SettingsRolePermissionsObjectLevelObjectForm.tsx @@ -17,7 +17,8 @@ import { Button } from 'twenty-ui/input'; import { type BillingEntitlement, BillingEntitlementKey, - useFindOneAgentQuery + FeatureFlagKey, + useFindOneAgentQuery, } from '~/generated-metadata/graphql'; type SettingsRolePermissionsObjectLevelObjectFormProps = { @@ -58,6 +59,9 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ entitlement.value === true, ) ?? false; + const isRowLevelPermissionPredicatesEnabled = + featureFlagsMap[FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED]; + const objectMetadataItem = objectMetadata.objectMetadataItem; const objectLabelSingular = objectMetadataItem.labelSingular; @@ -160,11 +164,13 @@ export const SettingsRolePermissionsObjectLevelObjectForm = ({ objectMetadataItem={objectMetadataItem} roleId={roleId} /> - + {isRowLevelPermissionPredicatesEnabled && ( + + )} ); diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts index 6f19c91f136..a92a7143ff0 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts @@ -20,6 +20,15 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ 'Enable many-to-many relations through junction tables configuration', }, }, + { + key: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + metadata: { + label: 'Row Level Permissions', + description: 'Enable row level permission', + imagePath: + 'https://twenty.com/images/lab/is-row-level-permission-predicates-enabled.png', + }, + }, { key: FeatureFlagKey.IS_SSE_DB_EVENTS_ENABLED, metadata: { diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index cb11410b3b0..7f05938ef51 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -15,6 +15,7 @@ export enum FeatureFlagKey { IS_NOTE_TARGET_MIGRATED = 'IS_NOTE_TARGET_MIGRATED', IS_TASK_TARGET_MIGRATED = 'IS_TASK_TARGET_MIGRATED', IS_FILES_FIELD_MIGRATED = 'IS_FILES_FIELD_MIGRATED', + IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED', IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 37ef204bf8f..fcc22ca82f8 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -235,6 +235,7 @@ describe('WorkspaceEntityManager', () => { IS_TASK_TARGET_MIGRATED: false, IS_TIMELINE_ACTIVITY_MIGRATED: false, IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false, + IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false, IS_JUNCTION_RELATIONS_ENABLED: false, IS_SSE_DB_EVENTS_ENABLED: false, IS_COMMAND_MENU_ITEM_ENABLED: false, @@ -268,6 +269,7 @@ describe('WorkspaceEntityManager', () => { IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_ENABLED: false, IS_DASHBOARD_V2_ENABLED: false, + IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false, }, permissionsPerRoleId: {}, eventEmitterService: mockInternalContext.eventEmitterService, diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts index cdb78a30a87..08e7e67aa01 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts @@ -12,6 +12,7 @@ import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type'; import { type RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type'; import { type RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type'; @@ -317,6 +318,14 @@ export class WorkspaceInsertQueryBuilder< } private validateRLSPredicatesForInsert(): void { + if ( + this.featureFlagMap[ + FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED + ] !== true + ) { + return; + } + const mainAliasTarget = this.getMainAliasTarget(); const objectMetadata = getObjectMetadataFromEntityTarget( mainAliasTarget, 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 6c7b851c7d2..fc2fa206fee 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 @@ -1,8 +1,8 @@ import { type ObjectsPermissions } from 'twenty-shared/types'; import { - type EntityTarget, - type ObjectLiteral, - SelectQueryBuilder, + type EntityTarget, + type ObjectLiteral, + SelectQueryBuilder, } from 'typeorm'; import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @@ -10,14 +10,15 @@ import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interf import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { - PermissionsException, - PermissionsExceptionCode, + PermissionsException, + PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { - TwentyORMException, - TwentyORMExceptionCode, + TwentyORMException, + TwentyORMExceptionCode, } from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; @@ -357,6 +358,14 @@ export class WorkspaceSelectQueryBuilder< } private applyRowLevelPermissionPredicates(): void { + if ( + this.featureFlagMap[ + FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED + ] !== true + ) { + return; + } + if (this.shouldBypassPermissionChecks) { return; } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts index cb3c4d1d80e..3d6a9ae0124 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts @@ -16,6 +16,7 @@ import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/ import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type'; import { type RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type'; import { type RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type'; @@ -615,6 +616,14 @@ export class WorkspaceUpdateQueryBuilder< } private applyRowLevelPermissionPredicates(): void { + if ( + this.featureFlagMap[ + FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED + ] !== true + ) { + return; + } + if (this.shouldBypassPermissionChecks) { return; } @@ -640,6 +649,14 @@ export class WorkspaceUpdateQueryBuilder< }: { updatedRecords: T[]; }): void { + if ( + this.featureFlagMap[ + FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED + ] !== true + ) { + return; + } + const mainAliasTarget = this.getMainAliasTarget(); const objectMetadata = getObjectMetadataFromEntityTarget( mainAliasTarget, diff --git a/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts b/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts index 1ff3370f576..b8251f97349 100644 --- a/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts +++ b/packages/twenty-server/src/engine/twenty-orm/utils/apply-row-level-permission-predicates.util.ts @@ -12,6 +12,7 @@ import { type WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/ import { GraphqlQueryFilterFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser'; import { type AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; import { type WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { buildRowLevelPermissionRecordFilter } from 'src/engine/twenty-orm/utils/build-row-level-permission-record-filter.util'; @@ -29,7 +30,16 @@ export const applyRowLevelPermissionPredicates = ({ objectMetadata, internalContext, authContext, + featureFlagMap, }: ApplyRowLevelPermissionPredicatesArgs): void => { + if ( + featureFlagMap[ + FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED + ] !== true + ) { + return; + } + const roleId = authContext.userWorkspaceId ? internalContext.userWorkspaceRoleMap[authContext.userWorkspaceId] : undefined; diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index 5495ff5f131..32085bc033e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -76,6 +76,11 @@ export const seedFeatureFlags = async ({ workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + workspaceId: workspaceId, + value: true, + }, { key: FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED, workspaceId: workspaceId, diff --git a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/failing-row-level-permission-predicate-upsert.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/failing-row-level-permission-predicate-upsert.integration-spec.ts index 20b2e704853..accb8cc0499 100644 --- a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/failing-row-level-permission-predicate-upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/failing-row-level-permission-predicate-upsert.integration-spec.ts @@ -3,6 +3,7 @@ import { findManyObjectMetadata } from 'test/integration/metadata/suites/object- import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; import { upsertRowLevelPermissionPredicates } from 'test/integration/metadata/suites/row-level-permission-predicate/utils/upsert-row-level-permission-predicates.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { eachTestingContextFilter, @@ -14,6 +15,7 @@ import { } from 'twenty-shared/types'; import { v4 } from 'uuid'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type UpsertRowLevelPermissionPredicatesInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; type TestSetup = { @@ -161,6 +163,12 @@ describe('Row Level Permission Predicate upsert should fail', () => { let companyNameFieldMetadataId: string; beforeAll(async () => { + await updateFeatureFlag({ + featureFlag: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + value: true, + expectToFail: false, + }); + const { objects } = await findManyObjectMetadata({ expectToFail: false, input: { @@ -217,6 +225,12 @@ describe('Row Level Permission Predicate upsert should fail', () => { }); afterAll(async () => { + await updateFeatureFlag({ + featureFlag: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + value: false, + expectToFail: false, + }); + if (createdRoleId) { await deleteOneRole({ expectToFail: false, diff --git a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts index a4735d90506..237e42176e3 100644 --- a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts @@ -2,12 +2,14 @@ import { findManyObjectMetadata } from 'test/integration/metadata/suites/object- import { createOneRole } from 'test/integration/metadata/suites/role/utils/create-one-role.util'; import { deleteOneRole } from 'test/integration/metadata/suites/role/utils/delete-one-role.util'; import { upsertRowLevelPermissionPredicates } from 'test/integration/metadata/suites/row-level-permission-predicate/utils/upsert-row-level-permission-predicates.util'; +import { updateFeatureFlag } from 'test/integration/metadata/suites/utils/update-feature-flag.util'; import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { RowLevelPermissionPredicateGroupLogicalOperator, RowLevelPermissionPredicateOperand, } from 'twenty-shared/types'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type UpsertRowLevelPermissionPredicatesInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; describe('Row Level Permission Predicate upsert should succeed', () => { @@ -16,6 +18,12 @@ describe('Row Level Permission Predicate upsert should succeed', () => { let createdRoleId: string; beforeAll(async () => { + await updateFeatureFlag({ + featureFlag: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + value: true, + expectToFail: false, + }); + const { objects } = await findManyObjectMetadata({ expectToFail: false, input: { @@ -72,6 +80,12 @@ describe('Row Level Permission Predicate upsert should succeed', () => { }); afterAll(async () => { + await updateFeatureFlag({ + featureFlag: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, + value: false, + expectToFail: false, + }); + if (createdRoleId) { await deleteOneRole({ expectToFail: false,