P3-A: Add account_id to audit_logs model + migration (backfill via user_id → users.account_id). log_audit() gains optional account_id param with fallback SELECT to avoid churn across 40 call sites. P3-B: Add account_id to tree_shares model + migration (backfill via created_by → users.account_id). TreeShare constructor updated in trees.py. P3-C: Enable RLS on 6 remaining tables: step_ratings, step_usage_log, target_lists, session_shares, audit_logs, tree_shares. P3-D: Drop team_id from target_lists — endpoint, schema, and model now use account_id as the sole isolation key. P3-E: Append Phase 3 RLS isolation tests for all 6 tables. test_target_lists.py: fix cross-account test to use Account model (not Team) and set account_id on new User. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
"""Enable RLS on Phase 3 tables.
|
|
|
|
Tables covered:
|
|
- step_ratings (account_id NOT NULL since migration 7167e9374b0c)
|
|
- step_usage_log (account_id NOT NULL since migration 7167e9374b0c)
|
|
- target_lists (account_id NOT NULL since migration 2c6aabd89bc6)
|
|
- session_shares (account_id NOT NULL since session_share model)
|
|
- audit_logs (account_id NOT NULL since migration 2a9056eddd90)
|
|
- tree_shares (account_id NOT NULL since migration a05e1a1bea7c)
|
|
|
|
All use a standard intra-tenant isolation policy.
|
|
Token-based access to session_shares and tree_shares goes through
|
|
endpoints that use get_admin_db (BYPASSRLS), so a strict tenant
|
|
policy here is correct.
|
|
|
|
Revision ID: 04f013768235
|
|
Revises: a05e1a1bea7c
|
|
Create Date: 2026-04-11 00:00:00.000000
|
|
"""
|
|
from typing import Sequence, Union
|
|
from alembic import op
|
|
|
|
revision: str = '04f013768235'
|
|
down_revision: Union[str, None] = 'a05e1a1bea7c'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
_CURRENT_ACCOUNT = (
|
|
"COALESCE(NULLIF(current_setting('app.current_account_id', TRUE), ''), "
|
|
"'00000000-0000-0000-0000-000000000000')::uuid"
|
|
)
|
|
|
|
_STANDARD_USING = f"account_id = {_CURRENT_ACCOUNT}"
|
|
|
|
_PHASE3_TABLES = [
|
|
"step_ratings",
|
|
"step_usage_log",
|
|
"target_lists",
|
|
"session_shares",
|
|
"audit_logs",
|
|
"tree_shares",
|
|
]
|
|
|
|
|
|
def upgrade() -> None:
|
|
for table in _PHASE3_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 ({_STANDARD_USING})
|
|
""")
|
|
|
|
|
|
def downgrade() -> None:
|
|
for table in _PHASE3_TABLES:
|
|
op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table}")
|
|
op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")
|
|
op.execute(f"ALTER TABLE {table} NO FORCE ROW LEVEL SECURITY")
|