[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

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