Merge pull request #2116 from dgageot/1628-oauth2

Move `goauth2/oauth` dependency to `golang.org/x/oauth2`
This commit is contained in:
Olivier Gambier 2015-11-04 08:54:20 -08:00
commit 34ee11e51d
10 changed files with 5 additions and 1954 deletions

5
Godeps/Godeps.json generated
View File

@ -54,11 +54,6 @@
"github.com/docker/machine/version" "github.com/docker/machine/version"
], ],
"Deps": [ "Deps": [
{
"ImportPath": "code.google.com/p/goauth2/oauth",
"Comment": "weekly-56",
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
},
{ {
"ImportPath": "github.com/MSOpenTech/azure-sdk-for-go", "ImportPath": "github.com/MSOpenTech/azure-sdk-for-go",
"Comment": "v1.1-17-g515f3ec", "Comment": "v1.1-17-g515f3ec",

View File

@ -5,13 +5,13 @@ import (
"io/ioutil" "io/ioutil"
"time" "time"
"code.google.com/p/goauth2/oauth"
"github.com/digitalocean/godo" "github.com/digitalocean/godo"
"github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/state"
"golang.org/x/oauth2"
) )
type Driver struct { type Driver struct {
@ -293,11 +293,11 @@ func (d *Driver) Kill() error {
} }
func (d *Driver) getClient() *godo.Client { func (d *Driver) getClient() *godo.Client {
t := &oauth.Transport{ token := &oauth2.Token{AccessToken: d.AccessToken}
Token: &oauth.Token{AccessToken: d.AccessToken}, tokenSource := oauth2.StaticTokenSource(token)
} client := oauth2.NewClient(oauth2.NoContext, tokenSource)
return godo.NewClient(t.Client()) return godo.NewClient(client)
} }
func (d *Driver) publicSSHKeyPath() string { func (d *Driver) publicSSHKeyPath() string {

View File

@ -1,100 +0,0 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This program makes a call to the specified API, authenticated with OAuth2.
// a list of example APIs can be found at https://code.google.com/oauthplayground/
package main
import (
"flag"
"fmt"
"io"
"log"
"os"
"code.google.com/p/goauth2/oauth"
)
var (
clientId = flag.String("id", "", "Client ID")
clientSecret = flag.String("secret", "", "Client Secret")
scope = flag.String("scope", "https://www.googleapis.com/auth/userinfo.profile", "OAuth scope")
redirectURL = flag.String("redirect_url", "oob", "Redirect URL")
authURL = flag.String("auth_url", "https://accounts.google.com/o/oauth2/auth", "Authentication URL")
tokenURL = flag.String("token_url", "https://accounts.google.com/o/oauth2/token", "Token URL")
requestURL = flag.String("request_url", "https://www.googleapis.com/oauth2/v1/userinfo", "API request")
code = flag.String("code", "", "Authorization Code")
cachefile = flag.String("cache", "cache.json", "Token cache file")
)
const usageMsg = `
To obtain a request token you must specify both -id and -secret.
To obtain Client ID and Secret, see the "OAuth 2 Credentials" section under
the "API Access" tab on this page: https://code.google.com/apis/console/
Once you have completed the OAuth flow, the credentials should be stored inside
the file specified by -cache and you may run without the -id and -secret flags.
`
func main() {
flag.Parse()
// Set up a configuration.
config := &oauth.Config{
ClientId: *clientId,
ClientSecret: *clientSecret,
RedirectURL: *redirectURL,
Scope: *scope,
AuthURL: *authURL,
TokenURL: *tokenURL,
TokenCache: oauth.CacheFile(*cachefile),
}
// Set up a Transport using the config.
transport := &oauth.Transport{Config: config}
// Try to pull the token from the cache; if this fails, we need to get one.
token, err := config.TokenCache.Token()
if err != nil {
if *clientId == "" || *clientSecret == "" {
flag.Usage()
fmt.Fprint(os.Stderr, usageMsg)
os.Exit(2)
}
if *code == "" {
// Get an authorization code from the data provider.
// ("Please ask the user if I can access this resource.")
url := config.AuthCodeURL("")
fmt.Print("Visit this URL to get a code, then run again with -code=YOUR_CODE\n\n")
fmt.Println(url)
return
}
// Exchange the authorization code for an access token.
// ("Here's the code you gave the user, now give me a token!")
token, err = transport.Exchange(*code)
if err != nil {
log.Fatal("Exchange:", err)
}
// (The Exchange method will automatically cache the token.)
fmt.Printf("Token is cached in %v\n", config.TokenCache)
}
// Make the actual request using the cached token to authenticate.
// ("Here's the token, let me in!")
transport.Token = token
// Make the request.
r, err := transport.Client().Get(*requestURL)
if err != nil {
log.Fatal("Get:", err)
}
defer r.Body.Close()
// Write the response to standard output.
io.Copy(os.Stdout, r.Body)
// Send final carriage return, just to be neat.
fmt.Println()
}

View File

@ -1 +0,0 @@
{"web":{"auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://accounts.google.com/o/oauth2/token","client_email":"XXXXXXXXXXXX@developer.gserviceaccount.com","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/XXXXXXXXXXXX@developer.gserviceaccount.com","client_id":"XXXXXXXXXXXX.apps.googleusercontent.com","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs"}}

View File

@ -1,20 +0,0 @@
Bag Attributes
friendlyName: privatekey
localKeyID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
XXXXxyXXXXXXXxxyxxxX9y0XXYXXXXYXXxXyxxXxXxXXXyXXXXx4yx1xy1xyYxxY
1XxYy38YxXxxxyXxyyxx+xxxxyx1Y1xYx7yx2/Y1XyyXYYYxY5YXxX0xY/Y642yX
zYYxYXzXYxY0Y8y9YxyYXxxX40YyXxxXX4XXxx7XxXxxXyXxYYXxXyxX5XY0Yy2X
1YX0XXyy6YXyXx9XxXxyXX9XXYXxXxXXXXXXxYXYY3Y8Yy311XYYY81XyY14Xyyx
xXyx7xxXXXxxxxyyyX4YYYXyYyYXyxX4XYXYyxXYyx9xy23xXYyXyxYxXxx1XXXY
y98yX6yYxyyyX4Xyx1Xy/0yxxYxXxYYx2xx7yYXXXxYXXXxyXyyYYxx5XX2xxyxy
y6Yyyx0XX3YYYyx9YYXXXX7y0yxXXy+90XYz1y2xyx7yXxX+8X0xYxXXYxxyxYYy
YXx8Yy4yX0Xyxxx6yYX92yxy1YYYzyyyyxy55x/yyXXXYYXYXXzXXxYYxyXY8XXX
+y9+yXxX7XxxyYYxxXYxyY623xxXxYX59x5Y6yYyXYY4YxXXYXXXYxXYxXxXXx6x
YXX7XxXX2X0XY7YXyYy1XXxYXxXxYY1xXXxxxyy+07zXYxYxxXyyxxyxXx1XYy5X
5XYzyxYxXXYyX9XX7xX8xXxx+XXYyYXXXX5YY1x8Yxyx54Xy/1XXyyYXY5YxYyxY
XyyxXyX/YxxXXXxXXYXxyxx63xX/xxyYXXyYzx0XY+YxX5xyYyyxxxXXYX/94XXy
Xx63xYxXyXY3/XXxyyXX15XXXyz08XYY5YYXY/YXy/96x68XyyXXxYyXy4xYXx5x
7yxxyxxYxXxyx3y=
-----END PRIVATE KEY-----

View File

@ -1,114 +0,0 @@
// Copyright 2011 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This program makes a read only call to the Google Cloud Storage API,
// authenticated with OAuth2. A list of example APIs can be found at
// https://code.google.com/oauthplayground/
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"code.google.com/p/goauth2/oauth/jwt"
)
const scope = "https://www.googleapis.com/auth/devstorage.read_only"
var (
secretsFile = flag.String("s", "", "JSON encoded secrets for the service account")
pemFile = flag.String("k", "", "private pem key file for the service account")
)
const usageMsg = `
You must specify -k and -s.
To obtain client secrets and pem, see the "OAuth 2 Credentials" section under
the "API Access" tab on this page: https://code.google.com/apis/console/
Google Cloud Storage must also be turned on in the API console.
`
func main() {
flag.Parse()
if *secretsFile == "" || *pemFile == "" {
flag.Usage()
fmt.Println(usageMsg)
return
}
// Read the secret file bytes into the config.
secretBytes, err := ioutil.ReadFile(*secretsFile)
if err != nil {
log.Fatal("error reading secerets file:", err)
}
var config struct {
Web struct {
ClientEmail string `json:"client_email"`
ClientID string `json:"client_id"`
TokenURI string `json:"token_uri"`
}
}
err = json.Unmarshal(secretBytes, &config)
if err != nil {
log.Fatal("error unmarshalling secerets:", err)
}
// Get the project ID from the client ID.
projectID := strings.SplitN(config.Web.ClientID, "-", 2)[0]
// Read the pem file bytes for the private key.
keyBytes, err := ioutil.ReadFile(*pemFile)
if err != nil {
log.Fatal("error reading private key file:", err)
}
// Craft the ClaimSet and JWT token.
t := jwt.NewToken(config.Web.ClientEmail, scope, keyBytes)
t.ClaimSet.Aud = config.Web.TokenURI
// We need to provide a client.
c := &http.Client{}
// Get the access token.
o, err := t.Assert(c)
if err != nil {
log.Fatal("assertion error:", err)
}
// Refresh token will be missing, but this access_token will be good
// for one hour.
fmt.Printf("access_token = %v\n", o.AccessToken)
fmt.Printf("refresh_token = %v\n", o.RefreshToken)
fmt.Printf("expires %v\n", o.Expiry)
// Form the request to list Google Cloud Storage buckets.
req, err := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
if err != nil {
log.Fatal("http.NewRequest:", err)
}
req.Header.Set("Authorization", "OAuth "+o.AccessToken)
req.Header.Set("x-goog-api-version", "2")
req.Header.Set("x-goog-project-id", projectID)
// Make the request.
r, err := c.Do(req)
if err != nil {
log.Fatal("API request error:", err)
}
defer r.Body.Close()
// Write the response to standard output.
res, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Fatal("error reading API request results:", err)
}
fmt.Printf("\nRESULT:\n%s\n", res)
}

View File

@ -1,511 +0,0 @@
// Copyright 2012 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// The jwt package provides support for creating credentials for OAuth2 service
// account requests.
//
// For examples of the package usage please see jwt_test.go.
// Example usage (error handling omitted for brevity):
//
// // Craft the ClaimSet and JWT token.
// iss := "XXXXXXXXXXXX@developer.gserviceaccount.com"
// scope := "https://www.googleapis.com/auth/devstorage.read_only"
// t := jwt.NewToken(iss, scope, pemKeyBytes)
//
// // We need to provide a client.
// c := &http.Client{}
//
// // Get the access token.
// o, _ := t.Assert(c)
//
// // Form the request to the service.
// req, _ := http.NewRequest("GET", "https://storage.googleapis.com/", nil)
// req.Header.Set("Authorization", "OAuth "+o.AccessToken)
// req.Header.Set("x-goog-api-version", "2")
// req.Header.Set("x-goog-project-id", "XXXXXXXXXXXX")
//
// // Make the request.
// result, _ := c.Do(req)
//
// For info on OAuth2 service accounts please see the online documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount
//
package jwt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"code.google.com/p/goauth2/oauth"
)
// These are the default/standard values for this to work for Google service accounts.
const (
stdAlgorithm = "RS256"
stdType = "JWT"
stdAssertionType = "http://oauth.net/grant_type/jwt/1.0/bearer"
stdGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
stdAud = "https://accounts.google.com/o/oauth2/token"
)
var (
ErrInvalidKey = errors.New("Invalid Key")
)
// base64Encode returns and Base64url encoded version of the input string with any
// trailing "=" stripped.
func base64Encode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
// base64Decode decodes the Base64url encoded string
func base64Decode(s string) ([]byte, error) {
// add back missing padding
switch len(s) % 4 {
case 2:
s += "=="
case 3:
s += "="
}
return base64.URLEncoding.DecodeString(s)
}
// The JWT claim set contains information about the JWT including the
// permissions being requested (scopes), the target of the token, the issuer,
// the time the token was issued, and the lifetime of the token.
//
// Aud is usually https://accounts.google.com/o/oauth2/token
type ClaimSet struct {
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
Prn string `json:"prn,omitempty"` // email for which the application is requesting delegated access (Optional).
Exp int64 `json:"exp"`
Iat int64 `json:"iat"`
Typ string `json:"typ,omitempty"`
Sub string `json:"sub,omitempty"` // Add support for googleapi delegation support
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
PrivateClaims map[string]interface{} `json:"-"`
exp time.Time
iat time.Time
}
// setTimes sets iat and exp to time.Now() and iat.Add(time.Hour) respectively.
//
// Note that these times have nothing to do with the expiration time for the
// access_token returned by the server. These have to do with the lifetime of
// the encoded JWT.
//
// A JWT can be re-used for up to one hour after it was encoded. The access
// token that is granted will also be good for one hour so there is little point
// in trying to use the JWT a second time.
func (c *ClaimSet) setTimes(t time.Time) {
c.iat = t
c.exp = c.iat.Add(time.Hour)
}
var (
jsonStart = []byte{'{'}
jsonEnd = []byte{'}'}
)
// encode returns the Base64url encoded form of the Signature.
func (c *ClaimSet) encode() string {
if c.exp.IsZero() || c.iat.IsZero() {
c.setTimes(time.Now())
}
if c.Aud == "" {
c.Aud = stdAud
}
c.Exp = c.exp.Unix()
c.Iat = c.iat.Unix()
b, err := json.Marshal(c)
if err != nil {
panic(err)
}
if len(c.PrivateClaims) == 0 {
return base64Encode(b)
}
// Marshal private claim set and then append it to b.
prv, err := json.Marshal(c.PrivateClaims)
if err != nil {
panic(fmt.Errorf("Invalid map of private claims %v", c.PrivateClaims))
}
// Concatenate public and private claim JSON objects.
if !bytes.HasSuffix(b, jsonEnd) {
panic(fmt.Errorf("Invalid JSON %s", b))
}
if !bytes.HasPrefix(prv, jsonStart) {
panic(fmt.Errorf("Invalid JSON %s", prv))
}
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
b = append(b, prv[1:]...) // Append private claims.
return base64Encode(b)
}
// Header describes the algorithm and type of token being generated,
// and optionally a KeyID describing additional parameters for the
// signature.
type Header struct {
Algorithm string `json:"alg"`
Type string `json:"typ"`
KeyId string `json:"kid,omitempty"`
}
func (h *Header) encode() string {
b, err := json.Marshal(h)
if err != nil {
panic(err)
}
return base64Encode(b)
}
// A JWT is composed of three parts: a header, a claim set, and a signature.
// The well formed and encoded JWT can then be exchanged for an access token.
//
// The Token is not a JWT, but is is encoded to produce a well formed JWT.
//
// When obtaining a key from the Google API console it will be downloaded in a
// PKCS12 encoding. To use this key you will need to convert it to a PEM file.
// This can be achieved with openssl.
//
// $ openssl pkcs12 -in <key.p12> -nocerts -passin pass:notasecret -nodes -out <key.pem>
//
// The contents of this file can then be used as the Key.
type Token struct {
ClaimSet *ClaimSet // claim set used to construct the JWT
Header *Header // header used to construct the JWT
Key []byte // PEM printable encoding of the private key
pKey *rsa.PrivateKey
header string
claim string
sig string
useExternalSigner bool
signer Signer
}
// NewToken returns a filled in *Token based on the standard header,
// and sets the Iat and Exp times based on when the call to Assert is
// made.
func NewToken(iss, scope string, key []byte) *Token {
c := &ClaimSet{
Iss: iss,
Scope: scope,
Aud: stdAud,
}
h := &Header{
Algorithm: stdAlgorithm,
Type: stdType,
}
t := &Token{
ClaimSet: c,
Header: h,
Key: key,
}
return t
}
// Signer is an interface that given a JWT token, returns the header &
// claim (serialized and urlEncoded to a byte slice), along with the
// signature and an error (if any occured). It could modify any data
// to sign (typically the KeyID).
//
// Example usage where a SHA256 hash of the original url-encoded token
// with an added KeyID and secret data is used as a signature:
//
// var privateData = "secret data added to hash, indexed by KeyID"
//
// type SigningService struct{}
//
// func (ss *SigningService) Sign(in *jwt.Token) (newTokenData, sig []byte, err error) {
// in.Header.KeyID = "signing service"
// newTokenData = in.EncodeWithoutSignature()
// dataToSign := fmt.Sprintf("%s.%s", newTokenData, privateData)
// h := sha256.New()
// _, err := h.Write([]byte(dataToSign))
// sig = h.Sum(nil)
// return
// }
type Signer interface {
Sign(in *Token) (tokenData, signature []byte, err error)
}
// NewSignerToken returns a *Token, using an external signer function
func NewSignerToken(iss, scope string, signer Signer) *Token {
t := NewToken(iss, scope, nil)
t.useExternalSigner = true
t.signer = signer
return t
}
// Expired returns a boolean value letting us know if the token has expired.
func (t *Token) Expired() bool {
return t.ClaimSet.exp.Before(time.Now())
}
// Encode constructs and signs a Token returning a JWT ready to use for
// requesting an access token.
func (t *Token) Encode() (string, error) {
var tok string
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
err := t.sign()
if err != nil {
return tok, err
}
tok = fmt.Sprintf("%s.%s.%s", t.header, t.claim, t.sig)
return tok, nil
}
// EncodeWithoutSignature returns the url-encoded value of the Token
// before signing has occured (typically for use by external signers).
func (t *Token) EncodeWithoutSignature() string {
t.header = t.Header.encode()
t.claim = t.ClaimSet.encode()
return fmt.Sprintf("%s.%s", t.header, t.claim)
}
// sign computes the signature for a Token. The details for this can be found
// in the OAuth2 Service Account documentation.
// https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
func (t *Token) sign() error {
if t.useExternalSigner {
fulldata, sig, err := t.signer.Sign(t)
if err != nil {
return err
}
split := strings.Split(string(fulldata), ".")
if len(split) != 2 {
return errors.New("no token returned")
}
t.header = split[0]
t.claim = split[1]
t.sig = base64Encode(sig)
return err
}
ss := fmt.Sprintf("%s.%s", t.header, t.claim)
if t.pKey == nil {
err := t.parsePrivateKey()
if err != nil {
return err
}
}
h := sha256.New()
h.Write([]byte(ss))
b, err := rsa.SignPKCS1v15(rand.Reader, t.pKey, crypto.SHA256, h.Sum(nil))
t.sig = base64Encode(b)
return err
}
// parsePrivateKey converts the Token's Key ([]byte) into a parsed
// rsa.PrivateKey. If the key is not well formed this method will return an
// ErrInvalidKey error.
func (t *Token) parsePrivateKey() error {
block, _ := pem.Decode(t.Key)
if block == nil {
return ErrInvalidKey
}
parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
}
var ok bool
t.pKey, ok = parsedKey.(*rsa.PrivateKey)
if !ok {
return ErrInvalidKey
}
return nil
}
// Assert obtains an *oauth.Token from the remote server by encoding and sending
// a JWT. The access_token will expire in one hour (3600 seconds) and cannot be
// refreshed (no refresh_token is returned with the response). Once this token
// expires call this method again to get a fresh one.
func (t *Token) Assert(c *http.Client) (*oauth.Token, error) {
var o *oauth.Token
t.ClaimSet.setTimes(time.Now())
u, v, err := t.buildRequest()
if err != nil {
return o, err
}
resp, err := c.PostForm(u, v)
if err != nil {
return o, err
}
o, err = handleResponse(resp)
return o, err
}
// buildRequest sets up the URL values and the proper URL string for making our
// access_token request.
func (t *Token) buildRequest() (string, url.Values, error) {
v := url.Values{}
j, err := t.Encode()
if err != nil {
return t.ClaimSet.Aud, v, err
}
v.Set("grant_type", stdGrantType)
v.Set("assertion", j)
return t.ClaimSet.Aud, v, nil
}
// Used for decoding the response body.
type respBody struct {
IdToken string `json:"id_token"`
Access string `json:"access_token"`
Type string `json:"token_type"`
ExpiresIn time.Duration `json:"expires_in"`
}
// handleResponse returns a filled in *oauth.Token given the *http.Response from
// a *http.Request created by buildRequest.
func handleResponse(r *http.Response) (*oauth.Token, error) {
o := &oauth.Token{}
defer r.Body.Close()
if r.StatusCode != 200 {
return o, errors.New("invalid response: " + r.Status)
}
b := &respBody{}
err := json.NewDecoder(r.Body).Decode(b)
if err != nil {
return o, err
}
o.AccessToken = b.Access
if b.IdToken != "" {
// decode returned id token to get expiry
o.AccessToken = b.IdToken
s := strings.Split(b.IdToken, ".")
if len(s) < 2 {
return nil, errors.New("invalid token received")
}
d, err := base64Decode(s[1])
if err != nil {
return o, err
}
c := &ClaimSet{}
err = json.NewDecoder(bytes.NewBuffer(d)).Decode(c)
if err != nil {
return o, err
}
o.Expiry = time.Unix(c.Exp, 0)
return o, nil
}
o.Expiry = time.Now().Add(b.ExpiresIn * time.Second)
return o, nil
}
// Transport implements http.RoundTripper. When configured with a valid
// JWT and OAuth tokens it can be used to make authenticated HTTP requests.
//
// t := &jwt.Transport{jwtToken, oauthToken}
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the OAuth token if it can, updating in place.
type Transport struct {
JWTToken *Token
OAuthToken *oauth.Token
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
Transport http.RoundTripper
}
// Creates a new authenticated transport.
func NewTransport(token *Token) (*Transport, error) {
oa, err := token.Assert(new(http.Client))
if err != nil {
return nil, err
}
return &Transport{
JWTToken: token,
OAuthToken: oa,
}, nil
}
// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
// Fetches the internal transport.
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// RoundTrip executes a single HTTP transaction using the Transport's
// OAuthToken as authorization headers.
//
// This method will attempt to renew the token if it has expired and may return
// an error related to that token renewal before attempting the client request.
// If the token cannot be renewed a non-nil os.Error value will be returned.
// If the token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
// Sanity check the two tokens
if t.JWTToken == nil {
return nil, fmt.Errorf("no JWT token supplied")
}
if t.OAuthToken == nil {
return nil, fmt.Errorf("no OAuth token supplied")
}
// Refresh the OAuth token if it has expired
if t.OAuthToken.Expired() {
if oa, err := t.JWTToken.Assert(new(http.Client)); err != nil {
return nil, err
} else {
t.OAuthToken = oa
}
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+t.OAuthToken.AccessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}

View File

@ -1,486 +0,0 @@
// Copyright 2012 The goauth2 Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// For package documentation please see jwt.go.
//
package jwt
import (
"bytes"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/json"
"encoding/pem"
"io/ioutil"
"net/http"
"testing"
"time"
)
const (
stdHeaderStr = `{"alg":"RS256","typ":"JWT"}`
iss = "761326798069-r5mljlln1rd4lrbhg75efgigp36m78j5@developer.gserviceaccount.com"
scope = "https://www.googleapis.com/auth/prediction"
exp = 1328554385
iat = 1328550785 // exp + 1 hour
)
// Base64url encoded Header
const headerEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
// Base64url encoded ClaimSet
const claimSetEnc = "eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ"
// Base64url encoded Signature
const sigEnc = "olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
// Base64url encoded Token
const tokEnc = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU1NDM4NSwiaWF0IjoxMzI4NTUwNzg1fQ.olukbHreNiYrgiGCTEmY3eWGeTvYDSUHYoE84Jz3BRPBSaMdZMNOn_0CYK7UHPO7OdvUofjwft1dH59UxE9GWS02pjFti1uAQoImaqjLZoTXr8qiF6O_kDa9JNoykklWlRAIwGIZkDupCS-8cTAnM_ksSymiH1coKJrLDUX_BM0x2f4iMFQzhL5vT1ll-ZipJ0lNlxb5QsyXxDYcxtHYguF12-vpv3ItgT0STfcXoWzIGQoEbhwB9SBp9JYcQ8Ygz6pYDjm0rWX9LrchmTyDArCodpKLFtutNgcIFUP9fWxvwd1C2dNw5GjLcKr9a_SAERyoJ2WnCR1_j9N0wD2o0g"
// Private key for testing
const privateKeyPem = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj
7wZgkdmM7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/
xmVU1WeruQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYs
SliS5qQpgyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18
pe+zpyl4+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xk
SBc//fy3ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABAoIBAQDGGHzQxGKX+ANk
nQi53v/c6632dJKYXVJC+PDAz4+bzU800Y+n/bOYsWf/kCp94XcG4Lgsdd0Gx+Zq
HD9CI1IcqqBRR2AFscsmmX6YzPLTuEKBGMW8twaYy3utlFxElMwoUEsrSWRcCA1y
nHSDzTt871c7nxCXHxuZ6Nm/XCL7Bg8uidRTSC1sQrQyKgTPhtQdYrPQ4WZ1A4J9
IisyDYmZodSNZe5P+LTJ6M1SCgH8KH9ZGIxv3diMwzNNpk3kxJc9yCnja4mjiGE2
YCNusSycU5IhZwVeCTlhQGcNeV/skfg64xkiJE34c2y2ttFbdwBTPixStGaF09nU
Z422D40BAoGBAPvVyRRsC3BF+qZdaSMFwI1yiXY7vQw5+JZh01tD28NuYdRFzjcJ
vzT2n8LFpj5ZfZFvSMLMVEFVMgQvWnN0O6xdXvGov6qlRUSGaH9u+TCPNnIldjMP
B8+xTwFMqI7uQr54wBB+Poq7dVRP+0oHb0NYAwUBXoEuvYo3c/nDoRcZAoGBAOWl
aLHjMv4CJbArzT8sPfic/8waSiLV9Ixs3Re5YREUTtnLq7LoymqB57UXJB3BNz/2
eCueuW71avlWlRtE/wXASj5jx6y5mIrlV4nZbVuyYff0QlcG+fgb6pcJQuO9DxMI
aqFGrWP3zye+LK87a6iR76dS9vRU+bHZpSVvGMKJAoGAFGt3TIKeQtJJyqeUWNSk
klORNdcOMymYMIlqG+JatXQD1rR6ThgqOt8sgRyJqFCVT++YFMOAqXOBBLnaObZZ
CFbh1fJ66BlSjoXff0W+SuOx5HuJJAa5+WtFHrPajwxeuRcNa8jwxUsB7n41wADu
UqWWSRedVBg4Ijbw3nWwYDECgYB0pLew4z4bVuvdt+HgnJA9n0EuYowVdadpTEJg
soBjNHV4msLzdNqbjrAqgz6M/n8Ztg8D2PNHMNDNJPVHjJwcR7duSTA6w2p/4k28
bvvk/45Ta3XmzlxZcZSOct3O31Cw0i2XDVc018IY5be8qendDYM08icNo7vQYkRH
504kQQKBgQDjx60zpz8ozvm1XAj0wVhi7GwXe+5lTxiLi9Fxq721WDxPMiHDW2XL
YXfFVy/9/GIMvEiGYdmarK1NW+VhWl1DC5xhDg0kvMfxplt4tynoq1uTsQTY31Mx
BeF5CT/JuNYk3bEBF0H/Q3VGO1/ggVS+YezdFbLWIRoMnLj6XCFEGg==
-----END RSA PRIVATE KEY-----`
// Public key to go with the private key for testing
const publicKeyPem = `-----BEGIN CERTIFICATE-----
MIIDIzCCAgugAwIBAgIJAMfISuBQ5m+5MA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
BAMTCnVuaXQtdGVzdHMwHhcNMTExMjA2MTYyNjAyWhcNMjExMjAzMTYyNjAyWjAV
MRMwEQYDVQQDEwp1bml0LXRlc3RzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEA4ej0p7bQ7L/r4rVGUz9RN4VQWoej1Bg1mYWIDYslvKrk1gpj7wZgkdmM
7oVK2OfgrSj/FCTkInKPqaCR0gD7K80q+mLBrN3PUkDrJQZpvRZIff3/xmVU1Wer
uQLFJjnFb2dqu0s/FY/2kWiJtBCakXvXEOb7zfbINuayL+MSsCGSdVYsSliS5qQp
gyDap+8b5fpXZVJkq92hrcNtbkg7hCYUJczt8n9hcCTJCfUpApvaFQ18pe+zpyl4
+WzkP66I28hniMQyUlA1hBiskT7qiouq0m8IOodhv2fagSZKjOTTU2xkSBc//fy3
ZpsL7WqgsZS7Q+0VRK8gKfqkxg5OYQIDAQABo3YwdDAdBgNVHQ4EFgQU2RQ8yO+O
gN8oVW2SW7RLrfYd9jEwRQYDVR0jBD4wPIAU2RQ8yO+OgN8oVW2SW7RLrfYd9jGh
GaQXMBUxEzARBgNVBAMTCnVuaXQtdGVzdHOCCQDHyErgUOZvuTAMBgNVHRMEBTAD
AQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBRv+M/6+FiVu7KXNjFI5pSN17OcW5QUtPr
odJMlWrJBtynn/TA1oJlYu3yV5clc/71Vr/AxuX5xGP+IXL32YDF9lTUJXG/uUGk
+JETpKmQviPbRsvzYhz4pf6ZIOZMc3/GIcNq92ECbseGO+yAgyWUVKMmZM0HqXC9
ovNslqe0M8C1sLm1zAR5z/h/litE7/8O2ietija3Q/qtl2TOXJdCA6sgjJX2WUql
ybrC55ct18NKf3qhpcEkGQvFU40rVYApJpi98DiZPYFdx1oBDp/f4uZ3ojpxRVFT
cDwcJLfNRCPUhormsY7fDS9xSyThiHsW9mjJYdcaKQkwYZ0F11yB
-----END CERTIFICATE-----`
var (
privateKeyPemBytes = []byte(privateKeyPem)
publicKeyPemBytes = []byte(publicKeyPem)
stdHeader = &Header{Algorithm: stdAlgorithm, Type: stdType}
)
// Testing the urlEncode function.
func TestUrlEncode(t *testing.T) {
enc := base64Encode([]byte(stdHeaderStr))
b := []byte(enc)
if b[len(b)-1] == 61 {
t.Error("TestUrlEncode: last chat == \"=\"")
}
if enc != headerEnc {
t.Error("TestUrlEncode: enc != headerEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" headerEnc = %s", headerEnc)
}
}
// Test that the times are set properly.
func TestClaimSetSetTimes(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
}
iat := time.Unix(iat, 0)
c.setTimes(iat)
if c.exp.Unix() != exp {
t.Error("TestClaimSetSetTimes: c.exp != exp")
t.Errorf(" c.Exp = %d", c.exp.Unix())
t.Errorf(" exp = %d", exp)
}
}
// Given a well formed ClaimSet, test for proper encoding.
func TestClaimSetEncode(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
enc := c.encode()
re, err := base64Decode(enc)
if err != nil {
t.Fatalf("error decoding encoded claim set: %v", err)
}
wa, err := base64Decode(claimSetEnc)
if err != nil {
t.Fatalf("error decoding encoded expected claim set: %v", err)
}
if enc != claimSetEnc {
t.Error("TestClaimSetEncode: enc != claimSetEnc")
t.Errorf(" enc = %s", string(re))
t.Errorf(" claimSetEnc = %s", string(wa))
}
}
// Test that claim sets with private claim names are encoded correctly.
func TestClaimSetWithPrivateNameEncode(t *testing.T) {
iatT := time.Unix(iat, 0)
expT := time.Unix(exp, 0)
i, err := json.Marshal(iatT.Unix())
if err != nil {
t.Fatalf("error marshaling iatT value of %v: %v", iatT.Unix(), err)
}
iatStr := string(i)
e, err := json.Marshal(expT.Unix())
if err != nil {
t.Fatalf("error marshaling expT value of %v: %v", expT.Unix(), err)
}
expStr := string(e)
testCases := []struct {
desc string
input map[string]interface{}
want string
}{
// Test a simple int field.
{
"single simple field",
map[string]interface{}{"amount": 22},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"amount":22` +
`}`,
},
{
"multiple simple fields",
map[string]interface{}{"tracking_code": "axZf", "amount": 22},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"amount":22,` +
`"tracking_code":"axZf"` +
`}`,
},
{
"nested struct fields",
map[string]interface{}{
"tracking_code": "axZf",
"purchase": struct {
Description string `json:"desc"`
Quantity int32 `json:"q"`
Time int64 `json:"t"`
}{
"toaster",
5,
iat,
},
},
`{` +
`"iss":"` + iss + `",` +
`"scope":"` + scope + `",` +
`"aud":"` + stdAud + `",` +
`"exp":` + expStr + `,` +
`"iat":` + iatStr + `,` +
`"purchase":{"desc":"toaster","q":5,"t":` + iatStr + `},` +
`"tracking_code":"axZf"` +
`}`,
},
}
for _, testCase := range testCases {
c := &ClaimSet{
Iss: iss,
Scope: scope,
Aud: stdAud,
iat: iatT,
exp: expT,
PrivateClaims: testCase.input,
}
cJSON, err := base64Decode(c.encode())
if err != nil {
t.Fatalf("error decoding claim set: %v", err)
}
if string(cJSON) != testCase.want {
t.Errorf("TestClaimSetWithPrivateNameEncode: enc != want in case %s", testCase.desc)
t.Errorf(" enc = %s", cJSON)
t.Errorf(" want = %s", testCase.want)
}
}
}
// Test the NewToken constructor.
func TestNewToken(t *testing.T) {
tok := NewToken(iss, scope, privateKeyPemBytes)
if tok.ClaimSet.Iss != iss {
t.Error("TestNewToken: tok.ClaimSet.Iss != iss")
t.Errorf(" tok.ClaimSet.Iss = %s", tok.ClaimSet.Iss)
t.Errorf(" iss = %s", iss)
}
if tok.ClaimSet.Scope != scope {
t.Error("TestNewToken: tok.ClaimSet.Scope != scope")
t.Errorf(" tok.ClaimSet.Scope = %s", tok.ClaimSet.Scope)
t.Errorf(" scope = %s", scope)
}
if tok.ClaimSet.Aud != stdAud {
t.Error("TestNewToken: tok.ClaimSet.Aud != stdAud")
t.Errorf(" tok.ClaimSet.Aud = %s", tok.ClaimSet.Aud)
t.Errorf(" stdAud = %s", stdAud)
}
if !bytes.Equal(tok.Key, privateKeyPemBytes) {
t.Error("TestNewToken: tok.Key != privateKeyPemBytes")
t.Errorf(" tok.Key = %s", tok.Key)
t.Errorf(" privateKeyPemBytes = %s", privateKeyPemBytes)
}
}
// Make sure the private key parsing functions work.
func TestParsePrivateKey(t *testing.T) {
tok := &Token{
Key: privateKeyPemBytes,
}
err := tok.parsePrivateKey()
if err != nil {
t.Errorf("TestParsePrivateKey:tok.parsePrivateKey: %v", err)
}
}
// Test that the token signature generated matches the golden standard.
func TestTokenSign(t *testing.T) {
tok := &Token{
Key: privateKeyPemBytes,
claim: claimSetEnc,
header: headerEnc,
}
err := tok.parsePrivateKey()
if err != nil {
t.Errorf("TestTokenSign:tok.parsePrivateKey: %v", err)
}
err = tok.sign()
if err != nil {
t.Errorf("TestTokenSign:tok.sign: %v", err)
}
if tok.sig != sigEnc {
t.Error("TestTokenSign: tok.sig != sigEnc")
t.Errorf(" tok.sig = %s", tok.sig)
t.Errorf(" sigEnc = %s", sigEnc)
}
}
// Test that the token expiration function is working.
func TestTokenExpired(t *testing.T) {
c := &ClaimSet{}
tok := &Token{
ClaimSet: c,
}
now := time.Now()
c.setTimes(now)
if tok.Expired() != false {
t.Error("TestTokenExpired: tok.Expired != false")
}
// Set the times as if they were set 2 hours ago.
c.setTimes(now.Add(-2 * time.Hour))
if tok.Expired() != true {
t.Error("TestTokenExpired: tok.Expired != true")
}
}
// Given a well formed Token, test for proper encoding.
func TestTokenEncode(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Header: stdHeader,
Key: privateKeyPemBytes,
}
enc, err := tok.Encode()
if err != nil {
t.Errorf("TestTokenEncode:tok.Assertion: %v", err)
}
if enc != tokEnc {
t.Error("TestTokenEncode: enc != tokEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" tokEnc = %s", tokEnc)
}
}
// Given a well formed Token we should get back a well formed request.
func TestBuildRequest(t *testing.T) {
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Header: stdHeader,
Key: privateKeyPemBytes,
}
u, v, err := tok.buildRequest()
if err != nil {
t.Errorf("TestBuildRequest:BuildRequest: %v", err)
}
if u != c.Aud {
t.Error("TestBuildRequest: u != c.Aud")
t.Errorf(" u = %s", u)
t.Errorf(" c.Aud = %s", c.Aud)
}
if v.Get("grant_type") != stdGrantType {
t.Error("TestBuildRequest: grant_type != stdGrantType")
t.Errorf(" grant_type = %s", v.Get("grant_type"))
t.Errorf(" stdGrantType = %s", stdGrantType)
}
if v.Get("assertion") != tokEnc {
t.Error("TestBuildRequest: assertion != tokEnc")
t.Errorf(" assertion = %s", v.Get("assertion"))
t.Errorf(" tokEnc = %s", tokEnc)
}
}
// Given a well formed access request response we should get back a oauth.Token.
func TestHandleResponse(t *testing.T) {
rb := &respBody{
Access: "1/8xbJqaOZXSUZbHLl5EOtu1pxz3fmmetKx9W8CV4t79M",
Type: "Bearer",
ExpiresIn: 3600,
}
b, err := json.Marshal(rb)
if err != nil {
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
}
r := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}
o, err := handleResponse(r)
if err != nil {
t.Errorf("TestHandleResponse:handleResponse: %v", err)
}
if o.AccessToken != rb.Access {
t.Error("TestHandleResponse: o.AccessToken != rb.Access")
t.Errorf(" o.AccessToken = %s", o.AccessToken)
t.Errorf(" rb.Access = %s", rb.Access)
}
if o.Expired() {
t.Error("TestHandleResponse: o.Expired == true")
}
}
// passthrough signature for test
type FakeSigner struct{}
func (f FakeSigner) Sign(tok *Token) ([]byte, []byte, error) {
block, _ := pem.Decode(privateKeyPemBytes)
pKey, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
ss := headerEnc + "." + claimSetEnc
h := sha256.New()
h.Write([]byte(ss))
b, _ := rsa.SignPKCS1v15(rand.Reader, pKey, crypto.SHA256, h.Sum(nil))
return []byte(ss), b, nil
}
// Given an external signer, get back a valid and signed JWT
func TestExternalSigner(t *testing.T) {
tok := NewSignerToken(iss, scope, FakeSigner{})
enc, _ := tok.Encode()
if enc != tokEnc {
t.Errorf("TestExternalSigner: enc != tokEnc")
t.Errorf(" enc = %s", enc)
t.Errorf(" tokEnc = %s", tokEnc)
}
}
func TestHandleResponseWithNewExpiry(t *testing.T) {
rb := &respBody{
IdToken: tokEnc,
}
b, err := json.Marshal(rb)
if err != nil {
t.Errorf("TestHandleResponse:json.Marshal: %v", err)
}
r := &http.Response{
Status: "200 OK",
StatusCode: 200,
Body: ioutil.NopCloser(bytes.NewReader(b)),
}
o, err := handleResponse(r)
if err != nil {
t.Errorf("TestHandleResponse:handleResponse: %v", err)
}
if o.Expiry != time.Unix(exp, 0) {
t.Error("TestHandleResponse: o.Expiry != exp")
t.Errorf(" o.Expiry = %s", o.Expiry)
t.Errorf(" exp = %s", time.Unix(exp, 0))
}
}
// Placeholder for future Assert tests.
func TestAssert(t *testing.T) {
// Since this method makes a call to BuildRequest, an htttp.Client, and
// finally HandleResponse there is not much more to test. This is here
// as a placeholder if that changes.
}
// Benchmark for the end-to-end encoding of a well formed token.
func BenchmarkTokenEncode(b *testing.B) {
b.StopTimer()
c := &ClaimSet{
Iss: iss,
Scope: scope,
exp: time.Unix(exp, 0),
iat: time.Unix(iat, 0),
}
tok := &Token{
ClaimSet: c,
Key: privateKeyPemBytes,
}
b.StartTimer()
for i := 0; i < b.N; i++ {
tok.Encode()
}
}

View File

@ -1,476 +0,0 @@
// Copyright 2011 The goauth2 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 oauth supports making OAuth2-authenticated HTTP requests.
//
// Example usage:
//
// // Specify your configuration. (typically as a global variable)
// var config = &oauth.Config{
// ClientId: YOUR_CLIENT_ID,
// ClientSecret: YOUR_CLIENT_SECRET,
// Scope: "https://www.googleapis.com/auth/buzz",
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
// TokenURL: "https://accounts.google.com/o/oauth2/token",
// RedirectURL: "http://you.example.org/handler",
// }
//
// // A landing page redirects to the OAuth provider to get the auth code.
// func landing(w http.ResponseWriter, r *http.Request) {
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
// }
//
// // The user will be redirected back to this handler, that takes the
// // "code" query parameter and Exchanges it for an access token.
// func handler(w http.ResponseWriter, r *http.Request) {
// t := &oauth.Transport{Config: config}
// t.Exchange(r.FormValue("code"))
// // The Transport now has a valid Token. Create an *http.Client
// // with which we can make authenticated API requests.
// c := t.Client()
// c.Post(...)
// // ...
// // btw, r.FormValue("state") == "foo"
// }
//
package oauth
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
)
// OAuthError is the error type returned by many operations.
//
// In retrospect it should not exist. Don't depend on it.
type OAuthError struct {
prefix string
msg string
}
func (oe OAuthError) Error() string {
return "OAuthError: " + oe.prefix + ": " + oe.msg
}
// Cache specifies the methods that implement a Token cache.
type Cache interface {
Token() (*Token, error)
PutToken(*Token) error
}
// CacheFile implements Cache. Its value is the name of the file in which
// the Token is stored in JSON format.
type CacheFile string
func (f CacheFile) Token() (*Token, error) {
file, err := os.Open(string(f))
if err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
defer file.Close()
tok := &Token{}
if err := json.NewDecoder(file).Decode(tok); err != nil {
return nil, OAuthError{"CacheFile.Token", err.Error()}
}
return tok, nil
}
func (f CacheFile) PutToken(tok *Token) error {
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := json.NewEncoder(file).Encode(tok); err != nil {
file.Close()
return OAuthError{"CacheFile.PutToken", err.Error()}
}
if err := file.Close(); err != nil {
return OAuthError{"CacheFile.PutToken", err.Error()}
}
return nil
}
// Config is the configuration of an OAuth consumer.
type Config struct {
// ClientId is the OAuth client identifier used when communicating with
// the configured OAuth provider.
ClientId string
// ClientSecret is the OAuth client secret used when communicating with
// the configured OAuth provider.
ClientSecret string
// Scope identifies the level of access being requested. Multiple scope
// values should be provided as a space-delimited string.
Scope string
// AuthURL is the URL the user will be directed to in order to grant
// access.
AuthURL string
// TokenURL is the URL used to retrieve OAuth tokens.
TokenURL string
// RedirectURL is the URL to which the user will be returned after
// granting (or denying) access.
RedirectURL string
// TokenCache allows tokens to be cached for subsequent requests.
TokenCache Cache
// AccessType is an OAuth extension that gets sent as the
// "access_type" field in the URL from AuthCodeURL.
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
// It may be "online" (the default) or "offline".
// If your application needs to refresh access tokens when the
// user is not present at the browser, then use offline. This
// will result in your application obtaining a refresh token
// the first time your application exchanges an authorization
// code for a user.
AccessType string
// ApprovalPrompt indicates whether the user should be
// re-prompted for consent. If set to "auto" (default) the
// user will be prompted only if they haven't previously
// granted consent and the code can only be exchanged for an
// access token.
// If set to "force" the user will always be prompted, and the
// code can be exchanged for a refresh token.
ApprovalPrompt string
}
// Token contains an end-user's tokens.
// This is the data you must store to persist authentication.
type Token struct {
AccessToken string
RefreshToken string
Expiry time.Time // If zero the token has no (known) expiry time.
// Extra optionally contains extra metadata from the server
// when updating a token. The only current key that may be
// populated is "id_token". It may be nil and will be
// initialized as needed.
Extra map[string]string
}
// Expired reports whether the token has expired or is invalid.
func (t *Token) Expired() bool {
if t.AccessToken == "" {
return true
}
if t.Expiry.IsZero() {
return false
}
return t.Expiry.Before(time.Now())
}
// Transport implements http.RoundTripper. When configured with a valid
// Config and Token it can be used to make authenticated HTTP requests.
//
// t := &oauth.Transport{config}
// t.Exchange(code)
// // t now contains a valid Token
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
//
// It will automatically refresh the Token if it can,
// updating the supplied Token in place.
type Transport struct {
*Config
*Token
// mu guards modifying the token.
mu sync.Mutex
// Transport is the HTTP transport to use when making requests.
// It will default to http.DefaultTransport if nil.
// (It should never be an oauth.Transport.)
Transport http.RoundTripper
}
// Client returns an *http.Client that makes OAuth-authenticated requests.
func (t *Transport) Client() *http.Client {
return &http.Client{Transport: t}
}
func (t *Transport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// AuthCodeURL returns a URL that the end-user should be redirected to,
// so that they may obtain an authorization code.
func (c *Config) AuthCodeURL(state string) string {
url_, err := url.Parse(c.AuthURL)
if err != nil {
panic("AuthURL malformed: " + err.Error())
}
q := url.Values{
"response_type": {"code"},
"client_id": {c.ClientId},
"state": condVal(state),
"scope": condVal(c.Scope),
"redirect_uri": condVal(c.RedirectURL),
"access_type": condVal(c.AccessType),
"approval_prompt": condVal(c.ApprovalPrompt),
}.Encode()
if url_.RawQuery == "" {
url_.RawQuery = q
} else {
url_.RawQuery += "&" + q
}
return url_.String()
}
func condVal(v string) []string {
if v == "" {
return nil
}
return []string{v}
}
// Exchange takes a code and gets access Token from the remote server.
func (t *Transport) Exchange(code string) (*Token, error) {
if t.Config == nil {
return nil, OAuthError{"Exchange", "no Config supplied"}
}
// If the transport or the cache already has a token, it is
// passed to `updateToken` to preserve existing refresh token.
tok := t.Token
if tok == nil && t.TokenCache != nil {
tok, _ = t.TokenCache.Token()
}
if tok == nil {
tok = new(Token)
}
err := t.updateToken(tok, url.Values{
"grant_type": {"authorization_code"},
"redirect_uri": {t.RedirectURL},
"scope": {t.Scope},
"code": {code},
})
if err != nil {
return nil, err
}
t.Token = tok
if t.TokenCache != nil {
return tok, t.TokenCache.PutToken(tok)
}
return tok, nil
}
// RoundTrip executes a single HTTP transaction using the Transport's
// Token as authorization headers.
//
// This method will attempt to renew the Token if it has expired and may return
// an error related to that Token renewal before attempting the client request.
// If the Token cannot be renewed a non-nil os.Error value will be returned.
// If the Token is invalid callers should expect HTTP-level errors,
// as indicated by the Response's StatusCode.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
accessToken, err := t.getAccessToken()
if err != nil {
return nil, err
}
// To set the Authorization header, we must make a copy of the Request
// so that we don't modify the Request we were given.
// This is required by the specification of http.RoundTripper.
req = cloneRequest(req)
req.Header.Set("Authorization", "Bearer "+accessToken)
// Make the HTTP request.
return t.transport().RoundTrip(req)
}
func (t *Transport) getAccessToken() (string, error) {
t.mu.Lock()
defer t.mu.Unlock()
if t.Token == nil {
if t.Config == nil {
return "", OAuthError{"RoundTrip", "no Config supplied"}
}
if t.TokenCache == nil {
return "", OAuthError{"RoundTrip", "no Token supplied"}
}
var err error
t.Token, err = t.TokenCache.Token()
if err != nil {
return "", err
}
}
// Refresh the Token if it has expired.
if t.Expired() {
if err := t.Refresh(); err != nil {
return "", err
}
}
if t.AccessToken == "" {
return "", errors.New("no access token obtained from refresh")
}
return t.AccessToken, nil
}
// cloneRequest returns a clone of the provided *http.Request.
// The clone is a shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header)
for k, s := range r.Header {
r2.Header[k] = s
}
return r2
}
// Refresh renews the Transport's AccessToken using its RefreshToken.
func (t *Transport) Refresh() error {
if t.Token == nil {
return OAuthError{"Refresh", "no existing Token"}
}
if t.RefreshToken == "" {
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
}
if t.Config == nil {
return OAuthError{"Refresh", "no Config supplied"}
}
err := t.updateToken(t.Token, url.Values{
"grant_type": {"refresh_token"},
"refresh_token": {t.RefreshToken},
})
if err != nil {
return err
}
if t.TokenCache != nil {
return t.TokenCache.PutToken(t.Token)
}
return nil
}
// AuthenticateClient gets an access Token using the client_credentials grant
// type.
func (t *Transport) AuthenticateClient() error {
if t.Config == nil {
return OAuthError{"Exchange", "no Config supplied"}
}
if t.Token == nil {
t.Token = &Token{}
}
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
}
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
// implements the OAuth2 spec correctly
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
// In summary:
// - Reddit only accepts client secret in the Authorization header
// - Dropbox accepts either it in URL param or Auth header, but not both.
// - Google only accepts URL param (not spec compliant?), not Auth header
func providerAuthHeaderWorks(tokenURL string) bool {
if strings.HasPrefix(tokenURL, "https://accounts.google.com/") ||
strings.HasPrefix(tokenURL, "https://github.com/") ||
strings.HasPrefix(tokenURL, "https://api.instagram.com/") ||
strings.HasPrefix(tokenURL, "https://www.douban.com/") {
// Some sites fail to implement the OAuth2 spec fully.
return false
}
// Assume the provider implements the spec properly
// otherwise. We can add more exceptions as they're
// discovered. We will _not_ be adding configurable hooks
// to this package to let users select server bugs.
return true
}
// updateToken mutates both tok and v.
func (t *Transport) updateToken(tok *Token, v url.Values) error {
v.Set("client_id", t.ClientId)
bustedAuth := !providerAuthHeaderWorks(t.TokenURL)
if bustedAuth {
v.Set("client_secret", t.ClientSecret)
}
client := &http.Client{Transport: t.transport()}
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if !bustedAuth {
req.SetBasicAuth(t.ClientId, t.ClientSecret)
}
r, err := client.Do(req)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != 200 {
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
}
var b struct {
Access string `json:"access_token"`
Refresh string `json:"refresh_token"`
ExpiresIn int64 `json:"expires_in"` // seconds
Id string `json:"id_token"`
}
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
if err != nil {
return err
}
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
switch content {
case "application/x-www-form-urlencoded", "text/plain":
vals, err := url.ParseQuery(string(body))
if err != nil {
return err
}
b.Access = vals.Get("access_token")
b.Refresh = vals.Get("refresh_token")
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
b.Id = vals.Get("id_token")
default:
if err = json.Unmarshal(body, &b); err != nil {
return fmt.Errorf("got bad response from server: %q", body)
}
}
if b.Access == "" {
return errors.New("received empty access token from authorization server")
}
tok.AccessToken = b.Access
// Don't overwrite `RefreshToken` with an empty value
if b.Refresh != "" {
tok.RefreshToken = b.Refresh
}
if b.ExpiresIn == 0 {
tok.Expiry = time.Time{}
} else {
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
}
if b.Id != "" {
if tok.Extra == nil {
tok.Extra = make(map[string]string)
}
tok.Extra["id_token"] = b.Id
}
return nil
}

View File

@ -1,236 +0,0 @@
// Copyright 2011 The goauth2 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 oauth
import (
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"runtime"
"testing"
"time"
)
var requests = []struct {
path, query, auth string // request
contenttype, body string // response
}{
{
path: "/token",
query: "grant_type=authorization_code&code=c0d3&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token1",
"refresh_token":"refreshtoken1",
"id_token":"idtoken1",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token1", body: "first payload"},
{
path: "/token",
query: "grant_type=refresh_token&refresh_token=refreshtoken1&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token2",
"refresh_token":"refreshtoken2",
"id_token":"idtoken2",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token2", body: "second payload"},
{
path: "/token",
query: "grant_type=refresh_token&refresh_token=refreshtoken2&client_id=cl13nt1d",
contenttype: "application/x-www-form-urlencoded",
body: "access_token=token3&refresh_token=refreshtoken3&id_token=idtoken3&expires_in=3600",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
},
{path: "/secure", auth: "Bearer token3", body: "third payload"},
{
path: "/token",
query: "grant_type=client_credentials&client_id=cl13nt1d",
contenttype: "application/json",
auth: "Basic Y2wxM250MWQ6czNjcjN0",
body: `
{
"access_token":"token4",
"expires_in":3600
}
`,
},
{path: "/secure", auth: "Bearer token4", body: "fourth payload"},
}
func TestOAuth(t *testing.T) {
// Set up test server.
n := 0
handler := func(w http.ResponseWriter, r *http.Request) {
if n >= len(requests) {
t.Errorf("too many requests: %d", n)
return
}
req := requests[n]
n++
// Check request.
if g, w := r.URL.Path, req.path; g != w {
t.Errorf("request[%d] got path %s, want %s", n, g, w)
}
want, _ := url.ParseQuery(req.query)
for k := range want {
if g, w := r.FormValue(k), want.Get(k); g != w {
t.Errorf("query[%s] = %s, want %s", k, g, w)
}
}
if g, w := r.Header.Get("Authorization"), req.auth; w != "" && g != w {
t.Errorf("Authorization: %v, want %v", g, w)
}
// Send response.
w.Header().Set("Content-Type", req.contenttype)
io.WriteString(w, req.body)
}
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()
config := &Config{
ClientId: "cl13nt1d",
ClientSecret: "s3cr3t",
Scope: "https://example.net/scope",
AuthURL: server.URL + "/auth",
TokenURL: server.URL + "/token",
}
// TODO(adg): test AuthCodeURL
transport := &Transport{Config: config}
_, err := transport.Exchange("c0d3")
if err != nil {
t.Fatalf("Exchange: %v", err)
}
checkToken(t, transport.Token, "token1", "refreshtoken1", "idtoken1")
c := transport.Client()
resp, err := c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "first payload")
// test automatic refresh
transport.Expiry = time.Now().Add(-time.Hour)
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "second payload")
checkToken(t, transport.Token, "token2", "refreshtoken2", "idtoken2")
// refresh one more time, but get URL-encoded token instead of JSON
transport.Expiry = time.Now().Add(-time.Hour)
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "third payload")
checkToken(t, transport.Token, "token3", "refreshtoken3", "idtoken3")
transport.Token = &Token{}
err = transport.AuthenticateClient()
if err != nil {
t.Fatalf("AuthenticateClient: %v", err)
}
checkToken(t, transport.Token, "token4", "", "")
resp, err = c.Get(server.URL + "/secure")
if err != nil {
t.Fatalf("Get: %v", err)
}
checkBody(t, resp, "fourth payload")
}
func checkToken(t *testing.T, tok *Token, access, refresh, id string) {
if g, w := tok.AccessToken, access; g != w {
t.Errorf("AccessToken = %q, want %q", g, w)
}
if g, w := tok.RefreshToken, refresh; g != w {
t.Errorf("RefreshToken = %q, want %q", g, w)
}
if g, w := tok.Extra["id_token"], id; g != w {
t.Errorf("Extra['id_token'] = %q, want %q", g, w)
}
if tok.Expiry.IsZero() {
t.Errorf("Expiry is zero; want ~1 hour")
} else {
exp := tok.Expiry.Sub(time.Now())
const slop = 3 * time.Second // time moving during test
if (time.Hour-slop) > exp || exp > time.Hour {
t.Errorf("Expiry = %v, want ~1 hour", exp)
}
}
}
func checkBody(t *testing.T, r *http.Response, body string) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Errorf("reading reponse body: %v, want %q", err, body)
}
if g, w := string(b), body; g != w {
t.Errorf("request body mismatch: got %q, want %q", g, w)
}
}
func TestCachePermissions(t *testing.T) {
if runtime.GOOS == "windows" {
// Windows doesn't support file mode bits.
return
}
td, err := ioutil.TempDir("", "oauth-test")
if err != nil {
t.Fatalf("ioutil.TempDir: %v", err)
}
defer os.RemoveAll(td)
tempFile := filepath.Join(td, "cache-file")
cf := CacheFile(tempFile)
if err := cf.PutToken(new(Token)); err != nil {
t.Fatalf("PutToken: %v", err)
}
fi, err := os.Stat(tempFile)
if err != nil {
t.Fatalf("os.Stat: %v", err)
}
if fi.Mode()&0077 != 0 {
t.Errorf("Created cache file has mode %#o, want non-accessible to group+other", fi.Mode())
}
}
func TestTokenExpired(t *testing.T) {
tests := []struct {
token Token
expired bool
}{
{Token{AccessToken: "foo"}, false},
{Token{AccessToken: ""}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(-1 * time.Hour)}, true},
{Token{AccessToken: "foo", Expiry: time.Now().Add(1 * time.Hour)}, false},
}
for _, tt := range tests {
if got := tt.token.Expired(); got != tt.expired {
t.Errorf("token %+v Expired = %v; want %v", tt.token, got, !got)
}
}
}