From aa90e578ca8c707a74090b617095efb9d80b0f6d Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 23 May 2024 16:52:03 -0230 Subject: [PATCH] Chore/show unlock icon next to view entities (#23238) * Show unlock icon next to views and foreign tables * Temp header actions for all entities * Add warnings for views and foreign tables * Add labels for each entity type * Cleanup * Unneeded comma * Remove unneeded useEffect * Check lints on the entities menu too * Pass exposed schemas to lint query * Type cleanup * Update * Update lint, add 0016 * Fix materialized view logic * Cleanup * Grab lint count * Update apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx Co-authored-by: Inian * Update apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx Co-authored-by: Inian * Update apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx Co-authored-by: Inian * Update apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx Co-authored-by: Inian * Language changes * Use lints for gridheaderactions * Types cleanup --------- Co-authored-by: Inian Co-authored-by: Ivan Vasilov --- .../interfaces/Linter/Linter.utils.tsx | 10 + .../TableGridEditor/GridHeaderActions.tsx | 173 ++++++++++++++++-- .../TableGridEditor/TableEntity.utils.ts | 22 +++ .../TableGridEditor/TableGridEditor.tsx | 12 +- .../TableEditorLayout/EntityListItem.tsx | 96 +++++++++- .../data/tables/table-update-mutation.ts | 2 + 6 files changed, 281 insertions(+), 34 deletions(-) create mode 100644 apps/studio/components/interfaces/TableGridEditor/TableEntity.utils.ts diff --git a/apps/studio/components/interfaces/Linter/Linter.utils.tsx b/apps/studio/components/interfaces/Linter/Linter.utils.tsx index 14778af87fe..0a0991fe19f 100644 --- a/apps/studio/components/interfaces/Linter/Linter.utils.tsx +++ b/apps/studio/components/interfaces/Linter/Linter.utils.tsx @@ -158,6 +158,16 @@ export const lintInfoMap: LintInfo[] = [ docsLink: 'https://supabase.com/docs/guides/database/database-linter?queryGroups=lint&lint=0015_rls_references_user_metadata', }, + { + name: 'materialized_view_in_api', + title: 'Materialized View in API', + icon: , + link: () => + `https://supabase.com/docs/guides/database/database-advisors?lint=0016_materialized_view_in_api`, + linkText: 'View docs', + docsLink: + 'https://supabase.com/docs/guides/database/database-advisors?lint=0016_materialized_view_in_api', + }, ] export const LintCTA = ({ diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index c9564ac693f..bc039ac81f4 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -2,7 +2,7 @@ import * as Tooltip from '@radix-ui/react-tooltip' import type { PostgresTable } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' -import { Lock, MousePointer2, PlusCircle } from 'lucide-react' +import { Lock, MousePointer2, PlusCircle, Unlock } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import toast from 'react-hot-toast' @@ -19,17 +19,35 @@ import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { RoleImpersonationPopover } from '../RoleImpersonationSelector' +import useEntityType from 'hooks/misc/useEntityType' +import { ENTITY_TYPE } from 'data/entity-types/entity-type-constants' +import { useProjectLintsQuery } from 'data/lint/lint-query' +import { TableLike } from 'hooks/misc/useTable' +import { checkEntityForLints } from 'components/interfaces/TableGridEditor/TableEntity.utils' export interface GridHeaderActionsProps { - table: PostgresTable + table: TableLike canEditViaTableEditor: boolean - isViewSelected: boolean - isTableSelected: boolean } -const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeaderActionsProps) => { +const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { + const entityType = useEntityType(table?.id) const { ref } = useParams() const { project } = useProjectContext() + + // need project lints to get security status for views + const { data: lints = [] } = useProjectLintsQuery({ + projectRef: project?.ref, + }) + + const isTable = entityType?.type === ENTITY_TYPE.TABLE + const isMaterializedView = entityType?.type === ENTITY_TYPE.MATERIALIZED_VIEW + const isView = entityType?.type === ENTITY_TYPE.VIEW + + // check if current entity is a view and has an associated security definer lint + + const isForeignTable = entityType?.type === ENTITY_TYPE.FOREIGN_TABLE + const realtimeEnabled = useIsFeatureEnabled('realtime:all') const isLocked = EXCLUDED_SCHEMAS.includes(table.schema) @@ -81,6 +99,20 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade // This will change when we allow autogenerated API docs for schemas other than `public` const doesHaveAutoGeneratedAPIDocs = table.schema === 'public' + const viewHasLints = checkEntityForLints( + table.name, + 'security_definer_view', + ['ERROR', 'WARN'], + lints, + table.schema + ) + const materializedViewHasLints = checkEntityForLints( + table.name, + 'materialized_view_in_api', + ['ERROR', 'WARN'], + lints, + table.schema + ) const toggleRealtime = async () => { if (!project) return console.error('Project is required') if (!realtimePublication) return console.error('Unable to find realtime publication') @@ -108,7 +140,7 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade const onToggleRLS = async () => { const payload = { id: table.id, - rls_enabled: !table.rls_enabled, + rls_enabled: !(table as PostgresTable).rls_enabled, } updateTable({ @@ -148,8 +180,8 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade )} - {isTableSelected ? ( - table.rls_enabled ? ( + {isTable ? ( + (table as PostgresTable).rls_enabled ? ( <> {policies.length < 1 && !isLocked ? ( @@ -247,9 +279,110 @@ const GridHeaderActions = ({ table, isViewSelected, isTableSelected }: GridHeade ) ) : null} + {isView && viewHasLints && ( + setOpen(!open)} modal={false}> + + + + +

+ Secure your View +

+
+

+ This view is defined with the Security Definer property, giving it permissions of + the view's creator (Postgres), rather than the permissions of the querying user. +

+ +

+ Since this view is in the public schema, it is accessible via your project's APIs. +

+ +
+ +
+
+
+
+ )} + + {isMaterializedView && materializedViewHasLints && ( + setOpen(!open)} modal={false}> + + + + +

+ Secure your View +

+
+

+ This view is defined with the Security Definer property, giving it permissions of + the view's creator (Postgres), rather than the permissions of the querying user. +

+ +

+ Since this view is in the public schema, it is accessible via your project's APIs. +

+ +
+ +
+
+
+
+ )} + + {isForeignTable && entityType.schema === 'public' && ( + setOpen(!open)} modal={false}> + + + + +

+ Secure Foreign table +

+
+

+ Foreign tables do not enforce RLS. Move them to a private schema not exposed to + Postgrest or disable Postgrest. +

+ +
+ +
+
+
+
+ )} + - {realtimeEnabled && !isViewSelected && ( + {isTable && realtimeEnabled && (