cloudfloordns.models.record
1from itertools import chain 2from typing import Literal, Optional 3 4from pydantic import BaseModel 5 6TYPES_VALUES = Literal[ 7 "A", 8 "AAAA", 9 "ALIAS", 10 "CNAME", 11 "HINFO", 12 "MX", 13 "NS", 14 "PTR", 15 "RP", 16 "SRV", 17 "CAA", 18 "TXT", 19 "REDIRECT://", 20 # For comparison, the following are valid on Cloudflare 21 # "SOA", 22 # "DS", 23 # "DNSKEY", 24 # "LOC", 25 # "NAPTR", 26 # "SSHFP", 27 # "SVCB", 28 # "TSLA", 29 # "URI", 30 # "SPF", 31] 32 33FULL_COMPARISON = { 34 "A", 35 "AAAA", 36 "ALIAS", 37 "CNAME", 38} 39 40UNIQUE_BY_NAME = {"HINFO", "MX", "NS", "PTR", "RP", "SRV", "CAA", "TXT", "REDIRECT://"} 41 42REDIRECT_SERVERS = [ 43 "dforward.mtgsy.net.", 44] 45REDIRECT_SERVERS_IP = [ 46 "50.31.0.12", 47] 48 49REDIRECT_VALUES = list( 50 chain( 51 (("CNAME", value) for value in REDIRECT_SERVERS), 52 (("A", ip) for ip in REDIRECT_SERVERS_IP), 53 ) 54) 55 56 57class Record(BaseModel): 58 """ 59 Pydantic model 60 """ 61 62 name: str 63 type: str 64 data: str 65 id: Optional[str] = None 66 zone: Optional[str] = None 67 aux: str = "0" 68 ttl: int = 3600 69 active: Literal["Y", "N"] = "Y" 70 # isfwd: str = "0" 71 # cc: str = None 72 # lbType: str = "0" 73 74 class Config: 75 populate_by_name = True 76 extra = "allow" 77 78 @property 79 def identifier(self) -> str: 80 """ 81 This method returns an identifer for the record that does not depend on its remote id 82 """ 83 identifier = f"{self.name}/{self.type}" 84 if self.type in FULL_COMPARISON: 85 identifier = f"{identifier}/{self.data}" 86 return identifier 87 88 def __hash__(self): 89 return hash(self.identifier) 90 91 def is_same(self, right: "Record") -> bool: 92 """ 93 This method check the identity (e.g. same id if defined, or same name/name+value) 94 """ 95 if not isinstance(right, Record): 96 return NotImplemented 97 if self.id and right.id: 98 return self.id == right.id 99 if (self.name, self.type) != (right.name, right.type): 100 return False 101 if self.type in FULL_COMPARISON: 102 return self.data == right.data 103 return True 104 105 @property 106 def contains_spf_definition(self) -> bool: 107 # RFC states that we only have one spf record on the APEX 108 # But we may defined other records with spf definition to be included elsewhere. 109 return all((self.type == "TXT", "v=spf" in self.data.lower())) 110 111 @property 112 def is_apex(self) -> bool: 113 name = self.name.strip() 114 return name in ("", "@") # or name.endswith(".") 115 116 @property 117 def is_redirect(self) -> bool: 118 data = (self.type.strip(), self.data.strip()) 119 return data in REDIRECT_VALUES 120 121 @property 122 def is_spf(self) -> bool: 123 # RFC: 124 # https://www.rfc-editor.org/rfc/rfc6242#section-4.1 125 # https://datatracker.ietf.org/doc/html/rfc7208#section-4.5 126 # NOTE: There should be only 1 apex spf record, 127 # but we can create other spf record (e.g. spf1.mydomain.com) and include it in the apex 128 # (alternatively, we can define spf records with CNAME or even NS records) 129 return all((not self.name, self.contains_spf_definition)) 130 131 @property 132 def is_dkim(self) -> bool: 133 return all( 134 ( 135 "._domainkey" in self.name, 136 self.type == "TXT", 137 "v=dkim" in self.data.lower(), 138 ) 139 ) 140 141 @property 142 def is_dmarc(self) -> bool: 143 return all( 144 ("_dmarc" in self.name, self.type == "TXT", "v=dmarc" in self.data.lower()) 145 ) 146 147 @property 148 def is_null_mx(self) -> bool: 149 return ( 150 self.type.upper() == "MX" 151 and (self.name.endswith(".") or self.name in ("", "@")) 152 and self.data in ("", ".") 153 ) 154 155 @property 156 def is_standard(self) -> bool: 157 """ 158 Return True if the record is a standard one, False otherwise. 159 A record is a standard one if it it: 160 - A mail hardening record (SPF/DKIM/DMARC/Null MX) 161 - An apex NS record 162 """ 163 return any( 164 ( 165 self.is_null_mx, 166 self.is_spf, 167 self.is_dkim, 168 self.is_dmarc, 169 (self.type.upper() == "NS" and self.is_apex), 170 ) 171 )
TYPES_VALUES =
typing.Literal['A', 'AAAA', 'ALIAS', 'CNAME', 'HINFO', 'MX', 'NS', 'PTR', 'RP', 'SRV', 'CAA', 'TXT', 'REDIRECT://']
FULL_COMPARISON =
{'A', 'AAAA', 'ALIAS', 'CNAME'}
UNIQUE_BY_NAME =
{'MX', 'PTR', 'CAA', 'HINFO', 'SRV', 'REDIRECT://', 'NS', 'TXT', 'RP'}
REDIRECT_SERVERS =
['dforward.mtgsy.net.']
REDIRECT_SERVERS_IP =
['50.31.0.12']
REDIRECT_VALUES =
[('CNAME', 'dforward.mtgsy.net.'), ('A', '50.31.0.12')]
class
Record(pydantic.main.BaseModel):
58class Record(BaseModel): 59 """ 60 Pydantic model 61 """ 62 63 name: str 64 type: str 65 data: str 66 id: Optional[str] = None 67 zone: Optional[str] = None 68 aux: str = "0" 69 ttl: int = 3600 70 active: Literal["Y", "N"] = "Y" 71 # isfwd: str = "0" 72 # cc: str = None 73 # lbType: str = "0" 74 75 class Config: 76 populate_by_name = True 77 extra = "allow" 78 79 @property 80 def identifier(self) -> str: 81 """ 82 This method returns an identifer for the record that does not depend on its remote id 83 """ 84 identifier = f"{self.name}/{self.type}" 85 if self.type in FULL_COMPARISON: 86 identifier = f"{identifier}/{self.data}" 87 return identifier 88 89 def __hash__(self): 90 return hash(self.identifier) 91 92 def is_same(self, right: "Record") -> bool: 93 """ 94 This method check the identity (e.g. same id if defined, or same name/name+value) 95 """ 96 if not isinstance(right, Record): 97 return NotImplemented 98 if self.id and right.id: 99 return self.id == right.id 100 if (self.name, self.type) != (right.name, right.type): 101 return False 102 if self.type in FULL_COMPARISON: 103 return self.data == right.data 104 return True 105 106 @property 107 def contains_spf_definition(self) -> bool: 108 # RFC states that we only have one spf record on the APEX 109 # But we may defined other records with spf definition to be included elsewhere. 110 return all((self.type == "TXT", "v=spf" in self.data.lower())) 111 112 @property 113 def is_apex(self) -> bool: 114 name = self.name.strip() 115 return name in ("", "@") # or name.endswith(".") 116 117 @property 118 def is_redirect(self) -> bool: 119 data = (self.type.strip(), self.data.strip()) 120 return data in REDIRECT_VALUES 121 122 @property 123 def is_spf(self) -> bool: 124 # RFC: 125 # https://www.rfc-editor.org/rfc/rfc6242#section-4.1 126 # https://datatracker.ietf.org/doc/html/rfc7208#section-4.5 127 # NOTE: There should be only 1 apex spf record, 128 # but we can create other spf record (e.g. spf1.mydomain.com) and include it in the apex 129 # (alternatively, we can define spf records with CNAME or even NS records) 130 return all((not self.name, self.contains_spf_definition)) 131 132 @property 133 def is_dkim(self) -> bool: 134 return all( 135 ( 136 "._domainkey" in self.name, 137 self.type == "TXT", 138 "v=dkim" in self.data.lower(), 139 ) 140 ) 141 142 @property 143 def is_dmarc(self) -> bool: 144 return all( 145 ("_dmarc" in self.name, self.type == "TXT", "v=dmarc" in self.data.lower()) 146 ) 147 148 @property 149 def is_null_mx(self) -> bool: 150 return ( 151 self.type.upper() == "MX" 152 and (self.name.endswith(".") or self.name in ("", "@")) 153 and self.data in ("", ".") 154 ) 155 156 @property 157 def is_standard(self) -> bool: 158 """ 159 Return True if the record is a standard one, False otherwise. 160 A record is a standard one if it it: 161 - A mail hardening record (SPF/DKIM/DMARC/Null MX) 162 - An apex NS record 163 """ 164 return any( 165 ( 166 self.is_null_mx, 167 self.is_spf, 168 self.is_dkim, 169 self.is_dmarc, 170 (self.type.upper() == "NS" and self.is_apex), 171 ) 172 )
Pydantic model
identifier: str
79 @property 80 def identifier(self) -> str: 81 """ 82 This method returns an identifer for the record that does not depend on its remote id 83 """ 84 identifier = f"{self.name}/{self.type}" 85 if self.type in FULL_COMPARISON: 86 identifier = f"{identifier}/{self.data}" 87 return identifier
This method returns an identifer for the record that does not depend on its remote id
92 def is_same(self, right: "Record") -> bool: 93 """ 94 This method check the identity (e.g. same id if defined, or same name/name+value) 95 """ 96 if not isinstance(right, Record): 97 return NotImplemented 98 if self.id and right.id: 99 return self.id == right.id 100 if (self.name, self.type) != (right.name, right.type): 101 return False 102 if self.type in FULL_COMPARISON: 103 return self.data == right.data 104 return True
This method check the identity (e.g. same id if defined, or same name/name+value)
is_spf: bool
122 @property 123 def is_spf(self) -> bool: 124 # RFC: 125 # https://www.rfc-editor.org/rfc/rfc6242#section-4.1 126 # https://datatracker.ietf.org/doc/html/rfc7208#section-4.5 127 # NOTE: There should be only 1 apex spf record, 128 # but we can create other spf record (e.g. spf1.mydomain.com) and include it in the apex 129 # (alternatively, we can define spf records with CNAME or even NS records) 130 return all((not self.name, self.contains_spf_definition))
is_standard: bool
156 @property 157 def is_standard(self) -> bool: 158 """ 159 Return True if the record is a standard one, False otherwise. 160 A record is a standard one if it it: 161 - A mail hardening record (SPF/DKIM/DMARC/Null MX) 162 - An apex NS record 163 """ 164 return any( 165 ( 166 self.is_null_mx, 167 self.is_spf, 168 self.is_dkim, 169 self.is_dmarc, 170 (self.type.upper() == "NS" and self.is_apex), 171 ) 172 )
Return True if the record is a standard one, False otherwise. A record is a standard one if it it:
- A mail hardening record (SPF/DKIM/DMARC/Null MX)
- An apex NS record
class
Record.Config: