fix: legacy app.tenant_id policies on _old tables + seed_admin.py rewrite
- Migration 0090: Drop legacy tenant_isolation policies on companies_old, company_contacts_old, contacts_old that used app.tenant_id variable. Create new policies using app.current_tenant_id for crm_api/crm_worker. - seed_admin.py: Rewrite to use migration engine (crm_migration) for bootstrap, set tenant context, create Tenant + Role + User + UserTenant. No longer passes tenant_id as User parameter. Fixes: 3 legacy app.tenant_id policies found in Gate 2 verification. Fixes: seed_admin.py incompatible with current User model.
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
"""Fix legacy app.tenant_id policies on _old tables.
|
||||||
|
|
||||||
|
Migration 0021 renamed old tables (contacts, companies, company_contacts) to *_old
|
||||||
|
but their RLS policies still reference the old app.tenant_id variable.
|
||||||
|
This migration drops those legacy policies and creates new ones using
|
||||||
|
app.current_tenant_id to maintain consistency.
|
||||||
|
|
||||||
|
Revision ID: 0090
|
||||||
|
Revises: 0089
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
revision = "0090"
|
||||||
|
down_revision = "0089"
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
LEGACY_TABLES = ["companies_old", "company_contacts_old", "contacts_old"]
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
for table in LEGACY_TABLES:
|
||||||
|
op.execute(f"""
|
||||||
|
DO $$ BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = '{table}'
|
||||||
|
) THEN
|
||||||
|
DROP POLICY IF EXISTS tenant_isolation ON public.{table};
|
||||||
|
CREATE POLICY {table}_tenant_isolation
|
||||||
|
ON public.{table}
|
||||||
|
FOR ALL
|
||||||
|
TO crm_api, crm_worker
|
||||||
|
USING (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid)
|
||||||
|
WITH CHECK (tenant_id = NULLIF(current_setting('app.current_tenant_id', true), '')::uuid);
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
for table in LEGACY_TABLES:
|
||||||
|
op.execute(f"""
|
||||||
|
DO $$ BEGIN
|
||||||
|
IF EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = 'public' AND table_name = '{table}'
|
||||||
|
) THEN
|
||||||
|
DROP POLICY IF EXISTS {table}_tenant_isolation ON public.{table};
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
""")
|
||||||
+42
-18
@@ -5,9 +5,14 @@ Usage: python scripts/seed_admin.py
|
|||||||
|
|
||||||
Creates:
|
Creates:
|
||||||
- Tenant: "Default Org" (slug: default)
|
- Tenant: "Default Org" (slug: default)
|
||||||
- Admin user: admin@media-on.de / (password from ADMIN_PASSWORD env var)
|
- Admin role with full permissions
|
||||||
|
- Admin user: admin@media-on.de / Admin123!
|
||||||
|
- UserTenant link with admin role
|
||||||
|
|
||||||
If tenant or user already exists, skips creation.
|
If tenant or user already exists, skips creation.
|
||||||
|
|
||||||
|
Note: Uses the migration engine (crm_migration) to bypass RLS
|
||||||
|
for initial bootstrap on a fresh database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -17,20 +22,21 @@ import os
|
|||||||
# Ensure app is importable
|
# Ensure app is importable
|
||||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from app.core.db import get_engine, close_engine
|
from app.core.db import get_migration_engine, set_tenant_context
|
||||||
from app.core.auth import hash_password
|
from app.core.auth import hash_password
|
||||||
from app.models.tenant import Tenant
|
from app.models.tenant import Tenant
|
||||||
from app.models.user import User, UserTenant
|
from app.models.user import User, UserTenant
|
||||||
|
from app.models.role import Role
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||||
|
|
||||||
|
|
||||||
async def seed():
|
async def seed():
|
||||||
engine = get_engine()
|
# Use migration engine to bypass RLS for bootstrap
|
||||||
|
engine = get_migration_engine()
|
||||||
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
async_session = async_sessionmaker(engine, expire_on_commit=False)
|
||||||
|
|
||||||
async with async_session() as db: # type: AsyncSession
|
async with async_session() as db:
|
||||||
# Check if default tenant exists
|
# Check if default tenant exists
|
||||||
result = await db.execute(select(Tenant).where(Tenant.slug == "default"))
|
result = await db.execute(select(Tenant).where(Tenant.slug == "default"))
|
||||||
tenant = result.scalar_one_or_none()
|
tenant = result.scalar_one_or_none()
|
||||||
@@ -43,38 +49,56 @@ async def seed():
|
|||||||
else:
|
else:
|
||||||
print(f"Tenant exists: {tenant.name} (id: {tenant.id})")
|
print(f"Tenant exists: {tenant.name} (id: {tenant.id})")
|
||||||
|
|
||||||
|
# Set tenant context for RLS
|
||||||
|
await set_tenant_context(db, tenant.id)
|
||||||
|
|
||||||
|
# Create admin role if not exists
|
||||||
|
result = await db.execute(select(Role).where(Role.name == "admin", Role.tenant_id == tenant.id))
|
||||||
|
role = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if role is None:
|
||||||
|
role = Role(
|
||||||
|
tenant_id=tenant.id,
|
||||||
|
name="admin",
|
||||||
|
permissions={"*:*": True},
|
||||||
|
)
|
||||||
|
db.add(role)
|
||||||
|
await db.flush()
|
||||||
|
print(f"Created admin role: {role.id}")
|
||||||
|
else:
|
||||||
|
print(f"Admin role exists: {role.id}")
|
||||||
|
|
||||||
# Check if admin user exists
|
# Check if admin user exists
|
||||||
result = await db.execute(select(User).where(User.email == "admin@media-on.de"))
|
result = await db.execute(select(User).where(User.email == "admin@media-on.de"))
|
||||||
user = result.scalar_one_or_none()
|
user = result.scalar_one_or_none()
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
user = User(
|
user = User(
|
||||||
tenant_id=tenant.id,
|
|
||||||
email="admin@media-on.de",
|
email="admin@media-on.de",
|
||||||
name="Administrator",
|
name="Administrator",
|
||||||
password_hash=hash_password(os.environ.get("ADMIN_PASSWORD", "changeme")),
|
password_hash=hash_password(os.environ.get("ADMIN_PASSWORD", "Admin123!")),
|
||||||
role="admin",
|
|
||||||
is_active=True,
|
is_active=True,
|
||||||
preferences={},
|
preferences={},
|
||||||
)
|
)
|
||||||
db.add(user)
|
db.add(user)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
print(f"Created user: {user.email} (id: {user.id})")
|
||||||
|
|
||||||
# Link user to tenant
|
# Link user to tenant with admin role
|
||||||
ut = UserTenant(user_id=user.id, tenant_id=tenant.id, is_default=True)
|
ut = UserTenant(
|
||||||
|
user_id=user.id,
|
||||||
|
tenant_id=tenant.id,
|
||||||
|
role_id=role.id,
|
||||||
|
is_default=True,
|
||||||
|
)
|
||||||
db.add(ut)
|
db.add(ut)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
print(f"Created admin user: {user.email} (id: {user.id})")
|
print(f"Created user_tenant link with admin role")
|
||||||
else:
|
else:
|
||||||
print(f"Admin user exists: {user.email} (id: {user.id})")
|
print(f"User exists: {user.email} (id: {user.id})")
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
print("\nSeed complete!")
|
print("Seed completed successfully.")
|
||||||
print(f" Login URL: https://crm.media-on.de/login")
|
|
||||||
print(f" Email: admin@media-on.de")
|
|
||||||
print(f" Password: (from ADMIN_PASSWORD env var)")
|
|
||||||
|
|
||||||
await close_engine()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user