rclone/lib/jwtutil/jwtutil.go
albertony 5d6b8141ec Replace deprecated ioutil
As of Go 1.16, the same functionality is now provided by package io or
package os, and those implementations should be preferred in new code.
2022-11-07 11:41:47 +00:00

111 lines
2.9 KiB
Go

// Package jwtutil provides JWT utilities.
package jwtutil
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/lib/oauthutil"
"golang.org/x/oauth2"
"golang.org/x/oauth2/jws"
)
// RandomHex creates a random string of the given length
func RandomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {
return "", err
}
return hex.EncodeToString(bytes), nil
}
// Config configures rclone using JWT
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) {
payload, err := jws.Encode(header, claims, privateKey)
if err != nil {
return fmt.Errorf("jwtutil: failed to encode payload: %w", err)
}
req, err := http.NewRequest("POST", claims.Aud, nil)
if err != nil {
return fmt.Errorf("jwtutil: failed to create new request: %w", err)
}
q := req.URL.Query()
q.Add("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer")
q.Add("assertion", payload)
for key, value := range queryParams {
q.Add(key, value)
}
queryString := q.Encode()
req, err = http.NewRequest("POST", claims.Aud, bytes.NewBuffer([]byte(queryString)))
if err != nil {
return fmt.Errorf("jwtutil: failed to create new request: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("jwtutil: failed making auth request: %w", err)
}
s, err := bodyToString(resp.Body)
if err != nil {
fs.Debugf(nil, "jwtutil: failed to get response body")
}
if resp.StatusCode != 200 {
err = errors.New(resp.Status)
return fmt.Errorf("jwtutil: failed making auth request: %w", err)
}
defer func() {
deferredErr := resp.Body.Close()
if deferredErr != nil {
err = fmt.Errorf("jwtutil: failed to close resp.Body: %w", err)
}
}()
result := &response{}
err = json.NewDecoder(strings.NewReader(s)).Decode(result)
if result.AccessToken == "" && err == nil {
err = errors.New("no AccessToken in Response")
}
if err != nil {
return fmt.Errorf("jwtutil: failed to get token: %w", err)
}
token := &oauth2.Token{
AccessToken: result.AccessToken,
TokenType: result.TokenType,
}
e := result.ExpiresIn
if e != 0 {
token.Expiry = time.Now().Add(time.Duration(e) * time.Second)
}
return oauthutil.PutToken(name, m, token, true)
}
func bodyToString(responseBody io.Reader) (bodyString string, err error) {
bodyBytes, err := io.ReadAll(responseBody)
if err != nil {
return "", err
}
bodyString = string(bodyBytes)
fs.Debugf(nil, "jwtutil: Response Body: "+bodyString)
return bodyString, nil
}
type response struct {
AccessToken string `json:"access_token"`
TokenType string `json:"token_type"`
ExpiresIn int `json:"expires_in"`
}