Giter Club home page Giter Club logo

epp-client's Introduction

EPP (Extensible Provisioning Protocol) Library for Domain Registration and Management

Build Documentation

Description

epp-client is a client library written in Rust for Internet domain registration and management for domain registrars.

It supports the following basic Domain, Contact, Host, and Message management calls, with plans to add more calls and other EPP extensions in the future, and to eventually be RFC compliant with the EPP protocol.

  • Domain Check

  • Domain Create

  • Domain Info

  • Domain Update

  • Domain Delete

  • Domain Renew

  • Domain Transfer

  • Contact Check

  • Contact Create

  • Contact Info

  • Contact Update

  • Contact Delete

  • Host Check

  • Host Create

  • Host Info

  • Host Update

  • Host Delete

  • Message Poll

  • Message Ack

  • RGP Restore Request

  • RGP Restore Report

Usage

Just add the following to your project's Cargo.toml

epp-client = "0.4"

Operation

You can create a mut variable of type EppClient with the domain registry config.

use std::net::ToSocketAddrs;
use std::time::Duration;

use epp_client::EppClient;
use epp_client::domain::DomainCheck;
use epp_client::login::Login;

#[tokio::main]
async fn main() {
    // Create an instance of EppClient
    let host = "example.com";
    let addr = (host, 700).to_socket_addrs().unwrap().next().unwrap();
    let timeout = Duration::from_secs(5);
    let mut client = match EppClient::connect("registry_name".to_string(), addr, host, None, timeout).await {
        Ok(client) => client,
        Err(e) => panic!("Failed to create EppClient: {}",  e)
    };

    let login = Login::new("username", "password", None);
    client.transact(&login, "transaction-id").await.unwrap();

    // Execute an EPP Command against the registry with distinct request and response objects
    let domain_check = DomainCheck { domains: &["eppdev.com", "eppdev.net"] };
    let response = client.transact(&domain_check, "transaction-id").await.unwrap();

    response.res_data.unwrap().list
        .iter()
        .for_each(|chk| println!("Domain: {}, Available: {}", chk.id, chk.available));
}

The output would look like this:

Domain: eppdev.com, Available: 1
Domain: eppdev.net, Available: 1

Request

Currently I don't have access to a registry's OT&E account to do extensive testing. I am using hexonet's EPP Gateway for testing, but access to a registry's OT&E account would be very helpful, so if anyone could help me out with one I would be very grateful!

epp-client's People

Contributors

djc avatar kmkaplan avatar masalachai avatar mellowagain avatar nrempel avatar valkum avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

epp-client's Issues

Support for mixed request/response extension types

Verisign requires the NameStore extension to be provided with every request. In every response, there exists an <extension> block for the same NameStore extension. There appears to be at least one exception however: for DomainInfo, a RGP response type is returned instead.

request:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <command>
        <info>
            <domain:info xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
                <domain:name hosts="all">idstest111111.com</domain:name>
                <domain:authInfo>
                    <domain:pw>password</domain:pw>
                </domain:authInfo>
            </domain:info>
        </info>
        <extension>
            <namestoreExt:namestoreExt xmlns:namestoreExt="http://www.verisign-grs.com/epp/namestoreExt-1.1">
                <namestoreExt:subProduct>com</namestoreExt:subProduct>
            </namestoreExt:namestoreExt>
        </extension>
        <clTRID>abc</clTRID>
    </command>
</epp>

