[permissions V2] Upsert object and setting permissions (#11119)

Closes https://github.com/twentyhq/core-team-issues/issues/639
This commit is contained in:
Marie
2025-03-25 11:07:51 +01:00
committed by GitHub
parent 54e346a2aa
commit 4680bc740a
51 changed files with 985 additions and 205 deletions

View File

@ -9,9 +9,9 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { ObjectPermissionsEntity } from 'src/engine/metadata-modules/object-permissions/object-permissions.entity';
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
import { SettingsPermissionsEntity } from 'src/engine/metadata-modules/settings-permissions/settings-permissions.entity';
import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity';
@Entity('role')
@Unique('IndexOnRoleUnique', ['label', 'workspaceId'])
@ -62,15 +62,14 @@ export class RoleEntity {
userWorkspaceRoles: Relation<UserWorkspaceRoleEntity[]>;
@OneToMany(
() => ObjectPermissionsEntity,
(objectPermissions: ObjectPermissionsEntity) => objectPermissions.role,
() => ObjectPermissionEntity,
(objectPermission: ObjectPermissionEntity) => objectPermission.role,
)
objectPermissions: Relation<ObjectPermissionsEntity[]>;
objectPermissions: Relation<ObjectPermissionEntity[]>;
@OneToMany(
() => SettingsPermissionsEntity,
(settingsPermissions: SettingsPermissionsEntity) =>
settingsPermissions.role,
() => SettingPermissionEntity,
(settingPermission: SettingPermissionEntity) => settingPermission.role,
)
settingsPermissions: Relation<SettingsPermissionsEntity[]>;
settingPermissions: Relation<SettingPermissionEntity[]>;
}

View File

@ -4,11 +4,13 @@ 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 { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
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';
@Module({
@ -19,6 +21,8 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
PermissionsModule,
UserWorkspaceModule,
FeatureFlagModule,
ObjectPermissionModule,
SettingPermissionModule,
],
providers: [RoleService, RoleResolver],
exports: [RoleService],

View File

@ -16,22 +16,28 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
import { UpsertObjectPermissionInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permission-input';
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
import {
PermissionsException,
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/createRoleInput.dto';
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto';
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/updateRoleInput.dto';
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
import { UpsertSettingPermissionInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Resolver(() => RoleDTO)
@UseGuards(SettingsPermissionsGuard(SettingsPermissions.ROLES))
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.ROLES))
@UseFilters(PermissionsGraphqlApiExceptionFilter)
export class RoleResolver {
constructor(
@ -39,6 +45,8 @@ export class RoleResolver {
private readonly roleService: RoleService,
private readonly userWorkspaceService: UserWorkspaceService,
private readonly featureFlagService: FeatureFlagService,
private readonly objectPermissionService: ObjectPermissionService,
private readonly settingPermissionService: SettingPermissionService,
) {}
@Query(() => [RoleDTO])
@ -101,18 +109,7 @@ export class RoleResolver {
@AuthWorkspace() workspace: Workspace,
@Args('createRoleInput') createRoleInput: CreateRoleInput,
): Promise<RoleDTO> {
const isPermissionsV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
workspace.id,
);
if (!isPermissionsV2Enabled) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
);
}
await this.validatePermissionsV2EnabledOrThrow(workspace);
return this.roleService.createRole({
workspaceId: workspace.id,
@ -125,18 +122,11 @@ export class RoleResolver {
@AuthWorkspace() workspace: Workspace,
@Args('updateRoleInput') updateRoleInput: UpdateRoleInput,
): Promise<RoleDTO> {
const isPermissionsV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
workspace.id,
);
if (!isPermissionsV2Enabled) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
);
}
await this.validatePermissionsV2EnabledOrThrow(workspace);
await this.validateRoleIsEditableOrThrow({
roleId: updateRoleInput.id,
workspaceId: workspace.id,
});
return this.roleService.updateRole({
input: updateRoleInput,
@ -144,6 +134,42 @@ export class RoleResolver {
});
}
@Mutation(() => ObjectPermissionDTO)
async upsertOneObjectPermission(
@AuthWorkspace() workspace: Workspace,
@Args('upsertObjectPermissionInput')
upsertObjectPermissionInput: UpsertObjectPermissionInput,
) {
await this.validatePermissionsV2EnabledOrThrow(workspace);
await this.validateRoleIsEditableOrThrow({
roleId: upsertObjectPermissionInput.roleId,
workspaceId: workspace.id,
});
return this.objectPermissionService.upsertObjectPermission({
workspaceId: workspace.id,
input: upsertObjectPermissionInput,
});
}
@Mutation(() => SettingPermissionDTO)
async upsertOneSettingPermission(
@AuthWorkspace() workspace: Workspace,
@Args('upsertSettingPermissionInput')
upsertSettingPermissionInput: UpsertSettingPermissionInput,
) {
await this.validatePermissionsV2EnabledOrThrow(workspace);
await this.validateRoleIsEditableOrThrow({
roleId: upsertSettingPermissionInput.roleId,
workspaceId: workspace.id,
});
return this.settingPermissionService.upsertSettingPermission({
workspaceId: workspace.id,
input: upsertSettingPermissionInput,
});
}
@ResolveField('workspaceMembers', () => [WorkspaceMember])
async getWorkspaceMembersAssignedToRole(
@Parent() role: RoleDTO,
@ -154,4 +180,36 @@ export class RoleResolver {
workspace.id,
);
}
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
const isPermissionsV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsPermissionsV2Enabled,
workspace.id,
);
if (!isPermissionsV2Enabled) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
);
}
}
private async validateRoleIsEditableOrThrow({
roleId,
workspaceId,
}: {
roleId: string;
workspaceId: string;
}) {
const role = await this.roleService.getRoleById(roleId, workspaceId);
if (!role?.isEditable) {
throw new PermissionsException(
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
PermissionsExceptionCode.ROLE_NOT_EDITABLE,
);
}
}
}

View File

@ -1,7 +1,7 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
@ -10,11 +10,11 @@ import {
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/createRoleInput.dto';
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto';
import {
UpdateRoleInput,
UpdateRolePayload,
} from 'src/engine/metadata-modules/role/dtos/updateRoleInput.dto';
} from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';