oauth2, oauth2/jwt: break JWT off into its own package

Change-Id: Iaaa36728f87744e0d9609674f0d0ad96e6ac80b4
Reviewed-on: https://go-review.googlesource.com/2198
Reviewed-by: Burcu Dogan <jbd@google.com>
This commit is contained in:
Brad Fitzpatrick 2014-12-30 15:11:02 -08:00
parent dfb470cc49
commit ed997606a9
8 changed files with 87 additions and 49 deletions

View File

@ -48,24 +48,3 @@ func ExampleConfig() {
client := conf.Client(oauth2.NoContext, tok)
client.Get("...")
}
func ExampleJWTConfig() {
conf := &oauth2.JWTConfig{
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("...")
}

View File

@ -15,6 +15,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"golang.org/x/oauth2/jwt"
"google.golang.org/appengine"
"google.golang.org/appengine/urlfetch"
)
@ -76,7 +77,7 @@ func ExampleJWTConfigFromJSON() {
func Example_serviceAccount() {
// Your credentials should be obtained from the Google
// Developer Console (https://console.developers.google.com).
conf := &oauth2.JWTConfig{
conf := &jwt.Config{
Email: "xxx@developer.gserviceaccount.com",
// The contents of your RSA private key or your PEM file
// that contains a private key.

View File

@ -21,6 +21,7 @@ import (
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/jwt"
)
// TODO(bradfitz,jbd): import "google.golang.org/cloud/compute/metadata" instead of
@ -39,7 +40,7 @@ const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
// the credentials that authorize and authenticate the requests.
// Create a service account on "Credentials" page under "APIs & Auth" for your
// project at https://console.developers.google.com to download a JSON key file.
func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oauth2.JWTConfig, error) {
func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*jwt.Config, error) {
var key struct {
Email string `json:"client_email"`
PrivateKey string `json:"private_key"`
@ -47,7 +48,7 @@ func JWTConfigFromJSON(ctx oauth2.Context, jsonKey []byte, scope ...string) (*oa
if err := json.Unmarshal(jsonKey, &key); err != nil {
return nil, err
}
return &oauth2.JWTConfig{
return &jwt.Config{
Email: key.Email,
PrivateKey: []byte(key.PrivateKey),
Scopes: scope,

31
jwt/example_test.go Normal file
View 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("...")
}

View File

@ -2,7 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oauth2
// 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"
@ -14,6 +18,7 @@ import (
"strings"
"time"
"golang.org/x/oauth2"
"golang.org/x/oauth2/internal"
"golang.org/x/oauth2/jws"
)
@ -23,9 +28,9 @@ var (
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
)
// JWTConfig is the configuration for using JWT to fetch tokens,
// commonly known as "two-legged OAuth".
type JWTConfig struct {
// 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
@ -52,8 +57,8 @@ type JWTConfig struct {
// TokenSource returns a JWT TokenSource using the configuration
// in c and the HTTP client from the provided context.
func (c *JWTConfig) TokenSource(ctx Context) TokenSource {
return ReuseTokenSource(nil, jwtSource{ctx, c})
func (c *Config) TokenSource(ctx oauth2.Context) oauth2.TokenSource {
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
}
// Client returns an HTTP client wrapping the context's
@ -61,26 +66,23 @@ func (c *JWTConfig) TokenSource(ctx Context) TokenSource {
// obtained from c.
//
// The returned client and its Transport should not be modified.
func (c *JWTConfig) Client(ctx Context) *http.Client {
return NewClient(ctx, c.TokenSource(ctx))
func (c *Config) Client(ctx oauth2.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
conf *JWTConfig
ctx oauth2.Context
conf *Config
}
func (js jwtSource) Token() (*Token, error) {
func (js jwtSource) Token() (*oauth2.Token, error) {
pk, err := internal.ParseKey(js.conf.PrivateKey)
if err != nil {
return nil, err
}
hc, err := contextClient(js.ctx)
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, " "),
@ -121,12 +123,13 @@ func (js jwtSource) Token() (*Token, error) {
if err := json.Unmarshal(body, &tokenRes); err != nil {
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
}
token := &Token{
token := &oauth2.Token{
AccessToken: tokenRes.AccessToken,
TokenType: tokenRes.TokenType,
raw: make(map[string]interface{}),
}
json.Unmarshal(body, &token.raw) // no error checks for optional fields
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)

View File

@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package oauth2
package jwt
import (
"net/http"
"net/http/httptest"
"testing"
"golang.org/x/oauth2"
)
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
@ -50,12 +52,12 @@ func TestJWTFetch_JSONResponse(t *testing.T) {
}))
defer ts.Close()
conf := &JWTConfig{
conf := &Config{
Email: "aaa@xxx.com",
PrivateKey: dummyPrivateKey,
TokenURL: ts.URL,
}
tok, err := conf.TokenSource(NoContext).Token()
tok, err := conf.TokenSource(oauth2.NoContext).Token()
if err != nil {
t.Fatal(err)
}
@ -84,12 +86,12 @@ func TestJWTFetch_BadResponse(t *testing.T) {
}))
defer ts.Close()
conf := &JWTConfig{
conf := &Config{
Email: "aaa@xxx.com",
PrivateKey: dummyPrivateKey,
TokenURL: ts.URL,
}
tok, err := conf.TokenSource(NoContext).Token()
tok, err := conf.TokenSource(oauth2.NoContext).Token()
if err != nil {
t.Fatal(err)
}
@ -117,12 +119,12 @@ func TestJWTFetch_BadResponseType(t *testing.T) {
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
}))
defer ts.Close()
conf := &JWTConfig{
conf := &Config{
Email: "aaa@xxx.com",
PrivateKey: dummyPrivateKey,
TokenURL: ts.URL,
}
tok, err := conf.TokenSource(NoContext).Token()
tok, err := conf.TokenSource(oauth2.NoContext).Token()
if err == nil {
t.Error("got a token; expected error")
if tok.AccessToken != "" {

View File

@ -412,7 +412,18 @@ 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, 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),

View File

@ -60,6 +60,16 @@ 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 returned from the server during token
// retrieval.
func (t *Token) Extra(key string) string {