Source code for loom.core.identity.wire

"""Serialisation of the caller identity for job envelopes.

A job crosses a process boundary, where no context variable follows it.  The
identity therefore travels inside the envelope, as an explicit part of the wire
contract, encoded with plain JSON types so any broker serializer can carry it.

Decoding treats its input as untrusted: anything that cannot be read as an
identity becomes ``None``, and a use case declaring ``Caller()`` then fails
closed instead of running as a partially decoded caller.
"""

from __future__ import annotations

from collections.abc import Mapping
from typing import Any

from loom.core.identity.identity import Identity

_SUBJECT = "subject"
_ROLES = "roles"
_ATTRIBUTES = "attributes"
_MECHANISM = "mechanism"


[docs] def encode_identity(identity: Identity | None) -> dict[str, Any] | None: """Encode *identity* for transport inside a job envelope. Args: identity: Caller to propagate, or ``None``. Returns: A JSON-serialisable mapping, or ``None`` when there is no authenticated caller to propagate. Example:: envelope = {"payload": payload, "identity": encode_identity(caller)} """ if identity is None or not identity.is_authenticated: return None return { _SUBJECT: identity.subject, _ROLES: list(identity.roles), _ATTRIBUTES: dict(identity.attributes), _MECHANISM: identity.mechanism, }
[docs] def decode_identity(payload: Any) -> Identity | None: """Rebuild the caller from a job envelope, refusing anything unreadable. Args: payload: Value found under the envelope's identity key. Any type is accepted because the envelope may predate this contract or come from a tampered broker message. Returns: The decoded identity, or ``None`` when the envelope carries none or carries something that is not one. """ if not isinstance(payload, Mapping): return None subject = payload.get(_SUBJECT) if not isinstance(subject, str) or not subject: return None mechanism = payload.get(_MECHANISM) return Identity( subject=subject, roles=_decode_roles(payload.get(_ROLES)), attributes=_decode_attributes(payload.get(_ATTRIBUTES)), mechanism=mechanism if isinstance(mechanism, str) else "", )
def _decode_roles(value: Any) -> tuple[str, ...]: """Keep the non-empty string roles, dropping anything else.""" if not isinstance(value, (list, tuple)): return () return tuple(item for item in value if isinstance(item, str) and item) def _decode_attributes(value: Any) -> dict[str, str]: """Keep the string-valued attributes, dropping anything else.""" if not isinstance(value, Mapping): return {} return { name: item for name, item in value.items() if isinstance(name, str) and isinstance(item, str) }