[1/3] Rename permissionFlag to rolePermissionFlag + add permissionFlag catalog/backfill (#20481)
Split of #20377. ## Summary This PR separates available permission flags from per-role permission flag grants. Previously, `core.permissionFlag` stored the role assignment directly: `roleId + flag`. This PR renames that legacy grant table to `core.rolePermissionFlag`, then recreates `core.permissionFlag` as the catalog of available permission flags. ## What changed - Rename the existing `core.permissionFlag` grant table to `core.rolePermissionFlag`. - Add the new syncable `core.permissionFlag` catalog entity with key, label, description, icon, permission type, relevance flags, and custom/standard metadata. - Add stable `SystemPermissionFlag` universal identifiers for the built-in `PermissionFlagType` values. - Seed the standard permission flags for every workspace under the Twenty standard application. - Backfill existing role grants: - create missing catalog rows for existing grant keys, - add `rolePermissionFlag.permissionFlagId`, - migrate grants from the old string `flag` column to the new catalog FK, - replace the old `(flag, roleId)` uniqueness with `(permissionFlagId, roleId)`. - Rewire role permission flag caches, permission checks, role DTO mapping, and `upsertPermissionFlags` to resolve through the catalog. - Keep the existing public role permission API shape: product/app surfaces still talk about `permissionFlags` and return `{ id, roleId, flag }`. - Update metadata flat-entity machinery, migration builders, validators, action handlers, snapshots, generated schemas, docs, and app fixtures for the new `permissionFlag` / `rolePermissionFlag` split. ## Behavior after this PR - Existing permission flag grants keep working. - Existing GraphQL role permission flows keep the same public naming. - Standard permission flags are represented as catalog rows. - Permission checks now compare grants through catalog universal identifiers instead of the legacy `flag` column. - Workspace deletion cleanup now verifies both `permissionFlag` and `rolePermissionFlag`. ## What is not in this PR - Public GraphQL CRUD for custom permission flags. - App manifest support for declaring new custom permission flags. - Frontend UI for creating or assigning custom permission flags beyond the existing role permission flow. --------- Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
@@ -7,7 +7,7 @@ import { WorkspaceMemberDTO } from 'src/engine/core-modules/user/dtos/workspace-
|
||||
import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto';
|
||||
import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto';
|
||||
import { RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto';
|
||||
import { type RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RowLevelPermissionPredicateGroupDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate-group.dto';
|
||||
import { RowLevelPermissionPredicateDTO } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/row-level-permission-predicate.dto';
|
||||
@@ -86,8 +86,8 @@ export class RoleDTO {
|
||||
@Field({ nullable: false })
|
||||
canDestroyAllObjectRecords: boolean;
|
||||
|
||||
@Field(() => [PermissionFlagDTO], { nullable: true })
|
||||
permissionFlags?: PermissionFlagDTO[];
|
||||
@Field(() => [RolePermissionFlagDTO], { nullable: true })
|
||||
permissionFlags?: RolePermissionFlagDTO[];
|
||||
|
||||
@Field(() => [ObjectPermissionDTO], { nullable: true })
|
||||
objectPermissions?: ObjectPermissionDTO[];
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
|
||||
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
|
||||
@@ -81,10 +81,10 @@ export class RoleEntity extends SyncableEntity implements Required<RoleEntity> {
|
||||
objectPermissions: Relation<ObjectPermissionEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => PermissionFlagEntity,
|
||||
(permissionFlag: PermissionFlagEntity) => permissionFlag.role,
|
||||
() => RolePermissionFlagEntity,
|
||||
(rolePermissionFlag: RolePermissionFlagEntity) => rolePermissionFlag.role,
|
||||
)
|
||||
permissionFlags: Relation<PermissionFlagEntity[]>;
|
||||
rolePermissionFlags: Relation<RolePermissionFlagEntity[]>;
|
||||
|
||||
@OneToMany(
|
||||
() => FieldPermissionEntity,
|
||||
|
||||
@@ -15,8 +15,8 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
|
||||
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
|
||||
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
|
||||
import { RolePermissionFlagModule } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
@@ -39,7 +39,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
RoleEntity,
|
||||
RoleTargetEntity,
|
||||
ObjectPermissionEntity,
|
||||
PermissionFlagEntity,
|
||||
RolePermissionFlagEntity,
|
||||
FieldPermissionEntity,
|
||||
UserWorkspaceEntity,
|
||||
ObjectMetadataEntity,
|
||||
@@ -52,7 +52,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
ApiKeyModule,
|
||||
PermissionsModule,
|
||||
ObjectPermissionModule,
|
||||
PermissionFlagModule,
|
||||
RolePermissionFlagModule,
|
||||
RowLevelPermissionModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
WorkspaceMigrationModule,
|
||||
|
||||
@@ -33,16 +33,17 @@ import { AgentDTO } from 'src/engine/metadata-modules/ai/ai-agent/dtos/agent.dto
|
||||
import { fromFlatAgentWithRoleIdToAgentDto } from 'src/engine/metadata-modules/flat-agent/utils/from-agent-entity-to-agent-dto.util';
|
||||
import { FieldPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/field-permission.dto';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { UpsertFieldPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-field-permissions.input';
|
||||
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
|
||||
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
|
||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||
import { fromFlatFieldPermissionToFieldPermissionDto } from 'src/engine/metadata-modules/object-permission/utils/from-flat-field-permission-to-field-permission-dto.util';
|
||||
import { fromFlatObjectPermissionToObjectPermissionDto } from 'src/engine/metadata-modules/object-permission/utils/from-flat-object-permission-to-object-permission-dto.util';
|
||||
import { PermissionFlagDTO } from 'src/engine/metadata-modules/permission-flag/dtos/permission-flag.dto';
|
||||
import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/permission-flag/dtos/upsert-permission-flag-input';
|
||||
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
|
||||
import { fromFlatPermissionFlagToPermissionFlagDto } from 'src/engine/metadata-modules/permission-flag/utils/from-flat-permission-flag-to-permission-flag-dto.util';
|
||||
import { RolePermissionFlagDTO } from 'src/engine/metadata-modules/role-permission-flag/dtos/role-permission-flag.dto';
|
||||
import { UpsertPermissionFlagsInput } from 'src/engine/metadata-modules/role-permission-flag/dtos/upsert-permission-flags.input';
|
||||
import { RolePermissionFlagService } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.service';
|
||||
import { fromFlatRolePermissionFlagToRolePermissionFlagDto } from 'src/engine/metadata-modules/role-permission-flag/utils/from-flat-role-permission-flag-to-role-permission-flag-dto.util';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@@ -87,7 +88,7 @@ export class RoleResolver {
|
||||
private readonly roleService: RoleService,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
private readonly objectPermissionService: ObjectPermissionService,
|
||||
private readonly settingPermissionService: PermissionFlagService,
|
||||
private readonly rolePermissionFlagService: RolePermissionFlagService,
|
||||
private readonly agentRoleService: AiAgentRoleService,
|
||||
private readonly apiKeyRoleService: ApiKeyRoleService,
|
||||
private readonly fieldPermissionService: FieldPermissionService,
|
||||
@@ -95,6 +96,7 @@ export class RoleResolver {
|
||||
private readonly rowLevelPermissionPredicateService: RowLevelPermissionPredicateService,
|
||||
private readonly rowLevelPermissionPredicateGroupService: RowLevelPermissionPredicateGroupService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {}
|
||||
|
||||
@Query(() => [RoleDTO])
|
||||
@@ -223,18 +225,31 @@ export class RoleResolver {
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => [PermissionFlagDTO])
|
||||
@Mutation(() => [RolePermissionFlagDTO])
|
||||
async upsertPermissionFlags(
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@Args('upsertPermissionFlagsInput')
|
||||
upsertPermissionFlagsInput: UpsertPermissionFlagsInput,
|
||||
): Promise<PermissionFlagDTO[]> {
|
||||
const flatPermissionFlags =
|
||||
await this.settingPermissionService.upsertPermissionFlags({
|
||||
): Promise<RolePermissionFlagDTO[]> {
|
||||
const flatRolePermissionFlags =
|
||||
await this.rolePermissionFlagService.upsertPermissionFlags({
|
||||
workspaceId: workspace.id,
|
||||
input: upsertPermissionFlagsInput,
|
||||
});
|
||||
return flatPermissionFlags.map(fromFlatPermissionFlagToPermissionFlagDto);
|
||||
const { flatPermissionFlagMaps } =
|
||||
await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId: workspace.id,
|
||||
flatMapsKeys: ['flatPermissionFlagMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
return flatRolePermissionFlags.map((flatRolePermissionFlag) =>
|
||||
fromFlatRolePermissionFlagToRolePermissionFlagDto(
|
||||
flatRolePermissionFlag,
|
||||
flatPermissionFlagMaps,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => [FieldPermissionDTO])
|
||||
|
||||
@@ -49,7 +49,9 @@ export class RoleService {
|
||||
},
|
||||
relations: {
|
||||
roleTargets: true,
|
||||
permissionFlags: true,
|
||||
rolePermissionFlags: {
|
||||
permissionFlag: true,
|
||||
},
|
||||
objectPermissions: true,
|
||||
fieldPermissions: true,
|
||||
},
|
||||
@@ -67,7 +69,9 @@ export class RoleService {
|
||||
},
|
||||
relations: {
|
||||
roleTargets: true,
|
||||
permissionFlags: true,
|
||||
rolePermissionFlags: {
|
||||
permissionFlag: true,
|
||||
},
|
||||
objectPermissions: true,
|
||||
fieldPermissions: true,
|
||||
},
|
||||
|
||||
+30
-21
@@ -1,14 +1,17 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import {
|
||||
PermissionFlagType,
|
||||
SystemPermissionFlag,
|
||||
} from 'twenty-shared/constants';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import { type Repository } from 'typeorm';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/role/services/workspace-roles-permissions-cache.service';
|
||||
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
|
||||
@@ -23,7 +26,7 @@ const PERSON_OBJECT_METADATA_ID = '44444444-4444-4444-8444-444444444444';
|
||||
|
||||
const createBaseRole = (
|
||||
overrides: Partial<RoleEntity> &
|
||||
Pick<RoleEntity, 'permissionFlags' | 'objectPermissions'>,
|
||||
Pick<RoleEntity, 'rolePermissionFlags' | 'objectPermissions'>,
|
||||
): RoleEntity =>
|
||||
({
|
||||
id: ROLE_ID,
|
||||
@@ -56,8 +59,8 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
let objectPermissionRepository: jest.Mocked<
|
||||
Pick<Repository<ObjectPermissionEntity>, 'find'>
|
||||
>;
|
||||
let permissionFlagRepository: jest.Mocked<
|
||||
Pick<Repository<PermissionFlagEntity>, 'find'>
|
||||
let rolePermissionFlagRepository: jest.Mocked<
|
||||
Pick<Repository<RolePermissionFlagEntity>, 'find'>
|
||||
>;
|
||||
|
||||
const workspaceObjectMetadataFixture: ObjectMetadataEntity[] = [
|
||||
@@ -94,7 +97,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
find: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
permissionFlagRepository = {
|
||||
rolePermissionFlagRepository = {
|
||||
find: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
const fieldPermissionRepository = {
|
||||
@@ -123,8 +126,8 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
useValue: objectPermissionRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(PermissionFlagEntity),
|
||||
useValue: permissionFlagRepository,
|
||||
provide: getRepositoryToken(RolePermissionFlagEntity),
|
||||
useValue: rolePermissionFlagRepository,
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(FieldPermissionEntity),
|
||||
@@ -148,7 +151,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
it('should deny all record permissions when role has neither workspace members access nor update-all-settings', async () => {
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -164,16 +167,19 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
});
|
||||
|
||||
it('should grant all record permissions when role has WORKSPACE_MEMBERS permission flag', async () => {
|
||||
permissionFlagRepository.find.mockResolvedValue([
|
||||
rolePermissionFlagRepository.find.mockResolvedValue([
|
||||
{
|
||||
roleId: ROLE_ID,
|
||||
flag: PermissionFlagType.WORKSPACE_MEMBERS,
|
||||
} as PermissionFlagEntity,
|
||||
permissionFlag: {
|
||||
key: PermissionFlagType.WORKSPACE_MEMBERS,
|
||||
universalIdentifier: SystemPermissionFlag.WORKSPACE_MEMBERS,
|
||||
},
|
||||
} as RolePermissionFlagEntity,
|
||||
]);
|
||||
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -192,7 +198,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
canUpdateAllSettings: true,
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -210,7 +216,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
it('should deny all record permissions when role has neither workflows access nor update-all-settings', async () => {
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -225,16 +231,19 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
});
|
||||
|
||||
it('should grant all record permissions when role has WORKFLOWS permission flag', async () => {
|
||||
permissionFlagRepository.find.mockResolvedValue([
|
||||
rolePermissionFlagRepository.find.mockResolvedValue([
|
||||
{
|
||||
roleId: ROLE_ID,
|
||||
flag: PermissionFlagType.WORKFLOWS,
|
||||
} as PermissionFlagEntity,
|
||||
permissionFlag: {
|
||||
key: PermissionFlagType.WORKFLOWS,
|
||||
universalIdentifier: SystemPermissionFlag.WORKFLOWS,
|
||||
},
|
||||
} as RolePermissionFlagEntity,
|
||||
]);
|
||||
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -264,7 +273,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
|
||||
roleRepository.find.mockResolvedValue([
|
||||
createBaseRole({
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
@@ -285,7 +294,7 @@ describe('WorkspaceRolesPermissionsCacheService', () => {
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
permissionFlags: [],
|
||||
rolePermissionFlags: [],
|
||||
objectPermissions: [],
|
||||
}),
|
||||
]);
|
||||
|
||||
+9
-8
@@ -12,7 +12,7 @@ import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-
|
||||
import { fromRoleEntityToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util';
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
|
||||
import { RoleTargetEntity } from 'src/engine/metadata-modules/role-target/role-target.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
|
||||
@@ -36,8 +36,8 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
private readonly roleTargetRepository: Repository<RoleTargetEntity>,
|
||||
@InjectRepository(ObjectPermissionEntity)
|
||||
private readonly objectPermissionRepository: Repository<ObjectPermissionEntity>,
|
||||
@InjectRepository(PermissionFlagEntity)
|
||||
private readonly permissionFlagRepository: Repository<PermissionFlagEntity>,
|
||||
@InjectRepository(RolePermissionFlagEntity)
|
||||
private readonly rolePermissionFlagRepository: Repository<RolePermissionFlagEntity>,
|
||||
@InjectRepository(FieldPermissionEntity)
|
||||
private readonly fieldPermissionRepository: Repository<FieldPermissionEntity>,
|
||||
@InjectRepository(RowLevelPermissionPredicateEntity)
|
||||
@@ -56,7 +56,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
applications,
|
||||
roleTargets,
|
||||
objectPermissions,
|
||||
permissionFlags,
|
||||
rolePermissionFlags,
|
||||
fieldPermissions,
|
||||
rowLevelPermissionPredicates,
|
||||
rowLevelPermissionPredicateGroups,
|
||||
@@ -80,7 +80,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
select: ['id', 'universalIdentifier', 'roleId'],
|
||||
withDeleted: true,
|
||||
}),
|
||||
this.permissionFlagRepository.find({
|
||||
this.rolePermissionFlagRepository.find({
|
||||
where: { workspaceId },
|
||||
select: ['id', 'universalIdentifier', 'roleId'],
|
||||
withDeleted: true,
|
||||
@@ -105,7 +105,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
const [
|
||||
roleTargetsByRoleId,
|
||||
objectPermissionsByRoleId,
|
||||
permissionFlagsByRoleId,
|
||||
rolePermissionFlagsByRoleId,
|
||||
fieldPermissionsByRoleId,
|
||||
rowLevelPermissionPredicatesByRoleId,
|
||||
rowLevelPermissionPredicateGroupsByRoleId,
|
||||
@@ -120,7 +120,7 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
foreignKey: 'roleId',
|
||||
},
|
||||
{
|
||||
entities: permissionFlags,
|
||||
entities: rolePermissionFlags,
|
||||
foreignKey: 'roleId',
|
||||
},
|
||||
{
|
||||
@@ -149,7 +149,8 @@ export class WorkspaceFlatRoleMapCacheService extends WorkspaceCacheProvider<
|
||||
...roleEntity,
|
||||
roleTargets: roleTargetsByRoleId.get(roleEntity.id) || [],
|
||||
objectPermissions: objectPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
permissionFlags: permissionFlagsByRoleId.get(roleEntity.id) || [],
|
||||
rolePermissionFlags:
|
||||
rolePermissionFlagsByRoleId.get(roleEntity.id) || [],
|
||||
fieldPermissions: fieldPermissionsByRoleId.get(roleEntity.id) || [],
|
||||
rowLevelPermissionPredicates:
|
||||
rowLevelPermissionPredicatesByRoleId.get(roleEntity.id) || [],
|
||||
|
||||
+25
-15
@@ -1,7 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import {
|
||||
PermissionFlagType,
|
||||
SystemPermissionFlag,
|
||||
} from 'twenty-shared/constants';
|
||||
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
type ObjectsPermissions,
|
||||
@@ -16,7 +19,7 @@ import { WorkspaceCacheProvider } from 'src/engine/workspace-cache/interfaces/wo
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { RowLevelPermissionPredicateGroupEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate-group.entity';
|
||||
import { RowLevelPermissionPredicateEntity } from 'src/engine/metadata-modules/row-level-permission-predicate/entities/row-level-permission-predicate.entity';
|
||||
@@ -41,8 +44,8 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
@InjectRepository(ObjectPermissionEntity)
|
||||
private readonly objectPermissionRepository: Repository<ObjectPermissionEntity>,
|
||||
@InjectRepository(PermissionFlagEntity)
|
||||
private readonly permissionFlagRepository: Repository<PermissionFlagEntity>,
|
||||
@InjectRepository(RolePermissionFlagEntity)
|
||||
private readonly rolePermissionFlagRepository: Repository<RolePermissionFlagEntity>,
|
||||
@InjectRepository(FieldPermissionEntity)
|
||||
private readonly fieldPermissionRepository: Repository<FieldPermissionEntity>,
|
||||
@InjectRepository(RowLevelPermissionPredicateEntity)
|
||||
@@ -59,7 +62,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
const [
|
||||
roles,
|
||||
objectPermissions,
|
||||
permissionFlags,
|
||||
rolePermissionFlags,
|
||||
fieldPermissions,
|
||||
rowLevelPermissionPredicates,
|
||||
rowLevelPermissionPredicateGroups,
|
||||
@@ -71,8 +74,9 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
this.objectPermissionRepository.find({
|
||||
where: { workspaceId },
|
||||
}),
|
||||
this.permissionFlagRepository.find({
|
||||
this.rolePermissionFlagRepository.find({
|
||||
where: { workspaceId },
|
||||
relations: ['permissionFlag'],
|
||||
}),
|
||||
this.fieldPermissionRepository.find({
|
||||
where: { workspaceId },
|
||||
@@ -91,9 +95,9 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
entities: objectPermissions,
|
||||
foreignKey: 'roleId',
|
||||
});
|
||||
const permissionFlagsByRoleId =
|
||||
regroupEntitiesByRelatedEntityId<'permissionFlag'>({
|
||||
entities: permissionFlags,
|
||||
const rolePermissionFlagsByRoleId =
|
||||
regroupEntitiesByRelatedEntityId<'rolePermissionFlag'>({
|
||||
entities: rolePermissionFlags,
|
||||
foreignKey: 'roleId',
|
||||
});
|
||||
const fieldPermissionsByRoleId =
|
||||
@@ -117,7 +121,8 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
for (const role of roles) {
|
||||
const roleObjectPermissions =
|
||||
objectPermissionsByRoleId.get(role.id) ?? [];
|
||||
const rolePermissionFlags = permissionFlagsByRoleId.get(role.id) ?? [];
|
||||
const roleRolePermissionFlags =
|
||||
rolePermissionFlagsByRoleId.get(role.id) ?? [];
|
||||
const roleFieldPermissions = fieldPermissionsByRoleId.get(role.id) ?? [];
|
||||
const roleRowLevelPermissionPredicates =
|
||||
rowLevelPermissionPredicatesByRoleId.get(role.id) ?? [];
|
||||
@@ -150,7 +155,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
const hasWorkflowsPermissions =
|
||||
this.hasSettingsGatedObjectPermissions(
|
||||
role,
|
||||
rolePermissionFlags,
|
||||
roleRolePermissionFlags,
|
||||
PermissionFlagType.WORKFLOWS,
|
||||
);
|
||||
|
||||
@@ -163,7 +168,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
const hasWorkspaceMembersPermissions =
|
||||
this.hasSettingsGatedObjectPermissions(
|
||||
role,
|
||||
rolePermissionFlags,
|
||||
roleRolePermissionFlags,
|
||||
PermissionFlagType.WORKSPACE_MEMBERS,
|
||||
);
|
||||
|
||||
@@ -269,13 +274,18 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
|
||||
private hasSettingsGatedObjectPermissions(
|
||||
role: RoleEntity,
|
||||
permissionFlags: PermissionFlagEntity[],
|
||||
rolePermissionFlags: RolePermissionFlagEntity[],
|
||||
permissionFlagType: PermissionFlagType,
|
||||
): boolean {
|
||||
const hasPermissionFromRole = role.canUpdateAllSettings;
|
||||
const permissionFlagUniversalIdentifier =
|
||||
SystemPermissionFlag[permissionFlagType];
|
||||
const hasPermissionFromSettingPermissions = isDefined(
|
||||
permissionFlags.find(
|
||||
(permissionFlag) => permissionFlag.flag === permissionFlagType,
|
||||
rolePermissionFlags.find(
|
||||
(rolePermissionFlag) =>
|
||||
(rolePermissionFlag.permissionFlag?.universalIdentifier ??
|
||||
SystemPermissionFlag[rolePermissionFlag.flag]) ===
|
||||
permissionFlagUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { fromRoleEntityToRoleDto } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util';
|
||||
|
||||
const buildRoleEntity = (overrides: Partial<RoleEntity> = {}): RoleEntity =>
|
||||
({
|
||||
id: 'role-id',
|
||||
label: 'Role',
|
||||
universalIdentifier: 'role-universal-identifier',
|
||||
canUpdateAllSettings: false,
|
||||
canAccessAllTools: false,
|
||||
description: null,
|
||||
icon: null,
|
||||
isEditable: true,
|
||||
canReadAllObjectRecords: false,
|
||||
canUpdateAllObjectRecords: false,
|
||||
canSoftDeleteAllObjectRecords: false,
|
||||
canDestroyAllObjectRecords: false,
|
||||
canBeAssignedToUsers: true,
|
||||
canBeAssignedToAgents: true,
|
||||
canBeAssignedToApiKeys: true,
|
||||
roleTargets: [],
|
||||
objectPermissions: [],
|
||||
fieldPermissions: [],
|
||||
rolePermissionFlags: [],
|
||||
...overrides,
|
||||
}) as RoleEntity;
|
||||
|
||||
describe('fromRoleEntityToRoleDto', () => {
|
||||
it('returns undefined permission flags when role permission flags are not loaded', () => {
|
||||
const roleDto = fromRoleEntityToRoleDto(
|
||||
buildRoleEntity({
|
||||
rolePermissionFlags:
|
||||
undefined as unknown as RoleEntity['rolePermissionFlags'],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(roleDto.permissionFlags).toBeUndefined();
|
||||
});
|
||||
|
||||
it('maps loaded role permission flags to the public permission flag DTO shape', () => {
|
||||
const roleDto = fromRoleEntityToRoleDto(
|
||||
buildRoleEntity({
|
||||
rolePermissionFlags: [
|
||||
{
|
||||
id: 'role-permission-flag-id',
|
||||
roleId: 'role-id',
|
||||
permissionFlag: {
|
||||
key: PermissionFlagType.WORKSPACE,
|
||||
},
|
||||
},
|
||||
] as RoleEntity['rolePermissionFlags'],
|
||||
}),
|
||||
);
|
||||
|
||||
expect(roleDto.permissionFlags).toEqual([
|
||||
{
|
||||
id: 'role-permission-flag-id',
|
||||
roleId: 'role-id',
|
||||
flag: PermissionFlagType.WORKSPACE,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
+8
-1
@@ -1,3 +1,5 @@
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { type RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
@@ -19,7 +21,12 @@ export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => {
|
||||
canBeAssignedToAgents: role.canBeAssignedToAgents,
|
||||
canBeAssignedToApiKeys: role.canBeAssignedToApiKeys,
|
||||
roleTargets: role.roleTargets,
|
||||
permissionFlags: role.permissionFlags,
|
||||
permissionFlags: role.rolePermissionFlags?.map((rolePermissionFlag) => ({
|
||||
id: rolePermissionFlag.id,
|
||||
roleId: rolePermissionFlag.roleId,
|
||||
flag: (rolePermissionFlag.permissionFlag?.key ??
|
||||
rolePermissionFlag.flag) as PermissionFlagType,
|
||||
})),
|
||||
objectPermissions: role.objectPermissions,
|
||||
fieldPermissions: role.fieldPermissions,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user