mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-06 17:16:44 +00:00
![Paddy](/assets/img/avatar_default.png)
Switch to using Go modules. This migrates our vendor.json to use Go 1.11's modules system, and replaces the vendor folder with the output of go mod vendor. The vendored code should remain basically the same; I believe some tree shaking of packages and support scripts/licenses/READMEs/etc. happened. This also fixes Travis and our Makefile to no longer use govendor.
163 lines
4.7 KiB
Go
163 lines
4.7 KiB
Go
// 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 jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
|
// known as "two-legged OAuth 2.0".
|
|
//
|
|
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
|
package jwt
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/internal"
|
|
"golang.org/x/oauth2/jws"
|
|
)
|
|
|
|
var (
|
|
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
|
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
|
)
|
|
|
|
// Config is the configuration for using JWT to fetch tokens,
|
|
// commonly known as "two-legged OAuth 2.0".
|
|
type Config struct {
|
|
// Email is the OAuth client identifier used when communicating with
|
|
// the configured OAuth provider.
|
|
Email string
|
|
|
|
// PrivateKey contains the contents of an RSA private key or the
|
|
// contents of a PEM file that contains a private key. The provided
|
|
// private key is used to sign JWT payloads.
|
|
// PEM containers with a passphrase are not supported.
|
|
// Use the following command to convert a PKCS 12 file into a PEM.
|
|
//
|
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
|
//
|
|
PrivateKey []byte
|
|
|
|
// PrivateKeyID contains an optional hint indicating which key is being
|
|
// used.
|
|
PrivateKeyID string
|
|
|
|
// Subject is the optional user to impersonate.
|
|
Subject string
|
|
|
|
// Scopes optionally specifies a list of requested permission scopes.
|
|
Scopes []string
|
|
|
|
// TokenURL is the endpoint required to complete the 2-legged JWT flow.
|
|
TokenURL string
|
|
|
|
// Expires optionally specifies how long the token is valid for.
|
|
Expires time.Duration
|
|
}
|
|
|
|
// TokenSource returns a JWT TokenSource using the configuration
|
|
// in c and the HTTP client from the provided context.
|
|
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
|
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
|
}
|
|
|
|
// Client returns an HTTP client wrapping the context's
|
|
// HTTP transport and adding Authorization headers with tokens
|
|
// obtained from c.
|
|
//
|
|
// 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))
|
|
}
|
|
|
|
// jwtSource is a source that always does a signed JWT request for a token.
|
|
// It should typically be wrapped with a reuseTokenSource.
|
|
type jwtSource struct {
|
|
ctx context.Context
|
|
conf *Config
|
|
}
|
|
|
|
func (js jwtSource) Token() (*oauth2.Token, error) {
|
|
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hc := oauth2.NewClient(js.ctx, nil)
|
|
claimSet := &jws.ClaimSet{
|
|
Iss: js.conf.Email,
|
|
Scope: strings.Join(js.conf.Scopes, " "),
|
|
Aud: js.conf.TokenURL,
|
|
}
|
|
if subject := js.conf.Subject; subject != "" {
|
|
claimSet.Sub = subject
|
|
// prn is the old name of sub. Keep setting it
|
|
// to be compatible with legacy OAuth 2.0 providers.
|
|
claimSet.Prn = subject
|
|
}
|
|
if t := js.conf.Expires; t > 0 {
|
|
claimSet.Exp = time.Now().Add(t).Unix()
|
|
}
|
|
h := *defaultHeader
|
|
h.KeyID = js.conf.PrivateKeyID
|
|
payload, err := jws.Encode(&h, claimSet, pk)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
v := url.Values{}
|
|
v.Set("grant_type", defaultGrantType)
|
|
v.Set("assertion", payload)
|
|
resp, err := hc.PostForm(js.conf.TokenURL, v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
|
}
|
|
if c := resp.StatusCode; c < 200 || c > 299 {
|
|
return nil, &oauth2.RetrieveError{
|
|
Response: resp,
|
|
Body: body,
|
|
}
|
|
}
|
|
// tokenRes is the JSON response body.
|
|
var tokenRes struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
IDToken string `json:"id_token"`
|
|
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
|
}
|
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
|
}
|
|
token := &oauth2.Token{
|
|
AccessToken: tokenRes.AccessToken,
|
|
TokenType: tokenRes.TokenType,
|
|
}
|
|
raw := make(map[string]interface{})
|
|
json.Unmarshal(body, &raw) // no error checks for optional fields
|
|
token = token.WithExtra(raw)
|
|
|
|
if secs := tokenRes.ExpiresIn; secs > 0 {
|
|
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
|
}
|
|
if v := tokenRes.IDToken; v != "" {
|
|
// decode returned id token to get expiry
|
|
claimSet, err := jws.Decode(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
|
|
}
|
|
token.Expiry = time.Unix(claimSet.Exp, 0)
|
|
}
|
|
return token, nil
|
|
}
|