From 4c94fc2803761f40ac01b59165f189765717b1dc Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Mon, 23 Jun 2025 17:22:57 +0200 Subject: [PATCH] [permissions V2] Remove feature flag (#12790) --- .../src/generated-metadata/graphql.ts | 1 - .../twenty-front/src/generated/graphql.tsx | 1 - .../roles/components/SettingsRolesList.tsx | 7 -- .../components/SettingsRolePermissions.tsx | 8 +- ...SettingsRolePermissionsSettingsSection.tsx | 42 +++---- ...ettingsRolePermissionsSettingsTableRow.tsx | 8 +- .../roles/role/components/SettingsRole.tsx | 8 +- .../interfaces/base-resolver-service.ts | 45 +------- .../enums/feature-flag-key.enum.ts | 1 - .../services/feature-flag.service.ts | 12 -- .../permissions/permissions.module.ts | 2 - .../permissions/permissions.service.ts | 109 +----------------- .../metadata-modules/role/role.module.ts | 5 +- .../metadata-modules/role/role.resolver.ts | 30 ----- .../metadata-modules/role/role.service.ts | 3 - .../workspace-permissions-cache.service.ts | 90 ++++++--------- .../datasource/workspace.datasource.ts | 31 ++--- .../workspace-entity-manager.spec.ts | 13 +-- .../workspace-entity-manager.ts | 54 +++------ .../repository/workspace.repository.ts | 25 ++-- .../core/utils/seed-feature-flags.util.ts | 5 - .../workspace-manager.service.ts | 6 - 22 files changed, 99 insertions(+), 407 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 906b42c39..a9f69b48e 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -691,7 +691,6 @@ export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', - IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 01b468fb3..734ae4066 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -647,7 +647,6 @@ export enum FeatureFlagKey { IS_AIRTABLE_INTEGRATION_ENABLED = 'IS_AIRTABLE_INTEGRATION_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', - IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', IS_POSTGRESQL_INTEGRATION_ENABLED = 'IS_POSTGRESQL_INTEGRATION_ENABLED', IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED' diff --git a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx index 5e5b0acbe..09326b52c 100644 --- a/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx +++ b/packages/twenty-front/src/modules/settings/roles/components/SettingsRolesList.tsx @@ -7,12 +7,10 @@ import { SettingsRolesTableRow } from '@/settings/roles/components/SettingsRoles import { settingsAllRolesSelector } from '@/settings/roles/states/settingsAllRolesSelector'; import { SettingsPath } from '@/types/SettingsPath'; import { TableCell } from '@/ui/layout/table/components/TableCell'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useRecoilValue } from 'recoil'; import { H2Title, IconPlus } from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; -import { FeatureFlagKey } from '~/generated/graphql'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { sortByAscString } from '~/utils/array/sortByAscString'; @@ -35,9 +33,6 @@ const StyledNoRoles = styled(TableCell)` export const SettingsRolesList = () => { const navigateSettings = useNavigateSettings(); - const isPermissionsV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - ); const settingsAllRoles = useRecoilValue(settingsAllRolesSelector); @@ -69,8 +64,6 @@ export const SettingsRolesList = () => { title={t`Create Role`} variant="secondary" size="small" - soon={!isPermissionsV2Enabled} - disabled={!isPermissionsV2Enabled} onClick={() => navigateSettings(SettingsPath.RoleCreate)} /> diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx index 962ae37ee..d2ccddc5b 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/components/SettingsRolePermissions.tsx @@ -1,9 +1,7 @@ import { SettingsRolePermissionsObjectLevelSection } from '@/settings/roles/role-permissions/object-level-permissions/components/SettingsRolePermissionsObjectLevelSection'; import { SettingsRolePermissionsObjectsSection } from '@/settings/roles/role-permissions/objects-permissions/components/SettingsRolePermissionsObjectsSection'; import { SettingsRolePermissionsSettingsSection } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsSection'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import styled from '@emotion/styled'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; const StyledRolePermissionsContainer = styled.div` display: flex; @@ -22,17 +20,13 @@ export const SettingsRolePermissions = ({ isEditable, isCreateMode, }: SettingsRolePermissionsProps) => { - const isPermissionsV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - ); - return ( - {isPermissionsV2Enabled && !isCreateMode && ( + {!isCreateMode && ( theme.border.color.light}; @@ -45,10 +41,6 @@ export const SettingsRolePermissionsSettingsSection = ({ roleId, isEditable, }: SettingsRolePermissionsSettingsSectionProps) => { - const isPermissionsV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - ); - const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( settingsDraftRoleFamilyState(roleId), ); @@ -102,23 +94,21 @@ export const SettingsRolePermissionsSettingsSection = ({ return (
- {isPermissionsV2Enabled && ( - - { - setSettingsDraftRole({ - ...settingsDraftRole, - canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings, - }); - }} - /> - - )} + + { + setSettingsDraftRole({ + ...settingsDraftRole, + canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings, + }); + }} + /> + ` cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')}; @@ -57,9 +55,6 @@ export const SettingsRolePermissionsSettingsTableRow = ({ const [settingsDraftRole, setSettingsDraftRole] = useRecoilState( settingsDraftRoleFamilyState(roleId), ); - const isPermissionsV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - ); const canUpdateAllSettings = settingsDraftRole.canUpdateAllSettings; const isSettingPermissionEnabled = @@ -68,8 +63,7 @@ export const SettingsRolePermissionsSettingsTableRow = ({ ) ?? false; const isChecked = isSettingPermissionEnabled || canUpdateAllSettings; - const isDisabled = - !isEditable || canUpdateAllSettings || !isPermissionsV2Enabled; + const isDisabled = !isEditable || canUpdateAllSettings; const handleChange = (value: boolean) => { const currentPermissions = settingsDraftRole.settingPermissions ?? []; diff --git a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx index 20f756b3c..dcbe5cac9 100644 --- a/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role/components/SettingsRole.tsx @@ -18,7 +18,6 @@ import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBa import { TabList } from '@/ui/layout/tab-list/components/TabList'; import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2'; -import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { getOperationName } from '@apollo/client/utilities'; import { t } from '@lingui/core/macro'; import { useRecoilState, useRecoilValue } from 'recoil'; @@ -26,7 +25,6 @@ import { isDefined } from 'twenty-shared/utils'; import { IconLockOpen, IconSettings, IconUserPlus } from 'twenty-ui/display'; import { v4 } from 'uuid'; import { - FeatureFlagKey, Role, useCreateOneRoleMutation, useUpdateOneRoleMutation, @@ -60,10 +58,6 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID + '-' + roleId, ); - const isPermissionsV2Enabled = useIsFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - ); - const navigateSettings = useNavigateSettings(); const [createRole] = useCreateOneRoleMutation(); @@ -91,7 +85,7 @@ export const SettingsRole = ({ roleId, isCreateMode }: SettingsRoleProps) => { return <>; } - const isRoleEditable = isPermissionsV2Enabled && settingsDraftRole.isEditable; + const isRoleEditable = settingsDraftRole.isEditable; const tabs = [ { 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 55f0ce8c5..e55e3feed 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 @@ -26,6 +26,7 @@ import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/g import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service'; import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { PermissionsException, @@ -37,7 +38,6 @@ import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; -import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate'; export type GraphqlQueryResolverExecutionArgs = { args: Input; @@ -98,18 +98,8 @@ export abstract class GraphqlQueryBaseResolverService< const featureFlagsMap = workspaceDataSource.featureFlagMap; - const isPermissionsV2Enabled = - featureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - if (objectMetadataItemWithFieldMaps.isSystem === true) { await this.validateSettingsPermissionsOnObjectOrThrow(options); - } else { - if (!isPermissionsV2Enabled) - await this.validateObjectRecordPermissionsOrThrow({ - objectMetadataId: objectMetadataItemWithFieldMaps.id, - operationName, - options, - }); } const hookedArgs = @@ -228,39 +218,6 @@ export abstract class GraphqlQueryBaseResolverService< } } - private async validateObjectRecordPermissionsOrThrow({ - objectMetadataId, - operationName, - options, - }: { - objectMetadataId: string; - operationName: WorkspaceResolverBuilderMethodNames; - options: WorkspaceQueryRunnerOptions; - }) { - const requiredPermission = - this.getRequiredPermissionForMethod(operationName); - - const workspace = options.authContext.workspace; - - workspaceValidator.assertIsDefinedOrThrow(workspace); - - const userHasPermission = - await this.permissionsService.userHasObjectRecordsPermission({ - userWorkspaceId: options.authContext.userWorkspaceId, - requiredPermission, - workspaceId: workspace.id, - isExecutedByApiKey: isDefined(options.authContext.apiKey), - objectMetadataId, - }); - - if (!userHasPermission) { - throw new PermissionsException( - PermissionsExceptionMessage.PERMISSION_DENIED, - PermissionsExceptionCode.PERMISSION_DENIED, - ); - } - } - private getRequiredPermissionForMethod( operationName: WorkspaceResolverBuilderMethodNames, ) { 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 304ad099c..949571c2e 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 @@ -4,6 +4,5 @@ export enum FeatureFlagKey { IS_STRIPE_INTEGRATION_ENABLED = 'IS_STRIPE_INTEGRATION_ENABLED', IS_UNIQUE_INDEXES_ENABLED = 'IS_UNIQUE_INDEXES_ENABLED', IS_JSON_FILTER_ENABLED = 'IS_JSON_FILTER_ENABLED', - IS_PERMISSIONS_V2_ENABLED = 'IS_PERMISSIONS_V2_ENABLED', IS_AI_ENABLED = 'IS_AI_ENABLED', } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts index 3893594bd..4cbe27ece 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/services/feature-flag.service.ts @@ -73,12 +73,6 @@ export class FeatureFlagService { }, ); - if (keys.includes(FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED)) { - await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache( - { workspaceId, ignoreLock: true }, - ); - } - await this.workspaceFeatureFlagsMapCacheService.recomputeFeatureFlagsMapCache( { workspaceId, @@ -144,12 +138,6 @@ export class FeatureFlagService { }, ); - if (featureFlag === FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED) { - await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache( - { workspaceId, ignoreLock: true }, - ); - } - return result; } } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts index f3c93dfe3..b60c95a8f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -12,7 +11,6 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor @Module({ imports: [ TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'core'), - FeatureFlagModule, TypeOrmModule.forFeature([UserWorkspace], 'core'), UserRoleModule, WorkspacePermissionsCacheModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 244258f73..58e8d8814 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -7,8 +7,6 @@ import { AuthException, AuthExceptionCode, } from 'src/engine/core-modules/auth/auth.exception'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { PermissionsException, @@ -24,7 +22,6 @@ export class PermissionsService { constructor( private readonly userRoleService: UserRoleService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, - private readonly featureFlagService: FeatureFlagService, ) {} public async getUserWorkspacePermissions({ @@ -157,108 +154,10 @@ export class PermissionsService { return true; } - const isPermissionsV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - workspaceId, - ); + const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; - if (isPermissionsV2Enabled) { - const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; - - return settingPermissions.some( - (settingPermission) => settingPermission.setting === setting, - ); - } else { - return false; - } - } - - public async userHasObjectRecordsPermission({ - userWorkspaceId, - workspaceId, - requiredPermission, - isExecutedByApiKey, - objectMetadataId, - }: { - userWorkspaceId?: string; - workspaceId: string; - requiredPermission: PermissionsOnAllObjectRecords; - isExecutedByApiKey: boolean; - objectMetadataId: string; - }): Promise { - const isPermissionsV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - workspaceId, - ); - - if (isPermissionsV2Enabled) { - throw new Error( - 'This should not be called once Permissions V2 is enabled', - ); - } - - if (isExecutedByApiKey) { - return true; - } - - if (!isDefined(userWorkspaceId)) { - throw new AuthException( - 'Missing userWorkspaceId or apiKey in authContext', - AuthExceptionCode.USER_WORKSPACE_NOT_FOUND, - ); - } - - const roleIdOfUserWorkspace = - await this.userRoleService.getRoleIdForUserWorkspace({ - userWorkspaceId, - workspaceId, - }); - - if (!isDefined(roleIdOfUserWorkspace)) { - throw new PermissionsException( - PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, - PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, - ); - } - - const { data: rolesPermissions } = - await this.workspacePermissionsCacheService.getRolesPermissionsFromCache({ - workspaceId, - }); - - const rolePermissionsForUserWorkspaceRole = - rolesPermissions[roleIdOfUserWorkspace]; - - const objectPermissionKey = - this.getObjectPermissionKeyForRequiredPermission(requiredPermission); - - const objectPermissionValue = - rolePermissionsForUserWorkspaceRole[objectMetadataId]?.[ - objectPermissionKey - ]; - - return objectPermissionValue === true; - } - - private getObjectPermissionKeyForRequiredPermission( - requiredPermission: PermissionsOnAllObjectRecords, - ) { - switch (requiredPermission) { - case PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS: - return 'canRead'; - case PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS: - return 'canUpdate'; - case PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS: - return 'canSoftDelete'; - case PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS: - return 'canDestroy'; - default: - throw new PermissionsException( - PermissionsExceptionMessage.UNKNOWN_REQUIRED_PERMISSION, - PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION, - ); - } + return settingPermissions.some( + (settingPermission) => settingPermission.setting === setting, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts index f9fd53e43..6856fc877 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.module.ts @@ -1,7 +1,6 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileModule } from 'src/engine/core-modules/file/file.module'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; @@ -11,19 +10,17 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { RoleResolver } from 'src/engine/metadata-modules/role/role.resolver'; import { RoleService } from 'src/engine/metadata-modules/role/role.service'; -import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity'; import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module'; @Module({ imports: [ - TypeOrmModule.forFeature([RoleEntity, UserWorkspaceRoleEntity], 'core'), + TypeOrmModule.forFeature([RoleEntity], 'core'), TypeOrmModule.forFeature([UserWorkspace, Workspace], 'core'), UserRoleModule, PermissionsModule, UserWorkspaceModule, - FeatureFlagModule, ObjectPermissionModule, SettingPermissionModule, WorkspacePermissionsCacheModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts index 25b5541a2..596ef550f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts @@ -8,9 +8,6 @@ import { Resolver, } from '@nestjs/graphql'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; -import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; -import { FileService } from 'src/engine/core-modules/file/services/file.service'; import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter'; import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; @@ -56,10 +53,8 @@ export class RoleResolver { private readonly userRoleService: UserRoleService, private readonly roleService: RoleService, private readonly userWorkspaceService: UserWorkspaceService, - private readonly featureFlagService: FeatureFlagService, private readonly objectPermissionService: ObjectPermissionService, private readonly settingPermissionService: SettingPermissionService, - private readonly fileService: FileService, ) {} @Query(() => [RoleDTO]) @@ -123,8 +118,6 @@ export class RoleResolver { @AuthWorkspace() workspace: Workspace, @Args('createRoleInput') createRoleInput: CreateRoleInput, ): Promise { - await this.validatePermissionsV2EnabledOrThrow(workspace); - return await this.roleService.createRole({ workspaceId: workspace.id, input: createRoleInput, @@ -136,8 +129,6 @@ export class RoleResolver { @AuthWorkspace() workspace: Workspace, @Args('updateRoleInput') updateRoleInput: UpdateRoleInput, ): Promise { - await this.validatePermissionsV2EnabledOrThrow(workspace); - const role = await this.roleService.updateRole({ input: updateRoleInput, workspaceId: workspace.id, @@ -151,8 +142,6 @@ export class RoleResolver { @AuthWorkspace() workspace: Workspace, @Args('roleId') roleId: string, ): Promise { - await this.validatePermissionsV2EnabledOrThrow(workspace); - const deletedRoleId = await this.roleService.deleteRole( roleId, workspace.id, @@ -167,8 +156,6 @@ export class RoleResolver { @Args('upsertObjectPermissionsInput') upsertObjectPermissionsInput: UpsertObjectPermissionsInput, ): Promise { - await this.validatePermissionsV2EnabledOrThrow(workspace); - return this.objectPermissionService.upsertObjectPermissions({ workspaceId: workspace.id, input: upsertObjectPermissionsInput, @@ -181,8 +168,6 @@ export class RoleResolver { @Args('upsertSettingPermissionsInput') upsertSettingPermissionsInput: UpsertSettingPermissionsInput, ): Promise { - await this.validatePermissionsV2EnabledOrThrow(workspace); - return this.settingPermissionService.upsertSettingPermissions({ workspaceId: workspace.id, input: upsertSettingPermissionsInput, @@ -202,19 +187,4 @@ export class RoleResolver { return workspaceMembers; } - - private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) { - const isPermissionsV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - workspace.id, - ); - - if (!isPermissionsV2Enabled) { - throw new PermissionsException( - PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED, - PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED, - ); - } - } } diff --git a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts index 80b269602..d842318ed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/role.service.ts @@ -17,7 +17,6 @@ import { UpdateRolePayload, } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; -import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; @@ -28,8 +27,6 @@ export class RoleService { private readonly workspaceRepository: Repository, @InjectRepository(RoleEntity, 'core') private readonly roleRepository: Repository, - @InjectRepository(UserWorkspaceRoleEntity, 'core') - private readonly userWorkspaceRoleRepository: Repository, private readonly userRoleService: UserRoleService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, ) {} diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts index 2cf14d6d0..78333abf5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service.ts @@ -8,7 +8,6 @@ import { import { isDefined } from 'twenty-shared/utils'; import { In, Repository } from 'typeorm'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; @@ -85,18 +84,9 @@ export class WorkspacePermissionsCacheService { ); } - const workspaceFeatureFlagsMap = - await this.workspaceFeatureFlagsMapCacheService.getWorkspaceFeatureFlagsMap( - { workspaceId }, - ); - - const isPermissionsV2Enabled = - !!workspaceFeatureFlagsMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - const recomputedRolesPermissions = await this.getObjectRecordPermissionsForRoles({ workspaceId, - isPermissionsV2Enabled, roleIds, }); @@ -232,11 +222,9 @@ export class WorkspacePermissionsCacheService { private async getObjectRecordPermissionsForRoles({ workspaceId, - isPermissionsV2Enabled, roleIds, }: { workspaceId: string; - isPermissionsV2Enabled: boolean; roleIds?: string[]; }): Promise { let roles: RoleEntity[] = []; @@ -265,49 +253,47 @@ export class WorkspacePermissionsCacheService { let canSoftDelete = role.canSoftDeleteAllObjectRecords; let canDestroy = role.canDestroyAllObjectRecords; - if (isPermissionsV2Enabled) { - if ( - standardId && - [ - STANDARD_OBJECT_IDS.workflow, - STANDARD_OBJECT_IDS.workflowRun, - STANDARD_OBJECT_IDS.workflowVersion, - ].includes(standardId) - ) { - const hasWorkflowsPermissions = this.hasWorkflowsPermissions(role); + if ( + standardId && + [ + STANDARD_OBJECT_IDS.workflow, + STANDARD_OBJECT_IDS.workflowRun, + STANDARD_OBJECT_IDS.workflowVersion, + ].includes(standardId) + ) { + const hasWorkflowsPermissions = this.hasWorkflowsPermissions(role); - canRead = hasWorkflowsPermissions; - canUpdate = hasWorkflowsPermissions; - canSoftDelete = hasWorkflowsPermissions; - canDestroy = hasWorkflowsPermissions; - } else { - const objectRecordPermissionsOverride = role.objectPermissions.find( - (objectPermission) => - objectPermission.objectMetadataId === objectMetadataId, - ); + canRead = hasWorkflowsPermissions; + canUpdate = hasWorkflowsPermissions; + canSoftDelete = hasWorkflowsPermissions; + canDestroy = hasWorkflowsPermissions; + } else { + const objectRecordPermissionsOverride = role.objectPermissions.find( + (objectPermission) => + objectPermission.objectMetadataId === objectMetadataId, + ); - const getPermissionValue = ( - overrideValue: boolean | undefined, - defaultValue: boolean, - ) => (isSystem ? true : (overrideValue ?? defaultValue)); + const getPermissionValue = ( + overrideValue: boolean | undefined, + defaultValue: boolean, + ) => (isSystem ? true : (overrideValue ?? defaultValue)); - canRead = getPermissionValue( - objectRecordPermissionsOverride?.canReadObjectRecords, - canRead, - ); - canUpdate = getPermissionValue( - objectRecordPermissionsOverride?.canUpdateObjectRecords, - canUpdate, - ); - canSoftDelete = getPermissionValue( - objectRecordPermissionsOverride?.canSoftDeleteObjectRecords, - canSoftDelete, - ); - canDestroy = getPermissionValue( - objectRecordPermissionsOverride?.canDestroyObjectRecords, - canDestroy, - ); - } + canRead = getPermissionValue( + objectRecordPermissionsOverride?.canReadObjectRecords, + canRead, + ); + canUpdate = getPermissionValue( + objectRecordPermissionsOverride?.canUpdateObjectRecords, + canUpdate, + ); + canSoftDelete = getPermissionValue( + objectRecordPermissionsOverride?.canSoftDeleteObjectRecords, + canSoftDelete, + ); + canDestroy = getPermissionValue( + objectRecordPermissionsOverride?.canDestroyObjectRecords, + canDestroy, + ); } objectRecordsPermissions[objectMetadataId] = { 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 61483622e..82e5d63ba 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 @@ -15,7 +15,6 @@ import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactor 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, @@ -168,28 +167,22 @@ export class WorkspaceDataSource extends DataSource { ): 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 (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 (!(calledByWorkspaceEntityManager === true)) { + throw new PermissionsException( + 'Method not allowed because permissions are not implemented at datasource level.', + PermissionsExceptionCode.METHOD_NOT_ALLOWED, + ); } if (isCalledWithEntityTarget) { 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 431ac51fd..dab3a77e7 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 @@ -5,7 +5,6 @@ import { PlainObjectToDatabaseEntityTransformer } from 'typeorm/query-builder/tr 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 { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource'; import { validateOperationIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils'; @@ -58,15 +57,10 @@ describe('WorkspaceEntityManager', () => { objectMetadataMaps: { idByNameSingular: {}, }, - featureFlagsMap: { - [FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true, - }, } as WorkspaceInternalContext; mockDataSource = { - featureFlagMap: { - [FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true, - }, + featureFlagMap: {}, permissionsPerRoleId: {}, } as WorkspaceDataSource; @@ -141,11 +135,6 @@ describe('WorkspaceEntityManager', () => { return entityName; }); - // Mock getFeatureFlagMap - jest.spyOn(entityManager as any, 'getFeatureFlagMap').mockReturnValue({ - [FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]: true, - }); - // Mock typeORM's EntityManager methods jest .spyOn(EntityManager.prototype, 'save') 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 eb07916b8..df60d8547 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 @@ -32,7 +32,6 @@ import { InstanceChecker } from 'typeorm/util/InstanceChecker'; 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, @@ -94,25 +93,18 @@ export class WorkspaceEntityManager extends EntityManager { let objectPermissions = {}; - const featureFlagMap = this.getFeatureFlagMap(); - - const isPermissionsV2Enabled = - featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - if (permissionOptions?.roleId) { const objectPermissionsByRoleId = dataSource.permissionsPerRoleId; if (!isDefined(objectPermissionsByRoleId?.[permissionOptions.roleId])) { - if (isPermissionsV2Enabled) { - throw new PermissionsException( - `No permissions found for role in datasource (missing ${ - !isDefined(objectPermissionsByRoleId) - ? 'objectPermissionsByRoleId object' - : `roleId in objectPermissionsByRoleId object (${permissionOptions.roleId})` - })`, - PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE, - ); - } + throw new PermissionsException( + `No permissions found for role in datasource (missing ${ + !isDefined(objectPermissionsByRoleId) + ? 'objectPermissionsByRoleId object' + : `roleId in objectPermissionsByRoleId object (${permissionOptions.roleId})` + })`, + PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE, + ); } else { objectPermissions = objectPermissionsByRoleId[permissionOptions.roleId]; } @@ -165,21 +157,12 @@ export class WorkspaceEntityManager extends EntityManager { ); } - const featureFlagMap = this.getFeatureFlagMap(); - - const isPermissionsV2Enabled = - featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - - if (!isPermissionsV2Enabled) { - return queryBuilder; - } else { - return new WorkspaceSelectQueryBuilder( - queryBuilder, - options?.objectRecordsPermissions ?? {}, - this.internalContext, - options?.shouldBypassPermissionChecks ?? false, - ); - } + return new WorkspaceSelectQueryBuilder( + queryBuilder, + options?.objectRecordsPermissions ?? {}, + this.internalContext, + options?.shouldBypassPermissionChecks ?? false, + ); } override insert( @@ -391,15 +374,6 @@ export class WorkspaceEntityManager extends EntityManager { objectRecordsPermissions?: ObjectRecordsPermissions; }, ): void { - const featureFlagMap = this.getFeatureFlagMap(); - - const isPermissionsV2Enabled = - featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - - if (!isPermissionsV2Enabled) { - return; - } - if (permissionOptions?.shouldBypassPermissionChecks === true) { return; } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts index 3250622d5..cd3bc6807 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace.repository.ts @@ -23,7 +23,6 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions'; 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, @@ -70,23 +69,17 @@ export class WorkspaceRepository< alias, queryRunner, ) as unknown as WorkspaceSelectQueryBuilder; - const isPermissionsV2Enabled = - this.featureFlagMap[FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED]; - if (!isPermissionsV2Enabled) { - return queryBuilder; - } else { - if (!this.objectRecordsPermissions) { - throw new Error('Object records permissions are required'); - } - - return new WorkspaceSelectQueryBuilder( - queryBuilder, - this.objectRecordsPermissions, - this.internalContext, - this.shouldBypassPermissionChecks, - ); + if (!this.objectRecordsPermissions) { + throw new Error('Object records permissions are required'); } + + return new WorkspaceSelectQueryBuilder( + queryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + ); } /** 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 24019f97e..28f893d01 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 @@ -40,11 +40,6 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, - { - key: FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - workspaceId: workspaceId, - value: true, - }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts index 9a47febe8..773eadb65 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-manager.service.ts @@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; -import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; @@ -194,10 +193,5 @@ export class WorkspaceManagerService { await this.workspaceRepository.update(workspaceId, { defaultRoleId: memberRole.id, }); - - await this.featureFlagService.enableFeatureFlags( - [FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED], - workspaceId, - ); } }