forked from TrueCloudLab/lego
Add DNS Provider for TransIP (#703)
This commit is contained in:
parent
42d8637d87
commit
1837a3bb1c
14 changed files with 1711 additions and 0 deletions
14
Gopkg.lock
generated
14
Gopkg.lock
generated
|
@ -446,6 +446,18 @@
|
||||||
pruneopts = "NUT"
|
pruneopts = "NUT"
|
||||||
revision = "37e84520dcf74488f67654f9c775b9752c232dc1"
|
revision = "37e84520dcf74488f67654f9c775b9752c232dc1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:3b236e8930d31aeb375fe405c15c2afc581e04bd6cb68da4723e1aa8d2e2da37"
|
||||||
|
name = "github.com/transip/gotransip"
|
||||||
|
packages = [
|
||||||
|
".",
|
||||||
|
"domain",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
pruneopts = "NUT"
|
||||||
|
revision = "1dc93a7db3567a5ccf865106afac88278ba940cf"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:5dba68a1600a235630e208cb7196b24e58fcbb77bb7a6bec08fcd23f081b0a58"
|
digest = "1:5dba68a1600a235630e208cb7196b24e58fcbb77bb7a6bec08fcd23f081b0a58"
|
||||||
name = "github.com/urfave/cli"
|
name = "github.com/urfave/cli"
|
||||||
|
@ -659,6 +671,8 @@
|
||||||
"github.com/stretchr/testify/suite",
|
"github.com/stretchr/testify/suite",
|
||||||
"github.com/timewasted/linode",
|
"github.com/timewasted/linode",
|
||||||
"github.com/timewasted/linode/dns",
|
"github.com/timewasted/linode/dns",
|
||||||
|
"github.com/transip/gotransip",
|
||||||
|
"github.com/transip/gotransip/domain",
|
||||||
"github.com/urfave/cli",
|
"github.com/urfave/cli",
|
||||||
"golang.org/x/crypto/ocsp",
|
"golang.org/x/crypto/ocsp",
|
||||||
"golang.org/x/net/context",
|
"golang.org/x/net/context",
|
||||||
|
|
|
@ -85,6 +85,10 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/sacloud/libsacloud"
|
name = "github.com/sacloud/libsacloud"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/transip/gotransip"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
name = "github.com/exoscale/egoscale"
|
name = "github.com/exoscale/egoscale"
|
||||||
|
|
|
@ -48,6 +48,7 @@ import (
|
||||||
"github.com/xenolf/lego/providers/dns/sakuracloud"
|
"github.com/xenolf/lego/providers/dns/sakuracloud"
|
||||||
"github.com/xenolf/lego/providers/dns/selectel"
|
"github.com/xenolf/lego/providers/dns/selectel"
|
||||||
"github.com/xenolf/lego/providers/dns/stackpath"
|
"github.com/xenolf/lego/providers/dns/stackpath"
|
||||||
|
"github.com/xenolf/lego/providers/dns/transip"
|
||||||
"github.com/xenolf/lego/providers/dns/vegadns"
|
"github.com/xenolf/lego/providers/dns/vegadns"
|
||||||
"github.com/xenolf/lego/providers/dns/vultr"
|
"github.com/xenolf/lego/providers/dns/vultr"
|
||||||
)
|
)
|
||||||
|
@ -145,6 +146,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
|
||||||
return stackpath.NewDNSProvider()
|
return stackpath.NewDNSProvider()
|
||||||
case "selectel":
|
case "selectel":
|
||||||
return selectel.NewDNSProvider()
|
return selectel.NewDNSProvider()
|
||||||
|
case "transip":
|
||||||
|
return transip.NewDNSProvider()
|
||||||
case "vegadns":
|
case "vegadns":
|
||||||
return vegadns.NewDNSProvider()
|
return vegadns.NewDNSProvider()
|
||||||
case "vultr":
|
case "vultr":
|
||||||
|
|
0
providers/dns/transip/fixtures/private.key
Normal file
0
providers/dns/transip/fixtures/private.key
Normal file
150
providers/dns/transip/transip.go
Normal file
150
providers/dns/transip/transip.go
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// Package transip implements a DNS provider for solving the DNS-01 challenge using TransIP.
|
||||||
|
package transip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/transip/gotransip"
|
||||||
|
transipdomain "github.com/transip/gotransip/domain"
|
||||||
|
"github.com/xenolf/lego/acme"
|
||||||
|
"github.com/xenolf/lego/platform/config/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is used to configure the creation of the DNSProvider
|
||||||
|
type Config struct {
|
||||||
|
AccountName string
|
||||||
|
PrivateKeyPath string
|
||||||
|
PropagationTimeout time.Duration
|
||||||
|
PollingInterval time.Duration
|
||||||
|
TTL int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||||
|
func NewDefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
TTL: int64(env.GetOrDefaultInt("TRANSIP_TTL", 10)),
|
||||||
|
PropagationTimeout: env.GetOrDefaultSecond("TRANSIP_PROPAGATION_TIMEOUT", 10*time.Minute),
|
||||||
|
PollingInterval: env.GetOrDefaultSecond("TRANSIP_POLLING_INTERVAL", 10*time.Second),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSProvider describes a provider for TransIP
|
||||||
|
type DNSProvider struct {
|
||||||
|
config *Config
|
||||||
|
client gotransip.SOAPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProvider returns a DNSProvider instance configured for TransIP.
|
||||||
|
// Credentials must be passed in the environment variables:
|
||||||
|
// TRANSIP_ACCOUNTNAME, TRANSIP_PRIVATEKEYPATH.
|
||||||
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
|
values, err := env.Get("TRANSIP_ACCOUNT_NAME", "TRANSIP_PRIVATE_KEY_PATH")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transip: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := NewDefaultConfig()
|
||||||
|
config.AccountName = values["TRANSIP_ACCOUNT_NAME"]
|
||||||
|
config.PrivateKeyPath = values["TRANSIP_PRIVATE_KEY_PATH"]
|
||||||
|
|
||||||
|
return NewDNSProviderConfig(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDNSProviderConfig return a DNSProvider instance configured for TransIP.
|
||||||
|
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
|
if config == nil {
|
||||||
|
return nil, errors.New("transip: the configuration of the DNS provider is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gotransip.NewSOAPClient(gotransip.ClientConfig{
|
||||||
|
AccountName: config.AccountName,
|
||||||
|
PrivateKeyPath: config.PrivateKeyPath,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("transip: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DNSProvider{client: client, config: config}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
||||||
|
// Adjusting here to cope with spikes in propagation times.
|
||||||
|
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
|
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||||
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
domainName := acme.UnFqdn(authZone)
|
||||||
|
|
||||||
|
// get the subDomain
|
||||||
|
subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
|
||||||
|
|
||||||
|
// get all DNS entries
|
||||||
|
info, err := transipdomain.GetInfo(d.client, domainName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transip: error for %s in Present: %v", domain, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// include the new DNS entry
|
||||||
|
dnsEntries := append(info.DNSEntries, transipdomain.DNSEntry{
|
||||||
|
Name: subDomain,
|
||||||
|
TTL: d.config.TTL,
|
||||||
|
Type: transipdomain.DNSEntryTypeTXT,
|
||||||
|
Content: value,
|
||||||
|
})
|
||||||
|
|
||||||
|
// set the updated DNS entries
|
||||||
|
err = transipdomain.SetDNSEntries(d.client, domainName, dnsEntries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transip: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanUp removes the TXT record matching the specified parameters
|
||||||
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||||
|
|
||||||
|
authZone, err := acme.FindZoneByFqdn(fqdn, acme.RecursiveNameservers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
domainName := acme.UnFqdn(authZone)
|
||||||
|
|
||||||
|
// get the subDomain
|
||||||
|
subDomain := strings.TrimSuffix(acme.UnFqdn(fqdn), "."+domainName)
|
||||||
|
|
||||||
|
// get all DNS entries
|
||||||
|
info, err := transipdomain.GetInfo(d.client, domainName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transip: error for %s in CleanUp: %v", fqdn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop through the existing entries and remove the specific record
|
||||||
|
updatedEntries := info.DNSEntries[:0]
|
||||||
|
for _, e := range info.DNSEntries {
|
||||||
|
if e.Name != subDomain {
|
||||||
|
updatedEntries = append(updatedEntries, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the updated DNS entries
|
||||||
|
err = transipdomain.SetDNSEntries(d.client, domainName, updatedEntries)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("transip: couldn't get Record ID in CleanUp: %sv", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
164
providers/dns/transip/transip_test.go
Normal file
164
providers/dns/transip/transip_test.go
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
package transip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/xenolf/lego/platform/tester"
|
||||||
|
)
|
||||||
|
|
||||||
|
var envTest = tester.NewEnvTest(
|
||||||
|
"TRANSIP_ACCOUNT_NAME",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH").
|
||||||
|
WithDomain("TRANSIP_DOMAIN")
|
||||||
|
|
||||||
|
func TestNewDNSProvider(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
envVars map[string]string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "success",
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TRANSIP_ACCOUNT_NAME": "johndoe",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH": "./fixtures/private.key",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing all credentials",
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TRANSIP_ACCOUNT_NAME": "",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH": "",
|
||||||
|
},
|
||||||
|
expected: "transip: some credentials information are missing: TRANSIP_ACCOUNT_NAME,TRANSIP_PRIVATE_KEY_PATH",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing account name",
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TRANSIP_ACCOUNT_NAME": "",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH": "./fixtures/private.key",
|
||||||
|
},
|
||||||
|
expected: "transip: some credentials information are missing: TRANSIP_ACCOUNT_NAME",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing private key path",
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TRANSIP_ACCOUNT_NAME": "johndoe",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH": "",
|
||||||
|
},
|
||||||
|
expected: "transip: some credentials information are missing: TRANSIP_PRIVATE_KEY_PATH",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "could not open private key path",
|
||||||
|
envVars: map[string]string{
|
||||||
|
"TRANSIP_ACCOUNT_NAME": "johndoe",
|
||||||
|
"TRANSIP_PRIVATE_KEY_PATH": "./fixtures/non/existent/private.key",
|
||||||
|
},
|
||||||
|
expected: "transip: could not open private key: stat ./fixtures/non/existent/private.key: no such file or directory",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
defer envTest.RestoreEnv()
|
||||||
|
envTest.ClearEnv()
|
||||||
|
|
||||||
|
envTest.Apply(test.envVars)
|
||||||
|
|
||||||
|
p, err := NewDNSProvider()
|
||||||
|
|
||||||
|
if len(test.expected) == 0 {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, p)
|
||||||
|
require.NotNil(t, p.config)
|
||||||
|
require.NotNil(t, p.client)
|
||||||
|
} else {
|
||||||
|
require.EqualError(t, err, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDNSProviderConfig(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
accountName string
|
||||||
|
privateKeyPath string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "success",
|
||||||
|
accountName: "johndoe",
|
||||||
|
privateKeyPath: "./fixtures/private.key",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing all credentials",
|
||||||
|
expected: "transip: AccountName is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing account name",
|
||||||
|
privateKeyPath: "./fixtures/private.key",
|
||||||
|
expected: "transip: AccountName is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "missing private key path",
|
||||||
|
accountName: "johndoe",
|
||||||
|
expected: "transip: PrivateKeyPath or PrivateKeyBody is required",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "could not open private key path",
|
||||||
|
accountName: "johndoe",
|
||||||
|
privateKeyPath: "./fixtures/non/existent/private.key",
|
||||||
|
expected: "transip: could not open private key: stat ./fixtures/non/existent/private.key: no such file or directory",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
config := NewDefaultConfig()
|
||||||
|
config.AccountName = test.accountName
|
||||||
|
config.PrivateKeyPath = test.privateKeyPath
|
||||||
|
|
||||||
|
p, err := NewDNSProviderConfig(config)
|
||||||
|
|
||||||
|
if len(test.expected) == 0 {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, p)
|
||||||
|
require.NotNil(t, p.config)
|
||||||
|
require.NotNil(t, p.client)
|
||||||
|
} else {
|
||||||
|
require.EqualError(t, err, test.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLivePresent(t *testing.T) {
|
||||||
|
if !envTest.IsLiveTest() {
|
||||||
|
t.Skip("skipping live test")
|
||||||
|
}
|
||||||
|
|
||||||
|
envTest.RestoreEnv()
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = provider.Present(envTest.GetDomain(), "", "123d==")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLiveCleanUp(t *testing.T) {
|
||||||
|
if !envTest.IsLiveTest() {
|
||||||
|
t.Skip("skipping live test")
|
||||||
|
}
|
||||||
|
|
||||||
|
envTest.RestoreEnv()
|
||||||
|
provider, err := NewDNSProvider()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
|
||||||
|
err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
21
vendor/github.com/transip/gotransip/LICENSE
generated
vendored
Normal file
21
vendor/github.com/transip/gotransip/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 TransIP B.V.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
12
vendor/github.com/transip/gotransip/api.go
generated
vendored
Normal file
12
vendor/github.com/transip/gotransip/api.go
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package gotransip
|
||||||
|
|
||||||
|
// CancellationTime represents the possible ways of canceling a contract
|
||||||
|
type CancellationTime string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// CancellationTimeEnd specifies to cancel the contract when the contract was
|
||||||
|
// due to end anyway
|
||||||
|
CancellationTimeEnd CancellationTime = "end"
|
||||||
|
// CancellationTimeImmediately specifies to cancel the contract immediately
|
||||||
|
CancellationTimeImmediately CancellationTime = "immediately"
|
||||||
|
)
|
119
vendor/github.com/transip/gotransip/client.go
generated
vendored
Normal file
119
vendor/github.com/transip/gotransip/client.go
generated
vendored
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
package gotransip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
transipAPIHost = "api.transip.nl"
|
||||||
|
transipAPINamespace = "http://www.transip.nl/soap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APIMode specifies in which mode the API is used. Currently this is only
|
||||||
|
// supports either readonly or readwrite
|
||||||
|
type APIMode string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// APIModeReadOnly specifies that no changes can be made from API calls
|
||||||
|
APIModeReadOnly APIMode = "readonly"
|
||||||
|
// APIModeReadWrite specifies that changes can be made from API calls
|
||||||
|
APIModeReadWrite APIMode = "readwrite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClientConfig is a tool to easily create a new Client object
|
||||||
|
type ClientConfig struct {
|
||||||
|
AccountName string
|
||||||
|
PrivateKeyPath string
|
||||||
|
PrivateKeyBody []byte
|
||||||
|
Mode APIMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client is the interface which all clients should implement
|
||||||
|
type Client interface {
|
||||||
|
Call(SoapRequest, interface{}) error // execute request on client
|
||||||
|
}
|
||||||
|
|
||||||
|
// SOAPClient represents a TransIP API SOAP client, implementing the Client
|
||||||
|
// interface
|
||||||
|
type SOAPClient struct {
|
||||||
|
soapClient soapClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call performs given SOAP request and fills the response into result
|
||||||
|
func (c SOAPClient) Call(req SoapRequest, result interface{}) error {
|
||||||
|
return c.soapClient.call(req, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSOAPClient returns a new SOAPClient object for given config
|
||||||
|
// ClientConfig's PrivateKeyPath will override potentially given PrivateKeyBody
|
||||||
|
func NewSOAPClient(c ClientConfig) (SOAPClient, error) {
|
||||||
|
// check account name
|
||||||
|
if len(c.AccountName) == 0 {
|
||||||
|
return SOAPClient{}, errors.New("AccountName is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if private key was given in any form
|
||||||
|
if len(c.PrivateKeyPath) == 0 && len(c.PrivateKeyBody) == 0 {
|
||||||
|
return SOAPClient{}, errors.New("PrivateKeyPath or PrivateKeyBody is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// if PrivateKeyPath was set, this will override any given PrivateKeyBody
|
||||||
|
if len(c.PrivateKeyPath) > 0 {
|
||||||
|
// try to open private key and read contents
|
||||||
|
if _, err := os.Stat(c.PrivateKeyPath); err != nil {
|
||||||
|
return SOAPClient{}, fmt.Errorf("could not open private key: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// read private key so we can pass the body to the soapClient
|
||||||
|
var err error
|
||||||
|
c.PrivateKeyBody, err = ioutil.ReadFile(c.PrivateKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return SOAPClient{}, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to APIMode read/write
|
||||||
|
if len(c.Mode) == 0 {
|
||||||
|
c.Mode = APIModeReadWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
// create soapClient and pass it to a new Client pointer
|
||||||
|
sc := soapClient{
|
||||||
|
Login: c.AccountName,
|
||||||
|
Mode: c.Mode,
|
||||||
|
PrivateKey: c.PrivateKeyBody,
|
||||||
|
}
|
||||||
|
|
||||||
|
return SOAPClient{
|
||||||
|
soapClient: sc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FakeSOAPClient is a client doing nothing except implementing the gotransip.Client
|
||||||
|
// interface
|
||||||
|
// you can however set a fixture XML body which Call will try to Unmarshal into
|
||||||
|
// result
|
||||||
|
// useful for testing
|
||||||
|
type FakeSOAPClient struct {
|
||||||
|
fixture []byte // preset this fixture so Call can use it to Unmarshal
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixtureFromFile reads file and sets content as FakeSOAPClient's fixture
|
||||||
|
func (f *FakeSOAPClient) FixtureFromFile(file string) (err error) {
|
||||||
|
// read fixture file
|
||||||
|
f.fixture, err = ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("could not read fixture from file %s: %s", file, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call doesn't do anything except fill the XML unmarshalled result
|
||||||
|
func (f FakeSOAPClient) Call(req SoapRequest, result interface{}) error {
|
||||||
|
// this fake client just parses given fixture as if it was a SOAP response
|
||||||
|
return parseSoapResponse(f.fixture, req.Padding, 200, result)
|
||||||
|
}
|
314
vendor/github.com/transip/gotransip/domain/api.go
generated
vendored
Normal file
314
vendor/github.com/transip/gotransip/domain/api.go
generated
vendored
Normal file
|
@ -0,0 +1,314 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/transip/gotransip"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file holds all DomainService methods directly ported from TransIP API
|
||||||
|
|
||||||
|
// BatchCheckAvailability checks the availability of multiple domains
|
||||||
|
func BatchCheckAvailability(c gotransip.Client, domainNames []string) ([]CheckResult, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "batchCheckAvailability",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainNames", domainNames)
|
||||||
|
|
||||||
|
var v struct {
|
||||||
|
V []CheckResult `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v.V, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckAvailability returns the availability status of a domain.
|
||||||
|
func CheckAvailability(c gotransip.Client, domainName string) (Status, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "checkAvailability",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var v Status
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWhois returns the whois of a domain name
|
||||||
|
func GetWhois(c gotransip.Client, domainName string) (string, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getWhois",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var v string
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDomainNames returns list with domain names or error when this failed
|
||||||
|
func GetDomainNames(c gotransip.Client) ([]string, error) {
|
||||||
|
var d = struct {
|
||||||
|
D []string `xml:"item"`
|
||||||
|
}{}
|
||||||
|
err := c.Call(gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getDomainNames",
|
||||||
|
}, &d)
|
||||||
|
|
||||||
|
return d.D, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInfo returns Domain for given name or error when this failed
|
||||||
|
func GetInfo(c gotransip.Client, domainName string) (Domain, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getInfo",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var d Domain
|
||||||
|
err := c.Call(sr, &d)
|
||||||
|
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchGetInfo returns array of Domain for given name or error when this failed
|
||||||
|
func BatchGetInfo(c gotransip.Client, domainNames []string) ([]Domain, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "batchGetInfo",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainNames", domainNames)
|
||||||
|
|
||||||
|
var d = struct {
|
||||||
|
D []Domain `xml:"item"`
|
||||||
|
}{}
|
||||||
|
err := c.Call(sr, &d)
|
||||||
|
|
||||||
|
return d.D, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthCode returns the Auth code for a domainName
|
||||||
|
func GetAuthCode(c gotransip.Client, domainName string) (string, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getAuthCode",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var v string
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIsLocked returns the lock status for a domainName
|
||||||
|
func GetIsLocked(c gotransip.Client, domainName string) (bool, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getIsLocked",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var v bool
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a domain name and will automatically create and sign a proposition for it
|
||||||
|
func Register(c gotransip.Client, domain string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "register",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cancel cancels a domain name, will automatically create and sign a cancellation document
|
||||||
|
func Cancel(c gotransip.Client, domainName string, endTime gotransip.CancellationTime) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "cancel",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
sr.AddArgument("endTime", string(endTime))
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferWithOwnerChange transfers a domain with changing the owner
|
||||||
|
func TransferWithOwnerChange(c gotransip.Client, domain, authCode string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "transferWithOwnerChange",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
sr.AddArgument("authCode", authCode)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferWithoutOwnerChange transfers a domain without changing the owner
|
||||||
|
func TransferWithoutOwnerChange(c gotransip.Client, domain, authCode string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "transferWithoutOwnerChange",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
sr.AddArgument("authCode", authCode)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNameservers starts a nameserver change for this domain, will replace all
|
||||||
|
// existing nameservers with the new nameservers
|
||||||
|
func SetNameservers(c gotransip.Client, domainName string, nameservers Nameservers) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "setNameservers",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
sr.AddArgument("nameservers", nameservers)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLock locks this domain
|
||||||
|
func SetLock(c gotransip.Client, domainName string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "setLock",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsetLock unlocks this domain
|
||||||
|
func UnsetLock(c gotransip.Client, domainName string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "unsetLock",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDNSEntries sets the DnsEntries for this Domain, will replace all existing
|
||||||
|
// dns entries with the new entries
|
||||||
|
func SetDNSEntries(c gotransip.Client, domainName string, dnsEntries DNSEntries) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "setDnsEntries",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
sr.AddArgument("dnsEntries", dnsEntries)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOwner starts an owner change of a domain
|
||||||
|
func SetOwner(c gotransip.Client, domainName, registrantWhoisContact WhoisContact) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "setOwner",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
// make sure contact is of type registrant
|
||||||
|
registrantWhoisContact.Type = "registrant"
|
||||||
|
sr.AddArgument("registrantWhoisContact", registrantWhoisContact)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContacts starts a contact change of a domain, this will replace all existing contacts
|
||||||
|
func SetContacts(c gotransip.Client, domainName, contacts WhoisContacts) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "setContacts",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
sr.AddArgument("contacts", contacts)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllTLDInfos returns slice with TLD objects or error when this failed
|
||||||
|
func GetAllTLDInfos(c gotransip.Client) ([]TLD, error) {
|
||||||
|
var d = struct {
|
||||||
|
TLD []TLD `xml:"item"`
|
||||||
|
}{}
|
||||||
|
err := c.Call(gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getAllTldInfos",
|
||||||
|
}, &d)
|
||||||
|
|
||||||
|
return d.TLD, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTldInfo returns info about a specific TLD
|
||||||
|
func GetTldInfo(c gotransip.Client, tldName string) (TLD, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getTldInfo",
|
||||||
|
}
|
||||||
|
sr.AddArgument("tldName", tldName)
|
||||||
|
|
||||||
|
var v TLD
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentDomainAction returns info about the action this domain is currently running
|
||||||
|
func GetCurrentDomainAction(c gotransip.Client, domainName string) (ActionResult, error) {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "getCurrentDomainAction",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domainName", domainName)
|
||||||
|
|
||||||
|
var v ActionResult
|
||||||
|
err := c.Call(sr, &v)
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryCurrentDomainActionWithNewData retries a failed domain action with new
|
||||||
|
// domain data. The Domain.Name field must contain the name of the Domain. The
|
||||||
|
// Nameservers, Contacts, DNSEntries fields contain the new data for this domain.
|
||||||
|
func RetryCurrentDomainActionWithNewData(c gotransip.Client, domain Domain) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "retryCurrentDomainActionWithNewData",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryTransferWithDifferentAuthCode retries a transfer action with a new authcode
|
||||||
|
func RetryTransferWithDifferentAuthCode(c gotransip.Client, domain Domain, newAuthCode string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "retryTransferWithDifferentAuthCode",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
sr.AddArgument("newAuthCode", newAuthCode)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelDomainAction cancels a failed domain action
|
||||||
|
func CancelDomainAction(c gotransip.Client, domain string) error {
|
||||||
|
sr := gotransip.SoapRequest{
|
||||||
|
Service: serviceName,
|
||||||
|
Method: "cancelDomainAction",
|
||||||
|
}
|
||||||
|
sr.AddArgument("domain", domain)
|
||||||
|
|
||||||
|
return c.Call(sr, nil)
|
||||||
|
}
|
405
vendor/github.com/transip/gotransip/domain/domain.go
generated
vendored
Normal file
405
vendor/github.com/transip/gotransip/domain/domain.go
generated
vendored
Normal file
|
@ -0,0 +1,405 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/transip/gotransip"
|
||||||
|
"github.com/transip/gotransip/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serviceName string = "DomainService"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Domain represents a Transip_Domain object
|
||||||
|
// as described at https://api.transip.nl/docs/transip.nl/class-Transip_Domain.html
|
||||||
|
type Domain struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Nameservers []Nameserver `xml:"nameservers>item"`
|
||||||
|
Contacts []WhoisContact `xml:"contacts>item"`
|
||||||
|
DNSEntries []DNSEntry `xml:"dnsEntries>item"`
|
||||||
|
Branding Branding `xml:"branding"`
|
||||||
|
AuthorizationCode string `xml:"authCode"`
|
||||||
|
IsLocked bool `xml:"isLocked"`
|
||||||
|
RegistrationDate util.XMLTime `xml:"registrationDate"`
|
||||||
|
RenewalDate util.XMLTime `xml:"renewalDate"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeParams returns Domain parameters ready to be used for constructing a signature
|
||||||
|
func (d Domain) EncodeParams(prm gotransip.ParamsContainer) {
|
||||||
|
idx := prm.Len()
|
||||||
|
prm.Add(fmt.Sprintf("%d[name]", idx), d.Name)
|
||||||
|
prm.Add(fmt.Sprintf("%d[authCode]", idx), d.AuthorizationCode)
|
||||||
|
prm.Add(fmt.Sprintf("%d[isLocked]", idx), fmt.Sprintf("%t", d.IsLocked))
|
||||||
|
prm.Add(fmt.Sprintf("%d[registrationDate]", idx), d.RegistrationDate.Format("2006-01-02"))
|
||||||
|
prm.Add(fmt.Sprintf("%d[renewalDate]", idx), d.RenewalDate.Format("2006-01-02"))
|
||||||
|
// nameservers
|
||||||
|
for i, e := range d.Nameservers {
|
||||||
|
var ipv4, ipv6 string
|
||||||
|
if e.IPv4Address != nil {
|
||||||
|
ipv4 = e.IPv4Address.String()
|
||||||
|
}
|
||||||
|
if e.IPv6Address != nil {
|
||||||
|
ipv6 = e.IPv6Address.String()
|
||||||
|
}
|
||||||
|
prm.Add(fmt.Sprintf("%d[nameservers][%d][hostname]", idx, i), e.Hostname)
|
||||||
|
prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv4]", idx, i), ipv4)
|
||||||
|
prm.Add(fmt.Sprintf("%d[nameservers][%d][ipv6]", idx, i), ipv6)
|
||||||
|
}
|
||||||
|
// contacts
|
||||||
|
for i, e := range d.Contacts {
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][type]", idx, i), e.Type)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][firstName]", idx, i), e.FirstName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][middleName]", idx, i), e.MiddleName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][lastName]", idx, i), e.LastName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][companyName]", idx, i), e.CompanyName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][companyKvk]", idx, i), e.CompanyKvk)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][companyType]", idx, i), e.CompanyType)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][street]", idx, i), e.Street)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][number]", idx, i), e.Number)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][postalCode]", idx, i), e.PostalCode)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][city]", idx, i), e.City)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][phoneNumber]", idx, i), e.PhoneNumber)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][faxNumber]", idx, i), e.FaxNumber)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][email]", idx, i), e.Email)
|
||||||
|
prm.Add(fmt.Sprintf("%d[contacts][%d][country]", idx, i), e.Country)
|
||||||
|
}
|
||||||
|
// dnsEntries
|
||||||
|
for i, e := range d.DNSEntries {
|
||||||
|
prm.Add(fmt.Sprintf("%d[dnsEntries][%d][name]", idx, i), e.Name)
|
||||||
|
prm.Add(fmt.Sprintf("%d[dnsEntries][%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
|
||||||
|
prm.Add(fmt.Sprintf("%d[dnsEntries][%d][type]", idx, i), string(e.Type))
|
||||||
|
prm.Add(fmt.Sprintf("%d[dnsEntries][%d][content]", idx, i), e.Content)
|
||||||
|
}
|
||||||
|
// branding
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][companyName]", idx), d.Branding.CompanyName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][supportEmail]", idx), d.Branding.SupportEmail)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][companyUrl]", idx), d.Branding.CompanyURL)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][termsOfUsageUrl]", idx), d.Branding.TermsOfUsageURL)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][bannerLine1]", idx), d.Branding.BannerLine1)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][bannerLine2]", idx), d.Branding.BannerLine2)
|
||||||
|
prm.Add(fmt.Sprintf("%d[branding][bannerLine3]", idx), d.Branding.BannerLine3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeArgs returns Domain XML body ready to be passed in the SOAP call
|
||||||
|
func (d Domain) EncodeArgs(key string) string {
|
||||||
|
output := fmt.Sprintf(`<%s xsi:type="ns1:Domain">
|
||||||
|
<name xsi:type="xsd:string">%s</name>
|
||||||
|
<authCode xsi:type="xsd:string">%s</authCode>
|
||||||
|
<isLocked xsi:type="xsd:boolean">%t</isLocked>
|
||||||
|
<registrationDate xsi:type="xsd:string">%s</registrationDate>
|
||||||
|
<renewalDate xsi:type="xsd:string">%s</renewalDate>`,
|
||||||
|
key, d.Name, d.AuthorizationCode, d.IsLocked,
|
||||||
|
d.RegistrationDate.Format("2006-01-02"), d.RenewalDate.Format("2006-01-02"),
|
||||||
|
) + "\n"
|
||||||
|
|
||||||
|
output += Nameservers(d.Nameservers).EncodeArgs("nameservers") + "\n"
|
||||||
|
output += WhoisContacts(d.Contacts).EncodeArgs("contacts") + "\n"
|
||||||
|
output += DNSEntries(d.DNSEntries).EncodeArgs("dnsEntries") + "\n"
|
||||||
|
output += d.Branding.EncodeArgs("branding") + "\n"
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s</%s>", output, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capability represents the possible capabilities a TLD can have
|
||||||
|
type Capability string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// CapabilityRequiresAuthCode defines this TLD requires an auth code
|
||||||
|
// to be transferred
|
||||||
|
CapabilityRequiresAuthCode Capability = "requiresAuthCode"
|
||||||
|
// CapabilityCanRegister defines this TLD can be registered
|
||||||
|
CapabilityCanRegister Capability = "canRegister"
|
||||||
|
// CapabilityCanTransferWithOwnerChange defines this TLD can be transferred
|
||||||
|
// with change of ownership
|
||||||
|
CapabilityCanTransferWithOwnerChange Capability = "canTransferWithOwnerChange"
|
||||||
|
// CapabilityCanTransferWithoutOwnerChange defines this TLD can be
|
||||||
|
// transferred without change of ownership
|
||||||
|
CapabilityCanTransferWithoutOwnerChange Capability = "canTransferWithoutOwnerChange"
|
||||||
|
// CapabilityCanSetLock defines this TLD allows to be locked
|
||||||
|
CapabilityCanSetLock Capability = "canSetLock"
|
||||||
|
// CapabilityCanSetOwner defines this TLD supports setting an owner
|
||||||
|
CapabilityCanSetOwner Capability = "canSetOwner"
|
||||||
|
// CapabilityCanSetContacts defines this TLD supports setting contacts
|
||||||
|
CapabilityCanSetContacts Capability = "canSetContacts"
|
||||||
|
// CapabilityCanSetNameservers defines this TLD supports setting nameservers
|
||||||
|
CapabilityCanSetNameservers Capability = "canSetNameservers"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TLD represents a Transip_Tld object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_Tld.html
|
||||||
|
type TLD struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
Price float64 `xml:"price"`
|
||||||
|
RenewalPrice float64 `xml:"renewalPrice"`
|
||||||
|
Capabilities []Capability `xml:"capabilities>item"`
|
||||||
|
RegistrationPeriodLength int64 `xml:"registrationPeriodLength"`
|
||||||
|
CancelTimeFrame int64 `xml:"cancelTimeFrame"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nameserver represents a Transip_Nameserver object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_Nameserver.html
|
||||||
|
type Nameserver struct {
|
||||||
|
Hostname string `xml:"hostname"`
|
||||||
|
IPv4Address net.IP `xml:"ipv4"`
|
||||||
|
IPv6Address net.IP `xml:"ipv6"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nameservers is just an array of Nameserver
|
||||||
|
// basically only here so it can implement paramsEncoder
|
||||||
|
type Nameservers []Nameserver
|
||||||
|
|
||||||
|
// EncodeParams returns Nameservers parameters ready to be used for constructing a signature
|
||||||
|
func (n Nameservers) EncodeParams(prm gotransip.ParamsContainer) {
|
||||||
|
idx := prm.Len()
|
||||||
|
for i, e := range n {
|
||||||
|
var ipv4, ipv6 string
|
||||||
|
if e.IPv4Address != nil {
|
||||||
|
ipv4 = e.IPv4Address.String()
|
||||||
|
}
|
||||||
|
if e.IPv6Address != nil {
|
||||||
|
ipv6 = e.IPv6Address.String()
|
||||||
|
}
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][hostname]", idx, i), e.Hostname)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][ipv4]", idx, i), ipv4)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][ipv6]", idx, i), ipv6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeArgs returns Nameservers XML body ready to be passed in the SOAP call
|
||||||
|
func (n Nameservers) EncodeArgs(key string) string {
|
||||||
|
output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:Nameserver[%d]" xsi:type="ns1:ArrayOfNameserver">`, key, len(n)) + "\n"
|
||||||
|
for _, e := range n {
|
||||||
|
var ipv4, ipv6 string
|
||||||
|
if e.IPv4Address != nil {
|
||||||
|
ipv4 = e.IPv4Address.String()
|
||||||
|
}
|
||||||
|
if e.IPv6Address != nil {
|
||||||
|
ipv6 = e.IPv6Address.String()
|
||||||
|
}
|
||||||
|
output += fmt.Sprintf(` <item xsi:type="ns1:Nameserver">
|
||||||
|
<hostname xsi:type="xsd:string">%s</hostname>
|
||||||
|
<ipv4 xsi:type="xsd:string">%s</ipv4>
|
||||||
|
<ipv6 xsi:type="xsd:string">%s</ipv6>
|
||||||
|
</item>`, e.Hostname, ipv4, ipv6) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s</%s>", output, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSEntryType represents the possible types of DNS entries
|
||||||
|
type DNSEntryType string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DNSEntryTypeA represents an A-record
|
||||||
|
DNSEntryTypeA DNSEntryType = "A"
|
||||||
|
// DNSEntryTypeAAAA represents an AAAA-record
|
||||||
|
DNSEntryTypeAAAA DNSEntryType = "AAAA"
|
||||||
|
// DNSEntryTypeCNAME represents a CNAME-record
|
||||||
|
DNSEntryTypeCNAME DNSEntryType = "CNAME"
|
||||||
|
// DNSEntryTypeMX represents an MX-record
|
||||||
|
DNSEntryTypeMX DNSEntryType = "MX"
|
||||||
|
// DNSEntryTypeNS represents an NS-record
|
||||||
|
DNSEntryTypeNS DNSEntryType = "NS"
|
||||||
|
// DNSEntryTypeTXT represents a TXT-record
|
||||||
|
DNSEntryTypeTXT DNSEntryType = "TXT"
|
||||||
|
// DNSEntryTypeSRV represents an SRV-record
|
||||||
|
DNSEntryTypeSRV DNSEntryType = "SRV"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNSEntry represents a Transip_DnsEntry object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_DnsEntry.html
|
||||||
|
type DNSEntry struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
TTL int64 `xml:"expire"`
|
||||||
|
Type DNSEntryType `xml:"type"`
|
||||||
|
Content string `xml:"content"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DNSEntries is just an array of DNSEntry
|
||||||
|
// basically only here so it can implement paramsEncoder
|
||||||
|
type DNSEntries []DNSEntry
|
||||||
|
|
||||||
|
// EncodeParams returns DNSEntries parameters ready to be used for constructing a signature
|
||||||
|
func (d DNSEntries) EncodeParams(prm gotransip.ParamsContainer) {
|
||||||
|
idx := prm.Len()
|
||||||
|
for i, e := range d {
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][name]", idx, i), e.Name)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][expire]", idx, i), fmt.Sprintf("%d", e.TTL))
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), string(e.Type))
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][content]", idx, i), e.Content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeArgs returns DNSEntries XML body ready to be passed in the SOAP call
|
||||||
|
func (d DNSEntries) EncodeArgs(key string) string {
|
||||||
|
output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:DnsEntry[%d]" xsi:type="ns1:ArrayOfDnsEntry">`, key, len(d)) + "\n"
|
||||||
|
for _, e := range d {
|
||||||
|
output += fmt.Sprintf(` <item xsi:type="ns1:DnsEntry">
|
||||||
|
<name xsi:type="xsd:string">%s</name>
|
||||||
|
<expire xsi:type="xsd:int">%d</expire>
|
||||||
|
<type xsi:type="xsd:string">%s</type>
|
||||||
|
<content xsi:type="xsd:string">%s</content>
|
||||||
|
</item>`, e.Name, e.TTL, e.Type, e.Content) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s</%s>", output, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status reflects the current status of a domain in a check result
|
||||||
|
type Status string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// StatusInYourAccount means he domain name is already in your account
|
||||||
|
StatusInYourAccount Status = "inyouraccount"
|
||||||
|
// StatusUnavailable means the domain name is currently unavailable and can not be registered due to unknown reasons.
|
||||||
|
StatusUnavailable Status = "unavailable"
|
||||||
|
// StatusNotFree means the domain name has already been registered
|
||||||
|
StatusNotFree Status = "notfree"
|
||||||
|
// StatusFree means the domain name is currently free, is available and can be registered
|
||||||
|
StatusFree Status = "free"
|
||||||
|
// StatusInternalPull means the domain name is currently registered at TransIP and is available to be pulled from another account to yours.
|
||||||
|
StatusInternalPull Status = "internalpull"
|
||||||
|
// StatusInternalPush means the domain name is currently registered at TransIP in your accounta and is available to be pushed to another account.
|
||||||
|
StatusInternalPush Status = "internalpush"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CheckResult represents a Transip_DomainCheckResult object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_DomainCheckResult.html
|
||||||
|
type CheckResult struct {
|
||||||
|
DomainName string `xml:"domainName"`
|
||||||
|
Status Status `xml:"status"`
|
||||||
|
Actions []Action `xml:"actions>item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Branding represents a Transip_DomainBranding object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_DomainBranding.html
|
||||||
|
type Branding struct {
|
||||||
|
CompanyName string `xml:"companyName"`
|
||||||
|
SupportEmail string `xml:"supportEmail"`
|
||||||
|
CompanyURL string `xml:"companyUrl"`
|
||||||
|
TermsOfUsageURL string `xml:"termsOfUsageUrl"`
|
||||||
|
BannerLine1 string `xml:"bannerLine1"`
|
||||||
|
BannerLine2 string `xml:"bannerLine2"`
|
||||||
|
BannerLine3 string `xml:"bannerLine3"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
|
||||||
|
func (b Branding) EncodeParams(prm gotransip.ParamsContainer) {
|
||||||
|
idx := prm.Len()
|
||||||
|
prm.Add(fmt.Sprintf("%d[companyName]", idx), b.CompanyName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[supportEmail]", idx), b.SupportEmail)
|
||||||
|
prm.Add(fmt.Sprintf("%d[companyUrl]", idx), b.CompanyURL)
|
||||||
|
prm.Add(fmt.Sprintf("%d[termsOfUsageUrl]", idx), b.TermsOfUsageURL)
|
||||||
|
prm.Add(fmt.Sprintf("%d[bannerLine1]", idx), b.BannerLine1)
|
||||||
|
prm.Add(fmt.Sprintf("%d[bannerLine2]", idx), b.BannerLine2)
|
||||||
|
prm.Add(fmt.Sprintf("%d[bannerLine3]", idx), b.BannerLine3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeArgs returns Branding XML body ready to be passed in the SOAP call
|
||||||
|
func (b Branding) EncodeArgs(key string) string {
|
||||||
|
return fmt.Sprintf(`<branding xsi:type="ns1:DomainBranding">
|
||||||
|
<companyName xsi:type="xsd:string">%s</companyName>
|
||||||
|
<supportEmail xsi:type="xsd:string">%s</supportEmail>
|
||||||
|
<companyUrl xsi:type="xsd:string">%s</companyUrl>
|
||||||
|
<termsOfUsageUrl xsi:type="xsd:string">%s</termsOfUsageUrl>
|
||||||
|
<bannerLine1 xsi:type="xsd:string">%s</bannerLine1>
|
||||||
|
<bannerLine2 xsi:type="xsd:string">%s</bannerLine2>
|
||||||
|
<bannerLine3 xsi:type="xsd:string">%s</bannerLine3>
|
||||||
|
</branding>`, b.CompanyName, b.SupportEmail, b.CompanyURL, b.TermsOfUsageURL, b.BannerLine1, b.BannerLine2, b.BannerLine3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Action reflects the available actions to perform on a domain
|
||||||
|
type Action string
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ActionRegister registers a domain
|
||||||
|
ActionRegister Action = "register"
|
||||||
|
// ActionTransfer transfers a domain to another provider
|
||||||
|
ActionTransfer Action = "transfer"
|
||||||
|
// ActionInternalPull transfers a domain to another account at TransIP
|
||||||
|
ActionInternalPull Action = "internalpull"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ActionResult represents a Transip_DomainAction object as described at
|
||||||
|
// https://api.transip.nl/docs/transip.nl/class-Transip_DomainAction.html
|
||||||
|
type ActionResult struct {
|
||||||
|
Name string `xml:"name"`
|
||||||
|
HasFailed bool `xml:"hasFailed"`
|
||||||
|
Message string `xml:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhoisContact represents a TransIP_WhoisContact object
|
||||||
|
// as described at https://api.transip.nl/docs/transip.nl/class-Transip_WhoisContact.html
|
||||||
|
type WhoisContact struct {
|
||||||
|
Type string `xml:"type"`
|
||||||
|
FirstName string `xml:"firstName"`
|
||||||
|
MiddleName string `xml:"middleName"`
|
||||||
|
LastName string `xml:"lastName"`
|
||||||
|
CompanyName string `xml:"companyName"`
|
||||||
|
CompanyKvk string `xml:"companyKvk"`
|
||||||
|
CompanyType string `xml:"companyType"`
|
||||||
|
Street string `xml:"street"`
|
||||||
|
Number string `xml:"number"`
|
||||||
|
PostalCode string `xml:"postalCode"`
|
||||||
|
City string `xml:"city"`
|
||||||
|
PhoneNumber string `xml:"phoneNumber"`
|
||||||
|
FaxNumber string `xml:"faxNumber"`
|
||||||
|
Email string `xml:"email"`
|
||||||
|
Country string `xml:"country"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhoisContacts is just an array of WhoisContact
|
||||||
|
// basically only here so it can implement paramsEncoder
|
||||||
|
type WhoisContacts []WhoisContact
|
||||||
|
|
||||||
|
// EncodeParams returns WhoisContacts parameters ready to be used for constructing a signature
|
||||||
|
func (w WhoisContacts) EncodeParams(prm gotransip.ParamsContainer) {
|
||||||
|
idx := prm.Len()
|
||||||
|
for i, e := range w {
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][type]", idx, i), e.Type)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][firstName]", idx, i), e.FirstName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][middleName]", idx, i), e.MiddleName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][lastName]", idx, i), e.LastName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][companyName]", idx, i), e.CompanyName)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][companyKvk]", idx, i), e.CompanyKvk)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][companyType]", idx, i), e.CompanyType)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][street]", idx, i), e.Street)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][number]", idx, i), e.Number)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][postalCode]", idx, i), e.PostalCode)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][city]", idx, i), e.City)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][phoneNumber]", idx, i), e.PhoneNumber)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][faxNumber]", idx, i), e.FaxNumber)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][email]", idx, i), e.Email)
|
||||||
|
prm.Add(fmt.Sprintf("%d[%d][country]", idx, i), e.Country)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeArgs returns WhoisContacts XML body ready to be passed in the SOAP call
|
||||||
|
func (w WhoisContacts) EncodeArgs(key string) string {
|
||||||
|
output := fmt.Sprintf(`<%s SOAP-ENC:arrayType="ns1:WhoisContact[%d]" xsi:type="ns1:ArrayOfWhoisContact">`, key, len(w)) + "\n"
|
||||||
|
for _, e := range w {
|
||||||
|
output += fmt.Sprintf(` <item xsi:type="ns1:WhoisContact">
|
||||||
|
<type xsi:type="xsd:string">%s</type>
|
||||||
|
<firstName xsi:type="xsd:string">%s</firstName>
|
||||||
|
<middleName xsi:type="xsd:string">%s</middleName>
|
||||||
|
<lastName xsi:type="xsd:string">%s</lastName>
|
||||||
|
<companyName xsi:type="xsd:string">%s</companyName>
|
||||||
|
<companyKvk xsi:type="xsd:string">%s</companyKvk>
|
||||||
|
<companyType xsi:type="xsd:string">%s</companyType>
|
||||||
|
<street xsi:type="xsd:string">%s</street>
|
||||||
|
<number xsi:type="xsd:string">%s</number>
|
||||||
|
<postalCode xsi:type="xsd:string">%s</postalCode>
|
||||||
|
<city xsi:type="xsd:string">%s</city>
|
||||||
|
<phoneNumber xsi:type="xsd:string">%s</phoneNumber>
|
||||||
|
<faxNumber xsi:type="xsd:string">%s</faxNumber>
|
||||||
|
<email xsi:type="xsd:string">%s</email>
|
||||||
|
<country xsi:type="xsd:string">%s</country>
|
||||||
|
</item>`, e.Type, e.FirstName, e.MiddleName, e.LastName, e.CompanyName,
|
||||||
|
e.CompanyKvk, e.CompanyType, e.Street, e.Number, e.PostalCode, e.City,
|
||||||
|
e.PhoneNumber, e.FaxNumber, e.Email, e.Country) + "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
return output + fmt.Sprintf("</%s>", key)
|
||||||
|
}
|
49
vendor/github.com/transip/gotransip/sign.go
generated
vendored
Normal file
49
vendor/github.com/transip/gotransip/sign.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package gotransip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha512"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
asn1Header = []byte{
|
||||||
|
0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03,
|
||||||
|
0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func signWithKey(params *soapParams, key []byte) (string, error) {
|
||||||
|
// create SHA512 hash of given parameters
|
||||||
|
h := sha512.New()
|
||||||
|
h.Write([]byte(params.Encode()))
|
||||||
|
|
||||||
|
// prefix ASN1 header to SHA512 hash
|
||||||
|
digest := append(asn1Header, h.Sum(nil)...)
|
||||||
|
|
||||||
|
// prepare key struct
|
||||||
|
block, _ := pem.Decode(key)
|
||||||
|
if block == nil {
|
||||||
|
return "", errors.New("could not decode private key")
|
||||||
|
}
|
||||||
|
parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not parse private key: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
pkey := parsed.(*rsa.PrivateKey)
|
||||||
|
|
||||||
|
enc, err := rsa.SignPKCS1v15(rand.Reader, pkey, crypto.Hash(0), digest)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("could not sign data: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.QueryEscape(base64.StdEncoding.EncodeToString(enc)), nil
|
||||||
|
}
|
419
vendor/github.com/transip/gotransip/soap.go
generated
vendored
Normal file
419
vendor/github.com/transip/gotransip/soap.go
generated
vendored
Normal file
|
@ -0,0 +1,419 @@
|
||||||
|
package gotransip
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// format for SOAP envelopes
|
||||||
|
soapEnvelopeFixture string = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="%s">
|
||||||
|
<SOAP-ENV:Body>%s</SOAP-ENV:Body>
|
||||||
|
</SOAP-ENV:Envelope>`
|
||||||
|
)
|
||||||
|
|
||||||
|
// getSOAPArgs returns XML representing given name and argument as SOAP body
|
||||||
|
func getSOAPArgs(name string, input ...string) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
buf.WriteString(fmt.Sprintf("<ns1:%s>", name))
|
||||||
|
for _, x := range input {
|
||||||
|
buf.WriteString(x)
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("</ns1:%s>", name))
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSOAPArg returns XML representing given input argument as SOAP parameters
|
||||||
|
// in combination with getSOAPArgs you can build SOAP body
|
||||||
|
func getSOAPArg(name string, input interface{}) (output string) {
|
||||||
|
switch input.(type) {
|
||||||
|
case []string:
|
||||||
|
i := input.([]string)
|
||||||
|
output = fmt.Sprintf(`<%s SOAP-ENC:arrayType="xsd:string[%d]" xsi:type="ns1:ArrayOfString">`, name, len(i))
|
||||||
|
for _, x := range i {
|
||||||
|
output = output + fmt.Sprintf(`<item xsi:type="xsd:string">%s</item>`, x)
|
||||||
|
}
|
||||||
|
output = output + fmt.Sprintf("</%s>", name)
|
||||||
|
case string:
|
||||||
|
output = fmt.Sprintf(`<%s xsi:type="xsd:string">%s</%s>`, name, input, name)
|
||||||
|
case int, int32, int64:
|
||||||
|
output = fmt.Sprintf(`<%s xsi:type="xsd:integer">%d</%s>`, name, input, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapFault struct {
|
||||||
|
Code string `xml:"faultcode,omitempty"`
|
||||||
|
Description string `xml:"faultstring,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s soapFault) String() string {
|
||||||
|
return fmt.Sprintf("SOAP Fault %s: %s", s.Code, s.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
// paramsEncoder allows SoapParams to hook into encoding theirselves, useful when
|
||||||
|
// fields consist of complex structs
|
||||||
|
type paramsEncoder interface {
|
||||||
|
EncodeParams(ParamsContainer)
|
||||||
|
EncodeArgs(string) string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParamsContainer is the interface a type should implement to be able to hold
|
||||||
|
// SOAP parameters
|
||||||
|
type ParamsContainer interface {
|
||||||
|
Len() int
|
||||||
|
Add(string, interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// soapParams is a utility to make sure parameter data is encoded into a query
|
||||||
|
// in the same order as we set them. The TransIP API requires this order for
|
||||||
|
// verifying the signature
|
||||||
|
type soapParams struct {
|
||||||
|
keys []string
|
||||||
|
values []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds parameter data to the end of this SoapParams
|
||||||
|
func (s *soapParams) Add(k string, v interface{}) {
|
||||||
|
if s.keys == nil {
|
||||||
|
s.keys = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.values == nil {
|
||||||
|
s.values = make([]interface{}, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.keys = append(s.keys, k)
|
||||||
|
s.values = append(s.values, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns amount of parameters set in this SoapParams
|
||||||
|
func (s soapParams) Len() int {
|
||||||
|
return len(s.keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns a URL-like query string that can be used to generate a request's
|
||||||
|
// signature. It's similar to url.Values.Encode() but without sorting of the keys
|
||||||
|
// and based on the value's type it tries to encode accordingly.
|
||||||
|
func (s soapParams) Encode() string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var key string
|
||||||
|
|
||||||
|
for i, v := range s.values {
|
||||||
|
// if this is not the first parameter, prefix with &
|
||||||
|
if i > 0 {
|
||||||
|
buf.WriteString("&")
|
||||||
|
}
|
||||||
|
|
||||||
|
// for empty data fields, don't encode anything
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key = s.keys[i]
|
||||||
|
|
||||||
|
switch v.(type) {
|
||||||
|
case []string:
|
||||||
|
c := v.([]string)
|
||||||
|
for j, cc := range c {
|
||||||
|
if j > 0 {
|
||||||
|
buf.WriteString("&")
|
||||||
|
}
|
||||||
|
buf.WriteString(fmt.Sprintf("%s[%d]=", key, j))
|
||||||
|
buf.WriteString(strings.Replace(url.QueryEscape(cc), "+", "%20", -1))
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
c := v.(string)
|
||||||
|
buf.WriteString(fmt.Sprintf("%s=", key))
|
||||||
|
buf.WriteString(strings.Replace(url.QueryEscape(c), "+", "%20", -1))
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
buf.WriteString(fmt.Sprintf("%s=", key))
|
||||||
|
buf.WriteString(fmt.Sprintf("%d", v))
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapHeader struct {
|
||||||
|
XMLName struct{} `xml:"Header"`
|
||||||
|
Contents []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapBody struct {
|
||||||
|
XMLName struct{} `xml:"Body"`
|
||||||
|
Contents []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapResponse struct {
|
||||||
|
Response struct {
|
||||||
|
InnerXML []byte `xml:",innerxml"`
|
||||||
|
} `xml:"return"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapEnvelope struct {
|
||||||
|
XMLName struct{} `xml:"Envelope"`
|
||||||
|
Header soapHeader
|
||||||
|
Body soapBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// SoapRequest holds all information for perfoming a SOAP request
|
||||||
|
// Arguments to the request can be specified with AddArgument
|
||||||
|
// If padding is defined, the SOAP response will be parsed after it being padded
|
||||||
|
// with items in Padding in reverse order
|
||||||
|
type SoapRequest struct {
|
||||||
|
Service string
|
||||||
|
Method string
|
||||||
|
params *soapParams // params used for creating signature
|
||||||
|
args []string // XML body arguments
|
||||||
|
Padding []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddArgument adds an argument to the SoapRequest; the arguments ared used to
|
||||||
|
// fill the XML request body as well as to create a valid signature for the
|
||||||
|
// request
|
||||||
|
func (sr *SoapRequest) AddArgument(key string, value interface{}) {
|
||||||
|
if sr.params == nil {
|
||||||
|
sr.params = &soapParams{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if value implements paramsEncoder
|
||||||
|
if pe, ok := value.(paramsEncoder); ok {
|
||||||
|
sr.args = append(sr.args, pe.EncodeArgs(key))
|
||||||
|
pe.EncodeParams(sr.params)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value.(type) {
|
||||||
|
case []string:
|
||||||
|
sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
|
||||||
|
sr.args = append(sr.args, getSOAPArg(key, value))
|
||||||
|
case string:
|
||||||
|
sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
|
||||||
|
sr.args = append(sr.args, getSOAPArg(key, value.(string)))
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), value)
|
||||||
|
sr.args = append(sr.args, getSOAPArg(key, fmt.Sprintf("%d", value)))
|
||||||
|
default:
|
||||||
|
// check if value implements the String interface
|
||||||
|
if str, ok := value.(fmt.Stringer); ok {
|
||||||
|
sr.params.Add(fmt.Sprintf("%d", sr.params.Len()), str.String())
|
||||||
|
sr.args = append(sr.args, getSOAPArg(key, str.String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sr SoapRequest) getEnvelope() string {
|
||||||
|
return fmt.Sprintf(soapEnvelopeFixture, transipAPIHost, getSOAPArgs(sr.Method, sr.args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
type soapClient struct {
|
||||||
|
Login string
|
||||||
|
Mode APIMode
|
||||||
|
PrivateKey []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// httpReqForSoapRequest creates the HTTP request for a specific SoapRequest
|
||||||
|
// this includes setting the URL, POST body and cookies
|
||||||
|
func (s soapClient) httpReqForSoapRequest(req SoapRequest) (*http.Request, error) {
|
||||||
|
// format URL
|
||||||
|
url := fmt.Sprintf("https://%s/soap/?service=%s", transipAPIHost, req.Service)
|
||||||
|
|
||||||
|
// create HTTP request
|
||||||
|
// TransIP API SOAP requests are always POST requests
|
||||||
|
httpReq, err := http.NewRequest("POST", url, strings.NewReader(req.getEnvelope()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate a number-used-once, a.k.a. nonce
|
||||||
|
// seeding the RNG is important if we want to do prevent using the same nonce
|
||||||
|
// in 2 sequential requests
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
nonce := fmt.Sprintf("%d", rand.Int())
|
||||||
|
// set time of request, used later for signature as well
|
||||||
|
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||||
|
|
||||||
|
// set cookies required for the request
|
||||||
|
// most of these cookies are used for the signature as well so they should
|
||||||
|
// obviously match
|
||||||
|
httpReq.AddCookie(&http.Cookie{
|
||||||
|
Name: "login",
|
||||||
|
Value: s.Login,
|
||||||
|
})
|
||||||
|
httpReq.AddCookie(&http.Cookie{
|
||||||
|
Name: "mode",
|
||||||
|
Value: string(s.Mode),
|
||||||
|
})
|
||||||
|
httpReq.AddCookie(&http.Cookie{
|
||||||
|
Name: "timestamp",
|
||||||
|
Value: timestamp,
|
||||||
|
})
|
||||||
|
httpReq.AddCookie(&http.Cookie{
|
||||||
|
Name: "nonce",
|
||||||
|
Value: nonce,
|
||||||
|
})
|
||||||
|
|
||||||
|
// add params required for signature to the request parameters
|
||||||
|
if req.params == nil {
|
||||||
|
req.params = &soapParams{}
|
||||||
|
}
|
||||||
|
// TransIP API is quite picky on the order of the parameters
|
||||||
|
// so don't change anything in the order below
|
||||||
|
req.params.Add("__method", req.Method)
|
||||||
|
req.params.Add("__service", req.Service)
|
||||||
|
req.params.Add("__hostname", transipAPIHost)
|
||||||
|
req.params.Add("__timestamp", timestamp)
|
||||||
|
req.params.Add("__nonce", nonce)
|
||||||
|
|
||||||
|
signature, err := signWithKey(req.params, s.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// add signature of the request to the cookies as well
|
||||||
|
httpReq.AddCookie(&http.Cookie{
|
||||||
|
Name: "signature",
|
||||||
|
Value: signature,
|
||||||
|
})
|
||||||
|
|
||||||
|
return httpReq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSoapResponse(data []byte, padding []string, statusCode int, result interface{}) error {
|
||||||
|
// try to decode the resulting XML
|
||||||
|
var env soapEnvelope
|
||||||
|
if err := xml.Unmarshal(data, &env); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to decode the body to a soapFault
|
||||||
|
var fault soapFault
|
||||||
|
if err := xml.Unmarshal(env.Body.Contents, &fault); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// by checking fault's Code, we can determine if the response body in fact
|
||||||
|
// was a SOAP fault and if it was: return it as an error
|
||||||
|
if len(fault.Code) > 0 {
|
||||||
|
return errors.New(fault.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to decode into soapResponse
|
||||||
|
sr := soapResponse{}
|
||||||
|
if err := xml.Unmarshal(env.Body.Contents, &sr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the response was empty and HTTP status was 200, consider it a success
|
||||||
|
if len(sr.Response.InnerXML) == 0 && statusCode == 200 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// it seems like xml.Unmarshal won't work well on the most outer element
|
||||||
|
// so even when no Padding is defined, always pad with "transip" element
|
||||||
|
p := append([]string{"transip"}, padding...)
|
||||||
|
innerXML := padXMLData(sr.Response.InnerXML, p)
|
||||||
|
|
||||||
|
// try to decode to given result interface
|
||||||
|
return xml.Unmarshal([]byte(innerXML), &result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *soapClient) call(req SoapRequest, result interface{}) error {
|
||||||
|
// get http request for soap request
|
||||||
|
httpReq, err := s.httpReqForSoapRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create HTTP client and do the actual request
|
||||||
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
|
// make sure to verify the validity of remote certificate
|
||||||
|
// this is the default, but adding this flag here makes it easier to toggle
|
||||||
|
// it for testing/debugging
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
InsecureSkipVerify: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("request error:\n%s", err.Error())
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// read entire response body
|
||||||
|
b, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse SOAP response into given result interface
|
||||||
|
return parseSoapResponse(b, req.Padding, resp.StatusCode, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply given padding around the XML data fed into this function
|
||||||
|
// padding is applied in reverse order, so last element of padding is the
|
||||||
|
// innermost element in the resulting XML
|
||||||
|
func padXMLData(data []byte, padding []string) []byte {
|
||||||
|
// get right information from padding elements by matching to regex
|
||||||
|
re, _ := regexp.Compile("^<?(?:([^ ]+) )?([^>]+)>?$")
|
||||||
|
|
||||||
|
var prefix, suffix []byte
|
||||||
|
var tag, attr string
|
||||||
|
// go over each padding element
|
||||||
|
for i := len(padding); i > 0; i-- {
|
||||||
|
res := re.FindStringSubmatch(padding[i-1])
|
||||||
|
// no attribute was given
|
||||||
|
if len(res[1]) == 0 {
|
||||||
|
tag = res[2]
|
||||||
|
attr = ""
|
||||||
|
} else {
|
||||||
|
tag = res[1]
|
||||||
|
attr = " " + res[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix = []byte(fmt.Sprintf("<%s%s>", tag, attr))
|
||||||
|
suffix = []byte(fmt.Sprintf("</%s>", tag))
|
||||||
|
data = append(append(prefix, data...), suffix...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestParamsContainer is only useful for unit testing the ParamsContainer
|
||||||
|
// implementation of other type
|
||||||
|
type TestParamsContainer struct {
|
||||||
|
Prm string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add just makes sure we use Len(), key and value in the result so it can be
|
||||||
|
// tested
|
||||||
|
func (t *TestParamsContainer) Add(key string, value interface{}) {
|
||||||
|
var prefix string
|
||||||
|
if t.Len() > 0 {
|
||||||
|
prefix = "&"
|
||||||
|
}
|
||||||
|
t.Prm = t.Prm + prefix + fmt.Sprintf("%d%s=%s", t.Len(), key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns current length of test data in TestParamsContainer
|
||||||
|
func (t TestParamsContainer) Len() int {
|
||||||
|
return len(t.Prm)
|
||||||
|
}
|
37
vendor/github.com/transip/gotransip/util/util.go
generated
vendored
Normal file
37
vendor/github.com/transip/gotransip/util/util.go
generated
vendored
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyValueXML resembles the complex struct for getting key/value pairs from XML
|
||||||
|
type KeyValueXML struct {
|
||||||
|
Cont []struct {
|
||||||
|
Item []struct {
|
||||||
|
Key string `xml:"key"`
|
||||||
|
Value string `xml:"value"`
|
||||||
|
} `xml:"item"`
|
||||||
|
} `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// XMLTime is a custom type to decode XML values to time.Time directly
|
||||||
|
type XMLTime struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalXML is implemented to be able act as custom XML type
|
||||||
|
// it tries to parse time from given elements value
|
||||||
|
func (x *XMLTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
var v string
|
||||||
|
if err := d.DecodeElement(&v, &start); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, _ := time.Parse("2006-01-02 15:04:05", v); !p.IsZero() {
|
||||||
|
*x = XMLTime{p}
|
||||||
|
} else if p, _ := time.Parse("2006-01-02", v); !p.IsZero() {
|
||||||
|
*x = XMLTime{p}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue