Replace deprecated oauth2 package
This commit is contained in:
parent
3390499a8c
commit
ee9dd8abdb
25
Godeps/Godeps.json
generated
25
Godeps/Godeps.json
generated
@ -15,11 +15,6 @@
|
||||
"Comment": "null-75",
|
||||
"Rev": "bd8df7009305d6ada223ea3c95b94c0f38bfa119"
|
||||
},
|
||||
{
|
||||
"ImportPath": "code.google.com/p/goauth2/oauth",
|
||||
"Comment": "weekly-56",
|
||||
"Rev": "afe77d958c701557ec5dc56f6936fcc194d15520"
|
||||
},
|
||||
{
|
||||
"ImportPath": "code.google.com/p/gomock/gomock",
|
||||
"Rev": "5d0b59c9f277f888d2c70b00908236a30ab90f45"
|
||||
@ -151,6 +146,26 @@
|
||||
"ImportPath": "github.com/stretchr/testify/require",
|
||||
"Rev": "8ce79b9f0b77745113f82c17d0756771456ccbd3"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "7dbad50ab5b31073856416cdcfeb2796d682f844"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/internal",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jwt",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2/jws",
|
||||
"Rev": "ca8a464d23d55afd32571475db223e065ffd8a52"
|
||||
},
|
||||
{
|
||||
"ImportPath": "google.golang.org/cloud/internal",
|
||||
"Rev": "542bfb014d8e28df6e27be847dfdc40c510dab1a"
|
||||
|
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
100
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/example/oauthreq.go
generated
vendored
@ -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()
|
||||
}
|
@ -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"}}
|
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
20
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/example.pem
generated
vendored
@ -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-----
|
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
114
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/example/main.go
generated
vendored
@ -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)
|
||||
}
|
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
511
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt.go
generated
vendored
@ -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
|
||||
}
|
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
486
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/jwt/jwt_test.go
generated
vendored
@ -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()
|
||||
}
|
||||
}
|
476
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
476
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth.go
generated
vendored
@ -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
|
||||
}
|
236
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
236
Godeps/_workspace/src/code.google.com/p/goauth2/oauth/oauth_test.go
generated
vendored
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
447
Godeps/_workspace/src/golang.org/x/net/context/context.go
generated
vendored
Normal file
@ -0,0 +1,447 @@
|
||||
// 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 context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context // import "golang.org/x/net/context"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface {
|
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool)
|
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out <-chan Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{}
|
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error
|
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{}
|
||||
}
|
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled")
|
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded")
|
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int
|
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *emptyCtx) String() string {
|
||||
switch e {
|
||||
case background:
|
||||
return "context.Background"
|
||||
case todo:
|
||||
return "context.TODO"
|
||||
}
|
||||
return "unknown empty Context"
|
||||
}
|
||||
|
||||
var (
|
||||
background = new(emptyCtx)
|
||||
todo = new(emptyCtx)
|
||||
)
|
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
func Background() Context {
|
||||
return background
|
||||
}
|
||||
|
||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it's is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
func TODO() Context {
|
||||
return todo
|
||||
}
|
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func()
|
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
|
||||
c := newCancelCtx(parent)
|
||||
propagateCancel(parent, &c)
|
||||
return &c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) cancelCtx {
|
||||
return cancelCtx{
|
||||
Context: parent,
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) {
|
||||
if parent.Done() == nil {
|
||||
return // parent is never canceled
|
||||
}
|
||||
if p, ok := parentCancelCtx(parent); ok {
|
||||
p.mu.Lock()
|
||||
if p.err != nil {
|
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err)
|
||||
} else {
|
||||
if p.children == nil {
|
||||
p.children = make(map[canceler]bool)
|
||||
}
|
||||
p.children[child] = true
|
||||
}
|
||||
p.mu.Unlock()
|
||||
} else {
|
||||
go func() {
|
||||
select {
|
||||
case <-parent.Done():
|
||||
child.cancel(false, parent.Err())
|
||||
case <-child.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) {
|
||||
for {
|
||||
switch c := parent.(type) {
|
||||
case *cancelCtx:
|
||||
return c, true
|
||||
case *timerCtx:
|
||||
return &c.cancelCtx, true
|
||||
case *valueCtx:
|
||||
parent = c.Context
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) {
|
||||
p, ok := parentCancelCtx(parent)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
p.mu.Lock()
|
||||
if p.children != nil {
|
||||
delete(p.children, child)
|
||||
}
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface {
|
||||
cancel(removeFromParent bool, err error)
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct {
|
||||
Context
|
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex
|
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} {
|
||||
return c.done
|
||||
}
|
||||
|
||||
func (c *cancelCtx) Err() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *cancelCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithCancel", c.Context)
|
||||
}
|
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) {
|
||||
if err == nil {
|
||||
panic("context: internal error: missing cancel error")
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.err != nil {
|
||||
c.mu.Unlock()
|
||||
return // already canceled
|
||||
}
|
||||
c.err = err
|
||||
close(c.done)
|
||||
for child := range c.children {
|
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err)
|
||||
}
|
||||
c.children = nil
|
||||
c.mu.Unlock()
|
||||
|
||||
if removeFromParent {
|
||||
removeChild(c.Context, c)
|
||||
}
|
||||
}
|
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) {
|
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) {
|
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent)
|
||||
}
|
||||
c := &timerCtx{
|
||||
cancelCtx: newCancelCtx(parent),
|
||||
deadline: deadline,
|
||||
}
|
||||
propagateCancel(parent, c)
|
||||
d := deadline.Sub(time.Now())
|
||||
if d <= 0 {
|
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.err == nil {
|
||||
c.timer = time.AfterFunc(d, func() {
|
||||
c.cancel(true, DeadlineExceeded)
|
||||
})
|
||||
}
|
||||
return c, func() { c.cancel(true, Canceled) }
|
||||
}
|
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct {
|
||||
cancelCtx
|
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time
|
||||
}
|
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) {
|
||||
return c.deadline, true
|
||||
}
|
||||
|
||||
func (c *timerCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now()))
|
||||
}
|
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) {
|
||||
c.cancelCtx.cancel(false, err)
|
||||
if removeFromParent {
|
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c)
|
||||
}
|
||||
c.mu.Lock()
|
||||
if c.timer != nil {
|
||||
c.timer.Stop()
|
||||
c.timer = nil
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) {
|
||||
return WithDeadline(parent, time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context {
|
||||
return &valueCtx{parent, key, val}
|
||||
}
|
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct {
|
||||
Context
|
||||
key, val interface{}
|
||||
}
|
||||
|
||||
func (c *valueCtx) String() string {
|
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val)
|
||||
}
|
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} {
|
||||
if c.key == key {
|
||||
return c.val
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
575
Godeps/_workspace/src/golang.org/x/net/context/context_test.go
generated
vendored
Normal file
@ -0,0 +1,575 @@
|
||||
// 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 context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// otherContext is a Context that's not one of the types defined in context.go.
|
||||
// This lets us test code paths that differ based on the underlying type of the
|
||||
// Context.
|
||||
type otherContext struct {
|
||||
Context
|
||||
}
|
||||
|
||||
func TestBackground(t *testing.T) {
|
||||
c := Background()
|
||||
if c == nil {
|
||||
t.Fatalf("Background returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.Background"; got != want {
|
||||
t.Errorf("Background().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTODO(t *testing.T) {
|
||||
c := TODO()
|
||||
if c == nil {
|
||||
t.Fatalf("TODO returned nil")
|
||||
}
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if got, want := fmt.Sprint(c), "context.TODO"; got != want {
|
||||
t.Errorf("TODO().String() = %q want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithCancel(t *testing.T) {
|
||||
c1, cancel := WithCancel(Background())
|
||||
|
||||
if got, want := fmt.Sprint(c1), "context.Background.WithCancel"; got != want {
|
||||
t.Errorf("c1.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
o := otherContext{c1}
|
||||
c2, _ := WithCancel(o)
|
||||
contexts := []Context{c1, o, c2}
|
||||
|
||||
for i, c := range contexts {
|
||||
if d := c.Done(); d == nil {
|
||||
t.Errorf("c[%d].Done() == %v want non-nil", i, d)
|
||||
}
|
||||
if e := c.Err(); e != nil {
|
||||
t.Errorf("c[%d].Err() == %v want nil", i, e)
|
||||
}
|
||||
|
||||
select {
|
||||
case x := <-c.Done():
|
||||
t.Errorf("<-c.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
cancel()
|
||||
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||
|
||||
for i, c := range contexts {
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c[%d].Done() blocked, but shouldn't have", i)
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c[%d].Err() == %v want %v", i, e, Canceled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParentFinishesChild(t *testing.T) {
|
||||
// Context tree:
|
||||
// parent -> cancelChild
|
||||
// parent -> valueChild -> timerChild
|
||||
parent, cancel := WithCancel(Background())
|
||||
cancelChild, stop := WithCancel(parent)
|
||||
defer stop()
|
||||
valueChild := WithValue(parent, "key", "value")
|
||||
timerChild, stop := WithTimeout(valueChild, 10000*time.Hour)
|
||||
defer stop()
|
||||
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-cancelChild.Done():
|
||||
t.Errorf("<-cancelChild.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-timerChild.Done():
|
||||
t.Errorf("<-timerChild.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-valueChild.Done():
|
||||
t.Errorf("<-valueChild.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
|
||||
// The parent's children should contain the two cancelable children.
|
||||
pc := parent.(*cancelCtx)
|
||||
cc := cancelChild.(*cancelCtx)
|
||||
tc := timerChild.(*timerCtx)
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 2 || !pc.children[cc] || !pc.children[tc] {
|
||||
t.Errorf("bad linkage: pc.children = %v, want %v and %v",
|
||||
pc.children, cc, tc)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
|
||||
if p, ok := parentCancelCtx(cc.Context); !ok || p != pc {
|
||||
t.Errorf("bad linkage: parentCancelCtx(cancelChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||
}
|
||||
if p, ok := parentCancelCtx(tc.Context); !ok || p != pc {
|
||||
t.Errorf("bad linkage: parentCancelCtx(timerChild.Context) = %v, %v want %v, true", p, ok, pc)
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 0 {
|
||||
t.Errorf("pc.cancel didn't clear pc.children = %v", pc.children)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
|
||||
// parent and children should all be finished.
|
||||
check := func(ctx Context, name string) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
t.Errorf("<-%s.Done() blocked, but shouldn't have", name)
|
||||
}
|
||||
if e := ctx.Err(); e != Canceled {
|
||||
t.Errorf("%s.Err() == %v want %v", name, e, Canceled)
|
||||
}
|
||||
}
|
||||
check(parent, "parent")
|
||||
check(cancelChild, "cancelChild")
|
||||
check(valueChild, "valueChild")
|
||||
check(timerChild, "timerChild")
|
||||
|
||||
// WithCancel should return a canceled context on a canceled parent.
|
||||
precanceledChild := WithValue(parent, "key", "value")
|
||||
select {
|
||||
case <-precanceledChild.Done():
|
||||
default:
|
||||
t.Errorf("<-precanceledChild.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := precanceledChild.Err(); e != Canceled {
|
||||
t.Errorf("precanceledChild.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildFinishesFirst(t *testing.T) {
|
||||
cancelable, stop := WithCancel(Background())
|
||||
defer stop()
|
||||
for _, parent := range []Context{Background(), cancelable} {
|
||||
child, cancel := WithCancel(parent)
|
||||
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
case x := <-child.Done():
|
||||
t.Errorf("<-child.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
|
||||
cc := child.(*cancelCtx)
|
||||
pc, pcok := parent.(*cancelCtx) // pcok == false when parent == Background()
|
||||
if p, ok := parentCancelCtx(cc.Context); ok != pcok || (ok && pc != p) {
|
||||
t.Errorf("bad linkage: parentCancelCtx(cc.Context) = %v, %v want %v, %v", p, ok, pc, pcok)
|
||||
}
|
||||
|
||||
if pcok {
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 1 || !pc.children[cc] {
|
||||
t.Errorf("bad linkage: pc.children = %v, cc = %v", pc.children, cc)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
}
|
||||
|
||||
cancel()
|
||||
|
||||
if pcok {
|
||||
pc.mu.Lock()
|
||||
if len(pc.children) != 0 {
|
||||
t.Errorf("child's cancel didn't remove self from pc.children = %v", pc.children)
|
||||
}
|
||||
pc.mu.Unlock()
|
||||
}
|
||||
|
||||
// child should be finished.
|
||||
select {
|
||||
case <-child.Done():
|
||||
default:
|
||||
t.Errorf("<-child.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := child.Err(); e != Canceled {
|
||||
t.Errorf("child.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
|
||||
// parent should not be finished.
|
||||
select {
|
||||
case x := <-parent.Done():
|
||||
t.Errorf("<-parent.Done() == %v want nothing (it should block)", x)
|
||||
default:
|
||||
}
|
||||
if e := parent.Err(); e != nil {
|
||||
t.Errorf("parent.Err() == %v want nil", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testDeadline(c Context, wait time.Duration, t *testing.T) {
|
||||
select {
|
||||
case <-time.After(wait):
|
||||
t.Fatalf("context should have timed out")
|
||||
case <-c.Done():
|
||||
}
|
||||
if e := c.Err(); e != DeadlineExceeded {
|
||||
t.Errorf("c.Err() == %v want %v", e, DeadlineExceeded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeadline(t *testing.T) {
|
||||
c, _ := WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
o := otherContext{c}
|
||||
testDeadline(o, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithDeadline(Background(), time.Now().Add(100*time.Millisecond))
|
||||
o = otherContext{c}
|
||||
c, _ = WithDeadline(o, time.Now().Add(300*time.Millisecond))
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
c, _ := WithTimeout(Background(), 100*time.Millisecond)
|
||||
if got, prefix := fmt.Sprint(c), "context.Background.WithDeadline("; !strings.HasPrefix(got, prefix) {
|
||||
t.Errorf("c.String() = %q want prefix %q", got, prefix)
|
||||
}
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||
o := otherContext{c}
|
||||
testDeadline(o, 200*time.Millisecond, t)
|
||||
|
||||
c, _ = WithTimeout(Background(), 100*time.Millisecond)
|
||||
o = otherContext{c}
|
||||
c, _ = WithTimeout(o, 300*time.Millisecond)
|
||||
testDeadline(c, 200*time.Millisecond, t)
|
||||
}
|
||||
|
||||
func TestCanceledTimeout(t *testing.T) {
|
||||
c, _ := WithTimeout(Background(), 200*time.Millisecond)
|
||||
o := otherContext{c}
|
||||
c, cancel := WithTimeout(o, 400*time.Millisecond)
|
||||
cancel()
|
||||
time.Sleep(100 * time.Millisecond) // let cancelation propagate
|
||||
select {
|
||||
case <-c.Done():
|
||||
default:
|
||||
t.Errorf("<-c.Done() blocked, but shouldn't have")
|
||||
}
|
||||
if e := c.Err(); e != Canceled {
|
||||
t.Errorf("c.Err() == %v want %v", e, Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
type key1 int
|
||||
type key2 int
|
||||
|
||||
var k1 = key1(1)
|
||||
var k2 = key2(1) // same int as k1, different type
|
||||
var k3 = key2(3) // same type as k2, different int
|
||||
|
||||
func TestValues(t *testing.T) {
|
||||
check := func(c Context, nm, v1, v2, v3 string) {
|
||||
if v, ok := c.Value(k1).(string); ok == (len(v1) == 0) || v != v1 {
|
||||
t.Errorf(`%s.Value(k1).(string) = %q, %t want %q, %t`, nm, v, ok, v1, len(v1) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k2).(string); ok == (len(v2) == 0) || v != v2 {
|
||||
t.Errorf(`%s.Value(k2).(string) = %q, %t want %q, %t`, nm, v, ok, v2, len(v2) != 0)
|
||||
}
|
||||
if v, ok := c.Value(k3).(string); ok == (len(v3) == 0) || v != v3 {
|
||||
t.Errorf(`%s.Value(k3).(string) = %q, %t want %q, %t`, nm, v, ok, v3, len(v3) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
c0 := Background()
|
||||
check(c0, "c0", "", "", "")
|
||||
|
||||
c1 := WithValue(Background(), k1, "c1k1")
|
||||
check(c1, "c1", "c1k1", "", "")
|
||||
|
||||
if got, want := fmt.Sprint(c1), `context.Background.WithValue(1, "c1k1")`; got != want {
|
||||
t.Errorf("c.String() = %q want %q", got, want)
|
||||
}
|
||||
|
||||
c2 := WithValue(c1, k2, "c2k2")
|
||||
check(c2, "c2", "c1k1", "c2k2", "")
|
||||
|
||||
c3 := WithValue(c2, k3, "c3k3")
|
||||
check(c3, "c2", "c1k1", "c2k2", "c3k3")
|
||||
|
||||
c4 := WithValue(c3, k1, nil)
|
||||
check(c4, "c4", "", "c2k2", "c3k3")
|
||||
|
||||
o0 := otherContext{Background()}
|
||||
check(o0, "o0", "", "", "")
|
||||
|
||||
o1 := otherContext{WithValue(Background(), k1, "c1k1")}
|
||||
check(o1, "o1", "c1k1", "", "")
|
||||
|
||||
o2 := WithValue(o1, k2, "o2k2")
|
||||
check(o2, "o2", "c1k1", "o2k2", "")
|
||||
|
||||
o3 := otherContext{c4}
|
||||
check(o3, "o3", "", "c2k2", "c3k3")
|
||||
|
||||
o4 := WithValue(o3, k3, nil)
|
||||
check(o4, "o4", "", "c2k2", "")
|
||||
}
|
||||
|
||||
func TestAllocs(t *testing.T) {
|
||||
bg := Background()
|
||||
for _, test := range []struct {
|
||||
desc string
|
||||
f func()
|
||||
limit float64
|
||||
gccgoLimit float64
|
||||
}{
|
||||
{
|
||||
desc: "Background()",
|
||||
f: func() { Background() },
|
||||
limit: 0,
|
||||
gccgoLimit: 0,
|
||||
},
|
||||
{
|
||||
desc: fmt.Sprintf("WithValue(bg, %v, nil)", k1),
|
||||
f: func() {
|
||||
c := WithValue(bg, k1, nil)
|
||||
c.Value(k1)
|
||||
},
|
||||
limit: 3,
|
||||
gccgoLimit: 3,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 15*time.Millisecond)",
|
||||
f: func() {
|
||||
c, _ := WithTimeout(bg, 15*time.Millisecond)
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 13,
|
||||
},
|
||||
{
|
||||
desc: "WithCancel(bg)",
|
||||
f: func() {
|
||||
c, cancel := WithCancel(bg)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 5,
|
||||
gccgoLimit: 8,
|
||||
},
|
||||
{
|
||||
desc: "WithTimeout(bg, 100*time.Millisecond)",
|
||||
f: func() {
|
||||
c, cancel := WithTimeout(bg, 100*time.Millisecond)
|
||||
cancel()
|
||||
<-c.Done()
|
||||
},
|
||||
limit: 8,
|
||||
gccgoLimit: 25,
|
||||
},
|
||||
} {
|
||||
limit := test.limit
|
||||
if runtime.Compiler == "gccgo" {
|
||||
// gccgo does not yet do escape analysis.
|
||||
// TOOD(iant): Remove this when gccgo does do escape analysis.
|
||||
limit = test.gccgoLimit
|
||||
}
|
||||
if n := testing.AllocsPerRun(100, test.f); n > limit {
|
||||
t.Errorf("%s allocs = %f want %d", test.desc, n, int(limit))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSimultaneousCancels(t *testing.T) {
|
||||
root, cancel := WithCancel(Background())
|
||||
m := map[Context]CancelFunc{root: cancel}
|
||||
q := []Context{root}
|
||||
// Create a tree of contexts.
|
||||
for len(q) != 0 && len(m) < 100 {
|
||||
parent := q[0]
|
||||
q = q[1:]
|
||||
for i := 0; i < 4; i++ {
|
||||
ctx, cancel := WithCancel(parent)
|
||||
m[ctx] = cancel
|
||||
q = append(q, ctx)
|
||||
}
|
||||
}
|
||||
// Start all the cancels in a random order.
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(m))
|
||||
for _, cancel := range m {
|
||||
go func(cancel CancelFunc) {
|
||||
cancel()
|
||||
wg.Done()
|
||||
}(cancel)
|
||||
}
|
||||
// Wait on all the contexts in a random order.
|
||||
for ctx := range m {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for <-ctx.Done(); stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
// Wait for all the cancel functions to return.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for cancel functions; stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestInterlockedCancels(t *testing.T) {
|
||||
parent, cancelParent := WithCancel(Background())
|
||||
child, cancelChild := WithCancel(parent)
|
||||
go func() {
|
||||
parent.Done()
|
||||
cancelChild()
|
||||
}()
|
||||
cancelParent()
|
||||
select {
|
||||
case <-child.Done():
|
||||
case <-time.After(1 * time.Second):
|
||||
buf := make([]byte, 10<<10)
|
||||
n := runtime.Stack(buf, true)
|
||||
t.Fatalf("timed out waiting for child.Done(); stacks:\n%s", buf[:n])
|
||||
}
|
||||
}
|
||||
|
||||
func TestLayersCancel(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), false)
|
||||
}
|
||||
|
||||
func TestLayersTimeout(t *testing.T) {
|
||||
testLayers(t, time.Now().UnixNano(), true)
|
||||
}
|
||||
|
||||
func testLayers(t *testing.T, seed int64, testTimeout bool) {
|
||||
rand.Seed(seed)
|
||||
errorf := func(format string, a ...interface{}) {
|
||||
t.Errorf(fmt.Sprintf("seed=%d: %s", seed, format), a...)
|
||||
}
|
||||
const (
|
||||
timeout = 200 * time.Millisecond
|
||||
minLayers = 30
|
||||
)
|
||||
type value int
|
||||
var (
|
||||
vals []*value
|
||||
cancels []CancelFunc
|
||||
numTimers int
|
||||
ctx = Background()
|
||||
)
|
||||
for i := 0; i < minLayers || numTimers == 0 || len(cancels) == 0 || len(vals) == 0; i++ {
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
v := new(value)
|
||||
ctx = WithValue(ctx, v, v)
|
||||
vals = append(vals, v)
|
||||
case 1:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithCancel(ctx)
|
||||
cancels = append(cancels, cancel)
|
||||
case 2:
|
||||
var cancel CancelFunc
|
||||
ctx, cancel = WithTimeout(ctx, timeout)
|
||||
cancels = append(cancels, cancel)
|
||||
numTimers++
|
||||
}
|
||||
}
|
||||
checkValues := func(when string) {
|
||||
for _, key := range vals {
|
||||
if val := ctx.Value(key).(*value); key != val {
|
||||
errorf("%s: ctx.Value(%p) = %p want %p", when, key, val, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
errorf("ctx should not be canceled yet")
|
||||
default:
|
||||
}
|
||||
if s, prefix := fmt.Sprint(ctx), "context.Background."; !strings.HasPrefix(s, prefix) {
|
||||
t.Errorf("ctx.String() = %q want prefix %q", s, prefix)
|
||||
}
|
||||
t.Log(ctx)
|
||||
checkValues("before cancel")
|
||||
if testTimeout {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(timeout + timeout/10):
|
||||
errorf("ctx should have timed out")
|
||||
}
|
||||
checkValues("after timeout")
|
||||
} else {
|
||||
cancel := cancels[rand.Intn(len(cancels))]
|
||||
cancel()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
default:
|
||||
errorf("ctx should be canceled")
|
||||
}
|
||||
checkValues("after cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelRemoves(t *testing.T) {
|
||||
checkChildren := func(when string, ctx Context, want int) {
|
||||
if got := len(ctx.(*cancelCtx).children); got != want {
|
||||
t.Errorf("%s: context has %d children, want %d", when, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
ctx, _ := WithCancel(Background())
|
||||
checkChildren("after creation", ctx, 0)
|
||||
_, cancel := WithCancel(ctx)
|
||||
checkChildren("with WithCancel child ", ctx, 1)
|
||||
cancel()
|
||||
checkChildren("after cancelling WithCancel child", ctx, 0)
|
||||
|
||||
ctx, _ = WithCancel(Background())
|
||||
checkChildren("after creation", ctx, 0)
|
||||
_, cancel = WithTimeout(ctx, 60*time.Minute)
|
||||
checkChildren("with WithTimeout child ", ctx, 1)
|
||||
cancel()
|
||||
checkChildren("after cancelling WithTimeout child", ctx, 0)
|
||||
}
|
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
26
Godeps/_workspace/src/golang.org/x/net/context/withtimeout_test.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
// 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 context_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func ExampleWithTimeout() {
|
||||
// Pass a context with a timeout to tell a blocking function that it
|
||||
// should abandon its work after the timeout elapses.
|
||||
ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
select {
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
fmt.Println("overslept")
|
||||
case <-ctx.Done():
|
||||
fmt.Println(ctx.Err()) // prints "context deadline exceeded"
|
||||
}
|
||||
// Output:
|
||||
// context deadline exceeded
|
||||
}
|
3
Godeps/_workspace/src/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
3
Godeps/_workspace/src/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code refers to The Go Authors for copyright purposes.
|
||||
# The master list of authors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/AUTHORS.
|
31
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
31
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
# Contributing to Go
|
||||
|
||||
Go is an open source project.
|
||||
|
||||
It is the work of hundreds of contributors. We appreciate your help!
|
||||
|
||||
|
||||
## Filing issues
|
||||
|
||||
When [filing an issue](https://golang.org/issue/new), make sure to answer these five questions:
|
||||
|
||||
1. What version of Go are you using (`go version`)?
|
||||
2. What operating system and processor architecture are you using?
|
||||
3. What did you do?
|
||||
4. What did you expect to see?
|
||||
5. What did you see instead?
|
||||
|
||||
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||
|
||||
## Contributing code
|
||||
|
||||
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||
before sending patches.
|
||||
|
||||
**We do not accept GitHub pull requests**
|
||||
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||
|
||||
Unless otherwise noted, the Go source files are distributed under
|
||||
the BSD-style license found in the LICENSE file.
|
||||
|
3
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
3
Godeps/_workspace/src/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# This source code was written by the Go contributors.
|
||||
# The master list of contributors is in the main Go distribution,
|
||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
Godeps/_workspace/src/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
27
Godeps/_workspace/src/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
64
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
Normal file
64
Godeps/_workspace/src/golang.org/x/oauth2/README.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
# OAuth2 for Go
|
||||
|
||||
[](https://travis-ci.org/golang/oauth2)
|
||||
|
||||
oauth2 package contains a client implementation for OAuth 2.0 spec.
|
||||
|
||||
## Installation
|
||||
|
||||
~~~~
|
||||
go get golang.org/x/oauth2
|
||||
~~~~
|
||||
|
||||
See godoc for further documentation and examples.
|
||||
|
||||
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||
|
||||
|
||||
## App Engine
|
||||
|
||||
In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor
|
||||
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||
the `golang.org/x/net/context` package
|
||||
|
||||
This means its no longer possible to use the "Classic App Engine"
|
||||
`appengine.Context` type with the `oauth2` package. (You're using
|
||||
Classic App Engine if you import the package `"appengine"`.)
|
||||
|
||||
To work around this, you may use the new `"google.golang.org/appengine"`
|
||||
package. This package has almost the same API as the `"appengine"` package,
|
||||
but it can be fetched with `go get` and used on "Managed VMs" and well as
|
||||
Classic App Engine.
|
||||
|
||||
See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app)
|
||||
for information on updating your app.
|
||||
|
||||
If you don't want to update your entire app to use the new App Engine packages,
|
||||
you may use both sets of packages in parallel, using only the new packages
|
||||
with the `oauth2` package.
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
newappengine "google.golang.org/appengine"
|
||||
newurlftech "google.golang.org/urlfetch"
|
||||
|
||||
"appengine"
|
||||
)
|
||||
|
||||
func handler(w http.ResponseWriter, r *http.Request) {
|
||||
var c appengine.Context = appengine.NewContext(r)
|
||||
c.Infof("Logging a message with the old package")
|
||||
|
||||
var ctx context.Context = newappengine.NewContext(r)
|
||||
client := &http.Client{
|
||||
Transport: &oauth2.Transport{
|
||||
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||
Base: &newurlfetch.Transport{Context: ctx},
|
||||
},
|
||||
}
|
||||
client.Get("...")
|
||||
}
|
||||
|
24
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
24
Godeps/_workspace/src/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine appenginevm
|
||||
|
||||
// App Engine hooks.
|
||||
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/appengine/urlfetch"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registerContextClientFunc(contextClientAppEngine)
|
||||
}
|
||||
|
||||
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
|
||||
return urlfetch.Client(ctx), nil
|
||||
}
|
45
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
45
Godeps/_workspace/src/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func ExampleConfig() {
|
||||
conf := &oauth2.Config{
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||
Scopes: []string{"SCOPE1", "SCOPE2"},
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: "https://provider.com/o/oauth2/auth",
|
||||
TokenURL: "https://provider.com/o/oauth2/token",
|
||||
},
|
||||
}
|
||||
|
||||
// Redirect user to consent page to ask for permission
|
||||
// for the scopes specified above.
|
||||
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
||||
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||
|
||||
// Use the authorization code that is pushed to the redirect URL.
|
||||
// NewTransportWithCode will do the handshake to retrieve
|
||||
// an access token and initiate a Transport that is
|
||||
// authorized and authenticated by the retrieved token.
|
||||
var code string
|
||||
if _, err := fmt.Scan(&code); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tok, err := conf.Exchange(oauth2.NoContext, code)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
client := conf.Client(oauth2.NoContext, tok)
|
||||
client.Get("...")
|
||||
}
|
69
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
69
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
// Copyright 2014 The oauth2 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 internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseKey converts the binary contents of a private key file
|
||||
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||
// PEM container or not. If so, it extracts the the private key
|
||||
// from PEM container before conversion. It only supports PEM
|
||||
// containers with no passphrase.
|
||||
func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(key)
|
||||
if block != nil {
|
||||
key = block.Bytes
|
||||
}
|
||||
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err)
|
||||
}
|
||||
}
|
||||
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, errors.New("private key is invalid")
|
||||
}
|
||||
return parsed, nil
|
||||
}
|
||||
|
||||
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
|
||||
result := map[string]map[string]string{
|
||||
"": map[string]string{}, // root section
|
||||
}
|
||||
scanner := bufio.NewScanner(ini)
|
||||
currentSection := ""
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if strings.HasPrefix(line, ";") {
|
||||
// comment.
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||
currentSection = strings.TrimSpace(line[1 : len(line)-1])
|
||||
result[currentSection] = map[string]string{}
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 && parts[0] != "" {
|
||||
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error scanning ini: %v", err)
|
||||
}
|
||||
return result, nil
|
||||
}
|
62
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
62
Godeps/_workspace/src/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2014 The oauth2 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 internal contains support packages for oauth2 package.
|
||||
package internal
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseINI(t *testing.T) {
|
||||
tests := []struct {
|
||||
ini string
|
||||
want map[string]map[string]string
|
||||
}{
|
||||
{
|
||||
`root = toor
|
||||
[foo]
|
||||
bar = hop
|
||||
ini = nin
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{"root": "toor"},
|
||||
"foo": map[string]string{"bar": "hop", "ini": "nin"},
|
||||
},
|
||||
},
|
||||
{
|
||||
`[empty]
|
||||
[section]
|
||||
empty=
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
"empty": map[string]string{},
|
||||
"section": map[string]string{"empty": ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
`ignore
|
||||
[invalid
|
||||
=stuff
|
||||
;comment=true
|
||||
`,
|
||||
map[string]map[string]string{
|
||||
"": map[string]string{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
result, err := ParseINI(strings.NewReader(tt.ini))
|
||||
if err != nil {
|
||||
t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(result, tt.want) {
|
||||
t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
160
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
160
Godeps/_workspace/src/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
@ -0,0 +1,160 @@
|
||||
// Copyright 2014 The oauth2 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 jws provides encoding and decoding utilities for
|
||||
// signed JWS messages.
|
||||
package jws // import "golang.org/x/oauth2/jws"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClaimSet contains information about the JWT signature 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.
|
||||
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).
|
||||
Exp int64 `json:"exp"` // the expiration time of the assertion
|
||||
Iat int64 `json:"iat"` // the time the assertion was issued.
|
||||
Typ string `json:"typ,omitempty"` // token type (Optional).
|
||||
|
||||
// Email for which the application is requesting delegated access (Optional).
|
||||
Sub string `json:"sub,omitempty"`
|
||||
|
||||
// The old name of Sub. Client keeps setting Prn to be
|
||||
// complaint with legacy OAuth 2.0 providers. (Optional)
|
||||
Prn string `json:"prn,omitempty"`
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (c *ClaimSet) encode() (string, error) {
|
||||
if c.exp.IsZero() || c.iat.IsZero() {
|
||||
// Reverting time back for machines whose time is not perfectly in sync.
|
||||
// If client machine's time is in the future according
|
||||
// to Google servers, an access token will not be issued.
|
||||
now := time.Now().Add(-10 * time.Second)
|
||||
c.iat = now
|
||||
c.exp = now.Add(time.Hour)
|
||||
}
|
||||
|
||||
c.Exp = c.exp.Unix()
|
||||
c.Iat = c.iat.Unix()
|
||||
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(c.PrivateClaims) == 0 {
|
||||
return base64Encode(b), nil
|
||||
}
|
||||
|
||||
// Marshal private claim set and then append it to b.
|
||||
prv, err := json.Marshal(c.PrivateClaims)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims)
|
||||
}
|
||||
|
||||
// Concatenate public and private claim JSON objects.
|
||||
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||
return "", fmt.Errorf("jws: invalid JSON %s", b)
|
||||
}
|
||||
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||
return "", fmt.Errorf("jws: 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), nil
|
||||
}
|
||||
|
||||
// Header represents the header for the signed JWS payloads.
|
||||
type Header struct {
|
||||
// The algorithm used for signature.
|
||||
Algorithm string `json:"alg"`
|
||||
|
||||
// Represents the token type.
|
||||
Typ string `json:"typ"`
|
||||
}
|
||||
|
||||
func (h *Header) encode() (string, error) {
|
||||
b, err := json.Marshal(h)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64Encode(b), nil
|
||||
}
|
||||
|
||||
// Decode decodes a claim set from a JWS payload.
|
||||
func Decode(payload string) (*ClaimSet, error) {
|
||||
// decode returned id token to get expiry
|
||||
s := strings.Split(payload, ".")
|
||||
if len(s) < 2 {
|
||||
// TODO(jbd): Provide more context about the error.
|
||||
return nil, errors.New("jws: invalid token received")
|
||||
}
|
||||
decoded, err := base64Decode(s[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &ClaimSet{}
|
||||
err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c)
|
||||
return c, err
|
||||
}
|
||||
|
||||
// Encode encodes a signed JWS with provided header and claim set.
|
||||
func Encode(header *Header, c *ClaimSet, signature *rsa.PrivateKey) (string, error) {
|
||||
head, err := header.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cs, err := c.encode()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ss := fmt.Sprintf("%s.%s", head, cs)
|
||||
h := sha256.New()
|
||||
h.Write([]byte(ss))
|
||||
b, err := rsa.SignPKCS1v15(rand.Reader, signature, crypto.SHA256, h.Sum(nil))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sig := base64Encode(b)
|
||||
return fmt.Sprintf("%s.%s", ss, sig), nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
31
Godeps/_workspace/src/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2014 The oauth2 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_test
|
||||
|
||||
import (
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/jwt"
|
||||
)
|
||||
|
||||
func ExampleJWTConfig() {
|
||||
conf := &jwt.Config{
|
||||
Email: "xxx@developer.com",
|
||||
// The contents of your RSA private key or your PEM file
|
||||
// that contains a private key.
|
||||
// If you have a p12 file instead, you
|
||||
// can use `openssl` to export the private key into a pem file.
|
||||
//
|
||||
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||
//
|
||||
// It only supports PEM containers with no passphrase.
|
||||
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||
Subject: "user@example.com",
|
||||
TokenURL: "https://provider.com/o/oauth2/token",
|
||||
}
|
||||
// Initiate an http.Client, the following GET request will be
|
||||
// authorized and authenticated on the behalf of user@example.com.
|
||||
client := conf.Client(oauth2.NoContext)
|
||||
client.Get("...")
|
||||
}
|
147
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
147
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
// Copyright 2014 The oauth2 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 (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
payload, err := jws.Encode(defaultHeader, 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, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, 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
|
||||
}
|
134
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
134
Godeps/_workspace/src/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2014 The oauth2 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
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE
|
||||
DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY
|
||||
fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK
|
||||
1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr
|
||||
k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9
|
||||
/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt
|
||||
3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn
|
||||
2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3
|
||||
nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK
|
||||
6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf
|
||||
5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e
|
||||
DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1
|
||||
M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g
|
||||
z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y
|
||||
1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK
|
||||
J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U
|
||||
f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx
|
||||
QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA
|
||||
cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr
|
||||
Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw
|
||||
5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg
|
||||
KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84
|
||||
OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd
|
||||
mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ
|
||||
5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg==
|
||||
-----END RSA PRIVATE KEY-----`)
|
||||
|
||||
func TestJWTFetch_JSONResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{
|
||||
"access_token": "90d64460d14870c08c81352a05dedd3465940a7c",
|
||||
"scope": "user",
|
||||
"token_type": "bearer",
|
||||
"expires_in": 3600
|
||||
}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Errorf("Token invalid")
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v", tok.AccessToken)
|
||||
}
|
||||
if tok.TokenType != "bearer" {
|
||||
t.Errorf("Unexpected token type, %#v", tok.TokenType)
|
||||
}
|
||||
if tok.Expiry.IsZero() {
|
||||
t.Errorf("Unexpected token expiry, %#v", tok.Expiry)
|
||||
}
|
||||
scope := tok.Extra("scope")
|
||||
if scope != "user" {
|
||||
t.Errorf("Unexpected value for scope: %v", scope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTFetch_BadResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok == nil {
|
||||
t.Fatalf("token is nil")
|
||||
}
|
||||
if tok.Valid() {
|
||||
t.Errorf("token is valid. want invalid.")
|
||||
}
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken)
|
||||
}
|
||||
if want := "bearer"; tok.TokenType != want {
|
||||
t.Errorf("TokenType = %q; want %q", tok.TokenType, want)
|
||||
}
|
||||
scope := tok.Extra("scope")
|
||||
if want := "user"; scope != want {
|
||||
t.Errorf("token scope = %q; want %q", scope, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestJWTFetch_BadResponseType(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := &Config{
|
||||
Email: "aaa@xxx.com",
|
||||
PrivateKey: dummyPrivateKey,
|
||||
TokenURL: ts.URL,
|
||||
}
|
||||
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||
if err == nil {
|
||||
t.Error("got a token; expected error")
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
}
|
||||
}
|
||||
}
|
517
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
517
Godeps/_workspace/src/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
@ -0,0 +1,517 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2 provides support for making
|
||||
// OAuth2 authorized and authenticated HTTP requests.
|
||||
// It can additionally grant authorization with Bearer JWT.
|
||||
package oauth2 // import "golang.org/x/oauth2"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NoContext is the default context you should supply if not using
|
||||
// your own context.Context (see https://golang.org/x/net/context).
|
||||
var NoContext = context.TODO()
|
||||
|
||||
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||
// client application information and the server's endpoint URLs.
|
||||
type Config struct {
|
||||
// ClientID is the application's ID.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the application's secret.
|
||||
ClientSecret string
|
||||
|
||||
// Endpoint contains the resource server's token endpoint
|
||||
// URLs. These are constants specific to each server and are
|
||||
// often available via site-specific packages, such as
|
||||
// google.Endpoint or github.Endpoint.
|
||||
Endpoint Endpoint
|
||||
|
||||
// RedirectURL is the URL to redirect users going through
|
||||
// the OAuth flow, after the resource owner's URLs.
|
||||
RedirectURL string
|
||||
|
||||
// Scope specifies optional requested permissions.
|
||||
Scopes []string
|
||||
}
|
||||
|
||||
// A TokenSource is anything that can return a token.
|
||||
type TokenSource interface {
|
||||
// Token returns a token or an error.
|
||||
// Token must be safe for concurrent use by multiple goroutines.
|
||||
// The returned Token must not be modified.
|
||||
Token() (*Token, error)
|
||||
}
|
||||
|
||||
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
||||
// endpoint URLs.
|
||||
type Endpoint struct {
|
||||
AuthURL string
|
||||
TokenURL string
|
||||
}
|
||||
|
||||
var (
|
||||
// AccessTypeOnline and AccessTypeOffline are options passed
|
||||
// to the Options.AuthCodeURL method. They modify the
|
||||
// "access_type" field that gets sent in the URL returned by
|
||||
// AuthCodeURL.
|
||||
//
|
||||
// Online is the default if neither is specified. 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.
|
||||
AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"}
|
||||
AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"}
|
||||
|
||||
// ApprovalForce forces the users to view the consent dialog
|
||||
// and confirm the permissions request at the URL returned
|
||||
// from AuthCodeURL, even if they've already done so.
|
||||
ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"}
|
||||
)
|
||||
|
||||
type setParam struct{ k, v string }
|
||||
|
||||
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
||||
|
||||
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||
type AuthCodeOption interface {
|
||||
setValue(url.Values)
|
||||
}
|
||||
|
||||
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
||||
// that asks for permissions for the required scopes explicitly.
|
||||
//
|
||||
// State is a token to protect the user from CSRF attacks. You must
|
||||
// always provide a non-zero string and validate that it matches the
|
||||
// the state query parameter on your redirect callback.
|
||||
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
||||
//
|
||||
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
||||
// as ApprovalForce.
|
||||
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(c.Endpoint.AuthURL)
|
||||
v := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {c.ClientID},
|
||||
"redirect_uri": condVal(c.RedirectURL),
|
||||
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||
"state": condVal(state),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.setValue(v)
|
||||
}
|
||||
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
||||
buf.WriteByte('&')
|
||||
} else {
|
||||
buf.WriteByte('?')
|
||||
}
|
||||
buf.WriteString(v.Encode())
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// PasswordCredentialsToken converts a resource owner username and password
|
||||
// pair into a token.
|
||||
//
|
||||
// Per the RFC, this grant type should only be used "when there is a high
|
||||
// degree of trust between the resource owner and the client (e.g., the client
|
||||
// is part of the device operating system or a highly privileged application),
|
||||
// and when other authorization grant types are not available."
|
||||
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If nil, http.DefaultClient is used.
|
||||
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"password"},
|
||||
"username": {username},
|
||||
"password": {password},
|
||||
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
|
||||
// Exchange converts an authorization code into a token.
|
||||
//
|
||||
// It is used after a resource provider redirects the user back
|
||||
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||
//
|
||||
// The HTTP client to use is derived from the context.
|
||||
// If a client is not provided via the context, http.DefaultClient is used.
|
||||
//
|
||||
// The code will be in the *http.Request.FormValue("code"). Before
|
||||
// calling Exchange, be sure to validate FormValue("state").
|
||||
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
|
||||
return retrieveToken(ctx, c, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"code": {code},
|
||||
"redirect_uri": condVal(c.RedirectURL),
|
||||
"scope": condVal(strings.Join(c.Scopes, " ")),
|
||||
})
|
||||
}
|
||||
|
||||
// contextClientFunc is a func which tries to return an *http.Client
|
||||
// given a Context value. If it returns an error, the search stops
|
||||
// with that error. If it returns (nil, nil), the search continues
|
||||
// down the list of registered funcs.
|
||||
type contextClientFunc func(context.Context) (*http.Client, error)
|
||||
|
||||
var contextClientFuncs []contextClientFunc
|
||||
|
||||
func registerContextClientFunc(fn contextClientFunc) {
|
||||
contextClientFuncs = append(contextClientFuncs, fn)
|
||||
}
|
||||
|
||||
func contextClient(ctx context.Context) (*http.Client, error) {
|
||||
for _, fn := range contextClientFuncs {
|
||||
c, err := fn(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if c != nil {
|
||||
return c, nil
|
||||
}
|
||||
}
|
||||
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
|
||||
return hc, nil
|
||||
}
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
func contextTransport(ctx context.Context) http.RoundTripper {
|
||||
hc, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
// This is a rare error case (somebody using nil on App Engine),
|
||||
// so I'd rather not everybody do an error check on this Client
|
||||
// method. They can get the error that they're doing it wrong
|
||||
// later, at client.Get/PostForm time.
|
||||
return errorTransport{err}
|
||||
}
|
||||
return hc.Transport
|
||||
}
|
||||
|
||||
// Client returns an HTTP client using the provided token.
|
||||
// The token will auto-refresh as necessary. The underlying
|
||||
// HTTP transport will be obtained using the provided context.
|
||||
// The returned client and its Transport should not be modified.
|
||||
func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
|
||||
return NewClient(ctx, c.TokenSource(ctx, t))
|
||||
}
|
||||
|
||||
// TokenSource returns a TokenSource that returns t until t expires,
|
||||
// automatically refreshing it as necessary using the provided context.
|
||||
//
|
||||
// Most users will use Config.Client instead.
|
||||
func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
|
||||
tkr := &tokenRefresher{
|
||||
ctx: ctx,
|
||||
conf: c,
|
||||
}
|
||||
if t != nil {
|
||||
tkr.refreshToken = t.RefreshToken
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: tkr,
|
||||
}
|
||||
}
|
||||
|
||||
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
||||
// HTTP requests to renew a token using a RefreshToken.
|
||||
type tokenRefresher struct {
|
||||
ctx context.Context // used to get HTTP requests
|
||||
conf *Config
|
||||
refreshToken string
|
||||
}
|
||||
|
||||
// WARNING: Token is not safe for concurrent access, as it
|
||||
// updates the tokenRefresher's refreshToken field.
|
||||
// Within this package, it is used by reuseTokenSource which
|
||||
// synchronizes calls to this method with its own mutex.
|
||||
func (tf *tokenRefresher) Token() (*Token, error) {
|
||||
if tf.refreshToken == "" {
|
||||
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
||||
}
|
||||
|
||||
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {tf.refreshToken},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tf.refreshToken != tk.RefreshToken {
|
||||
tf.refreshToken = tk.RefreshToken
|
||||
}
|
||||
return tk, err
|
||||
}
|
||||
|
||||
// reuseTokenSource is a TokenSource that holds a single token in memory
|
||||
// and validates its expiry before each call to retrieve it with
|
||||
// Token. If it's expired, it will be auto-refreshed using the
|
||||
// new TokenSource.
|
||||
type reuseTokenSource struct {
|
||||
new TokenSource // called when t is expired.
|
||||
|
||||
mu sync.Mutex // guards t
|
||||
t *Token
|
||||
}
|
||||
|
||||
// Token returns the current token if it's still valid, else will
|
||||
// refresh the current token (using r.Context for HTTP client
|
||||
// information) and return the new one.
|
||||
func (s *reuseTokenSource) Token() (*Token, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.t.Valid() {
|
||||
return s.t, nil
|
||||
}
|
||||
t, err := s.new.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.t = t
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||
hc, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.Set("client_id", c.ClientID)
|
||||
bustedAuth := !providerAuthHeaderWorks(c.Endpoint.TokenURL)
|
||||
if bustedAuth && c.ClientSecret != "" {
|
||||
v.Set("client_secret", c.ClientSecret)
|
||||
}
|
||||
req, err := http.NewRequest("POST", c.Endpoint.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
if !bustedAuth {
|
||||
req.SetBasicAuth(c.ClientID, c.ClientSecret)
|
||||
}
|
||||
r, err := hc.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||
}
|
||||
if code := r.StatusCode; code < 200 || code > 299 {
|
||||
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
||||
}
|
||||
|
||||
var token *Token
|
||||
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 nil, err
|
||||
}
|
||||
token = &Token{
|
||||
AccessToken: vals.Get("access_token"),
|
||||
TokenType: vals.Get("token_type"),
|
||||
RefreshToken: vals.Get("refresh_token"),
|
||||
raw: vals,
|
||||
}
|
||||
e := vals.Get("expires_in")
|
||||
if e == "" {
|
||||
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
||||
// returns expires_in field in expires. Remove the fallback to expires,
|
||||
// when Facebook fixes their implementation.
|
||||
e = vals.Get("expires")
|
||||
}
|
||||
expires, _ := strconv.Atoi(e)
|
||||
if expires != 0 {
|
||||
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||
}
|
||||
default:
|
||||
var tj tokenJSON
|
||||
if err = json.Unmarshal(body, &tj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token = &Token{
|
||||
AccessToken: tj.AccessToken,
|
||||
TokenType: tj.TokenType,
|
||||
RefreshToken: tj.RefreshToken,
|
||||
Expiry: tj.expiry(),
|
||||
raw: make(map[string]interface{}),
|
||||
}
|
||||
json.Unmarshal(body, &token.raw) // no error checks for optional fields
|
||||
}
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
// if this was a token refreshing request.
|
||||
if token.RefreshToken == "" {
|
||||
token.RefreshToken = v.Get("refresh_token")
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// tokenJSON is the struct representing the HTTP response from OAuth2
|
||||
// providers returning a token in JSON form.
|
||||
type tokenJSON struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
|
||||
Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in
|
||||
}
|
||||
|
||||
func (e *tokenJSON) expiry() (t time.Time) {
|
||||
if v := e.ExpiresIn; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
if v := e.Expires; v != 0 {
|
||||
return time.Now().Add(time.Duration(v) * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type expirationTime int32
|
||||
|
||||
func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
||||
var n json.Number
|
||||
err := json.Unmarshal(b, &n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i, err := n.Int64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*e = expirationTime(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
func condVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
var brokenAuthHeaderProviders = []string{
|
||||
"https://accounts.google.com/",
|
||||
"https://www.googleapis.com/",
|
||||
"https://github.com/",
|
||||
"https://api.instagram.com/",
|
||||
"https://www.douban.com/",
|
||||
"https://api.dropbox.com/",
|
||||
"https://api.soundcloud.com/",
|
||||
"https://www.linkedin.com/",
|
||||
"https://api.twitch.tv/",
|
||||
"https://oauth.vk.com/",
|
||||
"https://api.odnoklassniki.ru/",
|
||||
"https://connect.stripe.com/",
|
||||
"https://api.pushbullet.com/",
|
||||
"https://oauth.sandbox.trainingpeaks.com/",
|
||||
"https://oauth.trainingpeaks.com/",
|
||||
"https://www.strava.com/oauth/",
|
||||
}
|
||||
|
||||
// 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
|
||||
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||
for _, s := range brokenAuthHeaderProviders {
|
||||
if strings.HasPrefix(tokenURL, s) {
|
||||
// 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
|
||||
}
|
||||
|
||||
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||
// WithValue function to associate an *http.Client value with a context.
|
||||
var HTTPClient contextKey
|
||||
|
||||
// contextKey is just an empty struct. It exists so HTTPClient can be
|
||||
// an immutable public variable with a unique type. It's immutable
|
||||
// because nobody else can create a contextKey, being unexported.
|
||||
type contextKey struct{}
|
||||
|
||||
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||
// The returned client is not valid beyond the lifetime of the context.
|
||||
//
|
||||
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||
// using the provided context. This exists to support related OAuth2
|
||||
// packages.
|
||||
func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
||||
if src == nil {
|
||||
c, err := contextClient(ctx)
|
||||
if err != nil {
|
||||
return &http.Client{Transport: errorTransport{err}}
|
||||
}
|
||||
return c
|
||||
}
|
||||
return &http.Client{
|
||||
Transport: &Transport{
|
||||
Base: contextTransport(ctx),
|
||||
Source: ReuseTokenSource(nil, src),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ReuseTokenSource returns a TokenSource which repeatedly returns the
|
||||
// same token as long as it's valid, starting with t.
|
||||
// When its cached token is invalid, a new token is obtained from src.
|
||||
//
|
||||
// ReuseTokenSource is typically used to reuse tokens from a cache
|
||||
// (such as a file on disk) between runs of a program, rather than
|
||||
// obtaining new tokens unnecessarily.
|
||||
//
|
||||
// The initial token t may be nil, in which case the TokenSource is
|
||||
// wrapped in a caching version if it isn't one already. This also
|
||||
// means it's always safe to wrap ReuseTokenSource around any other
|
||||
// TokenSource without adverse effects.
|
||||
func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
|
||||
// Don't wrap a reuseTokenSource in itself. That would work,
|
||||
// but cause an unnecessary number of mutex operations.
|
||||
// Just build the equivalent one.
|
||||
if rt, ok := src.(*reuseTokenSource); ok {
|
||||
if t == nil {
|
||||
// Just use it directly.
|
||||
return rt
|
||||
}
|
||||
src = rt.new
|
||||
}
|
||||
return &reuseTokenSource{
|
||||
t: t,
|
||||
new: src,
|
||||
}
|
||||
}
|
426
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
426
Godeps/_workspace/src/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
@ -0,0 +1,426 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type mockTransport struct {
|
||||
rt func(req *http.Request) (resp *http.Response, err error)
|
||||
}
|
||||
|
||||
func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||
return t.rt(req)
|
||||
}
|
||||
|
||||
type mockCache struct {
|
||||
token *Token
|
||||
readErr error
|
||||
}
|
||||
|
||||
func (c *mockCache) ReadToken() (*Token, error) {
|
||||
return c.token, c.readErr
|
||||
}
|
||||
|
||||
func (c *mockCache) WriteToken(*Token) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
func newConf(url string) *Config {
|
||||
return &Config{
|
||||
ClientID: "CLIENT_ID",
|
||||
ClientSecret: "CLIENT_SECRET",
|
||||
RedirectURL: "REDIRECT_URL",
|
||||
Scopes: []string{"scope1", "scope2"},
|
||||
Endpoint: Endpoint{
|
||||
AuthURL: url + "/auth",
|
||||
TokenURL: url + "/token",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthCodeURL(t *testing.T) {
|
||||
conf := newConf("server")
|
||||
url := conf.AuthCodeURL("foo", AccessTypeOffline, ApprovalForce)
|
||||
if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" {
|
||||
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthCodeURL_Optional(t *testing.T) {
|
||||
conf := &Config{
|
||||
ClientID: "CLIENT_ID",
|
||||
Endpoint: Endpoint{
|
||||
AuthURL: "/auth-url",
|
||||
TokenURL: "/token-url",
|
||||
},
|
||||
}
|
||||
url := conf.AuthCodeURL("")
|
||||
if url != "/auth-url?client_id=CLIENT_ID&response_type=code" {
|
||||
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/token" {
|
||||
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||
}
|
||||
headerAuth := r.Header.Get("Authorization")
|
||||
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
if headerContentType != "application/x-www-form-urlencoded" {
|
||||
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
}
|
||||
if tok.TokenType != "bearer" {
|
||||
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||
}
|
||||
scope := tok.Extra("scope")
|
||||
if scope != "user" {
|
||||
t.Errorf("Unexpected value for scope: %v", scope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() != "/token" {
|
||||
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||
}
|
||||
headerAuth := r.Header.Get("Authorization")
|
||||
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
if headerContentType != "application/x-www-form-urlencoded" {
|
||||
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer", "expires_in": 86400}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
}
|
||||
if tok.TokenType != "bearer" {
|
||||
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||
}
|
||||
scope := tok.Extra("scope")
|
||||
if scope != "user" {
|
||||
t.Errorf("Unexpected value for scope: %v", scope)
|
||||
}
|
||||
}
|
||||
|
||||
const day = 24 * time.Hour
|
||||
|
||||
func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) {
|
||||
seconds := int32(day.Seconds())
|
||||
jsonNumberType := reflect.TypeOf(json.Number("0"))
|
||||
for _, c := range []struct {
|
||||
expires string
|
||||
expect error
|
||||
}{
|
||||
{fmt.Sprintf(`"expires_in": %d`, seconds), nil},
|
||||
{fmt.Sprintf(`"expires_in": "%d"`, seconds), nil}, // PayPal case
|
||||
{fmt.Sprintf(`"expires": %d`, seconds), nil}, // Facebook case
|
||||
{`"expires": false`, &json.UnmarshalTypeError{"bool", jsonNumberType}}, // wrong type
|
||||
{`"expires": {}`, &json.UnmarshalTypeError{"object", jsonNumberType}}, // wrong type
|
||||
{`"expires": "zzz"`, &strconv.NumError{"ParseInt", "zzz", strconv.ErrSyntax}}, // wrong value
|
||||
} {
|
||||
testExchangeRequest_JSONResponse_expiry(t, c.expires, c.expect)
|
||||
}
|
||||
}
|
||||
|
||||
func testExchangeRequest_JSONResponse_expiry(t *testing.T, exp string, expect error) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(fmt.Sprintf(`{"access_token": "90d", "scope": "user", "token_type": "bearer", %s}`, exp)))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
t1 := time.Now().Add(day)
|
||||
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||
t2 := time.Now().Add(day)
|
||||
if err == nil && expect != nil {
|
||||
t.Errorf("Incorrect state, conf.Exchange() should return an error: %v", expect)
|
||||
} else if err != nil {
|
||||
if reflect.DeepEqual(err, expect) {
|
||||
t.Logf("Expected error: %v", err)
|
||||
return
|
||||
} else {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
expiry := tok.Expiry
|
||||
if expiry.Before(t1) || expiry.After(t2) {
|
||||
t.Errorf("Unexpected value for Expiry: %v (shold be between %v and %v)", expiry, t1, t2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest_BadResponse(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.Exchange(NoContext, "code")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if tok.AccessToken != "" {
|
||||
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest_BadResponseType(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
_, err := conf.Exchange(NoContext, "exchange-code")
|
||||
if err == nil {
|
||||
t.Error("expected error from invalid access_token type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExchangeRequest_NonBasicAuth(t *testing.T) {
|
||||
tr := &mockTransport{
|
||||
rt: func(r *http.Request) (w *http.Response, err error) {
|
||||
headerAuth := r.Header.Get("Authorization")
|
||||
if headerAuth != "" {
|
||||
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||
}
|
||||
return nil, errors.New("no response")
|
||||
},
|
||||
}
|
||||
c := &http.Client{Transport: tr}
|
||||
conf := &Config{
|
||||
ClientID: "CLIENT_ID",
|
||||
Endpoint: Endpoint{
|
||||
AuthURL: "https://accounts.google.com/auth",
|
||||
TokenURL: "https://accounts.google.com/token",
|
||||
},
|
||||
}
|
||||
|
||||
ctx := context.WithValue(context.Background(), HTTPClient, c)
|
||||
conf.Exchange(ctx, "code")
|
||||
}
|
||||
|
||||
func TestPasswordCredentialsTokenRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
expected := "/token"
|
||||
if r.URL.String() != expected {
|
||||
t.Errorf("URL = %q; want %q", r.URL, expected)
|
||||
}
|
||||
headerAuth := r.Header.Get("Authorization")
|
||||
expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ="
|
||||
if headerAuth != expected {
|
||||
t.Errorf("Authorization header = %q; want %q", headerAuth, expected)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
expected = "application/x-www-form-urlencoded"
|
||||
if headerContentType != expected {
|
||||
t.Errorf("Content-Type header = %q; want %q", headerContentType, expected)
|
||||
}
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Failed reading request body: %s.", err)
|
||||
}
|
||||
expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1"
|
||||
if string(body) != expected {
|
||||
t.Errorf("res.Body = %q; want %q", string(body), expected)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tok, err := conf.PasswordCredentialsToken(NoContext, "user1", "password1")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !tok.Valid() {
|
||||
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||
}
|
||||
expected := "90d64460d14870c08c81352a05dedd3465940a7c"
|
||||
if tok.AccessToken != expected {
|
||||
t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected)
|
||||
}
|
||||
expected = "bearer"
|
||||
if tok.TokenType != expected {
|
||||
t.Errorf("TokenType = %q; want %q", tok.TokenType, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenRefreshRequest(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() == "/somethingelse" {
|
||||
return
|
||||
}
|
||||
if r.URL.String() != "/token" {
|
||||
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
if headerContentType != "application/x-www-form-urlencoded" {
|
||||
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||
}
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
c := conf.Client(NoContext, &Token{RefreshToken: "REFRESH_TOKEN"})
|
||||
c.Get(ts.URL + "/somethingelse")
|
||||
}
|
||||
|
||||
func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.String() == "/somethingelse" {
|
||||
return
|
||||
}
|
||||
if r.URL.String() != "/token" {
|
||||
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||
}
|
||||
headerContentType := r.Header.Get("Content-Type")
|
||||
if headerContentType != "application/x-www-form-urlencoded" {
|
||||
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||
}
|
||||
body, _ := ioutil.ReadAll(r.Body)
|
||||
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
c := conf.Client(NoContext, nil)
|
||||
_, err := c.Get(ts.URL + "/somethingelse")
|
||||
if err == nil {
|
||||
t.Errorf("Fetch should return an error if no refresh token is set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`))
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
tkr := tokenRefresher{
|
||||
conf: conf,
|
||||
ctx: NoContext,
|
||||
refreshToken: "OLD REFRESH TOKEN",
|
||||
}
|
||||
tk, err := tkr.Token()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected refreshToken error returned: %v", err)
|
||||
return
|
||||
}
|
||||
if tk.RefreshToken != tkr.refreshToken {
|
||||
t.Errorf("tokenRefresher.refresh_token = %s; want %s", tkr.refreshToken, tk.RefreshToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigClientWithToken(t *testing.T) {
|
||||
tok := &Token{
|
||||
AccessToken: "abc123",
|
||||
}
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got, want := r.Header.Get("Authorization"), fmt.Sprintf("Bearer %s", tok.AccessToken); got != want {
|
||||
t.Errorf("Authorization header = %q; want %q", got, want)
|
||||
}
|
||||
return
|
||||
}))
|
||||
defer ts.Close()
|
||||
conf := newConf(ts.URL)
|
||||
|
||||
c := conf.Client(NoContext, tok)
|
||||
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = c.Do(req)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_providerAuthHeaderWorks(t *testing.T) {
|
||||
for _, p := range brokenAuthHeaderProviders {
|
||||
if providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s not found in list", p)
|
||||
}
|
||||
p := fmt.Sprintf("%ssomesuffix", p)
|
||||
if providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s not found in list", p)
|
||||
}
|
||||
}
|
||||
p := "https://api.not-in-the-list-example.com/"
|
||||
if !providerAuthHeaderWorks(p) {
|
||||
t.Errorf("URL: %s found in list", p)
|
||||
}
|
||||
|
||||
}
|
104
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
Normal file
104
Godeps/_workspace/src/golang.org/x/oauth2/token.go
generated
vendored
Normal file
@ -0,0 +1,104 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// expiryDelta determines how earlier a token should be considered
|
||||
// expired than its actual expiration time. It is used to avoid late
|
||||
// expirations due to client-server time mismatches.
|
||||
const expiryDelta = 10 * time.Second
|
||||
|
||||
// Token represents the crendentials used to authorize
|
||||
// the requests to access protected resources on the OAuth 2.0
|
||||
// provider's backend.
|
||||
//
|
||||
// Most users of this package should not access fields of Token
|
||||
// directly. They're exported mostly for use by related packages
|
||||
// implementing derivative OAuth2 flows.
|
||||
type Token struct {
|
||||
// AccessToken is the token that authorizes and authenticates
|
||||
// the requests.
|
||||
AccessToken string `json:"access_token"`
|
||||
|
||||
// TokenType is the type of token.
|
||||
// The Type method returns either this or "Bearer", the default.
|
||||
TokenType string `json:"token_type,omitempty"`
|
||||
|
||||
// RefreshToken is a token that's used by the application
|
||||
// (as opposed to the user) to refresh the access token
|
||||
// if it expires.
|
||||
RefreshToken string `json:"refresh_token,omitempty"`
|
||||
|
||||
// Expiry is the optional expiration time of the access token.
|
||||
//
|
||||
// If zero, TokenSource implementations will reuse the same
|
||||
// token forever and RefreshToken or equivalent
|
||||
// mechanisms for that TokenSource will not be used.
|
||||
Expiry time.Time `json:"expiry,omitempty"`
|
||||
|
||||
// raw optionally contains extra metadata from the server
|
||||
// when updating a token.
|
||||
raw interface{}
|
||||
}
|
||||
|
||||
// Type returns t.TokenType if non-empty, else "Bearer".
|
||||
func (t *Token) Type() string {
|
||||
if t.TokenType != "" {
|
||||
return t.TokenType
|
||||
}
|
||||
return "Bearer"
|
||||
}
|
||||
|
||||
// SetAuthHeader sets the Authorization header to r using the access
|
||||
// token in t.
|
||||
//
|
||||
// This method is unnecessary when using Transport or an HTTP Client
|
||||
// returned by this package.
|
||||
func (t *Token) SetAuthHeader(r *http.Request) {
|
||||
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||
}
|
||||
|
||||
// WithExtra returns a new Token that's a clone of t, but using the
|
||||
// provided raw extra map. This is only intended for use by packages
|
||||
// implementing derivative OAuth2 flows.
|
||||
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||
t2 := new(Token)
|
||||
*t2 = *t
|
||||
t2.raw = extra
|
||||
return t2
|
||||
}
|
||||
|
||||
// Extra returns an extra field.
|
||||
// Extra fields are key-value pairs returned by the server as a
|
||||
// part of the token retrieval response.
|
||||
func (t *Token) Extra(key string) interface{} {
|
||||
if vals, ok := t.raw.(url.Values); ok {
|
||||
// TODO(jbd): Cast numeric values to int64 or float64.
|
||||
return vals.Get(key)
|
||||
}
|
||||
if raw, ok := t.raw.(map[string]interface{}); ok {
|
||||
return raw[key]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// expired reports whether the token is expired.
|
||||
// t must be non-nil.
|
||||
func (t *Token) expired() bool {
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Add(-expiryDelta).Before(time.Now())
|
||||
}
|
||||
|
||||
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||
func (t *Token) Valid() bool {
|
||||
return t != nil && t.AccessToken != "" && !t.expired()
|
||||
}
|
50
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
50
Godeps/_workspace/src/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestTokenExtra(t *testing.T) {
|
||||
type testCase struct {
|
||||
key string
|
||||
val interface{}
|
||||
want interface{}
|
||||
}
|
||||
const key = "extra-key"
|
||||
cases := []testCase{
|
||||
{key: key, val: "abc", want: "abc"},
|
||||
{key: key, val: 123, want: 123},
|
||||
{key: key, val: "", want: ""},
|
||||
{key: "other-key", val: "def", want: nil},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
extra := make(map[string]interface{})
|
||||
extra[tc.key] = tc.val
|
||||
tok := &Token{raw: extra}
|
||||
if got, want := tok.Extra(key), tc.want; got != want {
|
||||
t.Errorf("Extra(%q) = %q; want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenExpiry(t *testing.T) {
|
||||
now := time.Now()
|
||||
cases := []struct {
|
||||
name string
|
||||
tok *Token
|
||||
want bool
|
||||
}{
|
||||
{name: "12 seconds", tok: &Token{Expiry: now.Add(12 * time.Second)}, want: false},
|
||||
{name: "10 seconds", tok: &Token{Expiry: now.Add(expiryDelta)}, want: true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
if got, want := tc.tok.expired(), tc.want; got != want {
|
||||
t.Errorf("expired (%q) = %v; want %v", tc.name, got, want)
|
||||
}
|
||||
}
|
||||
}
|
138
Godeps/_workspace/src/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
138
Godeps/_workspace/src/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
// Copyright 2014 The oauth2 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 oauth2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests,
|
||||
// wrapping a base RoundTripper and adding an Authorization header
|
||||
// with a token from the supplied Sources.
|
||||
//
|
||||
// Transport is a low-level mechanism. Most code will use the
|
||||
// higher-level Config.Client method instead.
|
||||
type Transport struct {
|
||||
// Source supplies the token to add to outgoing requests'
|
||||
// Authorization headers.
|
||||
Source TokenSource
|
||||
|
||||
// Base is the base RoundTripper used to make HTTP requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Base http.RoundTripper
|
||||
|
||||
mu sync.Mutex // guards modReq
|
||||
modReq map[*http.Request]*http.Request // original -> modified
|
||||
}
|
||||
|
||||
// RoundTrip authorizes and authenticates the request with an
|
||||
// access token. If no token exists or token is expired,
|
||||
// tries to refresh/fetch a new token.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if t.Source == nil {
|
||||
return nil, errors.New("oauth2: Transport's Source is nil")
|
||||
}
|
||||
token, err := t.Source.Token()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req2 := cloneRequest(req) // per RoundTripper contract
|
||||
token.SetAuthHeader(req2)
|
||||
t.setModReq(req, req2)
|
||||
res, err := t.base().RoundTrip(req2)
|
||||
if err != nil {
|
||||
t.setModReq(req, nil)
|
||||
return nil, err
|
||||
}
|
||||
res.Body = &onEOFReader{
|
||||
rc: res.Body,
|
||||
fn: func() { t.setModReq(req, nil) },
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CancelRequest cancels an in-flight request by closing its connection.
|
||||
func (t *Transport) CancelRequest(req *http.Request) {
|
||||
type canceler interface {
|
||||
CancelRequest(*http.Request)
|
||||
}
|
||||
if cr, ok := t.base().(canceler); ok {
|
||||
t.mu.Lock()
|
||||
modReq := t.modReq[req]
|
||||
delete(t.modReq, req)
|
||||
t.mu.Unlock()
|
||||
cr.CancelRequest(modReq)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Transport) base() http.RoundTripper {
|
||||
if t.Base != nil {
|
||||
return t.Base
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
func (t *Transport) setModReq(orig, mod *http.Request) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if t.modReq == nil {
|
||||
t.modReq = make(map[*http.Request]*http.Request)
|
||||
}
|
||||
if mod == nil {
|
||||
delete(t.modReq, orig)
|
||||
} else {
|
||||
t.modReq[orig] = mod
|
||||
}
|
||||
}
|
||||
|
||||
// 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, len(r.Header))
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = append([]string(nil), s...)
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
type onEOFReader struct {
|
||||
rc io.ReadCloser
|
||||
fn func()
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||
n, err = r.rc.Read(p)
|
||||
if err == io.EOF {
|
||||
r.runFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (r *onEOFReader) Close() error {
|
||||
err := r.rc.Close()
|
||||
r.runFunc()
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *onEOFReader) runFunc() {
|
||||
if fn := r.fn; fn != nil {
|
||||
fn()
|
||||
r.fn = nil
|
||||
}
|
||||
}
|
||||
|
||||
type errorTransport struct{ err error }
|
||||
|
||||
func (t errorTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||
return nil, t.err
|
||||
}
|
53
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
53
Godeps/_workspace/src/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type tokenSource struct{ token *Token }
|
||||
|
||||
func (t *tokenSource) Token() (*Token, error) {
|
||||
return t.token, nil
|
||||
}
|
||||
|
||||
func TestTransportTokenSource(t *testing.T) {
|
||||
ts := &tokenSource{
|
||||
token: &Token{
|
||||
AccessToken: "abc",
|
||||
},
|
||||
}
|
||||
tr := &Transport{
|
||||
Source: ts,
|
||||
}
|
||||
server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Authorization") != "Bearer abc" {
|
||||
t.Errorf("Transport doesn't set the Authorization header from the fetched token")
|
||||
}
|
||||
})
|
||||
defer server.Close()
|
||||
client := http.Client{Transport: tr}
|
||||
client.Get(server.URL)
|
||||
}
|
||||
|
||||
func TestTokenValidNoAccessToken(t *testing.T) {
|
||||
token := &Token{}
|
||||
if token.Valid() {
|
||||
t.Errorf("Token should not be valid with no access token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpiredWithExpiry(t *testing.T) {
|
||||
token := &Token{
|
||||
Expiry: time.Now().Add(-5 * time.Hour),
|
||||
}
|
||||
if token.Valid() {
|
||||
t.Errorf("Token should not be valid if it expired in the past")
|
||||
}
|
||||
}
|
||||
|
||||
func newMockServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
|
||||
return httptest.NewServer(http.HandlerFunc(handler))
|
||||
}
|
Loading…
Reference in New Issue
Block a user