Coverage for frappe_manager / ssl_manager / letsencrypt_certificate.py: 81%
16 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:35 +0530
« prev ^ index » next coverage.py v7.13.5, created at 2026-07-02 18:35 +0530
1from pydantic import Field, model_validator
3from frappe_manager.ssl_manager.certificate import SSLCertificate
6class LetsencryptSSLCertificate(SSLCertificate):
7 # Email field removed - Let's Encrypt discontinued email notifications (June 2025)
8 api_token: str | None = Field(None, description="Cloudflare API token.")
9 api_key: str | None = Field(None, description="Cloudflare Global API Key.")
10 toml_exclude: set | None = {"domain", "toml_exclude"}
12 @model_validator(mode="after")
13 def validate_credentials(self) -> "LetsencryptSSLCertificate":
14 """
15 Validate DNS-01 credentials at certificate creation time.
17 Note: Credentials are loaded at runtime from FM config via
18 get_dns_credentials_for_certificate(), so we only validate if
19 credentials are provided on the cert object itself (backward compat).
20 The actual credential availability check happens at certificate
21 generation time in acmesh_certificate_service.py.
22 """
23 # Skip validation - credentials are loaded dynamically from FM config
24 # The acme.sh service will validate credentials when generating the cert
25 return self
28class CustomDomainCertificate(LetsencryptSSLCertificate):
29 """
30 Certificate for custom domain with CNAME delegation.
32 Used for delegated DNS validation pattern where user creates CNAME
33 pointing to a domain we control.
35 Example:
36 User domain: a.gg.com
37 CNAME: a.gg.com → a-gg-com.fm.com
38 Challenge validation: _acme-challenge.a-gg-com.fm.com
40 This allows issuing certificates for customer domains without requiring
41 direct access to their DNS provider.
42 """
44 delegation_cname: str | None = None # e.g., a-gg-com.fm.com
46 @model_validator(mode="after")
47 def validate_credentials(self) -> "CustomDomainCertificate":
48 """
49 Override parent validation - CNAME delegation doesn't require credentials
50 for the custom domain itself. DNS validation happens on the delegated domain.
51 """
52 # Skip validation for CNAME delegation - credentials are for the delegated domain
53 return self
55 def get_delegation_subdomain(self) -> str:
56 """
57 Generate delegation subdomain from domain name.
59 Converts dots to hyphens for use in delegation CNAME.
61 Example:
62 a.gg.com -> a-gg-com
64 Returns:
65 Hyphenated domain name suitable for subdomain use
66 """
67 return self.domain.replace(".", "-")