[permissions V2] Remove feature flag (#12790)

This commit is contained in:
Marie
2025-06-23 17:22:57 +02:00
committed by GitHub
parent b6787c6fcd
commit 4c94fc2803
22 changed files with 99 additions and 407 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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)}
/>
</StyledCreateRoleSection>

View File

@ -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 (
<StyledRolePermissionsContainer>
<SettingsRolePermissionsObjectsSection
roleId={roleId}
isEditable={isEditable}
/>
{isPermissionsV2Enabled && !isCreateMode && (
{!isCreateMode && (
<SettingsRolePermissionsObjectLevelSection
roleId={roleId}
isEditable={isEditable}

View File

@ -3,7 +3,6 @@ import { SettingsRolePermissionsSettingsTableHeader } from '@/settings/roles/rol
import { SettingsRolePermissionsSettingsTableRow } from '@/settings/roles/role-permissions/settings-permissions/components/SettingsRolePermissionsSettingsTableRow';
import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-permissions/settings-permissions/types/SettingsRolePermissionsSettingPermission';
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useRecoilState } from 'recoil';
@ -18,10 +17,7 @@ import {
IconUsers,
} from 'twenty-ui/display';
import { AnimatedExpandableContainer, Card, Section } from 'twenty-ui/layout';
import {
FeatureFlagKey,
SettingPermissionType,
} from '~/generated-metadata/graphql';
import { SettingPermissionType } from '~/generated-metadata/graphql';
const StyledTable = styled.div`
border-bottom: 1px solid ${({ theme }) => 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 (
<Section>
<H2Title title={t`Settings`} description={t`Settings permissions`} />
{isPermissionsV2Enabled && (
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconSettings}
title={t`Settings All Access`}
description={t`Ability to edit all settings`}
checked={settingsDraftRole.canUpdateAllSettings}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
});
}}
/>
</StyledCard>
)}
<StyledCard rounded>
<SettingsOptionCardContentToggle
Icon={IconSettings}
title={t`Settings All Access`}
description={t`Ability to edit all settings`}
checked={settingsDraftRole.canUpdateAllSettings}
disabled={!isEditable}
onChange={() => {
setSettingsDraftRole({
...settingsDraftRole,
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
});
}}
/>
</StyledCard>
<AnimatedExpandableContainer
isExpanded={!settingsDraftRole.canUpdateAllSettings}
dimension="height"

View File

@ -2,13 +2,11 @@ import { SettingsRolePermissionsSettingPermission } from '@/settings/roles/role-
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilState } from 'recoil';
import { Checkbox } from 'twenty-ui/input';
import { v4 } from 'uuid';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
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 ?? [];

View File

@ -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 = [
{

View File

@ -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<Input extends ResolverArgs> = {
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,
) {

View File

@ -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',
}

View File

@ -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;
}
}

View File

@ -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,

View File

@ -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<boolean> {
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,
);
}
}

View File

@ -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,

View File

@ -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<RoleDTO> {
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<RoleDTO> {
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<string> {
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<ObjectPermissionDTO[]> {
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<SettingPermissionDTO[]> {
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,
);
}
}
}

View File

@ -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<Workspace>,
@InjectRepository(RoleEntity, 'core')
private readonly roleRepository: Repository<RoleEntity>,
@InjectRepository(UserWorkspaceRoleEntity, 'core')
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
private readonly userRoleService: UserRoleService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
) {}

View File

@ -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<ObjectRecordsPermissionsByRoleId> {
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] = {

View File

@ -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<any> {
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) {

View File

@ -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')

View File

@ -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<Entity extends ObjectLiteral>(
@ -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;
}

View File

@ -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<U>;
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,
);
}
/**

View File

@ -40,11 +40,6 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
workspaceId: workspaceId,
value: true,
},
])
.execute();
};

View File

@ -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,
);
}
}