namecheap: allow external domains (#1042)

This commit is contained in:
Mohamed Akram 2020-01-18 05:25:50 +04:00 committed by Ludovic Fernandez
parent 470f04590b
commit f5180ad521
4 changed files with 29 additions and 112 deletions

View file

@ -90,7 +90,7 @@
text = "`envTest` is a global variable"
[[issues.exclude-rules]]
path = "providers/dns/namecheap/namecheap_test.go"
text = "`(tldsMock|testCases)` is a global variable"
text = "`testCases` is a global variable"
[[issues.exclude-rules]]
path = "providers/dns/acmedns/acmedns_test.go"
text = "`(errorClientErr|errorStorageErr|egTestAccount)` is a global variable"

View file

@ -42,39 +42,6 @@ type getHostsResponse struct {
Hosts []Record `xml:"CommandResponse>DomainDNSGetHostsResult>host"`
}
type getTldsResponse struct {
XMLName xml.Name `xml:"ApiResponse"`
Errors []apiError `xml:"Errors>Error"`
Result []struct {
Name string `xml:",attr"`
} `xml:"CommandResponse>Tlds>Tld"`
}
// getTLDs requests the list of available TLDs.
// https://www.namecheap.com/support/api/methods/domains/get-tld-list.aspx
func (d *DNSProvider) getTLDs() (map[string]string, error) {
request, err := d.newRequestGet("namecheap.domains.getTldList")
if err != nil {
return nil, err
}
var gtr getTldsResponse
err = d.do(request, &gtr)
if err != nil {
return nil, err
}
if len(gtr.Errors) > 0 {
return nil, fmt.Errorf("%s [%d]", gtr.Errors[0].Description, gtr.Errors[0].Number)
}
tlds := make(map[string]string)
for _, t := range gtr.Result {
tlds[t.Name] = t.Name
}
return tlds, nil
}
// getHosts reads the full list of DNS host records.
// https://www.namecheap.com/support/api/methods/domains-dns/get-hosts.aspx
func (d *DNSProvider) getHosts(sld, tld string) ([]Record, error) {

View file

@ -13,6 +13,7 @@ import (
"github.com/go-acme/lego/v3/challenge/dns01"
"github.com/go-acme/lego/v3/log"
"github.com/go-acme/lego/v3/platform/config/env"
"golang.org/x/net/publicsuffix"
)
// Notes about namecheap's tool API:
@ -129,12 +130,7 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
// Present installs a TXT record for the DNS challenge.
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
tlds, err := d.getTLDs()
if err != nil {
return fmt.Errorf("namecheap: %v", err)
}
ch, err := newChallenge(domain, keyAuth, tlds)
ch, err := newChallenge(domain, keyAuth)
if err != nil {
return fmt.Errorf("namecheap: %v", err)
}
@ -169,12 +165,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
// CleanUp removes a TXT record used for a previous DNS challenge.
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
tlds, err := d.getTLDs()
if err != nil {
return fmt.Errorf("namecheap: %v", err)
}
ch, err := newChallenge(domain, keyAuth, tlds)
ch, err := newChallenge(domain, keyAuth)
if err != nil {
return fmt.Errorf("namecheap: %v", err)
}
@ -226,25 +217,17 @@ func getClientIP(client *http.Client, debug bool) (addr string, err error) {
return string(clientIP), nil
}
// newChallenge builds a challenge record from a domain name, a challenge
// authentication key, and a map of available TLDs.
func newChallenge(domain, keyAuth string, tlds map[string]string) (*challenge, error) {
// newChallenge builds a challenge record from a domain name and a challenge authentication key.
func newChallenge(domain, keyAuth string) (*challenge, error) {
domain = dns01.UnFqdn(domain)
parts := strings.Split(domain, ".")
// Find the longest matching TLD.
longest := -1
for i := len(parts); i > 0; i-- {
t := strings.Join(parts[i-1:], ".")
if _, found := tlds[t]; found {
longest = i - 1
}
}
if longest < 1 {
tld, _ := publicsuffix.PublicSuffix(domain)
if tld == domain {
return nil, fmt.Errorf("invalid domain name %q", domain)
}
tld := strings.Join(parts[longest:], ".")
parts := strings.Split(domain, ".")
longest := len(parts) - strings.Count(tld, ".") - 1
sld := parts[longest-1]
var host string

View file

@ -18,16 +18,6 @@ const (
envTestClientIP = "10.0.0.1"
)
var tldsMock = map[string]string{
"com.au": "com.au",
"com": "com",
"co.uk": "co.uk",
"uk": "uk",
"edu": "edu",
"co.com": "co.com",
"za.com": "za.com",
}
func TestDNSProvider_getHosts(t *testing.T) {
for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
@ -36,7 +26,7 @@ func TestDNSProvider_getHosts(t *testing.T) {
provider := mockDNSProvider(mock.URL)
ch, err := newChallenge(test.domain, "", tldsMock)
ch, err := newChallenge(test.domain, "")
require.NoError(t, err)
hosts, err := provider.getHosts(ch.sld, ch.tld)
@ -77,7 +67,7 @@ func TestDNSProvider_setHosts(t *testing.T) {
prov := mockDNSProvider(mock.URL)
ch, err := newChallenge(test.domain, "", tldsMock)
ch, err := newChallenge(test.domain, "")
require.NoError(t, err)
hosts, err := prov.getHosts(ch.sld, ch.tld)
@ -144,23 +134,22 @@ func TestDomainSplit(t *testing.T) {
{domain: "test.co.com", valid: true, tld: "co.com", sld: "test"},
{domain: "www.test.com.au", valid: true, tld: "com.au", sld: "test", host: "www"},
{domain: "www.za.com", valid: true, tld: "za.com", sld: "www"},
{domain: "my.test.tf", valid: true, tld: "tf", sld: "test", host: "my"},
{},
{domain: "a"},
{domain: "com"},
{domain: "com.au"},
{domain: "co.com"},
{domain: "co.uk"},
{domain: "test.au"},
{domain: "tf"},
{domain: "za.com"},
{domain: "www.za"},
{domain: "www.test.au"},
{domain: "www.test.unk"},
}
for _, test := range tests {
test := test
t.Run(test.domain, func(t *testing.T) {
valid := true
ch, err := newChallenge(test.domain, "", tldsMock)
ch, err := newChallenge(test.domain, "")
if err != nil {
valid = false
}
@ -172,30 +161,26 @@ func TestDomainSplit(t *testing.T) {
}
if test.valid && valid {
assertEq(t, "domain", ch.domain, test.domain)
assertEq(t, "tld", ch.tld, test.tld)
assertEq(t, "sld", ch.sld, test.sld)
assertEq(t, "host", ch.host, test.host)
require.NotNil(t, ch)
assert.Equal(t, test.domain, ch.domain, "domain")
assert.Equal(t, test.tld, ch.tld, "tld")
assert.Equal(t, test.sld, ch.sld, "sld")
assert.Equal(t, test.host, ch.host, "host")
}
})
}
}
func assertEq(t *testing.T, variable, got, want string) {
if got != want {
t.Errorf("Expected %s to be '%s' but got '%s'", variable, want, got)
}
}
func assertHdr(tc *testCase, t *testing.T, values *url.Values) {
ch, _ := newChallenge(tc.domain, "", tldsMock)
t.Helper()
assertEq(t, "ApiUser", values.Get("ApiUser"), envTestUser)
assertEq(t, "ApiKey", values.Get("ApiKey"), envTestKey)
assertEq(t, "UserName", values.Get("UserName"), envTestUser)
assertEq(t, "ClientIp", values.Get("ClientIp"), envTestClientIP)
assertEq(t, "SLD", values.Get("SLD"), ch.sld)
assertEq(t, "TLD", values.Get("TLD"), ch.tld)
ch, _ := newChallenge(tc.domain, "")
assert.Equal(t, envTestUser, values.Get("ApiUser"), "ApiUser")
assert.Equal(t, envTestKey, values.Get("ApiKey"), "ApiKey")
assert.Equal(t, envTestUser, values.Get("UserName"), "UserName")
assert.Equal(t, envTestClientIP, values.Get("ClientIp"), "ClientIp")
assert.Equal(t, ch.sld, values.Get("SLD"), "SLD")
assert.Equal(t, ch.tld, values.Get("TLD"), "TLD")
}
func mockServer(tc *testCase, t *testing.T) http.Handler {
@ -209,9 +194,6 @@ func mockServer(tc *testCase, t *testing.T) http.Handler {
assertHdr(tc, t, &values)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, tc.getHostsResponse)
case "namecheap.domains.getTldList":
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, responseGetTlds)
default:
t.Errorf("Unexpected GET command: %s", cmd)
}
@ -373,18 +355,3 @@ const responseGetHostsErrorBadAPIKey1 = `<?xml version="1.0" encoding="utf-8"?>
<GMTTimeDifference>--5:00</GMTTimeDifference>
<ExecutionTime>0</ExecutionTime>
</ApiResponse>`
const responseGetTlds = `<?xml version="1.0" encoding="utf-8"?>
<ApiResponse Status="OK" xmlns="http://api.namecheap.com/xml.response">
<Errors />
<Warnings />
<RequestedCommand>namecheap.domains.getTldList</RequestedCommand>
<CommandResponse Type="namecheap.domains.getTldList">
<Tlds>
<Tld Name="com" NonRealTime="false" MinRegisterYears="1" MaxRegisterYears="10" MinRenewYears="1" MaxRenewYears="10" RenewalMinDays="0" RenewalMaxDays="4000" ReactivateMaxDays="27" MinTransferYears="1" MaxTransferYears="1" IsApiRegisterable="true" IsApiRenewable="true" IsApiTransferable="true" IsEppRequired="true" IsDisableModContact="false" IsDisableWGAllot="false" IsIncludeInExtendedSearchOnly="false" SequenceNumber="10" Type="GTLD" SubType="" IsSupportsIDN="true" Category="A" SupportsRegistrarLock="true" AddGracePeriodDays="5" WhoisVerification="false" ProviderApiDelete="true" TldState="" SearchGroup="" Registry="">Most recognized top level domain<Categories><TldCategory Name="popular" SequenceNumber="10" /></Categories></Tld>
</Tlds>
</CommandResponse>
<Server>PHX01SBAPI01</Server>
<GMTTimeDifference>--5:00</GMTTimeDifference>
<ExecutionTime>0.004</ExecutionTime>
</ApiResponse>`