diff --git a/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx b/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx index cded49eff8..f378531feb 100644 --- a/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx +++ b/packages/editor/src/components/assets/FileBrowser/FileBrowserContentPanel.tsx @@ -83,6 +83,7 @@ import { FilePropertiesPanel } from './FilePropertiesPanel' type FileBrowserContentPanelProps = { onSelectionChanged: (assetSelectionChange: AssetSelectionChangePropsType) => void disableDnD?: boolean + projectName?: string selectedFile?: string folderName?: string nestingDirectory?: string @@ -386,6 +387,7 @@ const FileBrowserContentPanel: React.FC = (props) key: { $in: isListView ? files.map((file) => file.key) : [] }, + project: props.projectName, $select: ['key', 'updatedAt'] as any, $limit: FILES_PAGE_LIMIT } diff --git a/packages/editor/src/components/assets/FileBrowser/FileBrowserGrid.tsx b/packages/editor/src/components/assets/FileBrowser/FileBrowserGrid.tsx index 36c8d8f132..c6395d7f26 100755 --- a/packages/editor/src/components/assets/FileBrowser/FileBrowserGrid.tsx +++ b/packages/editor/src/components/assets/FileBrowser/FileBrowserGrid.tsx @@ -34,7 +34,7 @@ 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' @@ -42,6 +42,7 @@ 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' @@ -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 = { @@ -178,7 +184,13 @@ type FileGridItemProps = { export const FileGridItem: React.FC = (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 (
diff --git a/packages/editor/src/components/assets/SceneAssetsPanel.tsx b/packages/editor/src/components/assets/SceneAssetsPanel.tsx index c021027dc7..6d0eb6e776 100644 --- a/packages/editor/src/components/assets/SceneAssetsPanel.tsx +++ b/packages/editor/src/components/assets/SceneAssetsPanel.tsx @@ -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' @@ -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 }) => { @@ -124,6 +125,7 @@ const SceneAssetsPanel = () => { const searchText = useHookstate('') const searchTimeoutCancelRef = useRef<(() => void) | null>(null) const searchedStaticResources = useHookstate([]) + const { projectName } = useMutableState(EditorState) const AssetCategory = useCallback( (props: { @@ -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) { diff --git a/packages/server-core/src/media/file-browser/file-browser.class.ts b/packages/server-core/src/media/file-browser/file-browser.class.ts index 3ac06c9034..ad4241c0f5 100755 --- a/packages/server-core/src/media/file-browser/file-browser.class.ts +++ b/packages/server-core/src/media/file-browser/file-browser.class.ts @@ -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' ) }) } diff --git a/packages/server-core/src/media/static-resource/static-resource.hooks.ts b/packages/server-core/src/media/static-resource/static-resource.hooks.ts index 8e5b51adf7..adb90c7912 100755 --- a/packages/server-core/src/media/static-resource/static-resource.hooks.ts +++ b/packages/server-core/src/media/static-resource/static-resource.hooks.ts @@ -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, @@ -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' @@ -66,6 +69,26 @@ const ensureResource = async (context: HookContext) => { } } +/** + * Gets the name of the project to which the resource belongs + * @param context + * @returns + */ +const getProjectName = async (context: HookContext) => { + 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: [ @@ -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: { diff --git a/packages/ui/src/components/editor/panels/Assets/container/index.tsx b/packages/ui/src/components/editor/panels/Assets/container/index.tsx index 66261dee5f..44f9368bf3 100644 --- a/packages/ui/src/components/editor/panels/Assets/container/index.tsx +++ b/packages/ui/src/components/editor/panels/Assets/container/index.tsx @@ -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 { @@ -217,6 +218,7 @@ const AssetPanel = () => { const searchedStaticResources = useHookstate([]) const searchText = useHookstate('') const breadcrumbPath = useHookstate('') + const { projectName } = useMutableState(EditorState) const CategoriesList = () => { return ( @@ -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 } diff --git a/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx index a942ee7232..df04abf096 100644 --- a/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx +++ b/packages/ui/src/components/editor/panels/Files/browserGrid/index.tsx @@ -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' @@ -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 = { @@ -151,7 +153,8 @@ type FileGridItemProps = { export const FileGridItem: React.FC = (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 (
void disableDnD?: boolean selectedFile?: string @@ -391,6 +392,7 @@ const FileBrowserContentPanel: React.FC = (props) key: { $in: isListView ? files.map((file) => file.key) : [] }, + project: props.projectName, $select: ['key', 'updatedAt'] as any, $limit: FILES_PAGE_LIMIT } @@ -639,7 +641,11 @@ export default function FilesPanelContainer() { return ( <> - + ) }