OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 /* | |
6 Package internal contains interfaces and structs used internally | |
7 by infra.libs.auth. They are moved to a separate package for a better | |
8 readability of main infra.libs.auth code: infra.libs.auth implements top level | |
9 logic, infra.libs.auth.internal implements dirty details. | |
10 */ | |
11 package internal | |
12 | |
13 import ( | |
14 "encoding/json" | |
15 "errors" | |
16 "fmt" | |
17 "net/http" | |
18 "time" | |
19 | |
20 "golang.org/x/net/context" | |
21 "golang.org/x/oauth2" | |
22 ) | |
23 | |
24 // ErrInsufficientAccess is can't be minted for given OAuth scopes. For example | |
25 // if GCE instance wasn't granted access to requested scopes when it was created
. | |
26 var ErrInsufficientAccess = errors.New("Can't get access token for given scopes"
) | |
27 | |
28 // Token is immutable object that internally holds authentication credentials | |
29 // (short term, long term, or both). Token knows how to modify http.Request to | |
30 // apply the credentials to it. TokenProvider acts as a factory for Tokens. | |
31 type Token interface { | |
32 // Equals compares this token to another token of the same kind. | |
33 Equals(Token) bool | |
34 | |
35 // RequestHeaders returns a map of authorization headers to add to the r
equest. | |
36 RequestHeaders() map[string]string | |
37 | |
38 // Expired is true if token MUST be refreshed via RefreshToken call. | |
39 Expired() bool | |
40 } | |
41 | |
42 // TokenProvider knows how to mint new tokens, refresh existing ones, marshal | |
43 // and umarshal tokens to byte buffer. TokenProvider acts as a factory for | |
44 // particular implementations of Token interface. Tokens from two different | |
45 // provider instances must not be mixed together. | |
46 type TokenProvider interface { | |
47 // RequiresInteraction is true if provider may start user interaction in
MintToken. | |
48 RequiresInteraction() bool | |
49 | |
50 // MintToken launches authentication flow (possibly interactive) and ret
urns | |
51 // a new refreshable token. | |
52 MintToken() (Token, error) | |
53 | |
54 // RefreshToken takes existing token (probably expired, but not necessar
ily) | |
55 // and returns a new refreshed token. It should never do any user intera
ction. | |
56 // If a user interaction is required, a error should be returned instead
. | |
57 RefreshToken(Token) (Token, error) | |
58 | |
59 // MarshalToken converts a token to byte buffer. | |
60 MarshalToken(Token) ([]byte, error) | |
61 | |
62 // UnmarshalToken takes byte buffer produced by MarshalToken and returns | |
63 // original Token. | |
64 UnmarshalToken([]byte) (Token, error) | |
65 } | |
66 | |
67 // TransportFromContext returns http.RoundTripper buried inside the given | |
68 // context. | |
69 func TransportFromContext(ctx context.Context) http.RoundTripper { | |
70 // When nil is passed to NewClient it skips all OAuth stuff and returns | |
71 // client extracted from the context or http.DefaultClient. | |
72 c := oauth2.NewClient(ctx, nil) | |
73 if c == http.DefaultClient { | |
74 return http.DefaultTransport | |
75 } | |
76 return c.Transport | |
77 } | |
78 | |
79 /////////////////////////////////////////////////////////////////////////////// | |
80 | |
81 // tokenImpl implements Token interface by adapting oauth2.Token. | |
82 type tokenImpl struct { | |
83 oauth2.Token | |
84 } | |
85 | |
86 // makeToken builds Token from oauth2.Token by copying it. | |
87 func makeToken(tok *oauth2.Token) Token { | |
88 return &tokenImpl{ | |
89 Token: *tok, | |
90 } | |
91 } | |
92 | |
93 // extractOAuthToken takes Token, checks its type and return oauth2.Token. | |
94 // It returns a copy that can be safely mutated. | |
95 func extractOAuthToken(tok Token) oauth2.Token { | |
96 // It's OK to panic here on type mismatch. | |
97 return tok.(*tokenImpl).Token | |
98 } | |
99 | |
100 func (t *tokenImpl) Equals(another Token) bool { | |
101 if another == nil { | |
102 return false | |
103 } | |
104 casted, ok := another.(*tokenImpl) | |
105 if !ok { | |
106 return false | |
107 } | |
108 return t.AccessToken == casted.AccessToken | |
109 } | |
110 | |
111 func (t *tokenImpl) Expired() bool { | |
112 if t.AccessToken == "" { | |
113 return true | |
114 } | |
115 if t.Expiry.IsZero() { | |
116 return false | |
117 } | |
118 // Allow 1 min clock skew. | |
119 expiry := t.Expiry.Add(-time.Minute) | |
120 return expiry.Before(time.Now()) | |
121 } | |
122 | |
123 func (t *tokenImpl) RequestHeaders() map[string]string { | |
124 ret := make(map[string]string) | |
125 if t.AccessToken != "" { | |
126 ret["Authorization"] = "Bearer " + t.AccessToken | |
127 } | |
128 return ret | |
129 } | |
130 | |
131 /////////////////////////////////////////////////////////////////////////////// | |
132 | |
133 // oauthTokenProvider partially implements a TokenProvider built on top of oauth | |
134 // library. Concrete implementations embed this struct and provide missing | |
135 // MintToken and RefreshToken methods. | |
136 type oauthTokenProvider struct { | |
137 interactive bool | |
138 tokenFlavor string | |
139 } | |
140 | |
141 type tokenOnDisk struct { | |
142 Version string `json:"version"` | |
143 Flavor string `json:"flavor"` | |
144 AccessToken string `json:"access_token"` | |
145 RefreshToken string `json:"refresh_token,omitempty"` | |
146 ExpiresAtSec int64 `json:"expires_at,omitempty"` | |
147 } | |
148 | |
149 const tokFormatVersion = "1" | |
150 | |
151 func (p *oauthTokenProvider) RequiresInteraction() bool { | |
152 return p.interactive | |
153 } | |
154 | |
155 func (p *oauthTokenProvider) MarshalToken(t Token) ([]byte, error) { | |
156 tok := extractOAuthToken(t) | |
157 return json.Marshal(&tokenOnDisk{ | |
158 Version: tokFormatVersion, | |
159 Flavor: p.tokenFlavor, | |
160 AccessToken: tok.AccessToken, | |
161 RefreshToken: tok.RefreshToken, | |
162 ExpiresAtSec: tok.Expiry.Unix(), | |
163 }) | |
164 } | |
165 | |
166 func (p *oauthTokenProvider) UnmarshalToken(data []byte) (Token, error) { | |
167 onDisk := tokenOnDisk{} | |
168 if err := json.Unmarshal(data, &onDisk); err != nil { | |
169 return nil, err | |
170 } | |
171 if onDisk.Version != tokFormatVersion { | |
172 return nil, fmt.Errorf("auth: bad token version %q, expected %q"
, onDisk.Version, tokFormatVersion) | |
173 } | |
174 if onDisk.Flavor != p.tokenFlavor { | |
175 return nil, fmt.Errorf("auth: bad token flavor %q, expected %q",
onDisk.Flavor, p.tokenFlavor) | |
176 } | |
177 return makeToken(&oauth2.Token{ | |
178 AccessToken: onDisk.AccessToken, | |
179 RefreshToken: onDisk.RefreshToken, | |
180 Expiry: time.Unix(onDisk.ExpiresAtSec, 0), | |
181 }), nil | |
182 } | |
OLD | NEW |