feat(admin): implement group-centric relations and system/module/company linkage views
This commit is contained in:
@@ -13,6 +13,10 @@ from app.repositories.users_repo import UsersRepository
|
|||||||
from app.schemas.catalog import (
|
from app.schemas.catalog import (
|
||||||
CompanyCreateRequest,
|
CompanyCreateRequest,
|
||||||
CompanyItem,
|
CompanyItem,
|
||||||
|
GroupBindingSnapshot,
|
||||||
|
GroupBindingUpdateRequest,
|
||||||
|
GroupRelationItem,
|
||||||
|
MemberRelationItem,
|
||||||
CompanyUpdateRequest,
|
CompanyUpdateRequest,
|
||||||
MemberItem,
|
MemberItem,
|
||||||
MemberPermissionGroupsResponse,
|
MemberPermissionGroupsResponse,
|
||||||
@@ -69,6 +73,14 @@ def _resolve_scope_ids(db: Session, scope_type: str, scope_id: str) -> tuple[str
|
|||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_scope_type")
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="invalid_scope_type")
|
||||||
|
|
||||||
|
|
||||||
|
def _split_module_key(payload_module: str | None) -> str:
|
||||||
|
if not payload_module:
|
||||||
|
return "__system__"
|
||||||
|
if "." in payload_module:
|
||||||
|
return payload_module.split(".", 1)[1]
|
||||||
|
return payload_module
|
||||||
|
|
||||||
|
|
||||||
def _sync_member_to_authentik(
|
def _sync_member_to_authentik(
|
||||||
*,
|
*,
|
||||||
authentik_sub: str,
|
authentik_sub: str,
|
||||||
@@ -195,6 +207,96 @@ def update_module(
|
|||||||
return ModuleItem(id=row.id, system_key=system_key, module_key=row.module_key, name=row.name, status=row.status)
|
return ModuleItem(id=row.id, system_key=system_key, module_key=row.module_key, name=row.name, status=row.status)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/systems/{system_key}/groups")
|
||||||
|
def list_system_groups(
|
||||||
|
system_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> dict[str, list[dict]]:
|
||||||
|
systems_repo = SystemsRepository(db)
|
||||||
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
|
if not systems_repo.get_by_key(system_key):
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found")
|
||||||
|
groups = groups_repo.list_system_groups(system_key)
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
GroupRelationItem(group_key=g.group_key, group_name=g.name, status=g.status).model_dump()
|
||||||
|
for g in groups
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/systems/{system_key}/members")
|
||||||
|
def list_system_members(
|
||||||
|
system_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> dict[str, list[dict]]:
|
||||||
|
systems_repo = SystemsRepository(db)
|
||||||
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
|
if not systems_repo.get_by_key(system_key):
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="system_not_found")
|
||||||
|
members = groups_repo.list_system_members(system_key)
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
MemberRelationItem(
|
||||||
|
authentik_sub=m.authentik_sub,
|
||||||
|
email=m.email,
|
||||||
|
display_name=m.display_name,
|
||||||
|
is_active=m.is_active,
|
||||||
|
).model_dump()
|
||||||
|
for m in members
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/modules/{module_key}/groups")
|
||||||
|
def list_module_groups(
|
||||||
|
module_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> dict[str, list[dict]]:
|
||||||
|
modules_repo = ModulesRepository(db)
|
||||||
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
|
module = modules_repo.get_by_key(module_key)
|
||||||
|
if not module:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found")
|
||||||
|
system_key, module_name = module.module_key.split(".", 1) if "." in module.module_key else ("", module.module_key)
|
||||||
|
groups = groups_repo.list_module_groups(system_key, module_name)
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
GroupRelationItem(group_key=g.group_key, group_name=g.name, status=g.status).model_dump()
|
||||||
|
for g in groups
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/modules/{module_key}/members")
|
||||||
|
def list_module_members(
|
||||||
|
module_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> dict[str, list[dict]]:
|
||||||
|
modules_repo = ModulesRepository(db)
|
||||||
|
groups_repo = PermissionGroupsRepository(db)
|
||||||
|
module = modules_repo.get_by_key(module_key)
|
||||||
|
if not module:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="module_not_found")
|
||||||
|
system_key, module_name = module.module_key.split(".", 1) if "." in module.module_key else ("", module.module_key)
|
||||||
|
members = groups_repo.list_module_members(system_key, module_name)
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
MemberRelationItem(
|
||||||
|
authentik_sub=m.authentik_sub,
|
||||||
|
email=m.email,
|
||||||
|
display_name=m.display_name,
|
||||||
|
is_active=m.is_active,
|
||||||
|
).model_dump()
|
||||||
|
for m in members
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/companies")
|
@router.get("/companies")
|
||||||
def list_companies(
|
def list_companies(
|
||||||
_: ApiClient = Depends(require_api_client),
|
_: ApiClient = Depends(require_api_client),
|
||||||
@@ -236,6 +338,32 @@ def update_company(
|
|||||||
return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status)
|
return CompanyItem(id=row.id, company_key=row.company_key, name=row.name, status=row.status)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/companies/{company_key}/sites")
|
||||||
|
def list_company_sites(
|
||||||
|
company_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> dict[str, list[dict]]:
|
||||||
|
companies_repo = CompaniesRepository(db)
|
||||||
|
sites_repo = SitesRepository(db)
|
||||||
|
company = companies_repo.get_by_key(company_key)
|
||||||
|
if not company:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="company_not_found")
|
||||||
|
items, _ = sites_repo.list(company_id=company.id, limit=1000, offset=0)
|
||||||
|
return {
|
||||||
|
"items": [
|
||||||
|
SiteItem(
|
||||||
|
id=i.id,
|
||||||
|
site_key=i.site_key,
|
||||||
|
company_key=company.company_key,
|
||||||
|
name=i.name,
|
||||||
|
status=i.status,
|
||||||
|
).model_dump()
|
||||||
|
for i in items
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/sites")
|
@router.get("/sites")
|
||||||
def list_sites(
|
def list_sites(
|
||||||
_: ApiClient = Depends(require_api_client),
|
_: ApiClient = Depends(require_api_client),
|
||||||
@@ -481,16 +609,78 @@ def list_permission_group_permissions(
|
|||||||
PermissionGroupPermissionItem(
|
PermissionGroupPermissionItem(
|
||||||
id=r.id,
|
id=r.id,
|
||||||
system=r.system,
|
system=r.system,
|
||||||
module=r.module,
|
module=("" if r.module == "__system__" else (r.module if "." in r.module else f"{r.system}.{r.module}")),
|
||||||
action=r.action,
|
action=r.action,
|
||||||
scope_type=r.scope_type,
|
scope_type=r.scope_type,
|
||||||
scope_id=r.scope_id,
|
scope_id=r.scope_id,
|
||||||
).model_dump()
|
).model_dump()
|
||||||
for r in rows
|
for r in rows
|
||||||
|
if r.action in {"view", "edit"} and r.scope_type == "site"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/permission-groups/{group_key}/bindings", response_model=GroupBindingSnapshot)
|
||||||
|
def get_permission_group_bindings(
|
||||||
|
group_key: str,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> GroupBindingSnapshot:
|
||||||
|
repo = PermissionGroupsRepository(db)
|
||||||
|
group = repo.get_by_key(group_key)
|
||||||
|
if not group:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
||||||
|
snapshot = repo.get_group_binding_snapshot(group.id, group_key)
|
||||||
|
return GroupBindingSnapshot(**snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/permission-groups/{group_key}/bindings", response_model=GroupBindingSnapshot)
|
||||||
|
def replace_permission_group_bindings(
|
||||||
|
group_key: str,
|
||||||
|
payload: GroupBindingUpdateRequest,
|
||||||
|
_: ApiClient = Depends(require_api_client),
|
||||||
|
db: Session = Depends(get_db),
|
||||||
|
) -> GroupBindingSnapshot:
|
||||||
|
repo = PermissionGroupsRepository(db)
|
||||||
|
sites_repo = SitesRepository(db)
|
||||||
|
systems_repo = SystemsRepository(db)
|
||||||
|
modules_repo = ModulesRepository(db)
|
||||||
|
|
||||||
|
group = repo.get_by_key(group_key)
|
||||||
|
if not group:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
||||||
|
|
||||||
|
site_keys = list(dict.fromkeys(payload.site_keys))
|
||||||
|
system_keys = list(dict.fromkeys(payload.system_keys))
|
||||||
|
module_keys = list(dict.fromkeys(payload.module_keys))
|
||||||
|
|
||||||
|
valid_sites = {s.site_key for s in sites_repo.list(limit=10000, offset=0)[0]}
|
||||||
|
missing_sites = [k for k in site_keys if k not in valid_sites]
|
||||||
|
if missing_sites:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"site_not_found:{','.join(missing_sites)}")
|
||||||
|
|
||||||
|
valid_systems = {s.system_key for s in systems_repo.list(limit=10000, offset=0)[0]}
|
||||||
|
missing_systems = [k for k in system_keys if k not in valid_systems]
|
||||||
|
if missing_systems:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"system_not_found:{','.join(missing_systems)}")
|
||||||
|
|
||||||
|
valid_modules = {m.module_key for m in modules_repo.list(limit=10000, offset=0)[0]}
|
||||||
|
missing_modules = [k for k in module_keys if k not in valid_modules]
|
||||||
|
if missing_modules:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"module_not_found:{','.join(missing_modules)}")
|
||||||
|
|
||||||
|
repo.replace_group_bindings(
|
||||||
|
group_id=group.id,
|
||||||
|
site_keys=site_keys,
|
||||||
|
system_keys=system_keys,
|
||||||
|
module_keys=module_keys,
|
||||||
|
member_subs=payload.member_subs,
|
||||||
|
actions=payload.actions,
|
||||||
|
)
|
||||||
|
snapshot = repo.get_group_binding_snapshot(group.id, group_key)
|
||||||
|
return GroupBindingSnapshot(**snapshot)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/permission-groups", response_model=PermissionGroupItem)
|
@router.post("/permission-groups", response_model=PermissionGroupItem)
|
||||||
def create_permission_group(
|
def create_permission_group(
|
||||||
payload: PermissionGroupCreateRequest,
|
payload: PermissionGroupCreateRequest,
|
||||||
@@ -562,11 +752,11 @@ def grant_group_permission(
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
||||||
_resolve_module_id(db, payload.system, payload.module)
|
_resolve_module_id(db, payload.system, payload.module)
|
||||||
_resolve_scope_ids(db, payload.scope_type, payload.scope_id)
|
_resolve_scope_ids(db, payload.scope_type, payload.scope_id)
|
||||||
module_key = f"{payload.system}.{payload.module}" if payload.module else f"{payload.system}.__system__"
|
module_name = _split_module_key(payload.module)
|
||||||
row = groups_repo.grant_group_permission(
|
row = groups_repo.grant_group_permission(
|
||||||
group_id=group.id,
|
group_id=group.id,
|
||||||
system=payload.system,
|
system=payload.system,
|
||||||
module=module_key,
|
module=module_name,
|
||||||
action=payload.action,
|
action=payload.action,
|
||||||
scope_type=payload.scope_type,
|
scope_type=payload.scope_type,
|
||||||
scope_id=payload.scope_id,
|
scope_id=payload.scope_id,
|
||||||
@@ -587,11 +777,11 @@ def revoke_group_permission(
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="group_not_found")
|
||||||
_resolve_module_id(db, payload.system, payload.module)
|
_resolve_module_id(db, payload.system, payload.module)
|
||||||
_resolve_scope_ids(db, payload.scope_type, payload.scope_id)
|
_resolve_scope_ids(db, payload.scope_type, payload.scope_id)
|
||||||
module_key = f"{payload.system}.{payload.module}" if payload.module else f"{payload.system}.__system__"
|
module_name = _split_module_key(payload.module)
|
||||||
deleted = groups_repo.revoke_group_permission(
|
deleted = groups_repo.revoke_group_permission(
|
||||||
group_id=group.id,
|
group_id=group.id,
|
||||||
system=payload.system,
|
system=payload.system,
|
||||||
module=module_key,
|
module=module_name,
|
||||||
action=payload.action,
|
action=payload.action,
|
||||||
scope_type=payload.scope_type,
|
scope_type=payload.scope_type,
|
||||||
scope_id=payload.scope_id,
|
scope_id=payload.scope_id,
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
from sqlalchemy import delete, func, select
|
from sqlalchemy import delete, func, select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.models.permission_group import PermissionGroup
|
from app.models.permission_group import PermissionGroup
|
||||||
from app.models.permission_group_member import PermissionGroupMember
|
from app.models.permission_group_member import PermissionGroupMember
|
||||||
from app.models.permission_group_permission import PermissionGroupPermission
|
from app.models.permission_group_permission import PermissionGroupPermission
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
class PermissionGroupsRepository:
|
class PermissionGroupsRepository:
|
||||||
@@ -125,6 +128,128 @@ class PermissionGroupsRepository:
|
|||||||
)
|
)
|
||||||
return list(self.db.scalars(stmt).all())
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|
||||||
|
def replace_group_bindings(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
group_id: str,
|
||||||
|
site_keys: list[str],
|
||||||
|
system_keys: list[str],
|
||||||
|
module_keys: list[str],
|
||||||
|
member_subs: list[str],
|
||||||
|
actions: list[str],
|
||||||
|
) -> None:
|
||||||
|
normalized_sites = list(dict.fromkeys([s for s in site_keys if s]))
|
||||||
|
normalized_actions = [a for a in list(dict.fromkeys(actions)) if a in {"view", "edit"}]
|
||||||
|
normalized_member_subs = list(dict.fromkeys([s for s in member_subs if s]))
|
||||||
|
|
||||||
|
modules_by_system: dict[str, list[str]] = defaultdict(list)
|
||||||
|
for full_module_key in list(dict.fromkeys([m for m in module_keys if m])):
|
||||||
|
if "." not in full_module_key:
|
||||||
|
continue
|
||||||
|
system_key, module_name = full_module_key.split(".", 1)
|
||||||
|
if module_name == "__system__":
|
||||||
|
continue
|
||||||
|
modules_by_system[system_key].append(module_name)
|
||||||
|
|
||||||
|
normalized_systems = set([s for s in system_keys if s])
|
||||||
|
normalized_systems.update(modules_by_system.keys())
|
||||||
|
|
||||||
|
self.db.execute(delete(PermissionGroupPermission).where(PermissionGroupPermission.group_id == group_id))
|
||||||
|
self.db.execute(delete(PermissionGroupMember).where(PermissionGroupMember.group_id == group_id))
|
||||||
|
|
||||||
|
for sub in normalized_member_subs:
|
||||||
|
self.db.add(PermissionGroupMember(group_id=group_id, authentik_sub=sub))
|
||||||
|
|
||||||
|
for site_key in normalized_sites:
|
||||||
|
for action in normalized_actions:
|
||||||
|
for system_key in sorted(normalized_systems):
|
||||||
|
module_names = modules_by_system.get(system_key) or ["__system__"]
|
||||||
|
for module_name in module_names:
|
||||||
|
self.db.add(
|
||||||
|
PermissionGroupPermission(
|
||||||
|
group_id=group_id,
|
||||||
|
system=system_key,
|
||||||
|
module=module_name,
|
||||||
|
action=action,
|
||||||
|
scope_type="site",
|
||||||
|
scope_id=site_key,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.db.commit()
|
||||||
|
|
||||||
|
def get_group_binding_snapshot(self, group_id: str, group_key: str) -> dict:
|
||||||
|
permissions = self.list_group_permissions(group_id)
|
||||||
|
site_keys = sorted({p.scope_id for p in permissions if p.scope_type == "site"})
|
||||||
|
system_keys = sorted({p.system for p in permissions})
|
||||||
|
actions = sorted({p.action for p in permissions if p.action in {"view", "edit"}})
|
||||||
|
module_keys = sorted(
|
||||||
|
{
|
||||||
|
p.module if "." in p.module else f"{p.system}.{p.module}"
|
||||||
|
for p in permissions
|
||||||
|
if p.module and p.module != "__system__"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
member_subs = sorted(self.list_group_member_subs(group_id))
|
||||||
|
return {
|
||||||
|
"group_key": group_key,
|
||||||
|
"site_keys": site_keys,
|
||||||
|
"system_keys": system_keys,
|
||||||
|
"module_keys": module_keys,
|
||||||
|
"member_subs": member_subs,
|
||||||
|
"actions": actions,
|
||||||
|
}
|
||||||
|
|
||||||
|
def list_group_member_subs(self, group_id: str) -> list[str]:
|
||||||
|
stmt = (
|
||||||
|
select(PermissionGroupMember.authentik_sub)
|
||||||
|
.where(PermissionGroupMember.group_id == group_id)
|
||||||
|
.order_by(PermissionGroupMember.authentik_sub.asc())
|
||||||
|
)
|
||||||
|
return [row[0] for row in self.db.execute(stmt).all()]
|
||||||
|
|
||||||
|
def list_system_groups(self, system_key: str) -> list[PermissionGroup]:
|
||||||
|
stmt = (
|
||||||
|
select(PermissionGroup)
|
||||||
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroup.id)
|
||||||
|
.where(PermissionGroupPermission.system == system_key)
|
||||||
|
.order_by(PermissionGroup.name.asc())
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|
||||||
|
def list_system_members(self, system_key: str) -> list[User]:
|
||||||
|
stmt = (
|
||||||
|
select(User)
|
||||||
|
.join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub)
|
||||||
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id)
|
||||||
|
.where(PermissionGroupPermission.system == system_key)
|
||||||
|
.order_by(User.email.asc(), User.authentik_sub.asc())
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|
||||||
|
def list_module_groups(self, system_key: str, module_name: str) -> list[PermissionGroup]:
|
||||||
|
stmt = (
|
||||||
|
select(PermissionGroup)
|
||||||
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroup.id)
|
||||||
|
.where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name)
|
||||||
|
.order_by(PermissionGroup.name.asc())
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|
||||||
|
def list_module_members(self, system_key: str, module_name: str) -> list[User]:
|
||||||
|
stmt = (
|
||||||
|
select(User)
|
||||||
|
.join(PermissionGroupMember, PermissionGroupMember.authentik_sub == User.authentik_sub)
|
||||||
|
.join(PermissionGroupPermission, PermissionGroupPermission.group_id == PermissionGroupMember.group_id)
|
||||||
|
.where(PermissionGroupPermission.system == system_key, PermissionGroupPermission.module == module_name)
|
||||||
|
.order_by(User.email.asc(), User.authentik_sub.asc())
|
||||||
|
.distinct()
|
||||||
|
)
|
||||||
|
return list(self.db.scalars(stmt).all())
|
||||||
|
|
||||||
def revoke_group_permission(
|
def revoke_group_permission(
|
||||||
self,
|
self,
|
||||||
group_id: str,
|
group_id: str,
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ class PermissionsRepository:
|
|||||||
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
||||||
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
||||||
.where(UserScopePermission.user_id == user_id)
|
.where(UserScopePermission.user_id == user_id)
|
||||||
|
.where(UserScopePermission.action.in_(["view", "edit"]))
|
||||||
|
.where(UserScopePermission.scope_type == "site")
|
||||||
)
|
)
|
||||||
group_stmt = (
|
group_stmt = (
|
||||||
select(
|
select(
|
||||||
@@ -42,6 +44,8 @@ class PermissionsRepository:
|
|||||||
.select_from(PermissionGroupPermission)
|
.select_from(PermissionGroupPermission)
|
||||||
.join(PermissionGroupMember, PermissionGroupMember.group_id == PermissionGroupPermission.group_id)
|
.join(PermissionGroupMember, PermissionGroupMember.group_id == PermissionGroupPermission.group_id)
|
||||||
.where(PermissionGroupMember.authentik_sub == authentik_sub)
|
.where(PermissionGroupMember.authentik_sub == authentik_sub)
|
||||||
|
.where(PermissionGroupPermission.action.in_(["view", "edit"]))
|
||||||
|
.where(PermissionGroupPermission.scope_type == "site")
|
||||||
)
|
)
|
||||||
rows = self.db.execute(direct_stmt).all() + self.db.execute(group_stmt).all()
|
rows = self.db.execute(direct_stmt).all() + self.db.execute(group_stmt).all()
|
||||||
result: list[tuple[str, str, str | None, str, str]] = []
|
result: list[tuple[str, str, str | None, str, str]] = []
|
||||||
@@ -50,6 +54,10 @@ class PermissionsRepository:
|
|||||||
source = row[0]
|
source = row[0]
|
||||||
if source == "group":
|
if source == "group":
|
||||||
_, scope_type, scope_id, system_key, module_key, action = row
|
_, scope_type, scope_id, system_key, module_key, action = row
|
||||||
|
if module_key == "__system__":
|
||||||
|
module_key = f"{system_key}.__system__"
|
||||||
|
elif module_key and "." not in module_key:
|
||||||
|
module_key = f"{system_key}.{module_key}"
|
||||||
else:
|
else:
|
||||||
_, scope_type, company_key, site_key, module_key, action = row
|
_, scope_type, company_key, site_key, module_key, action = row
|
||||||
scope_id = company_key if scope_type == "company" else site_key
|
scope_id = company_key if scope_type == "company" else site_key
|
||||||
@@ -147,6 +155,8 @@ class PermissionsRepository:
|
|||||||
.join(Module, Module.id == UserScopePermission.module_id)
|
.join(Module, Module.id == UserScopePermission.module_id)
|
||||||
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
||||||
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
||||||
|
.where(UserScopePermission.action.in_(["view", "edit"]))
|
||||||
|
.where(UserScopePermission.scope_type == "site")
|
||||||
)
|
)
|
||||||
count_stmt = (
|
count_stmt = (
|
||||||
select(func.count())
|
select(func.count())
|
||||||
@@ -155,9 +165,11 @@ class PermissionsRepository:
|
|||||||
.join(Module, Module.id == UserScopePermission.module_id)
|
.join(Module, Module.id == UserScopePermission.module_id)
|
||||||
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
.join(Company, Company.id == UserScopePermission.company_id, isouter=True)
|
||||||
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
.join(Site, Site.id == UserScopePermission.site_id, isouter=True)
|
||||||
|
.where(UserScopePermission.action.in_(["view", "edit"]))
|
||||||
|
.where(UserScopePermission.scope_type == "site")
|
||||||
)
|
)
|
||||||
|
|
||||||
if scope_type in {"company", "site"}:
|
if scope_type == "site":
|
||||||
stmt = stmt.where(UserScopePermission.scope_type == scope_type)
|
stmt = stmt.where(UserScopePermission.scope_type == scope_type)
|
||||||
count_stmt = count_stmt.where(UserScopePermission.scope_type == scope_type)
|
count_stmt = count_stmt.where(UserScopePermission.scope_type == scope_type)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
|
||||||
class SystemCreateRequest(BaseModel):
|
class SystemCreateRequest(BaseModel):
|
||||||
@@ -134,11 +135,41 @@ class PermissionGroupPermissionItem(BaseModel):
|
|||||||
id: str
|
id: str
|
||||||
system: str
|
system: str
|
||||||
module: str
|
module: str
|
||||||
action: str
|
action: Literal["view", "edit"]
|
||||||
scope_type: str
|
scope_type: Literal["site"]
|
||||||
scope_id: str
|
scope_id: str
|
||||||
|
|
||||||
|
|
||||||
class MemberPermissionGroupsResponse(BaseModel):
|
class MemberPermissionGroupsResponse(BaseModel):
|
||||||
authentik_sub: str
|
authentik_sub: str
|
||||||
group_keys: list[str]
|
group_keys: list[str]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupBindingUpdateRequest(BaseModel):
|
||||||
|
site_keys: list[str]
|
||||||
|
system_keys: list[str]
|
||||||
|
module_keys: list[str]
|
||||||
|
member_subs: list[str]
|
||||||
|
actions: list[Literal["view", "edit"]]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupBindingSnapshot(BaseModel):
|
||||||
|
group_key: str
|
||||||
|
site_keys: list[str]
|
||||||
|
system_keys: list[str]
|
||||||
|
module_keys: list[str]
|
||||||
|
member_subs: list[str]
|
||||||
|
actions: list[Literal["view", "edit"]]
|
||||||
|
|
||||||
|
|
||||||
|
class GroupRelationItem(BaseModel):
|
||||||
|
group_key: str
|
||||||
|
group_name: str
|
||||||
|
status: str
|
||||||
|
|
||||||
|
|
||||||
|
class MemberRelationItem(BaseModel):
|
||||||
|
authentik_sub: str
|
||||||
|
email: str | None = None
|
||||||
|
display_name: str | None = None
|
||||||
|
is_active: bool
|
||||||
|
|||||||
@@ -1,34 +1,38 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
ActionType = Literal["view", "edit"]
|
||||||
|
ScopeType = Literal["site"]
|
||||||
|
|
||||||
|
|
||||||
class PermissionGrantRequest(BaseModel):
|
class PermissionGrantRequest(BaseModel):
|
||||||
authentik_sub: str
|
authentik_sub: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
scope_type: str
|
scope_type: ScopeType
|
||||||
scope_id: str
|
scope_id: str
|
||||||
system: str
|
system: str
|
||||||
module: str | None = None
|
module: str | None = None
|
||||||
action: str
|
action: ActionType
|
||||||
|
|
||||||
|
|
||||||
class PermissionRevokeRequest(BaseModel):
|
class PermissionRevokeRequest(BaseModel):
|
||||||
authentik_sub: str
|
authentik_sub: str
|
||||||
scope_type: str
|
scope_type: ScopeType
|
||||||
scope_id: str
|
scope_id: str
|
||||||
system: str
|
system: str
|
||||||
module: str | None = None
|
module: str | None = None
|
||||||
action: str
|
action: ActionType
|
||||||
|
|
||||||
|
|
||||||
class PermissionItem(BaseModel):
|
class PermissionItem(BaseModel):
|
||||||
scope_type: str
|
scope_type: ScopeType
|
||||||
scope_id: str
|
scope_id: str
|
||||||
system: str | None = None
|
system: str | None = None
|
||||||
module: str
|
module: str
|
||||||
action: str
|
action: ActionType
|
||||||
|
|
||||||
|
|
||||||
class PermissionSnapshotResponse(BaseModel):
|
class PermissionSnapshotResponse(BaseModel):
|
||||||
@@ -41,11 +45,11 @@ class DirectPermissionRow(BaseModel):
|
|||||||
authentik_sub: str
|
authentik_sub: str
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
display_name: str | None = None
|
display_name: str | None = None
|
||||||
scope_type: str
|
scope_type: ScopeType
|
||||||
scope_id: str
|
scope_id: str
|
||||||
system: str | None = None
|
system: str | None = None
|
||||||
module: str | None = None
|
module: str | None = None
|
||||||
action: str
|
action: ActionType
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user