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}`}
+
+ )}
+
+
+
+
+ )
+}