mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
78 lines
2.5 KiB
Python
78 lines
2.5 KiB
Python
"""Which role names a user holds on one application.
|
|
|
|
Most role names are global: ``roles: [administrator]`` means administrator
|
|
everywhere it is declared. ``APPLICATION_SCOPED_ROLES`` are the exception.
|
|
Those carry a distinct grant per application, so reading them from the
|
|
unscoped list would hand a user every deployed application's copy at once.
|
|
They come only from ``application_roles``::
|
|
|
|
users:
|
|
alice:
|
|
application_roles:
|
|
web-app-baserow: [mcp]
|
|
|
|
This module is the single source of truth for that rule. The Keycloak realm
|
|
group builder, the LDAP role entry builder and the Open WebUI group reconciler
|
|
all resolve membership through it, so the three paths cannot drift.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Mapping
|
|
|
|
MCP_READER_ROLE = "mcp-reader"
|
|
MCP_WRITER_ROLE = "mcp-writer"
|
|
MCP_LEGACY_ROLE = "mcp"
|
|
|
|
MCP_ROLES = (MCP_READER_ROLE, MCP_WRITER_ROLE)
|
|
|
|
APPLICATION_SCOPED_ROLES = frozenset({MCP_LEGACY_ROLE, *MCP_ROLES})
|
|
|
|
|
|
def expand_mcp_role(role_name: str) -> str:
|
|
"""Return the role a grant confers, resolving the pre-split name.
|
|
|
|
Args:
|
|
role_name: a role as written in a user's grants.
|
|
"""
|
|
return MCP_READER_ROLE if role_name == MCP_LEGACY_ROLE else role_name
|
|
|
|
|
|
def granted_roles(user_config: Mapping[str, object], application_id: str) -> set[str]:
|
|
"""Return the role names this user holds on one application.
|
|
|
|
Args:
|
|
user_config: the user's merged configuration.
|
|
application_id: the application whose grants are being resolved.
|
|
"""
|
|
scoped = (user_config.get("application_roles") or {}).get(application_id) or []
|
|
unscoped = [
|
|
role
|
|
for role in (user_config.get("roles") or [])
|
|
if role not in APPLICATION_SCOPED_ROLES
|
|
]
|
|
return set(unscoped) | set(scoped) | {expand_mcp_role(r) for r in scoped}
|
|
|
|
|
|
def members_with_role(
|
|
users: Mapping[str, Mapping[str, object]] | None,
|
|
application_id: str,
|
|
role_name: str,
|
|
) -> list[str]:
|
|
"""Return the usernames holding ``role_name`` on ``application_id``.
|
|
|
|
Args:
|
|
users: the merged users mapping.
|
|
application_id: the application whose grants are being resolved.
|
|
role_name: the role to filter by.
|
|
"""
|
|
members = []
|
|
for username, user_config in (users or {}).items():
|
|
cfg = user_config or {}
|
|
if role_name in granted_roles(cfg, application_id):
|
|
members.append(str(cfg.get("username", username)))
|
|
return sorted(members)
|