Source code for dogwood.enforcement

"""SDK-level enforcement modes for Dogwood authorization decisions.

The Rust Dogwood core evaluates policy and returns a policy decision. This
module adds Python SDK rollout behavior around that decision: enforce denials,
or run in log-only mode and report what would have happened.

Behavior matrix:

+------------------+-------------+---------------------------+
| Dogwood decision | SDK mode    | Enforcement result        |
+==================+=============+===========================+
| ``Allow``        | ``enforce`` | ``allowed=True``          |
+------------------+-------------+---------------------------+
| ``Deny``         | ``enforce`` | ``allowed=False``         |
+------------------+-------------+---------------------------+
| ``Allow``        | ``log_only``| ``allowed=True``          |
+------------------+-------------+---------------------------+
| ``Deny``         | ``log_only``| ``allowed=True``,         |
|                  |             | ``would_have_denied=True``|
+------------------+-------------+---------------------------+
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Literal

EnforcementMode = Literal["enforce", "log_only"]


[docs] @dataclass(frozen=True) class EnforcementResult: """Effective SDK decision for an evaluated Dogwood event or request. ``decision`` is the raw Dogwood policy result, usually ``"Allow"`` or ``"Deny"``. ``allowed`` is the effective SDK outcome after applying ``mode``. In ``log_only`` mode, a Dogwood denial produces ``allowed=True`` and ``would_have_denied=True``. """ decision: str | None mode: EnforcementMode allowed: bool policy_allowed: bool | None would_have_denied: bool response: Any = None
[docs] def denied(self) -> bool: """Return true when the effective SDK outcome blocks the operation.""" return not self.allowed
[docs] class PolicyEnforcer: """Apply ``enforce`` or ``log_only`` behavior to Dogwood decisions. Wrap either the Python fallback ``Authorizer`` or native authorizer-like objects. The wrapped authorizer still performs the Dogwood policy evaluation; this class only decides how that result affects the caller. """ def __init__(self, authorizer: Any, mode: str = "enforce"): self.authorizer = authorizer self.mode = normalize_mode(mode)
[docs] def authorize(self, event: Any) -> EnforcementResult: """Authorize an SDK ``Event`` using ``authorizer.is_authorized``.""" if not hasattr(self.authorizer, "is_authorized"): raise TypeError("authorizer must provide is_authorized(event)") response = self.authorizer.is_authorized(event) return apply_enforcement(response, self.mode)
[docs] def is_authorized(self, event: Any) -> EnforcementResult: """Alias for :meth:`authorize` for ``Authorizer``-like usage.""" return self.authorize(event)
[docs] def authorize_request( self, action: str, principal: str, resource: str, input: dict[str, Any], ) -> EnforcementResult: """Authorize a native-style request using ``authorize_request``.""" if not hasattr(self.authorizer, "authorize_request"): raise TypeError("authorizer must provide authorize_request(action, principal, resource, input)") response = self.authorizer.authorize_request(action, principal, resource, input) return apply_enforcement(response, self.mode)
[docs] def apply_enforcement(response: Any, mode: str = "enforce") -> EnforcementResult: """Return the effective SDK outcome for a raw Dogwood response.""" normalized_mode = normalize_mode(mode) decision = decision_text(response) policy_allowed = None if decision is None else is_allowed_decision(decision) effective_allowed = True if policy_allowed is None else policy_allowed or normalized_mode == "log_only" return EnforcementResult( decision=decision, mode=normalized_mode, allowed=effective_allowed, policy_allowed=policy_allowed, would_have_denied=normalized_mode == "log_only" and policy_allowed is False, response=response, )
[docs] def normalize_mode(mode: str) -> EnforcementMode: """Normalize SDK enforcement mode names. Accepts Python-style lowercase values and AWS-style uppercase values: ``"enforce"``, ``"log_only"``, ``"ENFORCE"``, and ``"LOG_ONLY"``. """ normalized = mode.lower() if normalized not in {"enforce", "log_only"}: raise ValueError("mode must be 'enforce' or 'log_only'") return normalized # type: ignore[return-value]
[docs] def is_enforced(mode: str) -> bool: """Return true when ``mode`` enforces Dogwood denials.""" return normalize_mode(mode) == "enforce"
[docs] def decision_text(response: Any) -> str | None: """Extract ``Allow`` or ``Deny`` text from common Dogwood response shapes.""" if response is None: return None decision = getattr(response, "decision", response) value = getattr(decision, "value", decision) return str(value)
[docs] def is_allowed_decision(response: Any) -> bool: """Return true when a raw Dogwood response or decision is ``Allow``.""" return decision_text(response) == "Allow"
__all__ = [ "EnforcementMode", "EnforcementResult", "PolicyEnforcer", "apply_enforcement", "decision_text", "is_allowed_decision", "is_enforced", "normalize_mode", ]