Skip to content
This repository has been archived by the owner on Aug 21, 2024. It is now read-only.

Commit

Permalink
IR-2155: Check for project permissions in static resource service (#1…
Browse files Browse the repository at this point in the history
…0310)

* feat: implemented project permissions in static resource service

* feat: updated regex to include

* chore: replace getMutableState with useMutable and added hooks to check permissions on remove static ressource

* chore: drop project from query

* feat: file filter to work with standalone project and multitenancy

* chore: code formatting

---------

Co-authored-by: Josh Field <[email protected]>
  • Loading branch information
jose-galvan and HexaField authored Jun 11, 2024
1 parent b848f2c commit 5406697
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import { FilePropertiesPanel } from './FilePropertiesPanel'
type FileBrowserContentPanelProps = {
onSelectionChanged: (assetSelectionChange: AssetSelectionChangePropsType) => void
disableDnD?: boolean
projectName?: string
selectedFile?: string
folderName?: string
nestingDirectory?: string
Expand Down Expand Up @@ -386,6 +387,7 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
key: {
$in: isListView ? files.map((file) => file.key) : []
},
project: props.projectName,
$select: ['key', 'updatedAt'] as any,
$limit: FILES_PAGE_LIMIT
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import { useTranslation } from 'react-i18next'
import { Vector3 } from 'three'

import { fileBrowserPath, staticResourcePath } from '@etherealengine/common/src/schema.type.module'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
import { useFind, useMutation } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
import Paper from '@etherealengine/ui/src/primitives/mui/Paper'

import { SupportedFileTypes } from '../../../constants/AssetTypes'
import { addMediaNode } from '../../../functions/addMediaNode'
import { getSpawnPositionAtCenter } from '../../../functions/screenSpaceFunctions'
import { EditorState } from '../../../services/EditorServices'
import { ContextMenu } from '../../layout/ContextMenu'
import styles from '../styles.module.scss'
import { availableTableColumns, FilesViewModeSettings } from './FileBrowserState'
Expand Down Expand Up @@ -132,8 +133,13 @@ export const FileTableListBody = ({
const fontSize = useHookstate(getMutableState(FilesViewModeSettings).list.fontSize).value
const dragFn = drag ?? ((input) => input)
const dropFn = drop ?? ((input) => input)

const staticResource = useFind(staticResourcePath, { query: { key: file.key } })
const { projectName } = useMutableState(EditorState)
const staticResource = useFind(staticResourcePath, {
query: {
key: file.key,
project: projectName.value!
}
})
const thumbnailURL = staticResource.data[0]?.thumbnailURL

const tableColumns = {
Expand Down Expand Up @@ -178,7 +184,13 @@ type FileGridItemProps = {

export const FileGridItem: React.FC<FileGridItemProps> = (props) => {
const iconSize = useHookstate(getMutableState(FilesViewModeSettings).icons.iconSize).value
const staticResource = useFind(staticResourcePath, { query: { key: props.item.key } })
const { projectName } = useMutableState(EditorState)
const staticResource = useFind(staticResourcePath, {
query: {
key: props.item.key,
project: projectName.value!
}
})
const thumbnailURL = staticResource.data[0]?.thumbnailURL
return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import { logger } from '@etherealengine/client-core/src/user/services/AuthServic
import { staticResourcePath, StaticResourceType } from '@etherealengine/common/src/schema.type.module'
import { projectResourcesPath } from '@etherealengine/common/src/schemas/media/project-resource.schema'
import { Engine } from '@etherealengine/ecs/src/Engine'
import { NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux'
import { getMutableState, NO_PROXY, State, useHookstate } from '@etherealengine/hyperflux'
import { useFind } from '@etherealengine/spatial/src/common/functions/FeathersHooks'

import { EditorState } from '../../../services/EditorServices'
import { Button } from '../../inputs/Button'
import styles from '../styles.module.scss'
import { FileType } from './FileBrowserContentPanel'
Expand Down Expand Up @@ -77,7 +78,8 @@ export const FilePropertiesPanel = (props: {

const staticResource = useFind(staticResourcePath, {
query: {
key: fileProperties.value!.key
key: fileProperties.value!.key,
project: getMutableState(EditorState).projectName.value!
}
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default function ProjectBrowserPanel() {
title: t('editor:layout.filebrowser.tab-name'),
content: (
<FileBrowserContentPanel
projectName={projectName ?? undefined}
selectedFile={projectName ?? undefined}
onSelectionChanged={onSelectionChanged}
/>
Expand Down
7 changes: 5 additions & 2 deletions packages/editor/src/components/assets/SceneAssetsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { useTranslation } from 'react-i18next'
import { staticResourcePath, StaticResourceType } from '@etherealengine/common/src/schema.type.module'
import { Engine } from '@etherealengine/ecs/src/Engine'
import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
import { getState, NO_PROXY, useHookstate } from '@etherealengine/hyperflux'
import { getState, NO_PROXY, useHookstate, useMutableState } from '@etherealengine/hyperflux'

import { DockContainer } from '../EditorContainer'
import StringInput from '../inputs/StringInput'
Expand All @@ -46,6 +46,7 @@ import { FileIcon } from './FileBrowser/FileIcon'

import { AssetsPanelCategories } from './AssetsPanelCategories'

import { EditorState } from '../../services/EditorServices'
import styles from './styles.module.scss'

const ResourceFile = ({ resource }: { resource: StaticResourceType }) => {
Expand Down Expand Up @@ -124,6 +125,7 @@ const SceneAssetsPanel = () => {
const searchText = useHookstate('')
const searchTimeoutCancelRef = useRef<(() => void) | null>(null)
const searchedStaticResources = useHookstate<StaticResourceType[]>([])
const { projectName } = useMutableState(EditorState)

const AssetCategory = useCallback(
(props: {
Expand Down Expand Up @@ -190,7 +192,8 @@ const SceneAssetsPanel = () => {
const query = {
key: { $like: `%${searchText.value}%` || undefined },
$sort: { mimeType: 1 },
$limit: 10000
$limit: 10000,
project: projectName.value!
}

if (selectedCategory.value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,12 +156,8 @@ export class FileBrowserService

const allowedProjectNames = projectPermissions.map((permission) => permission.project.name)
result = result.filter((item) => {
const projectRegexExec = /projects\/(.+)$/.exec(item.key)
const subFileRegexExec = /projects\/(.+)\//.exec(item.key)
return (
(subFileRegexExec && allowedProjectNames.indexOf(subFileRegexExec[1]) > -1) ||
(projectRegexExec && allowedProjectNames.indexOf(projectRegexExec[1]) > -1) ||
item.name === 'projects'
allowedProjectNames.some((project) => item.key.startsWith(`projects/${project}`)) || item.name === 'projects'
)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ Original Code is the Ethereal Engine team.
All portions of the code written by the Ethereal Engine team are Copyright © 2021-2023
Ethereal Engine. All Rights Reserved.
*/
import { Forbidden } from '@feathersjs/errors'
import { BadRequest, Forbidden, NotFound } from '@feathersjs/errors'
import { hooks as schemaHooks } from '@feathersjs/schema'
import { disallow, discardQuery, iff, isProvider } from 'feathers-hooks-common'
import { disallow, discardQuery, iff, iffElse, isProvider } from 'feathers-hooks-common'

import {
staticResourceDataValidator,
Expand All @@ -35,7 +35,10 @@ import {
import collectAnalytics from '@etherealengine/server-core/src/hooks/collect-analytics'

import { HookContext } from '../../../declarations'
import checkScope from '../../hooks/check-scope'
import resolveProjectId from '../../hooks/resolve-project-id'
import setLoggedinUserInBody from '../../hooks/set-loggedin-user-in-body'
import verifyProjectPermission from '../../hooks/verify-project-permission'
import verifyScope from '../../hooks/verify-scope'
import { getStorageProvider } from '../storageprovider/storageprovider'
import { StaticResourceService } from './static-resource.class'
Expand Down Expand Up @@ -66,6 +69,26 @@ const ensureResource = async (context: HookContext<StaticResourceService>) => {
}
}

/**
* Gets the name of the project to which the resource belongs
* @param context
* @returns
*/
const getProjectName = async (context: HookContext<StaticResourceService>) => {
if (!context.id) {
throw new BadRequest('Static Resource id missing in the request')
}
const resource = await context.app.service(staticResourcePath).get(context.id)
if (!resource) {
throw new NotFound('resource not found.')
}
context.params.query = {
...context.params.query,
project: resource.project
}
return context
}

export default {
around: {
all: [
Expand All @@ -80,24 +103,61 @@ export default {
schemaHooks.resolveQuery(staticResourceQueryResolver)
],
find: [
iff(isProvider('external'), verifyScope('static_resource', 'read')),
discardQuery('action'),
iff(
isProvider('external'),
iffElse(
checkScope('static_resource', 'read'),
[],
[verifyScope('editor', 'write'), resolveProjectId(), verifyProjectPermission(['owner', 'editor', 'reviewer'])]
)
),
discardQuery('action', 'project', 'projectId'),
collectAnalytics()
],
get: [disallow('external')],
create: [
iff(isProvider('external'), verifyScope('static_resource', 'write')),
iff(
isProvider('external'),
iffElse(
checkScope('static_resource', 'write'),
[],
[verifyScope('editor', 'write'), resolveProjectId(), verifyProjectPermission(['owner', 'editor'])]
)
),
setLoggedinUserInBody('userId'),
() => schemaHooks.validateData(staticResourceDataValidator),
schemaHooks.resolveData(staticResourceDataResolver)
],
update: [disallow()],
patch: [
iff(isProvider('external'), verifyScope('static_resource', 'write')),
iff(
isProvider('external'),
iffElse(
checkScope('static_resource', 'write'),
[],
[verifyScope('editor', 'write'), resolveProjectId(), verifyProjectPermission(['owner', 'editor'])]
)
),
() => schemaHooks.validateData(staticResourcePatchValidator),
schemaHooks.resolveData(staticResourcePatchResolver)
],
remove: [iff(isProvider('external'), verifyScope('static_resource', 'write')), ensureResource]
remove: [
iff(
isProvider('external'),
iffElse(
checkScope('static_resource', 'write'),
[],
[
verifyScope('editor', 'write'),
getProjectName,
resolveProjectId(),
verifyProjectPermission(['owner', 'editor'])
]
)
),
discardQuery('project', 'projectId'),
ensureResource
]
},

after: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ import { staticResourcePath, StaticResourceType } from '@etherealengine/common/s
import { Engine } from '@etherealengine/ecs/src/Engine'
import { AssetsPanelCategories } from '@etherealengine/editor/src/components/assets/AssetsPanelCategories'
import { AssetSelectionChangePropsType } from '@etherealengine/editor/src/components/assets/AssetsPreviewPanel'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { AssetLoader } from '@etherealengine/engine/src/assets/classes/AssetLoader'
import { getState, State, useHookstate } from '@etherealengine/hyperflux'
import { getState, State, useHookstate, useMutableState } from '@etherealengine/hyperflux'
import { useDrag } from 'react-dnd'
import { getEmptyImage } from 'react-dnd-html5-backend'
import {
Expand Down Expand Up @@ -217,6 +218,7 @@ const AssetPanel = () => {
const searchedStaticResources = useHookstate<StaticResourceType[]>([])
const searchText = useHookstate('')
const breadcrumbPath = useHookstate('')
const { projectName } = useMutableState(EditorState)

const CategoriesList = () => {
return (
Expand Down Expand Up @@ -274,7 +276,10 @@ const AssetPanel = () => {
useEffect(() => {
const staticResourcesFindApi = () => {
const query = {
key: { $like: `%${searchText.value}%` || undefined },
key: {
$like: `%${searchText.value}%` || undefined
},
project: projectName.value!,
$sort: { mimeType: 1 },
$limit: 10000
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ import { FileDataType } from '@etherealengine/editor/src/components/assets/FileB
import { SupportedFileTypes } from '@etherealengine/editor/src/constants/AssetTypes'
import { addMediaNode } from '@etherealengine/editor/src/functions/addMediaNode'
import { getSpawnPositionAtCenter } from '@etherealengine/editor/src/functions/screenSpaceFunctions'
import { getMutableState, useHookstate } from '@etherealengine/hyperflux'
import { EditorState } from '@etherealengine/editor/src/services/EditorServices'
import { getMutableState, useHookstate, useMutableState } from '@etherealengine/hyperflux'
import { useFind, useMutation } from '@etherealengine/spatial/src/common/functions/FeathersHooks'
import { TransformComponent } from '@etherealengine/spatial/src/transform/components/TransformComponent'
import React, { MouseEventHandler, MutableRefObject, useEffect } from 'react'
Expand Down Expand Up @@ -108,7 +109,8 @@ export const FileTableListBody = ({
const dragFn = drag ?? ((input) => input)
const dropFn = drop ?? ((input) => input)

const staticResource = useFind(staticResourcePath, { query: { key: file.key } })
const { projectName } = useMutableState(EditorState)
const staticResource = useFind(staticResourcePath, { query: { key: file.key, project: projectName.value! } })
const thumbnailURL = staticResource.data[0]?.thumbnailURL

const tableColumns = {
Expand Down Expand Up @@ -151,7 +153,8 @@ type FileGridItemProps = {

export const FileGridItem: React.FC<FileGridItemProps> = (props) => {
const iconSize = useHookstate(getMutableState(FilesViewModeSettings).icons.iconSize).value
const staticResource = useFind(staticResourcePath, { query: { key: props.item.key } })
const { projectName } = useMutableState(EditorState)
const staticResource = useFind(staticResourcePath, { query: { key: props.item.key, project: projectName.value! } })
const thumbnailURL = staticResource.data[0]?.thumbnailURL
return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ import Popover from '../../../layout/Popover'
import { FileBrowserItem, FileTableWrapper, canDropItemOverFolder } from '../browserGrid'

type FileBrowserContentPanelProps = {
projectName?: string
onSelectionChanged: (assetSelectionChange: AssetSelectionChangePropsType) => void
disableDnD?: boolean
selectedFile?: string
Expand Down Expand Up @@ -391,6 +392,7 @@ const FileBrowserContentPanel: React.FC<FileBrowserContentPanelProps> = (props)
key: {
$in: isListView ? files.map((file) => file.key) : []
},
project: props.projectName,
$select: ['key', 'updatedAt'] as any,
$limit: FILES_PAGE_LIMIT
}
Expand Down Expand Up @@ -639,7 +641,11 @@ export default function FilesPanelContainer() {

return (
<>
<FileBrowserContentPanel selectedFile={projectName ?? undefined} onSelectionChanged={onSelectionChanged} />
<FileBrowserContentPanel
projectName={projectName!}
selectedFile={projectName ?? undefined}
onSelectionChanged={onSelectionChanged}
/>
</>
)
}

0 comments on commit 5406697

Please sign in to comment.