Browse Source

feat: restrict data/meta operations in gui

pull/8708/head
Pranav C 4 months ago
parent
commit
5d8b1fc55a
  1. 3
      packages/nc-gui/components/cell/MultiSelect.vue
  2. 3
      packages/nc-gui/components/cell/SingleSelect.vue
  3. 3
      packages/nc-gui/components/cell/attachment/Modal.vue
  4. 3
      packages/nc-gui/components/cell/attachment/index.vue
  5. 4
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  6. 3
      packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue
  7. 6
      packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue
  8. 2
      packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue
  9. 19
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  10. 9
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  11. 8
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  12. 4
      packages/nc-gui/components/project/AllTables.vue
  13. 3
      packages/nc-gui/components/smartsheet/Details.vue
  14. 3
      packages/nc-gui/components/smartsheet/Form.vue
  15. 3
      packages/nc-gui/components/smartsheet/Gallery.vue
  16. 3
      packages/nc-gui/components/smartsheet/Kanban.vue
  17. 3
      packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue
  18. 3
      packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue
  19. 3
      packages/nc-gui/components/smartsheet/calendar/MonthView.vue
  20. 3
      packages/nc-gui/components/smartsheet/calendar/SideMenu.vue
  21. 3
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue
  22. 2
      packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue
  23. 3
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  24. 3
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  25. 2
      packages/nc-gui/components/smartsheet/grid/Table.vue
  26. 3
      packages/nc-gui/components/smartsheet/header/Cell.vue
  27. 3
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  28. 3
      packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue
  29. 4
      packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue
  30. 3
      packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue
  31. 3
      packages/nc-gui/components/smartsheet/toolbar/ShareView.vue
  32. 3
      packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue
  33. 3
      packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue
  34. 4
      packages/nc-gui/components/smartsheet/topbar/SelectMode.vue
  35. 14
      packages/nc-gui/components/tabs/Smartsheet.vue
  36. 3
      packages/nc-gui/components/virtual-cell/BelongsTo.vue
  37. 3
      packages/nc-gui/components/virtual-cell/HasMany.vue
  38. 3
      packages/nc-gui/components/virtual-cell/Links.vue
  39. 3
      packages/nc-gui/components/virtual-cell/ManyToMany.vue
  40. 3
      packages/nc-gui/components/virtual-cell/OneToOne.vue
  41. 3
      packages/nc-gui/components/virtual-cell/components/ItemChip.vue
  42. 3
      packages/nc-gui/composables/useCalendarViewStore.ts
  43. 3
      packages/nc-gui/composables/useExpandedFormStore.ts
  44. 3
      packages/nc-gui/composables/useKanbanViewStore.ts
  45. 3
      packages/nc-gui/composables/useMapViewDataStore.ts
  46. 39
      packages/nc-gui/composables/useRoles/index.ts
  47. 3
      packages/nc-gui/composables/useViewColumns.ts
  48. 3
      packages/nc-gui/composables/useViewData.ts
  49. 3
      packages/nc-gui/composables/useViewFilters.ts
  50. 3
      packages/nc-gui/composables/useViewGroupBy.ts
  51. 2
      packages/nc-gui/composables/useViewSorts.ts
  52. 9
      packages/nc-gui/context/index.ts
  53. 8
      packages/nocodb-sdk/src/lib/enums.ts
  54. 7
      packages/nocodb/src/services/public-datas.service.ts
  55. 16
      packages/nocodb/src/utils/acl.ts

3
packages/nc-gui/components/cell/MultiSelect.vue

@ -5,6 +5,7 @@ import type { Select as AntSelect } from 'ant-design-vue'
import type { SelectOptionType, SelectOptionsType } from 'nocodb-sdk'
import type { FormFieldsLimitOptionsType } from '~/lib/types'
import MdiCloseCircle from '~icons/mdi/close-circle'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
modelValue?: string | string[]
@ -57,7 +58,7 @@ const { $api } = useNuxtApp()
const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isPg, isMysql } = useBase()

3
packages/nc-gui/components/cell/SingleSelect.vue

