63 lines
1.8 KiB
Python
63 lines
1.8 KiB
Python
"""B1.0 — make superadmin an explicit flag instead of a null tenant
|
|
|
|
Adds users.is_superadmin and backfills it from the signal it replaces, so the
|
|
operators who are superadmins today stay superadmins after deploy.
|
|
|
|
Until now privilege was inferred from `tenant_id IS NULL`. That made the
|
|
*absence* of tenant context a grant of authority: a bug that dropped the tenant
|
|
escalated instead of denying. The flag separates "has no tenant" from "may do
|
|
anything".
|
|
|
|
This migration only adds and backfills. It deliberately does not drop or alter
|
|
`tenant_id`, and the application honours both signals for one release, so this
|
|
can be reverted without locking anyone out.
|
|
|
|
Revision ID: b1_0_explicit_superadmin
|
|
Revises: dfae38e4ede3
|
|
"""
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision = "b1_0_explicit_superadmin"
|
|
down_revision = "73dff81eed72"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.add_column(
|
|
"users",
|
|
sa.Column(
|
|
"is_superadmin",
|
|
sa.Boolean(),
|
|
nullable=False,
|
|
server_default=sa.false(),
|
|
),
|
|
)
|
|
|
|
# Backfill in the same migration, not a follow-up script. If this ran later
|
|
# there would be a window in which every superadmin was demoted.
|
|
op.execute(
|
|
"""
|
|
UPDATE users
|
|
SET is_superadmin = true
|
|
WHERE tenant_id IS NULL
|
|
AND is_deleted = false
|
|
"""
|
|
)
|
|
|
|
op.create_index(
|
|
"ix_users_is_superadmin",
|
|
"users",
|
|
["is_superadmin"],
|
|
postgresql_where=sa.text("is_superadmin"),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
# Safe to reverse: `tenant_id IS NULL` still identifies the same people,
|
|
# and the application still honours it.
|
|
op.drop_index("ix_users_is_superadmin", table_name="users")
|
|
op.drop_column("users", "is_superadmin")
|