| OLD | NEW |
| 1 // Copyright 2015 The LUCI Authors. All rights reserved. | 1 // Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 // Package client implements OAuth2 authentication for outbound connections | 5 // Package client implements OAuth2 authentication for outbound connections |
| 6 // from Appengine using the application services account. | 6 // from Appengine using the application services account. |
| 7 package client | 7 package client |
| 8 | 8 |
| 9 import ( | 9 import ( |
| 10 "fmt" | 10 "fmt" |
| 11 "sort" | 11 "sort" |
| 12 "strings" | 12 "strings" |
| 13 "time" | 13 "time" |
| 14 | 14 |
| 15 "golang.org/x/net/context" | 15 "golang.org/x/net/context" |
| 16 "golang.org/x/oauth2" | 16 "golang.org/x/oauth2" |
| 17 | 17 |
| 18 "github.com/luci/gae/service/info" | 18 "github.com/luci/gae/service/info" |
| 19 | 19 |
| 20 "github.com/luci/luci-go/common/auth" | 20 "github.com/luci/luci-go/common/auth" |
| 21 "github.com/luci/luci-go/common/clock" | 21 "github.com/luci/luci-go/common/clock" |
| 22 "github.com/luci/luci-go/common/data/caching/proccache" | 22 "github.com/luci/luci-go/common/data/caching/proccache" |
| 23 "github.com/luci/luci-go/common/data/rand/mathrand" | 23 "github.com/luci/luci-go/common/data/rand/mathrand" |
| 24 "github.com/luci/luci-go/common/data/stringset" | 24 "github.com/luci/luci-go/common/data/stringset" |
| 25 "github.com/luci/luci-go/common/errors" | |
| 26 "github.com/luci/luci-go/common/logging" | 25 "github.com/luci/luci-go/common/logging" |
| 26 "github.com/luci/luci-go/common/retry" |
| 27 ) | 27 ) |
| 28 | 28 |
| 29 // GetAccessToken returns an OAuth access token representing app's service | 29 // GetAccessToken returns an OAuth access token representing app's service |
| 30 // account. | 30 // account. |
| 31 // | 31 // |
| 32 // If scopes is empty, uses auth.OAuthScopeEmail scope. | 32 // If scopes is empty, uses auth.OAuthScopeEmail scope. |
| 33 // | 33 // |
| 34 // Implements a caching layer on top of GAE's GetAccessToken RPC. May return | 34 // Implements a caching layer on top of GAE's GetAccessToken RPC. May return |
| 35 // transient errors. | 35 // transient errors. |
| 36 func GetAccessToken(c context.Context, scopes []string) (*oauth2.Token, error) { | 36 func GetAccessToken(c context.Context, scopes []string) (*oauth2.Token, error) { |
| 37 scopes, cacheKey := normalizeScopes(scopes) | 37 scopes, cacheKey := normalizeScopes(scopes) |
| 38 | 38 |
| 39 // Try to find the token in the local memory first. If it expires soon, | 39 // Try to find the token in the local memory first. If it expires soon, |
| 40 // refresh it earlier with some probability. That avoids a situation whe
n | 40 // refresh it earlier with some probability. That avoids a situation whe
n |
| 41 // parallel requests that use access tokens suddenly see the cache expir
ed | 41 // parallel requests that use access tokens suddenly see the cache expir
ed |
| 42 // and rush to refresh the token all at once. | 42 // and rush to refresh the token all at once. |
| 43 pcache := proccache.GetCache(c) | 43 pcache := proccache.GetCache(c) |
| 44 if entry := pcache.Get(cacheKey); entry != nil && !closeToExpRandomized(
c, entry.Exp) { | 44 if entry := pcache.Get(cacheKey); entry != nil && !closeToExpRandomized(
c, entry.Exp) { |
| 45 return entry.Value.(*oauth2.Token), nil | 45 return entry.Value.(*oauth2.Token), nil |
| 46 } | 46 } |
| 47 | 47 |
| 48 // The token needs to be refreshed. | 48 // The token needs to be refreshed. |
| 49 logging.Debugf(c, "Getting an access token for scopes %q", strings.Join(
scopes, ", ")) | 49 logging.Debugf(c, "Getting an access token for scopes %q", strings.Join(
scopes, ", ")) |
| 50 accessToken, exp, err := info.AccessToken(c, scopes...) | 50 accessToken, exp, err := info.AccessToken(c, scopes...) |
| 51 if err != nil { | 51 if err != nil { |
| 52 » » return nil, errors.WrapTransient(err) | 52 » » return nil, retry.Tag.Apply(err) |
| 53 } | 53 } |
| 54 logging.Debugf(c, "The token expires in %s", exp.Sub(clock.Now(c))) | 54 logging.Debugf(c, "The token expires in %s", exp.Sub(clock.Now(c))) |
| 55 | 55 |
| 56 // Prematurely expire it to guarantee all returned token live for at lea
st | 56 // Prematurely expire it to guarantee all returned token live for at lea
st |
| 57 // 'expirationMinLifetime'. | 57 // 'expirationMinLifetime'. |
| 58 tok := &oauth2.Token{ | 58 tok := &oauth2.Token{ |
| 59 AccessToken: accessToken, | 59 AccessToken: accessToken, |
| 60 Expiry: exp.Add(-expirationMinLifetime), | 60 Expiry: exp.Add(-expirationMinLifetime), |
| 61 TokenType: "Bearer", | 61 TokenType: "Bearer", |
| 62 } | 62 } |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 116 case now.After(exp): | 116 case now.After(exp): |
| 117 return true // expired already | 117 return true // expired already |
| 118 case now.Add(expirationRandomization).Before(exp): | 118 case now.Add(expirationRandomization).Before(exp): |
| 119 return false // far from expiration | 119 return false // far from expiration |
| 120 default: | 120 default: |
| 121 // The expiration is close enough. Do the randomization. | 121 // The expiration is close enough. Do the randomization. |
| 122 rnd := time.Duration(mathrand.Int63n(c, int64(expirationRandomiz
ation))) | 122 rnd := time.Duration(mathrand.Int63n(c, int64(expirationRandomiz
ation))) |
| 123 return now.Add(rnd).After(exp) | 123 return now.Add(rnd).After(exp) |
| 124 } | 124 } |
| 125 } | 125 } |
| OLD | NEW |