| 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 auth defines an opinionated wrapper around OAuth2. | |
| 7 | |
| 8 It hides configurability of base oauth2 library and instead makes a predefined | |
| 9 set of choices regarding where the credentials should be stored and how OAuth2 | |
| 10 should be used. It makes authentication flows look more uniform across tools | |
| 11 that use infra.libs.auth and allow credentials reuse across multiple binaries. | |
| 12 | |
| 13 Also it knows about various environments Chrome Infra tools are running under | |
| 14 (GCE, Chrome Infra Golo, GAE, developers' machine) and switches default | |
| 15 authentication scheme accordingly (e.g. on GCE machine the default is to use | |
| 16 GCE metadata server). | |
| 17 | |
| 18 All tools that use infra.libs.auth share same credentials by default, meaning | |
| 19 a user needs to authenticate only once to use them all. Credentials are cached | |
| 20 in ~/.config/chrome_infra/auth/* and reused by all processes running under | |
| 21 the same user account. | |
| 22 */ | |
| 23 package auth | |
| 24 | |
| 25 import ( | |
| 26 "crypto/sha1" | |
| 27 "encoding/hex" | |
| 28 "errors" | |
| 29 "fmt" | |
| 30 "io/ioutil" | |
| 31 "net/http" | |
| 32 "os" | |
| 33 "os/user" | |
| 34 "path/filepath" | |
| 35 "sort" | |
| 36 "sync" | |
| 37 | |
| 38 "golang.org/x/net/context" | |
| 39 "google.golang.org/cloud/compute/metadata" | |
| 40 | |
| 41 "infra/libs/auth/internal" | |
| 42 "infra/libs/logging" | |
| 43 ) | |
| 44 | |
| 45 var ( | |
| 46 // ErrLoginRequired is returned by Transport() in case long term credent
ials | |
| 47 // are not cached and the user must go through interactive login. | |
| 48 ErrLoginRequired = errors.New("Interactive login is required") | |
| 49 | |
| 50 // ErrInsufficientAccess is returned by Login() or Transport() if access
_token | |
| 51 // can't be minted for given OAuth scopes. For example if GCE instance w
asn't | |
| 52 // granted access to requested scopes when it was created. | |
| 53 ErrInsufficientAccess = internal.ErrInsufficientAccess | |
| 54 ) | |
| 55 | |
| 56 // Known Google API OAuth scopes. | |
| 57 const ( | |
| 58 OAuthScopeEmail = "https://www.googleapis.com/auth/userinfo.email" | |
| 59 ) | |
| 60 | |
| 61 // Method defines a method to use to obtain OAuth access_token. | |
| 62 type Method string | |
| 63 | |
| 64 // Supported authentication methods. | |
| 65 const ( | |
| 66 // AutoSelectMethod can be used to allow the library to pick a method mo
st | |
| 67 // appropriate for current execution environment. It will search for a p
rivate | |
| 68 // key for a service account, then (if running on GCE) will try to query
GCE | |
| 69 // metadata server, and only then pick UserCredentialsMethod that requir
es | |
| 70 // interaction with a user. | |
| 71 AutoSelectMethod Method = "" | |
| 72 | |
| 73 // UserCredentialsMethod is used for interactive OAuth 3-legged login fl
ow. | |
| 74 UserCredentialsMethod Method = "UserCredentialsMethod" | |
| 75 | |
| 76 // ServiceAccountMethod is used to authenticate as a service account usi
ng | |
| 77 // a private key. | |
| 78 ServiceAccountMethod Method = "ServiceAccountMethod" | |
| 79 | |
| 80 // GCEMetadataMethod is used on Compute Engine to use tokens provided by | |
| 81 // Metadata server. See https://cloud.google.com/compute/docs/authentica
tion | |
| 82 GCEMetadataMethod Method = "GCEMetadataMethod" | |
| 83 ) | |
| 84 | |
| 85 // LoginMode is used as enum in AuthenticatedClient function. | |
| 86 type LoginMode string | |
| 87 | |
| 88 const ( | |
| 89 // InteractiveLogin is passed to AuthenticatedClient to forcefully rerun
full | |
| 90 // login flow and cache resulting tokens. Used by 'login' CLI command. | |
| 91 InteractiveLogin LoginMode = "InteractiveLogin" | |
| 92 | |
| 93 // SilentLogin is passed to AuthenticatedClient if authentication must b
e used | |
| 94 // and it is NOT OK to run interactive login flow to get the tokens. The
call | |
| 95 // will fail with ErrLoginRequired error if there's no cached tokens. Sh
ould | |
| 96 // normally be used by all CLI tools that need to use authentication. | |
| 97 SilentLogin LoginMode = "SilentLogin" | |
| 98 | |
| 99 // OptionalLogin is passed to AuthenticatedClient if it is OK not to use | |
| 100 // authentication if there are no cached credentials. Interactive login
will | |
| 101 // never be called, default unauthenticated client will be returned inst
ead. | |
| 102 // Should be used by CLI tools where authentication is optional. | |
| 103 OptionalLogin LoginMode = "OptionalLogin" | |
| 104 ) | |
| 105 | |
| 106 // Options are used by NewAuthenticator call. All fields are optional and have | |
| 107 // sane default values. | |
| 108 type Options struct { | |
| 109 // Method defaults to AutoSelectMethod. | |
| 110 Method Method | |
| 111 // Scopes is a list of OAuth scopes to request, defaults to [OAuthScopeE
mail]. | |
| 112 Scopes []string | |
| 113 | |
| 114 // ClientID is OAuth client_id to use with UserCredentialsMethod. | |
| 115 // Default: provided by DefaultClient(). | |
| 116 ClientID string | |
| 117 // ClientID is OAuth client_secret to use with UserCredentialsMethod. | |
| 118 // Default: provided by DefaultClient(). | |
| 119 ClientSecret string | |
| 120 | |
| 121 // ServiceAccountJSONPath is a path to a JSON blob with a private key to
use | |
| 122 // with ServiceAccountMethod. See the "Credentials" page under "APIs & A
uth" | |
| 123 // for your project at Cloud Console. | |
| 124 // Default: ~/.config/chrome_infra/auth/service_account.json. | |
| 125 ServiceAccountJSONPath string | |
| 126 | |
| 127 // GCEAccountName is an account name to query to fetch token for from me
tadata | |
| 128 // server when GCEMetadataMethod is used. If given account wasn't grante
d | |
| 129 // required set of scopes during instance creation time, Transport() cal
l | |
| 130 // fails with ErrInsufficientAccess. | |
| 131 // Default: "default" account. | |
| 132 GCEAccountName string | |
| 133 | |
| 134 // Context carries the underlying HTTP transport to use. If context is n
ot | |
| 135 // provided or doesn't contain the transport, http.DefaultTransport will
be | |
| 136 // used. Context will also be used to grab a logger if passed Logger is
nil. | |
| 137 Context context.Context | |
| 138 | |
| 139 // Logger is used to write log messages. If nil, extract it from the con
text. | |
| 140 Logger logging.Logger | |
| 141 } | |
| 142 | |
| 143 // Authenticator is a factory for http.RoundTripper objects that know how to use | |
| 144 // cached OAuth credentials. Authenticator also knows how to run interactive | |
| 145 // login flow, if required. | |
| 146 type Authenticator interface { | |
| 147 // Transport returns http.RoundTripper that adds authentication details
to | |
| 148 // each request. An interactive authentication flow (if required) must b
e | |
| 149 // complete before making a transport, otherwise ErrLoginRequired is ret
urned. | |
| 150 // Returned transport object can be safely reused across many http.Clien
t's. | |
| 151 Transport() (http.RoundTripper, error) | |
| 152 | |
| 153 // Login perform an interaction with the user to get a long term refresh
token | |
| 154 // and cache it. Blocks for user input, can use stdin. Returns ErrNoTerm
inal | |
| 155 // if interaction with a user is required, but the process is not runnin
g | |
| 156 // under a terminal. It overwrites currently cached credentials, if any. | |
| 157 Login() error | |
| 158 | |
| 159 // PurgeCredentialsCache removes cached tokens. | |
| 160 PurgeCredentialsCache() error | |
| 161 } | |
| 162 | |
| 163 // NewAuthenticator returns a new instance of Authenticator given its options. | |
| 164 func NewAuthenticator(opts Options) Authenticator { | |
| 165 // Add default scope, sort scopes. | |
| 166 if len(opts.Scopes) == 0 { | |
| 167 opts.Scopes = []string{OAuthScopeEmail} | |
| 168 } | |
| 169 tmp := make([]string, len(opts.Scopes)) | |
| 170 copy(tmp, opts.Scopes) | |
| 171 sort.Strings(tmp) | |
| 172 opts.Scopes = tmp | |
| 173 | |
| 174 // Fill in blanks with default values. | |
| 175 if opts.ClientID == "" || opts.ClientSecret == "" { | |
| 176 opts.ClientID, opts.ClientSecret = DefaultClient() | |
| 177 } | |
| 178 if opts.ServiceAccountJSONPath == "" { | |
| 179 opts.ServiceAccountJSONPath = filepath.Join(SecretsDir(), "servi
ce_account.json") | |
| 180 } | |
| 181 if opts.GCEAccountName == "" { | |
| 182 opts.GCEAccountName = "default" | |
| 183 } | |
| 184 if opts.Context == nil { | |
| 185 opts.Context = context.Background() | |
| 186 } | |
| 187 if opts.Logger == nil { | |
| 188 opts.Logger = logging.Get(opts.Context) | |
| 189 } | |
| 190 | |
| 191 // See ensureInitialized for the rest of the initialization. | |
| 192 auth := &authenticatorImpl{opts: &opts, log: opts.Logger} | |
| 193 auth.transport = &authTransport{ | |
| 194 parent: auth, | |
| 195 base: internal.TransportFromContext(opts.Context), | |
| 196 log: opts.Logger, | |
| 197 } | |
| 198 return auth | |
| 199 } | |
| 200 | |
| 201 // AuthenticatedClient performs login (if requested) and returns http.Client. | |
| 202 // See documentation for 'mode' for more details. | |
| 203 func AuthenticatedClient(mode LoginMode, auth Authenticator) (*http.Client, erro
r) { | |
| 204 if mode == InteractiveLogin { | |
| 205 if err := auth.PurgeCredentialsCache(); err != nil { | |
| 206 return nil, err | |
| 207 } | |
| 208 } | |
| 209 transport, err := auth.Transport() | |
| 210 if err == nil { | |
| 211 return &http.Client{Transport: transport}, nil | |
| 212 } | |
| 213 if err != ErrLoginRequired || mode == SilentLogin { | |
| 214 return nil, err | |
| 215 } | |
| 216 if mode == OptionalLogin { | |
| 217 return http.DefaultClient, nil | |
| 218 } | |
| 219 if mode != InteractiveLogin { | |
| 220 return nil, fmt.Errorf("Invalid mode argument: %s", mode) | |
| 221 } | |
| 222 if err = auth.Login(); err != nil { | |
| 223 return nil, err | |
| 224 } | |
| 225 if transport, err = auth.Transport(); err != nil { | |
| 226 return nil, err | |
| 227 } | |
| 228 return &http.Client{Transport: transport}, nil | |
| 229 } | |
| 230 | |
| 231 //////////////////////////////////////////////////////////////////////////////// | |
| 232 // Authenticator implementation. | |
| 233 | |
| 234 type authenticatorImpl struct { | |
| 235 // Immutable members. | |
| 236 opts *Options | |
| 237 transport http.RoundTripper | |
| 238 log logging.Logger | |
| 239 | |
| 240 // Mutable members. | |
| 241 lock sync.Mutex | |
| 242 cache *tokenCache | |
| 243 provider internal.TokenProvider | |
| 244 err error | |
| 245 token internal.Token | |
| 246 } | |
| 247 | |
| 248 func (a *authenticatorImpl) Transport() (http.RoundTripper, error) { | |
| 249 a.lock.Lock() | |
| 250 defer a.lock.Unlock() | |
| 251 | |
| 252 err := a.ensureInitialized() | |
| 253 if err != nil { | |
| 254 return nil, err | |
| 255 } | |
| 256 | |
| 257 // No cached token and token provider requires interaction with a user:
need | |
| 258 // to login. Only non-interactive token providers are allowed to mint to
kens | |
| 259 // on the fly, see refreshToken. | |
| 260 if a.token == nil && a.provider.RequiresInteraction() { | |
| 261 return nil, ErrLoginRequired | |
| 262 } | |
| 263 return a.transport, nil | |
| 264 } | |
| 265 | |
| 266 func (a *authenticatorImpl) Login() error { | |
| 267 a.lock.Lock() | |
| 268 defer a.lock.Unlock() | |
| 269 | |
| 270 err := a.ensureInitialized() | |
| 271 if err != nil { | |
| 272 return err | |
| 273 } | |
| 274 if !a.provider.RequiresInteraction() { | |
| 275 return nil | |
| 276 } | |
| 277 | |
| 278 // Create initial token. This may require interaction with a user. | |
| 279 a.token, err = a.provider.MintToken() | |
| 280 if err != nil { | |
| 281 return err | |
| 282 } | |
| 283 | |
| 284 // Store the initial token in the cache. Don't abort if it fails, the to
ken | |
| 285 // is still usable from the memory. | |
| 286 if err = a.cacheToken(a.token); err != nil { | |
| 287 a.log.Warningf("auth: failed to write token to cache: %v", err) | |
| 288 } | |
| 289 | |
| 290 return nil | |
| 291 } | |
| 292 | |
| 293 func (a *authenticatorImpl) PurgeCredentialsCache() error { | |
| 294 a.lock.Lock() | |
| 295 defer a.lock.Unlock() | |
| 296 | |
| 297 if err := a.ensureInitialized(); err != nil { | |
| 298 return err | |
| 299 } | |
| 300 if err := a.cache.clear(); err != nil { | |
| 301 return err | |
| 302 } | |
| 303 a.token = nil | |
| 304 return nil | |
| 305 } | |
| 306 | |
| 307 //////////////////////////////////////////////////////////////////////////////// | |
| 308 // Authenticator private methods. | |
| 309 | |
| 310 // ensureInitialized is supposed to be called under the lock. | |
| 311 func (a *authenticatorImpl) ensureInitialized() error { | |
| 312 if a.err != nil || a.provider != nil { | |
| 313 return a.err | |
| 314 } | |
| 315 | |
| 316 // selectDefaultMethod may do heavy calls, call it lazily here rather th
an in | |
| 317 // NewAuthenticator. | |
| 318 if a.opts.Method == AutoSelectMethod { | |
| 319 a.opts.Method = selectDefaultMethod(a.opts) | |
| 320 } | |
| 321 a.log.Infof("auth: using %s", a.opts.Method) | |
| 322 a.provider, a.err = makeTokenProvider(a.opts) | |
| 323 if a.err != nil { | |
| 324 return a.err | |
| 325 } | |
| 326 | |
| 327 // Setup the cache only when Method is known, cache filename depends on
it. | |
| 328 a.cache = &tokenCache{ | |
| 329 path: filepath.Join(SecretsDir(), cacheFileName(a.opts)+".tok"), | |
| 330 log: a.log, | |
| 331 } | |
| 332 | |
| 333 // Broken token cache is not a fatal error. So just log it and forget, a
new | |
| 334 // token will be minted. | |
| 335 var err error | |
| 336 a.token, err = a.readTokenCache() | |
| 337 if err != nil { | |
| 338 a.log.Warningf("auth: failed to read token from cache: %v", err) | |
| 339 } | |
| 340 return nil | |
| 341 } | |
| 342 | |
| 343 // readTokenCache may be called with a.lock held or not held. It works either wa
y. | |
| 344 func (a *authenticatorImpl) readTokenCache() (internal.Token, error) { | |
| 345 // 'read' returns (nil, nil) if cache is empty. | |
| 346 buf, err := a.cache.read() | |
| 347 if err != nil || buf == nil { | |
| 348 return nil, err | |
| 349 } | |
| 350 token, err := a.provider.UnmarshalToken(buf) | |
| 351 if err != nil { | |
| 352 return nil, err | |
| 353 } | |
| 354 return token, nil | |
| 355 } | |
| 356 | |
| 357 // cacheToken may be called with a.lock held or not held. It works either way. | |
| 358 func (a *authenticatorImpl) cacheToken(tok internal.Token) error { | |
| 359 buf, err := a.provider.MarshalToken(tok) | |
| 360 if err != nil { | |
| 361 return err | |
| 362 } | |
| 363 return a.cache.write(buf) | |
| 364 } | |
| 365 | |
| 366 // currentToken lock a.lock inside. It MUST NOT be called when a.lock is held. | |
| 367 func (a *authenticatorImpl) currentToken() internal.Token { | |
| 368 // TODO(vadimsh): Test with go test -race. The lock may be unnecessary. | |
| 369 a.lock.Lock() | |
| 370 defer a.lock.Unlock() | |
| 371 return a.token | |
| 372 } | |
| 373 | |
| 374 // refreshToken compares current token to 'prev' and launches token refresh | |
| 375 // procedure if they still match. Returns a refreshed token (if a refresh | |
| 376 // procedure happened) or the current token (i.e. if it's different from prev). | |
| 377 // Acts as "Compare-And-Swap" where "Swap" is a token refresh procedure. | |
| 378 func (a *authenticatorImpl) refreshToken(prev internal.Token) (internal.Token, e
rror) { | |
| 379 // Refresh the token under the lock. | |
| 380 tok, cache, err := func() (internal.Token, bool, error) { | |
| 381 a.lock.Lock() | |
| 382 defer a.lock.Unlock() | |
| 383 | |
| 384 // Some other goroutine already updated the token, just return t
he token. | |
| 385 if a.token != nil && !a.token.Equals(prev) { | |
| 386 return a.token, false, nil | |
| 387 } | |
| 388 | |
| 389 // Rescan the cache. Maybe some other process updated the token. | |
| 390 cached, err := a.readTokenCache() | |
| 391 if err == nil && cached != nil && !cached.Equals(prev) && !cache
d.Expired() { | |
| 392 a.log.Infof("auth: some other process put refreshed toke
n in the cache") | |
| 393 a.token = cached | |
| 394 return a.token, false, nil | |
| 395 } | |
| 396 | |
| 397 // Mint a new token or refresh the existing one. | |
| 398 if a.token == nil { | |
| 399 // Can't do user interaction outside of Login. | |
| 400 if a.provider.RequiresInteraction() { | |
| 401 return nil, false, ErrLoginRequired | |
| 402 } | |
| 403 a.log.Infof("auth: minting a new token") | |
| 404 a.token, err = a.provider.MintToken() | |
| 405 if err != nil { | |
| 406 a.log.Warningf("auth: failed to mint a token: %v
", err) | |
| 407 return nil, false, err | |
| 408 } | |
| 409 } else { | |
| 410 a.log.Infof("auth: refreshing the token") | |
| 411 a.token, err = a.provider.RefreshToken(a.token) | |
| 412 if err != nil { | |
| 413 a.log.Warningf("auth: failed to refresh the toke
n: %v", err) | |
| 414 return nil, false, err | |
| 415 } | |
| 416 } | |
| 417 return a.token, true, nil | |
| 418 }() | |
| 419 | |
| 420 if err != nil { | |
| 421 return nil, err | |
| 422 } | |
| 423 | |
| 424 // Store the new token in the cache outside the lock, no need for caller
s to | |
| 425 // wait for this. Do not die if failed, token is still usable from the m
emory. | |
| 426 if cache { | |
| 427 if err = a.cacheToken(tok); err != nil { | |
| 428 a.log.Warningf("auth: failed to write refreshed token to
the cache: %v", err) | |
| 429 } | |
| 430 } | |
| 431 return tok, nil | |
| 432 } | |
| 433 | |
| 434 //////////////////////////////////////////////////////////////////////////////// | |
| 435 // authTransport implementation. | |
| 436 | |
| 437 // TODO(vadimsh): Support CancelRequest if underlying transport supports it. | |
| 438 // It's tricky. http.Client uses type cast to figure out whether transport | |
| 439 // supports request cancellation or not. So new authTransportWithCancelation | |
| 440 // should be used when parent transport provides CancelRequest. Also | |
| 441 // authTransport should keep a mapping between original http.Request objects | |
| 442 // and ones with access tokens attached (to know what to pass to | |
| 443 // parent transport CancelRequest). | |
| 444 | |
| 445 type authTransport struct { | |
| 446 parent *authenticatorImpl | |
| 447 base http.RoundTripper | |
| 448 log logging.Logger | |
| 449 } | |
| 450 | |
| 451 // RoundTrip appends authorization details to the request. | |
| 452 func (t *authTransport) RoundTrip(req *http.Request) (resp *http.Response, err e
rror) { | |
| 453 tok := t.parent.currentToken() | |
| 454 if tok == nil || tok.Expired() { | |
| 455 tok, err = t.parent.refreshToken(tok) | |
| 456 if err != nil { | |
| 457 return | |
| 458 } | |
| 459 if tok == nil || tok.Expired() { | |
| 460 err = fmt.Errorf("auth: failed to refresh the token") | |
| 461 return | |
| 462 } | |
| 463 } | |
| 464 clone := *req | |
| 465 clone.Header = make(http.Header) | |
| 466 for k, v := range req.Header { | |
| 467 clone.Header[k] = v | |
| 468 } | |
| 469 for k, v := range tok.RequestHeaders() { | |
| 470 clone.Header.Set(k, v) | |
| 471 } | |
| 472 return t.base.RoundTrip(&clone) | |
| 473 } | |
| 474 | |
| 475 //////////////////////////////////////////////////////////////////////////////// | |
| 476 // tokenCache implementation. | |
| 477 | |
| 478 type tokenCache struct { | |
| 479 path string | |
| 480 log logging.Logger | |
| 481 lock sync.Mutex | |
| 482 } | |
| 483 | |
| 484 func (c *tokenCache) read() (buf []byte, err error) { | |
| 485 c.lock.Lock() | |
| 486 defer c.lock.Unlock() | |
| 487 c.log.Infof("auth: reading token from %s", c.path) | |
| 488 buf, err = ioutil.ReadFile(c.path) | |
| 489 if err != nil && os.IsNotExist(err) { | |
| 490 err = nil | |
| 491 } | |
| 492 return | |
| 493 } | |
| 494 | |
| 495 func (c *tokenCache) write(buf []byte) error { | |
| 496 c.lock.Lock() | |
| 497 defer c.lock.Unlock() | |
| 498 c.log.Infof("auth: writing token to %s", c.path) | |
| 499 err := os.MkdirAll(filepath.Dir(c.path), 0700) | |
| 500 if err != nil { | |
| 501 return err | |
| 502 } | |
| 503 // TODO(vadimsh): Make it atomic across multiple processes. | |
| 504 return ioutil.WriteFile(c.path, buf, 0600) | |
| 505 } | |
| 506 | |
| 507 func (c *tokenCache) clear() error { | |
| 508 c.lock.Lock() | |
| 509 defer c.lock.Unlock() | |
| 510 err := os.Remove(c.path) | |
| 511 if err != nil && !os.IsNotExist(err) { | |
| 512 return err | |
| 513 } | |
| 514 return nil | |
| 515 } | |
| 516 | |
| 517 //////////////////////////////////////////////////////////////////////////////// | |
| 518 // Utility functions. | |
| 519 | |
| 520 func cacheFileName(opts *Options) string { | |
| 521 // Construct a name of cache file from data that identifies requested | |
| 522 // credential, to allow multiple differently configured instances of | |
| 523 // Authenticator to coexist. | |
| 524 sum := sha1.New() | |
| 525 sum.Write([]byte(opts.Method)) | |
| 526 sum.Write([]byte{0}) | |
| 527 sum.Write([]byte(opts.ClientID)) | |
| 528 sum.Write([]byte{0}) | |
| 529 sum.Write([]byte(opts.ClientSecret)) | |
| 530 sum.Write([]byte{0}) | |
| 531 for _, scope := range opts.Scopes { | |
| 532 sum.Write([]byte(scope)) | |
| 533 sum.Write([]byte{0}) | |
| 534 } | |
| 535 sum.Write([]byte(opts.GCEAccountName)) | |
| 536 return hex.EncodeToString(sum.Sum(nil))[:16] | |
| 537 } | |
| 538 | |
| 539 // selectDefaultMethod is mocked in tests. | |
| 540 var selectDefaultMethod = func(opts *Options) Method { | |
| 541 if opts.ServiceAccountJSONPath != "" { | |
| 542 info, _ := os.Stat(opts.ServiceAccountJSONPath) | |
| 543 if info != nil && info.Mode().IsRegular() { | |
| 544 return ServiceAccountMethod | |
| 545 } | |
| 546 } | |
| 547 if metadata.OnGCE() { | |
| 548 return GCEMetadataMethod | |
| 549 } | |
| 550 return UserCredentialsMethod | |
| 551 } | |
| 552 | |
| 553 // secretsDir is mocked in tests. Called by publicly visible SecretsDir(). | |
| 554 var secretsDir = func() string { | |
| 555 usr, err := user.Current() | |
| 556 if err != nil { | |
| 557 panic(err.Error()) | |
| 558 } | |
| 559 // TODO(vadimsh): On Windows use SHGetFolderPath with CSIDL_LOCAL_APPDAT
A to | |
| 560 // locate a directory to store app files. | |
| 561 return filepath.Join(usr.HomeDir, ".config", "chrome_infra", "auth") | |
| 562 } | |
| 563 | |
| 564 // makeTokenProvider is mocked in tests. Called by ensureInitialized. | |
| 565 var makeTokenProvider = func(opts *Options) (internal.TokenProvider, error) { | |
| 566 switch opts.Method { | |
| 567 case UserCredentialsMethod: | |
| 568 return internal.NewUserAuthTokenProvider( | |
| 569 opts.Context, | |
| 570 opts.ClientID, | |
| 571 opts.ClientSecret, | |
| 572 opts.Scopes) | |
| 573 case ServiceAccountMethod: | |
| 574 return internal.NewServiceAccountTokenProvider( | |
| 575 opts.Context, | |
| 576 opts.ServiceAccountJSONPath, | |
| 577 opts.Scopes) | |
| 578 case GCEMetadataMethod: | |
| 579 return internal.NewGCETokenProvider( | |
| 580 opts.GCEAccountName, | |
| 581 opts.Scopes) | |
| 582 default: | |
| 583 return nil, fmt.Errorf("Unrecognized authentication method: %s",
opts.Method) | |
| 584 } | |
| 585 } | |
| 586 | |
| 587 // DefaultClient returns OAuth client_id and client_secret to use for 3 legged | |
| 588 // OAuth flow. Note that client_secret is not really a secret since it's | |
| 589 // hardcoded into the source code (and binaries). It's totally fine, as long | |
| 590 // as it's callback URI is configured to be 'localhost'. If someone decides to | |
| 591 // reuse such client_secret they have to run something on user's local machine | |
| 592 // to get the refresh_token. | |
| 593 func DefaultClient() (clientID string, clientSecret string) { | |
| 594 clientID = "446450136466-2hr92jrq8e6i4tnsa56b52vacp7t3936.apps.googleuse
rcontent.com" | |
| 595 clientSecret = "uBfbay2KCy9t4QveJ-dOqHtp" | |
| 596 return | |
| 597 } | |
| 598 | |
| 599 // SecretsDir returns an absolute path to a directory to keep secret files in. | |
| 600 func SecretsDir() string { | |
| 601 return secretsDir() | |
| 602 } | |
| OLD | NEW |