Ejemplo n.º 1
0
def test_020():
    u = "http://*****:*****@%s:%s/auth" % (HOST, PORT)
    res = Resource(u)
    r = res.get()
    t.eq(r.status_int, 200)
    u = "http://*****:*****@%s:%s/auth" % (HOST, PORT)
    res = Resource(u)
    t.raises(Unauthorized, res.get)
Ejemplo n.º 2
0
def test_020():
    u = "http://*****:*****@%s:%s/auth" % (HOST, PORT)
    res = Resource(u)
    r = res.get()
    t.eq(r.status_int, 200)
    u = "http://*****:*****@%s:%s/auth" % (HOST, PORT)
    res = Resource(u)
    t.raises(Unauthorized, res.get)
Ejemplo n.º 3
0
def main():
    res = Resource(uri)
    clientResponse = res.get(path=path, headers={"Content-Type":"application/json"})
    body = clientResponse.body_string()
    docs = json.loads(body)
  
    deletable = []
    for result in docs["rows"]:
        doc = result[u'value']
        if doc.has_key(u'_id') == True and doc.has_key(u'_rev') == True:
            deletable.append({ "_id": doc[u'_id'], "_rev": doc[u'_rev'], "_deleted": True })
     
        
    clientResponse = res.post("/lrdemo/_bulk_docs", payload=json.dumps({"all_or_nothing": True, "docs": deletable}), headers={"Content-Type":"application/json"})
    body = clientResponse.body_string()
    print(body)
    print ("done")
Ejemplo n.º 4
0
def main():
    res = Resource(uri)
    clientResponse = res.get(path=path, headers={"Content-Type": "application/json"})
    body = clientResponse.body_string()
    docs = json.loads(body)

    deletable = []
    for result in docs["rows"]:
        doc = result[u"value"]
        if doc.has_key(u"_id") == True and doc.has_key(u"_rev") == True:
            deletable.append({"_id": doc[u"_id"], "_rev": doc[u"_rev"], "_deleted": True})

    clientResponse = res.post(
        "/lrdemo/_bulk_docs",
        payload=json.dumps({"all_or_nothing": True, "docs": deletable}),
        headers={"Content-Type": "application/json"},
    )
    body = clientResponse.body_string()
    print(body)
    print("done")
