9d2be4fcc501628ad8151d49642f212836b232cc — Drew DeVault 1 year, 10 months ago 75d6aa1
Begin this brutal slide towards insanity
3 files changed, 366 insertions(+), 1 deletions(-)

M names/types/__init__.py
A names/types/contact.py
A names/types/gtld_specific.py
M names/types/__init__.py => names/types/__init__.py +79 -1
@@ 1,5 1,83 @@
import sqlalchemy as sa
from enum import IntFlag, auto
from srht.database import Base
from srht.flagtype import FlagType
from srht.oauth import ExternalUserMixin
from names.types.contact import DomainContact, ContactType
from names.types.gtld_specific import *

class ExtendedDomainData(IntFlag):
    Various gTLDs require us to collect extra information. This flag tells
    us what we've already collected for each such gTLD.
    # Do not modify the sort order of this list! It is append-only.
    abogado = auto()
    aero = auto()
    au = auto()
    cl = auto()
    co_hu = auto()
    co_za = auto()
    com_ar = auto()
    com_br = auto()
    com_lv = auto()
    com_mx = auto()
    com_pt = auto()
    com_ro = auto()
    coop = auto()
    de = auto()
    dk = auto()
    es = auto()
    eu = auto()
    fi = auto()
    fr = auto()
    hk = auto()
    hu = auto()
    it = auto()
    jobs = auto()
    law = auto()
    lv = auto()
    mx = auto()
    my = auto()
    no = auto()
    nu = auto()
    nyc = auto()
    pm = auto()
    pro = auto()
    pt = auto()
    re = auto()
    ro = auto()
    ru = auto()
    se = auto()
    sg = auto()
    tf = auto()
    travel = auto()
    uk = auto()
    us = auto()
    wf = auto()
    xxx = auto()
    yt = auto()

class User(Base, ExternalUserMixin):
    extra_info = sa.Column(FlagType(ExtendedDomainData),
            nullable=False, server_default='0')

    owner_contact = sa.orm.relationship("DomainContact",
                ExternalUserMixin.id == DomainContact.user_id,
                DomainContact._contact_type == ContactType.owner.value))

    admin_contact = sa.orm.relationship("DomainContact",
                ExternalUserMixin.id == DomainContact.user_id,
                DomainContact._contact_type == ContactType.admin.value))

    billing_contact = sa.orm.relationship("DomainContact",
                ExternalUserMixin.id == DomainContact.user_id,
                DomainContact._contact_type == ContactType.billing.value))

    tech_contact = sa.orm.relationship("DomainContact",
                ExternalUserMixin.id == DomainContact.user_id,
                DomainContact._contact_type == ContactType.tech.value))

A names/types/contact.py => names/types/contact.py +43 -0
@@ 0,0 1,43 @@
import enum
import sqlalchemy as sa
from srht.database import Base

class ContactType(enum.Enum):
    owner = "owner"
    admin = "admin"
    billing = "billing"
    tech = "tech"

class DomainContact(Base):
    __tablename__ = 'domain_contact'
    id = sa.Column(sa.Integer, primary_key=True)
    created = sa.Column(sa.DateTime, nullable=False)
    updated = sa.Column(sa.DateTime, nullable=False)
    _contact_type = sa.Column("contact_type", sa.Unicode, nullable=False)
    user_id = sa.Column(sa.Integer, sa.ForeignKey('user.id'), nullable=False)

    def contact_type(self):
        return ContactType(self._contact_type)

    def contact_type_set(self, val):
        self._contact_type = val.value

    # https://domains.opensrs.guide/docs/domain-contacts
    first_name = sa.Column(sa.Unicode(64), nullable=False)
    last_name = sa.Column(sa.Unicode(64), nullable=False)
    org_name = sa.Column(sa.Unicode(64), nullable=False)
    address1 = sa.Column(sa.Unicode(64), nullable=False)
    address2 = sa.Column(sa.Unicode(64))
    address3 = sa.Column(sa.Unicode(64))
    city = sa.Column(sa.Unicode(64), nullable=False)
    state = sa.Column(sa.Unicode(32)) # Required if country in [CA, US]
    postal_code = sa.Column(sa.Unicode(16), nullable=False)
    country = sa.Column(sa.Unicode(2), nullable=False)
    phone = sa.Column(sa.Unicode(20), nullable=False)
    fax = sa.Column(sa.Unicode(20))
    email = sa.Column(sa.Unicode(128), nullable=False)
    # Required for only some kinds of domains:
    lang = sa.Column(sa.Unicode(2))
    vat = sa.Column(sa.Unicode(16))

