"""Operator-facing auth result contracts.
These records project provider catalogue entries, readiness checks, live-login
results, and preflight state through :class:`AuthProvidersReport`,
:class:`AuthStatusResult`, :class:`AuthTestResult`, and
:class:`LiveAuthPreflightReport`.
See Also:
:mod:`application.auth._operator`
Application services that construct these result contracts for CLI
commands.
:mod:`application.state_projection`
Canonical readiness projection consumed by status and test results.
:class:`application.workflow.WorkflowState`
Encrypted state envelope carrying the persisted
:class:`application.auth.AuthState`.
:class:`application.auth.AuthProviderDescription`
Provider-readiness description that feeds provider catalogue output.
:class:`application.auth.AuthenticatedAeatSessionResult`
Live-session result consumed by :class:`AuthLoginResult`.
"""
from __future__ import annotations
from pydantic import BaseModel
from ...core import STRICT_FROZEN_CONFIG as _STRICT_FROZEN
from ...core.errors import AeatError
from ._catalogue import AuthProviderListing
[docs]
class AuthProviderReservedError(AeatError, ValueError):
"""Raised when a known provider slot is reserved but not implemented."""
[docs]
class AuthLoginNotEnabledError(AeatError):
"""Raised when pytest invokes ``auth login`` without the live-test opt-in enabled."""
[docs]
class AuthLoginPreconditionError(AeatError):
"""Raised when ``auth login`` cannot proceed because the configured provider is unusable."""
[docs]
class AuthProvidersReport(BaseModel):
"""Auth provider catalogue projected for operator output."""
model_config = _STRICT_FROZEN
providers: tuple[AuthProviderListing, ...]
[docs]
class AuthStatusResult(BaseModel):
"""Current local auth readiness state.
Built from
:class:`application.state_projection.OperatorStateProjection`.
Provider readiness mirrors
:class:`application.state_projection.ProjectionAuthReadiness`;
active-profile fields mirror
:class:`application.state_projection.ProjectionActiveProfile`.
"""
model_config = _STRICT_FROZEN
provider: str = ""
configured: bool = False
authenticated: bool = False
available: bool = False
active_profile: str = ""
active_profile_status: str = ""
active_profile_registered: bool = False
active_profile_record_present: bool = False
active_profile_next_action: str = ""
backend_configured: bool = False
backend_available: bool = False
certificate_path: str = ""
health_severity: str = ""
health_summary: str = ""
[docs]
class AuthTestResult(AuthStatusResult):
"""Auth readiness plus a deeper local readiness probe.
``auth status`` is a pure read of the canonical state projection.
``auth test`` carries every field ``auth status`` does - so the two
can never disagree on ``configured`` - and runs a real per-provider
local probe. For the certificate provider the probe opens the
``.p12`` file, parses the PKCS#12 envelope, classifies the bundle
health (``ok`` / ``expired`` / ``expiring`` / ``corrupt`` /
``unreadable``), and surfaces the verdict as ``probe_result``. For
Cl@ve Movil the probe classifies the configured DNI/NIE through the
real identity classifier and reports ``ok`` / ``invalid_identity`` /
``identity_unset``. The persisted-session inspection ``auth status``
cannot perform - does an encrypted AEAT session token exist on disk
and is it still within its idle deadline - is also reported here
(round-3 G5 + round-5 M4).
Attributes:
persisted_session_present: Whether an encrypted AEAT session
token is on disk for the probed provider.
persisted_session_expired: Whether that token has passed its
idle deadline; ``None`` when no token is present.
probe_summary: A one-line operator-facing verdict of the local
probe.
probe_result: A typed verdict of the per-provider probe. Values
include ``ok``, ``expired``, ``expiring``, ``corrupt``,
``unreadable``, ``invalid_identity``, ``identity_unset``,
``no_path_set``, ``file_missing``, ``no_provider``. Empty
only when no provider could be resolved.
"""
model_config = _STRICT_FROZEN
persisted_session_present: bool = False
persisted_session_expired: bool | None = None
persisted_session_state: str = ""
probe_summary: str = ""
probe_result: str = ""
[docs]
class LiveAuthPreflightReport(BaseModel):
"""Redacted live-auth readiness report rendered before operator approval waits.
Combines the :class:`AuthTestResult` readiness fields with live-auth
identity-alignment settings before
:class:`core.access_gate.AeatAccessGate` can allow an authenticated
read.
"""
model_config = _STRICT_FROZEN
provider: str = ""
configured: bool = False
available: bool = False
active_profile: str = ""
active_profile_status: str = ""
active_profile_registered: bool = False
active_profile_record_present: bool = False
profile_tax_id_present: bool = False
provider_identity_present: bool = False
identity_alignment: str = ""
identity_kind: str = ""
auth_mode: str = ""
prefer_non_qr: bool | None = None
timeout_ms: int | None = None
dni_fecha_configured: bool | None = None
nie_soporte_configured: bool | None = None
certificate_path_configured: bool | None = None
certificate_file_present: bool | None = None
certificate_backend: str = ""
persisted_session_present: bool = False
persisted_session_expired: bool | None = None
persisted_session_state: str = ""
probe_result: str = ""
[docs]
class AuthLoginResult(BaseModel):
"""Result of an operator-triggered live authentication attempt.
Summarises the
:class:`application.auth.AuthenticatedAeatSessionResult` produced by
the provider-session lifecycle without exposing session material.
"""
model_config = _STRICT_FROZEN
provider: str
authenticated: bool
reused_persisted_session: bool
fresh: bool
removed_sessions: int
acquired_lock: bool
reset_lock_state: str = ""
verification_status: str = ""
[docs]
class AuthClearResult(BaseModel):
"""Result of clearing local auth metadata and persisted state.
Reports the local side effects after
:func:`application.auth.clear_operator_auth` resets
:class:`application.auth.AuthState`, deletes persisted sessions, and
removes acquisition locks for the requested provider scope.
"""
model_config = _STRICT_FROZEN
removed_sessions: int
cleared_workflow_state: bool
cleared_locks: int
[docs]
class CertificateSourceNotFoundError(AeatError, KeyError):
"""Raised when an operator names a certificate source that is not registered."""
[docs]
class CertificateSourcePayload(BaseModel):
"""One registered certificate source, operator-facing.
Projects :class:`application.auth.CertificateSourceRecord` for the
``certificate register`` / ``certificate list`` verbs. Never carries
certificate passwords or key material — only the filesystem
reference already stored in workflow state.
"""
model_config = _STRICT_FROZEN
name: str
certificate_path: str
friendly_name: str = ""
active: bool = False
registered_at: str = ""
[docs]
class CertificateSourceListResult(BaseModel):
"""Result of ``aeat config auth certificate list``."""
model_config = _STRICT_FROZEN
sources: tuple[CertificateSourcePayload, ...] = ()
active_source: str = ""
[docs]
class CertificateSourceMutationResult(BaseModel):
"""Result of registering, selecting, or removing a certificate source."""
model_config = _STRICT_FROZEN
name: str
certificate_path: str = ""
active: bool = False
removed: bool = False
[docs]
class CertificateSourceCheckEntry(BaseModel):
"""Expiry/rotation verdict for one registered certificate source.
Reuses the same local PKCS#12 health classification
:func:`application.auth.probe_provider_configuration` runs for the
single-certificate provider path (``ok`` / ``expiring`` / ``expired`` /
``corrupt`` / ``unreadable`` / ``file_missing``), applied per named
source in :class:`application.auth.AuthState.certificate_sources`
rather than only the active ``certificate_path``. Never carries
certificate passwords or key material.
Attributes:
name: The registered source name.
certificate_path: Filesystem path of the source's PKCS#12 bundle.
friendly_name: Optional human-readable label.
active: Whether this source is the currently selected one.
result: Typed :class:`application.auth.ProviderProbeResult` verdict
(as its string value, matching the sibling ``AuthTestResult``
convention).
summary: Localised one-line operator-facing verdict.
days_until_expiry: Whole days until ``not_after``, when the
certificate could be parsed; negative when already expired;
``None`` when expiry could not be determined (unreadable,
corrupt, missing path/file, or no configured decode password).
"""
model_config = _STRICT_FROZEN
name: str
certificate_path: str
friendly_name: str = ""
active: bool = False
result: str = ""
summary: str = ""
days_until_expiry: int | None = None
[docs]
class CertificateSourceCheckReport(BaseModel):
"""Result of ``aeat config auth certificate check``.
``has_warnings`` is ``True`` when at least one entry's ``result`` is
``expiring`` or ``expired``, letting the CLI decide whether to attach
a non-blocking rotation-reminder :class:`~core.json_contract.Notice`
per entry without re-deriving the same predicate.
"""
model_config = _STRICT_FROZEN
entries: tuple[CertificateSourceCheckEntry, ...] = ()
has_warnings: bool = False
[docs]
class CertificateSourceSecretMutationResult(BaseModel):
"""Result of setting, rotating, or removing a named certificate source's secret.
Never carries the secret value itself — only whether one is now
registered, which backend holds it, and whether the call rotated an
existing secret (``rotated``) or set one for the first time. Mirrors
the ``sensitive-financial-data-secure-storage-only`` and
``no-silent-under-declaration`` disciplines: the secret's *presence*
is observable, its *value* never is.
"""
model_config = _STRICT_FROZEN
name: str
backend: str = ""
has_secret: bool = False
rotated: bool = False
removed: bool = False
__all__ = [
"AuthClearResult",
"AuthConfigureDanglingActiveProfileError",
"AuthConfigureNoActiveBucketError",
"AuthConfigureResult",
"AuthLoginNotEnabledError",
"AuthLoginPreconditionError",
"AuthLoginResult",
"AuthProviderReservedError",
"AuthProvidersReport",
"AuthStatusResult",
"AuthTestResult",
"CertificateSourceCheckEntry",
"CertificateSourceCheckReport",
"CertificateSourceListResult",
"CertificateSourceMutationResult",
"CertificateSourceNotFoundError",
"CertificateSourcePayload",
"CertificateSourceSecretMutationResult",
"LiveAuthPreflightReport",
]