// 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 google import ( "errors" "sort" "strings" "sync" "time" "golang.org/x/oauth2" ) var ( aeTokensMu sync.Mutex // guards aeTokens and appEngineTokenSource.key // aeTokens helps the fetched tokens to be reused until their expiration. aeTokens = make(map[string]*tokenLock) // key is '\0'-separated scopes ) var errInvalidContext = errors.New("oauth2: context must come from appengine.NewContext") type tokenLock struct { mu sync.Mutex // guards t; held while updating t t *oauth2.Token } type appEngineTokenSource struct { ctx oauth2.Context // fetcherFunc makes the actual RPC to fetch a new access // token with an expiry time. Provider of this function is // responsible to assert that the given context is valid. fetcherFunc func(ctx oauth2.Context, scope ...string) (accessToken string, expiry time.Time, err error) // scopes and key are guarded by the package-level mutex aeTokensMu scopes []string key string } func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) { aeTokensMu.Lock() if ts.key == "" { sort.Sort(sort.StringSlice(ts.scopes)) ts.key = strings.Join(ts.scopes, string(0)) } tok, ok := aeTokens[ts.key] if !ok { tok = &tokenLock{} aeTokens[ts.key] = tok } aeTokensMu.Unlock() tok.mu.Lock() defer tok.mu.Unlock() if tok.t.Valid() { return tok.t, nil } access, exp, err := ts.fetcherFunc(ts.ctx, ts.scopes...) if err != nil { return nil, err } tok.t = &oauth2.Token{ AccessToken: access, Expiry: exp, } return tok.t, nil }