From 3266976bfe98037ef66a434fa0a1f829936a6ec5 Mon Sep 17 00:00:00 2001 From: Brian Hartvigsen Date: Fri, 1 Oct 2021 20:34:12 -0600 Subject: [PATCH] Updated to support latest Cloudflare Registrar responses Also adds registrar/contacts support since registrant_contact does not actually appear to be a thing anymore in the normal case. I am not able to test on a transfer in as I don't have a domain to transfer in right now. --- registrar.go | 83 +++++++++++- registrar_test.go | 336 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 413 insertions(+), 6 deletions(-) diff --git a/registrar.go b/registrar.go index 9f3aa784cb1..b45a5a7e667 100644 --- a/registrar.go +++ b/registrar.go @@ -10,9 +10,25 @@ import ( "github.com/pkg/errors" ) +type RegistrarDomainContacts struct { + Administrator RegistrantContact `json:"administrator"` + Billing RegistrantContact `json:"billing"` + Registrant RegistrantContact `json:"registrant"` + Technical RegistrantContact `json:"technical"` +} + +type RegistrarFees struct { + Icann float64 `json:"icann_fee"` + Redemption float64 `json:"redemption_fee"` + Registration float64 `json:"registration_fee"` + Renewal float64 `json:"renewal_fee"` + Transfer float64 `json:"transfer_fee"` +} + // RegistrarDomain is the structure of the API response for a new // Cloudflare Registrar domain. type RegistrarDomain struct { + RegistrarDomainConfiguration ID string `json:"id"` Available bool `json:"available"` SupportedTLD bool `json:"supported_tld"` @@ -21,10 +37,13 @@ type RegistrarDomain struct { CurrentRegistrar string `json:"current_registrar"` ExpiresAt time.Time `json:"expires_at"` RegistryStatuses string `json:"registry_statuses"` - Locked bool `json:"locked"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` RegistrantContact RegistrantContact `json:"registrant_contact"` + Fees RegistrarFees `json:"fees"` + Name string `json:"name"` + PendingTransfer bool `json:"pending_transfer"` + Permissions []string `json:"permissions"` } // RegistrarTransferIn contains the structure for a domain transfer in @@ -53,6 +72,8 @@ type RegistrantContact struct { Phone string `json:"phone"` Email string `json:"email"` Fax string `json:"fax"` + Verified bool `json:"email_verified"` + Label string `json:"label"` } // RegistrarDomainConfiguration is the structure for making updates to @@ -78,6 +99,16 @@ type RegistrarDomainsDetailResponse struct { Result []RegistrarDomain `json:"result"` } +type RegistrarContactsDetailResponse struct { + Response + Result []RegistrantContact `json:"result"` +} + +type RegistrarContactDetailResponse struct { + Response + Result RegistrantContact `json:"result"` +} + // RegistrarDomain returns a single domain based on the account ID and // domain name. // @@ -175,3 +206,53 @@ func (api *API) UpdateRegistrarDomain(ctx context.Context, accountID, domainName } return r.Result, nil } + +func (api *API) getContacts(ctx context.Context, uri string) ([]RegistrantContact, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []RegistrantContact{}, err + } + + var r RegistrarContactsDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []RegistrantContact{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// RegistrarContacts gets a list of all Contacts based on the account ID +// +// API reference: undocumented +func (api *API) RegistrarContacts(ctx context.Context, accountID string) ([]RegistrantContact, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/contacts", accountID) + return api.getContacts(ctx, uri) +} + +// RegistrarContact gets a RegistrantContact based on the account ID and contact ID +// +// API reference: undocumented +func (api *API) RegistrarContact(ctx context.Context, accountID, contactID string) (RegistrantContact, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/contacts/%s", accountID, contactID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return RegistrantContact{}, err + } + + var r RegistrarContactDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return RegistrantContact{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// RegistrarDomainContacts gets a list of all Contacts based on the account and domain ID +// +// API reference: undocumented +func (api *API) RegistrarDomainContacts(ctx context.Context, accountID, domainName string) ([]RegistrantContact, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s/contacts", accountID, domainName) + return api.getContacts(ctx, uri) +} diff --git a/registrar_test.go b/registrar_test.go index 3b58be1603a..b71172dc633 100644 --- a/registrar_test.go +++ b/registrar_test.go @@ -37,7 +37,14 @@ var ( Fax: "123-867-5309", } expectedRegistrarDomain = RegistrarDomain{ + RegistrarDomainConfiguration: RegistrarDomainConfiguration{ + Locked: false, + NameServers: []string{"ns1.cloudflare.com", "ns2.cloudflare.com"}, + Privacy: true, + AutoRenew: true, + }, ID: "ea95132c15732412d22c1476fa83f27a", + Name: "cloudflare.com", Available: false, SupportedTLD: true, CanRegister: false, @@ -45,10 +52,10 @@ var ( CurrentRegistrar: "Cloudflare", ExpiresAt: expiresAtTimestamp, RegistryStatuses: "ok,serverTransferProhibited", - Locked: false, CreatedAt: createdAndModifiedTimestamp, UpdatedAt: createdAndModifiedTimestamp, RegistrantContact: expectedRegistrarContact, + Permissions: []string{"show_private_data"}, } ) @@ -65,8 +72,11 @@ func TestRegistrarDomain(t *testing.T) { "messages": [], "result": { "id": "ea95132c15732412d22c1476fa83f27a", + "administrator_contact_id": 12345, + "auto_renew": true, "available": false, "supported_tld": true, + "billing_contact_id": 12345, "can_register": false, "transfer_in": { "unlock_domain": "ok", @@ -76,6 +86,16 @@ func TestRegistrarDomain(t *testing.T) { "accept_foa": "needed", "can_cancel_transfer": true }, + "cloudflare_dns": false, + "cloudflare_registration": true, + "contacts": { + "administrator_id": 12345, + "billing_id": 12345, + "registrant_id": 12345, + "technical_id": 12345 + }, + "contacts_updated_at": "2018-08-28T17:26:26Z", + "created_registrar": "Cloudflare", "current_registrar": "Cloudflare", "expires_at": "2019-08-28T23:59:59Z", "registry_statuses": "ok,serverTransferProhibited", @@ -96,6 +116,103 @@ func TestRegistrarDomain(t *testing.T) { "phone": "+1 123-123-1234", "email": "user@example.com", "fax": "123-867-5309" + }, + "dns": [], + "ds_records": [], + "email_verified": true, + "fees": { + "icann_fee": 0, + "redemption_fee": 0, + "registration_fee": 0, + "renewal_fee": 0, + "transfer_fee": 0 + }, + "last_known_status": "registrationActive", + "last_transferred_at": "2020-07-26T04:18:28Z", + "name": "cloudflare.com", + "name_servers": [ + "ns1.cloudflare.com", + "ns2.cloudflare.com" + ], + "payment_expires_at": "2022-09-10T05:16:19Z", + "pending_transfer": false, + "permissions": [ + "show_private_data" + ], + "previous_registrar": "", + "privacy": true, + "registered_at": null, + "registrant_contact_id": 12345, + "registry_object_id": "SOME_ID_FROM_REGISTRY", + "technical_contact_id": 12345, + "transfer_conditions": { + "exists": true, + "not_premium": true, + "not_secure": true, + "not_started": true, + "not_waiting": true, + "supported_tld": true + }, + "updated_registrar": "Cloudflare", + "using_created_registrar_nameservers": false, + "using_current_registrar_nameservers": false, + "using_previous_registrar_nameservers": false, + "using_updated_registrar_nameservers": false, + "whois": { + "administrator": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "billing": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "privacy": true, + "raw": "A really long string with all the WHOIS data in a pretty format", + "registrant": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "technical": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + } } } } @@ -125,8 +242,11 @@ func TestRegistrarDomains(t *testing.T) { "result": [ { "id": "ea95132c15732412d22c1476fa83f27a", + "administrator_contact_id": 12345, + "auto_renew": true, "available": false, "supported_tld": true, + "billing_contact_id": 12345, "can_register": false, "transfer_in": { "unlock_domain": "ok", @@ -136,6 +256,16 @@ func TestRegistrarDomains(t *testing.T) { "accept_foa": "needed", "can_cancel_transfer": true }, + "cloudflare_dns": false, + "cloudflare_registration": true, + "contacts": { + "administrator_id": 12345, + "billing_id": 12345, + "registrant_id": 12345, + "technical_id": 12345 + }, + "contacts_updated_at": "2018-08-28T17:26:26Z", + "created_registrar": "Cloudflare", "current_registrar": "Cloudflare", "expires_at": "2019-08-28T23:59:59Z", "registry_statuses": "ok,serverTransferProhibited", @@ -156,6 +286,103 @@ func TestRegistrarDomains(t *testing.T) { "phone": "+1 123-123-1234", "email": "user@example.com", "fax": "123-867-5309" + }, + "dns": [], + "ds_records": [], + "email_verified": true, + "fees": { + "icann_fee": 0, + "redemption_fee": 0, + "registration_fee": 0, + "renewal_fee": 0, + "transfer_fee": 0 + }, + "last_known_status": "registrationActive", + "last_transferred_at": "2020-07-26T04:18:28Z", + "name": "cloudflare.com", + "name_servers": [ + "ns1.cloudflare.com", + "ns2.cloudflare.com" + ], + "payment_expires_at": "2022-09-10T05:16:19Z", + "pending_transfer": false, + "permissions": [ + "show_private_data" + ], + "previous_registrar": "", + "privacy": true, + "registered_at": null, + "registrant_contact_id": 12345, + "registry_object_id": "SOME_ID_FROM_REGISTRY", + "technical_contact_id": 12345, + "transfer_conditions": { + "exists": true, + "not_premium": true, + "not_secure": true, + "not_started": true, + "not_waiting": true, + "supported_tld": true + }, + "updated_registrar": "Cloudflare", + "using_created_registrar_nameservers": false, + "using_current_registrar_nameservers": false, + "using_previous_registrar_nameservers": false, + "using_updated_registrar_nameservers": false, + "whois": { + "administrator": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "billing": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "privacy": true, + "raw": "A really long string with all the WHOIS data in a pretty format", + "registrant": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + }, + "technical": { + "address": "DATA REDACTED", + "city": "DATA REDACTED", + "country": "US", + "email": "https://domaincontact.cloudflareregistrar.com/cloudflare.com", + "fax": "DATA REDACTED", + "first_name": "DATA REDACTED", + "last_name": "DATA REDACTED", + "organization": "DATA REDACTED", + "phone": "DATA REDACTED", + "state": "TX", + "zip": "DATA REDACTED" + } } } ], @@ -223,7 +450,14 @@ func TestTransferRegistrarDomain(t *testing.T) { "phone": "+1 123-123-1234", "email": "user@example.com", "fax": "123-867-5309" - } + }, + "name": "cloudflare.com", + "name_servers": ["ns1.cloudflare.com", "ns2.cloudflare.com"], + "privacy": true, + "auto_renew": true, + "permissions": [ + "show_private_data" + ] } ], "result_info": { @@ -290,7 +524,14 @@ func TestCancelRegistrarDomainTransfer(t *testing.T) { "phone": "+1 123-123-1234", "email": "user@example.com", "fax": "123-867-5309" - } + }, + "name": "cloudflare.com", + "name_servers": ["ns1.cloudflare.com", "ns2.cloudflare.com"], + "privacy": true, + "auto_renew": true, + "permissions": [ + "show_private_data" + ] } ], "result_info": { @@ -356,7 +597,14 @@ func TestUpdateRegistrarDomain(t *testing.T) { "phone": "+1 123-123-1234", "email": "user@example.com", "fax": "123-867-5309" - } + }, + "name": "cloudflare.com", + "name_servers": ["ns1.cloudflare.com", "ns2.cloudflare.com"], + "privacy": true, + "auto_renew": true, + "permissions": [ + "show_private_data" + ] } } `) @@ -366,10 +614,88 @@ func TestUpdateRegistrarDomain(t *testing.T) { actual, err := client.UpdateRegistrarDomain(context.Background(), "01a7362d577a6c3019a474fd6f485823", "cloudflare.com", RegistrarDomainConfiguration{ NameServers: []string{"ns1.cloudflare.com", "ns2.cloudflare.com"}, - Locked: false, + Locked: true, }) if assert.NoError(t, err) { assert.Equal(t, expectedRegistrarDomain, actual) } } + +func TestGetRegistrarContact(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": { + "id": "ea95132c15732412d22c1476fa83f27a", + "first_name": "John", + "last_name": "Appleseed", + "organization": "Cloudflare, Inc.", + "address": "123 Sesame St.", + "address2": "Suite 430", + "city": "Austin", + "state": "TX", + "zip": "12345", + "country": "US", + "phone": "+1 123-123-1234", + "email": "user@example.com", + "fax": "123-867-5309" + } + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/registrar/contacts/01a7362d577a6c3019a474fd6f485823", handler) + + actual, err := client.RegistrarContact(context.Background(), "01a7362d577a6c3019a474fd6f485823", "01a7362d577a6c3019a474fd6f485823") + + if assert.NoError(t, err) { + assert.Equal(t, expectedRegistrarContact, actual) + } +} + +func TestGetRegistrarContacts(t *testing.T) { + setup() + defer teardown() + + handler := func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method) + w.Header().Set("content-type", "application/json") + fmt.Fprintf(w, `{ + "success": true, + "errors": [], + "messages": [], + "result": [{ + "id": "ea95132c15732412d22c1476fa83f27a", + "first_name": "John", + "last_name": "Appleseed", + "organization": "Cloudflare, Inc.", + "address": "123 Sesame St.", + "address2": "Suite 430", + "city": "Austin", + "state": "TX", + "zip": "12345", + "country": "US", + "phone": "+1 123-123-1234", + "email": "user@example.com", + "fax": "123-867-5309" + }] + } + `) + } + + mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/registrar/contacts", handler) + + actual, err := client.RegistrarContacts(context.Background(), "01a7362d577a6c3019a474fd6f485823") + + if assert.NoError(t, err) { + assert.Equal(t, []RegistrantContact{expectedRegistrarContact}, actual) + } +}