identity.go raw

   1  package internal
   2  
   3  import (
   4  	"context"
   5  	"encoding/json"
   6  	"fmt"
   7  	"io"
   8  	"net/http"
   9  	"net/url"
  10  	"strings"
  11  	"time"
  12  
  13  	"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
  14  )
  15  
  16  type token string
  17  
  18  const tokenKey token = "token"
  19  
  20  // obtainToken Logs into cloud.ru and acquires a bearer token for use in future API calls.
  21  // https://cloud.ru/ru/docs/clouddns/ug/topics/api-ref_authentication.html
  22  func (c *Client) obtainToken(ctx context.Context) (*Token, error) {
  23  	data := make(url.Values)
  24  	data.Set("grant_type", "access_key")
  25  	data.Set("client_id", c.keyID)
  26  	data.Set("client_secret", c.secret)
  27  
  28  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.AuthEndpoint.String(), strings.NewReader(data.Encode()))
  29  	if err != nil {
  30  		return nil, fmt.Errorf("unable to create request: %w", err)
  31  	}
  32  
  33  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
  34  
  35  	resp, err := c.HTTPClient.Do(req)
  36  	if err != nil {
  37  		return nil, errutils.NewHTTPDoError(req, err)
  38  	}
  39  
  40  	defer func() { _ = resp.Body.Close() }()
  41  
  42  	if resp.StatusCode != http.StatusOK {
  43  		return nil, parseError(req, resp)
  44  	}
  45  
  46  	raw, err := io.ReadAll(resp.Body)
  47  	if err != nil {
  48  		return nil, errutils.NewReadResponseError(req, resp.StatusCode, err)
  49  	}
  50  
  51  	tok := Token{}
  52  
  53  	err = json.Unmarshal(raw, &tok)
  54  	if err != nil {
  55  		return nil, errutils.NewUnmarshalError(req, resp.StatusCode, raw, err)
  56  	}
  57  
  58  	if !strings.EqualFold(tok.TokenType, "Bearer") {
  59  		return nil, fmt.Errorf("received unexpected token type: %s", tok.TokenType)
  60  	}
  61  
  62  	tok.Deadline = time.Now().Add(time.Duration(tok.ExpiresIn) * time.Second)
  63  
  64  	return &tok, nil
  65  }
  66  
  67  func (c *Client) CreateAuthenticatedContext(ctx context.Context) (context.Context, error) {
  68  	c.muToken.Lock()
  69  	defer c.muToken.Unlock()
  70  
  71  	if c.token != nil && time.Now().Before(c.token.Deadline) {
  72  		// Already authenticated, stop now
  73  		return context.WithValue(ctx, tokenKey, c.token), nil
  74  	}
  75  
  76  	tok, err := c.obtainToken(ctx)
  77  	if err != nil {
  78  		return nil, err
  79  	}
  80  
  81  	return context.WithValue(ctx, tokenKey, tok), nil
  82  }
  83  
  84  func parseError(req *http.Request, resp *http.Response) error {
  85  	if resp.StatusCode < 400 || resp.StatusCode > 499 {
  86  		return errutils.NewUnexpectedResponseStatusCodeError(req, resp)
  87  	}
  88  
  89  	raw, _ := io.ReadAll(resp.Body)
  90  
  91  	errResp := &authResponseError{}
  92  
  93  	err := json.Unmarshal(raw, errResp)
  94  	if err != nil {
  95  		return errutils.NewUnexpectedStatusCodeError(req, resp.StatusCode, raw)
  96  	}
  97  
  98  	return fmt.Errorf("%d: %w", resp.StatusCode, errResp)
  99  }
 100  
 101  func getToken(ctx context.Context) *Token {
 102  	tok, ok := ctx.Value(tokenKey).(*Token)
 103  	if !ok {
 104  		return nil
 105  	}
 106  
 107  	return tok
 108  }
 109