feat: add access module boundary migrations
This commit is contained in:
@@ -1,17 +1,34 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
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_GOVERNANCE_MATERIALIZER,
|
||||
CAPABILITY_ACCESS_PERMISSION_EVALUATOR,
|
||||
CAPABILITY_ACCESS_PRINCIPAL_RESOLVER,
|
||||
CAPABILITY_ACCESS_TENANT_PROVISIONER,
|
||||
CAPABILITY_ACCESS_SEMANTIC_DIRECTORY,
|
||||
)
|
||||
from govoplan_core.core.module_guards import persistent_table_uninstall_guard
|
||||
from govoplan_core.core.modules import FrontendModule, FrontendRoute, MigrationSpec, ModuleContext, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate
|
||||
from govoplan_core.core.modules import (
|
||||
DocumentationCondition,
|
||||
DocumentationLink,
|
||||
DocumentationTopic,
|
||||
FrontendModule,
|
||||
FrontendRoute,
|
||||
MigrationSpec,
|
||||
ModuleContext,
|
||||
ModuleManifest,
|
||||
NavItem,
|
||||
PermissionDefinition,
|
||||
RoleTemplate,
|
||||
)
|
||||
from govoplan_access.backend.configuration_provider import ACCESS_CONFIGURATION_CAPABILITY
|
||||
|
||||
|
||||
def _permission(scope: str, label: str, description: str, category: str, level: str) -> PermissionDefinition:
|
||||
@@ -44,6 +61,10 @@ ACCESS_PERMISSIONS: tuple[PermissionDefinition, ...] = (
|
||||
_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"),
|
||||
@@ -103,6 +124,10 @@ ACCESS_ROLE_TEMPLATES: tuple[RoleTemplate, ...] = (
|
||||
"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",
|
||||
@@ -128,6 +153,169 @@ ADMIN_READ_SCOPES = (
|
||||
"access:tenant:read",
|
||||
"access:account:read",
|
||||
"access:governance:read",
|
||||
"access:function:read",
|
||||
)
|
||||
|
||||
ACCESS_DOCUMENTATION: tuple[DocumentationTopic, ...] = (
|
||||
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",
|
||||
"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.",
|
||||
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"),
|
||||
),
|
||||
configuration_keys=("access_governance",),
|
||||
metadata={
|
||||
"kind": "reference",
|
||||
"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",
|
||||
],
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -152,6 +340,20 @@ def _access_directory(context: ModuleContext) -> object:
|
||||
return SqlAccessDirectory()
|
||||
|
||||
|
||||
def _access_semantic_directory(context: ModuleContext) -> object:
|
||||
del context
|
||||
from govoplan_access.backend.directory import SqlAccessDirectory
|
||||
|
||||
return SqlAccessDirectory()
|
||||
|
||||
|
||||
def _access_explanation_service(context: ModuleContext) -> object:
|
||||
del context
|
||||
from govoplan_access.backend.explanation import SqlAccessExplanationService
|
||||
|
||||
return SqlAccessExplanationService()
|
||||
|
||||
|
||||
def _tenant_provisioner(context: ModuleContext) -> object:
|
||||
del context
|
||||
from govoplan_access.backend.tenancy.provisioning import LegacyTenantAccessProvisioner
|
||||
@@ -173,6 +375,13 @@ def _governance_materializer(context: ModuleContext) -> object:
|
||||
return SqlAccessGovernanceMaterializer()
|
||||
|
||||
|
||||
def _configuration_provider(context: ModuleContext) -> object:
|
||||
del context
|
||||
from govoplan_access.backend.configuration_provider import SqlAccessConfigurationProvider
|
||||
|
||||
return SqlAccessConfigurationProvider()
|
||||
|
||||
|
||||
def _route_factory(context: ModuleContext):
|
||||
from fastapi import APIRouter
|
||||
|
||||
@@ -194,16 +403,28 @@ manifest = ModuleManifest(
|
||||
name="Access",
|
||||
version="0.1.6",
|
||||
dependencies=("tenancy",),
|
||||
optional_dependencies=("identity", "organizations"),
|
||||
permissions=ACCESS_PERMISSIONS,
|
||||
role_templates=ACCESS_ROLE_TEMPLATES,
|
||||
route_factory=_route_factory,
|
||||
migration_spec=MigrationSpec(module_id="access", metadata=AccessBase.metadata),
|
||||
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.OrganizationUnit,
|
||||
access_models.Function,
|
||||
access_models.FunctionRoleAssignment,
|
||||
access_models.FunctionAssignment,
|
||||
access_models.FunctionDelegation,
|
||||
access_models.SystemRoleAssignment,
|
||||
access_models.UserGroupMembership,
|
||||
access_models.UserRoleAssignment,
|
||||
@@ -224,10 +445,14 @@ manifest = ModuleManifest(
|
||||
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_ADMINISTRATION: _access_administration,
|
||||
CAPABILITY_ACCESS_GOVERNANCE_MATERIALIZER: _governance_materializer,
|
||||
ACCESS_CONFIGURATION_CAPABILITY: _configuration_provider,
|
||||
},
|
||||
documentation=ACCESS_DOCUMENTATION,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user