lego/providers/dns/allinkl/internal/client.go

224 lines
4.7 KiB
Go
Raw Permalink Normal View History

2021-06-30 20:49:02 +00:00
package internal
import (
"bytes"
2023-05-05 07:49:38 +00:00
"context"
2021-06-30 20:49:02 +00:00
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
2023-05-05 07:49:38 +00:00
"sync"
2021-06-30 20:49:02 +00:00
"time"
2023-05-05 07:49:38 +00:00
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
2021-06-30 20:49:02 +00:00
"github.com/mitchellh/mapstructure"
)
2023-05-05 07:49:38 +00:00
const apiEndpoint = "https://kasapi.kasserver.com/soap/KasApi.php"
type Authentication interface {
Authentication(ctx context.Context, sessionLifetime int, sessionUpdateLifetime bool) (string, error)
}
2021-06-30 20:49:02 +00:00
// Client a KAS server client.
type Client struct {
2023-05-05 07:49:38 +00:00
login string
floodTime time.Time
muFloodTime sync.Mutex
2021-06-30 20:49:02 +00:00
2023-05-05 07:49:38 +00:00
baseURL string
HTTPClient *http.Client
2021-06-30 20:49:02 +00:00
}
// NewClient creates a new Client.
2023-05-05 07:49:38 +00:00
func NewClient(login string) *Client {
2021-06-30 20:49:02 +00:00
return &Client{
2023-05-05 07:49:38 +00:00
login: login,
baseURL: apiEndpoint,
HTTPClient: &http.Client{Timeout: 10 * time.Second},
2021-06-30 20:49:02 +00:00
}
}
// GetDNSSettings Reading out the DNS settings of a zone.
// - zone: host zone.
// - recordID: the ID of the resource record (optional).
2023-05-05 07:49:38 +00:00
func (c *Client) GetDNSSettings(ctx context.Context, zone, recordID string) ([]ReturnInfo, error) {
2021-06-30 20:49:02 +00:00
requestParams := map[string]string{"zone_host": zone}
if recordID != "" {
requestParams["record_id"] = recordID
}
2023-05-05 07:49:38 +00:00
req, err := c.newRequest(ctx, "get_dns_settings", requestParams)
2021-06-30 20:49:02 +00:00
if err != nil {
return nil, err
}
var g GetDNSSettingsAPIResponse
2023-05-05 07:49:38 +00:00
err = c.do(req, &g)
2021-06-30 20:49:02 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, err
2021-06-30 20:49:02 +00:00
}
c.updateFloodTime(g.Response.KasFloodDelay)
return g.Response.ReturnInfo, nil
}
// AddDNSSettings Creation of a DNS resource record.
2023-05-05 07:49:38 +00:00
func (c *Client) AddDNSSettings(ctx context.Context, record DNSRequest) (string, error) {
req, err := c.newRequest(ctx, "add_dns_settings", record)
2021-06-30 20:49:02 +00:00
if err != nil {
return "", err
}
var g AddDNSSettingsAPIResponse
2023-05-05 07:49:38 +00:00
err = c.do(req, &g)
2021-06-30 20:49:02 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return "", err
2021-06-30 20:49:02 +00:00
}
c.updateFloodTime(g.Response.KasFloodDelay)
return g.Response.ReturnInfo, nil
}
// DeleteDNSSettings Deleting a DNS Resource Record.
2023-05-05 07:49:38 +00:00
func (c *Client) DeleteDNSSettings(ctx context.Context, recordID string) (bool, error) {
2021-06-30 20:49:02 +00:00
requestParams := map[string]string{"record_id": recordID}
2023-05-05 07:49:38 +00:00
req, err := c.newRequest(ctx, "delete_dns_settings", requestParams)
2021-06-30 20:49:02 +00:00
if err != nil {
return false, err
}
var g DeleteDNSSettingsAPIResponse
2023-05-05 07:49:38 +00:00
err = c.do(req, &g)
2021-06-30 20:49:02 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return false, err
2021-06-30 20:49:02 +00:00
}
c.updateFloodTime(g.Response.KasFloodDelay)
return g.Response.ReturnInfo, nil
}
2023-05-05 07:49:38 +00:00
func (c *Client) newRequest(ctx context.Context, action string, requestParams any) (*http.Request, error) {
2021-06-30 20:49:02 +00:00
ar := KasRequest{
Login: c.login,
AuthType: "session",
2023-05-05 07:49:38 +00:00
AuthData: getToken(ctx),
2021-06-30 20:49:02 +00:00
Action: action,
RequestParams: requestParams,
}
body, err := json.Marshal(ar)
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
2021-06-30 20:49:02 +00:00
}
payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAPIEnvelope, body)))
2023-05-05 07:49:38 +00:00
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL, bytes.NewReader(payload))
2021-06-30 20:49:02 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return nil, fmt.Errorf("unable to create request: %w", err)
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
return req, nil
}
func (c *Client) do(req *http.Request, result any) error {
c.muFloodTime.Lock()
time.Sleep(time.Until(c.floodTime))
c.muFloodTime.Unlock()
2021-06-30 20:49:02 +00:00
resp, err := c.HTTPClient.Do(req)
if err != nil {
2023-05-05 07:49:38 +00:00
return errutils.NewHTTPDoError(req, err)
2021-06-30 20:49:02 +00:00
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
2023-05-05 07:49:38 +00:00
return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
envlp, err := decodeXML[KasAPIResponseEnvelope](resp.Body)
2021-06-30 20:49:02 +00:00
if err != nil {
2023-05-05 07:49:38 +00:00
return err
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
if envlp.Body.Fault != nil {
return envlp.Body.Fault
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
raw := getValue(envlp.Body.KasAPIResponse.Return)
err = mapstructure.Decode(raw, result)
if err != nil {
return fmt.Errorf("response struct decode: %w", err)
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
return nil
2021-06-30 20:49:02 +00:00
}
func (c *Client) updateFloodTime(delay float64) {
2023-05-05 07:49:38 +00:00
c.muFloodTime.Lock()
2021-06-30 20:49:02 +00:00
c.floodTime = time.Now().Add(time.Duration(delay * float64(time.Second)))
2023-05-05 07:49:38 +00:00
c.muFloodTime.Unlock()
2021-06-30 20:49:02 +00:00
}
2023-05-05 07:49:38 +00:00
func getValue(item *Item) any {
2021-06-30 20:49:02 +00:00
switch {
case item.Raw != "":
v, _ := strconv.ParseBool(item.Raw)
return v
case item.Text != "":
switch item.Type {
case "xsd:string":
return item.Text
case "xsd:float":
v, _ := strconv.ParseFloat(item.Text, 64)
return v
case "xsd:int":
v, _ := strconv.ParseInt(item.Text, 10, 64)
return v
default:
return item.Text
}
case item.Value != nil:
return getValue(item.Value)
case len(item.Items) > 0 && item.Type == "SOAP-ENC:Array":
2023-05-05 07:49:38 +00:00
var v []any
2021-06-30 20:49:02 +00:00
for _, i := range item.Items {
v = append(v, getValue(i))
}
return v
case len(item.Items) > 0:
2023-05-05 07:49:38 +00:00
v := map[string]any{}
2021-06-30 20:49:02 +00:00
for _, i := range item.Items {
v[getKey(i)] = getValue(i)
}
return v
default:
return ""
}
}
func getKey(item *Item) string {
if item.Key == nil {
return ""
}
return item.Key.Text
}