mirror of
https://github.com/go-acme/lego
synced 2026-03-14 14:35:48 +01:00
allinkl: detect zone through API (#2721)
This commit is contained in:
parent
de869c8a7e
commit
16894fb99e
12 changed files with 277 additions and 66 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/log"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"github.com/go-acme/lego/v4/providers/dns/allinkl/internal"
|
||||
"github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
|
||||
|
|
@ -121,11 +122,6 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allinkl: could not find zone for domain %q: %w", domain, err)
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
credential, err := d.identifier.Authentication(ctx, 60, true)
|
||||
|
|
@ -135,6 +131,24 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
|
||||
ctx = internal.WithContext(ctx, credential)
|
||||
|
||||
var authZone string
|
||||
|
||||
for z := range dns01.DomainsSeq(info.EffectiveFQDN) {
|
||||
_, errG := d.client.GetDNSSettings(ctx, z, "")
|
||||
if errG != nil {
|
||||
log.Infof("allinkl: get DNS settings zone[%q] %v", z, errG)
|
||||
continue
|
||||
}
|
||||
|
||||
authZone = z
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
if authZone == "" {
|
||||
return fmt.Errorf("allinkl: unable to find auth zone for '%s'", info.EffectiveFQDN)
|
||||
}
|
||||
|
||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("allinkl: %w", err)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,18 @@
|
|||
package allinkl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester"
|
||||
"github.com/go-acme/lego/v4/platform/tester/servermock"
|
||||
"github.com/go-acme/lego/v4/providers/dns/allinkl/internal"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
@ -143,3 +152,108 @@ func TestLiveCleanUp(t *testing.T) {
|
|||
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func mockBuilder() *servermock.Builder[*DNSProvider] {
|
||||
return servermock.NewBuilder(
|
||||
func(server *httptest.Server) (*DNSProvider, error) {
|
||||
config := NewDefaultConfig()
|
||||
config.Login = "user"
|
||||
config.Password = "secret"
|
||||
config.HTTPClient = server.Client()
|
||||
|
||||
p, err := NewDNSProviderConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.client.BaseURL, _ = url.Parse(server.URL)
|
||||
p.identifier.BaseURL, _ = url.Parse(server.URL)
|
||||
|
||||
return p, err
|
||||
},
|
||||
).Route("POST /KasAuth.php",
|
||||
servermock.ResponseFromInternal("auth.xml"),
|
||||
servermock.CheckRequestBodyFromInternal("auth-request.xml").
|
||||
IgnoreWhitespace(),
|
||||
)
|
||||
}
|
||||
|
||||
func extractKasRequest(reader io.Reader) (*internal.KasRequest, error) {
|
||||
type ReqEnvelope struct {
|
||||
XMLName xml.Name `xml:"Envelope"`
|
||||
Body struct {
|
||||
KasAPI struct {
|
||||
Params string `xml:"Params"`
|
||||
} `xml:"KasApi"`
|
||||
} `xml:"Body"`
|
||||
}
|
||||
|
||||
raw, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reqEnvelope := ReqEnvelope{}
|
||||
|
||||
err = xml.Unmarshal(raw, &reqEnvelope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var kReq internal.KasRequest
|
||||
|
||||
err = json.Unmarshal([]byte(reqEnvelope.Body.KasAPI.Params), &kReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &kReq, nil
|
||||
}
|
||||
|
||||
func TestDNSProvider_Present(t *testing.T) {
|
||||
provider := mockBuilder().
|
||||
Route("POST /KasApi.php",
|
||||
http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
kReq, err := extractKasRequest(req.Body)
|
||||
if err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
switch kReq.Action {
|
||||
case "get_dns_settings":
|
||||
params := kReq.RequestParams.(map[string]any)
|
||||
|
||||
if params["zone_host"] == "_acme-challenge.example.com." {
|
||||
servermock.ResponseFromInternal("get_dns_settings_not_found.xml").ServeHTTP(rw, req)
|
||||
} else {
|
||||
servermock.ResponseFromInternal("get_dns_settings.xml").ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
case "add_dns_settings":
|
||||
servermock.ResponseFromInternal("add_dns_settings.xml").ServeHTTP(rw, req)
|
||||
|
||||
default:
|
||||
http.Error(rw, fmt.Sprintf("unknown action: %v", kReq.Action), http.StatusBadRequest)
|
||||
}
|
||||
}),
|
||||
).
|
||||
Build(t)
|
||||
|
||||
err := provider.Present("example.com", "abc", "123d==")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDNSProvider_CleanUp(t *testing.T) {
|
||||
provider := mockBuilder().
|
||||
Route("POST /KasApi.php",
|
||||
servermock.ResponseFromInternal("delete_dns_settings.xml"),
|
||||
servermock.CheckRequestBodyFromInternal("delete_dns_settings-request.xml").
|
||||
IgnoreWhitespace()).
|
||||
Build(t)
|
||||
|
||||
provider.recordIDs["abc"] = "57347450"
|
||||
|
||||
err := provider.CleanUp("example.com", "abc", "123d==")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff/v5"
|
||||
"github.com/go-acme/lego/v4/platform/wait"
|
||||
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
||||
"github.com/go-viper/mapstructure/v2"
|
||||
)
|
||||
|
||||
const apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php"
|
||||
const defaultBaseURL = "https://kasapi.kasserver.com/soap/"
|
||||
|
||||
const apiPath = "KasApi.php"
|
||||
|
||||
type Authentication interface {
|
||||
Authentication(ctx context.Context, sessionLifetime int, sessionUpdateLifetime bool) (string, error)
|
||||
|
|
@ -28,16 +33,21 @@ type Client struct {
|
|||
floodTime time.Time
|
||||
muFloodTime sync.Mutex
|
||||
|
||||
baseURL string
|
||||
maxElapsedTime time.Duration
|
||||
|
||||
BaseURL *url.URL
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Client.
|
||||
func NewClient(login string) *Client {
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
return &Client{
|
||||
login: login,
|
||||
baseURL: apiEndpoint,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
login: login,
|
||||
BaseURL: baseURL,
|
||||
maxElapsedTime: 3 * time.Minute,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,14 +61,9 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R
|
|||
requestParams["record_id"] = recordID
|
||||
}
|
||||
|
||||
req, err := c.newRequest(ctx, "get_dns_settings", requestParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var g APIResponse[GetDNSSettingsResponse]
|
||||
|
||||
var g GetDNSSettingsAPIResponse
|
||||
|
||||
err = c.do(req, &g)
|
||||
err := c.doRequest(ctx, "get_dns_settings", requestParams, &g)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -70,14 +75,9 @@ func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]R
|
|||
|
||||
// AddDNSSettings Creation of a DNS resource record.
|
||||
func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, error) {
|
||||
req, err := c.newRequest(ctx, "add_dns_settings", record)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var g APIResponse[AddDNSSettingsResponse]
|
||||
|
||||
var g AddDNSSettingsAPIResponse
|
||||
|
||||
err = c.do(req, &g)
|
||||
err := c.doRequest(ctx, "add_dns_settings", record, &g)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -91,14 +91,9 @@ func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string,
|
|||
func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (string, error) {
|
||||
requestParams := map[string]string{"record_id": recordID}
|
||||
|
||||
req, err := c.newRequest(ctx, "delete_dns_settings", requestParams)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var g APIResponse[DeleteDNSSettingsResponse]
|
||||
|
||||
var g DeleteDNSSettingsAPIResponse
|
||||
|
||||
err = c.do(req, &g)
|
||||
err := c.doRequest(ctx, "delete_dns_settings", requestParams, &g)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -124,7 +119,9 @@ func (c *Client) newRequest(ctx context.Context, action string, requestParams an
|
|||
|
||||
payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAPIEnvelope, body)))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL, bytes.NewReader(payload))
|
||||
endpoint := c.BaseURL.JoinPath(apiPath)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create request: %w", err)
|
||||
}
|
||||
|
|
@ -132,6 +129,21 @@ func (c *Client) newRequest(ctx context.Context, action string, requestParams an
|
|||
return req, nil
|
||||
}
|
||||
|
||||
func (c *Client) doRequest(ctx context.Context, action string, requestParams, result any) error {
|
||||
return wait.Retry(ctx,
|
||||
func() error {
|
||||
req, err := c.newRequest(ctx, action, requestParams)
|
||||
if err != nil {
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
return c.do(req, result)
|
||||
},
|
||||
backoff.WithBackOff(&backoff.ZeroBackOff{}),
|
||||
backoff.WithMaxElapsedTime(c.maxElapsedTime),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *Client) do(req *http.Request, result any) error {
|
||||
c.muFloodTime.Lock()
|
||||
time.Sleep(time.Until(c.floodTime))
|
||||
|
|
@ -139,29 +151,40 @@ func (c *Client) do(req *http.Request, result any) error {
|
|||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return errutils.NewHTTPDoError(req, err)
|
||||
return backoff.Permanent(errutils.NewHTTPDoError(req, err))
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
||||
return backoff.Permanent(errutils.NewUnexpectedResponseStatusCodeError(req, resp))
|
||||
}
|
||||
|
||||
envlp, err := decodeXML[KasAPIResponseEnvelope](resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return backoff.Permanent(err)
|
||||
}
|
||||
|
||||
if envlp.Body.Fault != nil {
|
||||
return envlp.Body.Fault
|
||||
if envlp.Body.Fault.Message == "flood_protection" {
|
||||
ft, errP := strconv.ParseFloat(envlp.Body.Fault.Detail, 64)
|
||||
if errP != nil {
|
||||
return fmt.Errorf("parse flood protection delay: %w", envlp.Body.Fault)
|
||||
}
|
||||
|
||||
c.updateFloodTime(ft)
|
||||
|
||||
return envlp.Body.Fault
|
||||
}
|
||||
|
||||
return backoff.Permanent(envlp.Body.Fault)
|
||||
}
|
||||
|
||||
raw := getValue(envlp.Body.KasAPIResponse.Return)
|
||||
|
||||
err = mapstructure.Decode(raw, result)
|
||||
if err != nil {
|
||||
return fmt.Errorf("response struct decode: %w", err)
|
||||
return backoff.Permanent(fmt.Errorf("response struct decode: %w", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ package internal
|
|||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester/servermock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -11,15 +13,17 @@ import (
|
|||
|
||||
func setupClient(server *httptest.Server) (*Client, error) {
|
||||
client := NewClient("user")
|
||||
client.baseURL = server.URL
|
||||
client.BaseURL, _ = url.Parse(server.URL)
|
||||
client.HTTPClient = server.Client()
|
||||
|
||||
client.maxElapsedTime = 1 * time.Second
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func TestClient_GetDNSSettings(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Client](setupClient).
|
||||
Route("POST /", servermock.ResponseFromFixture("get_dns_settings.xml"),
|
||||
Route("POST /KasApi.php", servermock.ResponseFromFixture("get_dns_settings.xml"),
|
||||
servermock.CheckRequestBodyFromFixture("get_dns_settings-request.xml").
|
||||
IgnoreWhitespace()).
|
||||
Build(t)
|
||||
|
|
@ -96,9 +100,24 @@ func TestClient_GetDNSSettings(t *testing.T) {
|
|||
assert.Equal(t, expected, records)
|
||||
}
|
||||
|
||||
func TestClient_GetDNSSettings_error_flood_protection(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Client](setupClient).
|
||||
Route("POST /KasApi.php",
|
||||
servermock.ResponseFromFixture("flood_protection.xml"),
|
||||
).
|
||||
Build(t)
|
||||
|
||||
assert.Zero(t, client.floodTime)
|
||||
|
||||
_, err := client.GetDNSSettings(mockContext(t), "example.com", "")
|
||||
require.EqualError(t, err, "KasApi: SOAP-ENV:Server: flood_protection: 0.0688529014587")
|
||||
|
||||
assert.NotZero(t, client.floodTime)
|
||||
}
|
||||
|
||||
func TestClient_AddDNSSettings(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Client](setupClient).
|
||||
Route("POST /", servermock.ResponseFromFixture("add_dns_settings.xml"),
|
||||
Route("POST /KasApi.php", servermock.ResponseFromFixture("add_dns_settings.xml"),
|
||||
servermock.CheckRequestBodyFromFixture("add_dns_settings-request.xml").
|
||||
IgnoreWhitespace()).
|
||||
Build(t)
|
||||
|
|
@ -118,7 +137,7 @@ func TestClient_AddDNSSettings(t *testing.T) {
|
|||
|
||||
func TestClient_DeleteDNSSettings(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Client](setupClient).
|
||||
Route("POST /", servermock.ResponseFromFixture("delete_dns_settings.xml"),
|
||||
Route("POST /KasApi.php", servermock.ResponseFromFixture("delete_dns_settings.xml"),
|
||||
servermock.CheckRequestBodyFromFixture("delete_dns_settings-request.xml").
|
||||
IgnoreWhitespace()).
|
||||
Build(t)
|
||||
|
|
|
|||
7
providers/dns/allinkl/internal/fixtures/auth-request.xml
Normal file
7
providers/dns/allinkl/internal/fixtures/auth-request.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<Body>
|
||||
<KasAuth xmlns="https://kasserver.com/">
|
||||
<Params>{"kas_login":"user","kas_auth_data":"secret","kas_auth_type":"plain","session_lifetime":60,"session_update_lifetime":"Y"}</Params>
|
||||
</KasAuth>
|
||||
</Body>
|
||||
</Envelope>
|
||||
11
providers/dns/allinkl/internal/fixtures/flood_protection.xml
Normal file
11
providers/dns/allinkl/internal/fixtures/flood_protection.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<SOAP-ENV:Body>
|
||||
<SOAP-ENV:Fault>
|
||||
<faultcode>SOAP-ENV:Server</faultcode>
|
||||
<faultstring>flood_protection</faultstring>
|
||||
<faultactor>KasApi</faultactor>
|
||||
<detail>0.0688529014587</detail>
|
||||
</SOAP-ENV:Fault>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<SOAP-ENV:Body>
|
||||
<SOAP-ENV:Fault>
|
||||
<faultcode>SOAP-ENV:Server</faultcode>
|
||||
<faultstring>zone_not_found</faultstring>
|
||||
<faultactor>KasApi</faultactor>
|
||||
<detail>example.com</detail>
|
||||
</SOAP-ENV:Fault>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<SOAP-ENV:Body>
|
||||
<SOAP-ENV:Fault>
|
||||
<faultcode>SOAP-ENV:Server</faultcode>
|
||||
<faultstring>zone_syntax_incorrect</faultstring>
|
||||
<faultactor>KasApi</faultactor>
|
||||
<detail>_acme-challenge.example.com</detail>
|
||||
</SOAP-ENV:Fault>
|
||||
</SOAP-ENV:Body>
|
||||
</SOAP-ENV:Envelope>
|
||||
|
|
@ -6,14 +6,14 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
||||
)
|
||||
|
||||
// authEndpoint represents the Identity API endpoint to call.
|
||||
const authEndpoint = "https://kasapi.kasserver.com/soap/KasAuth.php"
|
||||
const authPath = "KasAuth.php"
|
||||
|
||||
type token string
|
||||
|
||||
|
|
@ -24,17 +24,19 @@ type Identifier struct {
|
|||
login string
|
||||
password string
|
||||
|
||||
authEndpoint string
|
||||
HTTPClient *http.Client
|
||||
BaseURL *url.URL
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// NewIdentifier creates a new Identifier.
|
||||
func NewIdentifier(login, password string) *Identifier {
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
return &Identifier{
|
||||
login: login,
|
||||
password: password,
|
||||
authEndpoint: authEndpoint,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
login: login,
|
||||
password: password,
|
||||
BaseURL: baseURL,
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +64,9 @@ func (c *Identifier) Authentication(ctx context.Context, sessionLifetime int, se
|
|||
|
||||
payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAuthEnvelope, body)))
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.authEndpoint, bytes.NewReader(payload))
|
||||
endpoint := c.BaseURL.JoinPath(authPath)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to create request: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package internal
|
|||
import (
|
||||
"context"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/go-acme/lego/v4/platform/tester/servermock"
|
||||
|
|
@ -12,7 +13,7 @@ import (
|
|||
|
||||
func setupIdentifierClient(server *httptest.Server) (*Identifier, error) {
|
||||
client := NewIdentifier("user", "secret")
|
||||
client.authEndpoint = server.URL
|
||||
client.BaseURL, _ = url.Parse(server.URL)
|
||||
client.HTTPClient = server.Client()
|
||||
|
||||
return client, nil
|
||||
|
|
@ -26,10 +27,13 @@ func mockContext(t *testing.T) context.Context {
|
|||
|
||||
func TestIdentifier_Authentication(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Identifier](setupIdentifierClient).
|
||||
Route("POST /", servermock.ResponseFromFixture("auth.xml")).
|
||||
Route("POST /KasAuth.php",
|
||||
servermock.ResponseFromFixture("auth.xml"),
|
||||
servermock.CheckRequestBodyFromFixture("auth-request.xml").
|
||||
IgnoreWhitespace()).
|
||||
Build(t)
|
||||
|
||||
credentialToken, err := client.Authentication(t.Context(), 60, false)
|
||||
credentialToken, err := client.Authentication(t.Context(), 60, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "593959ca04f0de9689b586c6a647d15d", credentialToken)
|
||||
|
|
@ -37,7 +41,7 @@ func TestIdentifier_Authentication(t *testing.T) {
|
|||
|
||||
func TestIdentifier_Authentication_error(t *testing.T) {
|
||||
client := servermock.NewBuilder[*Identifier](setupIdentifierClient).
|
||||
Route("POST /", servermock.ResponseFromFixture("auth_fault.xml")).
|
||||
Route("POST /KasAuth.php", servermock.ResponseFromFixture("auth_fault.xml")).
|
||||
Build(t)
|
||||
|
||||
_, err := client.Authentication(t.Context(), 60, false)
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ type Fault struct {
|
|||
Code string `xml:"faultcode"`
|
||||
Message string `xml:"faultstring"`
|
||||
Actor string `xml:"faultactor"`
|
||||
Detail string `xml:"detail"`
|
||||
}
|
||||
|
||||
func (f Fault) Error() string {
|
||||
return fmt.Sprintf("%s: %s: %s", f.Actor, f.Code, f.Message)
|
||||
func (f *Fault) Error() string {
|
||||
return fmt.Sprintf("%s: %s: %s: %s", f.Actor, f.Code, f.Message, f.Detail)
|
||||
}
|
||||
|
||||
// KasResponse a KAS SOAP response.
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ type DNSRequest struct {
|
|||
|
||||
// ---
|
||||
|
||||
type GetDNSSettingsAPIResponse struct {
|
||||
Response GetDNSSettingsResponse `json:"Response" mapstructure:"Response"`
|
||||
type APIResponse[T any] struct {
|
||||
Response T `json:"Response" mapstructure:"Response"`
|
||||
}
|
||||
|
||||
type GetDNSSettingsResponse struct {
|
||||
|
|
@ -73,20 +73,12 @@ type ReturnInfo struct {
|
|||
Aux int `json:"record_aux,omitempty" mapstructure:"record_aux"`
|
||||
}
|
||||
|
||||
type AddDNSSettingsAPIResponse struct {
|
||||
Response AddDNSSettingsResponse `json:"Response" mapstructure:"Response"`
|
||||
}
|
||||
|
||||
type AddDNSSettingsResponse struct {
|
||||
KasFloodDelay float64 `json:"KasFloodDelay" mapstructure:"KasFloodDelay"`
|
||||
ReturnInfo string `json:"ReturnInfo" mapstructure:"ReturnInfo"`
|
||||
ReturnString string `json:"ReturnString" mapstructure:"ReturnString"`
|
||||
}
|
||||
|
||||
type DeleteDNSSettingsAPIResponse struct {
|
||||
Response DeleteDNSSettingsResponse `json:"Response"`
|
||||
}
|
||||
|
||||
type DeleteDNSSettingsResponse struct {
|
||||
KasFloodDelay float64 `json:"KasFloodDelay"`
|
||||
ReturnString string `json:"ReturnString"`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue