veyron/services/identity, veyron/tools/identity: Identity tool can request blessing with
caveats.

* New oauth flow to keep the blessing process secure from malicious identity tools.
* The oauth flow can be seen https://docs.google.com/a/google.com/document/d/1SRoc2cKE9iE1fWR7aSmMoccZoi4ZE8BQL7sr1LDNVkk/edit?usp=sharing.

Change-Id: I534f216953a1825cce899ffbfd82768db49b4108
diff --git a/tools/identity/bless.go b/tools/identity/bless.go
new file mode 100644
index 0000000..05d5546
--- /dev/null
+++ b/tools/identity/bless.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+	"crypto/rand"
+	"encoding/base64"
+	"fmt"
+	"html/template"
+	"net"
+	"net/http"
+	"net/url"
+	"os"
+	"os/exec"
+	"strings"
+
+	"veyron.io/veyron/veyron/services/identity/googleoauth"
+	"veyron.io/veyron/veyron2/vlog"
+)
+
+func getMacaroonForBlessRPC(blessServerURL string, blessedChan <-chan string) (<-chan string, error) {
+	// Setup a HTTP server to recieve a blessing macaroon from the identity server.
+	// Steps:
+	// 1. Generate a state token to be included in the HTTP request
+	//    (though, arguably, the random port assigment for the HTTP server is enough
+	//    for XSRF protection)
+	// 2. Setup a HTTP server which will receive the final blessing macaroon from the id server.
+	// 3. Print out the link (to start the auth flow) for the user to click.
+	// 4. Return the macaroon and the rpc object name(where to make the MacaroonBlesser.Bless RPC call)
+	//    in the "result" channel.
+	var stateBuf [32]byte
+	if _, err := rand.Read(stateBuf[:]); err != nil {
+		return nil, fmt.Errorf("failed to generate state token for OAuth: %v", err)
+	}
+	state := base64.URLEncoding.EncodeToString(stateBuf[:])
+
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	if err != nil {
+		return nil, fmt.Errorf("failed to setup authorization code interception server: %v", err)
+	}
+	result := make(chan string)
+
+	redirectURL := fmt.Sprintf("http://%s/macaroon", ln.Addr())
+	http.HandleFunc("/macaroon", func(w http.ResponseWriter, r *http.Request) {
+		w.Header().Set("Content-Type", "text/html")
+		tmplArgs := struct {
+			Blessing, ErrShort, ErrLong string
+		}{}
+		defer func() {
+			if len(tmplArgs.ErrShort) > 0 {
+				w.WriteHeader(http.StatusBadRequest)
+			}
+			if err := tmpl.Execute(w, tmplArgs); err != nil {
+				vlog.Info("Failed to render template:", err)
+			}
+		}()
+
+		toolState := r.FormValue("state")
+		if toolState != state {
+			tmplArgs.ErrShort = "Unexpected request"
+			tmplArgs.ErrLong = "Mismatched state parameter. Possible cross-site-request-forging?"
+			return
+		}
+		result <- r.FormValue("macaroon")
+		result <- r.FormValue("object_name")
+		defer close(result)
+		blessed, ok := <-blessedChan
+		if !ok {
+			tmplArgs.ErrShort = "No blessing received"
+			tmplArgs.ErrLong = "Unable to obtain blessing from the Veyron service"
+			return
+		}
+		tmplArgs.Blessing = blessed
+		ln.Close()
+	})
+	go http.Serve(ln, nil)
+
+	// Print the link to start the flow.
+	url, err := seekBlessingURL(blessServerURL, redirectURL, state)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create seekBlessingURL: %s", err)
+	}
+	fmt.Fprintln(os.Stderr, "Please visit the following URL to complete the blessing creation:")
+	fmt.Fprintln(os.Stderr, url)
+	// Make an attempt to start the browser as a convenience.
+	// If it fails, doesn't matter - the client can see the URL printed above.
+	// Use exec.Command().Start instead of exec.Command().Run since there is no
+	// need to wait for the command to return (and indeed on some window managers,
+	// the command will not exit until the browser is closed).
+	exec.Command(openCommand, url).Start()
+	return result, nil
+}
+
+func seekBlessingURL(blessServerURL, redirectURL, state string) (string, error) {
+	baseURL, err := url.Parse(joinURL(blessServerURL, googleoauth.SeekBlessingsRoute))
+	if err != nil {
+		return "", fmt.Errorf("failed to parse url: %v", err)
+	}
+	params := url.Values{}
+	params.Add("redirect_url", redirectURL)
+	params.Add("state", state)
+	baseURL.RawQuery = params.Encode()
+	return baseURL.String(), nil
+}
+
+func joinURL(baseURL, suffix string) string {
+	if !strings.HasSuffix(baseURL, "/") {
+		baseURL += "/"
+	}
+	return baseURL + suffix
+}
+
+var tmpl = template.Must(template.New("name").Parse(`<!doctype html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Veyron Identity: Google</title>
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+{{if .Blessing}}
+<!--Attempt to close the window. Though this script does not work on many browser configurations-->
+<script type="text/javascript">window.close();</script>
+{{end}}
+</head>
+<body>
+<div class="container">
+{{if .ErrShort}}
+<h1><span class="label label-danger">error</span>{{.ErrShort}}</h1>
+<div class="well">{{.ErrLong}}</div>
+{{else}}
+<h3>Received blessing: <tt>{{.Blessing}}</tt></h3>
+<div class="well">If the name is prefixed with "unknown/", ignore that. You can close this window, the command line tool has retrieved the blessing</div>
+{{end}}
+</div>
+</body>
+</html>`))
diff --git a/tools/identity/googleoauth.go b/tools/identity/googleoauth.go
deleted file mode 100644
index df07829..0000000
--- a/tools/identity/googleoauth.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package main
-
-import (
-	"crypto/rand"
-	"encoding/base64"
-	"fmt"
-	"html/template"
-	"net"
-	"net/http"
-	"os"
-	"os/exec"
-
-	"veyron.io/veyron/veyron/services/identity/googleoauth"
-	"veyron.io/veyron/veyron2/vlog"
-)
-
-func getOAuthAuthorizationCodeFromGoogle(clientID string, blessing <-chan string) (<-chan string, error) {
-	// Setup an HTTP server so that OAuth authorization codes can be intercepted.
-	// Steps:
-	// 1. Generate a state token to be included in the HTTP request
-	//    (though, arguably, the random port assignment for the HTTP server is
-	//    enough for XSRF protecetion)
-	// 2. Setup an HTTP server which will intercept redirect links from the OAuth
-	//    flow.
-	// 3. Print out the link for the user to click
-	// 4. Return the authorization code obtained from the redirect to the "result"
-	//    channel.
-	var stateBuf [32]byte
-	if _, err := rand.Read(stateBuf[:]); err != nil {
-		return nil, fmt.Errorf("failed to generate state token for OAuth: %v", err)
-	}
-	state := base64.URLEncoding.EncodeToString(stateBuf[:])
-
-	ln, err := net.Listen("tcp", "127.0.0.1:0")
-	if err != nil {
-		return nil, fmt.Errorf("failed to setup OAuth authorization code interception: %v", err)
-	}
-	redirectURL := fmt.Sprintf("http://%s", ln.Addr())
-	result := make(chan string, 1)
-	result <- redirectURL
-	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Set("Content-Type", "text/html")
-		tmplArgs := struct {
-			Blessing, ErrShort, ErrLong string
-		}{}
-		defer func() {
-			if len(tmplArgs.ErrShort) > 0 {
-				w.WriteHeader(http.StatusBadRequest)
-			}
-			if err := tmpl.Execute(w, tmplArgs); err != nil {
-				vlog.Info("Failed to render template:", err)
-			}
-		}()
-		if urlstate := r.FormValue("state"); urlstate != state {
-			tmplArgs.ErrShort = "Unexpected request"
-			tmplArgs.ErrLong = "Mismatched state parameter. Possible cross-site-request-forging?"
-			return
-		}
-		code := r.FormValue("code")
-		if len(code) == 0 {
-			tmplArgs.ErrShort = "No authorization code received"
-			tmplArgs.ErrLong = "Expected Google to issue an authorization code using 'code' as a URL parameter in the redirect"
-			return
-		}
-		ln.Close() // No need for the HTTP server anymore
-		result <- code
-		blessed, ok := <-blessing
-		defer close(result)
-		if !ok {
-			tmplArgs.ErrShort = "No blessing received"
-			tmplArgs.ErrLong = "Unable to obtain blessing from the Veyron service"
-			return
-		}
-		tmplArgs.Blessing = blessed
-		return
-	})
-	go http.Serve(ln, nil)
-
-	// Print out the link to start the OAuth flow (to STDERR so that STDOUT output contains
-	// only the final blessing) and try to open it in the browser as well.
-	//
-	// TODO(ashankar): Detect devices with limited I/O and then decide to use the device flow
-	// instead? See: https://developers.google.com/accounts/docs/OAuth2#device
-	url := googleoauth.NewOAuthConfig(clientID, "", redirectURL).AuthCodeURL(state)
-	fmt.Fprintln(os.Stderr, "Please visit the following URL to authenticate with Google:")
-	fmt.Fprintln(os.Stderr, url)
-	// Make an attempt to start the browser as a convenience.
-	// If it fails, doesn't matter - the client can see the URL printed above.
-	// Use exec.Command().Start instead of exec.Command().Run since there is no
-	// need to wait for the command to return (and indeed on some window managers,
-	// the command will not exit until the browser is closed).
-	exec.Command(openCommand, url).Start()
-	return result, nil
-}
-
-var tmpl = template.Must(template.New("name").Parse(`<!doctype html>
-<html>
-<head>
-<meta charset="UTF-8">
-<title>Veyron Identity: Google</title>
-<meta name="viewport" content="width=device-width, initial-scale=1.0">
-<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
-{{if .Blessing}}
-<!--Attempt to close the window. Though this script does not work on many browser configurations-->
-<script type="text/javascript">window.close();</script>
-{{end}}
-</head>
-<body>
-<div class="container">
-{{if .ErrShort}}
-<h1><span class="label label-danger">error</span>{{.ErrShort}}</h1>
-<div class="well">{{.ErrLong}}</div>
-{{else}}
-<h3>Received blessing: <tt>{{.Blessing}}</tt></h3>
-<div class="well">If the name is prefixed with "unknown/", ignore that. You can close this window, the command line tool has retrieved the blessing</div>
-{{end}}
-</div>
-</body>
-</html>`))
diff --git a/tools/identity/main.go b/tools/identity/main.go
index 35b5379..3519345 100644
--- a/tools/identity/main.go
+++ b/tools/identity/main.go
@@ -316,12 +316,12 @@
 
 			blessedChan := make(chan string)
 			defer close(blessedChan)
