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/transient" |
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, transient.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 |