| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- import { difference } from 'lodash'
- import { useRouter } from 'next/router'
- import { STORAGE_CLIENT_LIBRARY_MAPPINGS } from './Storage.constants'
- import type { StoragePolicyFormField } from './Storage.types'
- import type { Policy } from '@/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils'
- import { WrapperMeta } from '@/components/interfaces/Integrations/Wrappers/Wrappers.types'
- import { convertKVStringArrayToJson } from '@/components/interfaces/Integrations/Wrappers/Wrappers.utils'
- import { FDW } from '@/data/fdw/fdws-query'
- import { Bucket } from '@/data/storage/buckets-query'
- import { getDecryptedValues } from '@/data/vault/vault-secret-decrypted-value-query'
- import { createWrappedSymbol } from '@/lib/helpers'
- const shortHash = (str: string) => {
- let hash = 0
- for (let i = 0; i < str.length; i++) {
- const char = str.charCodeAt(i)
- hash = (hash << 5) - hash + char
- hash &= hash // Convert to 32bit integer
- }
- return new Uint32Array([hash])[0].toString(36)
- }
- export type PoliciesByBucket = { name: string | Symbol; policies: Policy[] }[]
- /**
- * Formats the policies from the objects table in the storage schema
- * to be consumable for the storage policies dashboard.
- *
- * @param policies All policies from a table in a schema
- */
- export const formatPoliciesForStorage = (
- buckets: Bucket[],
- policies: Policy[]
- ): PoliciesByBucket => {
- if (policies.length === 0) return []
- /**
- * Format policies from storage objects to:
- * - Include bucket name
- * - Strip away ${bucketName}_{idx} suffix
- * - Strip away bucket_id from definitions
- * Note, if the policy definition has no bucket_id, we skip the formatting
- */
- const formattedPolicies = formatStoragePolicies(buckets, policies)
- const policiesByBucket = groupPoliciesByBucket(formattedPolicies)
- return policiesByBucket
- }
- /**
- * Policy that belongs to a bucket which is not loaded yet (might not have been
- * paginated to yet, or might have been deleted)
- */
- export const UNKNOWN_BUCKET_SYMBOL = createWrappedSymbol('unknown-bucket', 'Unknown')
- /**
- * Policy that is not associated with a specific bucket
- */
- export const UNGROUPED_POLICY_SYMBOL = createWrappedSymbol('ungrouped-policy', 'Ungrouped')
- const formatStoragePolicies = (buckets: Bucket[], policies: Policy[]) => {
- const availableBuckets = buckets.map((bucket) => bucket.name)
- const formattedPolicies = policies.map((policy) => {
- const { definition: policyDefinition, check: policyCheck } = policy
- const bucketName =
- policyDefinition !== null
- ? extractBucketNameFromDefinition(policyDefinition)
- : extractBucketNameFromDefinition(policyCheck)
- if (bucketName) {
- const isBucketLoaded = availableBuckets.includes(bucketName)
- return {
- ...policy,
- bucket: isBucketLoaded ? bucketName : UNKNOWN_BUCKET_SYMBOL,
- }
- }
- return { ...policy, bucket: UNGROUPED_POLICY_SYMBOL }
- })
- return formattedPolicies
- }
- export const extractBucketNameFromDefinition = (definition: string | null) => {
- if (!definition) return null
- const definitionSegments = definition?.split(' AND ') ?? []
- const [bucketDefinition] = definitionSegments.filter((segment: string) =>
- segment.includes('bucket_id')
- )
- return bucketDefinition ? bucketDefinition.split("'")[1] : null
- }
- const groupPoliciesByBucket = (policies: (Policy & { bucket: string | Symbol })[]) => {
- const policiesByBucket = new Map<string | Symbol, Policy[]>()
- policies.forEach((policy) => {
- if (!policiesByBucket.has(policy.bucket)) {
- policiesByBucket.set(policy.bucket, [])
- }
- policiesByBucket.get(policy.bucket)?.push(policy)
- })
- return Array.from(policiesByBucket).map(([bucketName, policies]) => ({
- name: bucketName,
- policies,
- }))
- }
- export const createPayloadsForAddPolicy = (
- bucketName = '',
- policyFormFields: StoragePolicyFormField,
- addSuffixToPolicyName = true
- ) => {
- const { name: policyName, definition, allowedOperations, roles } = policyFormFields
- const formattedDefinition = definition ? definition.replace(/\s+/g, ' ').trim() : ''
- return allowedOperations.map((operation: any, idx: number) => {
- return createPayloadForNewPolicy(
- idx,
- bucketName,
- policyName,
- formattedDefinition,
- operation,
- roles,
- addSuffixToPolicyName
- )
- })
- }
- const createPayloadForNewPolicy = (
- idx: number,
- bucketName: string,
- policyName: string,
- definition: string,
- operation: string,
- roles: string[],
- addSuffixToPolicyName: boolean
- ) => {
- const hashedBucketName = shortHash(bucketName)
- return {
- name: addSuffixToPolicyName ? `${policyName} ${hashedBucketName}_${idx}` : policyName,
- definition: operation === 'INSERT' ? undefined : `(${definition})`,
- action: 'PERMISSIVE',
- check: operation === 'INSERT' ? `(${definition})` : undefined,
- command: operation,
- schema: 'storage',
- table: 'objects',
- roles: roles.length > 0 ? roles : undefined,
- }
- }
- // Used in the policy editor to highlight which library methods are allowed depending on which operations are allowed
- export const deriveAllowedClientLibraryMethods = (allowedOperations = []) => {
- return Object.keys(STORAGE_CLIENT_LIBRARY_MAPPINGS).filter((method) => {
- const requiredOperations = (STORAGE_CLIENT_LIBRARY_MAPPINGS as any)[method]
- if (difference(requiredOperations, allowedOperations).length === 0) {
- return method
- }
- })
- }
- // Create policy SQL statements on save based on configuration.
- // Used purely for previewing in the review step, not actually fired
- const createSQLStatementForCreatePolicy = (
- idx: number,
- bucketName: string,
- policyName: string,
- definition: string,
- operation: string,
- selectedRoles: string[],
- addSuffixToPolicyName: boolean
- ) => {
- const hashedBucketName = shortHash(bucketName)
- const formattedPolicyName = addSuffixToPolicyName
- ? `${policyName} ${hashedBucketName}_${idx}`
- : policyName
- const description = `Add policy for the ${operation} operation under the policy "${policyName}"`
- const roles = selectedRoles.length === 0 ? ['public'] : selectedRoles
- const statement = `
- CREATE POLICY "${formattedPolicyName}"
- ON storage.objects
- FOR ${operation}
- TO ${roles.join(', ')}
- ${operation === 'INSERT' ? 'WITH CHECK' : 'USING'} (${definition});
- `
- .replace(/\s+/g, ' ')
- .trim()
- return { description, statement }
- }
- export const createSQLPolicies = (
- bucketName: string,
- policyFormFields: StoragePolicyFormField,
- addSuffixToPolicyName = true
- ) => {
- const { name: policyName, definition, allowedOperations, roles } = policyFormFields
- const policies = allowedOperations.map((operation: any, idx: number) =>
- createSQLStatementForCreatePolicy(
- idx,
- bucketName,
- policyName,
- definition || '',
- operation,
- roles,
- addSuffixToPolicyName
- )
- )
- return policies
- }
- export const applyBucketIdToTemplateDefinition = (definition: string, bucketId: any) => {
- return definition.replace('{bucket_id}', `'${bucketId}'`)
- }
- export const useStorageV2Page = () => {
- const router = useRouter()
- return router.pathname.split('/')[4] as undefined | 'files' | 'analytics' | 'vectors' | 's3'
- }
- export const getDecryptedParameters = async ({
- ref,
- connectionString,
- wrapper,
- wrapperMeta,
- }: {
- ref?: string
- connectionString?: string
- wrapper: FDW
- wrapperMeta: WrapperMeta
- }) => {
- const wrapperServerOptions = wrapperMeta.server.options
- const serverOptions = convertKVStringArrayToJson(wrapper?.server_options ?? [])
- const paramsToBeDecrypted = Object.fromEntries(
- new Map(
- Object.entries(serverOptions).filter(([key, _value]) => {
- return wrapperServerOptions.find((option) => option.name === key)?.encrypted
- })
- )
- )
- const decryptedValues = await getDecryptedValues({
- projectRef: ref,
- connectionString: connectionString,
- ids: Object.values(paramsToBeDecrypted),
- })
- const paramsWithDecryptedValues = Object.fromEntries(
- new Map(
- Object.entries(paramsToBeDecrypted).map(([name, id]) => {
- const decryptedValue = decryptedValues[id]
- return [name, decryptedValue]
- })
- )
- )
- return {
- ...serverOptions,
- ...paramsWithDecryptedValues,
- }
- }
|