diff --git a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx b/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx index 37052b8017..37e910044c 100644 --- a/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field-list/components/RecordFieldList.tsx @@ -1,5 +1,6 @@ import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; @@ -15,6 +16,7 @@ import { RecordFieldListComponentInstanceContext } from '@/object-record/record- import { recordFieldListHoverPositionComponentState } from '@/object-record/record-field-list/states/recordFieldListHoverPositionComponentState'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; +import { isJunctionRelationForbidden } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox'; import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader'; @@ -53,6 +55,7 @@ export const RecordFieldList = ({ }); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); + const { objectMetadataItems } = useObjectMetadataItems(); const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({ objectNameSingular, @@ -187,6 +190,12 @@ export const RecordFieldList = ({ fieldName: fieldMetadataItem.name, prefix: instanceId, })}`, + isForbidden: isJunctionRelationForbidden({ + fieldMetadataItem, + sourceObjectMetadataId: objectMetadataItem.id, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + }), }} > { }) .filter(isDefined); + if (fieldValue.some(isDefined) && targetRecordsWithMetadata.length === 0) { + return ; + } + return ( {targetRecordsWithMetadata.map(({ record, objectMetadata }) => ( diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden.ts b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden.ts new file mode 100644 index 0000000000..ec5e785231 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden.ts @@ -0,0 +1,75 @@ +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject'; +import { getJunctionConfig } from '@/object-record/record-field/ui/utils/junction/getJunctionConfig'; +import { getTargetObjectMetadataIdsFromField } from '@/object-record/record-field/ui/utils/junction/getTargetObjectMetadataIdsFromField'; +import { hasJunctionConfig } from '@/object-record/record-field/ui/utils/junction/hasJunctionConfig'; +import { type ObjectPermissions } from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { type JunctionObjectMetadataItem } from './getJunctionConfig'; + +type ObjectPermissionsByObjectMetadataId = Record< + string, + ObjectPermissions & { objectMetadataId: string } +>; + +// Returns true if a junction field's intermediate or final target object +// is not readable by the current user. +export const isJunctionRelationForbidden = ({ + fieldMetadataItem, + sourceObjectMetadataId, + objectMetadataItems, + objectPermissionsByObjectMetadataId, +}: { + fieldMetadataItem: Pick; + sourceObjectMetadataId: string; + objectMetadataItems: JunctionObjectMetadataItem[]; + objectPermissionsByObjectMetadataId: ObjectPermissionsByObjectMetadataId; +}): boolean => { + if (!hasJunctionConfig(fieldMetadataItem.settings)) { + return false; + } + + const junctionObjectMetadataId = + fieldMetadataItem.relation?.targetObjectMetadata.id; + + if (!isDefined(junctionObjectMetadataId)) { + return false; + } + + const junctionPermissions = getObjectPermissionsForObject( + objectPermissionsByObjectMetadataId, + junctionObjectMetadataId, + ); + + if (!junctionPermissions.canReadObjectRecords) { + return true; + } + + const junctionConfig = getJunctionConfig({ + settings: fieldMetadataItem.settings, + relationObjectMetadataId: junctionObjectMetadataId, + sourceObjectMetadataId, + objectMetadataItems, + }); + + if (!isDefined(junctionConfig)) { + return false; + } + + const targetObjectMetadataIds = junctionConfig.targetFields.flatMap( + getTargetObjectMetadataIdsFromField, + ); + + if (targetObjectMetadataIds.length === 0) { + return false; + } + + return !targetObjectMetadataIds.some( + (targetId) => + getObjectPermissionsForObject( + objectPermissionsByObjectMetadataId, + targetId, + ).canReadObjectRecords, + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx index 4d1ed2e0a6..4eee7a2c31 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortal.tsx @@ -1,6 +1,8 @@ +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; +import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord'; import { useIsRecordFieldReadOnly } from '@/object-record/read-only/hooks/useIsRecordFieldReadOnly'; import { @@ -10,6 +12,7 @@ import { } from '@/object-record/record-field/ui/contexts/FieldContext'; import { FieldFocusContextProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; +import { isJunctionRelationForbidden } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden'; import { RecordInlineCellAnchoredPortalContext } from '@/object-record/record-inline-cell/components/RecordInlineCellAnchoredPortalContext'; import { RecordInlineCellCloseOnCommandMenuOpeningEffect } from '@/object-record/record-inline-cell/components/RecordInlineCellCloseOnCommandMenuOpeningEffect'; import { getRecordFieldInputInstanceId } from '@/object-record/utils/getRecordFieldInputId'; @@ -19,7 +22,14 @@ import { isDefined } from 'twenty-shared/utils'; type RecordInlineCellAnchoredPortalProps = { fieldMetadataItem: Pick< FieldMetadataItem, - 'id' | 'name' | 'type' | 'createdAt' | 'updatedAt' | 'label' + | 'id' + | 'name' + | 'type' + | 'createdAt' + | 'updatedAt' + | 'label' + | 'settings' + | 'relation' >; objectMetadataItem: ObjectMetadataItem; recordId: string; @@ -52,6 +62,16 @@ export const RecordInlineCellAnchoredPortal = ({ recordId: recordId ?? '', }); + const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); + const { objectMetadataItems } = useObjectMetadataItems(); + + const isForbidden = isJunctionRelationForbidden({ + fieldMetadataItem, + sourceObjectMetadataId: objectMetadataItem.id, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + }); + const { updateOneRecord } = useUpdateOneRecord(); const useUpdateOneObjectRecordMutation: RecordUpdateHook = () => { @@ -88,6 +108,7 @@ export const RecordInlineCellAnchoredPortal = ({ useUpdateRecord: useUpdateOneObjectRecordMutation, isDisplayModeFixHeight: true, isRecordFieldReadOnly, + isForbidden, onCloseEditMode, }} > diff --git a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx index bb84725723..ba5dfc37a5 100644 --- a/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx +++ b/packages/twenty-front/src/modules/object-record/record-inline-cell/components/RecordInlineCellValue.tsx @@ -1,8 +1,11 @@ import { css } from '@emotion/react'; import styled from '@emotion/styled'; +import { useContext } from 'react'; import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay'; +import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { useFieldFocus } from '@/object-record/record-field/ui/hooks/useFieldFocus'; +import { ForbiddenFieldDisplay } from '@/object-record/record-field/ui/meta-types/display/components/ForbiddenFieldDisplay'; import { useRecordInlineCellContext } from '@/object-record/record-inline-cell/components/RecordInlineCellContext'; import { RecordInlineCellDisplayMode } from '@/object-record/record-inline-cell/components/RecordInlineCellDisplayMode'; import { RecordInlineCellSkeletonLoader } from '@/object-record/record-inline-cell/components/RecordInlineCellSkeletonLoader'; @@ -33,11 +36,16 @@ export const RecordInlineCellValue = () => { const { readonly, loading, isCentered, onOpenEditMode } = useRecordInlineCellContext(); const { isFocused } = useFieldFocus(); + const { isForbidden } = useContext(FieldContext); if (loading === true) { return ; } + if (isForbidden === true) { + return ; + } + return ( 0) { + hasObjectReadPermissions = targetObjectMetadataIds.some( + (targetId) => + getObjectPermissionsForObject( + objectPermissionsByObjectMetadataId, + targetId, + ).canReadObjectRecords, + ); + } + } + } } return ( diff --git a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx index b458f47657..c93db53823 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/fields/components/FieldsWidget.tsx @@ -1,5 +1,6 @@ import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { type CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { formatFieldMetadataItemAsColumnDefinition } from '@/object-metadata/utils/formatFieldMetadataItemAsColumnDefinition'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; @@ -11,6 +12,7 @@ import { recordFieldListHoverPositionComponentState } from '@/object-record/reco import { isActivityTargetField } from '@/object-record/record-field-list/utils/categorizeRelationFields'; import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext'; import { RecordFieldComponentInstanceContext } from '@/object-record/record-field/ui/states/contexts/RecordFieldComponentInstanceContext'; +import { isJunctionRelationForbidden } from '@/object-record/record-field/ui/utils/junction/isJunctionRelationForbidden'; import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell'; import { PropertyBoxSkeletonLoader } from '@/object-record/record-inline-cell/property-box/components/PropertyBoxSkeletonLoader'; import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions'; @@ -73,6 +75,7 @@ export const FieldsWidget = ({ widget }: FieldsWidgetProps) => { }); const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); + const { objectMetadataItems } = useObjectMetadataItems(); const { useUpdateOneObjectRecordMutation } = useRecordShowContainerActions({ objectNameSingular: targetRecord.targetObjectNameSingular, @@ -181,6 +184,12 @@ export const FieldsWidget = ({ widget }: FieldsWidgetProps) => { fieldName: fieldMetadataItem.name, prefix: instanceId, })}`, + isForbidden: isJunctionRelationForbidden({ + fieldMetadataItem, + sourceObjectMetadataId: objectMetadataItem.id, + objectMetadataItems, + objectPermissionsByObjectMetadataId, + }), }} > {isActivityTarget ? (