| 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 |