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

name: str
type: str
data: str
id: Optional[str]
zone: Optional[str]
aux: str
ttl: int
active: Literal['Y', 'N']
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

def is_same(self, right: Record) -> bool:
 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)

contains_spf_definition: bool
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()))
is_apex: bool
112    @property
113    def is_apex(self) -> bool:
114        name = self.name.strip()
115        return name in ("", "@")  # or name.endswith(".")
is_redirect: bool
117    @property
118    def is_redirect(self) -> bool:
119        data = (self.type.strip(), self.data.strip())
120        return data in REDIRECT_VALUES
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_dkim: bool
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        )
is_dmarc: bool
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        )
is_null_mx: bool
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        )
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
model_config: ClassVar[pydantic.config.ConfigDict] = {'extra': 'allow', 'populate_by_name': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class Record.Config:
75    class Config:
76        populate_by_name = True
77        extra = "allow"
populate_by_name = True
extra = 'allow'