108 lines
3.3 KiB
PL/PgSQL
108 lines
3.3 KiB
PL/PgSQL
-- AwoooP RLS Canary Wave 1.2: projects table with explicit operator list path
|
|
-- Date: 2026-05-12
|
|
--
|
|
-- Scope:
|
|
-- - awooop_projects
|
|
--
|
|
-- Safety model:
|
|
-- - normal app access is tenant-scoped by app.project_id.
|
|
-- - Operator Console cross-tenant list uses a fixed SECURITY DEFINER
|
|
-- function owned by the migration/operator role.
|
|
-- - no NULL/empty-string/__platform__ policy bypass.
|
|
|
|
BEGIN;
|
|
|
|
SET LOCAL lock_timeout = '5s';
|
|
SET LOCAL statement_timeout = '30s';
|
|
|
|
DO $$
|
|
DECLARE
|
|
total_rows bigint;
|
|
null_project_rows bigint;
|
|
BEGIN
|
|
IF to_regclass('public.awooop_projects') IS NULL THEN
|
|
RAISE EXCEPTION 'RLS canary target table does not exist: awooop_projects';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT 1
|
|
FROM information_schema.columns
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'awooop_projects'
|
|
AND column_name = 'project_id'
|
|
) THEN
|
|
RAISE EXCEPTION 'RLS canary target missing project_id: awooop_projects';
|
|
END IF;
|
|
|
|
SELECT COUNT(*), COUNT(*) FILTER (WHERE project_id IS NULL)
|
|
INTO total_rows, null_project_rows
|
|
FROM awooop_projects;
|
|
|
|
IF null_project_rows <> 0 THEN
|
|
RAISE EXCEPTION 'RLS canary target has NULL project_id rows: %, nulls=%',
|
|
'awooop_projects', null_project_rows;
|
|
END IF;
|
|
|
|
IF total_rows > 20 THEN
|
|
RAISE EXCEPTION 'RLS canary wave1.2 reviewed cap exceeded: %, rows=%',
|
|
'awooop_projects', total_rows;
|
|
END IF;
|
|
END
|
|
$$;
|
|
|
|
CREATE OR REPLACE FUNCTION public.awooop_operator_list_projects()
|
|
RETURNS TABLE (
|
|
project_id varchar,
|
|
display_name varchar,
|
|
migration_mode varchar,
|
|
budget_limit_usd numeric,
|
|
is_active boolean,
|
|
created_at timestamp without time zone
|
|
)
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path = public, pg_catalog
|
|
AS $$
|
|
SELECT
|
|
p.project_id,
|
|
p.display_name,
|
|
p.migration_mode,
|
|
p.budget_limit_usd,
|
|
p.is_active,
|
|
p.created_at
|
|
FROM public.awooop_projects AS p
|
|
ORDER BY p.created_at ASC;
|
|
$$;
|
|
|
|
REVOKE ALL ON FUNCTION public.awooop_operator_list_projects() FROM PUBLIC;
|
|
GRANT EXECUTE ON FUNCTION public.awooop_operator_list_projects() TO awooop_app;
|
|
|
|
ALTER TABLE awooop_projects ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE awooop_projects FORCE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS awooop_projects_select_tenant ON awooop_projects;
|
|
DROP POLICY IF EXISTS awooop_projects_insert_tenant ON awooop_projects;
|
|
DROP POLICY IF EXISTS awooop_projects_update_tenant ON awooop_projects;
|
|
DROP POLICY IF EXISTS awooop_projects_delete_tenant ON awooop_projects;
|
|
DROP POLICY IF EXISTS projects_tenant_isolation ON awooop_projects;
|
|
|
|
CREATE POLICY awooop_projects_select_tenant ON awooop_projects
|
|
FOR SELECT TO awooop_app
|
|
USING (project_id = current_setting('app.project_id', TRUE));
|
|
|
|
CREATE POLICY awooop_projects_insert_tenant ON awooop_projects
|
|
FOR INSERT TO awooop_app
|
|
WITH CHECK (project_id = current_setting('app.project_id', TRUE));
|
|
|
|
CREATE POLICY awooop_projects_update_tenant ON awooop_projects
|
|
FOR UPDATE TO awooop_app
|
|
USING (project_id = current_setting('app.project_id', TRUE))
|
|
WITH CHECK (project_id = current_setting('app.project_id', TRUE));
|
|
|
|
CREATE POLICY awooop_projects_delete_tenant ON awooop_projects
|
|
FOR DELETE TO awooop_app
|
|
USING (project_id = current_setting('app.project_id', TRUE));
|
|
|
|
COMMIT;
|