0374ea2c79
golang.org/x/oauth2/jws is deprecated: this package is not intended for public use and might be removed in the future. It exists for internal use only. Please switch to another JWS package or copy this package into your own source tree. github.com/golang-jwt/jwt/v4 seems to be a good alternative, and was already an implicit dependency.
115 lines
3.1 KiB
Go
115 lines
3.1 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/golang-jwt/jwt/v4"
|
|
"github.com/rclone/rclone/fs"
|
|
"github.com/rclone/rclone/fs/config/configmap"
|
|
"github.com/rclone/rclone/lib/oauthutil"
|
|
|
|
"golang.org/x/oauth2"
|
|
)
|
|
|
|
// 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, url string, claims jwt.Claims, headerParams map[string]interface{}, queryParams map[string]string, privateKey *rsa.PrivateKey, m configmap.Mapper, client *http.Client) (err error) {
|
|
jwtToken := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
|
for key, value := range headerParams {
|
|
jwtToken.Header[key] = value
|
|
}
|
|
payload, err := jwtToken.SignedString(privateKey)
|
|
if err != nil {
|
|
return fmt.Errorf("jwtutil: failed to encode payload: %w", err)
|
|
}
|
|
req, err := http.NewRequest("POST", url, 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", url, 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"`
|
|
}
|