appenginevm.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. // Copyright 2014 The oauth2 Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // +build appenginevm !appengine
  5. package google
  6. import (
  7. "strings"
  8. "sync"
  9. "time"
  10. "github.com/golang/oauth2"
  11. "google.golang.org/appengine"
  12. "google.golang.org/appengine/memcache"
  13. )
  14. var (
  15. // memcacheGob enables mocking of the memcache.Gob calls for unit testing.
  16. memcacheGob memcacher = &aeMemcache{}
  17. // accessTokenFunc enables mocking of the appengine.AccessToken call for unit testing.
  18. accessTokenFunc = appengine.AccessToken
  19. // mu protects multiple threads from attempting to fetch a token at the same time.
  20. mu sync.Mutex
  21. // tokens implements a local cache of tokens to prevent hitting quota limits for appengine.AccessToken calls.
  22. tokens map[string]*oauth2.Token
  23. )
  24. // safetyMargin is used to avoid clock-skew problems.
  25. // 5 minutes is conservative because tokens are valid for 60 minutes.
  26. const safetyMargin = 5 * time.Minute
  27. func init() {
  28. tokens = make(map[string]*oauth2.Token)
  29. }
  30. // AppEngineContext requires an App Engine request context.
  31. func AppEngineContext(ctx appengine.Context) oauth2.Option {
  32. return func(opts *oauth2.Options) error {
  33. opts.TokenFetcherFunc = makeAppEngineTokenFetcher(ctx, opts)
  34. return nil
  35. }
  36. }
  37. // FetchToken fetches a new access token for the provided scopes.
  38. // Tokens are cached locally and also with Memcache so that the app can scale
  39. // without hitting quota limits by calling appengine.AccessToken too frequently.
  40. func makeAppEngineTokenFetcher(ctx appengine.Context, opts *oauth2.Options) func(*oauth2.Token) (*oauth2.Token, error) {
  41. return func(existing *oauth2.Token) (*oauth2.Token, error) {
  42. mu.Lock()
  43. defer mu.Unlock()
  44. key := ":" + strings.Join(opts.Scopes, "_")
  45. now := time.Now().Add(safetyMargin)
  46. if t, ok := tokens[key]; ok && !t.Expiry.Before(now) {
  47. return t, nil
  48. }
  49. delete(tokens, key)
  50. // Attempt to get token from Memcache
  51. tok := new(oauth2.Token)
  52. _, err := memcacheGob.Get(ctx, key, tok)
  53. if err == nil && !tok.Expiry.Before(now) {
  54. tokens[key] = tok // Save token locally
  55. return tok, nil
  56. }
  57. token, expiry, err := accessTokenFunc(ctx, opts.Scopes...)
  58. if err != nil {
  59. return nil, err
  60. }
  61. t := &oauth2.Token{
  62. AccessToken: token,
  63. Expiry: expiry,
  64. }
  65. tokens[key] = t
  66. // Also back up token in Memcache
  67. if err = memcacheGob.Set(ctx, &memcache.Item{
  68. Key: key,
  69. Value: []byte{},
  70. Object: *t,
  71. Expiration: expiry.Sub(now),
  72. }); err != nil {
  73. ctx.Errorf("unexpected memcache.Set error: %v", err)
  74. }
  75. return t, nil
  76. }
  77. }
  78. // aeMemcache wraps the needed Memcache functionality to make it easy to mock
  79. type aeMemcache struct{}
  80. func (m *aeMemcache) Get(c appengine.Context, key string, tok *oauth2.Token) (*memcache.Item, error) {
  81. return memcache.Gob.Get(c, key, tok)
  82. }
  83. func (m *aeMemcache) Set(c appengine.Context, item *memcache.Item) error {
  84. return memcache.Gob.Set(c, item)
  85. }
  86. type memcacher interface {
  87. Get(c appengine.Context, key string, tok *oauth2.Token) (*memcache.Item, error)
  88. Set(c appengine.Context, item *memcache.Item) error
  89. }