142 lines
4.4 KiB
Python
142 lines
4.4 KiB
Python
from pydantic import BaseModel, Field, ConfigDict
|
|
from typing import Literal, Optional, List
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
|
|
class RoleAccessOut(BaseModel):
|
|
id: UUID
|
|
name: str
|
|
access_code: str
|
|
category: str
|
|
parent_id: Optional[UUID] = None
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class RoleCreate(BaseModel):
|
|
role_name: str = Field(..., max_length=100)
|
|
description: Optional[str] = None
|
|
tenant_id: Optional[UUID] = None
|
|
access_ids: Optional[List[UUID]] = []
|
|
is_default: Optional[bool] = False
|
|
|
|
|
|
class RoleUpdate(BaseModel):
|
|
role_name: Optional[str] = None
|
|
description: Optional[str] = None
|
|
access_ids: Optional[List[UUID]] = None
|
|
is_default: Optional[bool] = None
|
|
|
|
|
|
class RoleOut(BaseModel):
|
|
id: UUID
|
|
role_name: str = Field(..., alias="name")
|
|
description: Optional[str] = None
|
|
tenant_id: Optional[UUID] = None
|
|
is_default: bool = False
|
|
is_system: bool = False
|
|
created_at: Optional[datetime] = None
|
|
|
|
model_config = ConfigDict(from_attributes=True, populate_by_name=True)
|
|
|
|
|
|
class RoleWithAccessesOut(RoleOut):
|
|
accesses: List[RoleAccessOut] = []
|
|
assigned_user_count: Optional[int] = None
|
|
"""
|
|
How many principals this role reaches, or null when it was not counted.
|
|
|
|
Added to the *details* response and deliberately not to `RoleOut`: the list
|
|
endpoints render up to a hundred roles and counting each one would be an
|
|
N+1 nobody asked for. It exists so that deleting a role can say what it is
|
|
about to do — `user_roles.role_id` is `ON DELETE CASCADE` and
|
|
`users.role_id` is `ON DELETE SET NULL`, so a silent delete removes
|
|
authority from people the operator was not thinking about.
|
|
"""
|
|
|
|
|
|
class RoleAssignmentOut(BaseModel):
|
|
"""
|
|
One role a user holds, and how they hold it.
|
|
|
|
`source` is the field that keeps the interface honest:
|
|
|
|
"primary" the legacy `users.role_id`. Changed through
|
|
`PATCH /api/admin/users/{id}`, **not** revocable as a grant,
|
|
and while it is set it cannot be narrowed by anything (see
|
|
`ScopeService._resolve`)
|
|
"grant" a `user_roles` row naming this user. Added and revoked freely
|
|
"group" a `user_roles` row naming a group this user belongs to. It
|
|
confers codes; it is removed by editing the group
|
|
|
|
Rendering the three identically would imply that removing any of them is the
|
|
same action. It is not, and that is exactly where a permissions screen
|
|
misleads the person using it.
|
|
"""
|
|
|
|
grant_id: Optional[UUID] = None
|
|
role_id: UUID
|
|
role_name: str
|
|
source: Literal["primary", "grant", "group"]
|
|
org_unit_id: Optional[UUID] = None
|
|
org_unit_name: Optional[str] = None
|
|
group_id: Optional[UUID] = None
|
|
group_name: Optional[str] = None
|
|
expires_at: Optional[datetime] = None
|
|
assigned_by_id: Optional[int] = None
|
|
created_at: Optional[datetime] = None
|
|
is_expired: bool = False
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class AddUserRoleIn(BaseModel):
|
|
"""
|
|
Add one role to one user.
|
|
|
|
`org_unit_id` omitted or null means **tenant-wide** — the authority every
|
|
user already has through their primary role. That default is the point of
|
|
this endpoint: the organisation tree could only ever grant *scoped to a
|
|
unit*, so "Alice is an Editor and an Approver, everywhere" was not
|
|
expressible anywhere in the product.
|
|
"""
|
|
|
|
role_id: UUID
|
|
org_unit_id: Optional[UUID] = None
|
|
expires_at: Optional[datetime] = None
|
|
|
|
|
|
class AccessAttributionOut(BaseModel):
|
|
"""Which role conferred one access code, and at what scope."""
|
|
|
|
role_id: Optional[UUID] = None
|
|
role_name: Optional[str] = None
|
|
source: str
|
|
org_unit_id: Optional[UUID] = None
|
|
org_unit_name: Optional[str] = None
|
|
expires_at: Optional[datetime] = None
|
|
|
|
|
|
class EffectiveAccessOut(BaseModel):
|
|
"""
|
|
One access code the user holds, with every reason they hold it.
|
|
|
|
Answers "why can this person do that". With one role per user the question
|
|
answered itself. With several it does not, and support cannot read the
|
|
database.
|
|
"""
|
|
|
|
access_code: str
|
|
granted_by: List[AccessAttributionOut]
|
|
|
|
|
|
class RolePaginatedOut(BaseModel):
|
|
items: List[RoleOut]
|
|
total: int
|
|
page: int
|
|
page_size: int
|
|
total_pages: int
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|