diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts index b21b7f243..569d7b0a1 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service.ts @@ -190,7 +190,7 @@ export abstract class GraphqlQueryBaseResolverService< const userHasPermission = await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId: authContext.userWorkspaceId, - _setting: permissionRequired, + setting: permissionRequired, workspaceId: authContext.workspace.id, isExecutedByApiKey: isDefined(authContext.apiKey), }); diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts index 82a6b1b69..5ef85c335 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.resolver.ts @@ -163,7 +163,7 @@ export class BillingResolver { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - _setting: SettingPermissionType.WORKSPACE, + setting: SettingPermissionType.WORKSPACE, isExecutedByApiKey, }); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index fb9c7e3f3..8cf996155 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -441,7 +441,7 @@ export class WorkspaceService extends TypeOrmQueryService { const userHasPermission = await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, - _setting: SettingPermissionType.SECURITY, + setting: SettingPermissionType.SECURITY, workspaceId: workspaceId, isExecutedByApiKey: isDefined(apiKey), }); @@ -480,7 +480,7 @@ export class WorkspaceService extends TypeOrmQueryService { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - _setting: SettingPermissionType.WORKSPACE, + setting: SettingPermissionType.WORKSPACE, isExecutedByApiKey: isDefined(apiKey), }); diff --git a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts index 9169e5a9b..f9ea8c7a2 100644 --- a/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts +++ b/packages/twenty-server/src/engine/guards/settings-permissions.guard.ts @@ -32,7 +32,7 @@ export const SettingsPermissionsGuard = ( const hasPermission = await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, - _setting: requiredPermission, + setting: requiredPermission, workspaceId, isExecutedByApiKey: isDefined(ctx.getContext().req.apiKey), }); diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts index b59549e85..577fd9c4a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.module.ts @@ -7,6 +7,7 @@ import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-works import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity'; +import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module'; @Module({ @@ -16,6 +17,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role. TypeOrmModule.forFeature([UserWorkspace], 'core'), EnvironmentModule, UserRoleModule, + TypeOrmModule.forFeature([SettingPermissionEntity], 'metadata'), ], providers: [PermissionsService], exports: [PermissionsService], diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts index 797046c49..1164a08f1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.service.ts @@ -1,7 +1,9 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { AuthException, @@ -15,6 +17,7 @@ import { PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; +import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; @Injectable() @@ -22,6 +25,8 @@ export class PermissionsService { constructor( private readonly environmentService: EnvironmentService, private readonly userRoleService: UserRoleService, + @InjectRepository(SettingPermissionEntity, 'metadata') + private readonly settingPermissionRepository: Repository, ) {} public async getUserWorkspacePermissions({ @@ -43,14 +48,27 @@ export class PermissionsService { let hasPermissionOnSettingFeature = false; - if (roleOfUserWorkspace?.canUpdateAllSettings === true) { + if (!isDefined(roleOfUserWorkspace)) { + throw new PermissionsException( + PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, + PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, + ); + } + + if (roleOfUserWorkspace.canUpdateAllSettings === true) { hasPermissionOnSettingFeature = true; } + const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; + const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce( (acc, feature) => ({ ...acc, - [feature]: hasPermissionOnSettingFeature, + [feature]: + hasPermissionOnSettingFeature || + settingPermissions.some( + (settingPermission) => settingPermission.setting === feature, + ), }), {} as Record, ); @@ -60,13 +78,13 @@ export class PermissionsService { boolean > = { [PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]: - roleOfUserWorkspace?.canReadAllObjectRecords ?? false, + roleOfUserWorkspace.canReadAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]: - roleOfUserWorkspace?.canUpdateAllObjectRecords ?? false, + roleOfUserWorkspace.canUpdateAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: - roleOfUserWorkspace?.canSoftDeleteAllObjectRecords ?? false, + roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: - roleOfUserWorkspace?.canDestroyAllObjectRecords ?? false, + roleOfUserWorkspace.canDestroyAllObjectRecords ?? false, }; return { @@ -78,12 +96,12 @@ export class PermissionsService { public async userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - _setting, + setting, isExecutedByApiKey, }: { userWorkspaceId?: string; workspaceId: string; - _setting: SettingPermissionType; + setting: SettingPermissionType; isExecutedByApiKey: boolean; }): Promise { if (isExecutedByApiKey) { @@ -104,11 +122,22 @@ export class PermissionsService { }) .then((roles) => roles?.get(userWorkspaceId) ?? []); - if (roleOfUserWorkspace?.canUpdateAllSettings === true) { + if (!isDefined(roleOfUserWorkspace)) { + throw new PermissionsException( + PermissionsExceptionMessage.NO_ROLE_FOUND_FOR_USER_WORKSPACE, + PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE, + ); + } + + if (roleOfUserWorkspace.canUpdateAllSettings === true) { return true; } - return false; + const settingPermissions = roleOfUserWorkspace.settingPermissions ?? []; + + return settingPermissions.some( + (settingPermission) => settingPermission.setting === setting, + ); } public async userHasObjectRecordsPermission({ diff --git a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts index 6885760f3..c9a2a0848 100644 --- a/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/user-role/user-role.service.ts @@ -1,7 +1,7 @@ import { InjectRepository } from '@nestjs/typeorm'; -import { In, Not, Repository } from 'typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { In, Not, Repository } from 'typeorm'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants'; @@ -75,7 +75,9 @@ export class UserRoleService { workspaceId, }, relations: { - role: true, + role: { + settingPermissions: true, + }, }, }); diff --git a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts index 2ea2631e5..8649005b0 100644 --- a/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts +++ b/packages/twenty-server/src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service.ts @@ -54,7 +54,7 @@ export class WorkspaceMemberPreQueryHookService { await this.permissionsService.userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, - _setting: SettingPermissionType.WORKSPACE_MEMBERS, + setting: SettingPermissionType.WORKSPACE_MEMBERS, isExecutedByApiKey: isDefined(apiKey), }) ) {