from __future__ import annotations from pathlib import Path from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER from govoplan_core.core.idm import CAPABILITY_IDM_DIRECTORY from govoplan_core.core.module_guards import persistent_table_uninstall_guard from govoplan_core.core.modules import ( DocumentationTopic, FrontendModule, FrontendRoute, MigrationSpec, ModuleContext, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate, ) from govoplan_core.db.base import Base from govoplan_idm.backend.db import models as idm_models # noqa: F401 - populate metadata IDM_READ_SCOPES = ( "idm:organization_assignment:read", "idm:organization_assignment:write", "idm:settings:read", "organizations:function:assign", ) def _permission(scope: str, label: str, description: str) -> PermissionDefinition: module_id, resource, action = scope.split(":", 2) return PermissionDefinition( scope=scope, label=label, description=description, category="IDM", level="tenant", module_id=module_id, resource=resource, action=action, ) PERMISSIONS = ( _permission( "idm:organization_identity:read", "View organization identity candidates", "Search identities and account links for organization function assignments.", ), _permission( "idm:organization_assignment:read", "View organization function assignments", "Read identity-to-organization-function assignment links.", ), _permission( "idm:organization_assignment:write", "Manage organization function assignments", "Create and edit identity-to-organization-function assignment links.", ), _permission( "idm:settings:read", "View IDM settings", "Read IDM governance and assignment-change policy settings.", ), _permission( "idm:settings:write", "Manage IDM settings", "Update IDM governance and assignment-change policy settings.", ), ) ROLE_TEMPLATES = ( RoleTemplate( slug="idm_organization_mapper", name="IDM organization mapper", description="Resolve identity/account candidates and manage organization function assignment links.", permissions=( "idm:organization_identity:read", "idm:organization_assignment:read", "idm:organization_assignment:write", "idm:settings:read", "idm:settings:write", ), ), ) def _route_factory(context: ModuleContext): del context from govoplan_idm.backend.api.v1.routes import router return router def _idm_directory(context: ModuleContext) -> object: del context from govoplan_idm.backend.directory import SqlIdmDirectory return SqlIdmDirectory() manifest = ModuleManifest( id="idm", name="IDM", version="0.1.8", dependencies=("identity", "organizations"), optional_dependencies=("access", "audit"), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), permissions=PERMISSIONS, role_templates=ROLE_TEMPLATES, route_factory=_route_factory, nav_items=(NavItem(path="/idm", label="IDM", icon="users", required_any=IDM_READ_SCOPES, order=72),), frontend=FrontendModule( module_id="idm", package_name="@govoplan/idm-webui", routes=(FrontendRoute(path="/idm", component="IdmPage", required_any=IDM_READ_SCOPES, order=72),), nav_items=(NavItem(path="/idm", label="IDM", icon="users", required_any=IDM_READ_SCOPES, order=72),), ), migration_spec=MigrationSpec( module_id="idm", metadata=Base.metadata, script_location=str(Path(__file__).with_name("migrations") / "versions"), ), uninstall_guard_providers=( persistent_table_uninstall_guard( idm_models.IdmOrganizationFunctionAssignment, idm_models.IdmTenantSettings, label="IDM", ), ), capability_factories={ CAPABILITY_IDM_DIRECTORY: _idm_directory, }, documentation=( DocumentationTopic( id="idm.organization_identity_bridge", title="Identity to organization bridge", summary="IDM resolves which identities and accounts can be matched to organization function assignments.", body=( "Identity owns normalized identities and account links. Organizations owns units and functions. " "IDM owns assignment links between identities and organization functions, including identity search for organization assignments and future synchronization/mapping workflows. " "Access may consume these assignment links when IDM is installed, but IDM does not require access to evaluate rights." ), layer="configured", documentation_types=("admin", "user"), audience=("tenant_admin", "access_admin", "operator"), related_modules=("identity", "organizations", "access"), order=26, ), DocumentationTopic( id="idm.reference.assignment-governance", title="IDM assignment governance", summary="Tenant IDM settings can require approved change requests before identity-to-function assignments are applied.", body=( "Assignment links are high-impact because they can later feed access decisions. " "Tenants can enable recorded change requests for assignment create and update operations. " "The legacy organizations:function:assign scope remains accepted for transition, while new role templates should grant idm:organization_assignment:write." ), layer="configured", documentation_types=("admin",), audience=("tenant_admin", "access_admin", "operator"), related_modules=("identity", "organizations", "access", "audit", "policy"), order=27, ), DocumentationTopic( id="idm.workflow.assign-function-to-identity", title="Assign an organization function to an identity", summary="IDM links identities or accounts to organization functions. Access can consume those accepted links when a function-to-role mapping exists.", body=( "Create the unit and function in Organizations first. Make sure the person and account exist in Identity. " "Then create the assignment in IDM. Direct assignments state who holds the function. Delegated assignments require a source assignment and a delegable function. " "Acting-for assignments require a source assignment, an acting account, and a function that allows acting in place. " "Access maps accepted function facts to roles and rights; without such a mapping, the assignment is recorded but does not grant application permissions." ), layer="configured", documentation_types=("admin", "user"), audience=("tenant_admin", "access_admin", "operator", "user"), related_modules=("identity", "organizations", "access"), order=28, ), ), ) def get_manifest() -> ModuleManifest: return manifest