Fix: acme DNS providers
This commit is contained in:
parent
1b54f4d32a
commit
7eeac63139
11 changed files with 488 additions and 5 deletions
4
Gopkg.lock
generated
4
Gopkg.lock
generated
|
@ -1393,10 +1393,11 @@
|
|||
"providers/dns/rfc2136",
|
||||
"providers/dns/route53",
|
||||
"providers/dns/sakuracloud",
|
||||
"providers/dns/stackpath",
|
||||
"providers/dns/vegadns",
|
||||
"providers/dns/vultr"
|
||||
]
|
||||
revision = "dd087560a0a4a52b3388dd320a5982a0e8233eff"
|
||||
revision = "01c63ec08d1d85e3ad44c16dff95dadee26a81bc"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
@ -1442,6 +1443,7 @@
|
|||
name = "golang.org/x/oauth2"
|
||||
packages = [
|
||||
".",
|
||||
"clientcredentials",
|
||||
"google",
|
||||
"internal",
|
||||
"jws",
|
||||
|
|
|
@ -291,6 +291,7 @@ Here is a list of supported `provider`s, that can automate the DNS verification,
|
|||
| [RFC2136](https://tools.ietf.org/html/rfc2136) | `rfc2136` | `RFC2136_TSIG_KEY`, `RFC2136_TSIG_SECRET`, `RFC2136_TSIG_ALGORITHM`, `RFC2136_NAMESERVER` | Not tested yet |
|
||||
| [Route 53](https://aws.amazon.com/route53/) | `route53` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `[AWS_REGION]`, `[AWS_HOSTED_ZONE_ID]` or a configured user/instance IAM profile. | YES |
|
||||
| [Sakura Cloud](https://cloud.sakura.ad.jp/) | `sakuracloud` | `SAKURACLOUD_ACCESS_TOKEN`, `SAKURACLOUD_ACCESS_TOKEN_SECRET` | Not tested yet |
|
||||
| [Stackpath](https://www.stackpath.com/) | `stackpath` | `STACKPATH_CLIENT_ID`, `STACKPATH_CLIENT_SECRET`, `STACKPATH_STACK_ID` | Not tested yet |
|
||||
| [VegaDNS](https://github.com/shupp/VegaDNS-API) | `vegadns` | `SECRET_VEGADNS_KEY`, `SECRET_VEGADNS_SECRET`, `VEGADNS_URL` | Not tested yet |
|
||||
| [VULTR](https://www.vultr.com) | `vultr` | `VULTR_API_KEY` | Not tested yet |
|
||||
|
||||
|
|
2
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
2
vendor/github.com/xenolf/lego/providers/dns/bluecat/bluecat.go
generated
vendored
|
@ -61,7 +61,7 @@ type DNSProvider struct {
|
|||
// The REST endpoint will be appended. In addition, the Configuration name
|
||||
// and external DNS View Name must be passed in BLUECAT_CONFIG_NAME and BLUECAT_DNS_VIEW
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("BLUECAT_SERVER_URL", "BLUECAT_USER_NAME", "BLUECAT_CONFIG_NAME", "BLUECAT_CONFIG_NAME", "BLUECAT_DNS_VIEW")
|
||||
values, err := env.Get("BLUECAT_SERVER_URL", "BLUECAT_USER_NAME", "BLUECAT_PASSWORD", "BLUECAT_CONFIG_NAME", "BLUECAT_DNS_VIEW")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bluecat: %v", err)
|
||||
}
|
||||
|
|
2
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
2
vendor/github.com/xenolf/lego/providers/dns/cloudxns/cloudxns.go
generated
vendored
|
@ -76,7 +76,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
|
||||
client.HTTPClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client}, nil
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
|
|
3
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
3
vendor/github.com/xenolf/lego/providers/dns/dns_providers.go
generated
vendored
|
@ -42,6 +42,7 @@ import (
|
|||
"github.com/xenolf/lego/providers/dns/rfc2136"
|
||||
"github.com/xenolf/lego/providers/dns/route53"
|
||||
"github.com/xenolf/lego/providers/dns/sakuracloud"
|
||||
"github.com/xenolf/lego/providers/dns/stackpath"
|
||||
"github.com/xenolf/lego/providers/dns/vegadns"
|
||||
"github.com/xenolf/lego/providers/dns/vultr"
|
||||
)
|
||||
|
@ -127,6 +128,8 @@ func NewDNSChallengeProviderByName(name string) (acme.ChallengeProvider, error)
|
|||
return rfc2136.NewDNSProvider()
|
||||
case "sakuracloud":
|
||||
return sakuracloud.NewDNSProvider()
|
||||
case "stackpath":
|
||||
return stackpath.NewDNSProvider()
|
||||
case "vegadns":
|
||||
return vegadns.NewDNSProvider()
|
||||
case "vultr":
|
||||
|
|
2
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
2
vendor/github.com/xenolf/lego/providers/dns/dnspod/dnspod.go
generated
vendored
|
@ -81,7 +81,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
client := dnspod.NewClient(params)
|
||||
client.HttpClient = config.HTTPClient
|
||||
|
||||
return &DNSProvider{client: client}, nil
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
|
|
1
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
1
vendor/github.com/xenolf/lego/providers/dns/glesys/glesys.go
generated
vendored
|
@ -94,6 +94,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
}
|
||||
|
||||
return &DNSProvider{
|
||||
config: config,
|
||||
activeRecords: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
|
2
vendor/github.com/xenolf/lego/providers/dns/sakuracloud/sakuracloud.go
generated
vendored
2
vendor/github.com/xenolf/lego/providers/dns/sakuracloud/sakuracloud.go
generated
vendored
|
@ -82,7 +82,7 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|||
client := api.NewClient(config.Token, config.Secret, "tk1a")
|
||||
client.UserAgent = acme.UserAgent
|
||||
|
||||
return &DNSProvider{client: client}, nil
|
||||
return &DNSProvider{client: client, config: config}, nil
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||
|
|
217
vendor/github.com/xenolf/lego/providers/dns/stackpath/client.go
generated
vendored
Normal file
217
vendor/github.com/xenolf/lego/providers/dns/stackpath/client.go
generated
vendored
Normal file
|
@ -0,0 +1,217 @@
|
|||
package stackpath
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Zones is the response struct from the Stackpath api GetZones
|
||||
type Zones struct {
|
||||
Zones []Zone `json:"zones"`
|
||||
}
|
||||
|
||||
// Zone a DNS zone representation
|
||||
type Zone struct {
|
||||
ID string
|
||||
Domain string
|
||||
}
|
||||
|
||||
// Records is the response struct from the Stackpath api GetZoneRecords
|
||||
type Records struct {
|
||||
Records []Record `json:"records"`
|
||||
}
|
||||
|
||||
// Record a DNS record representation
|
||||
type Record struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
TTL int `json:"ttl"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
|
||||
// ErrorResponse the API error response representation
|
||||
type ErrorResponse struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"error"`
|
||||
}
|
||||
|
||||
func (e *ErrorResponse) Error() string {
|
||||
return fmt.Sprintf("%d %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/GetZones
|
||||
func (d *DNSProvider) getZones(domain string) (*Zone, error) {
|
||||
domain = acme.UnFqdn(domain)
|
||||
tld, err := publicsuffix.EffectiveTLDPlusOne(domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := d.newRequest(http.MethodGet, "/zones", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
query.Add("page_request.filter", fmt.Sprintf("domain='%s'", tld))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
var zones Zones
|
||||
err = d.do(req, &zones)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(zones.Zones) == 0 {
|
||||
return nil, fmt.Errorf("did not find zone with domain %s", domain)
|
||||
}
|
||||
|
||||
return &zones.Zones[0], nil
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/GetZoneRecords
|
||||
func (d *DNSProvider) getZoneRecords(name string, zone *Zone) ([]Record, error) {
|
||||
u := fmt.Sprintf("/zones/%s/records", zone.ID)
|
||||
req, err := d.newRequest(http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
query.Add("page_request.filter", fmt.Sprintf("name='%s' and type='TXT'", name))
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
var records Records
|
||||
err = d.do(req, &records)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(records.Records) == 0 {
|
||||
return nil, fmt.Errorf("did not find record with name %s", name)
|
||||
}
|
||||
|
||||
return records.Records, nil
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/CreateZoneRecord
|
||||
func (d *DNSProvider) createZoneRecord(zone *Zone, record Record) error {
|
||||
u := fmt.Sprintf("/zones/%s/records", zone.ID)
|
||||
req, err := d.newRequest(http.MethodPost, u, record)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.do(req, nil)
|
||||
}
|
||||
|
||||
// https://developer.stackpath.com/en/api/dns/#operation/DeleteZoneRecord
|
||||
func (d *DNSProvider) deleteZoneRecord(zone *Zone, record Record) error {
|
||||
u := fmt.Sprintf("/zones/%s/records/%s", zone.ID, record.ID)
|
||||
req, err := d.newRequest(http.MethodDelete, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.do(req, nil)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) newRequest(method, urlStr string, body interface{}) (*http.Request, error) {
|
||||
u, err := d.BaseURL.Parse(path.Join(d.config.StackID, urlStr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if body == nil {
|
||||
var req *http.Request
|
||||
req, err = http.NewRequest(method, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
reqBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), bytes.NewBuffer(reqBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) do(req *http.Request, v interface{}) error {
|
||||
resp, err := d.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = checkResponse(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
raw, err := readBody(resp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read body: %v", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(raw, v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unmarshaling error: %v: %s", err, string(raw))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkResponse(resp *http.Response) error {
|
||||
if resp.StatusCode > 299 {
|
||||
data, err := readBody(resp)
|
||||
if err != nil {
|
||||
return &ErrorResponse{Code: resp.StatusCode, Message: err.Error()}
|
||||
}
|
||||
|
||||
errResp := &ErrorResponse{}
|
||||
err = json.Unmarshal(data, errResp)
|
||||
if err != nil {
|
||||
return &ErrorResponse{Code: resp.StatusCode, Message: fmt.Sprintf("unmarshaling error: %v: %s", err, string(data))}
|
||||
}
|
||||
return errResp
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readBody(resp *http.Response) ([]byte, error) {
|
||||
if resp.Body == nil {
|
||||
return nil, fmt.Errorf("response body is nil")
|
||||
}
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
rawBody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return rawBody, nil
|
||||
}
|
150
vendor/github.com/xenolf/lego/providers/dns/stackpath/stackpath.go
generated
vendored
Normal file
150
vendor/github.com/xenolf/lego/providers/dns/stackpath/stackpath.go
generated
vendored
Normal file
|
@ -0,0 +1,150 @@
|
|||
// Package stackpath implements a DNS provider for solving the DNS-01 challenge using Stackpath DNS.
|
||||
// https://developer.stackpath.com/en/api/dns/
|
||||
package stackpath
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/xenolf/lego/acme"
|
||||
"github.com/xenolf/lego/platform/config/env"
|
||||
"golang.org/x/oauth2/clientcredentials"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultBaseURL = "https://gateway.stackpath.com/dns/v1/stacks/"
|
||||
defaultAuthURL = "https://gateway.stackpath.com/identity/v1/oauth2/token"
|
||||
)
|
||||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
StackID string
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a default configuration for the DNSProvider
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
TTL: env.GetOrDefaultInt("STACKPATH_TTL", 120),
|
||||
PropagationTimeout: env.GetOrDefaultSecond("STACKPATH_PROPAGATION_TIMEOUT", acme.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond("STACKPATH_POLLING_INTERVAL", acme.DefaultPollingInterval),
|
||||
}
|
||||
}
|
||||
|
||||
// DNSProvider is an implementation of the acme.ChallengeProvider interface.
|
||||
type DNSProvider struct {
|
||||
BaseURL *url.URL
|
||||
client *http.Client
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Stackpath.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// STACKPATH_CLIENT_ID, STACKPATH_CLIENT_SECRET, and STACKPATH_STACK_ID.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get("STACKPATH_CLIENT_ID", "STACKPATH_CLIENT_SECRET", "STACKPATH_STACK_ID")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.ClientID = values["STACKPATH_CLIENT_ID"]
|
||||
config.ClientSecret = values["STACKPATH_CLIENT_SECRET"]
|
||||
config.StackID = values["STACKPATH_STACK_ID"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
// NewDNSProviderConfig return a DNSProvider instance configured for Stackpath.
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, errors.New("stackpath: the configuration of the DNS provider is nil")
|
||||
}
|
||||
|
||||
if len(config.ClientID) == 0 || len(config.ClientSecret) == 0 {
|
||||
return nil, errors.New("stackpath: credentials missing")
|
||||
}
|
||||
|
||||
if len(config.StackID) == 0 {
|
||||
return nil, errors.New("stackpath: stack id missing")
|
||||
}
|
||||
|
||||
baseURL, _ := url.Parse(defaultBaseURL)
|
||||
|
||||
return &DNSProvider{
|
||||
BaseURL: baseURL,
|
||||
client: getOathClient(config),
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getOathClient(config *Config) *http.Client {
|
||||
oathConfig := &clientcredentials.Config{
|
||||
TokenURL: defaultAuthURL,
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.ClientSecret,
|
||||
}
|
||||
|
||||
return oathConfig.Client(context.Background())
|
||||
}
|
||||
|
||||
// Present creates a TXT record to fulfill the dns-01 challenge
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
zone, err := d.getZones(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
fqdn, value, _ := acme.DNS01Record(domain, keyAuth)
|
||||
parts := strings.Split(fqdn, ".")
|
||||
|
||||
record := Record{
|
||||
Name: parts[0],
|
||||
Type: "TXT",
|
||||
TTL: d.config.TTL,
|
||||
Data: value,
|
||||
}
|
||||
|
||||
return d.createZoneRecord(zone, record)
|
||||
}
|
||||
|
||||
// CleanUp removes the TXT record matching the specified parameters
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
zone, err := d.getZones(domain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("stackpath: %v", err)
|
||||
}
|
||||
|
||||
fqdn, _, _ := acme.DNS01Record(domain, keyAuth)
|
||||
parts := strings.Split(fqdn, ".")
|
||||
|
||||
records, err := d.getZoneRecords(parts[0], zone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
err = d.deleteZoneRecord(zone, record)
|
||||
if err != nil {
|
||||
log.Printf("stackpath: failed to delete TXT record: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return 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
|
||||
}
|
109
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
109
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package clientcredentials implements the OAuth2.0 "client credentials" token flow,
|
||||
// also known as the "two-legged OAuth 2.0".
|
||||
//
|
||||
// This should be used when the client is acting on its own behalf or when the client
|
||||
// is the resource owner. It may also be used when requesting access to protected
|
||||
// resources based on an authorization previously arranged with the authorization
|
||||
// server.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.4
|
||||
package clientcredentials // import "golang.org/x/oauth2/clientcredentials"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/internal"
|
||||
)
|
||||
|
||||
// Config describes a 2-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the application's secret.
|
||||
ClientSecret string
|
||||
|
||||
// TokenURL is the resource server's token endpoint
|
||||
// URL. This is a constant specific to each server.
|
||||
TokenURL string
|
||||
|
||||
// Scope specifies optional requested permissions.
|
||||
Scopes []string
|
||||
|
||||
// EndpointParams specifies additional parameters for requests to the token endpoint.
|
||||
EndpointParams url.Values
|
||||
}
|
||||
|
||||
// Token uses client credentials to retrieve a token.
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
|
||||
return c.TokenSource(ctx).Token()
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
// The token will auto-refresh as necessary. The underlying
|
||||
// HTTP transport will be obtained using the provided context.
|
||||
// The returned client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context and the
|
||||
// client ID and client secret.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||
source := &tokenSource{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
return oauth2.ReuseTokenSource(nil, source)
|
||||
}
|
||||
|
||||
type tokenSource struct {
|
||||
ctx context.Context
|
||||
conf *Config
|
||||
}
|
||||
|
||||
// Token refreshes the token by using a new client credentials request.
|
||||
// tokens received this way do not include a refresh token
|
||||
func (c *tokenSource) Token() (*oauth2.Token, error) {
|
||||
v := url.Values{
|
||||
"grant_type": {"client_credentials"},
|
||||
}
|
||||
if len(c.conf.Scopes) > 0 {
|
||||
v.Set("scope", strings.Join(c.conf.Scopes, " "))
|
||||
}
|
||||
for k, p := range c.conf.EndpointParams {
|
||||
if _, ok := v[k]; ok {
|
||||
return nil, fmt.Errorf("oauth2: cannot overwrite parameter %q", k)
|
||||
}
|
||||
v[k] = p
|
||||
}
|
||||
tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v)
|
||||
if err != nil {
|
||||
if rErr, ok := err.(*internal.RetrieveError); ok {
|
||||
return nil, (*oauth2.RetrieveError)(rErr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t := &oauth2.Token{
|
||||
AccessToken: tk.AccessToken,
|
||||
TokenType: tk.TokenType,
|
||||
RefreshToken: tk.RefreshToken,
|
||||
Expiry: tk.Expiry,
|
||||
}
|
||||
return t.WithExtra(tk.Raw), nil
|
||||
}
|
Loading…
Reference in a new issue