response:

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <response>
        <result code="1000">
            <msg>Command completed successfully</msg>
        </result>
        <resData>
            <domain:infData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
                <domain:name>idstest111111.com</domain:name>
                <domain:roid>128416858_DOMAIN_COM-VRSN</domain:roid>
                <domain:status s="clientDeleteProhibited"/>
                <domain:status s="clientTransferProhibited"/>
                <domain:status s="clientHold"/>
                <domain:status s="clientUpdateProhibited"/>
                <domain:ns>
                    <domain:hostObj>ONE.NS.IDSTEST111111.COM</domain:hostObj>
                    <domain:hostObj>TWO.NS.IDSTEST111111.COM</domain:hostObj>
                </domain:ns>
                <domain:host>TWO.NS.IDSTEST111111.COM</domain:host>
                <domain:host>ONE.NS.IDSTEST111111.COM</domain:host>
                <domain:clID>29000333</domain:clID>
                <domain:crID>insdom1</domain:crID>
                <domain:crDate>2021-12-02T19:08:08Z</domain:crDate>
                <domain:upID>insdom1</domain:upID>
                <domain:upDate>2021-12-02T19:08:08Z</domain:upDate>
                <domain:exDate>2023-12-02T19:08:08Z</domain:exDate>
                <domain:authInfo>
                    <domain:pw>password</domain:pw>
                </domain:authInfo>
            </domain:infData>
        </resData>
        <extension>
            <rgp:infData xmlns:rgp="urn:ietf:params:xml:ns:rgp-1.0">
                <rgp:rgpStatus s="addPeriod">endDate=2021-12-07T19:08:08Z</rgp:rgpStatus>
            </rgp:infData>
        </extension>
        <trID>
            <clTRID>abc</clTRID>
            <svTRID>123</svTRID>
        </trID>
    </response>
</epp>

So we will need a way to model this. I think it makes sense to simple add an additional impl EppExtension for this mapping for now. However if there are many of these exceptions that will become unwieldy quickly.

Improving the public API

Here are some thoughts on things that could be better:

  • "Stuttery" type names. For example, this library currently exposes epp_client::epp::request::domain::check::EppDomainCheck. I would probably refrain from glob-reexporting the world in epp_client::epp and instead name the usable type something like epp_client::domain::check::Request.
  • This also entails reorganizing the library along data types instead of request/response axes; since request::domain::check and response::domain::check are somewhat tightly coupled, I think it'd make more sense to merge both into a domain::check module.
  • Right now, the library exposes the deep nesting required by XML serialization to the user; for example, the EppDomainCheck type the user is supposed to pass to EppClient::transact() is actually an EppObject<Command<DomainCheck>> where DomainCheck itself contains a DomainList which is actually the type that contains the request's arguments. IMO the public API should be such that you can pass an epp_client::domain::check::Request to transact(), and the Request knows (as part of a Request trait) how to wrap itself into an EppObject<Command<DomainCheck>> before serializing. We can then also use an associated type in the Request trait to avoid the need for passing in the Response type explicitly.

@masalachai what do you think? If you like this direction we can probably work on making this happen.

Support more response types for MessagePollResponse

We need a way to support multiple response types for MessagePollResponse. Right now we expect a DomainTransferResponseData object but it can be many things.

For example the contents of resData might be a HostInfoResponseData:

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <response>
        <result code="1301">
            <msg>Command completed successfully; ack to dequeue</msg>
        </result>
        <msgQ count="4" id="2728300">
            <qDate>2022-01-02T11:30:45Z</qDate>
            <msg>Unused objects policy</msg>
        </msgQ>
        <resData>
            <host:infData xmlns:host="urn:ietf:params:xml:ns:host-1.0">
                <host:name>ONE.NS.IDS11111111.COM</host:name>
                <host:roid>33661487_HOST_CNE-VRSN</host:roid>
                <host:status s="ok"/>
                <host:addr ip="v4">1.1.1.1</host:addr>
                <host:clID>29000333</host:clID>
                <host:crID>insdom1</host:crID>
                <host:crDate>2021-12-01T22:40:48Z</host:crDate>
                <host:upID>insdom1</host:upID>
                <host:upDate>2021-12-01T22:40:48Z</host:upDate>
            </host:infData>
        </resData>
        <extension>
            <changePoll:changeData state="before" xmlns:changePoll="urn:ietf:params:xml:ns:changePoll-1.0">
                <changePoll:operation op="purge">delete</changePoll:operation>
                <changePoll:date>2022-01-02T11:30:45Z</changePoll:date>
                <changePoll:svTRID>1641123045000-1774862978</changePoll:svTRID>
                <changePoll:who>regy_batch</changePoll:who>
                <changePoll:reason>Unused objects policy</changePoll:reason>
            </changePoll:changeData>
        </extension>
        <trID>
            <clTRID>abc</clTRID>
            <svTRID>123</svTRID>
        </trID>
    </response>
</epp>

Config file discovery is awkward

Hi there, thanks for the great library!

There seems to be a couple issues with the way the config file is discovered.

