diff --git a/packages/twenty-front/src/modules/object-record/record-field/components/FieldContextProvider.tsx b/packages/twenty-front/src/modules/object-record/record-field/components/FieldContextProvider.tsx index f1b2573ea..58a5e72fe 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/components/FieldContextProvider.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/components/FieldContextProvider.tsx @@ -1,5 +1,6 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; +import { useObjectPermissionsForObject } from '@/object-record/hooks/useObjectPermissionsForObject'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { FieldContext, @@ -33,6 +34,10 @@ export const FieldContextProvider = ({ objectNameSingular, }); + const objectPermissions = useObjectPermissionsForObject( + objectMetadataItem.id, + ); + const fieldMetadataItem = objectMetadataItem?.fields.find( (field) => field.name === fieldMetadataName, ); @@ -56,6 +61,8 @@ export const FieldContextProvider = ({ return null; } + const isObjectReadOnly = !objectPermissions.canUpdateObjectRecords; + return ( {children} diff --git a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx index 504b9bd34..bb772e259 100644 --- a/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx +++ b/packages/twenty-front/src/modules/object-record/record-show/record-detail-section/components/RecordDetailRelationRecordsListItem.tsx @@ -308,7 +308,7 @@ export const RecordDetailRelationRecordsListItem = ({ labelWidth: 90, }), useUpdateRecord: useUpdateOneObjectRecordMutation, - isReadOnly: false, + isReadOnly: isFieldReadOnly, }} > (({ children, recordId, focusIndex, isDragging = false, ...props }, ref) => { const { objectMetadataItem } = useRecordTableContextOrThrow(); + const objectPermissions = useObjectPermissionsForObject( + objectMetadataItem.id, + ); const currentRowSelected = useRecoilComponentFamilyValueV2( isRowSelectedComponentFamilyState, recordId, @@ -121,6 +125,8 @@ export const RecordTableTr = forwardRef< const isNextRowActiveOrFocused = (isRowFocusActive && isNextRowFocused) || isNextRowActive; + const isReadOnly = !objectPermissions.canUpdateObjectRecords; + return ( { - const { fieldDefinition, recordId } = useContext(FieldContext); + const { fieldDefinition, recordId, isReadOnly } = useContext(FieldContext); const isFieldInputOnly = useIsFieldInputOnly(); @@ -82,6 +82,7 @@ export const RecordTitleCell = ({ displayModeContent: , editModeContentOnly: isFieldInputOnly, loading: loading, + isReadOnly, }; return ( diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx index a43c3ec43..c41d0ca76 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContainer.tsx @@ -1,13 +1,18 @@ import { useInlineCell } from '@/object-record/record-inline-cell/hooks/useInlineCell'; import { RecordTitleCellContext } from '@/object-record/record-title-cell/components/RecordTitleCellContext'; import { useContext } from 'react'; +import { isDefined } from 'twenty-shared/utils'; export const RecordTitleCellContainer = () => { - const { displayModeContent, editModeContent } = useContext( + const { displayModeContent, editModeContent, isReadOnly } = useContext( RecordTitleCellContext, ); const { isInlineCellInEditMode } = useInlineCell(); + if (isDefined(isReadOnly) && isReadOnly) { + return <>{displayModeContent}; + } + return <>{isInlineCellInEditMode ? editModeContent : displayModeContent}; }; diff --git a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContext.tsx b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContext.tsx index 054328969..186dd3644 100644 --- a/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContext.tsx +++ b/packages/twenty-front/src/modules/object-record/record-title-cell/components/RecordTitleCellContext.tsx @@ -5,6 +5,7 @@ export type RecordTitleCellContextProps = { editModeContentOnly?: boolean; displayModeContent?: ReactElement; loading?: boolean; + isReadOnly?: boolean; }; const defaultRecordTitleCellContextProp: RecordTitleCellContextProps = { @@ -12,6 +13,7 @@ const defaultRecordTitleCellContextProp: RecordTitleCellContextProps = { editModeContentOnly: false, displayModeContent: undefined, loading: false, + isReadOnly: false, }; export const RecordTitleCellContext = diff --git a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts index 75b507b5b..245760bdc 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.resolver.ts @@ -24,7 +24,7 @@ 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 { AvailableWorkspaces } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto'; import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service'; @@ -46,9 +46,12 @@ import { import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service'; import { User } from 'src/engine/core-modules/user/user.entity'; import { userValidator } from 'src/engine/core-modules/user/user.validate'; +import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { AuthProvider } from 'src/engine/decorators/auth/auth-provider.decorator'; import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions'; @@ -57,10 +60,6 @@ import { fromUserWorkspacePermissionsToUserWorkspacePermissionsDto } from 'src/e import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type'; import { streamToBuffer } from 'src/utils/stream-to-buffer'; -import { AvailableWorkspaces } from 'src/engine/core-modules/auth/dto/available-workspaces.output'; -import { AuthProvider } from 'src/engine/decorators/auth/auth-provider.decorator'; -import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type'; -import { UserAuthGuard } from 'src/engine/guards/user-auth.guard'; const getHMACKey = (email?: string, key?: string | null) => { if (!email || !key) return null; @@ -106,20 +105,7 @@ export class UserResolver { return this.permissionsService.getDefaultUserWorkspacePermissions(); } - const isPermissionsV2Enabled = - await this.featureFlagService.isFeatureEnabled( - FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED, - workspace.id, - ); - - if (!isPermissionsV2Enabled) { - return await this.permissionsService.getUserWorkspacePermissions({ - userWorkspaceId: currentUserWorkspace.id, - workspaceId: workspace.id, - }); - } - - return await this.permissionsService.getUserWorkspacePermissionsV2({ + return await this.permissionsService.getUserWorkspacePermissions({ userWorkspaceId: currentUserWorkspace.id, workspaceId: workspace.id, }); 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 36fec92d6..244258f73 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 @@ -27,7 +27,7 @@ export class PermissionsService { private readonly featureFlagService: FeatureFlagService, ) {} - public async getUserWorkspacePermissionsV2({ + public async getUserWorkspacePermissions({ userWorkspaceId, workspaceId, }: { @@ -117,68 +117,6 @@ export class PermissionsService { objectPermissions: {}, }) as const satisfies UserWorkspacePermissions; - public async getUserWorkspacePermissions({ - userWorkspaceId, - workspaceId, - }: { - userWorkspaceId: string; - workspaceId: string; - }): Promise { - 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 defaultSettingsPermissions = - this.getDefaultUserWorkspacePermissions().settingsPermissions; - const settingsPermissions = Object.keys(SettingPermissionType).reduce( - (acc, feature) => ({ - ...acc, - [feature]: - hasPermissionOnSettingFeature || - settingPermissions.some( - (settingPermission) => settingPermission.setting === feature, - ), - }), - defaultSettingsPermissions, - ); - - const objectRecordsPermissions: UserWorkspacePermissions['objectRecordsPermissions'] = - { - [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, - objectRecordsPermissions, - objectPermissions: {}, - }; - } - public async userHasWorkspaceSettingPermission({ userWorkspaceId, workspaceId,