mirror of
				https://github.com/go-acme/lego.git
				synced 2025-10-31 16:37:41 +02:00 
			
		
		
		
	joker: add support for SVC API (#1267)
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							abd783a124
						
					
				
				
					commit
					fbab0e3c64
				
			| @@ -975,7 +975,8 @@ func displayDNSHelp(name string) error { | ||||
| 		ew.writeln() | ||||
|  | ||||
| 		ew.writeln(`Credentials:`) | ||||
| 		ew.writeln(`	- "JOKER_API_KEY":	API key`) | ||||
| 		ew.writeln(`	- "JOKER_API_KEY":	API key (only with DMAPI mode)`) | ||||
| 		ew.writeln(`	- "JOKER_API_MODE":	'DMAPI' or 'SVC'. DMAPI is for resellers accounts. (Default: DMAPI)`) | ||||
| 		ew.writeln(`	- "JOKER_PASSWORD":	Joker.com password`) | ||||
| 		ew.writeln(`	- "JOKER_USERNAME":	Joker.com username (email address)`) | ||||
| 		ew.writeln() | ||||
|   | ||||
| @@ -21,10 +21,19 @@ Configuration for [Joker](https://joker.com). | ||||
| Here is an example bash command using the Joker provider: | ||||
|  | ||||
| ```bash | ||||
| # SVC | ||||
| JOKER_API_MODE=SVC \ | ||||
| JOKER_USERNAME=<your email> \ | ||||
| JOKER_PASSWORD=<your password> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| # or | ||||
|  | ||||
| # DMAPI | ||||
| JOKER_API_MODE=DMAPI \ | ||||
| JOKER_USERNAME=<your email> \ | ||||
| JOKER_PASSWORD=<your password> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| ## or | ||||
| JOKER_API_MODE=DMAPI \ | ||||
| JOKER_API_KEY=<your API key> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| ``` | ||||
| @@ -36,7 +45,8 @@ lego --dns joker --domains my.domain.com --email my@email.com run | ||||
|  | ||||
| | Environment Variable Name | Description | | ||||
| |-----------------------|-------------| | ||||
| | `JOKER_API_KEY` | API key | | ||||
| | `JOKER_API_KEY` | API key (only with DMAPI mode) | | ||||
| | `JOKER_API_MODE` | 'DMAPI' or 'SVC'. DMAPI is for resellers accounts. (Default: DMAPI) | | ||||
| | `JOKER_PASSWORD` | Joker.com password | | ||||
| | `JOKER_USERNAME` | Joker.com username (email address) | | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| package joker | ||||
| // Package dmapi Client for DMAPI joker.com. | ||||
| // https://joker.com/faq/category/39/22-dmapi.html | ||||
| package dmapi | ||||
| 
 | ||||
| import ( | ||||
| 	"errors" | ||||
| @@ -6,6 +8,7 @@ import ( | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"path" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| @@ -15,8 +18,8 @@ import ( | ||||
| 
 | ||||
| const defaultBaseURL = "https://dmapi.joker.com/request/" | ||||
| 
 | ||||
| // Joker DMAPI Response. | ||||
| type response struct { | ||||
| // Response Joker DMAPI Response. | ||||
| type Response struct { | ||||
| 	Headers    url.Values | ||||
| 	Body       string | ||||
| 	StatusCode int | ||||
| @@ -24,9 +27,143 @@ type response struct { | ||||
| 	AuthSid    string | ||||
| } | ||||
| 
 | ||||
| type AuthInfo struct { | ||||
| 	APIKey   string | ||||
| 	Username string | ||||
| 	Password string | ||||
| 	authSid  string | ||||
| } | ||||
| 
 | ||||
| // Client a DMAPI Client. | ||||
| type Client struct { | ||||
| 	HTTPClient *http.Client | ||||
| 	BaseURL    string | ||||
| 
 | ||||
| 	Debug bool | ||||
| 
 | ||||
| 	auth AuthInfo | ||||
| } | ||||
| 
 | ||||
| // NewClient creates a new DMAPI Client. | ||||
| func NewClient(auth AuthInfo) *Client { | ||||
| 	return &Client{ | ||||
| 		HTTPClient: http.DefaultClient, | ||||
| 		BaseURL:    defaultBaseURL, | ||||
| 		Debug:      false, | ||||
| 		auth:       auth, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // Login performs a login to Joker's DMAPI. | ||||
| func (c *Client) Login() (*Response, error) { | ||||
| 	if c.auth.authSid != "" { | ||||
| 		// already logged in | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var values url.Values | ||||
| 	switch { | ||||
| 	case c.auth.Username != "" && c.auth.Password != "": | ||||
| 		values = url.Values{ | ||||
| 			"username": {c.auth.Username}, | ||||
| 			"password": {c.auth.Password}, | ||||
| 		} | ||||
| 	case c.auth.APIKey != "": | ||||
| 		values = url.Values{"api-key": {c.auth.APIKey}} | ||||
| 	default: | ||||
| 		return nil, errors.New("no username and password or api-key") | ||||
| 	} | ||||
| 
 | ||||
| 	response, err := c.postRequest("login", values) | ||||
| 	if err != nil { | ||||
| 		return response, err | ||||
| 	} | ||||
| 
 | ||||
| 	if response == nil { | ||||
| 		return nil, errors.New("login returned nil response") | ||||
| 	} | ||||
| 
 | ||||
| 	if response.AuthSid == "" { | ||||
| 		return response, errors.New("login did not return valid Auth-Sid") | ||||
| 	} | ||||
| 
 | ||||
| 	c.auth.authSid = response.AuthSid | ||||
| 
 | ||||
| 	return response, nil | ||||
| } | ||||
| 
 | ||||
| // Logout closes authenticated session with Joker's DMAPI. | ||||
| func (c *Client) Logout() (*Response, error) { | ||||
| 	if c.auth.authSid == "" { | ||||
| 		return nil, errors.New("already logged out") | ||||
| 	} | ||||
| 
 | ||||
| 	response, err := c.postRequest("logout", url.Values{}) | ||||
| 	if err == nil { | ||||
| 		c.auth.authSid = "" | ||||
| 	} | ||||
| 	return response, err | ||||
| } | ||||
| 
 | ||||
| // GetZone returns content of DNS zone for domain. | ||||
| func (c *Client) GetZone(domain string) (*Response, error) { | ||||
| 	if c.auth.authSid == "" { | ||||
| 		return nil, errors.New("must be logged in to get zone") | ||||
| 	} | ||||
| 
 | ||||
| 	return c.postRequest("dns-zone-get", url.Values{"domain": {dns01.UnFqdn(domain)}}) | ||||
| } | ||||
| 
 | ||||
| // PutZone uploads DNS zone to Joker DMAPI. | ||||
| func (c *Client) PutZone(domain, zone string) (*Response, error) { | ||||
| 	if c.auth.authSid == "" { | ||||
| 		return nil, errors.New("must be logged in to put zone") | ||||
| 	} | ||||
| 
 | ||||
| 	return c.postRequest("dns-zone-put", url.Values{"domain": {dns01.UnFqdn(domain)}, "zone": {strings.TrimSpace(zone)}}) | ||||
| } | ||||
| 
 | ||||
| // postRequest performs actual HTTP request. | ||||
| func (c *Client) postRequest(cmd string, data url.Values) (*Response, error) { | ||||
| 	baseURL, err := url.Parse(c.BaseURL) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	endpoint, err := baseURL.Parse(path.Join(baseURL.Path, cmd)) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if c.auth.authSid != "" { | ||||
| 		data.Set("auth-sid", c.auth.authSid) | ||||
| 	} | ||||
| 
 | ||||
| 	if c.Debug { | ||||
| 		log.Infof("postRequest:\n\tURL: %q\n\tData: %v", endpoint.String(), data) | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := c.HTTPClient.PostForm(endpoint.String(), data) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return nil, fmt.Errorf("HTTP error %d [%s]: %v", resp.StatusCode, http.StatusText(resp.StatusCode), string(body)) | ||||
| 	} | ||||
| 
 | ||||
| 	return parseResponse(string(body)), nil | ||||
| } | ||||
| 
 | ||||
| // parseResponse parses HTTP response body. | ||||
| func parseResponse(message string) *response { | ||||
| 	r := &response{Headers: url.Values{}, StatusCode: -1} | ||||
| func parseResponse(message string) *Response { | ||||
| 	r := &Response{Headers: url.Values{}, StatusCode: -1} | ||||
| 
 | ||||
| 	parts := strings.SplitN(message, "\n\n", 2) | ||||
| 
 | ||||
| @@ -64,105 +201,6 @@ func parseResponse(message string) *response { | ||||
| 	return r | ||||
| } | ||||
| 
 | ||||
| // login performs a login to Joker's DMAPI. | ||||
| func (d *DNSProvider) login() (*response, error) { | ||||
| 	if d.config.AuthSid != "" { | ||||
| 		// already logged in | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 
 | ||||
| 	var values url.Values | ||||
| 	switch { | ||||
| 	case d.config.Username != "" && d.config.Password != "": | ||||
| 		values = url.Values{ | ||||
| 			"username": {d.config.Username}, | ||||
| 			"password": {d.config.Password}, | ||||
| 		} | ||||
| 	case d.config.APIKey != "": | ||||
| 		values = url.Values{"api-key": {d.config.APIKey}} | ||||
| 	default: | ||||
| 		return nil, errors.New("no username and password or api-key") | ||||
| 	} | ||||
| 
 | ||||
| 	response, err := d.postRequest("login", values) | ||||
| 	if err != nil { | ||||
| 		return response, err | ||||
| 	} | ||||
| 
 | ||||
| 	if response == nil { | ||||
| 		return nil, errors.New("login returned nil response") | ||||
| 	} | ||||
| 
 | ||||
| 	if response.AuthSid == "" { | ||||
| 		return response, errors.New("login did not return valid Auth-Sid") | ||||
| 	} | ||||
| 
 | ||||
| 	d.config.AuthSid = response.AuthSid | ||||
| 
 | ||||
| 	return response, nil | ||||
| } | ||||
| 
 | ||||
| // logout closes authenticated session with Joker's DMAPI. | ||||
| func (d *DNSProvider) logout() (*response, error) { | ||||
| 	if d.config.AuthSid == "" { | ||||
| 		return nil, errors.New("already logged out") | ||||
| 	} | ||||
| 
 | ||||
| 	response, err := d.postRequest("logout", url.Values{}) | ||||
| 	if err == nil { | ||||
| 		d.config.AuthSid = "" | ||||
| 	} | ||||
| 	return response, err | ||||
| } | ||||
| 
 | ||||
| // getZone returns content of DNS zone for domain. | ||||
| func (d *DNSProvider) getZone(domain string) (*response, error) { | ||||
| 	if d.config.AuthSid == "" { | ||||
| 		return nil, errors.New("must be logged in to get zone") | ||||
| 	} | ||||
| 
 | ||||
| 	return d.postRequest("dns-zone-get", url.Values{"domain": {dns01.UnFqdn(domain)}}) | ||||
| } | ||||
| 
 | ||||
| // putZone uploads DNS zone to Joker DMAPI. | ||||
| func (d *DNSProvider) putZone(domain, zone string) (*response, error) { | ||||
| 	if d.config.AuthSid == "" { | ||||
| 		return nil, errors.New("must be logged in to put zone") | ||||
| 	} | ||||
| 
 | ||||
| 	return d.postRequest("dns-zone-put", url.Values{"domain": {dns01.UnFqdn(domain)}, "zone": {strings.TrimSpace(zone)}}) | ||||
| } | ||||
| 
 | ||||
| // postRequest performs actual HTTP request. | ||||
| func (d *DNSProvider) postRequest(cmd string, data url.Values) (*response, error) { | ||||
| 	uri := d.config.BaseURL + cmd | ||||
| 
 | ||||
| 	if d.config.AuthSid != "" { | ||||
| 		data.Set("auth-sid", d.config.AuthSid) | ||||
| 	} | ||||
| 
 | ||||
| 	if d.config.Debug { | ||||
| 		log.Infof("postRequest:\n\tURL: %q\n\tData: %v", uri, data) | ||||
| 	} | ||||
| 
 | ||||
| 	resp, err := d.config.HTTPClient.PostForm(uri, data) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer resp.Body.Close() | ||||
| 
 | ||||
| 	body, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 
 | ||||
| 	if resp.StatusCode != 200 { | ||||
| 		return nil, fmt.Errorf("HTTP error %d [%s]: %v", resp.StatusCode, http.StatusText(resp.StatusCode), string(body)) | ||||
| 	} | ||||
| 
 | ||||
| 	return parseResponse(string(body)), nil | ||||
| } | ||||
| 
 | ||||
| // Temporary workaround, until it get fixed on API side. | ||||
| func fixTxtLines(line string) string { | ||||
| 	fields := strings.Fields(line) | ||||
| @@ -179,8 +217,8 @@ func fixTxtLines(line string) string { | ||||
| 	return strings.Join(fields, " ") | ||||
| } | ||||
| 
 | ||||
| // removeTxtEntryFromZone clean-ups all TXT records with given name. | ||||
| func removeTxtEntryFromZone(zone, relative string) (string, bool) { | ||||
| // RemoveTxtEntryFromZone clean-ups all TXT records with given name. | ||||
| func RemoveTxtEntryFromZone(zone, relative string) (string, bool) { | ||||
| 	prefix := fmt.Sprintf("%s TXT 0 ", relative) | ||||
| 
 | ||||
| 	modified := false | ||||
| @@ -196,8 +234,8 @@ func removeTxtEntryFromZone(zone, relative string) (string, bool) { | ||||
| 	return strings.TrimSpace(strings.Join(zoneEntries, "\n")), modified | ||||
| } | ||||
| 
 | ||||
| // addTxtEntryToZone returns DNS zone with added TXT record. | ||||
| func addTxtEntryToZone(zone, relative, value string, ttl int) string { | ||||
| // AddTxtEntryToZone returns DNS zone with added TXT record. | ||||
| func AddTxtEntryToZone(zone, relative, value string, ttl int) string { | ||||
| 	var zoneEntries []string | ||||
| 
 | ||||
| 	for _, line := range strings.Split(zone, "\n") { | ||||
| @@ -1,4 +1,4 @@ | ||||
| package joker | ||||
| package dmapi | ||||
| 
 | ||||
| import ( | ||||
| 	"io" | ||||
| @@ -23,10 +23,13 @@ const ( | ||||
| 	serverErrorUsername = "error" | ||||
| ) | ||||
| 
 | ||||
| func setup() (*http.ServeMux, *httptest.Server) { | ||||
| func setup(t *testing.T) (*http.ServeMux, string) { | ||||
| 	mux := http.NewServeMux() | ||||
| 
 | ||||
| 	server := httptest.NewServer(mux) | ||||
| 	return mux, server | ||||
| 	t.Cleanup(server.Close) | ||||
| 
 | ||||
| 	return mux, server.URL | ||||
| } | ||||
| 
 | ||||
| func TestDNSProvider_login_api_key(t *testing.T) { | ||||
| @@ -63,11 +66,10 @@ func TestDNSProvider_login_api_key(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	mux, server := setup() | ||||
| 	defer server.Close() | ||||
| 	mux, serverURL := setup(t) | ||||
| 
 | ||||
| 	mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		require.Equal(t, "POST", r.Method) | ||||
| 		require.Equal(t, http.MethodPost, r.Method) | ||||
| 
 | ||||
| 		switch r.FormValue("api-key") { | ||||
| 		case correctAPIKey: | ||||
| @@ -83,15 +85,10 @@ func TestDNSProvider_login_api_key(t *testing.T) { | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.BaseURL = server.URL | ||||
| 			config.APIKey = test.apiKey | ||||
| 			client := NewClient(AuthInfo{APIKey: test.apiKey}) | ||||
| 			client.BaseURL = serverURL | ||||
| 
 | ||||
| 			p, err := NewDNSProviderConfig(config) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
| 
 | ||||
| 			response, err := p.login() | ||||
| 			response, err := client.Login() | ||||
| 			if test.expectedError { | ||||
| 				require.Error(t, err) | ||||
| 			} else { | ||||
| @@ -144,11 +141,10 @@ func TestDNSProvider_login_username(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	mux, server := setup() | ||||
| 	defer server.Close() | ||||
| 	mux, serverURL := setup(t) | ||||
| 
 | ||||
| 	mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		require.Equal(t, "POST", r.Method) | ||||
| 		require.Equal(t, http.MethodPost, r.Method) | ||||
| 
 | ||||
| 		switch r.FormValue("username") { | ||||
| 		case correctUsername: | ||||
| @@ -164,16 +160,10 @@ func TestDNSProvider_login_username(t *testing.T) { | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.BaseURL = server.URL | ||||
| 			config.Username = test.username | ||||
| 			config.Password = test.password | ||||
| 			client := NewClient(AuthInfo{Username: test.username, Password: test.password}) | ||||
| 			client.BaseURL = serverURL | ||||
| 
 | ||||
| 			p, err := NewDNSProviderConfig(config) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
| 
 | ||||
| 			response, err := p.login() | ||||
| 			response, err := client.Login() | ||||
| 			if test.expectedError { | ||||
| 				require.Error(t, err) | ||||
| 			} else { | ||||
| @@ -215,11 +205,10 @@ func TestDNSProvider_logout(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	mux, server := setup() | ||||
| 	defer server.Close() | ||||
| 	mux, serverURL := setup(t) | ||||
| 
 | ||||
| 	mux.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		require.Equal(t, "POST", r.Method) | ||||
| 		require.Equal(t, http.MethodPost, r.Method) | ||||
| 
 | ||||
| 		switch r.FormValue("auth-sid") { | ||||
| 		case correctAPIKey: | ||||
| @@ -233,16 +222,10 @@ func TestDNSProvider_logout(t *testing.T) { | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.BaseURL = server.URL | ||||
| 			config.APIKey = "12345" | ||||
| 			config.AuthSid = test.authSid | ||||
| 			client := NewClient(AuthInfo{APIKey: "12345", authSid: test.authSid}) | ||||
| 			client.BaseURL = serverURL | ||||
| 
 | ||||
| 			p, err := NewDNSProviderConfig(config) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
| 
 | ||||
| 			response, err := p.logout() | ||||
| 			response, err := client.Logout() | ||||
| 			if test.expectedError { | ||||
| 				require.Error(t, err) | ||||
| 			} else { | ||||
| @@ -291,11 +274,10 @@ func TestDNSProvider_getZone(t *testing.T) { | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	mux, server := setup() | ||||
| 	defer server.Close() | ||||
| 	mux, serverURL := setup(t) | ||||
| 
 | ||||
| 	mux.HandleFunc("/dns-zone-get", func(w http.ResponseWriter, r *http.Request) { | ||||
| 		require.Equal(t, "POST", r.Method) | ||||
| 		require.Equal(t, http.MethodPost, r.Method) | ||||
| 
 | ||||
| 		authSid := r.FormValue("auth-sid") | ||||
| 		domain := r.FormValue("domain") | ||||
| @@ -312,16 +294,10 @@ func TestDNSProvider_getZone(t *testing.T) { | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.BaseURL = server.URL | ||||
| 			config.APIKey = "12345" | ||||
| 			config.AuthSid = test.authSid | ||||
| 			client := NewClient(AuthInfo{APIKey: "12345", authSid: test.authSid}) | ||||
| 			client.BaseURL = serverURL | ||||
| 
 | ||||
| 			p, err := NewDNSProviderConfig(config) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
| 
 | ||||
| 			response, err := p.getZone(test.domain) | ||||
| 			response, err := client.GetZone(test.domain) | ||||
| 			if test.expectedError { | ||||
| 				require.Error(t, err) | ||||
| 			} else { | ||||
| @@ -338,12 +314,12 @@ func Test_parseResponse(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		input    string | ||||
| 		expected *response | ||||
| 		expected *Response | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc:  "Empty response", | ||||
| 			input: "", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{}, | ||||
| 				StatusCode: -1, | ||||
| 			}, | ||||
| @@ -351,7 +327,7 @@ func Test_parseResponse(t *testing.T) { | ||||
| 		{ | ||||
| 			desc:  "No headers, just body", | ||||
| 			input: "\n\nTest body", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{}, | ||||
| 				Body:       "Test body", | ||||
| 				StatusCode: -1, | ||||
| @@ -360,7 +336,7 @@ func Test_parseResponse(t *testing.T) { | ||||
| 		{ | ||||
| 			desc:  "Headers and body", | ||||
| 			input: "Test-Header: value\n\nTest body", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{"Test-Header": {"value"}}, | ||||
| 				Body:       "Test body", | ||||
| 				StatusCode: -1, | ||||
| @@ -369,7 +345,7 @@ func Test_parseResponse(t *testing.T) { | ||||
| 		{ | ||||
| 			desc:  "Headers and body + Auth-Sid", | ||||
| 			input: "Test-Header: value\nAuth-Sid: 123\n\nTest body", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{"Test-Header": {"value"}, "Auth-Sid": {"123"}}, | ||||
| 				Body:       "Test body", | ||||
| 				StatusCode: -1, | ||||
| @@ -379,7 +355,7 @@ func Test_parseResponse(t *testing.T) { | ||||
| 		{ | ||||
| 			desc:  "Headers and body + Status-Text", | ||||
| 			input: "Test-Header: value\nStatus-Text: OK\n\nTest body", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{"Test-Header": {"value"}, "Status-Text": {"OK"}}, | ||||
| 				Body:       "Test body", | ||||
| 				StatusText: "OK", | ||||
| @@ -389,7 +365,7 @@ func Test_parseResponse(t *testing.T) { | ||||
| 		{ | ||||
| 			desc:  "Headers and body + Status-Code", | ||||
| 			input: "Test-Header: value\nStatus-Code: 2020\n\nTest body", | ||||
| 			expected: &response{ | ||||
| 			expected: &Response{ | ||||
| 				Headers:    url.Values{"Test-Header": {"value"}, "Status-Code": {"2020"}}, | ||||
| 				Body:       "Test body", | ||||
| 				StatusCode: 2020, | ||||
| @@ -453,7 +429,7 @@ func Test_removeTxtEntryFromZone(t *testing.T) { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			t.Parallel() | ||||
| 
 | ||||
| 			zone, modified := removeTxtEntryFromZone(test.input, "_acme-challenge") | ||||
| 			zone, modified := RemoveTxtEntryFromZone(test.input, "_acme-challenge") | ||||
| 			assert.Equal(t, zone, test.expected) | ||||
| 			assert.Equal(t, modified, test.modified) | ||||
| 		}) | ||||
| @@ -485,7 +461,7 @@ func Test_addTxtEntryToZone(t *testing.T) { | ||||
| 
 | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			zone := addTxtEntryToZone(test.input, "_acme-challenge", "test", 120) | ||||
| 			zone := AddTxtEntryToZone(test.input, "_acme-challenge", "test", 120) | ||||
| 			assert.Equal(t, zone, test.expected) | ||||
| 		}) | ||||
| 	} | ||||
							
								
								
									
										72
									
								
								providers/dns/joker/internal/svc/client.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								providers/dns/joker/internal/svc/client.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| // Package svc Client for the SVC API. | ||||
| // https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html | ||||
| package svc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	querystring "github.com/google/go-querystring/query" | ||||
| ) | ||||
|  | ||||
| const defaultBaseURL = "https://svc.joker.com/nic/replace" | ||||
|  | ||||
| type request struct { | ||||
| 	Username string `url:"username"` | ||||
| 	Password string `url:"password"` | ||||
| 	Zone     string `url:"zone"` | ||||
| 	Label    string `url:"label"` | ||||
| 	Type     string `url:"type"` | ||||
| 	Value    string `url:"value"` | ||||
| } | ||||
|  | ||||
| type Client struct { | ||||
| 	HTTPClient *http.Client | ||||
| 	BaseURL    string | ||||
|  | ||||
| 	username string | ||||
| 	password string | ||||
| } | ||||
|  | ||||
| func NewClient(username, password string) *Client { | ||||
| 	return &Client{ | ||||
| 		HTTPClient: http.DefaultClient, | ||||
| 		BaseURL:    defaultBaseURL, | ||||
| 		username:   username, | ||||
| 		password:   password, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Client) Send(zone, label, value string) error { | ||||
| 	req := request{ | ||||
| 		Username: c.username, | ||||
| 		Password: c.password, | ||||
| 		Zone:     zone, | ||||
| 		Label:    label, | ||||
| 		Type:     "TXT", | ||||
| 		Value:    value, | ||||
| 	} | ||||
|  | ||||
| 	v, err := querystring.Values(req) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	resp, err := c.HTTPClient.PostForm(c.BaseURL, v) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	all, err := ioutil.ReadAll(resp.Body) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if resp.StatusCode == http.StatusOK && strings.HasPrefix(string(all), "OK") { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	return fmt.Errorf("error: %d: %s", resp.StatusCode, string(all)) | ||||
| } | ||||
							
								
								
									
										79
									
								
								providers/dns/joker/internal/svc/client_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								providers/dns/joker/internal/svc/client_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| package svc | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func TestClient_Send(t *testing.T) { | ||||
| 	mux := http.NewServeMux() | ||||
| 	server := httptest.NewServer(mux) | ||||
|  | ||||
| 	mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { | ||||
| 		if req.Method != http.MethodPost { | ||||
| 			http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		all, _ := ioutil.ReadAll(req.Body) | ||||
|  | ||||
| 		if string(all) != "label=_acme-challenge&password=secret&type=TXT&username=test&value=123&zone=example.com" { | ||||
| 			http.Error(rw, fmt.Sprintf("invalid request: %q", string(all)), http.StatusBadRequest) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		_, err := rw.Write([]byte("OK: 1 inserted, 0 deleted")) | ||||
| 		if err != nil { | ||||
| 			http.Error(rw, err.Error(), http.StatusInternalServerError) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	client := NewClient("test", "secret") | ||||
| 	client.BaseURL = server.URL | ||||
|  | ||||
| 	zone := "example.com" | ||||
| 	label := "_acme-challenge" | ||||
| 	value := "123" | ||||
|  | ||||
| 	err := client.Send(zone, label, value) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
|  | ||||
| func TestClient_Send_empty(t *testing.T) { | ||||
| 	mux := http.NewServeMux() | ||||
| 	server := httptest.NewServer(mux) | ||||
|  | ||||
| 	mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) { | ||||
| 		if req.Method != http.MethodPost { | ||||
| 			http.Error(rw, fmt.Sprintf("unsupported method: %s", req.Method), http.StatusMethodNotAllowed) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		all, _ := ioutil.ReadAll(req.Body) | ||||
|  | ||||
| 		if string(all) != "label=_acme-challenge&password=secret&type=TXT&username=test&value=&zone=example.com" { | ||||
| 			http.Error(rw, fmt.Sprintf("invalid request: %q", string(all)), http.StatusBadRequest) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		_, err := rw.Write([]byte("OK: 1 inserted, 0 deleted")) | ||||
| 		if err != nil { | ||||
| 			http.Error(rw, err.Error(), http.StatusInternalServerError) | ||||
| 		} | ||||
| 	}) | ||||
|  | ||||
| 	client := NewClient("test", "secret") | ||||
| 	client.BaseURL = server.URL | ||||
|  | ||||
| 	zone := "example.com" | ||||
| 	label := "_acme-challenge" | ||||
| 	value := "" | ||||
|  | ||||
| 	err := client.Send(zone, label, value) | ||||
| 	require.NoError(t, err) | ||||
| } | ||||
| @@ -1,15 +1,13 @@ | ||||
| // Package joker implements a DNS provider for solving the DNS-01 challenge using joker.com DMAPI. | ||||
| // Package joker implements a DNS provider for solving the DNS-01 challenge using joker.com. | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"os" | ||||
| 	"time" | ||||
|  | ||||
| 	"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" | ||||
| ) | ||||
|  | ||||
| @@ -21,178 +19,64 @@ const ( | ||||
| 	EnvUsername = envNamespace + "USERNAME" | ||||
| 	EnvPassword = envNamespace + "PASSWORD" | ||||
| 	EnvDebug    = envNamespace + "DEBUG" | ||||
| 	EnvMode     = envNamespace + "API_MODE" | ||||
|  | ||||
| 	EnvTTL                = envNamespace + "TTL" | ||||
| 	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT" | ||||
| 	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL" | ||||
| 	EnvSequenceInterval   = envNamespace + "SEQUENCE_INTERVAL" | ||||
| 	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	modeDMAPI = "DMAPI" | ||||
| 	modeSVC   = "SVC" | ||||
| ) | ||||
|  | ||||
| // Config is used to configure the creation of the DNSProvider. | ||||
| type Config struct { | ||||
| 	Debug              bool | ||||
| 	BaseURL            string | ||||
| 	APIKey             string | ||||
| 	Username           string | ||||
| 	Password           string | ||||
| 	APIMode            string | ||||
| 	PropagationTimeout time.Duration | ||||
| 	PollingInterval    time.Duration | ||||
| 	SequenceInterval   time.Duration | ||||
| 	TTL                int | ||||
| 	HTTPClient         *http.Client | ||||
| 	AuthSid            string | ||||
| } | ||||
|  | ||||
| // NewDefaultConfig returns a default configuration for the DNSProvider. | ||||
| func NewDefaultConfig() *Config { | ||||
| 	return &Config{ | ||||
| 		BaseURL:            defaultBaseURL, | ||||
| 		APIMode:            env.GetOrDefaultString(EnvMode, modeDMAPI), | ||||
| 		Debug:              env.GetOrDefaultBool(EnvDebug, false), | ||||
| 		TTL:                env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL), | ||||
| 		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout), | ||||
| 		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 2*time.Minute), | ||||
| 		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval), | ||||
| 		SequenceInterval:   env.GetOrDefaultSecond(EnvSequenceInterval, dns01.DefaultPropagationTimeout), | ||||
| 		HTTPClient: &http.Client{ | ||||
| 			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 60*time.Second), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // DNSProvider implements the challenge.Provider interface. | ||||
| type DNSProvider struct { | ||||
| 	config *Config | ||||
| } | ||||
|  | ||||
| // NewDNSProvider returns a DNSProvider instance configured for Joker DMAPI. | ||||
| // NewDNSProvider returns a DNSProvider instance configured for Joker. | ||||
| // Credentials must be passed in the environment variable JOKER_API_KEY. | ||||
| func NewDNSProvider() (*DNSProvider, error) { | ||||
| 	values, err := env.Get(EnvAPIKey) | ||||
| 	if err != nil { | ||||
| 		var errU error | ||||
| 		values, errU = env.Get(EnvUsername, EnvPassword) | ||||
| 		if errU != nil { | ||||
| 			return nil, fmt.Errorf("joker: %v or %v", errU, err) | ||||
| 		} | ||||
| func NewDNSProvider() (challenge.ProviderTimeout, error) { | ||||
| 	if os.Getenv(EnvMode) == modeSVC { | ||||
| 		return newSvcProvider() | ||||
| 	} | ||||
|  | ||||
| 	config := NewDefaultConfig() | ||||
| 	config.APIKey = values[EnvAPIKey] | ||||
| 	config.Username = values[EnvUsername] | ||||
| 	config.Password = values[EnvPassword] | ||||
|  | ||||
| 	return NewDNSProviderConfig(config) | ||||
| 	return newDmapiProvider() | ||||
| } | ||||
|  | ||||
| // NewDNSProviderConfig return a DNSProvider instance configured for Joker DMAPI. | ||||
| func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { | ||||
| 	if config == nil { | ||||
| 		return nil, errors.New("joker: the configuration of the DNS provider is nil") | ||||
| // NewDNSProviderConfig return a DNSProvider instance configured for Joker. | ||||
| func NewDNSProviderConfig(config *Config) (challenge.ProviderTimeout, error) { | ||||
| 	if config.APIMode == modeSVC { | ||||
| 		return newSvcProviderConfig(config) | ||||
| 	} | ||||
|  | ||||
| 	if config.APIKey == "" { | ||||
| 		if config.Username == "" || config.Password == "" { | ||||
| 			return nil, errors.New("joker: credentials missing") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if !strings.HasSuffix(config.BaseURL, "/") { | ||||
| 		config.BaseURL += "/" | ||||
| 	} | ||||
|  | ||||
| 	return &DNSProvider{config: config}, nil | ||||
| } | ||||
|  | ||||
| // Timeout returns the timeout and interval to use when checking for DNS propagation. | ||||
| func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { | ||||
| 	return d.config.PropagationTimeout, d.config.PollingInterval | ||||
| } | ||||
|  | ||||
| // Present installs a TXT record for the DNS challenge. | ||||
| func (d *DNSProvider) Present(domain, token, keyAuth string) error { | ||||
| 	fqdn, value := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	if d.config.Debug { | ||||
| 		log.Infof("[%s] joker: adding TXT record %q to zone %q with value %q", domain, relative, zone, value) | ||||
| 	} | ||||
|  | ||||
| 	response, err := d.login() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	response, err = d.getZone(zone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	dnsZone := addTxtEntryToZone(response.Body, relative, value, d.config.TTL) | ||||
|  | ||||
| 	response, err = d.putZone(zone, dnsZone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CleanUp removes a TXT record used for a previous DNS challenge. | ||||
| func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { | ||||
| 	fqdn, _ := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	if d.config.Debug { | ||||
| 		log.Infof("[%s] joker: removing entry %q from zone %q", domain, relative, zone) | ||||
| 	} | ||||
|  | ||||
| 	response, err := d.login() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		// Try to logout in case of errors | ||||
| 		_, _ = d.logout() | ||||
| 	}() | ||||
|  | ||||
| 	response, err = d.getZone(zone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	dnsZone, modified := removeTxtEntryFromZone(response.Body, relative) | ||||
| 	if modified { | ||||
| 		response, err = d.putZone(zone, dnsZone) | ||||
| 		if err != nil || response.StatusCode != 0 { | ||||
| 			return formatResponseError(response, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	response, err = d.logout() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getRelative(fqdn, zone string) string { | ||||
| 	return dns01.UnFqdn(strings.TrimSuffix(fqdn, dns01.ToFqdn(zone))) | ||||
| } | ||||
|  | ||||
| // formatResponseError formats error with optional details from DMAPI response. | ||||
| func formatResponseError(response *response, err error) error { | ||||
| 	if response != nil { | ||||
| 		return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) | ||||
| 	} | ||||
| 	return fmt.Errorf("joker: DMAPI error: %w", err) | ||||
| 	return newDmapiProviderConfig(config) | ||||
| } | ||||
|   | ||||
| @@ -5,19 +5,29 @@ Code = "joker" | ||||
| Since = "v2.6.0" | ||||
|  | ||||
| Example = ''' | ||||
| # SVC | ||||
| JOKER_API_MODE=SVC \ | ||||
| JOKER_USERNAME=<your email> \ | ||||
| JOKER_PASSWORD=<your password> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| # or | ||||
|  | ||||
| # DMAPI | ||||
| JOKER_API_MODE=DMAPI \ | ||||
| JOKER_USERNAME=<your email> \ | ||||
| JOKER_PASSWORD=<your password> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| ## or | ||||
| JOKER_API_MODE=DMAPI \ | ||||
| JOKER_API_KEY=<your API key> \ | ||||
| lego --dns joker --domains my.domain.com --email my@email.com run | ||||
| ''' | ||||
|  | ||||
| [Configuration] | ||||
|   [Configuration.Credentials] | ||||
|     JOKER_API_KEY = "API key" | ||||
|     JOKER_API_MODE = "'DMAPI' or 'SVC'. DMAPI is for resellers accounts. (Default: DMAPI)" | ||||
|     JOKER_USERNAME = "Joker.com username (email address)" | ||||
|     JOKER_PASSWORD = "Joker.com password" | ||||
|     JOKER_API_KEY = "API key (only with DMAPI mode)" | ||||
|   [Configuration.Additional] | ||||
|     JOKER_POLLING_INTERVAL = "Time between DNS propagation check" | ||||
|     JOKER_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation" | ||||
| @@ -26,3 +36,4 @@ lego --dns joker --domains my.domain.com --email my@email.com run | ||||
|  | ||||
| [Links] | ||||
|   API = "https://joker.com/faq/category/39/22-dmapi.html" | ||||
|   API_SVC = "https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html" | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| @@ -11,54 +13,40 @@ import ( | ||||
|  | ||||
| const envDomain = envNamespace + "DOMAIN" | ||||
|  | ||||
| var envTest = tester.NewEnvTest(EnvAPIKey, EnvUsername, EnvPassword). | ||||
| var envTest = tester.NewEnvTest(EnvAPIKey, EnvUsername, EnvPassword, EnvMode). | ||||
| 	WithDomain(envDomain) | ||||
|  | ||||
| func TestNewDNSProvider(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		envVars  map[string]string | ||||
| 		expected string | ||||
| 		expected interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc: "success API key", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey: "123", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "success username password", | ||||
| 			desc: "mode DMAPI (default)", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 			expected: &dmapiProvider{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing credentials", | ||||
| 			desc: "mode DMAPI", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME,JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing password", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvMode:     modeDMAPI, | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing username", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME or some credentials information are missing: JOKER_API_KEY", | ||||
| 			expected: &dmapiProvider{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "mode SVC", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvMode:     modeSVC, | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 			expected: &svcProvider{}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| @@ -69,92 +57,51 @@ func TestNewDNSProvider(t *testing.T) { | ||||
|  | ||||
| 			envTest.Apply(test.envVars) | ||||
|  | ||||
| 			p, err := NewDNSProvider() | ||||
| 			fmt.Println(os.Getenv(EnvMode)) | ||||
|  | ||||
| 			if len(test.expected) == 0 { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 			} else { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} | ||||
| 			p, err := NewDNSProvider() | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
|  | ||||
| 			assert.IsType(t, test.expected, p) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestNewDNSProviderConfig(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc            string | ||||
| 		apiKey          string | ||||
| 		username        string | ||||
| 		password        string | ||||
| 		baseURL         string | ||||
| 		expected        string | ||||
| 		expectedBaseURL string | ||||
| 		desc     string | ||||
| 		mode     string | ||||
| 		expected interface{} | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc:            "success api key", | ||||
| 			apiKey:          "123", | ||||
| 			expectedBaseURL: defaultBaseURL, | ||||
| 			desc:     "mode DMAPI (default)", | ||||
| 			expected: &dmapiProvider{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "success username and password", | ||||
| 			username:        "123", | ||||
| 			password:        "123", | ||||
| 			expectedBaseURL: defaultBaseURL, | ||||
| 			desc:     "mode DMAPI", | ||||
| 			mode:     modeDMAPI, | ||||
| 			expected: &dmapiProvider{}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "missing credentials", | ||||
| 			expected:        "joker: credentials missing", | ||||
| 			expectedBaseURL: defaultBaseURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "missing credentials: username", | ||||
| 			expected:        "joker: credentials missing", | ||||
| 			username:        "123", | ||||
| 			expectedBaseURL: defaultBaseURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "missing credentials: password", | ||||
| 			expected:        "joker: credentials missing", | ||||
| 			password:        "123", | ||||
| 			expectedBaseURL: defaultBaseURL, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "Base URL should ends with /", | ||||
| 			apiKey:          "123", | ||||
| 			baseURL:         "http://example.com", | ||||
| 			expectedBaseURL: "http://example.com/", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:            "Base URL already ends with /", | ||||
| 			apiKey:          "123", | ||||
| 			baseURL:         "http://example.com/", | ||||
| 			expectedBaseURL: "http://example.com/", | ||||
| 			desc:     "mode SVC", | ||||
| 			mode:     modeSVC, | ||||
| 			expected: &svcProvider{}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.APIKey = test.apiKey | ||||
| 			config.Username = test.username | ||||
| 			config.Password = test.password | ||||
|  | ||||
| 			if test.baseURL != "" { | ||||
| 				config.BaseURL = test.baseURL | ||||
| 			} | ||||
| 			config.Username = "123" | ||||
| 			config.Password = "123" | ||||
| 			config.APIMode = test.mode | ||||
|  | ||||
| 			p, err := NewDNSProviderConfig(config) | ||||
| 			require.NoError(t, err) | ||||
| 			require.NotNil(t, p) | ||||
|  | ||||
| 			if len(test.expected) == 0 { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 				assert.Equal(t, test.expectedBaseURL, p.config.BaseURL) | ||||
| 			} else { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} | ||||
| 			assert.IsType(t, test.expected, p) | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										164
									
								
								providers/dns/joker/provider_dmapi.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								providers/dns/joker/provider_dmapi.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,164 @@ | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"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/joker/internal/dmapi" | ||||
| ) | ||||
|  | ||||
| // dmapiProvider implements the challenge.Provider interface. | ||||
| type dmapiProvider struct { | ||||
| 	config *Config | ||||
| 	client *dmapi.Client | ||||
| } | ||||
|  | ||||
| // newDmapiProvider returns a DNSProvider instance configured for Joker. | ||||
| // Credentials must be passed in the environment variable: JOKER_USERNAME, JOKER_PASSWORD or JOKER_API_KEY. | ||||
| func newDmapiProvider() (*dmapiProvider, error) { | ||||
| 	values, err := env.Get(EnvAPIKey) | ||||
| 	if err != nil { | ||||
| 		var errU error | ||||
| 		values, errU = env.Get(EnvUsername, EnvPassword) | ||||
| 		if errU != nil { | ||||
| 			return nil, fmt.Errorf("joker: %v or %v", errU, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	config := NewDefaultConfig() | ||||
| 	config.APIKey = values[EnvAPIKey] | ||||
| 	config.Username = values[EnvUsername] | ||||
| 	config.Password = values[EnvPassword] | ||||
|  | ||||
| 	return newDmapiProviderConfig(config) | ||||
| } | ||||
|  | ||||
| // newDmapiProviderConfig return a DNSProvider instance configured for Joker. | ||||
| func newDmapiProviderConfig(config *Config) (*dmapiProvider, error) { | ||||
| 	if config == nil { | ||||
| 		return nil, errors.New("joker: the configuration of the DNS provider is nil") | ||||
| 	} | ||||
|  | ||||
| 	if config.APIKey == "" { | ||||
| 		if config.Username == "" || config.Password == "" { | ||||
| 			return nil, errors.New("joker: credentials missing") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	client := dmapi.NewClient(dmapi.AuthInfo{ | ||||
| 		APIKey:   config.APIKey, | ||||
| 		Username: config.Username, | ||||
| 		Password: config.Password, | ||||
| 	}) | ||||
|  | ||||
| 	client.Debug = config.Debug | ||||
|  | ||||
| 	if config.HTTPClient != nil { | ||||
| 		client.HTTPClient = config.HTTPClient | ||||
| 	} | ||||
|  | ||||
| 	return &dmapiProvider{config: config, client: client}, 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 *dmapiProvider) Timeout() (timeout, interval time.Duration) { | ||||
| 	return d.config.PropagationTimeout, d.config.PollingInterval | ||||
| } | ||||
|  | ||||
| // Present creates a TXT record using the specified parameters. | ||||
| func (d *dmapiProvider) Present(domain, token, keyAuth string) error { | ||||
| 	fqdn, value := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	if d.config.Debug { | ||||
| 		log.Infof("[%s] joker: adding TXT record %q to zone %q with value %q", domain, relative, zone, value) | ||||
| 	} | ||||
|  | ||||
| 	response, err := d.client.Login() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	response, err = d.client.GetZone(zone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	dnsZone := dmapi.AddTxtEntryToZone(response.Body, relative, value, d.config.TTL) | ||||
|  | ||||
| 	response, err = d.client.PutZone(zone, dnsZone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // CleanUp removes the TXT record matching the specified parameters. | ||||
| func (d *dmapiProvider) CleanUp(domain, token, keyAuth string) error { | ||||
| 	fqdn, _ := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	if d.config.Debug { | ||||
| 		log.Infof("[%s] joker: removing entry %q from zone %q", domain, relative, zone) | ||||
| 	} | ||||
|  | ||||
| 	response, err := d.client.Login() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	defer func() { | ||||
| 		// Try to logout in case of errors | ||||
| 		_, _ = d.client.Logout() | ||||
| 	}() | ||||
|  | ||||
| 	response, err = d.client.GetZone(zone) | ||||
| 	if err != nil || response.StatusCode != 0 { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
|  | ||||
| 	dnsZone, modified := dmapi.RemoveTxtEntryFromZone(response.Body, relative) | ||||
| 	if modified { | ||||
| 		response, err = d.client.PutZone(zone, dnsZone) | ||||
| 		if err != nil || response.StatusCode != 0 { | ||||
| 			return formatResponseError(response, err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	response, err = d.client.Logout() | ||||
| 	if err != nil { | ||||
| 		return formatResponseError(response, err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func getRelative(fqdn, zone string) string { | ||||
| 	return dns01.UnFqdn(strings.TrimSuffix(fqdn, dns01.ToFqdn(zone))) | ||||
| } | ||||
|  | ||||
| // formatResponseError formats error with optional details from DMAPI response. | ||||
| func formatResponseError(response *dmapi.Response, err error) error { | ||||
| 	if response != nil { | ||||
| 		return fmt.Errorf("joker: DMAPI error: %w Response: %v", err, response.Headers) | ||||
| 	} | ||||
| 	return fmt.Errorf("joker: DMAPI error: %w", err) | ||||
| } | ||||
							
								
								
									
										129
									
								
								providers/dns/joker/provider_dmapi_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								providers/dns/joker/provider_dmapi_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func Test_newDmapiProvider(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		envVars  map[string]string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc: "success API key", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey: "123", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "success username password", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing credentials", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME,JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing password", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_PASSWORD or some credentials information are missing: JOKER_API_KEY", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing username", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvAPIKey:   "", | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME or some credentials information are missing: JOKER_API_KEY", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			defer envTest.RestoreEnv() | ||||
| 			envTest.ClearEnv() | ||||
|  | ||||
| 			envTest.Apply(test.envVars) | ||||
|  | ||||
| 			p, err := newDmapiProvider() | ||||
|  | ||||
| 			if test.expected != "" { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} else { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Test_newDmapiProviderConfig(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		apiKey   string | ||||
| 		username string | ||||
| 		password string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc:   "success api key", | ||||
| 			apiKey: "123", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "success username and password", | ||||
| 			username: "123", | ||||
| 			password: "123", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials", | ||||
| 			expected: "joker: credentials missing", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials: username", | ||||
| 			expected: "joker: credentials missing", | ||||
| 			username: "123", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials: password", | ||||
| 			expected: "joker: credentials missing", | ||||
| 			password: "123", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.APIKey = test.apiKey | ||||
| 			config.Username = test.username | ||||
| 			config.Password = test.password | ||||
|  | ||||
| 			p, err := newDmapiProviderConfig(config) | ||||
|  | ||||
| 			if test.expected != "" { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} else { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										87
									
								
								providers/dns/joker/provider_svc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								providers/dns/joker/provider_svc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,87 @@ | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/go-acme/lego/v4/challenge/dns01" | ||||
| 	"github.com/go-acme/lego/v4/platform/config/env" | ||||
| 	"github.com/go-acme/lego/v4/providers/dns/joker/internal/svc" | ||||
| ) | ||||
|  | ||||
| // svcProvider implements the challenge.Provider interface. | ||||
| type svcProvider struct { | ||||
| 	config *Config | ||||
| 	client *svc.Client | ||||
| } | ||||
|  | ||||
| // newSvcProvider returns a DNSProvider instance configured for Joker. | ||||
| // Credentials must be passed in the environment variable: JOKER_USERNAME, JOKER_PASSWORD. | ||||
| func newSvcProvider() (*svcProvider, error) { | ||||
| 	values, err := env.Get(EnvUsername, EnvPassword) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("joker: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	config := NewDefaultConfig() | ||||
| 	config.Username = values[EnvUsername] | ||||
| 	config.Password = values[EnvPassword] | ||||
|  | ||||
| 	return newSvcProviderConfig(config) | ||||
| } | ||||
|  | ||||
| // newSvcProviderConfig return a DNSProvider instance configured for Joker. | ||||
| func newSvcProviderConfig(config *Config) (*svcProvider, error) { | ||||
| 	if config == nil { | ||||
| 		return nil, errors.New("joker: the configuration of the DNS provider is nil") | ||||
| 	} | ||||
|  | ||||
| 	if config.Username == "" || config.Password == "" { | ||||
| 		return nil, errors.New("joker: credentials missing") | ||||
| 	} | ||||
|  | ||||
| 	client := svc.NewClient(config.Username, config.Password) | ||||
|  | ||||
| 	return &svcProvider{config: config, client: client}, 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 *svcProvider) Timeout() (timeout, interval time.Duration) { | ||||
| 	return d.config.PropagationTimeout, d.config.PollingInterval | ||||
| } | ||||
|  | ||||
| // Present creates a TXT record using the specified parameters. | ||||
| func (d *svcProvider) Present(domain, token, keyAuth string) error { | ||||
| 	fqdn, value := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	return d.client.Send(dns01.UnFqdn(zone), relative, value) | ||||
| } | ||||
|  | ||||
| // CleanUp removes the TXT record matching the specified parameters. | ||||
| func (d *svcProvider) CleanUp(domain, token, keyAuth string) error { | ||||
| 	fqdn, _ := dns01.GetRecord(domain, keyAuth) | ||||
|  | ||||
| 	zone, err := dns01.FindZoneByFqdn(fqdn) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("joker: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	relative := getRelative(fqdn, zone) | ||||
|  | ||||
| 	return d.client.Send(dns01.UnFqdn(zone), relative, "") | ||||
| } | ||||
|  | ||||
| // Sequential All DNS challenges for this provider will be resolved sequentially. | ||||
| // Returns the interval between each iteration. | ||||
| func (d *svcProvider) Sequential() time.Duration { | ||||
| 	return d.config.SequenceInterval | ||||
| } | ||||
							
								
								
									
										114
									
								
								providers/dns/joker/provider_svc_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								providers/dns/joker/provider_svc_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| package joker | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| ) | ||||
|  | ||||
| func Test_newSvcProvider(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		envVars  map[string]string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc: "success username password", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing credentials", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME,JOKER_PASSWORD", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing password", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "123", | ||||
| 				EnvPassword: "", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_PASSWORD", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc: "missing username", | ||||
| 			envVars: map[string]string{ | ||||
| 				EnvUsername: "", | ||||
| 				EnvPassword: "123", | ||||
| 			}, | ||||
| 			expected: "joker: some credentials information are missing: JOKER_USERNAME", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			defer envTest.RestoreEnv() | ||||
| 			envTest.ClearEnv() | ||||
|  | ||||
| 			envTest.Apply(test.envVars) | ||||
|  | ||||
| 			p, err := newSvcProvider() | ||||
|  | ||||
| 			if test.expected != "" { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} else { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Test_newSvcProviderConfig(t *testing.T) { | ||||
| 	testCases := []struct { | ||||
| 		desc     string | ||||
| 		username string | ||||
| 		password string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{ | ||||
| 			desc:     "success username and password", | ||||
| 			username: "123", | ||||
| 			password: "123", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials", | ||||
| 			expected: "joker: credentials missing", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials: username", | ||||
| 			expected: "joker: credentials missing", | ||||
| 			username: "123", | ||||
| 		}, | ||||
| 		{ | ||||
| 			desc:     "missing credentials: password", | ||||
| 			expected: "joker: credentials missing", | ||||
| 			password: "123", | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, test := range testCases { | ||||
| 		t.Run(test.desc, func(t *testing.T) { | ||||
| 			config := NewDefaultConfig() | ||||
| 			config.Username = test.username | ||||
| 			config.Password = test.password | ||||
|  | ||||
| 			p, err := newSvcProviderConfig(config) | ||||
|  | ||||
| 			if test.expected != "" { | ||||
| 				require.EqualError(t, err, test.expected) | ||||
| 			} else { | ||||
| 				require.NoError(t, err) | ||||
| 				require.NotNil(t, p) | ||||
| 				assert.NotNil(t, p.config) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user