Source code for acmeow.models.challenge

"""Challenge model for ACME protocol.

Represents ACME challenges used to prove control over identifiers.
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any

from acmeow.enums import ChallengeStatus, ChallengeType


[docs] @dataclass(frozen=True, slots=True) class Challenge: """An ACME challenge for domain validation. Challenges are used to prove control over an identifier. They are immutable; status changes require fetching updated challenge data from the server. Args: type: The challenge type. status: Current challenge status. url: URL for responding to and polling the challenge. token: Challenge token provided by the server. ``None`` for challenge types that do not use one, such as DNS-PERSIST-01. validated: Timestamp when the challenge was validated (if valid). error: Error details if the challenge failed. accounturi: DNS-PERSIST-01 only. URI identifying the ACME account that must appear in the ``accounturi`` parameter of the TXT record. issuer_domain_names: DNS-PERSIST-01 only. Issuer Domain Names the CA will accept; the client picks one for the record's issue-value. """ type: ChallengeType status: ChallengeStatus url: str token: str | None = None validated: str | None = None error: dict[str, str] | None = None accounturi: str | None = None issuer_domain_names: tuple[str, ...] = ()
[docs] @classmethod def from_dict(cls, data: dict[str, Any]) -> Challenge: """Create a Challenge from an ACME response dictionary. Args: data: Challenge object from ACME server response. Returns: New Challenge instance. """ challenge_type = data["type"] # Map ACME challenge type strings to enum if challenge_type == "dns-01": ctype = ChallengeType.DNS elif challenge_type == "http-01": ctype = ChallengeType.HTTP elif challenge_type == "tls-alpn-01": ctype = ChallengeType.TLS_ALPN elif challenge_type == "dns-persist-01": ctype = ChallengeType.DNS_PERSIST else: # Default to DNS for unknown types ctype = ChallengeType.DNS # DNS-PERSIST-01 challenges carry no token; every other type does. return cls( type=ctype, status=ChallengeStatus(data.get("status", "pending")), url=data["url"], token=data.get("token"), validated=data.get("validated"), error=data.get("error"), accounturi=data.get("accounturi"), issuer_domain_names=tuple(data.get("issuer-domain-names", ())), )
@property def is_pending(self) -> bool: """Check if the challenge is pending response.""" return self.status == ChallengeStatus.PENDING @property def is_processing(self) -> bool: """Check if the challenge is being validated.""" return self.status == ChallengeStatus.PROCESSING @property def is_valid(self) -> bool: """Check if the challenge was successfully validated.""" return self.status == ChallengeStatus.VALID @property def is_invalid(self) -> bool: """Check if the challenge failed validation.""" return self.status == ChallengeStatus.INVALID
[docs] def __str__(self) -> str: """Return a human-readable string representation.""" return f"Challenge({self.type.value}, status={self.status.value})"