import { Injectable } from '@nestjs/common'; import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants'; import { ObjectRecordsPermissions } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; 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, PermissionsExceptionCode, PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service'; @Injectable() export class PermissionsService { constructor( private readonly userRoleService: UserRoleService, private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService, private readonly featureFlagService: FeatureFlagService, ) {} public async getUserWorkspacePermissionsV2({ userWorkspaceId, workspaceId, }: { userWorkspaceId: string; workspaceId: string; }): Promise<{ settingsPermissions: Record; objectRecordsPermissions: Record; objectPermissions: ObjectRecordsPermissions; }> { const [roleOfUserWorkspace] = await this.userRoleService .getRolesByUserWorkspaces({ userWorkspaceIds: [userWorkspaceId], workspaceId, }) .then((roles) => roles?.get(userWorkspaceId) ?? []); let hasPermissionOnSettingFeature = false; 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 || settingPermissions.some( (settingPermission) => settingPermission.setting === feature, ), }), {} as Record, ); const { data: rolesPermissions } = await this.workspacePermissionsCacheService.getRolesPermissionsFromCache({ workspaceId, }); const objectPermissions = rolesPermissions[roleOfUserWorkspace.id] ?? {}; const objectRecordsPermissionsMap: Record< PermissionsOnAllObjectRecords, boolean > = { [PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canReadAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canUpdateAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canDestroyAllObjectRecords ?? false, }; return { settingsPermissions: settingsPermissionsMap, objectRecordsPermissions: objectRecordsPermissionsMap, objectPermissions, }; } public async getUserWorkspacePermissions({ userWorkspaceId, workspaceId, }: { userWorkspaceId: string; workspaceId: string; }): Promise<{ settingsPermissions: Record; objectRecordsPermissions: Record; }> { const [roleOfUserWorkspace] = await this.userRoleService .getRolesByUserWorkspaces({ userWorkspaceIds: [userWorkspaceId], workspaceId, }) .then((roles) => roles?.get(userWorkspaceId) ?? []); let hasPermissionOnSettingFeature = false; 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 || settingPermissions.some( (settingPermission) => settingPermission.setting === feature, ), }), {} as Record, ); const objectRecordsPermissionsMap: Record< PermissionsOnAllObjectRecords, boolean > = { [PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canReadAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canUpdateAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false, [PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: roleOfUserWorkspace.canDestroyAllObjectRecords ?? false, }; return { settingsPermissions: settingsPermissionsMap, objectRecordsPermissions: objectRecordsPermissionsMap, }; } public async userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId, setting, isExecutedByApiKey, }: { userWorkspaceId?: string; workspaceId: string; setting: SettingPermissionType; isExecutedByApiKey: boolean; }): Promise { if (isExecutedByApiKey) { return true; } if (!isDefined(userWorkspaceId)) { throw new AuthException( 'Missing userWorkspaceId or apiKey in authContext', AuthExceptionCode.USER_WORKSPACE_NOT_FOUND, ); } const [roleOfUserWorkspace] = await this.userRoleService .getRolesByUserWorkspaces({ userWorkspaceIds: [userWorkspaceId], workspaceId, }) .then((roles) => roles?.get(userWorkspaceId) ?? []); 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; } const isPermissionsV2Enabled = await this.featureFlagService.isFeatureEnabled( FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, workspaceId, ); 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 { 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, ); } } }