Skip to content

Commit ab64849

Browse files
committed
Establish access rights via migration
1 parent 992b60f commit ab64849

4 files changed

Lines changed: 116 additions & 112 deletions

File tree

src/api/__init__.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
from fastapi.routing import APIRoute
1414
from pydantic import BaseModel
1515

16-
from ..database import engine
1716
from ..deployment.monitors.health import vm_monitor
18-
from ._util.role import create_access_rights_if_emtpy
1917
from .backup import router as backup_router
2018
from .backupmonitor import run_backup_monitor
2119
from .organization import api as organization_api
@@ -194,9 +192,6 @@ async def _populate_db():
194192
config.set_main_option("script_location", migrations_path)
195193
command.upgrade(config, "head")
196194

197-
async with engine.begin() as conn:
198-
await create_access_rights_if_emtpy(conn)
199-
200195

201196
def _use_route_names_as_operation_ids(app: FastAPI) -> None:
202197
"""

src/api/_util/role.py

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
from typing import get_args
2-
3-
from sqlalchemy.ext.asyncio import AsyncConnection
4-
from sqlmodel import insert, select
5-
from ulid import ULID
1+
from sqlmodel import select
62

73
from ..._util import Identifier
84
from ...database import SessionDep
95
from ...models.branch import Branch
10-
from ...models.role import AccessRight, AccessRightPublic, Organization, Role, RoleAccessRight, RoleType, RoleUserLink
6+
from ...models.role import AccessRight, Organization, Role, RoleAccessRight, RoleType, RoleUserLink
117

128

139
async def clone_user_role_assignment(
@@ -34,33 +30,6 @@ async def clone_user_role_assignment(
3430
await session.refresh(target)
3531

3632

37-
def get_role_type(access_right: AccessRightPublic) -> RoleType:
38-
name = str(access_right)
39-
if name.startswith("org:"):
40-
return RoleType.organization
41-
elif name.startswith("env:"):
42-
return RoleType.environment
43-
elif name.startswith("project:"):
44-
return RoleType.project
45-
elif name.startswith("branch:"):
46-
return RoleType.branch
47-
else:
48-
raise ValueError(f"Invalid access right: {name}")
49-
50-
51-
async def create_access_rights_if_emtpy(conn: AsyncConnection):
52-
result = await conn.execute(select(AccessRight))
53-
if len(list(result.scalars().all())) == 0:
54-
for access_right_public in get_args(AccessRightPublic):
55-
await conn.execute(
56-
insert(AccessRight).values(
57-
id=ULID(),
58-
entry=access_right_public,
59-
role_type=get_role_type(access_right_public).name,
60-
)
61-
)
62-
63-
6433
async def get_access_rights(session: SessionDep) -> list[AccessRight]:
6534
result = await session.execute(select(AccessRight))
6635
return list(result.scalars().all())
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""establish-access-rights
2+
3+
Revision ID: f76768ab95d8
4+
Revises: 6726a34eb0ec
5+
Create Date: 2026-03-26 08:20:53.641921
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
import sqlalchemy as sa
11+
from alembic import op
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'f76768ab95d8'
16+
down_revision: Union[str, Sequence[str], None] = '6726a34eb0ec'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
_ACCESS_RIGHTS = [
22+
("org:owner:admin", "organization"),
23+
("org:settings:read", "organization"),
24+
("org:settings:admin", "organization"),
25+
("org:auth:read", "organization"),
26+
("org:auth:admin", "organization"),
27+
("org:backup:read", "organization"),
28+
("org:backup:update", "organization"),
29+
("org:backup:create", "organization"),
30+
("org:backup:delete", "organization"),
31+
("org:metering:read", "organization"),
32+
("org:role:read", "organization"),
33+
("org:role:admin", "organization"),
34+
("org:user:read", "organization"),
35+
("org:user:admin", "organization"),
36+
("org:role-assign:read", "organization"),
37+
("org:role-assign:admin", "organization"),
38+
("org:projects:read", "organization"),
39+
("org:projects:write", "organization"),
40+
("org:projects:create", "organization"),
41+
("org:projects:stop", "organization"),
42+
("org:projects:pause", "organization"),
43+
("org:projects:delete", "organization"),
44+
("org:projects:apikeys", "organization"),
45+
("org:limits:read", "organization"),
46+
("org:limits:admin", "organization"),
47+
("env:db:admin", "environment"),
48+
("env:projects:read", "environment"),
49+
("env:projects:admin", "environment"),
50+
("env:backup:read", "environment"),
51+
("env:backup:admin", "environment"),
52+
("env:projects:write", "environment"),
53+
("env:projects:create", "environment"),
54+
("env:role-assign:read", "environment"),
55+
("env:role-assign:admin", "environment"),
56+
("env:projects:stop", "environment"),
57+
("env:projects:pause", "environment"),
58+
("env:projects:delete", "environment"),
59+
("env:projects:getkeys", "environment"),
60+
("project:settings:read", "project"),
61+
("project:settings:write", "project"),
62+
("project:role-assign:read", "project"),
63+
("project:role-assign:admin", "project"),
64+
("project:branches:create", "project"),
65+
("project:branches:delete", "project"),
66+
("project:branches:stop", "project"),
67+
("branch:settings:read", "branch"),
68+
("branch:settings:admin", "branch"),
69+
("branch:role-assign:read", "branch"),
70+
("branch:role-assign:admin", "branch"),
71+
("branch:auth:read", "branch"),
72+
("branch:auth:admin", "branch"),
73+
("branch:api:getkeys", "branch"),
74+
("branch:replicate:read", "branch"),
75+
("branch:replicate:admin", "branch"),
76+
("branch:import:read", "branch"),
77+
("branch:import:admin", "branch"),
78+
("branch:logging:read", "branch"),
79+
("branch:monitoring:read", "branch"),
80+
("branch:db:admin", "branch"),
81+
("branch:rls:read", "branch"),
82+
("branch:rls:admin", "branch"),
83+
("branch:edge:read", "branch"),
84+
("branch:edge:admin", "branch"),
85+
("branch:rt:read", "branch"),
86+
("branch:rt:admin", "branch"),
87+
]
88+
89+
90+
def upgrade() -> None:
91+
"""Upgrade schema."""
92+
conn = op.get_bind()
93+
for entry, role_type in _ACCESS_RIGHTS:
94+
conn.execute(
95+
sa.text(
96+
"INSERT INTO accessright (id, entry, role_type) "
97+
"SELECT gen_random_uuid(), :entry, :role_type "
98+
"WHERE NOT EXISTS (SELECT 1 FROM accessright WHERE entry = :entry_check)"
99+
),
100+
{"entry": entry, "role_type": role_type, "entry_check": entry},
101+
)
102+
103+
104+
def downgrade() -> None:
105+
"""Downgrade schema."""
106+
conn = op.get_bind()
107+
conn.execute(
108+
sa.text("DELETE FROM accessright WHERE entry = ANY(:entries)").bindparams(
109+
sa.bindparam("entries", value=[e for e, _ in _ACCESS_RIGHTS], type_=sa.ARRAY(sa.String))
110+
)
111+
)

src/models/role.py

Lines changed: 3 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from enum import Enum as PyEnum
2-
from typing import TYPE_CHECKING, Literal
2+
from typing import TYPE_CHECKING, Annotated, Literal
33
from uuid import UUID
44

5-
from pydantic import BaseModel
5+
from pydantic import BaseModel, StringConstraints
66
from sqlalchemy.ext.asyncio import AsyncAttrs
77
from sqlmodel import Field, Relationship
88

@@ -17,78 +17,7 @@
1717
RoleTypePublic = Literal["organization", "environment", "project", "branch"]
1818

1919

20-
AccessRightPublic = Literal[
21-
"org:owner:admin",
22-
"org:settings:read",
23-
"org:settings:admin",
24-
"org:auth:read",
25-
"org:auth:admin",
26-
"org:backup:read",
27-
"org:backup:update",
28-
"org:backup:create",
29-
"org:backup:delete",
30-
"org:metering:read",
31-
"org:role:read",
32-
"org:role:admin",
33-
"org:user:read",
34-
"org:user:admin",
35-
"org:role-assign:read",
36-
"org:role-assign:admin",
37-
"org:projects:read",
38-
"org:projects:write",
39-
"org:projects:create",
40-
"org:projects:stop",
41-
"org:projects:pause",
42-
"org:projects:delete",
43-
"org:projects:pause",
44-
"org:projects:apikeys",
45-
"org:limits:read",
46-
"env:db:admin",
47-
"env:projects:read",
48-
"env:projects:admin",
49-
"org:limits:admin",
50-
"env:backup:read",
51-
"env:backup:admin",
52-
"env:projects:read",
53-
"env:projects:write",
54-
"env:projects:create",
55-
"env:role-assign:read",
56-
"env:role-assign:admin",
57-
"env:projects:stop",
58-
"env:projects:pause",
59-
"env:projects:delete",
60-
"env:projects:getkeys",
61-
"env:db:admin",
62-
"env:projects:read",
63-
"env:projects:admin",
64-
"project:settings:read",
65-
"project:settings:write",
66-
"project:role-assign:read",
67-
"project:role-assign:admin",
68-
"project:branches:create",
69-
"project:branches:delete",
70-
"project:branches:stop",
71-
"branch:settings:read",
72-
"branch:settings:admin",
73-
"branch:role-assign:read",
74-
"branch:role-assign:admin",
75-
"branch:auth:read",
76-
"branch:auth:admin",
77-
"branch:api:getkeys",
78-
"branch:replicate:read",
79-
"branch:replicate:admin",
80-
"branch:import:read",
81-
"branch:import:admin",
82-
"branch:logging:read",
83-
"branch:monitoring:read",
84-
"branch:db:admin",
85-
"branch:rls:read",
86-
"branch:rls:admin",
87-
"branch:edge:read",
88-
"branch:edge:admin",
89-
"branch:rt:read",
90-
"branch:rt:admin",
91-
]
20+
AccessRightPublic = Annotated[str, StringConstraints(pattern=r"^(org|env|project|branch):[a-z][a-z\-]*:[a-z][a-z\-]*$")]
9221

9322

9423
class RoleType(PyEnum):

0 commit comments

Comments
 (0)