Ejemplo n.º 5
0
class EnomDomainAPIClient():
    """
    Wraps core Commands from the Enom API
    All methods returns an instance of EnomAPIResponse or raises an EnomAPIError

    Please note that it is NOT possible to use subaccount credentials when creating an instance.
    The reason is that Enom does not permit a retail subaccount to use the API. This error will result:
      "User not permitted from this IP address - 4"

    This error will also occur when your external IP address has not been whitelisted by Enom.
    The solution is to contact Enom support (or dev Ron West) and add your public IP.
    Here is an example for the UPDATEACCOUNTINFO command:

        <StatusEditContact>Successful</StatusEditContact>
        <StatusDetailEditContact>Successfully updated</StatusDetailEditContact>
        <PendingVerification />
        <Command>UPDATEACCOUNTINFO</Command>
        <APIType>API</APIType>
        <Language>eng</Language>
        <ErrCount>2</ErrCount>
        <errors>
        <Err1>User not permitted from this IP address - 4</Err1>
        <Err2>Account not found in database.</Err2>
        ...
    """
    def __init__(self, test=True, user='******', password='******'):
        self.test = test
        if self.test:
            self.server = 'resellertest.enom.com'
        else:
            self.server = 'reseller.enom.com'
        self._user = user
        self._password = password
        self._base_uri = 'https://{server}/interface.asp'.format(server=self.server)
        self._base_parameters = {'UID': self._user, 'PW': self._password, 'ResponseType': 'XML'}
        self._resource = Resource(self._base_uri)
        self._converter = EnomEntityConverter()

    def _all_params(self, params):
        # create a new dictionary so we do not expose the credentials (in self.base_parameters) in "params"
        all_params = dict(params)
        all_params.update(self._base_parameters)
        return all_params

    def _get_response(self, params):
        response = self._resource.get(params_dict=self._all_params(params))
        sanitized_uri = self._converter.sanitize_uri(response.final_url)
        logger.info('GET %s: %s', params[COMMAND].upper(), sanitized_uri)
        enom_response = EnomAPIResponse(response)
        enom_response.request = sanitized_uri
        # log at the appropriate level
        if not enom_response:
            logger.error('Response %s: %s', enom_response.text('Command'), enom_response.message)
            # dump the xml response to the error log
            error_logger.error('Response body: %s', enom_response)
            error = EnomAPIError(enom_response.message)
            error.response = enom_response
            raise error
        logger.info('Response %s: %s', enom_response.text('Command'), enom_response.message)
        logger.debug('Response body: %s', enom_response)
        return enom_response

    def _get(self, command, sld, tld, **kwargs):
        parameters = {COMMAND: command, SLD: sld, TLD: tld}
        parameters.update(kwargs)
        return self._get_response(parameters)

    def to_domain(self, sld, tld):
        return sld + '.' + tld

    def to_sld_tld(self, domain):
        # if there is no dot, treat the domain as the sld
        if not '.' in domain:
            return domain, None
        # split on the first dot. Will handle xyz.co.uk correctly.
        return domain.split('.', 1)

    def suggestions(self, search_term, tld):
        """
        Checks for name suggestions based on a search term
        """
        parameters = {COMMAND: 'GetNameSuggestions', 'SearchTerm': search_term, 'MaxResults': 200}
        response = self._get_response(parameters)
        suggestions = []
        if response:
            for suggestion in response.find('DomainSuggestions/Domain', True):
                # TODO: Add a threshold score?
                if not tld or suggestion.attrib.get('tld') == tld:
                    suggestions.append(suggestion.text)
        response.suggestions = suggestions
        return response

    def is_available_domain_list(self, domains):
        """
        Checks if multiple domains are available
          domains is a list of domains to check
        """
        parameters = {COMMAND: 'Check', 'DomainList': ','.join(domains)}
        response = self._get_response(parameters)
        domain_count = int(response.text('DomainCount'))
        domain_availability = []
        for domain_index in range(1, domain_count + 1):
            index = str(domain_index)
            domain = {
                'domain': response.text('Domain'+index),
                'available': response.text('RRPCode'+index) == '210',
                'message': response.text('RRPText'+index),
                'is_premium': response.text('IsPremiumName'+index) == 'true'}
            domain_availability.append(domain)
        response.domain_availability = domain_availability
        return response

    def is_available(self, sld, tld):
        """
        Checks if a domain is available for purchase
        """
        parameters = {COMMAND: 'Check', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        rrp_code = response.rrp_code
        rrp_text = response.rrp_text
        response.is_premium = response.text('IsPremiumName') == 'true'
        response.price = response.text('PremiumPrice')
        response.registration_fee = response.text('RegistrationFee')
        if rrp_code == '210':
            response.message = rrp_text
        elif rrp_code == '211':
            response.success = False
            response.message = rrp_text
        else:
            response.success = False
            domain = response[DOMAIN_NAME]
            response.message = 'Domain "{domain}" not available: {text} ({code})'.format(
                domain=domain, code=rrp_code, text=rrp_text)
        return response

    def get_price(self, sld, tld, price_type):
        """
        Get the price for a domain (response.price)
        Valid price_type values are: register, renew, transfer
        Returns:
            response.is_premium
            response.price
            response.registration_fee
        """
        if not self._converter.is_valid_price_type(price_type):
            raise EnomAPIError('Price type "%s" not supported. Supported values: %s' % (price_type, ','.join(self._converter.ALLOWED_PRICE_TYPES)) )
        product_type = self._converter.get_product_type(price_type, True)
        parameters = {COMMAND: 'PE_GetPremiumPricing', PRODUCT_TYPE: product_type, SLD1: sld, TLD1: tld}
        response = self._get_response(parameters)
        is_premium = response.text('results/result/IsPremium').lower() == 'true'
        if is_premium:
            response.is_premium = is_premium
            response.price = response.text('results/result/Price')
            response.registration_fee = response.text('results/result/RegistrationFee')
            # response.message = response.rrp_text
        else:
            # retrieve registration fee for non-premium domains
            product_type = self._converter.get_product_type(price_type, False)
            parameters = {COMMAND: 'PE_GetRetailPrice', TLD: tld, 'ProductType': product_type}
            response = self._get_response(parameters)
            # populate response
            response.is_premium = False
            price = response.text('productprice/price')
            response.price = price
            response.registration_fee = price
        return response

    def renew(self, sld, tld, price=None):
        """
        Renews the domain. If it is a premium domain, a price must be supplied.
        Returns more information about the renewal in
            response.expiration_date
            response.order_ref
        """
        parameters = {COMMAND: 'Extend', SLD: sld, TLD: tld}
        domain = self.to_domain(sld, tld)
        if price:
            parameters['CustomerSuppliedPrice'] = str(price)
        response = self._get_response(parameters)
        response.expiration_date = response.text('DomainInfo/RegistryExpDate')
        response.order_ref = response.text('OrderID')
        if response.rrp_code == '200':
            response.success = True
            response.message = 'Domain "{domain}" was successfully renewed'.format(domain=domain)
        else:
            response.success = False
            response.message = 'Domain "{domain}" not be renewed: {text} ({code})'.format(
                domain=domain, code=response.rrp_code, text=response.rrp_text)
        return response

    def register(self, sld, tld, contacts_json=None, price=None):
        """
        Purchases and registers the domain. If it is a premium domain, a price must be supplied.
        Returns more information about the purchase in response.purchase
        """
        parameters = {COMMAND: 'Purchase', SLD: sld, TLD: tld, USE_DNS: 'default', NumYears: 1}
        domain = self.to_domain(sld, tld)
        if price:
            parameters['CustomerSuppliedPrice'] = str(price)
        if contacts_json:
            parameters.update(self._converter.get_contacts_as_enom_parameters(contacts_json))
        response = self._get_response(parameters)
        response.total_amount = response.text('TotalCharged')
        response.order_ref = response.text('OrderID')
        if response.rrp_code == '200':
            response.success = True
            response.message = 'Domain "{domain}" was successfully purchased'.format(domain=domain)
        else:
            # error: Domain not available
            # error: price is wrong (does not match)
            response.success = False
            response.message = 'Domain "{domain}" not purchased: {text} ({code})'.format(
                domain=domain, code=response.rrp_code, text=response.rrp_text)
        renew = self.set_auto_renew(sld, tld, False)
        return response

    def delete(self, sld, tld):
        """
        Deletes a domain registration
        Only allowed for test mode right now. Can be used for for unit testing
        NOTE: Deleting a domain is not always possible, for various reasons, example:
              "This domain's TLD is not allowed to be deleted." (.menu)
              Deletion needs to be done asap after creation, and prior to moving it to a subaccount
        """
        if not self.test:
            # For now, raise EnomAPIError()
            # TODO: return EnomAPIResponse?
            raise EnomAPIError('Delete Domain not implemented')
        parameters = {COMMAND: 'DeleteRegistration', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.message = response.rrp_text
        response.success = response.rrp_code == '200'
        return response

    def get_domain(self, sld, tld):
        """
        Get status information about the domain (response.status)
        Get verification information about the domain (response.verification)
        Raises EnomAPIError if the domain does not exist
        """
        parameters = {COMMAND: 'GetDomainInfo', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.status = self._converter.get_xml_as_json(response.find('GetDomainInfo/status'))
        response.verification = self._converter.get_xml_as_json(response.find('GetDomainInfo/services/entry/raasetting'))
        return response

    def get_account(self):
        """
        Get account information
        """
        parameters = {COMMAND: 'GetAccountInfo'}
        response = self._get_response(parameters)
        response.account_id = response.text('GetAccountInfo/Account')
        return response

    def get_subaccount(self, account_id):
        """
        Get subaccount information
        The contact information is returned as a contact dict in response.contact
        If the subaccount does not exist response.success is False
        """
        parameters = {COMMAND: 'GetSubaccountDetails', 'Account': account_id, 'Action': 'Info'}
        response = self._get_response(parameters)
        if response.find('SubAccountsManage/SubAccount') is None:
            response.success = False
            response.message = 'Could not find subaccount "{0}"'.format(account_id)
            response.contact = {}
        else:
            response.contact = self._converter.get_subaccount_contact_as_json(response)
        return response

    def add_subaccount(self, username, password, contacts_json):
        """
        Create a new subaccount that is accessed by the supplied username and password
        The registrant information from the contacts_json parameters is used as account owner
        The account_id for the new account is returned as response.account_id
        Error condition: (raises EnomAPIError): duplicate username (account already exists)
        """
        parameters = {COMMAND: 'CreateSubAccount', 'NewUID': username, 'NewPW': password, 'ConfirmPW': password}
        default_contact = self._converter.get_default_contact(contacts_json)
        parameters.update(self._converter.get_registrant(default_contact))
        response = self._get_response(parameters)
        response.account_id = response.text('NewAccount/Account')
        # TODO: This is the same contact that the user supplied, NOT what Enom has in its database
        response.contact = default_contact
        return response

    def update_subaccount(self, username, new_password, contacts_json):
        """
        Updates an existing subaccount with new password and new registrar contact information
        The account_id for the account is returned as response.account_id
        The contact for the account is returned as response.contact
        """
        parameters = {COMMAND: 'UpdateAccountInfo',
                      'NewUID': username,
                      'NewPW': new_password,
                      'ConfirmNewPW': new_password,
                      'AuthQuestionType': 'sbirth',
                      'AuthQuestionAnswer': 'nothere'}
        default_contact = self._converter.get_default_contact(contacts_json)
        parameters.update(self._converter.get_registrant(default_contact))
        response = self._get_response(parameters)
        response.account_id = response.text('GetAccountInfo/Account')
        response.contact = self._converter.get_contact_as_json(response,
                                                               None,
                                                               EnomEntityConverter.MAPPING_JSON_TO_ENOM_SUBACCOUNT_UPD)
        return response

    def move_domain_to_subaccount(self, sld, tld, username='******'):
        """
        Move a domain from its current account to the specified subaccount (username)
        If no username is specified, it moves it to the main account (Jetlaunch)
        """
        parameters = {COMMAND: 'PushDomain', SLD: sld, TLD: tld, 'AccountID': username, 'SendEmail':'No'}
        response = self._get_response(parameters)
        return response

    def get_subaccount_domains(self, account_id):
        """
        Get a list of all domains in this subaccount: response.domains
        """
        parameters = {COMMAND: 'SubAccountDomains', 'Account': account_id, 'Tab': 'iown'}
        response = self._get_response(parameters)
        response.domains = []
        for domain in response.find('GetDomains/domain-list/domain', True):
            sld = domain.find('sld').text
            tld = domain.find('tld').text
            response.domains.append(self.to_domain(sld, tld))
        return response

    def soft_delete_subaccount(self, account_id):
        """
        Soft delete the subdomain with the specified account_id
        All domains in the subaccount are set to not auto renew,
        and a 'status' flag on the domain is set to 'deleted'
        and a 'status' flag on the subaccount is set to 'deleted'
        """
        domains_response = self.get_subaccount_domains(account_id)
        for domain in domains_response.domains:
            self.soft_delete_domain(domain)
        return domains_response

    def soft_delete_domain(self, domain):
        """
        Soft delete the domain
        Set auto renew to False and set status=deleted on the domain
        """
        sld, tld = self.to_sld_tld(domain)
        # turn off auto renew
        renew = self.set_auto_renew(sld, tld, False)
        set_status = self.set_custom_domain_data(sld, tld, 'status', 'deleted')
        logger.info('Soft deleted %s', domain)
        return set_status

    def set_host_records(self, sld, tld, host_records):
        """
        Set the host records for a domain
        sld, tld: The host and domain name (host.sld.tld) that you want to update in the DNS. For example, www.resellerdocs.com
        host_records is a list of dictionaries with these keys:
            name: subdomain (e.g. www, *, ...)
            type: The record type (A, AAAA, CNET etc.)
            ip: The IP address to set the host record to.
        """
        if host_records is None or len(host_records) == 0:
            raise EnomAPIError('Cannot set host records: Missing hosts')
        parameters = {COMMAND: 'SetHosts', SLD: sld, TLD: tld}
        for index, host_record in enumerate(host_records, 1):
            host_type = host_record.get('type')
            host_address = host_record.get('address')
            # append dot if domain
            if host_type.lower() == 'url':
                scheme = urlparse(host_address).scheme
                if not scheme:
                    # scheme empty for domain and ip. Its http or https.
                    try:
                        isinstance(host_address.split('.')[0], int)
                    except ValueError:
                        # its domain
                        if not host_address.endswith('.'):
                            host_address = host_address + '.'
                else:
                    pass
            parameters.update({'RecordType' + str(index): host_type,
                               'HostName' + str(index): host_record.get('name'),
                               'Address' + str(index): host_address})
        response = self._get_response(parameters)
        response.message = 'Successfully set {count} host record(s) for "{sld}.{tld}"'.format(count=len(host_records),
                                                                                              sld=sld, tld=tld)
        return response

    def get_host_records(self, sld, tld):
        """
        Get the host records for a domain
        """
        parameters = {COMMAND: 'GetHosts', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        host_records = self._converter.get_host_records_as_json(response)
        response.host_records = host_records
        return response

    def set_name_servers(self, sld, tld, name_servers=[], default=False):
        """
        Set the name servers for a domain
          default = True sets it to eNoms name servers
          name_servers = None or [] sets it to no name server
          name_servers = ['ns1.nameserver.com', ...]  sets it to the user specified name servers
        """
        parameters = {COMMAND: 'ModifyNS', SLD: sld, TLD: tld}
        if default:
            parameters['UseDNS'] = 'Default'
        if not name_servers:
            parameters['NS1'] = None
        else:
            for index, name_server in enumerate(name_servers, start=1):
                parameters['NS'+str(index)] = name_server
        response = self._get_response(parameters)
        if response.rrp_code == '200':
            ns = 'default' if default else name_servers
            response.message = 'Successfully set "{sld}.{tld}" name server to {ns}'.format(sld=sld, tld=tld, ns=ns)
        return response

    def get_name_servers(self, sld, tld):
        """
        Get the name servers for a domain (response.name_servers)
        """
        parameters = {COMMAND: 'GetDNS', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        if response.text('RRPCodeGDNS') != '200':
            response.message = response.rrp_text
            response.success = False
            response.name_servers = []
            return response

        response.default = response.text('UseDNS') == 'default'
        dns = response.find('dns', True)
        if dns is None:
            response.name_servers = []
        else:
            name_servers = []
            for name_server in dns:
                name_servers.append(name_server.text)
            response.name_servers = name_servers
        return response

    def get_contacts(self, sld, tld):
        """
        Retrieve all contacts (response.contacts)
        """
        parameters = {COMMAND: 'GetContacts', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        json = self._converter.get_contacts_as_json(response)
        # remove the account billing address
        json.pop('account_billing', None)
        response.contacts = json
        return response

    def set_contacts(self, sld, tld, contacts_json):
        """
        Save contact information for registrant, admin, tech and aux billing in enom
        """
        # FOR NOW - DO NOT ALLOW UPDATING MAIN ACCOUNT BILLING ADDRESS
        # THERE IS ONLY ONE FOR ALL DOMAINS, IT USES 'UpdateAccountInfo'
        contacts_json.pop('account_billing', None)
        try:
            self._converter.validate_contacts(contacts_json)
        except ValueError as v:
            raise EnomAPIError(v.message)
        # billing (AuxBilling) is the billing contact at a domain level
        parameters = {COMMAND: 'Contacts', SLD: sld, TLD: tld}
        enom_parameters = self._converter.get_contacts_as_enom_parameters(contacts_json)
        parameters.update(enom_parameters)
        response = self._get_response(parameters)
        return response

    def set_custom_domain_data(self, sld, tld, key, value, entered_by=None):
        """
        Associate a key-value pair to the domain (sld, tld)
        entered_by is the person who set it - TBD
        """
        parameters = {COMMAND: 'SetCustomerDefinedData', 'ObjectID': '1', SLD: sld, TLD: tld,
                      'Type': '2', 'DisplayFlag': '0', 'EnteredBy': entered_by, 'Key': key, 'Value': value}
        response = self._get_response(parameters)
        return response

    def get_custom_domain_data(self, sld, tld, key):
        """
        Get the key-value pair associated
        with this domain (response.custom_data)
        """
        parameters = {COMMAND: 'GetCustomerDefinedData', 'ObjectID': '1', SLD: sld, TLD: tld,
                      'Type': '2', 'Key': key}
        response = self._get_response(parameters)
        if response:
            data = self._converter.get_domain_data_as_json(response)
            data['key'] = key
            if data['updated_at'] is not None:
                response.custom_data = data
            else:
                # return an empty dictionary if the key entry does not exist
                response.custom_data = {}
        return response

    def set_custom_account_data(self, key, value, entered_by=None):
        """
        Associate a key-value pair to the account
        entered_by is the person who set it - TBD
        """
        parameters = {COMMAND: 'SetCustomerDefinedData', 'ObjectID': '1',
                      'Type': '1', 'DisplayFlag': '0', 'EnteredBy': entered_by,
                      'Key': key, 'Value': value}
        response = self._get_response(parameters)
        return response

    def get_custom_account_data(self, key):
        """
        Get the key-value pair associated
        with this account (response.custom_data):
            response.custom_data['key']
            response.custom_data['value']
            response.custom_data['updated_at']
            response.custom_data['entered_by']

        Returns an empty dictionary if the key entry does not exist
        """
        parameters = {COMMAND: 'GetCustomerDefinedData', 'ObjectID': '1',
                      'Type': '1', 'Key': key}
        response = self._get_response(parameters)
        if response:
            data = self._converter.get_domain_data_as_json(response)
            data['key'] = key
            if data['updated_at'] is not None:
                response.custom_data = data
            else:
                # return an empty dictionary if the key entry does not exist
                response.custom_data = {}
        return response

    def set_auto_renew(self, sld, tld, auto_renew):
        """
        Set flag for domain auto-renewal; True if set for auto renewal, False otherwise
        response.auto_renew contains the latest flag value
        """
        parameters = {COMMAND: 'SetRenew', SLD: sld, TLD: tld, 'RenewFlag': int(bool(auto_renew))}
        response = self._get_response(parameters)
        response.success = bool(response.text('Success'))
        response.auto_renew = response.text('RenewName') == 'True'
        return response

    def get_auto_renew(self, sld, tld):
        """
        Get flag for domain auto-renewal
        response.auto_renew is True if set for auto-renewal, False otherwise.
        """
        parameters = {COMMAND: 'GetRenew', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.auto_renew = bool(int(response.text('auto-renew')))
        return response

    def set_lock(self, sld, tld, lock):
        """
        Set flag for domain lock; set lock = True if protected from being transferred to another registrar, False otherwise
        response.locked contains the latest flag value
        """
        # if lock=None is passed in (it shouldn't), it is interpreted as False
        parameters = {COMMAND: 'SetRegLock', SLD: sld, TLD: tld, 'UnlockRegistrar': int(not lock)}
        try:
            response = self._get_response(parameters)
        except EnomAPIError as e:
            response = e.response
            # 541 means that we tried to unlock when it is already unlocked (reg-lock='ACTIVE')
            if response.rrp_code != '541':
                raise e
        response.success = True
        response.locked = response.text('reg-lock') == 'REGISTRAR-LOCK'
        return response

    def get_lock(self, sld, tld):
        """
        Get flag for domain lock;
        response.locked is True if the domain is protected from being transferred to another registrar, False otherwise
        """
        parameters = {COMMAND: 'GetRegLock', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.locked = bool(int(response.text('reg-lock')))
        return response

    def synch_auth_info(self, sld, tld, email_epp=True, run_synch_auto_info=True):
        """
        Synch auth information for a domain
        response.done is True if the Synch is completed.
        """
        parameters = {COMMAND: 'SynchAuthInfo',
                      SLD: sld,
                      TLD: tld,
                      EmailEPP: email_epp,
                      RunSynchAutoInfo: run_synch_auto_info
                      }
        response = self._get_response(parameters)
        response.done = response.text('Done') == 'true'
        response.message = "Synchronized and mailed EPP successfully."
        return response

    def get_domain_status(self, sld, tld):
        """
        To get the status of domains.
        """
        parameters = {COMMAND: 'statusdomain', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.error_count = int(response.text('ErrCount'))
        if response.error_count:
            response.errors = self._converter.get_xml_as_json(response.find('errors'))
        response.rrp_code = int(response.text('RRPCode'))
        response.in_account = int(response.text('DomainStatus/InAccount')) == 1
        response.done = response.text('Done') == 'true'
        return response
Ejemplo n.º 6
0
class EnomDomainAPIClient():
    """
    Wraps core Commands from the Enom API
    All methods returns an instance of EnomAPIResponse or raises an EnomAPIError

    Please note that it is NOT possible to use subaccount credentials when creating an instance.
    The reason is that Enom does not permit a retail subaccount to use the API. This error will result:
      "User not permitted from this IP address - 4"

    This error will also occur when your external IP address has not been whitelisted by Enom.
    The solution is to contact Enom support (or dev Ron West) and add your public IP.
    Here is an example for the UPDATEACCOUNTINFO command:

        <StatusEditContact>Successful</StatusEditContact>
        <StatusDetailEditContact>Successfully updated</StatusDetailEditContact>
        <PendingVerification />
        <Command>UPDATEACCOUNTINFO</Command>
        <APIType>API</APIType>
        <Language>eng</Language>
        <ErrCount>2</ErrCount>
        <errors>
        <Err1>User not permitted from this IP address - 4</Err1>
        <Err2>Account not found in database.</Err2>
        ...
    """
    def __init__(self, test=True, user='******', password='******'):
        self.test = test
        if self.test:
            self.server = 'resellertest.enom.com'
        else:
            self.server = 'reseller.enom.com'
        self._user = user
        self._password = password
        self._base_uri = 'https://{server}/interface.asp'.format(
            server=self.server)
        self._base_parameters = {
            'UID': self._user,
            'PW': self._password,
            'ResponseType': 'XML'
        }
        self._resource = Resource(self._base_uri)
        self._converter = EnomEntityConverter()

    def _all_params(self, params):
        # create a new dictionary so we do not expose the credentials (in self.base_parameters) in "params"
        all_params = dict(params)
        all_params.update(self._base_parameters)
        return all_params

    def _get_response(self, params):
        response = self._resource.get(params_dict=self._all_params(params))
        sanitized_uri = self._converter.sanitize_uri(response.final_url)
        logger.info('GET %s: %s', params[COMMAND].upper(), sanitized_uri)
        enom_response = EnomAPIResponse(response)
        enom_response.request = sanitized_uri
        # log at the appropriate level
        if not enom_response:
            logger.error('Response %s: %s', enom_response.text('Command'),
                         enom_response.message)
            # dump the xml response to the error log
            error_logger.error('Response body: %s', enom_response)
            error = EnomAPIError(enom_response.message)
            error.response = enom_response
            raise error
        logger.info('Response %s: %s', enom_response.text('Command'),
                    enom_response.message)
        logger.debug('Response body: %s', enom_response)
        return enom_response

    def _get(self, command, sld, tld, **kwargs):
        parameters = {COMMAND: command, SLD: sld, TLD: tld}
        parameters.update(kwargs)
        return self._get_response(parameters)

    def to_domain(self, sld, tld):
        return sld + '.' + tld

    def to_sld_tld(self, domain):
        # if there is no dot, treat the domain as the sld
        if not '.' in domain:
            return domain, None
        # split on the first dot. Will handle xyz.co.uk correctly.
        return domain.split('.', 1)

    def suggestions(self, search_term, tld):
        """
        Checks for name suggestions based on a search term
        """
        parameters = {
            COMMAND: 'GetNameSuggestions',
            'SearchTerm': search_term,
            'MaxResults': 200
        }
        response = self._get_response(parameters)
        suggestions = []
        if response:
            for suggestion in response.find('DomainSuggestions/Domain', True):
                # TODO: Add a threshold score?
                if not tld or suggestion.attrib.get('tld') == tld:
                    suggestions.append(suggestion.text)
        response.suggestions = suggestions
        return response

    def is_available_domain_list(self, domains):
        """
        Checks if multiple domains are available
          domains is a list of domains to check
        """
        parameters = {COMMAND: 'Check', 'DomainList': ','.join(domains)}
        response = self._get_response(parameters)
        domain_count = int(response.text('DomainCount'))
        domain_availability = []
        for domain_index in range(1, domain_count + 1):
            index = str(domain_index)
            domain = {
                'domain': response.text('Domain' + index),
                'available': response.text('RRPCode' + index) == '210',
                'message': response.text('RRPText' + index),
                'is_premium': response.text('IsPremiumName' + index) == 'true'
            }
            domain_availability.append(domain)
        response.domain_availability = domain_availability
        return response

    def is_available(self, sld, tld):
        """
        Checks if a domain is available for purchase
        """
        parameters = {COMMAND: 'Check', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        rrp_code = response.rrp_code
        rrp_text = response.rrp_text
        response.is_premium = response.text('IsPremiumName') == 'true'
        response.price = response.text('PremiumPrice')
        response.registration_fee = response.text('RegistrationFee')
        if rrp_code == '210':
            response.message = rrp_text
        elif rrp_code == '211':
            response.success = False
            response.message = rrp_text
        else:
            response.success = False
            domain = response[DOMAIN_NAME]
            response.message = 'Domain "{domain}" not available: {text} ({code})'.format(
                domain=domain, code=rrp_code, text=rrp_text)
        return response

    def get_price(self, sld, tld, price_type):
        """
        Get the price for a domain (response.price)
        Valid price_type values are: register, renew, transfer
        Returns:
            response.is_premium
            response.price
            response.registration_fee
        """
        if not self._converter.is_valid_price_type(price_type):
            raise EnomAPIError(
                'Price type "%s" not supported. Supported values: %s' %
                (price_type, ','.join(self._converter.ALLOWED_PRICE_TYPES)))
        product_type = self._converter.get_product_type(price_type, True)
        parameters = {
            COMMAND: 'PE_GetPremiumPricing',
            PRODUCT_TYPE: product_type,
            SLD1: sld,
            TLD1: tld
        }
        response = self._get_response(parameters)
        is_premium = response.text(
            'results/result/IsPremium').lower() == 'true'
        if is_premium:
            response.is_premium = is_premium
            response.price = response.text('results/result/Price')
            response.registration_fee = response.text(
                'results/result/RegistrationFee')
            # response.message = response.rrp_text
        else:
            # retrieve registration fee for non-premium domains
            product_type = self._converter.get_product_type(price_type, False)
            parameters = {
                COMMAND: 'PE_GetRetailPrice',
                TLD: tld,
                'ProductType': product_type
            }
            response = self._get_response(parameters)
            # populate response
            response.is_premium = False
            price = response.text('productprice/price')
            response.price = price
            response.registration_fee = price
        return response

    def renew(self, sld, tld, price=None):
        """
        Renews the domain. If it is a premium domain, a price must be supplied.
        Returns more information about the renewal in
            response.expiration_date
            response.order_ref
        """
        parameters = {COMMAND: 'Extend', SLD: sld, TLD: tld}
        domain = self.to_domain(sld, tld)
        if price:
            parameters['CustomerSuppliedPrice'] = str(price)
        response = self._get_response(parameters)
        response.expiration_date = response.text('DomainInfo/RegistryExpDate')
        response.order_ref = response.text('OrderID')
        if response.rrp_code == '200':
            response.success = True
            response.message = 'Domain "{domain}" was successfully renewed'.format(
                domain=domain)
        else:
            response.success = False
            response.message = 'Domain "{domain}" not be renewed: {text} ({code})'.format(
                domain=domain, code=response.rrp_code, text=response.rrp_text)
        return response

    def register(self, sld, tld, contacts_json=None, price=None):
        """
        Purchases and registers the domain. If it is a premium domain, a price must be supplied.
        Returns more information about the purchase in response.purchase
        """
        parameters = {
            COMMAND: 'Purchase',
            SLD: sld,
            TLD: tld,
            USE_DNS: 'default',
            NumYears: 1
        }
        domain = self.to_domain(sld, tld)
        if price:
            parameters['CustomerSuppliedPrice'] = str(price)
        if contacts_json:
            parameters.update(
                self._converter.get_contacts_as_enom_parameters(contacts_json))
        response = self._get_response(parameters)
        response.total_amount = response.text('TotalCharged')
        response.order_ref = response.text('OrderID')
        if response.rrp_code == '200':
            response.success = True
            response.message = 'Domain "{domain}" was successfully purchased'.format(
                domain=domain)
        else:
            # error: Domain not available
            # error: price is wrong (does not match)
            response.success = False
            response.message = 'Domain "{domain}" not purchased: {text} ({code})'.format(
                domain=domain, code=response.rrp_code, text=response.rrp_text)
        renew = self.set_auto_renew(sld, tld, False)
        return response

    def delete(self, sld, tld):
        """
        Deletes a domain registration
        Only allowed for test mode right now. Can be used for for unit testing
        NOTE: Deleting a domain is not always possible, for various reasons, example:
              "This domain's TLD is not allowed to be deleted." (.menu)
              Deletion needs to be done asap after creation, and prior to moving it to a subaccount
        """
        if not self.test:
            # For now, raise EnomAPIError()
            # TODO: return EnomAPIResponse?
            raise EnomAPIError('Delete Domain not implemented')
        parameters = {COMMAND: 'DeleteRegistration', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.message = response.rrp_text
        response.success = response.rrp_code == '200'
        return response

    def get_domain(self, sld, tld):
        """
        Get status information about the domain (response.status)
        Get verification information about the domain (response.verification)
        Raises EnomAPIError if the domain does not exist
        """
        parameters = {COMMAND: 'GetDomainInfo', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.status = self._converter.get_xml_as_json(
            response.find('GetDomainInfo/status'))
        response.verification = self._converter.get_xml_as_json(
            response.find('GetDomainInfo/services/entry/raasetting'))
        return response

    def get_account(self):
        """
        Get account information
        """
        parameters = {COMMAND: 'GetAccountInfo'}
        response = self._get_response(parameters)
        response.account_id = response.text('GetAccountInfo/Account')
        return response

    def get_subaccount(self, account_id):
        """
        Get subaccount information
        The contact information is returned as a contact dict in response.contact
        If the subaccount does not exist response.success is False
        """
        parameters = {
            COMMAND: 'GetSubaccountDetails',
            'Account': account_id,
            'Action': 'Info'
        }
        response = self._get_response(parameters)
        if response.find('SubAccountsManage/SubAccount') is None:
            response.success = False
            response.message = 'Could not find subaccount "{0}"'.format(
                account_id)
            response.contact = {}
        else:
            response.contact = self._converter.get_subaccount_contact_as_json(
                response)
        return response

    def add_subaccount(self, username, password, contacts_json):
        """
        Create a new subaccount that is accessed by the supplied username and password
        The registrant information from the contacts_json parameters is used as account owner
        The account_id for the new account is returned as response.account_id
        Error condition: (raises EnomAPIError): duplicate username (account already exists)
        """
        parameters = {
            COMMAND: 'CreateSubAccount',
            'NewUID': username,
            'NewPW': password,
            'ConfirmPW': password
        }
        default_contact = self._converter.get_default_contact(contacts_json)
        parameters.update(self._converter.get_registrant(default_contact))
        response = self._get_response(parameters)
        response.account_id = response.text('NewAccount/Account')
        # TODO: This is the same contact that the user supplied, NOT what Enom has in its database
        response.contact = default_contact
        return response

    def update_subaccount(self, username, new_password, contacts_json):
        """
        Updates an existing subaccount with new password and new registrar contact information
        The account_id for the account is returned as response.account_id
        The contact for the account is returned as response.contact
        """
        parameters = {
            COMMAND: 'UpdateAccountInfo',
            'NewUID': username,
            'NewPW': new_password,
            'ConfirmNewPW': new_password,
            'AuthQuestionType': 'sbirth',
            'AuthQuestionAnswer': 'nothere'
        }
        default_contact = self._converter.get_default_contact(contacts_json)
        parameters.update(self._converter.get_registrant(default_contact))
        response = self._get_response(parameters)
        response.account_id = response.text('GetAccountInfo/Account')
        response.contact = self._converter.get_contact_as_json(
            response, None,
            EnomEntityConverter.MAPPING_JSON_TO_ENOM_SUBACCOUNT_UPD)
        return response

    def move_domain_to_subaccount(self, sld, tld, username='******'):
        """
        Move a domain from its current account to the specified subaccount (username)
        If no username is specified, it moves it to the main account (Jetlaunch)
        """
        parameters = {
            COMMAND: 'PushDomain',
            SLD: sld,
            TLD: tld,
            'AccountID': username,
            'SendEmail': 'No'
        }
        response = self._get_response(parameters)
        return response

    def get_subaccount_domains(self, account_id):
        """
        Get a list of all domains in this subaccount: response.domains
        """
        parameters = {
            COMMAND: 'SubAccountDomains',
            'Account': account_id,
            'Tab': 'iown'
        }
        response = self._get_response(parameters)
        response.domains = []
        for domain in response.find('GetDomains/domain-list/domain', True):
            sld = domain.find('sld').text
            tld = domain.find('tld').text
            response.domains.append(self.to_domain(sld, tld))
        return response

    def soft_delete_subaccount(self, account_id):
        """
        Soft delete the subdomain with the specified account_id
        All domains in the subaccount are set to not auto renew,
        and a 'status' flag on the domain is set to 'deleted'
        and a 'status' flag on the subaccount is set to 'deleted'
        """
        domains_response = self.get_subaccount_domains(account_id)
        for domain in domains_response.domains:
            self.soft_delete_domain(domain)
        return domains_response

    def soft_delete_domain(self, domain):
        """
        Soft delete the domain
        Set auto renew to False and set status=deleted on the domain
        """
        sld, tld = self.to_sld_tld(domain)
        # turn off auto renew
        renew = self.set_auto_renew(sld, tld, False)
        set_status = self.set_custom_domain_data(sld, tld, 'status', 'deleted')
        logger.info('Soft deleted %s', domain)
        return set_status

    def set_host_records(self, sld, tld, host_records):
        """
        Set the host records for a domain
        sld, tld: The host and domain name (host.sld.tld) that you want to update in the DNS. For example, www.resellerdocs.com
        host_records is a list of dictionaries with these keys:
            name: subdomain (e.g. www, *, ...)
            type: The record type (A, AAAA, CNET etc.)
            ip: The IP address to set the host record to.
        """
        if host_records is None or len(host_records) == 0:
            raise EnomAPIError('Cannot set host records: Missing hosts')
        parameters = {COMMAND: 'SetHosts', SLD: sld, TLD: tld}
        for index, host_record in enumerate(host_records, 1):
            host_type = host_record.get('type')
            host_address = host_record.get('address')
            # append dot if domain
            if host_type.lower() == 'url':
                scheme = urlparse(host_address).scheme
                if not scheme:
                    # scheme empty for domain and ip. Its http or https.
                    try:
                        isinstance(host_address.split('.')[0], int)
                    except ValueError:
                        # its domain
                        if not host_address.endswith('.'):
                            host_address = host_address + '.'
                else:
                    pass
            parameters.update({
                'RecordType' + str(index): host_type,
                'HostName' + str(index): host_record.get('name'),
                'Address' + str(index): host_address
            })
        response = self._get_response(parameters)
        response.message = 'Successfully set {count} host record(s) for "{sld}.{tld}"'.format(
            count=len(host_records), sld=sld, tld=tld)
        return response

    def get_host_records(self, sld, tld):
        """
        Get the host records for a domain
        """
        parameters = {COMMAND: 'GetHosts', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        host_records = self._converter.get_host_records_as_json(response)
        response.host_records = host_records
        return response

    def set_name_servers(self, sld, tld, name_servers=[], default=False):
        """
        Set the name servers for a domain
          default = True sets it to eNoms name servers
          name_servers = None or [] sets it to no name server
          name_servers = ['ns1.nameserver.com', ...]  sets it to the user specified name servers
        """
        parameters = {COMMAND: 'ModifyNS', SLD: sld, TLD: tld}
        if default:
            parameters['UseDNS'] = 'Default'
        if not name_servers:
            parameters['NS1'] = None
        else:
            for index, name_server in enumerate(name_servers, start=1):
                parameters['NS' + str(index)] = name_server
        response = self._get_response(parameters)
        if response.rrp_code == '200':
            ns = 'default' if default else name_servers
            response.message = 'Successfully set "{sld}.{tld}" name server to {ns}'.format(
                sld=sld, tld=tld, ns=ns)
        return response

    def get_name_servers(self, sld, tld):
        """
        Get the name servers for a domain (response.name_servers)
        """
        parameters = {COMMAND: 'GetDNS', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        if response.text('RRPCodeGDNS') != '200':
            response.message = response.rrp_text
            response.success = False
            response.name_servers = []
            return response

        response.default = response.text('UseDNS') == 'default'
        dns = response.find('dns', True)
        if dns is None:
            response.name_servers = []
        else:
            name_servers = []
            for name_server in dns:
                name_servers.append(name_server.text)
            response.name_servers = name_servers
        return response

    def get_contacts(self, sld, tld):
        """
        Retrieve all contacts (response.contacts)
        """
        parameters = {COMMAND: 'GetContacts', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        json = self._converter.get_contacts_as_json(response)
        # remove the account billing address
        json.pop('account_billing', None)
        response.contacts = json
        return response

    def set_contacts(self, sld, tld, contacts_json):
        """
        Save contact information for registrant, admin, tech and aux billing in enom
        """
        # FOR NOW - DO NOT ALLOW UPDATING MAIN ACCOUNT BILLING ADDRESS
        # THERE IS ONLY ONE FOR ALL DOMAINS, IT USES 'UpdateAccountInfo'
        contacts_json.pop('account_billing', None)
        try:
            self._converter.validate_contacts(contacts_json)
        except ValueError as v:
            raise EnomAPIError(v.message)
        # billing (AuxBilling) is the billing contact at a domain level
        parameters = {COMMAND: 'Contacts', SLD: sld, TLD: tld}
        enom_parameters = self._converter.get_contacts_as_enom_parameters(
            contacts_json)
        parameters.update(enom_parameters)
        response = self._get_response(parameters)
        return response

    def set_custom_domain_data(self, sld, tld, key, value, entered_by=None):
        """
        Associate a key-value pair to the domain (sld, tld)
        entered_by is the person who set it - TBD
        """
        parameters = {
            COMMAND: 'SetCustomerDefinedData',
            'ObjectID': '1',
            SLD: sld,
            TLD: tld,
            'Type': '2',
            'DisplayFlag': '0',
            'EnteredBy': entered_by,
            'Key': key,
            'Value': value
        }
        response = self._get_response(parameters)
        return response

    def get_custom_domain_data(self, sld, tld, key):
        """
        Get the key-value pair associated
        with this domain (response.custom_data)
        """
        parameters = {
            COMMAND: 'GetCustomerDefinedData',
            'ObjectID': '1',
            SLD: sld,
            TLD: tld,
            'Type': '2',
            'Key': key
        }
        response = self._get_response(parameters)
        if response:
            data = self._converter.get_domain_data_as_json(response)
            data['key'] = key
            if data['updated_at'] is not None:
                response.custom_data = data
            else:
                # return an empty dictionary if the key entry does not exist
                response.custom_data = {}
        return response

    def set_custom_account_data(self, key, value, entered_by=None):
        """
        Associate a key-value pair to the account
        entered_by is the person who set it - TBD
        """
        parameters = {
            COMMAND: 'SetCustomerDefinedData',
            'ObjectID': '1',
            'Type': '1',
            'DisplayFlag': '0',
            'EnteredBy': entered_by,
            'Key': key,
            'Value': value
        }
        response = self._get_response(parameters)
        return response

    def get_custom_account_data(self, key):
        """
        Get the key-value pair associated
        with this account (response.custom_data):
            response.custom_data['key']
            response.custom_data['value']
            response.custom_data['updated_at']
            response.custom_data['entered_by']

        Returns an empty dictionary if the key entry does not exist
        """
        parameters = {
            COMMAND: 'GetCustomerDefinedData',
            'ObjectID': '1',
            'Type': '1',
            'Key': key
        }
        response = self._get_response(parameters)
        if response:
            data = self._converter.get_domain_data_as_json(response)
            data['key'] = key
            if data['updated_at'] is not None:
                response.custom_data = data
            else:
                # return an empty dictionary if the key entry does not exist
                response.custom_data = {}
        return response

    def set_auto_renew(self, sld, tld, auto_renew):
        """
        Set flag for domain auto-renewal; True if set for auto renewal, False otherwise
        response.auto_renew contains the latest flag value
        """
        parameters = {
            COMMAND: 'SetRenew',
            SLD: sld,
            TLD: tld,
            'RenewFlag': int(bool(auto_renew))
        }
        response = self._get_response(parameters)
        response.success = bool(response.text('Success'))
        response.auto_renew = response.text('RenewName') == 'True'
        return response

    def get_auto_renew(self, sld, tld):
        """
        Get flag for domain auto-renewal
        response.auto_renew is True if set for auto-renewal, False otherwise.
        """
        parameters = {COMMAND: 'GetRenew', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.auto_renew = bool(int(response.text('auto-renew')))
        return response

    def set_lock(self, sld, tld, lock):
        """
        Set flag for domain lock; set lock = True if protected from being transferred to another registrar, False otherwise
        response.locked contains the latest flag value
        """
        # if lock=None is passed in (it shouldn't), it is interpreted as False
        parameters = {
            COMMAND: 'SetRegLock',
            SLD: sld,
            TLD: tld,
            'UnlockRegistrar': int(not lock)
        }
        try:
            response = self._get_response(parameters)
        except EnomAPIError as e:
            response = e.response
            # 541 means that we tried to unlock when it is already unlocked (reg-lock='ACTIVE')
            if response.rrp_code != '541':
                raise e
        response.success = True
        response.locked = response.text('reg-lock') == 'REGISTRAR-LOCK'
        return response

    def get_lock(self, sld, tld):
        """
        Get flag for domain lock;
        response.locked is True if the domain is protected from being transferred to another registrar, False otherwise
        """
        parameters = {COMMAND: 'GetRegLock', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.locked = bool(int(response.text('reg-lock')))
        return response

    def synch_auth_info(self,
                        sld,
                        tld,
                        email_epp=True,
                        run_synch_auto_info=True):
        """
        Synch auth information for a domain
        response.done is True if the Synch is completed.
        """
        parameters = {
            COMMAND: 'SynchAuthInfo',
            SLD: sld,
            TLD: tld,
            EmailEPP: email_epp,
            RunSynchAutoInfo: run_synch_auto_info
        }
        response = self._get_response(parameters)
        response.done = response.text('Done') == 'true'
        response.message = "Synchronized and mailed EPP successfully."
        return response

    def get_domain_status(self, sld, tld):
        """
        To get the status of domains.
        """
        parameters = {COMMAND: 'statusdomain', SLD: sld, TLD: tld}
        response = self._get_response(parameters)
        response.error_count = int(response.text('ErrCount'))
        if response.error_count:
            response.errors = self._converter.get_xml_as_json(
                response.find('errors'))
        response.rrp_code = int(response.text('RRPCode'))
        response.in_account = int(response.text('DomainStatus/InAccount')) == 1
        response.done = response.text('Done') == 'true'
        return response