First, the default config implementation creates a verisign entry with dummy host, username, and password. If you happen to call EppClient::new("verisign").await, this call will hang indefinitely because epphost is an invalid host. This default implementation also means that if the config file cannot be found, the error message to the user is missing credentials for {registry} when I think it would be more appropriate to indicate that the config file is missing.

Second, requiring config file discovery at a predetermined location is burdensome. For example, on macOS, XDG_CONFIG_HOME is not recognized and so the config file must be located in some obscure directory.

Would you consider a different approach? Perhaps defaulting to env!("CARGO_MANIFEST_DIR") and allowing this to be overridden using a different custom environment variable?

Support multiple extensions in a single request

We will need to support multiple extensions in a single request.

For instance, for a consolidate sync request against a virtual registry using namestore:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <command>
        <update>
            <domain:update xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
                <domain:name>eppdev.com</domain:name>
                <domain:chg/>
            </domain:update>
        </update>
        <extension>
            <sync:update xmlns:sync="http://www.verisign.com/epp/sync-1.0">
                <sync:expMonthDay>--01-15</sync:expMonthDay>
            </sync:update>
            <namestoreExt:namestoreExt xmlns:namestoreExt="http://www.verisign-grs.com/epp/namestoreExt-1.1">
                <namestoreExt:subProduct>com</namestoreExt:subProduct>
            </namestoreExt:namestoreExt>
        </extension>
        <clTRID>abc123</clTRID>
    </command>
</epp>

I think we should consider this improvement in concert with the changes recommended here: #39 (comment).

HostAddr ip_version can be an enum

https://github.com/masalachai/epp-client/blob/main/src/common.rs#L153

