From f9248aeaa8354d6134c8e1ccb467a1262cbfd241 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Sun, 12 Apr 2026 01:48:50 +0000 Subject: [PATCH] fix: remove platform_steps and template_trees from Phase 4 RLS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both tables have no account_id column — they are globally readable by all authenticated users and must not have RLS policies. Also removes the corresponding test cases that assumed these tables had account_id-based policies. Co-Authored-By: Claude Sonnet 4.6 --- .../b3c7e9f2a1d8_enable_rls_phase4.py | 28 +-------- backend/tests/test_rls_isolation.py | 61 ++----------------- 2 files changed, 7 insertions(+), 82 deletions(-) diff --git a/backend/alembic/versions/b3c7e9f2a1d8_enable_rls_phase4.py b/backend/alembic/versions/b3c7e9f2a1d8_enable_rls_phase4.py index 91f45feb..919c6904 100644 --- a/backend/alembic/versions/b3c7e9f2a1d8_enable_rls_phase4.py +++ b/backend/alembic/versions/b3c7e9f2a1d8_enable_rls_phase4.py @@ -13,8 +13,8 @@ Skipped intentionally: - accounts — IS the root table; no account_id column - plan_feature_defaults — platform config; no account_id column - script_categories — global lookup table; no account_id column - (ScriptTemplate in the same file has account_id, - ScriptCategory does not) + - platform_steps — global content; no account_id column (readable by all) + - template_trees — global content; no account_id column (readable by all) Revision ID: b3c7e9f2a1d8 Revises: 172ad76d7d20 @@ -29,8 +29,6 @@ down_revision: Union[str, None] = "172ad76d7d20" branch_labels = None depends_on = None -PLATFORM_ACCOUNT_ID = "00000000-0000-0000-0000-000000000001" - # Standard policy — tenant sees only own rows. _STANDARD_TABLES = [ "users", @@ -63,13 +61,6 @@ _STANDARD_TABLES = [ "user_pinned_trees", ] -# Platform-visibility policy — tenant sees own rows PLUS PLATFORM_ACCOUNT_ID rows. -# These tables hold global content created by ResolutionFlow admins. -_PLATFORM_TABLES = [ - "platform_steps", - "template_trees", -] - _POLICY_EXPR = ( "account_id = COALESCE(" "NULLIF(current_setting('app.current_account_id', TRUE), ''), " @@ -79,7 +70,6 @@ _POLICY_EXPR = ( def upgrade() -> None: - # Standard tables — tenant isolation only for table in _STANDARD_TABLES: op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY") op.execute(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY") @@ -88,20 +78,8 @@ def upgrade() -> None: USING ({_POLICY_EXPR}) """) - # Platform-visible tables — own rows OR global platform rows - for table in _PLATFORM_TABLES: - op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY") - op.execute(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY") - op.execute(f""" - CREATE POLICY tenant_isolation ON {table} - USING ( - {_POLICY_EXPR} - OR account_id = '{PLATFORM_ACCOUNT_ID}'::uuid - ) - """) - def downgrade() -> None: - for table in _STANDARD_TABLES + _PLATFORM_TABLES: + for table in _STANDARD_TABLES: op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table}") op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY") diff --git a/backend/tests/test_rls_isolation.py b/backend/tests/test_rls_isolation.py index e585845d..2a85cc5f 100644 --- a/backend/tests/test_rls_isolation.py +++ b/backend/tests/test_rls_isolation.py @@ -958,8 +958,10 @@ async def test_tree_shares_account_a_cannot_see_account_b(admin_conn, conn_a): # =========================================================================== # Phase 4 RLS isolation tests -# Tables: users, script_builder_sessions, ai_session_steps, -# notifications, platform_steps, template_trees +# Tables: users, script_builder_sessions, ai_session_steps, notifications +# +# Note: platform_steps and template_trees have no account_id column and no RLS — +# they are globally readable by all authenticated users. # =========================================================================== # --------------------------------------------------------------------------- @@ -1083,58 +1085,3 @@ async def test_notifications_account_a_cannot_see_account_b(admin_conn, conn_a): finally: await admin_conn.execute(f"DELETE FROM notifications WHERE id = '{notif_id}'") - -# --------------------------------------------------------------------------- -# platform_steps — platform content visible to all tenants -# --------------------------------------------------------------------------- - -@pytest.mark.asyncio -async def test_platform_steps_visible_to_all_tenants(admin_conn, conn_a): - """Platform steps (PLATFORM_ACCOUNT_ID) must be visible to any tenant.""" - step_id = str(uuid.uuid4()) - await admin_conn.execute(f""" - INSERT INTO platform_steps ( - id, account_id, title, step_type, content, - is_active, created_at, updated_at - ) VALUES ( - '{step_id}', '{PLATFORM_ACCOUNT_ID}', 'Phase4 RLS Platform Step', - 'action', '{{}}'::jsonb, TRUE, NOW(), NOW() - ) - """) - try: - rows = await conn_a.fetch( - f"SELECT id FROM platform_steps WHERE id = '{step_id}'" - ) - assert len(rows) == 1, ( - "Platform steps (PLATFORM_ACCOUNT_ID) must be visible to all tenants" - ) - finally: - await admin_conn.execute(f"DELETE FROM platform_steps WHERE id = '{step_id}'") - - -# --------------------------------------------------------------------------- -# template_trees — platform content visible to all tenants -# --------------------------------------------------------------------------- - -@pytest.mark.asyncio -async def test_template_trees_visible_to_all_tenants(admin_conn, conn_a): - """Template trees (PLATFORM_ACCOUNT_ID) must be visible to any tenant.""" - tmpl_id = str(uuid.uuid4()) - await admin_conn.execute(f""" - INSERT INTO template_trees ( - id, account_id, name, tree_structure, is_active, - created_at, updated_at - ) VALUES ( - '{tmpl_id}', '{PLATFORM_ACCOUNT_ID}', 'Phase4 RLS Template', - '{{}}'::jsonb, TRUE, NOW(), NOW() - ) - """) - try: - rows = await conn_a.fetch( - f"SELECT id FROM template_trees WHERE id = '{tmpl_id}'" - ) - assert len(rows) == 1, ( - "Template trees (PLATFORM_ACCOUNT_ID) must be visible to all tenants" - ) - finally: - await admin_conn.execute(f"DELETE FROM template_trees WHERE id = '{tmpl_id}'")