A names/types/gtld_specific.py => names/types/gtld_specific.py +244 -0
@@ 0,0 1,244 @@
import enum
import sqlalchemy as sa
import sqlalchemy_utils as sau
from sqlalchemy.ext.declarative import declared_attr
from srht.database import Base
# TODO: how to handle logic validating which things are required

class ExtraDataMixin:
    id = sa.Column(sa.Integer, primary_key=True)
    created = sa.Column(sa.DateTime, nullable=False)
    updated = sa.Column(sa.DateTime, nullable=False)

    def user_id(cls):
        return sa.Column(sa.Integer, sa.ForeignKey('user.id'))

    def user(cls):
        return sa.orm.relationship('User')

### .aero
class AeroExtraData(Base, ExtraDataMixin):
    Registration of .AERO domains is restricted to members of the aviation
    community such as airlines, airports, and qualifying companies and

    To prove that they meet the requirements, registrants must provide an
    Eligibility and Name Selection (ENS) ID and password, which can be obtained
    by applying to the registry through [nic.aero][nic.aero]

    [nic.aero]: http://www.nic.aero/registration/manage_your_aero_id/apply
    __tablename__ = "aero_extra_data"
    aero_ens_id = sa.Column(sa.Unicode, nullable=False)
    """Eligibility and Name Selection (ENS) identifier"""
    aero_ens_password = sa.Column(sa.Unicode, nullable=False)
    """Eligibility and Name Selection (ENS) password"""

