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

1from pydantic import Field, model_validator 

2 

3from frappe_manager.ssl_manager.certificate import SSLCertificate 

4 

5 

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"} 

11 

12 @model_validator(mode="after") 

13 def validate_credentials(self) -> "LetsencryptSSLCertificate": 

14 """ 

15 Validate DNS-01 credentials at certificate creation time. 

16 

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 

26 

27 

28class CustomDomainCertificate(LetsencryptSSLCertificate): 

29 """ 

30 Certificate for custom domain with CNAME delegation. 

31 

32 Used for delegated DNS validation pattern where user creates CNAME 

33 pointing to a domain we control. 

34 

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 

39 

40 This allows issuing certificates for customer domains without requiring 

41 direct access to their DNS provider. 

42 """ 

43 

44 delegation_cname: str | None = None # e.g., a-gg-com.fm.com 

45 

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 

54 

55 def get_delegation_subdomain(self) -> str: 

56 """ 

57 Generate delegation subdomain from domain name. 

58 

59 Converts dots to hyphens for use in delegation CNAME. 

60 

61 Example: 

62 a.gg.com -> a-gg-com 

63 

64 Returns: 

65 Hyphenated domain name suitable for subdomain use 

66 """ 

67 return self.domain.replace(".", "-")