diff --git a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx index 0defdff3844..0038fdf50ea 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MemberActions.tsx @@ -2,19 +2,18 @@ import * as Tooltip from '@radix-ui/react-tooltip' import { PermissionAction } from '@supabase/shared-types/out/constants' import { observer } from 'mobx-react-lite' import { FC } from 'react' -import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' -import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' -import { checkPermissions, useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' -import { Member, Role } from 'types' - +import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' import { useOrganizationMemberDeleteMutation } from 'data/organizations/organization-member-delete-mutation' import { useOrganizationMemberInviteCreateMutation } from 'data/organizations/organization-member-invite-create-mutation' import { useOrganizationMemberInviteDeleteMutation } from 'data/organizations/organization-member-invite-delete-mutation' -import { isInviteExpired } from '../Organization.utils' -import { getRolesManagementPermissions } from './TeamSettings.utils' import { usePermissionsQuery } from 'data/permissions/permissions-query' +import { checkPermissions, useSelectedOrganization, useStore } from 'hooks' +import { Member, Role } from 'types' +import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui' +import { isInviteExpired } from '../Organization.utils' +import { useGetRolesManagementPermissions } from './TeamSettings.utils' interface Props { member: Member @@ -26,10 +25,11 @@ const MemberActions: FC = ({ member, roles }) => { const { slug } = useParams() const selectedOrganization = useSelectedOrganization() const { data: permissions } = usePermissionsQuery() - const { rolesRemovable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) - : { rolesRemovable: [] as number[] } + const { rolesRemovable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const isExpired = isInviteExpired(member?.invited_at ?? '') const isPendingInviteAcceptance = member.invited_id diff --git a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx index feba9bd2b2f..89549bcc9c6 100644 --- a/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/MembersView.tsx @@ -1,24 +1,23 @@ -import Image from 'next/image' import * as Tooltip from '@radix-ui/react-tooltip' -import { useState, Fragment } from 'react' import { observer } from 'mobx-react-lite' -import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle, IconLoader } from 'ui' +import Image from 'next/image' +import { Fragment, useState } from 'react' -import { Member } from 'types' -import { useSelectedOrganization, useStore } from 'hooks' import { useParams } from 'common/hooks' - -import ShimmeringLoader from 'components/ui/ShimmeringLoader' import Table from 'components/to-be-cleaned/Table' -import MemberActions from './MemberActions' -import RolesHelperModal from './RolesHelperModal/RolesHelperModal' -import { getRolesManagementPermissions } from './TeamSettings.utils' -import { isInviteExpired, getUserDisplayName } from '../Organization.utils' -import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' +import ShimmeringLoader from 'components/ui/ShimmeringLoader' import { useOrganizationDetailQuery } from 'data/organizations/organization-detail-query' import { useOrganizationMemberUpdateMutation } from 'data/organizations/organization-member-update-mutation' -import { useProfileQuery } from 'data/profile/profile-query' +import { useOrganizationRolesQuery } from 'data/organizations/organization-roles-query' import { usePermissionsQuery } from 'data/permissions/permissions-query' +import { useProfileQuery } from 'data/profile/profile-query' +import { useSelectedOrganization, useStore } from 'hooks' +import { Member } from 'types' +import { Badge, Button, IconAlertCircle, IconLoader, IconUser, Listbox, Loading, Modal } from 'ui' +import { getUserDisplayName, isInviteExpired } from '../Organization.utils' +import MemberActions from './MemberActions' +import RolesHelperModal from './RolesHelperModal/RolesHelperModal' +import { useGetRolesManagementPermissions } from './TeamSettings.utils' interface SelectedMember extends Member { oldRoleId: number @@ -55,10 +54,11 @@ const MembersView = ({ searchString }: MembersViewProps) => { const roles = rolesData?.roles ?? [] const members = detailData?.members ?? [] - const { rolesAddable, rolesRemovable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization.id, roles, permissions ?? []) - : { rolesAddable: [] as number[], rolesRemovable: [] as number[] } + const { rolesAddable, rolesRemovable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const [selectedMember, setSelectedMember] = useState() const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false) diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx index 1c2c4704995..600ce662774 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.tsx @@ -13,7 +13,7 @@ import { delete_ } from 'lib/common/fetch' import { API_URL } from 'lib/constants' import InviteMemberButton from './InviteMemberButton' import MembersView from './MembersView' -import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils' +import { hasMultipleOwners, useGetRolesManagementPermissions } from './TeamSettings.utils' const TeamSettings = () => { const { ui } = useStore() @@ -30,10 +30,11 @@ const TeamSettings = () => { const members = detailData?.members ?? [] const roles = rolesData?.roles ?? [] - const { rolesAddable } = - selectedOrganization !== undefined - ? getRolesManagementPermissions(selectedOrganization?.id, roles, permissions ?? []) - : { rolesAddable: [] as number[] } + const { rolesAddable } = useGetRolesManagementPermissions( + selectedOrganization?.id, + roles, + permissions ?? [] + ) const [isLeaving, setIsLeaving] = useState(false) const [searchString, setSearchString] = useState('') diff --git a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts index c25f2235246..a245ffacce2 100644 --- a/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts +++ b/studio/components/interfaces/Organization/TeamSettings/TeamSettings.utils.ts @@ -1,36 +1,42 @@ -import { Member, Permission, Role } from 'types' -import { useCheckPermissions } from 'hooks' import { PermissionAction } from '@supabase/shared-types/out/constants' +import { doPermissionsCheck, useGetPermissions } from 'hooks' +import { Member, Permission, Role } from 'types' -export const getRolesManagementPermissions = ( - orgId: number, - roles: Role[], - permissions: Permission[] +export const useGetRolesManagementPermissions = ( + orgId?: number, + roles?: Role[], + permissions?: Permission[] ): { rolesAddable: Number[]; rolesRemovable: Number[] } => { + const { permissions: allPermissions, organizationId } = useGetPermissions( + permissions, + orgId, + permissions !== undefined && orgId !== undefined + ) + const rolesAddable: Number[] = [] const rolesRemovable: Number[] = [] - if (!roles) return { rolesAddable, rolesRemovable } + if (!roles || !orgId) return { rolesAddable, rolesRemovable } roles.forEach((role: Role) => { - const canAdd = useCheckPermissions( + const canAdd = doPermissionsCheck( + allPermissions, PermissionAction.CREATE, 'auth.subject_roles', { resource: { role_id: role.id }, }, - orgId, - permissions + organizationId ) if (canAdd) rolesAddable.push(role.id) - const canRemove = useCheckPermissions( + const canRemove = doPermissionsCheck( + allPermissions, PermissionAction.DELETE, 'auth.subject_roles', { resource: { role_id: role.id }, }, - orgId, - permissions + organizationId ) if (canRemove) rolesRemovable.push(role.id) }) diff --git a/studio/hooks/misc/useCheckPermissions.ts b/studio/hooks/misc/useCheckPermissions.ts index d46f4a30b30..c54c0a1717f 100644 --- a/studio/hooks/misc/useCheckPermissions.ts +++ b/studio/hooks/misc/useCheckPermissions.ts @@ -2,12 +2,58 @@ import jsonLogic from 'json-logic-js' import { usePermissionsQuery } from 'data/permissions/permissions-query' import { IS_PLATFORM } from 'lib/constants' -import { useSelectedOrganization } from './useSelectedOrganization' import { Permission } from 'types' +import { useSelectedOrganization } from './useSelectedOrganization' const toRegexpString = (actionOrResource: string) => `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$` +export function doPermissionsCheck( + permissions: Permission[] | undefined, + action: string, + resource: string, + data?: object, + organizationId?: number +) { + return (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 }) + ) +} + +export function useGetPermissions( + permissionsOverride?: Permission[], + organizationIdOverride?: number, + enabled = true +) { + const permissionsResult = usePermissionsQuery({ + enabled: permissionsOverride === undefined && enabled, + }) + + const permissions = + permissionsOverride === undefined ? permissionsResult.data : permissionsOverride + + const organizationResult = useSelectedOrganization({ + enabled: organizationIdOverride === undefined && enabled, + }) + + const organization = + organizationIdOverride === undefined ? organizationResult : { id: organizationIdOverride } + const organizationId = organization?.id + + return { + permissions, + organizationId, + } +} + export function useCheckPermissions( action: string, resource: string, @@ -17,32 +63,14 @@ export function useCheckPermissions( organizationId?: number, permissions?: Permission[] ) { - const permissionsResult = usePermissionsQuery({ - enabled: permissions === undefined, - }) - - const allPermissions = permissions === undefined ? permissionsResult.data : permissions - - const organizationResult = useSelectedOrganization({ - enabled: organizationId === undefined, - }) - - const organization = organizationId === undefined ? organizationResult : { id: organizationId } - const orgId = organization?.id + const { permissions: allPermissions, organizationId: orgId } = useGetPermissions( + permissions, + organizationId + ) if (!IS_PLATFORM) return true - return (allPermissions ?? []) - .filter( - (permission) => - permission.organization_id === orgId && - 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 }) - ) + return doPermissionsCheck(allPermissions, action, resource, data, orgId) } /**