useCheckPermissions.ts 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import { useIsLoggedIn, useParams } from 'common'
  2. import jsonLogic from 'json-logic-js'
  3. import { useMemo } from 'react'
  4. import { useSelectedOrganizationQuery } from './useSelectedOrganization'
  5. import { useSelectedProjectQuery } from './useSelectedProject'
  6. import { usePermissionsQuery } from '@/data/permissions/permissions-query'
  7. import { IS_PLATFORM } from '@/lib/constants'
  8. import type { Permission } from '@/types'
  9. const toRegexpString = (actionOrResource: string) =>
  10. `^${actionOrResource.replace('.', '\\.').replace('%', '.*')}$`
  11. function doPermissionConditionCheck(permissions: Permission[], data?: object) {
  12. const isRestricted = permissions
  13. .filter((permission) => permission.restrictive)
  14. .some(
  15. ({ condition }: { condition: jsonLogic.RulesLogic }) =>
  16. condition === null || jsonLogic.apply(condition, data)
  17. )
  18. if (isRestricted) return false
  19. return permissions
  20. .filter((permission) => !permission.restrictive)
  21. .some(
  22. ({ condition }: { condition: jsonLogic.RulesLogic }) =>
  23. condition === null || jsonLogic.apply(condition, data)
  24. )
  25. }
  26. export function doPermissionsCheck(
  27. permissions: Permission[] | undefined,
  28. action: string,
  29. resource: string,
  30. data?: object,
  31. organizationSlug?: string,
  32. projectRef?: string
  33. ) {
  34. if (!permissions || !Array.isArray(permissions)) {
  35. return false
  36. }
  37. if (projectRef) {
  38. const projectPermissions = permissions.filter(
  39. (permission) =>
  40. permission.organization_slug === organizationSlug &&
  41. permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) &&
  42. permission.resources.some((res) => resource.match(toRegexpString(res))) &&
  43. permission.project_refs?.includes(projectRef)
  44. )
  45. if (projectPermissions.length > 0) {
  46. return doPermissionConditionCheck(projectPermissions, { resource_name: resource, ...data })
  47. }
  48. }
  49. const orgPermissions = permissions
  50. // filter out org-level permission
  51. .filter((permission) => !permission.project_refs || permission.project_refs.length === 0)
  52. .filter(
  53. (permission) =>
  54. permission.organization_slug === organizationSlug &&
  55. permission.actions.some((act) => (action ? action.match(toRegexpString(act)) : null)) &&
  56. permission.resources.some((res) => resource.match(toRegexpString(res)))
  57. )
  58. return doPermissionConditionCheck(orgPermissions, { resource_name: resource, ...data })
  59. }
  60. export function useGetPermissions(
  61. permissionsOverride?: Permission[],
  62. organizationSlugOverride?: string,
  63. enabled = true
  64. ) {
  65. return useGetProjectPermissions(permissionsOverride, organizationSlugOverride, undefined, enabled)
  66. }
  67. function useGetProjectPermissions(
  68. permissionsOverride?: Permission[],
  69. organizationSlugOverride?: string,
  70. projectRefOverride?: string,
  71. enabled = true
  72. ) {
  73. const {
  74. data,
  75. isPending: isLoadingPermissions,
  76. isSuccess: isSuccessPermissions,
  77. } = usePermissionsQuery({
  78. enabled: permissionsOverride === undefined && enabled,
  79. })
  80. const permissions = permissionsOverride === undefined ? data : permissionsOverride
  81. const getOrganizationDataFromParamsSlug = organizationSlugOverride === undefined && enabled
  82. const {
  83. data: organizationData,
  84. isPending: isLoadingOrganization,
  85. isSuccess: isSuccessOrganization,
  86. } = useSelectedOrganizationQuery({
  87. enabled: getOrganizationDataFromParamsSlug,
  88. })
  89. const organization =
  90. organizationSlugOverride === undefined ? organizationData : { slug: organizationSlugOverride }
  91. const organizationSlug = organization?.slug
  92. const { ref: urlProjectRef } = useParams()
  93. const getProjectDataFromParamsRef = !!urlProjectRef && projectRefOverride === undefined && enabled
  94. const {
  95. data: projectData,
  96. isPending: isLoadingProject,
  97. isSuccess: isSuccessProject,
  98. } = useSelectedProjectQuery({
  99. enabled: getProjectDataFromParamsRef,
  100. })
  101. const project =
  102. projectRefOverride === undefined || projectData?.parent_project_ref
  103. ? projectData
  104. : { ref: projectRefOverride, parent_project_ref: undefined }
  105. const projectRef = project?.parent_project_ref ? project.parent_project_ref : project?.ref
  106. const isLoading =
  107. isLoadingPermissions ||
  108. (getOrganizationDataFromParamsSlug && isLoadingOrganization) ||
  109. (getProjectDataFromParamsRef && isLoadingProject)
  110. const isSuccess =
  111. isSuccessPermissions &&
  112. (!getOrganizationDataFromParamsSlug || isSuccessOrganization) &&
  113. (!getProjectDataFromParamsRef || isSuccessProject)
  114. return {
  115. permissions,
  116. organizationSlug,
  117. projectRef,
  118. isLoading,
  119. isSuccess,
  120. }
  121. }
  122. /** [Joshen] To be renamed to be useAsyncCheckPermissions, more generic as it covers both org and project perms */
  123. // Useful when you want to avoid layout changes while waiting for permissions to load
  124. export function useAsyncCheckPermissions(
  125. action: string,
  126. resource: string,
  127. data?: object,
  128. overrides?: {
  129. organizationSlug?: string
  130. projectRef?: string
  131. permissions?: Permission[]
  132. }
  133. ) {
  134. const isLoggedIn = useIsLoggedIn()
  135. const { organizationSlug, projectRef, permissions } = overrides ?? {}
  136. const {
  137. permissions: allPermissions,
  138. organizationSlug: _organizationSlug,
  139. projectRef: _projectRef,
  140. isLoading: isPermissionsLoading,
  141. isSuccess: isPermissionsSuccess,
  142. } = useGetProjectPermissions(permissions, organizationSlug, projectRef, isLoggedIn)
  143. const can = useMemo(() => {
  144. if (!IS_PLATFORM) return true
  145. if (!isLoggedIn) return false
  146. if (!isPermissionsSuccess || !allPermissions) return false
  147. return doPermissionsCheck(
  148. allPermissions,
  149. action,
  150. resource,
  151. data,
  152. _organizationSlug,
  153. _projectRef
  154. )
  155. }, [
  156. isLoggedIn,
  157. isPermissionsSuccess,
  158. allPermissions,
  159. action,
  160. resource,
  161. data,
  162. _organizationSlug,
  163. _projectRef,
  164. ])
  165. // Derive loading/success consistently from the same branches
  166. const isLoading = !IS_PLATFORM ? false : !isLoggedIn ? true : isPermissionsLoading
  167. const isSuccess = !IS_PLATFORM ? true : !isLoggedIn ? false : isPermissionsSuccess
  168. return { isLoading, isSuccess, can }
  169. }