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 (DNS-01 or HTTP-01).
status: Current challenge status.
url: URL for responding to and polling the challenge.
token: Challenge token provided by the server.
validated: Timestamp when the challenge was validated (if valid).
error: Error details if the challenge failed.
"""
type: ChallengeType
status: ChallengeStatus
url: str
token: str
validated: str | None = None
error: dict[str, str] | None = None
[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
else:
# Default to DNS for unknown types
ctype = ChallengeType.DNS
return cls(
type=ctype,
status=ChallengeStatus(data.get("status", "pending")),
url=data["url"],
token=data["token"],
validated=data.get("validated"),
error=data.get("error"),
)
@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})"