github.com/10XDev/rclone@v1.52.3-0.20200626220027-16af9ab76b2a/lib/jwtutil/jwtutil.go (about)

     1  package jwtutil
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"crypto/rsa"
     7  	"encoding/hex"
     8  	"encoding/json"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/pkg/errors"
    16  	"github.com/rclone/rclone/fs"
    17  	"github.com/rclone/rclone/fs/config/configmap"
    18  	"github.com/rclone/rclone/lib/oauthutil"
    19  
    20  	"golang.org/x/oauth2"
    21  	"golang.org/x/oauth2/jws"
    22  )
    23  
    24  // RandomHex creates a random string of the given length
    25  func RandomHex(n int) (string, error) {
    26  	bytes := make([]byte, n)
    27  	if _, err := rand.Read(bytes); err != nil {
    28  		return "", err
    29  	}
    30  	return hex.EncodeToString(bytes), nil
    31  }
    32  
    33  // Config configures rclone using JWT
    34  func Config(id, name string, claims *jws.ClaimSet, header *jws.Header, queryParams map[string]string, privateKey *rsa.PrivateKey, m configmap.Mapper, client *http.Client) (err error) {
    35  	payload, err := jws.Encode(header, claims, privateKey)
    36  	if err != nil {
    37  		return errors.Wrap(err, "jwtutil: failed to encode payload")
    38  	}
    39  	req, err := http.NewRequest("POST", claims.Aud, nil)
    40  	if err != nil {
    41  		return errors.Wrap(err, "jwtutil: failed to create new request")
    42  	}
    43  	q := req.URL.Query()
    44  	q.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
    45  	q.Add("assertion", payload)
    46  	for key, value := range queryParams {
    47  		q.Add(key, value)
    48  	}
    49  	queryString := q.Encode()
    50  
    51  	req, err = http.NewRequest("POST", claims.Aud, bytes.NewBuffer([]byte(queryString)))
    52  	if err != nil {
    53  		return errors.Wrap(err, "jwtutil: failed to create new request")
    54  	}
    55  	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    56  
    57  	resp, err := client.Do(req)
    58  	if err != nil {
    59  		return errors.Wrap(err, "jwtutil: failed making auth request")
    60  	}
    61  
    62  	s, err := bodyToString(resp.Body)
    63  	if err != nil {
    64  		fs.Debugf(nil, "jwtutil: failed to get response body")
    65  	}
    66  	if resp.StatusCode != 200 {
    67  		err = errors.New(resp.Status)
    68  		return errors.Wrap(err, "jwtutil: failed making auth request")
    69  	}
    70  	defer func() {
    71  		deferedErr := resp.Body.Close()
    72  		if deferedErr != nil {
    73  			err = errors.Wrap(err, "jwtutil: failed to close resp.Body")
    74  		}
    75  	}()
    76  
    77  	result := &response{}
    78  	err = json.NewDecoder(strings.NewReader(s)).Decode(result)
    79  	if result.AccessToken == "" && err == nil {
    80  		err = errors.New("No AccessToken in Response")
    81  	}
    82  	if err != nil {
    83  		return errors.Wrap(err, "jwtutil: failed to get token")
    84  	}
    85  	token := &oauth2.Token{
    86  		AccessToken: result.AccessToken,
    87  		TokenType:   result.TokenType,
    88  	}
    89  	e := result.ExpiresIn
    90  	if e != 0 {
    91  		token.Expiry = time.Now().Add(time.Duration(e) * time.Second)
    92  	}
    93  	return oauthutil.PutToken(name, m, token, true)
    94  }
    95  
    96  func bodyToString(responseBody io.Reader) (bodyString string, err error) {
    97  	bodyBytes, err := ioutil.ReadAll(responseBody)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  	bodyString = string(bodyBytes)
   102  	fs.Debugf(nil, "jwtutil: Response Body: "+bodyString)
   103  	return bodyString, nil
   104  }
   105  
   106  type response struct {
   107  	AccessToken string `json:"access_token"`
   108  	TokenType   string `json:"token_type"`
   109  	ExpiresIn   int    `json:"expires_in"`
   110  }