Zero or more OPTIONAL <domain:hostAddr> elements that contain the
      IP addresses to be associated with the host.  Each element MAY
      contain an "ip" attribute to identify the IP address format.
      Attribute value "v4" is used to note IPv4 address format.
      Attribute value "v6" is used to note IPv6 address format.  If the
      "ip" attribute is not specified, "v4" is the default attribute
      value.  IP address syntax requirements are described in [Section](https://datatracker.ietf.org/doc/html/rfc5731#section-2.5)
      [2.5](https://datatracker.ietf.org/doc/html/rfc5731#section-2.5) of the EPP host mapping [[RFC5732](https://datatracker.ietf.org/doc/html/rfc5732)].

https://datatracker.ietf.org/doc/html/rfc5731#section-1.1

XML is not escaped for tag values during deserialization

<?xml version="1.0" encoding="UTF-8"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
    <response>
        <result code="1000">
            <msg>Command completed successfully</msg>
        </result>
        <resData>
            <domain:infData xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
                <domain:name>example.com</domain:name>
                <domain:roid>123</domain:roid>
                <domain:status s="ok"/>
                <domain:ns>
                    <domain:hostObj>NS1.EXAMPLE.COM</domain:hostObj>
                    <domain:hostObj>NS2.EXAMPLE.COM</domain:hostObj>
                </domain:ns>
                <domain:clID>123</domain:clID>
                <domain:crID>user</domain:crID>
                <domain:crDate>2022-02-09T22:07:34Z</domain:crDate>
                <domain:upID>insdom1</domain:upID>
                <domain:upDate>2022-02-09T22:07:34Z</domain:upDate>
                <domain:exDate>2023-02-09T22:07:34Z</domain:exDate>
                <domain:authInfo>
                    <domain:pw>47uE&amp;@J!Ra6g</domain:pw>
                </domain:authInfo>
            </domain:infData>
        </resData>
    </response>
</epp>

Note 47uE&amp;@J!Ra6g should be 47uE&@J!Ra6g

In `contact:addrType` the elements `sp`, `pc` (`contact::Address` fields `province`, `postal_code`) are optional

The elements sp, pc are optional but the struct Address in module contact makes them compulsory.

Here is the schema definition from RFC 5733, page 31

<complexType name="addrType">
  <sequence>
    <element name="street" type="contact:optPostalLineType"
     minOccurs="0" maxOccurs="3"/>
    <element name="city" type="contact:postalLineType"/>
    <element name="sp" type="contact:optPostalLineType"
     minOccurs="0"/>
    <element name="pc" type="contact:pcType"
     minOccurs="0"/>
    <element name="cc" type="contact:ccType"/>
  </sequence>
</complexType>

Should I fix this and submit PR?

Map response codes to an enum

I think it would be nice to map all of the response codes to an enum if possible so that we can reference response types by name.

PostalInfo::info_type can be an enum

https://datatracker.ietf.org/doc/html/rfc5733#section-3.2.1

One or two contact:postalInfo elements that contain postal-
address information. Two elements are provided so that address
information can be provided in both internationalized and
localized forms; a "type" attribute is used to identify the two
forms. If an internationalized form (type="int") is provided,
element content MUST be represented in a subset of UTF-8 that can
be represented in the 7-bit US-ASCII character set. If a
localized form (type="loc") is provided, element content MAY be
represented in unrestricted UTF-8. The contact:postalInfo
element contains the following child elements:

ContactStatus can be an enum

Possible values:

clientDeleteProhibited, clientHold, clientRenewProhibited, clientTransferProhibited, clientUpdateProhibited, inactive, ok, pendingCreate, pendingDelete, pendingRenew, pendingTransfer, pendingUpdate, serverDeleteProhibited, serverHold, serverRenewProhibited, serverTransferProhibited, serverUpdateProhibited

https://datatracker.ietf.org/doc/html/rfc5731#section-2.3

This might be more ergonomic for developers, what do you think?

Login with no service extensions serialize to invalid XML EPP document

When the ext_uris argument to Login::new() is None, Login serializes to and XML document that is invalid against the EPP schema. That's because it outputs an empty <svcExtension/> element, whereas the schema is:

<complexType name="loginSvcType">
  <sequence>
    <element name="objURI" type="anyURI"
     maxOccurs="unbounded"/>
    <element name="svcExtension" type="epp:extURIType"
     minOccurs="0"/>
  </sequence>
</complexType>
<complexType name="extURIType">
  <sequence>
    <element name="extURI" type="anyURI"
     maxOccurs="unbounded"/>
  </sequence>
</complexType>

That means that the <svcExtension> element must contain at least one <extURI> element, it can not be empty. To indicate no service extension the <svcExtension> element should be omited.

Example of generated bogus XML:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
  <command>
    <login>
      <clID>username</clID>
      <pw>password</pw>
      <options>
        <version>1.0</version>
        <lang>en</lang>
      </options>
      <svcs>
        <objURI>urn:ietf:params:xml:ns:host-1.0</objURI>
        <objURI>urn:ietf:params:xml:ns:contact-1.0</objURI>
        <objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>
        <svcExtension/>
      </svcs>
    </login>
    <clTRID>cltrid:1626454866</clTRID>
  </command>
</epp>

Create interface/abstraction to manage extension modules

rfc5730 defines an extension framework to extend the protocol via:

  • Protocol extension
  • Object extension
  • Command-Response extension

We will need a way to define these 3 different kinds of extensions. Currently, EppClientConnection contains information about which <svcExtension> elements should be sent to the server. I think this value should likely be derived from which extension modules are loaded. Additionally, the <svcs> element is currently hardcoded to contain host, contact, and domain objects. We will need a way to load more objects dynamically via "Object extensions".

Some other thoughts and things we will need to consider:

  • Login request should send appropriate svcs and svcExtension elements based on which extension module is loaded
  • Greeting response from EPP server contains list of supported objects and extensions. epp-client should warn if extension module is not present for objURI? epp-client should panic if extension module is not present for extURI?
    • If server supports an extension, then the client must use the extension in order to read/modify that resource (e.g., VeriSign uses their NameStore extension and a client must implement this extension in order to utilize domain:check

Some requests XML are missing namespaces

For instance:

https://github.com/masalachai/epp-client/blob/master/epp-client/test/resources/request/domain/check.xml

Should include object types:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<epp xmlns="urn:ietf:params:xml:ns:epp-1.0">
	<command>
		<check>
			<domain:check xmlns:domain="urn:ietf:params:xml:ns:domain-1.0">
				<domain:name>eppdev.com</domain:name>
				<domain:name>eppdev.net</domain:name>
			</domain:check>
		</check>
		<clTRID>cltrid:1626454866</clTRID>
	</command>
</epp>

https://datatracker.ietf.org/doc/html/rfc5730#section-2.9.2.1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.