diff --git a/apps/studio/hooks/misc/useCheckPermissions.ts b/apps/studio/hooks/misc/useCheckPermissions.ts index b88983594e0..e6a57fb5e1d 100644 --- a/apps/studio/hooks/misc/useCheckPermissions.ts +++ b/apps/studio/hooks/misc/useCheckPermissions.ts @@ -1,4 +1,4 @@ -import { useIsLoggedIn } from 'common' +import { useIsLoggedIn, useParams } from 'common' import jsonLogic from 'json-logic-js' import { useOrganizationsQuery } from 'data/organizations/organizations-query' @@ -6,38 +6,76 @@ import { usePermissionsQuery } from 'data/permissions/permissions-query' import { IS_PLATFORM } from 'lib/constants' import type { Permission } from 'types' import { useSelectedOrganization } from './useSelectedOrganization' +import { useSelectedProject } from './useSelectedProject' +import { useProjectDetailQuery } from 'data/projects/project-detail-query' const toRegexpString = (actionOrResource: string) => `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$` +function doPermissionConditionCheck(permissions: Permission[], data?: object) { + const isRestricted = permissions + .filter((permission) => permission.restrictive) + .some( + ({ condition }: { condition: jsonLogic.RulesLogic }) => + condition === null || jsonLogic.apply(condition, data) + ) + if (isRestricted) return false + + return permissions + .filter((permission) => !permission.restrictive) + .some( + ({ condition }: { condition: jsonLogic.RulesLogic }) => + condition === null || jsonLogic.apply(condition, data) + ) +} + export function doPermissionsCheck( permissions: Permission[] | undefined, action: string, resource: string, data?: object, - organizationId?: number + organizationId?: number, + projectId?: number ) { if (!permissions || !Array.isArray(permissions)) { return false } - return permissions - .filter( + if (projectId) { + const projectPermissions = permissions.filter( (permission) => permission.organization_id === organizationId && permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) && - permission.resources.some((res) => resource.match(toRegexpString(res))) - ) - .some( - ({ condition }: { condition: jsonLogic.RulesLogic }) => - condition === null || jsonLogic.apply(condition, { resource_name: resource, ...data }) + permission.resources.some((res) => resource.match(toRegexpString(res))) && + permission.project_ids?.includes(projectId) ) + if (projectPermissions.length > 0) { + return doPermissionConditionCheck(projectPermissions, { resource_name: resource, ...data }) + } + } + + const orgPermissions = permissions.filter( + (permission) => + permission.organization_id === organizationId && + permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) && + permission.resources.some((res) => resource.match(toRegexpString(res))) + ) + return doPermissionConditionCheck(orgPermissions, { resource_name: resource, ...data }) } export function useGetPermissions( permissionsOverride?: Permission[], organizationIdOverride?: number, enabled = true +) { + return useGetProjectPermissions(permissionsOverride, organizationIdOverride, undefined, enabled) +} + +export function useGetProjectPermissions( + permissionsOverride?: Permission[], + organizationIdOverride?: number, + projectIdOverride?: number, + enabled = true ) { const permissionsResult = usePermissionsQuery({ enabled: permissionsOverride === undefined && enabled, @@ -54,9 +92,17 @@ export function useGetPermissions( organizationIdOverride === undefined ? organizationResult : { id: organizationIdOverride } const organizationId = organization?.id + const projectResult = useSelectedProject({ + enabled: projectIdOverride === undefined && enabled, + }) + + const project = projectIdOverride === undefined ? projectResult : { id: projectIdOverride } + const projectId = project?.id + return { permissions, organizationId, + projectId, } } @@ -68,19 +114,32 @@ export function useCheckPermissions( // e.g If you want to use useCheckPermissions in a loop like organization settings organizationId?: number, permissions?: Permission[] +) { + return useCheckProjectPermissions(action, resource, data, organizationId, undefined, permissions) +} + +export function useCheckProjectPermissions( + action: string, + resource: string, + data?: object, + // [Joshen] Pass the variables if you want to avoid hooks in this + // e.g If you want to use useCheckPermissions in a loop like organization settings + organizationId?: number, + projectId?: number, + permissions?: Permission[] ) { const isLoggedIn = useIsLoggedIn() - const { permissions: allPermissions, organizationId: orgId } = useGetPermissions( - permissions, - organizationId, - isLoggedIn - ) + const { + permissions: allPermissions, + organizationId: orgId, + projectId: _projectId, + } = useGetProjectPermissions(permissions, organizationId, projectId, isLoggedIn) if (!isLoggedIn) return false if (!IS_PLATFORM) return true - return doPermissionsCheck(allPermissions, action, resource, data, orgId) + return doPermissionsCheck(allPermissions, action, resource, data, orgId, _projectId) } export function usePermissionsLoaded() { @@ -88,7 +147,17 @@ export function usePermissionsLoaded() { const { isFetched: isPermissionsFetched } = usePermissionsQuery({ enabled: isLoggedIn }) const { isFetched: isOrganizationsFetched } = useOrganizationsQuery({ enabled: isLoggedIn }) + const { ref } = useParams() + const { isFetched: isProjectDetailFetched } = useProjectDetailQuery( + { ref }, + { enabled: !!ref && isLoggedIn } + ) + if (!IS_PLATFORM) return true + if (ref) { + return isLoggedIn && isPermissionsFetched && isOrganizationsFetched && isProjectDetailFetched + } + return isLoggedIn && isPermissionsFetched && isOrganizationsFetched } diff --git a/apps/studio/hooks/misc/useSelectedProject.ts b/apps/studio/hooks/misc/useSelectedProject.ts index e6c2fe6e086..e90ec47fbb6 100644 --- a/apps/studio/hooks/misc/useSelectedProject.ts +++ b/apps/studio/hooks/misc/useSelectedProject.ts @@ -3,9 +3,9 @@ import { useProjectDetailQuery } from 'data/projects/project-detail-query' import { useProjectsQuery } from 'data/projects/projects-query' import { useMemo } from 'react' -export function useSelectedProject() { +export function useSelectedProject({ enabled = true } = {}) { const { ref } = useParams() - const { data } = useProjectDetailQuery({ ref }) + const { data } = useProjectDetailQuery({ ref }, { enabled }) return useMemo( () => data && { ...data, parentRef: data?.parent_project_ref ?? data?.ref }, diff --git a/apps/studio/types/base.ts b/apps/studio/types/base.ts index 23c593e2307..a88549c2c86 100644 --- a/apps/studio/types/base.ts +++ b/apps/studio/types/base.ts @@ -81,6 +81,8 @@ export interface Permission { condition: jsonLogic.RulesLogic organization_id: number resources: string[] + restrictive?: boolean + project_ids?: number[] } export interface ResponseFailure {