@ -4,6 +4,7 @@ import { message } from 'ant-design-vue'
import tinycolor from 'tinycolor2'
import type { SelectOptionType } from 'nocodb-sdk'
import type { FormFieldsLimitOptionsType } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
modelValue?: string | undefined
@ -49,7 +50,7 @@ const searchVal = ref()
const { getMeta } = useMetas()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isPg, isMysql } = useBase()

3
packages/nc-gui/components/cell/attachment/Modal.vue

@ -2,8 +2,9 @@
import { onKeyDown, useEventListener } from '@vueuse/core'
import { useAttachmentCell } from './utils'
import { useSortable } from './sort'
import {useRolesWrapper} from "~/composables/useRoles";
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const {
open,

3
packages/nc-gui/components/cell/attachment/index.vue

@ -2,6 +2,7 @@
import { onKeyDown } from '@vueuse/core'
import { useProvideAttachmentCell } from './utils'
import { useSortable } from './sort'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
modelValue?: string | Record<string, any>[] | null
@ -175,7 +176,7 @@ const keydownSpace = (e: KeyboardEvent) => {
}
}
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const isConfirmModalOpen = ref(false)
const filetoDelete = reactive({
title: '',

4
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -1,8 +1,10 @@
<script setup lang="ts">
import {useRolesWrapper} from "~/composables/useRoles";
const workspaceStore = useWorkspace()
const baseStore = useBase()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { appInfo } = useGlobal()

3
packages/nc-gui/components/dashboard/TreeView/AddNewTableNode.vue

@ -4,6 +4,7 @@ import { storeToRefs } from 'pinia'
import { toRef } from '@vue/reactivity'
import { resolveComponent } from '@vue/runtime-core'
import { ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const props = withDefaults(
defineProps<{
@ -19,7 +20,7 @@ const emit = defineEmits<{
openTableCreateDialog: () => void
}>()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const base = toRef(props, 'base')

6
packages/nc-gui/components/dashboard/TreeView/BaseOptions.vue

@ -68,7 +68,7 @@ function openQuickImportDialog(type: string) {
<template #expandIcon></template>
<NcMenuItem
v-if="isUIAllowed('airtableImport', { roles: baseRole })"
v-if="isUIAllowed('airtableImport', { roles: baseRole, source })"
key="quick-import-airtable"
@click="openAirtableImportDialog(source.base_id, source.id)"
>
@ -86,7 +86,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('jsonImport', { roles: baseRole })"
v-if="isUIAllowed('jsonImport', { roles: baseRole, source })"
key="quick-import-json"
@click="openQuickImportDialog('json')"
>
@ -97,7 +97,7 @@ function openQuickImportDialog(type: string) {
</NcMenuItem>
<NcMenuItem
v-if="isUIAllowed('excelImport', { roles: baseRole })"
v-if="isUIAllowed('excelImport', { roles: baseRole, source })"
key="quick-import-excel"
@click="openQuickImportDialog('excel')"
>

2
packages/nc-gui/components/dashboard/TreeView/ProjectNode.vue

@ -823,7 +823,7 @@ const onTableIdCopy = async () => {
</NcDropdown>
<NcButton
v-if="isUIAllowed('tableCreate', { roles: baseRole })"
v-if="isUIAllowed('tableCreate', { roles: baseRole, source })"
v-e="['c:source:add-table']"
type="text"
size="xxsmall"

19
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -213,6 +213,10 @@ const refreshViews = async () => {
await nextTick()
isExpanded.value = true
}
const source = computed(() => {
return base.value?.sources?.[sourceIndex.value]
})
</script>
<template>
@ -331,15 +335,16 @@ const refreshViews = async () => {
<template
v-if="
!isSharedBase &&
(isUIAllowed('tableRename', { roles: baseRole }) || isUIAllowed('tableDelete', { roles: baseRole }))
(isUIAllowed('tableRename', { roles: baseRole, source }) ||
isUIAllowed('tableDelete', { roles: baseRole, source }))
"
>
<NcDivider />
<NcMenuItem
v-if="isUIAllowed('tableRename', { roles: baseRole })"
v-if="isUIAllowed('tableRename', { roles: baseRole, source })"
:data-testid="`sidebar-table-rename-${table.title}`"
class="nc-table-rename"
@click="openRenameTableDialog(table, base.sources[sourceIndex].id)"
@click="openRenameTableDialog(table, source.id)"
>
<div v-e="['c:table:rename']" class="flex gap-2 items-center">
<GeneralIcon icon="rename" class="text-gray-700" />
@ -349,9 +354,11 @@ const refreshViews = async () => {
<NcMenuItem
v-if="
isUIAllowed('tableDuplicate') &&
isUIAllowed('tableDuplicate', {
source,
}) &&
base.sources?.[sourceIndex] &&
(base.sources[sourceIndex].is_meta || base.sources[sourceIndex].is_local)
(source.is_meta || source.is_local)
"
:data-testid="`sidebar-table-duplicate-${table.title}`"
@click="duplicateTable(table)"
@ -364,7 +371,7 @@ const refreshViews = async () => {
<NcDivider />
<NcMenuItem
v-if="isUIAllowed('tableDelete', { roles: baseRole })"
v-if="isUIAllowed('tableDelete', { roles: baseRole, source })"
:data-testid="`sidebar-table-delete-${table.title}`"
class="!text-red-500 !hover:bg-red-50 nc-table-delete"
@click="deleteTable"

9
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -10,6 +10,7 @@ import {
SSLUsage,
clientTypes as _clientTypes,
} from '#imports'
import {SourceRestriction} from "nocodb-sdk";
const props = defineProps<{ open: boolean; connectionType?: ClientType }>()
@ -56,8 +57,8 @@ const formState = ref<ProjectCreateForm>({
sslUse: SSLUsage.No,
extraParameters: [],
meta: {
readOnlySchema: true,
readOnlyData: false,
[SourceRestriction.META_READONLY]: true,
[SourceRestriction.DATA_READONLY]: false,
},
})
@ -515,10 +516,10 @@ const toggleModal = (val: boolean) => {
</a-form-item>
<a-form-item label="Readonly Schema">
<a-switch v-model:checked="formState.meta.readOnlySchema" size="small"></a-switch>
<a-switch v-model:checked="formState.meta[SourceRestriction.META_READONLY]" size="small"></a-switch>
</a-form-item>
<a-form-item label="Readonly Data">
<a-switch v-model:checked="formState.meta.readOnlyData" size="small"></a-switch>
<a-switch v-model:checked="formState.meta[SourceRestriction.DATA_READONLY]" size="small"></a-switch>
</a-form-item>
<div class="flex items-right justify-end gap-2">

8
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -72,8 +72,8 @@ const customFormState = ref<ProjectCreateForm>({
sslUse: SSLUsage.No,
extraParameters: [],
meta: {
readOnlySchema: true,
readOnlyData: false,
[SourceRestriction.META_READONLY]: true,
[SourceRestriction.DATA_READONLY]: false,
},
})
@ -530,10 +530,10 @@ watch(
</a-form-item>
<a-form-item label="Readonly Schema">
<a-switch v-model:checked="formState.meta.readOnlySchema" size="small"></a-switch>
<a-switch v-model:checked="formState.meta[SourceRestriction.META_READONLY]" size="small"></a-switch>
</a-form-item>
<a-form-item label="Readonly Data">
<a-switch v-model:checked="formState.meta.readOnlyData" size="small"></a-switch>
<a-switch v-model:checked="formState.meta[SourceRestriction.DATA_READONLY]" size="small"></a-switch>
</a-form-item>
<!-- Use Connection URL -->

4
packages/nc-gui/components/project/AllTables.vue

@ -76,7 +76,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
}"
>
<div
v-if="isUIAllowed('tableCreate')"
v-if="isUIAllowed('tableCreate', {source: openedProject?.sources?.[0]})"
role="button"
class="nc-base-view-all-table-btn"
data-testid="proj-view-btn__add-new-table"
@ -86,7 +86,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
<div class="label">{{ $t('general.new') }} {{ $t('objects.table') }}</div>
</div>
<div
v-if="isUIAllowed('tableCreate')"
v-if="isUIAllowed('tableCreate', {source: openedProject?.sources?.[0]})"
v-e="['c:table:import']"
role="button"
class="nc-base-view-all-table-btn"

3
packages/nc-gui/components/smartsheet/Details.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { LoadingOutlined } from '@ant-design/icons-vue'
import {useRolesWrapper} from "~/composables/useRoles";
const { openedViewsTab } = storeToRefs(useViewsStore())
const { onViewsTabChange } = useViewsStore()
@ -8,7 +9,7 @@ const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { base } = storeToRefs(useBase())
const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/Form.vue

@ -15,6 +15,7 @@ import {
isVirtualCol,
} from 'nocodb-sdk'
import type { ImageCropperConfig } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
provide(IsFormInj, ref(true))
provide(IsGalleryInj, ref(false))
@ -52,7 +53,7 @@ const { isMobileMode, user } = useGlobal()
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { base } = storeToRefs(useBase())

3
packages/nc-gui/components/smartsheet/Gallery.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Row as RowType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
interface Attachment {
url: string
@ -64,7 +65,7 @@ const coverImageObjectFitClass = computed(() => {
if (fk_cover_image_object_fit === CoverImageObjectFit.COVER) return '!object-cover'
})
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
// TODO: extract this code (which is duplicated in grid and gallery) into a separate component
const _contextMenu = ref(false)

3
packages/nc-gui/components/smartsheet/Kanban.vue

@ -4,6 +4,7 @@ import Draggable from 'vuedraggable'
import tinycolor from 'tinycolor2'
import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Row as RowType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
interface Attachment {
url: string
@ -73,7 +74,7 @@ const {
const { isViewDataLoading } = storeToRefs(useViewsStore())
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { appInfo, isMobileMode } = useGlobal()

3
packages/nc-gui/components/smartsheet/calendar/DayView/DateField.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['expandRecord', 'newRecord'])
@ -8,7 +9,7 @@ const meta = inject(MetaInj, ref())
const container = ref()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { selectedDate, formattedData, formattedSideBarData, calendarRange, updateRowProperty } = useCalendarViewStoreOrThrow()

3
packages/nc-gui/components/smartsheet/calendar/DayView/DateTimeField.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['expandRecord', 'newRecord'])
@ -20,7 +21,7 @@ const { $e } = useNuxtApp()
const container = ref<null | HTMLElement>(null)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/calendar/MonthView.vue

@ -2,6 +2,7 @@
import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const emit = defineEmits(['newRecord', 'expandRecord'])
@ -23,7 +24,7 @@ const { $e } = useNuxtApp()
const isMondayFirst = ref(true)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/calendar/SideMenu.vue

@ -2,6 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core'
import { UITypes } from 'nocodb-sdk'
import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{
visible: boolean
@ -15,7 +16,7 @@ interface Attachment {
const INFINITY_SCROLL_THRESHOLD = 100
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { $e } = useNuxtApp()

3
packages/nc-gui/components/smartsheet/calendar/WeekView/DateField.vue

@ -2,6 +2,7 @@
import dayjs from 'dayjs'
import type { ColumnType } from 'nocodb-sdk'
import type { Row } from '~/lib/types'
import {useRolesWrapper} from "~/composables/useRoles";
const emits = defineEmits(['expandRecord', 'newRecord'])
@ -24,7 +25,7 @@ const container = ref<null | HTMLElement>(null)
const { width: containerWidth } = useElementSize(container)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref())

2
packages/nc-gui/components/smartsheet/calendar/WeekView/DateTimeField.vue

@ -28,7 +28,7 @@ const { width: containerWidth } = useElementSize(container)
const isPublic = inject(IsPublicInj, ref(false))
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { type CommentType, ProjectRoles } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{
loading: boolean
@ -45,7 +46,7 @@ const isExpandedFormLoading = computed(() => props.loading)
const tab = ref<'comments' | 'audits'>('comments')
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const router = useRouter()

3
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -12,6 +12,7 @@ import type { Ref } from 'vue'
import { Drawer } from 'ant-design-vue'
import NcModal from '../../nc/Modal.vue'
import MdiChevronDown from '~icons/mdi/chevron-down'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
modelValue?: boolean
@ -73,7 +74,7 @@ const isUnsavedDuplicatedRecordExist = ref(false)
const isRecordLinkCopied = ref(false)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value)

2
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -166,7 +166,7 @@ const isViewColumnsLoading = computed(() => _isViewColumnsLoading.value || !meta
const resizingColumn = ref(false)
// #Permissions
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const hasEditPermission = computed(() => isUIAllowed('dataEdit'))
const isAddingColumnAllowed = computed(() => !readOnly.value && !isLocked.value && isUIAllowed('fieldAdd') && !isSqlView.value)

3
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { UITypes, UITypesName } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
column: ColumnType
@ -32,7 +33,7 @@ const isDropDownOpen = ref(false)
const column = toRef(props, 'column')
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
provide(ColumnInj, column)

3
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -9,6 +9,7 @@ import {
isLinksOrLTAR,
} from 'nocodb-sdk'
import { RelationTypes, UITypes, UITypesName, substituteColumnIdWithAliasInFormula } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const props = defineProps<{
column: ColumnType
@ -36,7 +37,7 @@ provide(ColumnInj, column)
const { metas } = useMetas()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const meta = inject(MetaInj, ref())

3
packages/nc-gui/components/smartsheet/sidebar/RenameableMenuItem.vue

@ -2,6 +2,7 @@
import type { VNodeRef } from '@vue/runtime-core'
import type { KanbanType, ViewType, ViewTypes } from 'nocodb-sdk'
import type { WritableComputedRef } from '@vue/reactivity'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
view: ViewType
@ -30,7 +31,7 @@ const vModel = useVModel(props, 'view', emits) as WritableComputedRef<ViewType &
const { $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const activeView = inject(ActiveViewInj, ref())

4
packages/nc-gui/components/smartsheet/toolbar/KanbanStackEditOrAdd.vue

@ -1,5 +1,7 @@
<script setup lang="ts">
const { isUIAllowed } = useRoles()
import {useRolesWrapper} from "~/composables/useRoles";
const { isUIAllowed } = useRolesWrapper()
const { groupingFieldColumn } = useKanbanViewStoreOrThrow()

3
packages/nc-gui/components/smartsheet/toolbar/MoreActions.vue

@ -3,6 +3,7 @@ import type { RequestParams } from 'nocodb-sdk'
import { ExportTypes } from 'nocodb-sdk'
import { saveAs } from 'file-saver'
import * as XLSX from 'xlsx'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n()
@ -32,7 +33,7 @@ const showWebhookDrawer = ref(false)
const quickImportDialog = ref(false)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const exportFile = async (exportType: ExportTypes) => {
let offset = 0

3
packages/nc-gui/components/smartsheet/toolbar/ShareView.vue

@ -2,6 +2,7 @@
import { ViewTypes } from 'nocodb-sdk'
import { isString } from '@vue/shared'
import tinycolor from 'tinycolor2'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n()
@ -13,7 +14,7 @@ const { $e } = useNuxtApp()
const { dashboardUrl } = useDashboard()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase())

3
packages/nc-gui/components/smartsheet/toolbar/ViewActionMenu.vue

@ -2,6 +2,7 @@
import type { TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes } from 'nocodb-sdk'
import { LockType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const props = withDefaults(
defineProps<{
@ -16,7 +17,7 @@ const props = withDefaults(
const emits = defineEmits(['rename', 'closeModal', 'delete'])
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const isPublicView = inject(IsPublicInj, ref(false))

3
packages/nc-gui/components/smartsheet/toolbar/ViewActions.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { Ref } from '@vue/reactivity'
import { LockType } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const { t } = useI18n()
@ -37,7 +38,7 @@ const quickImportDialogs: Record<(typeof quickImportDialogTypes)[number], Ref<bo
{},
) as Record<QuickImportDialogType, Ref<boolean>>
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
useBase()

4
packages/nc-gui/components/smartsheet/topbar/SelectMode.vue

@ -1,7 +1,9 @@
<script lang="ts" setup>
import {useRolesWrapper} from "~/composables/useRoles";
const { openedViewsTab, activeView } = storeToRefs(useViewsStore())
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { onViewsTabChange } = useViewsStore()

14
packages/nc-gui/components/tabs/Smartsheet.vue

@ -39,6 +39,12 @@ const reloadViewMetaEventHook = createEventHook<void | boolean>()
const openNewRecordFormHook = createEventHook<void>()
const { base } = storeToRefs(useBase())
const activeSource = computed(() => {
return meta.value?.source_id && base.value && base.value.sources?.find((source) => source.id === meta.value?.source_id)
})
useProvideKanbanViewStore(meta, activeView)
useProvideMapViewStore(meta, activeView)
useProvideCalendarViewStore(meta, activeView)
@ -52,9 +58,15 @@ provide(ReloadViewMetaHookInj, reloadViewMetaEventHook)
provide(OpenNewRecordFormHookInj, openNewRecordFormHook)
provide(IsFormInj, isForm)
provide(TabMetaInj, activeTab)
provide(ActiveSourceInj, activeSource)
provide(
ReadonlyInj,
computed(() => !isUIAllowed('dataEdit')),
computed(
() =>
!isUIAllowed('dataEdit', {
source: activeSource.value,
}),
),
)
useExpandedFormDetachedProvider()

3
packages/nc-gui/components/virtual-cell/BelongsTo.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)!
@ -18,7 +19,7 @@ const isForm = inject(IsFormInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const listItemsDlg = ref(false)

3
packages/nc-gui/components/virtual-cell/HasMany.vue

@ -2,6 +2,7 @@
import type { ColumnType } from 'nocodb-sdk'
import { isSystemColumn } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)!
@ -25,7 +26,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()

3
packages/nc-gui/components/virtual-cell/Links.vue

@ -3,6 +3,7 @@ import { computed } from '@vue/reactivity'
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const value = inject(CellValueInj, ref(0))
@ -28,7 +29,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { t } = useI18n()

3
packages/nc-gui/components/virtual-cell/ManyToMany.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)!
@ -24,7 +25,7 @@ const isOpen = ref(false)
const hideBackBtn = ref(false)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { state, isNew, removeLTARRef } = useSmartsheetRowStoreOrThrow()

3
packages/nc-gui/components/virtual-cell/OneToOne.vue

@ -1,6 +1,7 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const column = inject(ColumnInj)!
@ -18,7 +19,7 @@ const isForm = inject(IsFormInj, ref(false))
const isUnderLookup = inject(IsUnderLookupInj, ref(false))
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const listItemsDlg = ref(false)

3
packages/nc-gui/components/virtual-cell/components/ItemChip.vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { ColumnType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
interface Props {
value: string | number | boolean
@ -17,7 +18,7 @@ const emit = defineEmits(['unlink'])
const { relatedTableMeta } = useLTARStoreOrThrow()!
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const readOnly = inject(ReadonlyInj, ref(false))

3
packages/nc-gui/composables/useCalendarViewStore.ts

@ -2,6 +2,7 @@ import type { ComputedRef, Ref } from 'vue'
import type { Api, CalendarRangeType, CalendarType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) =>
list.map(
@ -33,7 +34,7 @@ const [useProvideCalendarViewStore, useCalendarViewStore] = useInjectionState(
const pageDate = ref<dayjs.Dayjs>(dayjs())
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isMobileMode } = useGlobal()

3
packages/nc-gui/composables/useExpandedFormStore.ts

@ -2,6 +2,7 @@ import type { AuditType, ColumnType, CommentType, TableType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import dayjs from 'dayjs'
import {useRolesWrapper} from "~/composables/useRoles";
const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((meta: Ref<TableType>, _row: Ref<Row>) => {
const { $e, $state, $api } = useNuxtApp()
@ -61,7 +62,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
// getters
const displayValue = computed(() => {

3
packages/nc-gui/composables/useKanbanViewStore.ts

@ -1,5 +1,6 @@
import type { ComputedRef, Ref } from 'vue'
import type { Api, ColumnType, KanbanType, SelectOptionType, SelectOptionsType, TableType, ViewType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
type GroupingFieldColOptionsType = SelectOptionType & { collapsed: boolean }
@ -27,7 +28,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const { sharedView, fetchSharedViewData, fetchSharedViewGroupedData } = useSharedView()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))

3
packages/nc-gui/composables/useMapViewDataStore.ts

@ -1,5 +1,6 @@
import type { ComputedRef, Ref } from 'vue'
import type { ColumnType, MapType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) =>
list.map(
@ -32,7 +33,7 @@ const [useProvideMapViewStore, useMapViewStore] = useInjectionState(
const { $api } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const isPublic = ref(shared) || inject(IsPublicInj, ref(false))

39
packages/nc-gui/composables/useRoles/index.ts

@ -1,5 +1,5 @@
import { isString } from '@vue/shared'
import type { Roles, RolesObj, WorkspaceUserRoles } from 'nocodb-sdk'
import { type Roles, type RolesObj, SourceRestriction, type SourceType, type WorkspaceUserRoles } from 'nocodb-sdk'
import { extractRolesObj } from 'nocodb-sdk'
const hasPermission = (role: Exclude<Roles, WorkspaceUserRoles>, hasRole: boolean, permission: Permission | string) => {
@ -129,7 +129,10 @@ export const useRoles = createSharedComposable(() => {
const isUIAllowed = (
permission: Permission | string,
args: { roles?: string | Record<string, boolean> | string[] | null } = {},
args: {
roles?: string | Record<string, boolean> | string[] | null
source?: SourceType & { meta?: Record<string, any> }
} = {},
) => {
const { roles } = args
@ -141,6 +144,26 @@ export const useRoles = createSharedComposable(() => {
checkRoles = extractRolesObj(roles)
}
// check source level restrictions
if (
sourceRestrictions[SourceRestriction.DATA_READONLY][permission] ||
sourceRestrictions[SourceRestriction.META_READONLY][permission]
) {
const source = unref(args.source || null)
if (!source) {
console.warn('Source not found', permission, new Error().stack)
return false
}
if (source?.meta?.[SourceRestriction.DATA_READONLY] && sourceRestrictions[SourceRestriction.DATA_READONLY][permission]) {
return false
}
if (source?.meta?.[SourceRestriction.META_READONLY] && sourceRestrictions[SourceRestriction.META_READONLY][permission]) {
return false
}
}
return Object.entries(checkRoles).some(([role, hasRole]) =>
hasPermission(role as Exclude<Roles, WorkspaceUserRoles>, hasRole, permission),
)
@ -148,3 +171,15 @@ export const useRoles = createSharedComposable(() => {
return { allRoles, orgRoles, workspaceRoles, baseRoles, loadRoles, isUIAllowed }
})
export const useRolesWrapper = () => {
const currentSource = inject(ActiveSourceInj, ref())
const useRolesRes = useRoles()
return {
...useRolesRes,
isUIAllowed: (...args: Parameters<ReturnType<typeof useRoles>['isUIAllowed']>) => {
return useRolesRes.isUIAllowed(args[0], { source: currentSource, ...(args[1] || {}) })
},
}
}

3
packages/nc-gui/composables/useViewColumns.ts

@ -1,6 +1,7 @@
import type { ColumnType, GridColumnReqType, GridColumnType, MapType, TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes, isHiddenCol, isSystemColumn } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import {useRolesWrapper} from "~/composables/useRoles";
const [useProvideViewColumns, useViewColumns] = useInjectionState(
(
@ -17,7 +18,7 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase())

3
packages/nc-gui/composables/useViewData.ts

@ -3,6 +3,7 @@ import axios from 'axios'
import type { Api, ColumnType, FormColumnType, FormType, GalleryType, PaginatedType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import { NavigateDir } from '#imports'
import {useRolesWrapper} from "~/composables/useRoles";
const formatData = (list: Record<string, any>[]) =>
list.map((row) => ({
@ -59,7 +60,7 @@ export function useViewData(
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const routeQuery = computed(() => route.value.query as Record<string, string>)

3
packages/nc-gui/composables/useViewFilters.ts

@ -9,6 +9,7 @@ import {
import type { ComputedRef, Ref } from 'vue'
import type { SelectProps } from 'ant-design-vue'
import { UITypes, isSystemColumn } from 'nocodb-sdk'
import {useRolesWrapper} from "~/composables/useRoles";
type ColumnFilterType = FilterType & { status?: string; id?: string; children?: ColumnFilterType[]; is_group?: boolean }
@ -41,7 +42,7 @@ export function useViewFilters(
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { metas, getMeta } = useMetas()

3
packages/nc-gui/composables/useViewGroupBy.ts

@ -2,6 +2,7 @@ import type { ColumnType, LinkToAnotherRecordType, LookupType, SelectOptionsType
import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { message } from 'ant-design-vue'
import {useRolesWrapper} from "~/composables/useRoles";
const excludedGroupingUidt = [UITypes.Attachment, UITypes.QrCode, UITypes.Barcode]
@ -48,7 +49,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
const isGroupBy = computed(() => !!groupBy.value.length)
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { sorts, nestedFilters } = useSmartsheetStoreOrThrow()

2
packages/nc-gui/composables/useViewSorts.ts

@ -8,7 +8,7 @@ export function useViewSorts(view: Ref<ViewType | undefined>, reloadData?: () =>
const { $api, $e } = useNuxtApp()
const { isUIAllowed } = useRoles()
const { isUIAllowed } = useRolesWrapper()
const { isSharedBase } = storeToRefs(useBase())

9
packages/nc-gui/context/index.ts

@ -1,4 +1,4 @@
import type { ColumnType, FilterType, TableType, ViewType } from 'nocodb-sdk'
import type { ColumnType, FilterType, SourceType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, InjectionKey, Ref } from 'vue'
import type { EventHook } from '@vueuse/core'
import type { PageSidebarNode } from '#imports'
@ -60,3 +60,10 @@ export const JsonExpandInj: InjectionKey<Ref<boolean>> = Symbol('json-expand-inj
export const AllFiltersInj: InjectionKey<Ref<Record<string, FilterType[]>>> = Symbol('all-filters-injection')
export const IsAdminPanelInj: InjectionKey<Ref<boolean>> = Symbol('is-admin-panel-injection')
export const ActiveSourceInj: InjectionKey<
ComputedRef<
SourceType & {
meta?: Record<string, any>
}
>
> = Symbol('active-source-injection')

8
packages/nocodb-sdk/src/lib/enums.ts

@ -326,3 +326,11 @@ export enum APIContext {
FILTERS = 'filters',
SORTS = 'sorts',
}
export enum SourceRestriction {
META_READONLY = 'metaReadOnly',
DATA_READONLY = 'dataReadOnly',
}

7
packages/nocodb/src/services/public-datas.service.ts

@ -1,7 +1,7 @@
import path from 'path';
import { Injectable } from '@nestjs/common';
import { nanoid } from 'nanoid';
import { populateUniqueFileName, UITypes, ViewTypes } from 'nocodb-sdk';
import {populateUniqueFileName, SourceRestriction, UITypes, ViewTypes} from 'nocodb-sdk';
import slash from 'slash';
import { nocoExecute } from 'nc-help';
@ -293,6 +293,11 @@ export class PublicDatasService {
});
const source = await Source.get(context, model.source_id);
if (source?.meta?.[SourceRestriction.DATA_READONLY]) {
NcError.forbidden('Data insert is restricted');
}
const base = await source.getProject(context);
const baseModel = await Model.getBaseModelSQL(context, {

16
packages/nocodb/src/utils/acl.ts

@ -450,4 +450,20 @@ Object.values(rolePermissions).forEach((role) => {
}
});
// excluded/restricted permissions at source level based on source restriction
// `true` means permission is restricted and `false`/missing means permission is allowed
export const sourceRestrictions = {
[SourceRestriction.DATA_READONLY]: {
dataInsert: true,
dataEdit: true,
dataDelete: true,
},
[SourceRestriction.META_READONLY]: {
tableCreate: true,
tableRename: true,
tableDelete: true,
tableDuplicate: true,
},
}
export default rolePermissions;

Loading…
Cancel
Save