Source code for acmeow.models.authorization

"""Authorization model for ACME protocol.

Represents ACME authorizations that prove control over identifiers.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any

from acmeow.enums import AuthorizationStatus, ChallengeType
from acmeow.models.challenge import Challenge
from acmeow.models.identifier import Identifier


[docs] @dataclass(frozen=True, slots=True) class Authorization: """An ACME authorization for an identifier. Authorizations represent the server's acknowledgment that the account holder has proven control over an identifier. They contain one or more challenges that can be completed to validate control. Args: identifier: The identifier this authorization is for. status: Current authorization status. url: URL for polling authorization status. expires: Expiration timestamp for the authorization. challenges: List of available challenges. wildcard: Whether this is a wildcard authorization. """ identifier: Identifier status: AuthorizationStatus url: str expires: str | None = None challenges: tuple[Challenge, ...] = field(default_factory=tuple) wildcard: bool = False
[docs] @classmethod def from_dict(cls, data: dict[str, Any], url: str) -> Authorization: """Create an Authorization from an ACME response dictionary. Args: data: Authorization object from ACME server response. url: The authorization URL. Returns: New Authorization instance. """ challenges = tuple( Challenge.from_dict(c) for c in data.get("challenges", []) ) return cls( identifier=Identifier.from_dict(data["identifier"]), status=AuthorizationStatus(data.get("status", "pending")), url=url, expires=data.get("expires"), challenges=challenges, wildcard=data.get("wildcard", False), )
[docs] def get_challenge(self, challenge_type: ChallengeType) -> Challenge | None: """Get a challenge of the specified type. Args: challenge_type: The type of challenge to find. Returns: The matching challenge, or None if not found. """ for challenge in self.challenges: if challenge.type == challenge_type: return challenge return None
[docs] def get_dns_challenge(self) -> Challenge | None: """Get the DNS-01 challenge if available. Returns: The DNS-01 challenge, or None if not available. """ return self.get_challenge(ChallengeType.DNS)
[docs] def get_http_challenge(self) -> Challenge | None: """Get the HTTP-01 challenge if available. Returns: The HTTP-01 challenge, or None if not available. """ return self.get_challenge(ChallengeType.HTTP)
[docs] def get_tls_alpn_challenge(self) -> Challenge | None: """Get the TLS-ALPN-01 challenge if available. Returns: The TLS-ALPN-01 challenge, or None if not available. """ return self.get_challenge(ChallengeType.TLS_ALPN)
@property def is_pending(self) -> bool: """Check if the authorization is pending completion.""" return self.status == AuthorizationStatus.PENDING @property def is_valid(self) -> bool: """Check if the authorization has been validated.""" return self.status == AuthorizationStatus.VALID @property def is_invalid(self) -> bool: """Check if the authorization failed.""" return self.status == AuthorizationStatus.INVALID @property def domain(self) -> str: """Get the domain name for this authorization. Returns: The identifier value (domain name). """ return self.identifier.value
[docs] def __str__(self) -> str: """Return a human-readable string representation.""" return f"Authorization({self.identifier}, status={self.status.value})"