Skip to content

Commit

Permalink
oauth2: rewrite google package, fix the broken build
Browse files Browse the repository at this point in the history
Change-Id: I2753a88d7be483bdbc0cac09a1beccc4806ea4bc
Reviewed-on: https://go-review.googlesource.com/1361
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Reviewed-by: Andrew Gerrand <adg@golang.org>
  • Loading branch information
rakyll authored and bradfitz committed Dec 16, 2014
1 parent a568078 commit 9b6b761
Show file tree
Hide file tree
Showing 14 changed files with 335 additions and 1,024 deletions.
57 changes: 21 additions & 36 deletions example_test.go
Expand Up @@ -7,7 +7,6 @@ package oauth2_test
import (
"fmt"
"log"
"net/http"
"testing"

"golang.org/x/oauth2"
Expand All @@ -17,46 +16,43 @@ import (
// Related to https://codereview.appspot.com/107320046
func TestA(t *testing.T) {}

func Example_regular() {
opts, err := oauth2.New(
oauth2.Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"),
oauth2.RedirectURL("YOUR_REDIRECT_URL"),
oauth2.Scope("SCOPE1", "SCOPE2"),
oauth2.Endpoint(
"https://provider.com/o/oauth2/auth",
"https://provider.com/o/oauth2/token",
),
)
if err != nil {
log.Fatal(err)
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 := opts.AuthCodeURL("state", "online", "auto")
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

This comment has been minimized.

Copy link
@korya

korya Feb 25, 2015

It looks like the comment is obsolete and needs to be updated.

if _, err = fmt.Scan(&code); err != nil {
if _, err := fmt.Scan(&code); err != nil {
log.Fatal(err)
}
t, err := opts.NewTransportFromCode(code)
tok, err := conf.Exchange(oauth2.NoContext, code)
if err != nil {
log.Fatal(err)
}

// You can use t to initiate a new http.Client and
// start making authenticated requests.
client := http.Client{Transport: t}
client := conf.Client(oauth2.NoContext, tok)
client.Get("...")
}

func Example_jWT() {
opts, err := oauth2.New(
func ExampleJWTConfig() {
var initialToken *oauth2.Token // nil means no initial token
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
Expand All @@ -65,23 +61,12 @@ func Example_jWT() {
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
//
// It only supports PEM containers with no passphrase.
oauth2.JWTClient(
"xxx@developer.gserviceaccount.com",
[]byte("-----BEGIN RSA PRIVATE KEY-----...")),
oauth2.Scope("SCOPE1", "SCOPE2"),
oauth2.JWTEndpoint("https://provider.com/o/oauth2/token"),
// If you would like to impersonate a user, you can
// create a transport with a subject. The following GET
// request will be made on the behalf of user@example.com.
// Subject is optional.
oauth2.Subject("user@example.com"),
)
if err != nil {
log.Fatal(err)
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 := http.Client{Transport: opts.NewTransport()}
client := conf.Client(oauth2.NoContext, initialToken)
client.Get("...")
}
111 changes: 17 additions & 94 deletions google/appengine.go
Expand Up @@ -7,108 +7,31 @@
package google

import (
"net/http"
"strings"
"sync"
"time"

"golang.org/x/oauth2"

"appengine"
"appengine/memcache"
"appengine/urlfetch"
)

var (
// memcacheGob enables mocking of the memcache.Gob calls for unit testing.
memcacheGob memcacher = &aeMemcache{}

// accessTokenFunc enables mocking of the appengine.AccessToken call for unit testing.
accessTokenFunc = appengine.AccessToken

// mu protects multiple threads from attempting to fetch a token at the same time.
mu sync.Mutex

// tokens implements a local cache of tokens to prevent hitting quota limits for appengine.AccessToken calls.
tokens map[string]*oauth2.Token
"golang.org/x/oauth2"
)

// safetyMargin is used to avoid clock-skew problems.
// 5 minutes is conservative because tokens are valid for 60 minutes.
const safetyMargin = 5 * time.Minute

func init() {
tokens = make(map[string]*oauth2.Token)
}

// AppEngineContext requires an App Engine request context.
func AppEngineContext(ctx appengine.Context) oauth2.Option {
return func(opts *oauth2.Options) error {
opts.TokenFetcherFunc = makeAppEngineTokenFetcher(ctx, opts)
opts.Client = &http.Client{
Transport: &urlfetch.Transport{Context: ctx},
}
return nil
// AppEngineTokenSource returns a token source that fetches tokens
// issued to the current App Engine application's service account.
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
// that involves user accounts, see oauth2.Config instead.
//
// You are required to provide a valid appengine.Context as context.
func AppEngineTokenSource(ctx appengine.Context, scope ...string) oauth2.TokenSource {
return &appEngineTokenSource{
ctx: ctx,
scopes: scope,
fetcherFunc: aeFetcherFunc,
}
}

// FetchToken fetches a new access token for the provided scopes.
// Tokens are cached locally and also with Memcache so that the app can scale
// without hitting quota limits by calling appengine.AccessToken too frequently.
func makeAppEngineTokenFetcher(ctx appengine.Context, opts *oauth2.Options) func(*oauth2.Token) (*oauth2.Token, error) {
return func(existing *oauth2.Token) (*oauth2.Token, error) {
mu.Lock()
defer mu.Unlock()

key := ":" + strings.Join(opts.Scopes, "_")
now := time.Now().Add(safetyMargin)
if t, ok := tokens[key]; ok && !t.Expiry.Before(now) {
return t, nil
}
delete(tokens, key)

// Attempt to get token from Memcache
tok := new(oauth2.Token)
_, err := memcacheGob.Get(ctx, key, tok)
if err == nil && !tok.Expiry.Before(now) {
tokens[key] = tok // Save token locally
return tok, nil
}

token, expiry, err := accessTokenFunc(ctx, opts.Scopes...)
if err != nil {
return nil, err
}
t := &oauth2.Token{
AccessToken: token,
Expiry: expiry,
}
tokens[key] = t
// Also back up token in Memcache
if err = memcacheGob.Set(ctx, &memcache.Item{
Key: key,
Value: []byte{},
Object: *t,
Expiration: expiry.Sub(now),
}); err != nil {
ctx.Errorf("unexpected memcache.Set error: %v", err)
}
return t, nil
var aeFetcherFunc = func(ctx oauth2.Context, scope ...string) (string, time.Time, error) {
c, ok := ctx.(appengine.Context)
if !ok {
return "", time.Time{}, errInvalidContext
}
}

// aeMemcache wraps the needed Memcache functionality to make it easy to mock
type aeMemcache struct{}

func (m *aeMemcache) Get(c appengine.Context, key string, tok *oauth2.Token) (*memcache.Item, error) {
return memcache.Gob.Get(c, key, tok)
}

func (m *aeMemcache) Set(c appengine.Context, item *memcache.Item) error {
return memcache.Gob.Set(c, item)
}

type memcacher interface {
Get(c appengine.Context, key string, tok *oauth2.Token) (*memcache.Item, error)
Set(c appengine.Context, item *memcache.Item) error
return appengine.AccessToken(c, scope...)
}

0 comments on commit 9b6b761

Please sign in to comment.