From 4e12a1f5a29061f03824f32f9019c6ba0381a68f Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Wed, 5 Feb 2025 14:51:39 -0330 Subject: [PATCH] Chore/autofix views (#33363) * Autofix security status of views * Update apps/studio/components/interfaces/TableGridEditor/ViewEntityAutofixSecurityModal.tsx Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --------- Co-authored-by: Charis <26616127+charislam@users.noreply.github.com> --- .../TableGridEditor/GridHeaderActions.tsx | 20 +++- .../ViewEntityAutofixSecurityModal.tsx | 92 +++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 apps/studio/components/interfaces/TableGridEditor/ViewEntityAutofixSecurityModal.tsx diff --git a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 8b84431db8527..79a8dd44c0846 100644 --- a/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -38,6 +38,7 @@ import { import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { RoleImpersonationPopover } from '../RoleImpersonationSelector' +import ViewEntityAutofixSecurityModal from './ViewEntityAutofixSecurityModal' export interface GridHeaderActionsProps { table: Entity @@ -73,7 +74,7 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { const [open, setOpen] = useState(false) const [showEnableRealtime, setShowEnableRealtime] = useState(false) const [rlsConfirmModalOpen, setRlsConfirmModalOpen] = useState(false) - + const [isAutofixViewSecurityModalOpen, setIsAutofixViewSecurityModalOpen] = useState(false) const state = useTrackedState() const { selectedRows } = state const showHeaderActions = selectedRows.size === 0 @@ -299,7 +300,15 @@ const GridHeaderActions = ({ table }: GridHeaderActionsProps) => { APIs.

-
+
+
+ + + {isTable && ( void +} + +export default function ViewEntityAutofixSecurityModal({ + table, + isAutofixViewSecurityModalOpen, + setIsAutofixViewSecurityModalOpen, +}: ViewEntityAutofixSecurityModalProps) { + const { project } = useProjectContext() + const queryClient = useQueryClient() + const viewDefinition = useViewDefinitionQuery({ + id: table?.id, + projectRef: project?.ref, + connectionString: project?.connectionString, + }) + + const { mutate: execute } = useExecuteSqlMutation({ + onSuccess: async () => { + toast.success('View security changed successfully') + setIsAutofixViewSecurityModalOpen(false) + await queryClient.invalidateQueries(lintKeys.lint(project?.ref)) + }, + onError: (error) => { + toast.error(`Failed to autofix view security: ${error.message}`) + }, + }) + + function handleConfirm() { + const sql = ` + ALTER VIEW "${table.schema}"."${table.name}" SET (security_invoker = on); + ` + execute({ + projectRef: project?.ref, + connectionString: project?.connectionString, + sql, + }) + } + + return ( + setIsAutofixViewSecurityModalOpen(false)} + onConfirm={() => { + handleConfirm() + }} + > +

+ Setting security_invoker=on ensures the View runs with the permissions of the + querying user, reducing the risk of unintended data exposure. +

+
+
+
Existing query
+ + {viewDefinition.data && ( + + {`create view ${table.schema}.${table.name} as\n ${viewDefinition.data}`} + + )} + +
+ +
+
Updated query
+ + {viewDefinition.data && ( + + {`create view ${table.schema}.${table.name} with (security_invoker = on) as\n ${viewDefinition.data}`} + + )} + +
+
+
+ ) +}