1206 lines
66 KiB
Python
1206 lines
66 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from govoplan_access.backend.configuration_provider import ACCESS_CONFIGURATION_CAPABILITY
|
|
from govoplan_access.backend.dsar_provider import ACCESS_DSAR_CAPABILITY
|
|
from govoplan_access.backend.db.base import AccessBase
|
|
from govoplan_access.backend.db import models as access_models # noqa: F401 - populate access metadata
|
|
from govoplan_core.core.access import (
|
|
CAPABILITY_ACCESS_ADMINISTRATION,
|
|
CAPABILITY_ACCESS_DIRECTORY,
|
|
CAPABILITY_ACCESS_EXPLANATION,
|
|
CAPABILITY_ACCESS_FIRST_ADMIN_PROVISIONER,
|
|
CAPABILITY_ACCESS_GOVERNANCE_MATERIALIZER,
|
|
CAPABILITY_ACCESS_PERMISSION_EVALUATOR,
|
|
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
|
CAPABILITY_ACCESS_TENANT_PROVISIONER,
|
|
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY,
|
|
CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER,
|
|
CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER,
|
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR,
|
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER,
|
|
CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER,
|
|
ResourceAccessExplanationProvider,
|
|
)
|
|
from govoplan_core.core.campaigns import CAPABILITY_CAMPAIGNS_ACCESS
|
|
from govoplan_core.core.files import CAPABILITY_FILES_ACCESS
|
|
from govoplan_core.core.identity import CAPABILITY_IDENTITY_DIRECTORY, IdentityDirectory
|
|
from govoplan_core.core.idm import CAPABILITY_IDM_DIRECTORY, IdmDirectory
|
|
from govoplan_core.core.organizations import CAPABILITY_ORGANIZATION_DIRECTORY, OrganizationDirectory
|
|
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
|
from govoplan_core.core.provider_governance import declared_module_architecture
|
|
from govoplan_core.core.modules import (
|
|
DocumentationCondition,
|
|
DocumentationLink,
|
|
DocumentationTopic,
|
|
FrontendModule,
|
|
FrontendRoute,
|
|
MigrationSpec,
|
|
ModuleContext,
|
|
ModuleInterfaceProvider,
|
|
ModuleManifest,
|
|
NavItem,
|
|
PermissionDefinition,
|
|
RoleTemplate,
|
|
)
|
|
from govoplan_core.core.people import CAPABILITY_ACCESS_PEOPLE_SEARCH
|
|
from govoplan_core.core.references import CAPABILITY_ACCESS_REFERENCE_OPTIONS
|
|
from govoplan_core.core.views import ViewSurface
|
|
|
|
|
|
def _permission(scope: str, label: str, description: str, category: str, level: str) -> PermissionDefinition:
|
|
module_id, resource, action = scope.split(":", 2)
|
|
return PermissionDefinition(
|
|
scope=scope,
|
|
label=label,
|
|
description=description,
|
|
category=category,
|
|
level=level, # type: ignore[arg-type]
|
|
module_id=module_id,
|
|
resource=resource,
|
|
action=action,
|
|
)
|
|
|
|
|
|
ACCESS_PERMISSIONS: tuple[PermissionDefinition, ...] = (
|
|
_permission("access:tenant:read", "View tenants", "List and inspect tenant registry entries.", "Access", "system"),
|
|
_permission("access:tenant:create", "Create tenants", "Create tenant registry entries.", "Access", "system"),
|
|
_permission("access:tenant:update", "Update tenants", "Update tenant metadata and activation state.", "Access", "system"),
|
|
_permission("access:tenant:suspend", "Suspend tenants", "Activate or suspend tenant spaces while preserving evidence.", "Access", "system"),
|
|
_permission("access:account:read", "View accounts", "List and inspect global login accounts.", "Access", "system"),
|
|
_permission("access:account:create", "Create accounts", "Create global login accounts.", "Access", "system"),
|
|
_permission("access:account:update", "Update accounts", "Update or suspend global login accounts.", "Access", "system"),
|
|
_permission("access:account:suspend", "Suspend accounts", "Activate or suspend global login accounts while preserving a system owner.", "Access", "system"),
|
|
_permission("access:system_role:read", "View system roles", "Inspect instance-wide role definitions and their permissions.", "Access", "system"),
|
|
_permission("access:system_role:write", "Define system roles", "Create and edit instance-wide role definitions within delegation limits.", "Access", "system"),
|
|
_permission("access:system_role:assign", "Assign system roles", "Assign instance-wide roles to accounts while preserving a system owner.", "Access", "system"),
|
|
_permission("access:system_setting:read", "View system settings", "Read instance defaults and tenant-governance defaults.", "Access", "system"),
|
|
_permission("access:system_setting:write", "Manage system settings", "Change instance defaults and tenant-governance defaults.", "Access", "system"),
|
|
_permission("access:system_credential:read", "View system credentials", "List instance-wide reusable credential envelopes without revealing secret values.", "Access", "system"),
|
|
_permission("access:system_credential:write", "Manage system credentials", "Create, update, and retire instance-wide reusable credential envelopes.", "Access", "system"),
|
|
_permission("access:maintenance:access", "Access during maintenance", "Use the system while maintenance mode is active.", "Access", "system"),
|
|
_permission("access:audit:read", "View system audit", "Read audit records across tenants.", "Access", "system"),
|
|
_permission("access:membership:read", "View memberships", "List tenant memberships and effective access.", "Tenant access", "tenant"),
|
|
_permission("access:membership:create", "Create memberships", "Create tenant-local account memberships.", "Tenant access", "tenant"),
|
|
_permission("access:membership:update", "Update memberships", "Update or suspend tenant memberships.", "Tenant access", "tenant"),
|
|
_permission("access:session:manage_own", "Manage own sessions", "Inspect and revoke the current account's browser sessions without exposing credentials.", "Tenant access", "tenant"),
|
|
_permission("access:group:read", "View groups", "List tenant groups and members.", "Tenant access", "tenant"),
|
|
_permission("access:group:write", "Manage groups", "Create and update tenant groups.", "Tenant access", "tenant"),
|
|
_permission("access:group:manage_members", "Manage group members", "Add and remove memberships from groups.", "Tenant access", "tenant"),
|
|
_permission("access:role:read", "View roles", "Inspect tenant and system role definitions.", "Tenant access", "tenant"),
|
|
_permission("access:role:write", "Manage roles", "Create and update assignable roles.", "Tenant access", "tenant"),
|
|
_permission("access:role:assign", "Assign roles", "Bind roles to memberships, groups, accounts or services.", "Tenant access", "tenant"),
|
|
_permission("access:function:read", "View functions", "Inspect organization-bound functions and assignments.", "Tenant access", "tenant"),
|
|
_permission("access:function:write", "Manage functions", "Create and update organization units, functions, and role mappings.", "Tenant access", "tenant"),
|
|
_permission("access:function:assign", "Assign functions", "Assign organization-bound functions to accounts.", "Tenant access", "tenant"),
|
|
_permission("access:function:delegate", "Delegate functions", "Create and revoke permitted function delegations.", "Tenant access", "tenant"),
|
|
_permission("access:api_key:read", "View API keys", "List API keys without revealing secrets.", "Tenant access", "tenant"),
|
|
_permission("access:api_key:create", "Create API keys", "Create tenant API keys within delegation limits.", "Tenant access", "tenant"),
|
|
_permission("access:api_key:revoke", "Revoke API keys", "Revoke tenant API keys.", "Tenant access", "tenant"),
|
|
_permission("access:service_account:read", "View service accounts", "List non-login automation principals and their current scope ceilings.", "Tenant access", "tenant"),
|
|
_permission("access:service_account:write", "Manage service accounts", "Create, update, suspend, and retire scope-bounded automation principals.", "Tenant access", "tenant"),
|
|
_permission("access:setting:read", "View settings", "Read access and governance settings.", "Tenant access", "tenant"),
|
|
_permission("access:setting:write", "Manage settings", "Update access and governance settings.", "Tenant access", "tenant"),
|
|
_permission("access:credential:read", "View credentials", "List reusable credential envelopes without revealing secret values.", "Tenant access", "tenant"),
|
|
_permission("access:credential:write", "Manage credentials", "Create, update, and retire reusable credential envelopes.", "Tenant access", "tenant"),
|
|
_permission("access:credential:manage_own", "Manage own credentials", "Manage reusable credentials owned by the current membership.", "Tenant access", "tenant"),
|
|
_permission("access:policy:read", "View tenant policies", "Read tenant policy and governance settings.", "Tenant access", "tenant"),
|
|
_permission("access:policy:write", "Manage tenant policies", "Change tenant policy and governance settings where system policy permits it.", "Tenant access", "tenant"),
|
|
_permission("access:privacy:read", "View data-subject requests", "Inspect tenant data-subject requests, provider coverage, and retained evidence decisions.", "Privacy", "tenant"),
|
|
_permission("access:privacy:manage", "Manage data-subject requests", "Create requests and run provider searches and erasure planning.", "Privacy", "tenant"),
|
|
_permission("access:privacy:export", "Export data-subject requests", "Export the collected personal-data package and its coverage manifest.", "Privacy", "tenant"),
|
|
_permission("access:privacy:erase", "Execute data erasure", "Execute explicitly selected, provider-owned erasure and anonymization actions.", "Privacy", "tenant"),
|
|
_permission("access:governance:read", "View governance", "Inspect managed role and group templates.", "Access", "system"),
|
|
_permission("access:governance:write", "Manage governance", "Create and assign managed role and group templates.", "Access", "system"),
|
|
)
|
|
|
|
ACCESS_ROLE_TEMPLATES: tuple[RoleTemplate, ...] = (
|
|
RoleTemplate(
|
|
slug="system_owner",
|
|
name="System owner",
|
|
description="Protected full instance-wide administration.",
|
|
permissions=("system:*",),
|
|
level="system",
|
|
managed=True,
|
|
protected=True,
|
|
),
|
|
RoleTemplate(
|
|
slug="system_admin",
|
|
name="System administrator",
|
|
description="Manage tenants, accounts, settings, and governance without protected owner status.",
|
|
permissions=(
|
|
"access:tenant:read",
|
|
"access:tenant:create",
|
|
"access:tenant:update",
|
|
"access:tenant:suspend",
|
|
"access:account:read",
|
|
"access:account:create",
|
|
"access:account:update",
|
|
"access:account:suspend",
|
|
"access:system_role:read",
|
|
"access:system_role:write",
|
|
"access:system_role:assign",
|
|
"access:system_setting:read",
|
|
"access:system_setting:write",
|
|
"access:system_credential:read",
|
|
"access:system_credential:write",
|
|
"access:governance:read",
|
|
"access:governance:write",
|
|
),
|
|
level="system",
|
|
managed=False,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="system_auditor",
|
|
name="System auditor",
|
|
description="Read tenant registry, accounts, system roles, settings, governance, and cross-tenant audit records.",
|
|
permissions=(
|
|
"access:tenant:read",
|
|
"access:account:read",
|
|
"access:system_role:read",
|
|
"access:audit:read",
|
|
"access:system_setting:read",
|
|
"access:governance:read",
|
|
),
|
|
level="system",
|
|
managed=False,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="maintenance_operator",
|
|
name="Maintenance operator",
|
|
description="Access the system while maintenance mode is active.",
|
|
permissions=("access:system_setting:read", "access:maintenance:access"),
|
|
level="system",
|
|
managed=False,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="account_security",
|
|
name="Account security",
|
|
description="Authenticated baseline for inspecting and revoking the current account's browser sessions.",
|
|
permissions=("access:session:manage_own",),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=True,
|
|
default_authenticated=True,
|
|
),
|
|
RoleTemplate(
|
|
slug="owner",
|
|
name="Tenant owner",
|
|
description="Protected full tenant administration and module access.",
|
|
permissions=("tenant:*",),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=True,
|
|
),
|
|
RoleTemplate(
|
|
slug="tenant_admin",
|
|
name="Tenant administrator",
|
|
description="Manage tenant settings, policies, users, groups, roles and API keys.",
|
|
permissions=(
|
|
"access:membership:read",
|
|
"access:membership:create",
|
|
"access:membership:update",
|
|
"access:group:read",
|
|
"access:group:write",
|
|
"access:group:manage_members",
|
|
"access:role:read",
|
|
"access:role:write",
|
|
"access:role:assign",
|
|
"access:api_key:read",
|
|
"access:api_key:create",
|
|
"access:api_key:revoke",
|
|
"access:service_account:read",
|
|
"access:service_account:write",
|
|
"access:setting:read",
|
|
"access:setting:write",
|
|
"access:credential:read",
|
|
"access:credential:write",
|
|
"access:policy:read",
|
|
"access:policy:write",
|
|
),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="admin",
|
|
name="Administrator (legacy)",
|
|
description="Legacy broad tenant role retained for upgraded installations.",
|
|
permissions=("tenant:*",),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="access_admin",
|
|
name="Access administrator",
|
|
description="Manage memberships, groups, roles, and API keys within delegation limits.",
|
|
permissions=(
|
|
"access:membership:read",
|
|
"access:membership:create",
|
|
"access:membership:update",
|
|
"access:group:read",
|
|
"access:group:write",
|
|
"access:group:manage_members",
|
|
"access:role:read",
|
|
"access:role:assign",
|
|
"access:function:read",
|
|
"access:function:write",
|
|
"access:function:assign",
|
|
"access:function:delegate",
|
|
"access:api_key:read",
|
|
"access:api_key:create",
|
|
"access:api_key:revoke",
|
|
),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=False,
|
|
),
|
|
RoleTemplate(
|
|
slug="privacy_officer",
|
|
name="Privacy officer",
|
|
description="Search, export, plan, and execute governed data-subject requests.",
|
|
permissions=(
|
|
"access:privacy:read",
|
|
"access:privacy:manage",
|
|
"access:privacy:export",
|
|
"access:privacy:erase",
|
|
),
|
|
level="tenant",
|
|
managed=True,
|
|
protected=False,
|
|
),
|
|
)
|
|
|
|
ADMIN_READ_SCOPES = (
|
|
"admin:users:read",
|
|
"admin:groups:read",
|
|
"admin:roles:read",
|
|
"admin:api_keys:read",
|
|
"access:service_account:read",
|
|
"admin:settings:read",
|
|
"system:tenants:read",
|
|
"system:accounts:read",
|
|
"system:roles:read",
|
|
"system:settings:read",
|
|
"system:governance:read",
|
|
"system:audit:read",
|
|
"access:tenant:read",
|
|
"access:account:read",
|
|
"access:governance:read",
|
|
"access:function:read",
|
|
"access:privacy:read",
|
|
"views:definition:read",
|
|
"views:assignment:read",
|
|
"views:system_definition:read",
|
|
"views:system_assignment:read",
|
|
)
|
|
|
|
ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = (
|
|
DocumentationTopic(
|
|
id="access.reference.effective-appearance",
|
|
title="Understand the effective appearance source",
|
|
summary="The authenticated profile explains the effective palette, policy lock, and governed personal token overrides.",
|
|
body=(
|
|
"An explicit personal palette normally wins over tenant and system defaults. "
|
|
"Resetting it stores inheritance, not a copy of the current default. A tenant "
|
|
"policy lock suppresses personal choices; a system lock suppresses both tenant "
|
|
"and personal choices. Access enforces the lock during profile writes and returns "
|
|
"the effective palette, source, inherited value, and lock state on full profile responses. "
|
|
"Advanced accent, surface, and status overrides are accepted only when the system opts in, "
|
|
"the tenant does not block them, and neither palette scope is locked. Both light and dark "
|
|
"documents are versioned and validated atomically for hexadecimal values, WCAG AA paired "
|
|
"contrast, and distinct status colors. Invalid or disallowed documents are never partially "
|
|
"applied; users may still remove an inactive stored override to return to inheritance."
|
|
),
|
|
layer="always",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "tenant_admin", "system_admin"),
|
|
order=8,
|
|
links=(DocumentationLink(label="Profile API", href="/api/v1/auth/profile", kind="api"),),
|
|
related_modules=("admin", "tenancy"),
|
|
metadata={"kind": "reference", "help_contexts": ["core.settings", "admin.system-settings", "tenancy.admin.tenant-settings"]},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.reference.resource-explanation-subjects",
|
|
title="Select a user for resource-access diagnostics",
|
|
summary=(
|
|
"Resource explanations default to the signed-in user and expose "
|
|
"other tenant users only when Policy permits the diagnostic."
|
|
),
|
|
body=(
|
|
"The shared Files and Campaign explanation dialog asks Access for "
|
|
"the permitted subject list. If Policy is unavailable or the actor "
|
|
"lacks policy:access_explanation:select_user, Access returns only "
|
|
"the signed-in active membership and no metadata for other users. "
|
|
"When Policy permits selection, the picker is limited to active "
|
|
"users in the current tenant. Every explanation run for another "
|
|
"user creates audit evidence with the target membership, resource, "
|
|
"requested action, and policy source. The explanation is diagnostic "
|
|
"and never grants resource access."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("tenant_admin", "access_admin", "security_reviewer"),
|
|
order=29,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=(
|
|
"admin:users:read",
|
|
"admin:roles:read",
|
|
"access:membership:read",
|
|
"access:role:read",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(
|
|
label="Permitted explanation subjects API",
|
|
href="/api/v1/admin/access/resource-explanation/subjects",
|
|
kind="api",
|
|
),
|
|
DocumentationLink(
|
|
label="Resource explanation API",
|
|
href="/api/v1/admin/access/resource-explanation",
|
|
kind="api",
|
|
),
|
|
),
|
|
related_modules=("audit", "campaigns", "files", "policy"),
|
|
metadata={
|
|
"kind": "reference",
|
|
"help_contexts": ["access.resource-explanation.subject"],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.operator.enroll-first-administrator",
|
|
title="Enroll the first production administrator",
|
|
summary="A local operator can issue one expiring credential while no durable system administrator exists, then use it once to create the protected system owner and an initial tenant membership.",
|
|
body=(
|
|
"Run the Core first-admin issue command after migrations and after Access is installed. The command stores the random secret in a local mode-0600 artifact and prints only its path, fingerprint, and expiry. "
|
|
"The public bootstrap status endpoint exposes only minimum readiness. The enrollment endpoint accepts only the first account and initial tenant fields, creates one protected system owner plus one tenant-owner membership atomically, and retires the credential. "
|
|
"Identical retries return the completed account without creating another owner. Lost or expired material can be rotated only by the local recovery command and only while the durable-administrator check remains empty. Development bootstrap settings are a separate dev-only path and are never enabled by enrollment."
|
|
),
|
|
layer="always",
|
|
documentation_types=("admin",),
|
|
audience=("operator", "system_admin"),
|
|
order=10,
|
|
links=(
|
|
DocumentationLink(label="Bootstrap readiness API", href="/api/v1/bootstrap/status", kind="api"),
|
|
DocumentationLink(label="First-administrator enrollment API", href="/api/v1/bootstrap/first-admin", kind="api"),
|
|
),
|
|
metadata={
|
|
"kind": "operator_workflow",
|
|
"commands": [
|
|
"python -m govoplan_core.commands.first_admin status",
|
|
"python -m govoplan_core.commands.first_admin issue --reason 'initial production installation'",
|
|
"python -m govoplan_core.commands.first_admin recover --reason 'lost or expired handoff'",
|
|
],
|
|
"security_properties": [
|
|
"random expiring credential",
|
|
"mode-0600 local artifact",
|
|
"single-use idempotent enrollment",
|
|
"empty-install authority gate",
|
|
"hash-chained and audit evidence",
|
|
],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.workflow.grant-user-access",
|
|
title="Grant a person access",
|
|
summary="Use the access administration screens to create or update a tenant membership, place the person in groups, and assign only the roles they need.",
|
|
body="The common path is to find or create the person, review their existing membership, then use groups and roles to grant access. If a role or group is not available, the active governance rules or your own delegation limit may block the change.",
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("tenant_admin", "access_admin"),
|
|
order=30,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=(
|
|
"admin:users:read",
|
|
"admin:groups:read",
|
|
"admin:roles:read",
|
|
"access:membership:read",
|
|
"access:group:read",
|
|
"access:role:read",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Access administration", href="/admin", kind="runtime"),
|
|
DocumentationLink(label="Users API", href="/api/v1/admin/users", kind="api"),
|
|
DocumentationLink(label="Groups API", href="/api/v1/admin/groups", kind="api"),
|
|
DocumentationLink(label="Roles API", href="/api/v1/admin/roles", kind="api"),
|
|
),
|
|
configuration_keys=("access_governance",),
|
|
metadata={
|
|
"kind": "workflow",
|
|
"help_contexts": [
|
|
"access.admin.users",
|
|
"access.admin.groups",
|
|
"access.admin.roles",
|
|
"access.admin.blocked",
|
|
],
|
|
"outcome": "A person can sign in to the tenant and receives the intended access through groups and roles.",
|
|
"prerequisites": [
|
|
"You can open Admin.",
|
|
"You may read users, groups, and roles.",
|
|
"Write or assignment actions require matching management permissions.",
|
|
],
|
|
"steps": [
|
|
"Open Admin and go to Users.",
|
|
"Find the existing person or create a membership with their email address and display name.",
|
|
"Review current groups and direct roles before changing anything.",
|
|
"Add the person to the smallest group that grants the needed shared access.",
|
|
"Assign direct roles only when a group does not match the case.",
|
|
"Save and review any blocker message before asking a system or tenant owner for help.",
|
|
],
|
|
"result": "The membership has the intended effective permissions and no broader roles than necessary.",
|
|
"verification": "Open the user again and compare groups, direct roles, and effective permissions with the request.",
|
|
"related_field_ids": [
|
|
"access.user.email",
|
|
"access.user.display_name",
|
|
"access.user.groups",
|
|
"access.user.roles",
|
|
],
|
|
"related_topic_ids": [
|
|
"access.reference.admin-access-fields",
|
|
"docs.pattern.field-help",
|
|
],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.reference.admin-access-fields",
|
|
title="Access administration fields",
|
|
summary="The access administration screens show tenant memberships, groups, roles, and API keys. Admin docs map the visible fields to API payloads and permission scopes.",
|
|
body="Users need the visible labels and a short explanation. Admins also need the backing route, API field, permission, and governance note so they can diagnose unavailable actions. New API-key secrets use the GovOPlaN `gpn_` marker; previously issued keys retain their original value and remain valid until expiry or revocation.",
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("tenant_admin", "access_admin", "operator"),
|
|
order=31,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=(
|
|
"admin:users:read",
|
|
"admin:groups:read",
|
|
"admin:roles:read",
|
|
"admin:api_keys:read",
|
|
"access:membership:read",
|
|
"access:group:read",
|
|
"access:role:read",
|
|
"access:api_key:read",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Access administration", href="/admin", kind="runtime"),
|
|
DocumentationLink(label="Users API", href="/api/v1/admin/users", kind="api"),
|
|
DocumentationLink(label="Groups API", href="/api/v1/admin/groups", kind="api"),
|
|
DocumentationLink(label="Roles API", href="/api/v1/admin/roles", kind="api"),
|
|
DocumentationLink(label="API keys API", href="/api/v1/admin/api-keys", kind="api"),
|
|
DocumentationLink(label="Service accounts", href="/admin?section=tenant-service-accounts", kind="runtime"),
|
|
DocumentationLink(label="Service accounts API", href="/api/v1/admin/service-accounts", kind="api"),
|
|
),
|
|
configuration_keys=("access_governance",),
|
|
metadata={
|
|
"kind": "reference",
|
|
"help_contexts": [
|
|
"access.admin.system-users",
|
|
"access.admin.system-roles",
|
|
"access.admin.tenant-users",
|
|
"access.admin.tenant-groups",
|
|
"access.admin.tenant-roles",
|
|
"access.admin.api-keys",
|
|
"access.admin.service-accounts",
|
|
],
|
|
"route": "/admin",
|
|
"screen": "Admin",
|
|
"section": "Users, groups, roles, and API keys",
|
|
"fields": [
|
|
{
|
|
"field_id": "access.user.email",
|
|
"label": "Email",
|
|
"user_description": "The address used to identify the person when they sign in.",
|
|
"admin_description": "Stored on the account and membership payloads. It must be normalized and unique for the relevant login account.",
|
|
"api_path": "/api/v1/admin/users",
|
|
"api_field": "email",
|
|
"permission_scope": "access:membership:create",
|
|
"validation": "Must be a valid email address.",
|
|
"provenance": "Tenant membership creation or account lookup.",
|
|
},
|
|
{
|
|
"field_id": "access.user.display_name",
|
|
"label": "Display name",
|
|
"user_description": "The readable name shown in user lists and review screens.",
|
|
"admin_description": "Maps to display_name on user and account responses where available.",
|
|
"api_path": "/api/v1/admin/users",
|
|
"api_field": "display_name",
|
|
"permission_scope": "access:membership:update",
|
|
"validation": "Human-readable text; keep it recognizable for administrators.",
|
|
"provenance": "Tenant membership profile.",
|
|
},
|
|
{
|
|
"field_id": "access.user.groups",
|
|
"label": "Groups",
|
|
"user_description": "Shared access bundles that can add roles for many people at once.",
|
|
"admin_description": "Maps to group_ids when updating a user or group membership.",
|
|
"api_path": "/api/v1/admin/users/{user_id}",
|
|
"api_field": "group_ids",
|
|
"permission_scope": "access:group:manage_members",
|
|
"validation": "Groups must belong to the same tenant.",
|
|
"provenance": "User-group membership rows.",
|
|
},
|
|
{
|
|
"field_id": "access.user.roles",
|
|
"label": "Roles",
|
|
"user_description": "Direct access grants assigned to one person or inherited from groups.",
|
|
"admin_description": "Maps to role_ids on user and group role update requests.",
|
|
"api_path": "/api/v1/admin/users/{user_id}",
|
|
"api_field": "role_ids",
|
|
"permission_scope": "access:role:assign",
|
|
"validation": "Roles must be assignable and cannot exceed the actor's delegation limit.",
|
|
"provenance": "Direct user roles plus group role inheritance.",
|
|
},
|
|
{
|
|
"field_id": "access.api_key.scopes",
|
|
"label": "Scopes",
|
|
"user_description": "The actions an API key may perform.",
|
|
"admin_description": "Maps to scopes when creating an API key and is intersected with the owner's current permissions.",
|
|
"api_path": "/api/v1/admin/api-keys",
|
|
"api_field": "scopes",
|
|
"permission_scope": "access:api_key:create",
|
|
"validation": "Use the narrowest scopes possible.",
|
|
"provenance": "API key grant plus owner delegation.",
|
|
},
|
|
],
|
|
"related_topic_ids": [
|
|
"access.workflow.grant-user-access",
|
|
"docs.pattern.field-help",
|
|
],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.workflow.manage-reusable-credentials",
|
|
title="Manage reusable credentials safely",
|
|
summary="Reusable credential envelopes keep secrets write-only while administrators constrain which scopes, modules, and servers may use them.",
|
|
body=(
|
|
"A reusable credential envelope stores a secret behind the Access boundary and never returns the configured secret through the API. Choose the credential type before entering the secret; changing the type requires a replacement secret. When editing, an empty secret field retains the current value, while Remove configured secret clears it on save and leaves dependent connections unable to authenticate until a replacement is supplied. "
|
|
"The module and server lists are restrictions: an empty list means every module or server already permitted by the selected scope. Visible to lower scopes makes the envelope selectable from child scopes but does not bypass its module, server, or authorization limits. Deactivating keeps the configuration for review but blocks authentication. Deleting is irreversible in GovOPlaN, cannot recover the secret, and causes every referencing connection to stop authenticating. Review dependent connections and record the external secret-manager owner before clearing or deleting a credential."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("system_admin", "tenant_admin", "access_admin", "operator"),
|
|
order=32,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=(
|
|
"access:system_credential:read",
|
|
"access:system_credential:write",
|
|
"access:credential:read",
|
|
"access:credential:write",
|
|
"access:credential:manage_own",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="System credentials", href="/admin?section=system-credentials", kind="runtime"),
|
|
DocumentationLink(label="Tenant credentials", href="/admin?section=tenant-credentials", kind="runtime"),
|
|
DocumentationLink(label="Personal credentials", href="/settings", kind="runtime"),
|
|
),
|
|
translations={
|
|
"de": {
|
|
"title": "Wiederverwendbare Zugangsdaten sicher verwalten",
|
|
"summary": "Wiederverwendbare Zugangsdaten geben Geheimnisse nicht wieder aus und begrenzen ihre Nutzung auf freigegebene Ebenen, Module und Server.",
|
|
"body": "Ein Eintrag für wiederverwendbare Zugangsdaten speichert ein Geheimnis hinter der Access-Sicherheitsgrenze; das konfigurierte Geheimnis wird über die API niemals zurückgegeben. Wählen Sie den Zugangstyp vor der Eingabe. Eine Typänderung erfordert ein neues Geheimnis. Beim Bearbeiten behält ein leeres Geheimnisfeld den vorhandenen Wert. Mit „Konfiguriertes Geheimnis entfernen“ wird er beim Speichern gelöscht; abhängige Verbindungen können sich erst nach Hinterlegung eines Ersatzes wieder anmelden. Die Modul- und Serverlisten sind Einschränkungen: Eine leere Liste erlaubt alle Module beziehungsweise Server, die auf der gewählten Ebene bereits zulässig sind. „Für tiefere Ebenen sichtbar“ macht den Eintrag in Kindebenen auswählbar, umgeht aber weder Modul- und Servergrenzen noch Berechtigungen. Eine Deaktivierung erhält die Konfiguration zur Prüfung, verhindert jedoch die Anmeldung. Das Löschen kann in GovOPlaN nicht rückgängig gemacht werden, stellt das Geheimnis nicht wieder her und unterbricht die Anmeldung aller referenzierenden Verbindungen. Prüfen Sie deshalb vor dem Entfernen oder Löschen die abhängigen Verbindungen und die Zuständigkeit im externen Geheimnismanager.",
|
|
}
|
|
},
|
|
metadata={
|
|
"kind": "workflow",
|
|
"route": "/admin",
|
|
"screen": "Reusable credentials",
|
|
"help_contexts": [
|
|
"access.admin.system-credentials",
|
|
"access.admin.tenant-credentials",
|
|
"access.admin.group-credentials",
|
|
"access.admin.user-credentials",
|
|
"access.settings.credentials",
|
|
"access.credentials",
|
|
"access.credentials.target",
|
|
"access.credentials.editor",
|
|
"access.credentials.action.reload",
|
|
"access.credentials.action.create",
|
|
"access.credentials.action.edit",
|
|
"access.credentials.action.save",
|
|
"access.credentials.action.delete",
|
|
"access.credentials.field.name",
|
|
"access.credentials.field.type",
|
|
"access.credentials.field.description",
|
|
"access.credentials.field.account-label",
|
|
"access.credentials.field.secret",
|
|
"access.credentials.field.clear-secret",
|
|
"access.credentials.field.allowed-modules",
|
|
"access.credentials.field.allowed-servers",
|
|
"access.credentials.field.inherit-to-lower-scopes",
|
|
"access.credentials.field.active",
|
|
"access.credentials.confirm-delete",
|
|
],
|
|
"prerequisites": [
|
|
"The intended credential owner is selected.",
|
|
"The actor may read credentials and has write authority for mutations.",
|
|
"The external secret-manager owner and dependent connections are known.",
|
|
],
|
|
"steps": [
|
|
"Select the narrowest owning scope and credential type.",
|
|
"Restrict modules and servers explicitly when broad use is not intended.",
|
|
"Save a new or replacement secret without expecting it to be displayed again.",
|
|
"Review dependent connections before deactivation, secret clearing, or deletion.",
|
|
],
|
|
"outcome": "The credential remains write-only and is usable only within its active scope, module, server, and authorization boundaries.",
|
|
"limitations": [
|
|
"GovOPlaN cannot display or recover a configured secret.",
|
|
"An empty module or server restriction means every value permitted by scope.",
|
|
"Deleting or clearing a secret does not rewrite dependent connection references.",
|
|
],
|
|
"verification": "Reload the credential list, confirm its scope and availability, then test each intended dependent connection without exposing the secret in evidence.",
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.workflow.manage-service-account-credentials",
|
|
title="Manage service accounts and credentials",
|
|
summary="Create non-login automation principals, set a current scope ceiling, and rotate their one-time credentials without granting human login access.",
|
|
body=(
|
|
"Service accounts are tenant-owned automation principals. The account itself has no password or interactive session. Administrators first define its scope ceiling, then create one or more independently revocable credentials. "
|
|
"A credential secret is disclosed once and only its hash and prefix remain in GovOPlaN. Runtime authorization is always the intersection of the credential scopes and the service account's current ceiling, so lowering the ceiling or deactivating the account takes effect immediately. "
|
|
"Rotation creates the replacement and revokes the previous credential in one transaction. Retirement disables the backing principal and revokes every active credential. Every credential mutation requires the current service-account revision; a stale browser must reload instead of overwriting a concurrent change."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("tenant_admin", "access_admin", "operator"),
|
|
order=33,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=(
|
|
"access:service_account:read",
|
|
"access:service_account:write",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Service accounts", href="/admin?section=tenant-service-accounts", kind="runtime"),
|
|
DocumentationLink(label="Service accounts API", href="/api/v1/admin/service-accounts", kind="api"),
|
|
DocumentationLink(label="Credential lifecycle API", href="/api/v1/admin/service-accounts/{service_account_id}/credentials", kind="api"),
|
|
),
|
|
metadata={
|
|
"kind": "workflow",
|
|
"help_contexts": ["access.admin.service-accounts"],
|
|
"prerequisites": [
|
|
"The tenant permits API credentials.",
|
|
"You have service-account write permission and may delegate every selected scope.",
|
|
],
|
|
"steps": [
|
|
"Create a service account and define the narrowest useful scope ceiling.",
|
|
"Open the account and create a credential with an equal or narrower scope grant.",
|
|
"Record the one-time secret in an external secret manager.",
|
|
"Rotate credentials before expiry and revoke credentials that are no longer used.",
|
|
],
|
|
"verification": "The administration table shows the expected active credential count, last-use timestamp, revision, and audit events without exposing secret material.",
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.reference.personal-navigation",
|
|
title="Personalize the side rail",
|
|
summary="Users can reorder or hide available navigation entries without changing access or other users' workspaces.",
|
|
body=(
|
|
"Open Settings and use the workspace navigation editor to move or show available entries. Personal order and visibility take precedence over tenant and system preferences. Entries locked by a system or tenant administrator remain visible, and a user cannot create a lock. Choosing the inherited order removes the personal layer. Module entitlement, View policy, and permissions continue to decide which destinations are available, so changing navigation never grants access."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("user", "admin"),
|
|
audience=("user", "tenant_admin", "system_admin"),
|
|
order=85,
|
|
related_modules=("admin", "tenancy", "views"),
|
|
links=(
|
|
DocumentationLink(label="Workspace settings", href="/settings?section=workspace", kind="runtime"),
|
|
),
|
|
metadata={
|
|
"kind": "reference",
|
|
"help_contexts": ["core.settings.workspace"],
|
|
"outcome": "The user's side rail reflects the personal preference while locked and inaccessible entries remain governed by higher-level policy.",
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.workflow.manage-sessions",
|
|
title="Review and revoke account sessions",
|
|
summary="Inspect active browser sessions and revoke one or every other session without exposing credentials or network identifiers.",
|
|
body=(
|
|
"Settings > Sessions and devices marks the current browser session and shows only bounded client metadata plus creation, last-seen, and expiry times. "
|
|
"Users can revoke another session or all other active sessions; the command session is protected and normal logout remains the way to end it. Revocation is idempotent and takes effect on the next authenticated request. "
|
|
"Tenant administrators can inspect only sessions belonging to a membership in their governed tenant. Administrative revocation requires central membership-update permission and current-password re-authorization from an interactive session. "
|
|
"Audit evidence records stable actors, targets, and counts without tokens, hashes, cookies, IP addresses, or client strings."
|
|
),
|
|
layer="always",
|
|
documentation_types=("admin", "user"),
|
|
audience=("user", "tenant_admin", "access_admin", "operator"),
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access",),
|
|
any_scopes=("access:session:manage_own",),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Sessions and devices", href="/settings?section=sessions", kind="runtime"),
|
|
DocumentationLink(label="Own sessions API", href="/api/v1/auth/sessions", kind="api"),
|
|
DocumentationLink(label="Session management reference", href="docs/SESSION_MANAGEMENT.md", kind="repository"),
|
|
),
|
|
translations={
|
|
"de": {
|
|
"title": "Kontositzungen prüfen und widerrufen",
|
|
"summary": "Aktive Browsersitzungen prüfen und einzelne oder alle anderen Sitzungen widerrufen, ohne Zugangsdaten oder Netzwerkkennungen offenzulegen.",
|
|
"body": (
|
|
"Einstellungen > Sitzungen und Geräte kennzeichnet die aktuelle Browsersitzung und zeigt nur begrenzte Clientmetadaten sowie Erstellungs-, Aktivitäts- und Ablaufzeitpunkte. "
|
|
"Benutzende können eine andere oder alle anderen aktiven Sitzungen widerrufen; die ausführende Sitzung bleibt geschützt und wird regulär abgemeldet. Der Widerruf ist idempotent und gilt beim nächsten authentifizierten Aufruf. "
|
|
"Mandantenadministrierende sehen nur Sitzungen einer Mitgliedschaft im verwalteten Mandanten. Der administrative Widerruf erfordert die zentrale Berechtigung zur Mitgliedschaftsänderung und eine erneute Passwortbestätigung in einer interaktiven Sitzung. "
|
|
"Auditnachweise speichern stabile Akteure, Ziele und Anzahlen, aber keine Token, Hashes, Cookies, IP-Adressen oder Clienttexte."
|
|
),
|
|
}
|
|
},
|
|
metadata={
|
|
"kind": "workflow",
|
|
"help_contexts": [
|
|
"access.settings.sessions",
|
|
"access.sessions.action.revoke",
|
|
"access.sessions.action.revoke-others",
|
|
"access.admin.user-sessions",
|
|
],
|
|
"api_paths": [
|
|
"/api/v1/auth/sessions",
|
|
"/api/v1/auth/sessions/{session_id}/revoke",
|
|
"/api/v1/auth/sessions/revoke-others",
|
|
"/api/v1/admin/users/{user_id}/sessions",
|
|
"/api/v1/admin/users/{user_id}/sessions/{session_id}/revoke",
|
|
],
|
|
"sensitive_fields_never_returned": [
|
|
"token",
|
|
"token_hash",
|
|
"csrf_token_hash",
|
|
"cookie",
|
|
"ip_address",
|
|
],
|
|
},
|
|
order=33,
|
|
),
|
|
DocumentationTopic(
|
|
id="access.reference.external-function-role-mappings",
|
|
title="Organization function facts and access roles",
|
|
summary="Organizations defines function facts, IDM assigns them to identities, and Access maps accepted facts to roles and permissions.",
|
|
body=(
|
|
"Organizations owns the organization meta-model, concrete units, structures, and functions. IDM owns the fact that an identity, through one of its accounts, holds a function in an organization unit, including delegated and acting-for assignments. "
|
|
"Access consumes those accepted IDM facts through the directory capability, validates function identifiers against Organizations, and turns them into rights only when an explicit external function role mapping connects the organization function ID to an assignable tenant role. "
|
|
"The assignment itself does not grant rights. Removing the IDM assignment, disabling the Organizations function, or removing the Access mapping stops the derived role source from contributing effective permissions. "
|
|
"A delegated assignment contributes under the delegate's own account. An acting-for assignment contributes only after an interactive session selects that exact current assignment; Access retains both the real and represented account, audits context changes, and rejects stale or mismatched selections. "
|
|
"Tenant administrators inspect this from the user access explanation dialog: role sources link back to the Organizations function or unit that defines the fact and to the IDM assignment that produced it. "
|
|
"The same explanation is exposed by the admin API, while mapping management remains under Admin > Function role mappings. This keeps the audit trail clear: Organizations records what can exist, IDM records who holds it, and Access records which accepted facts produce permissions."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("tenant_admin", "access_admin", "operator"),
|
|
order=34,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access", "organizations"),
|
|
any_scopes=("access:function:read", "access:role:read", "admin:roles:read"),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Function role mappings", href="/admin?section=tenant-function-role-mappings", kind="runtime"),
|
|
DocumentationLink(label="External function role mappings API", href="/api/v1/admin/external-function-role-mappings", kind="api"),
|
|
DocumentationLink(label="Effective user access explanation API", href="/api/v1/admin/users/{user_id}/access-explanation", kind="api"),
|
|
DocumentationLink(label="Organizations functions", href="/organizations?section=functions", kind="runtime"),
|
|
DocumentationLink(label="IDM assignments", href="/idm", kind="runtime"),
|
|
DocumentationLink(label="Acting contexts API", href="/api/v1/auth/acting-contexts", kind="api"),
|
|
),
|
|
metadata={
|
|
"kind": "reference",
|
|
"help_contexts": [
|
|
"access.admin.function-mappings",
|
|
"access.explanation",
|
|
],
|
|
"route": "/admin",
|
|
"api_path": "/api/v1/admin/external-function-role-mappings",
|
|
"explanation_api_path": "/api/v1/admin/users/{user_id}/access-explanation",
|
|
"runtime_routes": ["/admin?section=tenant-function-role-mappings", "/organizations?section=functions", "/idm"],
|
|
"acting_context_api_paths": ["/api/v1/auth/acting-contexts", "/api/v1/auth/switch-acting-context"],
|
|
"permission_scopes": ["access:function:write", "access:role:assign"],
|
|
"responsibility_boundaries": {
|
|
"organizations": "Defines organization units, structures, function types, and functions.",
|
|
"idm": "Assigns organization functions to identities and accounts, including delegation and acting-for facts.",
|
|
"access": "Maps accepted function facts to tenant roles and explains effective permissions.",
|
|
},
|
|
"related_topic_ids": [
|
|
"idm.workflow.assign-function-to-identity",
|
|
"docs.reference.organization-identity-idm-access-boundary",
|
|
],
|
|
},
|
|
),
|
|
DocumentationTopic(
|
|
id="access.workflow.data-subject-request",
|
|
title="Process a data-subject request",
|
|
summary="Privacy officers search provider-owned data, export a coverage manifest, and execute only reviewed erasure actions while retaining required institutional evidence.",
|
|
body=(
|
|
"Create a request with at least one stable subject selector, run the cross-module search, and inspect provider coverage before treating the result as complete. "
|
|
"For erasure requests, generate a plan and review every provider-owned action. Immutable role, function, and audit evidence remains present with its retention reason; global accounts and identities require system-level review because they may serve more than one tenant. "
|
|
"Execution requires the dedicated erasure permission, the current resource revision, selected executable actions, and an exact confirmation phrase. Access anonymizes the tenant membership and revokes active API keys and sessions without deleting stable evidence identifiers."
|
|
),
|
|
layer="configured",
|
|
documentation_types=("admin", "user"),
|
|
audience=("privacy_officer", "tenant_owner", "operator"),
|
|
order=35,
|
|
conditions=(
|
|
DocumentationCondition(
|
|
required_modules=("access", "admin"),
|
|
any_scopes=(
|
|
"access:privacy:read",
|
|
"access:privacy:manage",
|
|
"access:privacy:export",
|
|
"access:privacy:erase",
|
|
),
|
|
),
|
|
),
|
|
links=(
|
|
DocumentationLink(label="Data-subject requests", href="/admin?section=tenant-data-subject-requests", kind="runtime"),
|
|
DocumentationLink(label="Data-subject request API", href="/api/v1/admin/privacy/data-subject-requests", kind="api"),
|
|
),
|
|
translations={
|
|
"de": {
|
|
"title": "Betroffenenanfrage bearbeiten",
|
|
"summary": "Datenschutzbeauftragte suchen modulspezifische Daten, exportieren einen Abdeckungsnachweis und führen nur geprüfte Löschaktionen aus; erforderliche institutionelle Nachweise bleiben erhalten.",
|
|
"body": "Legen Sie eine Anfrage mit mindestens einem stabilen Merkmal der betroffenen Person an, führen Sie die modulübergreifende Suche aus und prüfen Sie die Anbieterabdeckung. Erstellen Sie bei Löschanfragen anschließend einen Plan und prüfen Sie jede Aktion. Unveränderliche Rollen-, Funktions- und Auditnachweise bleiben mit Begründung erhalten. Die Ausführung erfordert ein eigenes Recht, die aktuelle Revision, ausgewählte Aktionen und die exakte Bestätigung.",
|
|
}
|
|
},
|
|
metadata={
|
|
"kind": "workflow",
|
|
"help_contexts": ["admin.privacy.data-subject-requests"],
|
|
"permission_scopes": [
|
|
"access:privacy:read",
|
|
"access:privacy:manage",
|
|
"access:privacy:export",
|
|
"access:privacy:erase",
|
|
],
|
|
"limitations": [
|
|
"Modules without a DSAR provider are reported as coverage gaps.",
|
|
"Global accounts and identities are not erased automatically.",
|
|
],
|
|
},
|
|
),
|
|
)
|
|
|
|
|
|
def _legacy_principal_resolver(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.auth.dependencies import LegacyPrincipalResolver
|
|
|
|
return LegacyPrincipalResolver(
|
|
idm_directory=_optional_idm_directory(context),
|
|
identity_directory=_optional_identity_directory(context),
|
|
organization_directory=_optional_organization_directory(context),
|
|
)
|
|
|
|
|
|
def _legacy_permission_evaluator(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.auth.dependencies import LegacyPermissionEvaluator
|
|
|
|
return LegacyPermissionEvaluator()
|
|
|
|
|
|
def _api_principal_provider(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.auth.dependencies import AccessApiPrincipalProvider
|
|
|
|
return AccessApiPrincipalProvider()
|
|
|
|
|
|
def _automation_principal_provider(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.auth.dependencies import (
|
|
AccessAutomationPrincipalProvider,
|
|
)
|
|
|
|
return AccessAutomationPrincipalProvider(
|
|
idm_directory=_optional_idm_directory(context),
|
|
identity_directory=_optional_identity_directory(context),
|
|
organization_directory=_optional_organization_directory(context),
|
|
)
|
|
|
|
|
|
def _tenant_context_switcher(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.auth.tenant_context import AccessTenantContextSwitcher
|
|
|
|
return AccessTenantContextSwitcher()
|
|
|
|
|
|
def _access_directory(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.directory import SqlAccessDirectory
|
|
|
|
return SqlAccessDirectory(
|
|
idm_directory=_optional_idm_directory(context),
|
|
identity_directory=_optional_identity_directory(context),
|
|
organization_directory=_optional_organization_directory(context),
|
|
)
|
|
|
|
|
|
def _access_semantic_directory(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.directory import SqlAccessDirectory
|
|
|
|
return SqlAccessDirectory(
|
|
idm_directory=_optional_idm_directory(context),
|
|
identity_directory=_optional_identity_directory(context),
|
|
organization_directory=_optional_organization_directory(context),
|
|
)
|
|
|
|
|
|
def _access_reference_options(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.reference_options import (
|
|
SqlAccessReferenceOptionProvider,
|
|
)
|
|
|
|
return SqlAccessReferenceOptionProvider()
|
|
|
|
|
|
def _optional_identity_directory(context: ModuleContext) -> IdentityDirectory | None:
|
|
if not context.registry.has_capability(CAPABILITY_IDENTITY_DIRECTORY):
|
|
return None
|
|
capability = context.registry.require_capability(CAPABILITY_IDENTITY_DIRECTORY)
|
|
if not isinstance(capability, IdentityDirectory):
|
|
raise RuntimeError(f"Invalid capability: {CAPABILITY_IDENTITY_DIRECTORY}")
|
|
return capability
|
|
|
|
|
|
def _optional_idm_directory(context: ModuleContext) -> IdmDirectory | None:
|
|
if not context.registry.has_capability(CAPABILITY_IDM_DIRECTORY):
|
|
return None
|
|
capability = context.registry.require_capability(CAPABILITY_IDM_DIRECTORY)
|
|
if not isinstance(capability, IdmDirectory):
|
|
raise RuntimeError(f"Invalid capability: {CAPABILITY_IDM_DIRECTORY}")
|
|
return capability
|
|
|
|
|
|
def _optional_organization_directory(context: ModuleContext) -> OrganizationDirectory | None:
|
|
if not context.registry.has_capability(CAPABILITY_ORGANIZATION_DIRECTORY):
|
|
return None
|
|
capability = context.registry.require_capability(CAPABILITY_ORGANIZATION_DIRECTORY)
|
|
if not isinstance(capability, OrganizationDirectory):
|
|
raise RuntimeError(f"Invalid capability: {CAPABILITY_ORGANIZATION_DIRECTORY}")
|
|
return capability
|
|
|
|
|
|
def _resource_explanation_providers(context: ModuleContext) -> tuple[ResourceAccessExplanationProvider, ...]:
|
|
providers: list[ResourceAccessExplanationProvider] = []
|
|
for capability_name in (CAPABILITY_FILES_ACCESS, CAPABILITY_CAMPAIGNS_ACCESS):
|
|
if not context.registry.has_capability(capability_name):
|
|
continue
|
|
capability = context.registry.require_capability(capability_name)
|
|
if isinstance(capability, ResourceAccessExplanationProvider):
|
|
providers.append(capability)
|
|
return tuple(providers)
|
|
|
|
|
|
def _access_explanation_service(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.explanation import SqlAccessExplanationService
|
|
|
|
return SqlAccessExplanationService(
|
|
identity_directory=_optional_identity_directory(context),
|
|
idm_directory=_optional_idm_directory(context),
|
|
organization_directory=_optional_organization_directory(context),
|
|
resource_explanation_providers=_resource_explanation_providers(context),
|
|
)
|
|
|
|
|
|
def _tenant_provisioner(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.tenancy.provisioning import LegacyTenantAccessProvisioner
|
|
|
|
return LegacyTenantAccessProvisioner()
|
|
|
|
|
|
def _first_admin_provisioner(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.tenancy.provisioning import LegacyFirstAdminProvisioner
|
|
|
|
return LegacyFirstAdminProvisioner()
|
|
|
|
|
|
def _access_administration(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.administration import SqlAccessAdministration
|
|
|
|
return SqlAccessAdministration()
|
|
|
|
|
|
def _governance_materializer(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.governance_materializer import SqlAccessGovernanceMaterializer
|
|
|
|
return SqlAccessGovernanceMaterializer()
|
|
|
|
|
|
def _configuration_provider(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.configuration_provider import SqlAccessConfigurationProvider
|
|
|
|
return SqlAccessConfigurationProvider()
|
|
|
|
|
|
def _dsar_provider(context: ModuleContext) -> object:
|
|
del context
|
|
from govoplan_access.backend.dsar_provider import AccessDsarProvider
|
|
|
|
return AccessDsarProvider()
|
|
|
|
|
|
def _route_factory(context: ModuleContext):
|
|
from fastapi import APIRouter
|
|
|
|
from govoplan_access.backend.runtime import configure_runtime
|
|
|
|
configure_runtime(context)
|
|
|
|
from govoplan_access.backend.api.v1.auth import router as auth_router
|
|
from govoplan_access.backend.api.v1.routes import router as access_admin_router
|
|
from govoplan_access.backend.api.v1.service_accounts import (
|
|
router as service_account_router,
|
|
)
|
|
|
|
router = APIRouter()
|
|
router.include_router(auth_router)
|
|
router.include_router(access_admin_router)
|
|
router.include_router(service_account_router)
|
|
return router
|
|
|
|
|
|
def _people_search(context: ModuleContext) -> object:
|
|
from govoplan_access.backend.people_search import people_search_capability
|
|
|
|
return people_search_capability(context)
|
|
|
|
|
|
manifest = ModuleManifest(
|
|
id="access",
|
|
name="Access",
|
|
version="0.1.18",
|
|
optional_dependencies=("identity", "organizations", "tenancy", "idm"),
|
|
provides_interfaces=(
|
|
ModuleInterfaceProvider(name=CAPABILITY_ACCESS_PEOPLE_SEARCH, version="0.1.0"),
|
|
ModuleInterfaceProvider(
|
|
name=CAPABILITY_ACCESS_REFERENCE_OPTIONS,
|
|
version="0.1.0",
|
|
),
|
|
ModuleInterfaceProvider(
|
|
name="auth.automation_principal",
|
|
version="0.2.0",
|
|
),
|
|
ModuleInterfaceProvider(name=ACCESS_DSAR_CAPABILITY, version="0.1.0"),
|
|
),
|
|
permissions=ACCESS_PERMISSIONS,
|
|
role_templates=ACCESS_ROLE_TEMPLATES,
|
|
route_factory=_route_factory,
|
|
migration_spec=MigrationSpec(
|
|
module_id="access",
|
|
metadata=AccessBase.metadata,
|
|
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
|
),
|
|
uninstall_guard_providers=(
|
|
persistent_table_uninstall_guard(
|
|
access_models.Account,
|
|
access_models.Identity,
|
|
access_models.IdentityAccountLink,
|
|
access_models.User,
|
|
access_models.Group,
|
|
access_models.Role,
|
|
access_models.ServiceAccount,
|
|
access_models.OrganizationUnit,
|
|
access_models.Function,
|
|
access_models.FunctionRoleAssignment,
|
|
access_models.ExternalFunctionRoleAssignment,
|
|
access_models.FunctionAssignment,
|
|
access_models.FunctionDelegation,
|
|
access_models.SystemRoleAssignment,
|
|
access_models.UserGroupMembership,
|
|
access_models.UserRoleAssignment,
|
|
access_models.GroupRoleAssignment,
|
|
access_models.ApiKey,
|
|
access_models.AuthSession,
|
|
label="Access",
|
|
),
|
|
),
|
|
nav_items=(NavItem(path="/admin", label="Admin", icon="admin", required_any=ADMIN_READ_SCOPES, order=900),),
|
|
frontend=FrontendModule(
|
|
module_id="access",
|
|
package_name="@govoplan/access-webui",
|
|
routes=(FrontendRoute(path="/admin", component="AdminPage", required_any=ADMIN_READ_SCOPES, order=900),),
|
|
nav_items=(NavItem(path="/admin", label="Admin", icon="admin", required_any=ADMIN_READ_SCOPES, order=900),),
|
|
view_surfaces=(
|
|
ViewSurface(id="access.admin.system-roles", module_id="access", kind="section", label="System roles", order=20),
|
|
ViewSurface(id="access.admin.system-users", module_id="access", kind="section", label="System users", order=50),
|
|
ViewSurface(id="access.admin.system-credentials", module_id="access", kind="section", label="System credentials", order=80),
|
|
ViewSurface(id="access.admin.tenant-roles", module_id="access", kind="section", label="Tenant roles", order=10),
|
|
ViewSurface(id="access.admin.tenant-function-mappings", module_id="access", kind="section", label="Function mappings", order=20),
|
|
ViewSurface(id="access.admin.tenant-groups", module_id="access", kind="section", label="Tenant groups", order=30),
|
|
ViewSurface(id="access.admin.tenant-users", module_id="access", kind="section", label="Tenant users", order=40),
|
|
ViewSurface(id="access.admin.tenant-credentials", module_id="access", kind="section", label="Tenant credentials", order=70),
|
|
ViewSurface(id="access.admin.tenant-api-keys", module_id="access", kind="section", label="Tenant API keys", order=80),
|
|
ViewSurface(id="access.admin.tenant-service-accounts", module_id="access", kind="section", label="Service accounts", order=90),
|
|
ViewSurface(id="access.admin.group-credentials", module_id="access", kind="section", label="Group credentials", order=30),
|
|
ViewSurface(id="access.admin.user-credentials", module_id="access", kind="section", label="User credentials", order=30),
|
|
ViewSurface(id="access.settings.credentials", module_id="access", kind="section", label="Personal credentials", order=30),
|
|
ViewSurface(id="access.settings.sessions", module_id="access", kind="section", label="Sessions and devices", order=20),
|
|
),
|
|
),
|
|
capability_factories={
|
|
CAPABILITY_AUTH_API_PRINCIPAL_PROVIDER: _api_principal_provider,
|
|
CAPABILITY_AUTH_AUTOMATION_PRINCIPAL_PROVIDER: (
|
|
_automation_principal_provider
|
|
),
|
|
CAPABILITY_AUTH_PRINCIPAL_RESOLVER: _legacy_principal_resolver,
|
|
CAPABILITY_AUTH_PERMISSION_EVALUATOR: _legacy_permission_evaluator,
|
|
CAPABILITY_AUTH_TENANT_CONTEXT_SWITCHER: _tenant_context_switcher,
|
|
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER: _legacy_principal_resolver,
|
|
CAPABILITY_ACCESS_PERMISSION_EVALUATOR: _legacy_permission_evaluator,
|
|
CAPABILITY_ACCESS_DIRECTORY: _access_directory,
|
|
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY: _access_semantic_directory,
|
|
CAPABILITY_ACCESS_EXPLANATION: _access_explanation_service,
|
|
CAPABILITY_ACCESS_TENANT_PROVISIONER: _tenant_provisioner,
|
|
CAPABILITY_ACCESS_FIRST_ADMIN_PROVISIONER: _first_admin_provisioner,
|
|
CAPABILITY_ACCESS_ADMINISTRATION: _access_administration,
|
|
CAPABILITY_ACCESS_GOVERNANCE_MATERIALIZER: _governance_materializer,
|
|
CAPABILITY_ACCESS_PEOPLE_SEARCH: _people_search,
|
|
CAPABILITY_ACCESS_REFERENCE_OPTIONS: _access_reference_options,
|
|
ACCESS_CONFIGURATION_CAPABILITY: _configuration_provider,
|
|
ACCESS_DSAR_CAPABILITY: _dsar_provider,
|
|
},
|
|
documentation=ACCESS_DOCUMENTATION,
|
|
architecture=declared_module_architecture(
|
|
layer="institutional_foundation",
|
|
kind="foundation",
|
|
maturity="vertical_slice",
|
|
documentation_ref="docs/ACCESS_MODULE_BOUNDARY.md",
|
|
test_ref="tests/test_login_security.py",
|
|
known_limits=("Recovery and upgrade evidence is not yet complete enough for supported maturity.",),
|
|
owned_concepts=("account authentication", "application role", "permission evaluation", "service account"),
|
|
non_owned_concepts=("person identity", "organization structure", "function incumbency", "policy definition"),
|
|
security_docs=("docs/ACCESS_MODULE_BOUNDARY.md",),
|
|
operations_docs=("README.md",),
|
|
),
|
|
)
|
|
|
|
|
|
def get_manifest() -> ModuleManifest:
|
|
return manifest
|