Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Unified Diff: impl/prod/devserver.go

Issue 2220193002: Implement info.ServiceAccount() on devserver via info.AccessToken(...). (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/gae@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | impl/prod/info.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: impl/prod/devserver.go
diff --git a/impl/prod/devserver.go b/impl/prod/devserver.go
new file mode 100644
index 0000000000000000000000000000000000000000..ef91dc2c4978019d1b7ef5f6c6b5bb195e918195
--- /dev/null
+++ b/impl/prod/devserver.go
@@ -0,0 +1,89 @@
+// Copyright 2016 The LUCI Authors. All rights reserved.
+// Use of this source code is governed under the Apache License, Version 2.0
+// that can be found in the LICENSE file.
+
+package prod
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "sync"
+
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/log"
+ "google.golang.org/appengine/urlfetch"
+)
+
+var devAccountCache struct {
+ once sync.Once
+ account string
+ err error
+}
+
+// developerAccount is used on dev server to get account name matching
+// the OAuth token produced by AccessToken.
+//
+// On dev server ServiceAccount returns empty string, but AccessToken(...) works
+// and returns developer's token (the one configured with "gcloud auth"). We can
+// use it to get the matching account name.
+func developerAccount(gaeCtx context.Context) (string, error) {
+ if !appengine.IsDevAppServer() {
+ panic("developerAccount must not be used outside of devserver")
+ }
+ devAccountCache.once.Do(func() {
+ devAccountCache.account, devAccountCache.err = fetchDevAccount(gaeCtx)
+ if devAccountCache.err == nil {
+ log.Debugf(gaeCtx, "Devserver is running as %q", devAccountCache.account)
+ } else {
+ log.Errorf(gaeCtx, "Failed to fetch account name associated with AccessToken - %s", devAccountCache.err)
+ }
+ })
+ return devAccountCache.account, devAccountCache.err
+}
+
+// fetchDevAccount grabs an access token and calls Google API to get associated
+// email.
+func fetchDevAccount(gaeCtx context.Context) (string, error) {
+ // Grab the developer's token from devserver.
+ tok, _, err := appengine.AccessToken(gaeCtx, "https://www.googleapis.com/auth/userinfo.email")
+ if err != nil {
+ return "", err
+ }
+
+ // Fetch the info dict associated with the token.
+ client := http.Client{
+ Transport: &urlfetch.Transport{Context: gaeCtx},
+ }
+ resp, err := client.Get("https://www.googleapis.com/oauth2/v3/tokeninfo?access_token=" + url.QueryEscape(tok))
+ if err != nil {
+ return "", err
+ }
+ defer func() {
+ ioutil.ReadAll(resp.Body)
+ resp.Body.Close()
+ }()
+ if resp.StatusCode >= 500 {
+ return "", fmt.Errorf("devserver: transient error when validating token (HTTP %d)", resp.StatusCode)
+ }
+
+ // There's more stuff in the reply, we don't need it.
+ var tokenInfo struct {
+ Email string `json:"email"`
+ Error string `json:"error_description"`
+ }
+ if err := json.NewDecoder(resp.Body).Decode(&tokenInfo); err != nil {
+ return "", fmt.Errorf("devserver: failed to deserialize token info JSON - %s", err)
+ }
+ switch {
+ case tokenInfo.Error != "":
+ return "", fmt.Errorf("devserver: bad token - %s", tokenInfo.Error)
+ case tokenInfo.Email == "":
+ return "", fmt.Errorf("devserver: token is not associated with an email")
+ }
+ return tokenInfo.Email, nil
+}
« no previous file with comments | « no previous file | impl/prod/info.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698