MediaRouter / app /security /scopes.py
basyx's picture
Upload 340 files
3493993 verified
Raw
History Blame Contribute Delete
7.45 kB
from __future__ import annotations
from collections.abc import Mapping, Sequence
ALL_SCOPES = frozenset(
{
"templates:read",
"templates:run",
"templates:create",
"templates:update",
"templates:delete",
"templates:apply",
"templates:publish",
"operations:read",
"operations:execute",
"jobs:read",
"jobs:create",
"jobs:cancel",
"assets:read",
"assets:write",
"assets:delete",
"mcp:read",
"mcp:execute",
"system:read",
"social:accounts:read",
"social:accounts:write",
"social:posts:read",
"social:posts:write",
"social:posts:publish",
"social:schedules:read",
"social:schedules:write",
"social:analytics:read",
"analytics:read",
"analytics:sync",
"analytics:export",
"generation:providers:read",
"generation:requests:read",
"generation:requests:create",
"generation:jobs:cancel",
"ai:read",
"ai:generate",
"ai:transform",
"ai:analyze",
"ai:create",
"copilot:read",
"copilot:execute",
"projects:read",
"projects:create",
"projects:update",
"projects:delete",
# Frontend Architecture v2 uses this aggregate solely for capability
# rendering. Enforcement remains method-specific in ScopePolicy.
"projects:write",
"members:read",
"members:invite",
"members:update",
"members:remove",
"teams:read",
"teams:create",
"teams:update",
"teams:delete",
"projects:share",
"projects:collaborate",
"comments:read",
"comments:create",
"approvals:read",
"approvals:create",
"approvals:review",
"approvals:manage",
"admin",
}
)
DEFAULT_ROLE_SCOPES: dict[str, frozenset[str]] = {
"admin": frozenset({"admin"}),
"developer": frozenset(
{
"templates:read",
"templates:run",
"templates:create",
"templates:update",
"templates:delete",
"templates:apply",
"templates:publish",
"operations:read",
"operations:execute",
"jobs:read",
"jobs:create",
"jobs:cancel",
"assets:read",
"assets:write",
"mcp:read",
"mcp:execute",
"system:read",
"social:accounts:read",
"social:accounts:write",
"social:posts:read",
"social:posts:write",
"social:posts:publish",
"social:schedules:read",
"social:schedules:write",
"social:analytics:read",
"analytics:read",
"analytics:sync",
"analytics:export",
"generation:providers:read",
"generation:requests:read",
"generation:requests:create",
"generation:jobs:cancel",
"ai:read",
"ai:generate",
"ai:transform",
"ai:analyze",
"ai:create",
"copilot:read",
"copilot:execute",
"projects:read",
"projects:create",
"projects:update",
"projects:delete",
"projects:write",
"members:read",
"members:invite",
"members:update",
"members:remove",
"teams:read",
"teams:create",
"teams:update",
"teams:delete",
"projects:share",
"projects:collaborate",
"comments:read",
"comments:create",
"approvals:read",
"approvals:create",
"approvals:review",
"approvals:manage",
}
),
"operator": frozenset(
{
"templates:read",
"templates:run",
"templates:create",
"templates:update",
"templates:delete",
"templates:apply",
"templates:publish",
"operations:read",
"operations:execute",
"jobs:read",
"jobs:create",
"jobs:cancel",
"assets:read",
"mcp:read",
"mcp:execute",
"system:read",
"social:accounts:read",
"social:accounts:write",
"social:posts:read",
"social:posts:write",
"social:posts:publish",
"social:schedules:read",
"social:schedules:write",
"social:analytics:read",
"analytics:read",
"analytics:sync",
"generation:providers:read",
"generation:requests:read",
"generation:requests:create",
"generation:jobs:cancel",
"ai:read",
"ai:generate",
"ai:transform",
"ai:analyze",
"ai:create",
"copilot:read",
"copilot:execute",
"projects:read",
"projects:create",
"projects:update",
"projects:delete",
"projects:write",
"members:read",
"teams:read",
"projects:share",
"projects:collaborate",
"comments:read",
"comments:create",
"approvals:read",
"approvals:review",
}
),
"viewer": frozenset(
{
"templates:read",
"operations:read",
"jobs:read",
"assets:read",
"mcp:read",
"system:read",
"social:accounts:read",
"social:posts:read",
"social:schedules:read",
"social:analytics:read",
"analytics:read",
"generation:providers:read",
"generation:requests:read",
"ai:read",
"copilot:read",
"projects:read",
"members:read",
"teams:read",
"projects:collaborate",
"comments:read",
"approvals:read",
}
),
}
def configured_roles(
overrides: Mapping[str, Sequence[str]] | None = None,
) -> dict[str, frozenset[str]]:
roles = dict(DEFAULT_ROLE_SCOPES)
for role, scopes in (overrides or {}).items():
normalized_role = role.strip().lower()
normalized_scopes = frozenset(scope.strip().lower() for scope in scopes)
unknown = normalized_scopes - ALL_SCOPES
if unknown:
raise ValueError(f"Role '{normalized_role}' has unsupported scopes: {sorted(unknown)}")
roles[normalized_role] = normalized_scopes
return roles
def effective_scopes(
role: str | None,
explicit_scopes: Sequence[str],
overrides: Mapping[str, Sequence[str]] | None = None,
) -> frozenset[str]:
scopes = set(scope.strip().lower() for scope in explicit_scopes)
if role:
scopes.update(configured_roles(overrides).get(role.strip().lower(), ()))
project_mutations = {"projects:create", "projects:update", "projects:delete"}
if "projects:write" in scopes:
scopes.update(project_mutations)
if project_mutations.issubset(scopes):
scopes.add("projects:write")
if "admin" in scopes:
return ALL_SCOPES
return frozenset(scopes)