Complete governed reporting execution and publication
This commit is contained in:
@@ -27,6 +27,11 @@ from govoplan_reporting.backend.domain import (
|
||||
ReportingDefinitionRecord,
|
||||
definition_from_row,
|
||||
)
|
||||
from govoplan_reporting.backend.governance import (
|
||||
apply_parent_governance,
|
||||
normalize_definition_governance,
|
||||
scope_visible,
|
||||
)
|
||||
from govoplan_reporting.backend.schemas import validate_definition_payload
|
||||
|
||||
|
||||
@@ -86,17 +91,29 @@ def create_definition(
|
||||
clean_reason = _required(change_reason, "Reporting change reason", 1_000)
|
||||
_aware(recorded_at, "Reporting recorded_at")
|
||||
validated_payload = validate_definition_payload(kind, dict(payload))
|
||||
validated_payload = validate_definition_payload(
|
||||
kind,
|
||||
normalize_definition_governance(
|
||||
validated_payload,
|
||||
principal,
|
||||
administrative=_has_scope(principal, ADMIN_SCOPE),
|
||||
),
|
||||
)
|
||||
parent_kind, parent_id, parent_revision = _parent_reference(
|
||||
kind,
|
||||
validated_payload,
|
||||
)
|
||||
_validate_parent(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
child_status=clean_status,
|
||||
parent_kind=parent_kind,
|
||||
parent_id=parent_id,
|
||||
parent_revision=parent_revision,
|
||||
validated_payload = validate_definition_payload(
|
||||
kind,
|
||||
_validate_parent(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
child_status=clean_status,
|
||||
parent_kind=parent_kind,
|
||||
parent_id=parent_id,
|
||||
parent_revision=parent_revision,
|
||||
child_payload=validated_payload,
|
||||
),
|
||||
)
|
||||
request = {
|
||||
"definition_kind": kind,
|
||||
@@ -241,14 +258,26 @@ def update_definition(
|
||||
kind,
|
||||
dict(changes.get("payload", current.payload)),
|
||||
)
|
||||
next_payload = validate_definition_payload(
|
||||
kind,
|
||||
normalize_definition_governance(
|
||||
next_payload,
|
||||
principal,
|
||||
administrative=_has_scope(principal, ADMIN_SCOPE),
|
||||
),
|
||||
)
|
||||
parent_kind, parent_id, parent_revision = _parent_reference(kind, next_payload)
|
||||
_validate_parent(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
child_status=next_status,
|
||||
parent_kind=parent_kind,
|
||||
parent_id=parent_id,
|
||||
parent_revision=parent_revision,
|
||||
next_payload = validate_definition_payload(
|
||||
kind,
|
||||
_validate_parent(
|
||||
session,
|
||||
tenant_id=tenant_id,
|
||||
child_status=next_status,
|
||||
parent_kind=parent_kind,
|
||||
parent_id=parent_id,
|
||||
parent_revision=parent_revision,
|
||||
child_payload=next_payload,
|
||||
),
|
||||
)
|
||||
identity = _identity(session, tenant_id, kind, definition_id)
|
||||
if identity is None:
|
||||
@@ -584,9 +613,10 @@ def _validate_parent(
|
||||
parent_kind: str | None,
|
||||
parent_id: str | None,
|
||||
parent_revision: int | None,
|
||||
) -> None:
|
||||
child_payload: Mapping[str, object],
|
||||
) -> dict[str, object]:
|
||||
if parent_kind is None:
|
||||
return
|
||||
return dict(child_payload)
|
||||
row = (
|
||||
session.query(ReportingDefinitionRevision)
|
||||
.filter(
|
||||
@@ -605,6 +635,7 @@ def _validate_parent(
|
||||
raise ReportingDefinitionError(
|
||||
f"An active Reporting definition requires an active {parent_kind} revision."
|
||||
)
|
||||
return apply_parent_governance(child_payload, row.payload)
|
||||
|
||||
|
||||
def _parent_reference(
|
||||
@@ -667,6 +698,8 @@ def _can_access(
|
||||
)
|
||||
if row is None:
|
||||
return False
|
||||
if not scope_visible(row.payload, principal):
|
||||
return False
|
||||
if _has_scope(principal, ADMIN_SCOPE):
|
||||
return True
|
||||
identity = _identity(session, tenant_id, definition_kind, definition_id)
|
||||
@@ -712,6 +745,24 @@ def _require_scope(principal: object, scope: str) -> None:
|
||||
def _filter_accessible(query: Query, principal: object) -> Query:
|
||||
if _has_scope(principal, ADMIN_SCOPE):
|
||||
return query
|
||||
governance = ReportingDefinitionRevision.payload["governance"]
|
||||
scope_type = governance["scope_type"].as_string()
|
||||
scope_id = governance["scope_id"].as_string()
|
||||
inherited = governance["inherit_to_lower_scopes"].as_boolean()
|
||||
scope_conditions = [
|
||||
scope_type.is_(None),
|
||||
and_(
|
||||
scope_type == "tenant",
|
||||
or_(scope_id.is_(None), scope_id == _principal_tenant(principal)),
|
||||
),
|
||||
and_(scope_type == "system", inherited.is_(True)),
|
||||
]
|
||||
group_ids = tuple(_string_subject_ids(principal, "group_ids"))
|
||||
if group_ids:
|
||||
scope_conditions.append(and_(scope_type == "group", scope_id.in_(group_ids)))
|
||||
user_ids = _actor_ids(principal)
|
||||
if user_ids:
|
||||
scope_conditions.append(and_(scope_type == "user", scope_id.in_(user_ids)))
|
||||
conditions = [ReportingDefinitionRevision.visibility == "tenant"]
|
||||
actor_ids = _actor_ids(principal)
|
||||
if actor_ids:
|
||||
@@ -752,7 +803,14 @@ def _filter_accessible(query: Query, principal: object) -> Query:
|
||||
)
|
||||
)
|
||||
)
|
||||
return query.filter(or_(*conditions))
|
||||
return query.filter(and_(or_(*scope_conditions), or_(*conditions)))
|
||||
|
||||
|
||||
def _string_subject_ids(principal: object, attribute: str) -> tuple[str, ...]:
|
||||
raw = getattr(principal, attribute, ()) or ()
|
||||
if isinstance(raw, (str, bytes)):
|
||||
return (str(raw),) if raw else ()
|
||||
return tuple(dict.fromkeys(str(value) for value in raw if str(value or "").strip()))
|
||||
|
||||
|
||||
def _current_row(
|
||||
|
||||
Reference in New Issue
Block a user