OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package prod |
| 6 |
| 7 import ( |
| 8 "encoding/json" |
| 9 "fmt" |
| 10 "io/ioutil" |
| 11 "net/http" |
| 12 "net/url" |
| 13 "sync" |
| 14 |
| 15 "golang.org/x/net/context" |
| 16 |
| 17 "google.golang.org/appengine" |
| 18 "google.golang.org/appengine/log" |
| 19 "google.golang.org/appengine/urlfetch" |
| 20 ) |
| 21 |
| 22 var devAccountCache struct { |
| 23 once sync.Once |
| 24 account string |
| 25 err error |
| 26 } |
| 27 |
| 28 // developerAccount is used on dev server to get account name matching |
| 29 // the OAuth token produced by AccessToken. |
| 30 // |
| 31 // On dev server ServiceAccount returns empty string, but AccessToken(...) works |
| 32 // and returns developer's token (the one configured with "gcloud auth"). We can |
| 33 // use it to get the matching account name. |
| 34 func developerAccount(gaeCtx context.Context) (string, error) { |
| 35 if !appengine.IsDevAppServer() { |
| 36 panic("developerAccount must not be used outside of devserver") |
| 37 } |
| 38 devAccountCache.once.Do(func() { |
| 39 devAccountCache.account, devAccountCache.err = fetchDevAccount(g
aeCtx) |
| 40 if devAccountCache.err == nil { |
| 41 log.Debugf(gaeCtx, "Devserver is running as %q", devAcco
untCache.account) |
| 42 } else { |
| 43 log.Errorf(gaeCtx, "Failed to fetch account name associa
ted with AccessToken - %s", devAccountCache.err) |
| 44 } |
| 45 }) |
| 46 return devAccountCache.account, devAccountCache.err |
| 47 } |
| 48 |
| 49 // fetchDevAccount grabs an access token and calls Google API to get associated |
| 50 // email. |
| 51 func fetchDevAccount(gaeCtx context.Context) (string, error) { |
| 52 // Grab the developer's token from devserver. |
| 53 tok, _, err := appengine.AccessToken(gaeCtx, "https://www.googleapis.com
/auth/userinfo.email") |
| 54 if err != nil { |
| 55 return "", err |
| 56 } |
| 57 |
| 58 // Fetch the info dict associated with the token. |
| 59 client := http.Client{ |
| 60 Transport: &urlfetch.Transport{Context: gaeCtx}, |
| 61 } |
| 62 resp, err := client.Get("https://www.googleapis.com/oauth2/v3/tokeninfo?
access_token=" + url.QueryEscape(tok)) |
| 63 if err != nil { |
| 64 return "", err |
| 65 } |
| 66 defer func() { |
| 67 ioutil.ReadAll(resp.Body) |
| 68 resp.Body.Close() |
| 69 }() |
| 70 if resp.StatusCode >= 500 { |
| 71 return "", fmt.Errorf("devserver: transient error when validatin
g token (HTTP %d)", resp.StatusCode) |
| 72 } |
| 73 |
| 74 // There's more stuff in the reply, we don't need it. |
| 75 var tokenInfo struct { |
| 76 Email string `json:"email"` |
| 77 Error string `json:"error_description"` |
| 78 } |
| 79 if err := json.NewDecoder(resp.Body).Decode(&tokenInfo); err != nil { |
| 80 return "", fmt.Errorf("devserver: failed to deserialize token in
fo JSON - %s", err) |
| 81 } |
| 82 switch { |
| 83 case tokenInfo.Error != "": |
| 84 return "", fmt.Errorf("devserver: bad token - %s", tokenInfo.Err
or) |
| 85 case tokenInfo.Email == "": |
| 86 return "", fmt.Errorf("devserver: token is not associated with a
n email") |
| 87 } |
| 88 return tokenInfo.Email, nil |
| 89 } |
OLD | NEW |