### .au
class AuEligibilityType(enum.Enum):
    charity = "Charity"
    club = "Club"
    incorporated_association = "Incorporated Association"
    nonprofit_organization = "Non-profit Organiaztion"
    trade_union = "Trade Union"
    company = "Company"
    partnership = "Partnership"
    pending_tm_owner = "Pending TM Owner"
    registered_business = "Registered Business"
    sole_trader = "Sole Trader"
    trademark_owner = "Trademark Owner"
    citizen_resident = "Citizen/Resident"

    def valid_for(tld):
        asn_org = [
        com_net = [
        return {
            ".asn.au": asn_org,
            ".org.au": asn_org,
            ".com.au": com_net,
            ".net.au": com_net,
            ".id.au": [AuEligibilityType.citizen_resident],

class AuEligibilityIdType(enum.Enum):
    abn = "ABN"
    act_bn = "ACT BN"
    nsw_bn = "NSW BN"
    nt_bn = "NT BN"
    other = "OTHER"
    qld_bn = "QLD BN"
    sa_bn = "SA BN"
    tas_bn = "TAS BN"
    tm = "TM"
    wa_bn = "WA BN"

    def valid_for(eligibility_type: AuEligibilityType):
        """Returns a list of valid ID types for a given eligibility type."""
        bns = [
        return {
            AuEligibilityType.charity: [AuEligibilityIdType.abn],
            AuEligibilityType.club: [AuEligibilityIdType.abn],
            AuEligibilityType.incorporated_association: bns,
            AuEligibilityType.nonprofit_organization: bns +
            AuEligibilityType.trade_union: [AuEligibilityIdType.other],
            AuEligibilityType.sole_trader: [AuEligibilityIdType.abn],
            AuEligibilityType.trademark_owner: [AuEligibilityIdType.tm],
            AuEligibilityType.pending_trademark_owner: [AuEligibilityIdType.tm],
            AuEligibilityType.partnership: [AuEligibilityIdType.abn],

class AuPolicyReason(enum.Enum):
    exact_match = 1
    The name exactly matches the acronym or abbreviation of the registrant's
    company or trading name, organization or association name, or trademark.
    closely_connected = 2
    The name is connected closely and substantially to the registrant.

class AuRegistrantIdType(enum.Enum):
    acn = "ACN"
    """Australian Company Number (ACN)"""
    abn = "ABN"
    """Australian Business Number (ABN)"""

class AuExtraData(Base, ExtraDataMixin):
    __tablename__ = "au_extra_data"
    # optional for .id.au unless egibility type is also specified:
    eligibility_id = sa.Column(sa.Unicode())
    The identifier of the eligibility document.

    Important: This number will be verified, and if you submit an incorrect
    number, the registration will fail.

    eligibility_id_type = sa.Column(
            sau.ChoiceType(AuEligibilityIdType, impl=sa.Unicode))
    The type of eligibility ID specified.

    eligibility_name = sa.Column(sa.Unicode)
    """The name on the eligibility ID document."""

    eligibility_type = sa.Column(sau.ChoiceType(
        AuEligibilityType, impl=sa.Unicode), nullable=False)
    The reason that the registrant is eligible for the domain.

    policy_reason = sa.Column(sau.ChoiceType(
        AuPolicyReason, impl=sa.Integer), nullable=False)

    registrant_id  = sa.Column(sa.Unicode)
    The registrant's ACN or ABN ID.

    Important: This number will be verified, and if you submit an incorrect
    number, the registration will fail.

    registrant_id_type = sa.Column(sau.ChoiceType(
        AuRegistrantIdType, impl=sa.Unicode), nullable=False)

    registrant_name = sa.Column(sa.Unicode)
    The legal entity such as a company, incorporated association, government
    agency or individual person. Cannot be a registered business name or

### .cl
class ClRegistrantType(enum.Enum):
    individual = "individual"
    organization = "organization"

class ClExtraData(Base, ExtraDataMixin):
    __tablename__ = "cl_extra_data"

    id_card_number = sa.Column(sa.Unicode) # required if type = individual
    """The number of the individual's identity card."""

    registrant_type = sa.Column(
            sau.ChoiceType(ClRegistrantType), nullable=False)
    """The entity type of registrant."""

    registrant_vat_id = sa.Column(sa.Unicode) # required if type = organization
    """Value Added Tax registration number."""

### .it
class ItEntityType(enum.Enum):
    italian_and_foreign_national_persons = 1
    """Italians and Italian foreign nationals"""
    companies_or_sole_proprietorships = 2
    """Companies or sole proprietorships"""
    freelance_workers_or_professionals = 3
    """Freelance workers or professionals"""
    non_profits = 4
    """Non-profit organizations"""
    public_organizations = 5
    """Public organizations"""
    other_subjects = 6
    """Other subjects"""
    foreigners = 7
    Foreigners in any category not including Italians or Italian foreign

class ItExtraData(Base, ExtraDataMixin):
    __tablename__ = "it_extra_data"
    entity_type = sa.Column(sau.ChoiceType(ItEntityType, impl=sa.Integer))
    """The legal entity type of registrant"""

    nationality_code = sa.Column(sa.Unicode(2))
    The 2-digit ISO 3166-1 code that identifies the Registrant's nationality.

    reg_code = sa.Column(sa.Unicode(16))
    An identifying number, usually the VAT or Codice Fiscale (numeric tax
    code). The value that you need to enter depends on the entity type of the

    - Italian citizens must enter their Codice Fiscale.
      *Non Italians not living in Italy can enter n.a.
    - Italian companies, freelancers, and other subjects must enter their 11
      digit VAT number or tax identification number.
    - Italian non-profit organizations must enter their VAT number or tax
      identification number; however, if they do not have one, they can enter
    - Non Italian entities other than persons (organizations, freelancers,
      companies, etc.) may enter their VAT number or n.a.