-			authcodeChan, err := getOAuthAuthorizationCodeFromGoogle(flagSeekBlessingOAuthClientID, blessedChan)
+			macaroonChan, err := getMacaroonForBlessRPC(flagSeekBlessingFrom, blessedChan)
 			if err != nil {
 				return fmt.Errorf("failed to get authorization code from Google: %v", err)
 			}
-			redirectURL := <-authcodeChan
-			authcode := <-authcodeChan
+			macaroon := <-macaroonChan
+			service := <-macaroonChan
 
 			ctx, cancel := r.NewContext().WithTimeout(time.Minute)
 			defer cancel()
@@ -330,13 +330,12 @@
 			const maxWait = 20 * time.Second
 			var reply vdlutil.Any
 			for {
-				blesser, err := identity.BindOAuthBlesser(flagSeekBlessingFrom, r.Client())
+				blesser, err := identity.BindMacaroonBlesser(service, r.Client())
 				if err == nil {
-
-					reply, err = blesser.BlessUsingAuthorizationCode(ctx, authcode, redirectURL)
+					reply, err = blesser.Bless(ctx, macaroon)
 				}
 				if err != nil {
-					vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", flagSeekBlessingFrom, err, wait)
+					vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", service, err, wait)
 					time.Sleep(wait)
 					if wait = wait + 2*time.Second; wait > maxWait {
 						wait = maxWait
@@ -348,7 +347,7 @@
 					return fmt.Errorf("received %T, want security.PublicID", reply)
 				}
 				if id, err = id.Derive(blessed); err != nil {
-					return fmt.Errorf("received incompatible blessing from %q: %v", flagSeekBlessingFrom, err)
+					return fmt.Errorf("received incompatible blessing from %q: %v", service, err)
 				}
 				output, err := util.Base64VomEncode(id)
 				if err != nil {
@@ -356,8 +355,8 @@
 				}
 				fmt.Println(output)
 				blessedChan <- fmt.Sprint(blessed)
-				// Wait for getOAuthAuthenticationCodeFromGoogle to clean up:
-				<-authcodeChan
+				// Wait for getTokenForBlessRPC to clean up:
+				<-macaroonChan
 				return nil
 			}
 		},
@@ -369,8 +368,7 @@
 	cmdBless.Flags.StringVar(&flagBlessWith, "with", "", "Path to file containing identity to bless with (or - for STDIN)")
 	cmdBless.Flags.DurationVar(&flagBlessFor, "for", 365*24*time.Hour, "Expiry time of blessing (defaults to 1 year)")
 	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFor, "for", "", "Path to file containing identity to bless (or - for STDIN)")
-	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingOAuthClientID, "clientid", "761523829214-4ms7bae18ef47j6590u9ncs19ffuo7b3.apps.googleusercontent.com", "OAuth client ID used to make OAuth request for an authorization code")
-	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFrom, "from", "/proxy.envyor.com:8101/identity/veyron-test/google", "Object name of Veyron service running the identity.OAuthBlesser service to seek blessings from")
+	cmdSeekBlessing.Flags.StringVar(&flagSeekBlessingFrom, "from", "http://proxy.envyor.com:8125/google", "URL to use to begin the seek blessings process")
 
 	(&cmdline.Command{
 		Name:  "identity",