Prefer canonical identity and organization directories

This commit is contained in:
2026-07-11 01:13:53 +02:00
parent 5b8baa6cde
commit 002d12e417
8 changed files with 322 additions and 31 deletions

View File

@@ -27,8 +27,9 @@ from govoplan_access.backend.semantic import (
identity_id_for_account,
)
from govoplan_core.core.access import AccessDecisionProvenance, AccessExplanationService, PrincipalRef, ResourceAccessExplanationProvider
from govoplan_core.core.identity import IdentityDirectory
from govoplan_core.core.idm import IdmDirectory, OrganizationFunctionAssignmentRef
from govoplan_core.core.organizations import OrganizationDirectory
from govoplan_core.core.organizations import ORGANIZATIONS_MODULE_ID, OrganizationDirectory
from govoplan_core.db.session import get_database
from govoplan_access.backend.permissions.catalog import expand_scopes, scopes_grant
@@ -159,10 +160,12 @@ class SqlAccessExplanationService(AccessExplanationService):
def __init__(
self,
*,
identity_directory: IdentityDirectory | None = None,
idm_directory: IdmDirectory | None = None,
organization_directory: OrganizationDirectory | None = None,
resource_explanation_providers: Iterable[ResourceAccessExplanationProvider] = (),
) -> None:
self._identity_directory = identity_directory
self._idm_directory = idm_directory
self._organization_directory = organization_directory
self._resource_explanation_providers = tuple(resource_explanation_providers)
@@ -178,6 +181,7 @@ class SqlAccessExplanationService(AccessExplanationService):
session,
principal,
required_scope,
identity_directory=self._identity_directory,
idm_directory=self._idm_directory,
organization_directory=self._organization_directory,
)
@@ -196,6 +200,7 @@ class SqlAccessExplanationService(AccessExplanationService):
session,
principal,
action,
identity_directory=self._identity_directory,
idm_directory=self._idm_directory,
organization_directory=self._organization_directory,
)
@@ -250,12 +255,17 @@ def _scope_provenance(
principal: PrincipalRef,
required_scope: str,
*,
identity_directory: IdentityDirectory | None = None,
idm_directory: IdmDirectory | None = None,
organization_directory: OrganizationDirectory | None = None,
) -> list[AccessDecisionProvenance]:
items: list[AccessDecisionProvenance] = []
account = session.get(Account, principal.account_id)
identity_id = principal.identity_id or identity_id_for_account(session, principal.account_id)
identity_id = principal.identity_id or identity_id_for_account(
session,
principal.account_id,
identity_directory=identity_directory,
)
if identity_id:
items.append(AccessDecisionProvenance(kind="identity", id=identity_id, source="identity_account_link"))
items.append(
@@ -421,6 +431,12 @@ def _idm_function_role_sources(
for assignment in idm_directory.organization_function_assignments_for_account(user.account_id, tenant_id=user.tenant_id)
if assignment.tenant_id == user.tenant_id and assignment.status == "active"
]
if organization_directory is not None:
assignments = [
assignment
for assignment in assignments
if _organization_function_active(organization_directory, assignment)
]
if not assignments:
return [], []
function_ids = sorted({assignment.function_id for assignment in assignments})
@@ -429,6 +445,7 @@ def _idm_function_role_sources(
.join(Role, ExternalFunctionRoleAssignment.role_id == Role.id)
.filter(
ExternalFunctionRoleAssignment.tenant_id == user.tenant_id,
ExternalFunctionRoleAssignment.source_module == ORGANIZATIONS_MODULE_ID,
ExternalFunctionRoleAssignment.function_id.in_(function_ids),
Role.tenant_id == user.tenant_id,
)
@@ -446,7 +463,7 @@ def _idm_function_role_sources(
mappings = mappings_by_function.get(assignment.function_id, [])
function_facts.append(
FunctionFactExplanation(
source_module="organizations",
source_module=ORGANIZATIONS_MODULE_ID,
assignment_id=assignment.id,
tenant_id=assignment.tenant_id,
identity_id=assignment.identity_id,
@@ -501,6 +518,14 @@ def _organization_labels(
return (function.name if function is not None else None, unit.name if unit is not None else None)
def _organization_function_active(
organization_directory: OrganizationDirectory,
assignment: OrganizationFunctionAssignmentRef,
) -> bool:
function = organization_directory.get_function(assignment.function_id)
return function is not None and function.tenant_id == assignment.tenant_id and function.status == "active"
def _system_role_sources(session: Session, account: Account) -> list[AccessRoleSourceExplanation]:
roles = (
session.query(Role)