feat: expose effective function incumbency contracts
This commit is contained in:
@@ -17,6 +17,13 @@ from govoplan_core.core.configuration_control import (
|
||||
ensure_configuration_change_allowed,
|
||||
record_configuration_change_applied,
|
||||
)
|
||||
from govoplan_core.core.events import (
|
||||
EventActorRef,
|
||||
EventObjectRef,
|
||||
EventTenantRef,
|
||||
PlatformEvent,
|
||||
emit_platform_event,
|
||||
)
|
||||
from govoplan_core.core.identity import (
|
||||
CAPABILITY_IDENTITY_DIRECTORY,
|
||||
CAPABILITY_IDENTITY_SEARCH,
|
||||
@@ -31,6 +38,7 @@ from govoplan_core.core.organizations import (
|
||||
)
|
||||
from govoplan_core.core.runtime import get_registry
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_core.security.time import utc_now
|
||||
from govoplan_idm.backend.db.models import IdmOrganizationFunctionAssignment, IdmTenantSettings
|
||||
|
||||
from .schemas import (
|
||||
@@ -413,6 +421,55 @@ def _record_assignment_audit(
|
||||
)
|
||||
|
||||
|
||||
def _publish_assignment_event(
|
||||
session: Session,
|
||||
principal: ApiPrincipal,
|
||||
item: OrganizationFunctionAssignmentItem,
|
||||
*,
|
||||
event_type: str,
|
||||
) -> None:
|
||||
emit_platform_event(
|
||||
session,
|
||||
PlatformEvent(
|
||||
type=event_type,
|
||||
module_id="idm",
|
||||
payload={
|
||||
"identity_id": item.identity_id,
|
||||
"account_id": item.account_id,
|
||||
"function_id": item.function_id,
|
||||
"organization_unit_id": item.organization_unit_id,
|
||||
"source": item.source,
|
||||
"delegated_from_assignment_id": (
|
||||
item.delegated_from_assignment_id
|
||||
),
|
||||
"acting_for_account_id": item.acting_for_account_id,
|
||||
"valid_from": (
|
||||
item.valid_from.isoformat()
|
||||
if item.valid_from is not None
|
||||
else None
|
||||
),
|
||||
"valid_until": (
|
||||
item.valid_until.isoformat()
|
||||
if item.valid_until is not None
|
||||
else None
|
||||
),
|
||||
"is_active": item.is_active,
|
||||
},
|
||||
actor=EventActorRef(type="account", id=principal.account_id),
|
||||
tenant=EventTenantRef(id=principal.tenant_id),
|
||||
subject=EventObjectRef(
|
||||
type="organization_function",
|
||||
id=item.function_id,
|
||||
),
|
||||
resource=EventObjectRef(
|
||||
type="organization_function_assignment",
|
||||
id=item.id,
|
||||
),
|
||||
classification="internal",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.get("/settings", response_model=IdmSettingsItem)
|
||||
def get_idm_settings(
|
||||
session: Session = Depends(get_session),
|
||||
@@ -467,17 +524,34 @@ def update_idm_settings(
|
||||
|
||||
@router.get("/organization-function-assignments", response_model=OrganizationFunctionAssignmentList)
|
||||
def list_organization_function_assignments(
|
||||
page: int = Query(default=1, ge=1),
|
||||
page_size: int = Query(default=500, ge=1, le=1000),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(require_any_scope(*IDM_ASSIGNMENT_READ_SCOPES)),
|
||||
) -> OrganizationFunctionAssignmentList:
|
||||
tenant_id = _tenant_id(principal)
|
||||
assignments = (
|
||||
query = (
|
||||
session.query(IdmOrganizationFunctionAssignment)
|
||||
.filter(IdmOrganizationFunctionAssignment.tenant_id == tenant_id)
|
||||
.order_by(IdmOrganizationFunctionAssignment.created_at.asc())
|
||||
)
|
||||
total = query.order_by(None).count()
|
||||
pages = max(1, (total + page_size - 1) // page_size)
|
||||
assignments = (
|
||||
query.order_by(
|
||||
IdmOrganizationFunctionAssignment.created_at.asc(),
|
||||
IdmOrganizationFunctionAssignment.id.asc(),
|
||||
)
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return OrganizationFunctionAssignmentList(assignments=[_assignment_item(item) for item in assignments])
|
||||
return OrganizationFunctionAssignmentList(
|
||||
assignments=[_assignment_item(item) for item in assignments],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
pages=pages,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/organization-function-assignments", response_model=OrganizationFunctionAssignmentItem, status_code=status.HTTP_201_CREATED)
|
||||
@@ -523,6 +597,13 @@ def create_organization_function_assignment(
|
||||
before=None,
|
||||
after=after,
|
||||
)
|
||||
_publish_assignment_event(
|
||||
session,
|
||||
principal,
|
||||
result,
|
||||
event_type="idm.function_assignment.created.v1",
|
||||
)
|
||||
session.commit()
|
||||
return result
|
||||
|
||||
|
||||
@@ -586,6 +667,36 @@ def update_organization_function_assignment(
|
||||
before=before,
|
||||
after=after,
|
||||
)
|
||||
_publish_assignment_event(
|
||||
session,
|
||||
principal,
|
||||
result,
|
||||
event_type="idm.function_assignment.changed.v1",
|
||||
)
|
||||
before_values = before if isinstance(before, dict) else {}
|
||||
if before_values.get("is_active") is True and result.is_active is False:
|
||||
_publish_assignment_event(
|
||||
session,
|
||||
principal,
|
||||
result,
|
||||
event_type="idm.function_assignment.revoked.v1",
|
||||
)
|
||||
previous_valid_until = before_values.get("valid_until")
|
||||
if (
|
||||
result.valid_until is not None
|
||||
and result.valid_until <= utc_now()
|
||||
and (
|
||||
previous_valid_until is None
|
||||
or str(previous_valid_until) != result.valid_until.isoformat()
|
||||
)
|
||||
):
|
||||
_publish_assignment_event(
|
||||
session,
|
||||
principal,
|
||||
result,
|
||||
event_type="idm.function_assignment.expired.v1",
|
||||
)
|
||||
session.commit()
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ class OrganizationFunctionAssignmentItem(BaseModel):
|
||||
|
||||
class OrganizationFunctionAssignmentList(BaseModel):
|
||||
assignments: list[OrganizationFunctionAssignmentItem]
|
||||
total: int = 0
|
||||
page: int = 1
|
||||
page_size: int = 500
|
||||
pages: int = 1
|
||||
|
||||
|
||||
class OrganizationFunctionAssignmentCreateRequest(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user