veyron/services/identity: Support for obtaining blessing given an OAuth access token.

Motivation:
The Veyron chrome extension will use the Chrome identity API to obtain an
access token (https://developer.chrome.com/apps/identity#method-getAuthToken)
and then provide that access token to the veyron identity server to obtain
a blessing.

Nick is working on the chrome extension in https://veyron-review.googlesource.com/#/c/3580/,
while this change updates the identity service so that the access token
can be exchanged for a blessing.

Change-Id: I0cd67ed7d523e80992ff5161665211668f6f259d
diff --git a/services/identity/blesser/oauth.go b/services/identity/blesser/oauth.go
index c2173e2..90d3875 100644
--- a/services/identity/blesser/oauth.go
+++ b/services/identity/blesser/oauth.go
@@ -1,7 +1,9 @@
 package blesser
 
 import (
+	"encoding/json"
 	"fmt"
+	"net/http"
 	"strings"
 	"time"
 
@@ -10,13 +12,33 @@
 	"veyron2"
 	"veyron2/ipc"
 	"veyron2/vdl/vdlutil"
+	"veyron2/vlog"
 )
 
 type googleOAuth struct {
-	rt                     veyron2.Runtime
-	clientID, clientSecret string
-	duration               time.Duration
-	domain                 string
+	rt                veyron2.Runtime
+	authcodeClient    struct{ ID, Secret string }
+	accessTokenClient struct{ ID string }
+	duration          time.Duration
+	domain            string
+}
+
+// GoogleParams represents all the parameters provided to NewGoogleOAuthBlesserServer
+type GoogleParams struct {
+	// The Veyron runtime to use
+	R veyron2.Runtime
+	// The OAuth client ID and secret for clients of the BlessUsingAuthorizationCode RPC
+	AuthorizationCodeClient struct {
+		ID, Secret string
+	}
+	// The OAuth client ID for the chrome-extension that will make BlessUsingAccessToken RPCs.
+	AccessTokenClient struct {
+		ID string
+	}
+	// The duration for which blessings will be valid.
+	BlessingDuration time.Duration
+	// If non-empty, only email addresses from this domain will be blessed.
+	DomainRestriction string
 }
 
 // NewGoogleOAuthBlesserServer provides an identity.OAuthBlesserService that uses authorization
@@ -27,20 +49,72 @@
 //
 // Blessings generated by this server expire after duration. If domain is non-empty, then blessings
 // are generated only for email addresses from that domain.
-func NewGoogleOAuthBlesserServer(rt veyron2.Runtime, clientID, clientSecret string, duration time.Duration, domain string) interface{} {
-	return identity.NewServerOAuthBlesser(&googleOAuth{rt, clientID, clientSecret, duration, domain})
+func NewGoogleOAuthBlesserServer(p GoogleParams) interface{} {
+	b := &googleOAuth{
+		rt:       p.R,
+		duration: p.BlessingDuration,
+		domain:   p.DomainRestriction,
+	}
+	b.authcodeClient.ID = p.AuthorizationCodeClient.ID
+	b.authcodeClient.Secret = p.AuthorizationCodeClient.Secret
+	b.accessTokenClient.ID = p.AccessTokenClient.ID
+	return identity.NewServerOAuthBlesser(b)
 }
 
-func (b *googleOAuth) Bless(ctx ipc.ServerContext, authcode, redirectURL string) (vdlutil.Any, error) {
-	config := googleoauth.NewOAuthConfig(b.clientID, b.clientSecret, redirectURL)
+func (b *googleOAuth) BlessUsingAuthorizationCode(ctx ipc.ServerContext, authcode, redirectURL string) (vdlutil.Any, error) {
+	if len(b.authcodeClient.ID) == 0 {
+		return nil, fmt.Errorf("server not configured for blessing based on authorization codes")
+	}
+	config := googleoauth.NewOAuthConfig(b.authcodeClient.ID, b.authcodeClient.Secret, redirectURL)
 	name, err := googleoauth.ExchangeAuthCodeForEmail(config, authcode)
 	if err != nil {
 		return nil, err
 	}
+	return b.bless(ctx, name)
+}
+
+func (b *googleOAuth) BlessUsingAccessToken(ctx ipc.ServerContext, accesstoken string) (vdlutil.Any, error) {
+	if len(b.accessTokenClient.ID) == 0 {
+		return nil, fmt.Errorf("server not configured for blessing based on access tokens")
+	}
+	// URL from: https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
+	tokeninfo, err := http.Get("https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=" + accesstoken)
+	if err != nil {
+		return nil, fmt.Errorf("unable to use token: %v", err)
+	}
+	if tokeninfo.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf("unable to verify access token: %v", tokeninfo.StatusCode)
+	}
+	// tokeninfo contains a JSON-encoded struct
+	var token struct {
+		IssuedTo      string `json:"issued_to"`
+		Audience      string `json:"audience"`
+		UserID        string `json:"user_id"`
+		Scope         string `json:"scope"`
+		ExpiresIn     int64  `json:"expires_in"`
+		Email         string `json:"email"`
+		VerifiedEmail bool   `json:"verified_email"`
+		AccessType    string `json:"access_type"`
+	}
+	if err := json.NewDecoder(tokeninfo.Body).Decode(&token); err != nil {
+		return "", fmt.Errorf("invalid JSON response from Google's tokeninfo API: %v", err)
+	}
+	if token.Audience != b.accessTokenClient.ID {
+		vlog.Infof("Got access token [%+v], wanted client id %v", token, b.accessTokenClient.ID)
+		return "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
+	}
+	if !token.VerifiedEmail {
+		return nil, fmt.Errorf("email not verified")
+	}
+	return b.bless(ctx, token.Email)
+}
+
+func (b *googleOAuth) bless(ctx ipc.ServerContext, name string) (vdlutil.Any, error) {
 	if len(b.domain) > 0 && !strings.HasSuffix(name, "@"+b.domain) {
 		return nil, fmt.Errorf("blessings for %q are not allowed", name)
 	}
 	self := b.rt.Identity()
+	var err error
 	// Use the blessing that was used to authenticate with the client to bless it.
 	if self, err = self.Derive(ctx.LocalID()); err != nil {
 		return nil, err
diff --git a/services/identity/identity.vdl b/services/identity/identity.vdl
index c803076..1733c27 100644
--- a/services/identity/identity.vdl
+++ b/services/identity/identity.vdl
@@ -2,17 +2,36 @@
 package identity
 
 
-// OAuthBlesser exchanges the provided authorization code for an email addres
-// from an OAuth-based identity provider and uses the email address as the
-// name to bless the client with.
+// OAuthBlesser exchanges OAuth authorization codes OR access tokens for
+// an email address from an OAuth-based identity provider and uses the email
+// address obtained to bless the client.
 //
-// The redirect URL used to obtain the authorization code must also be
-// provided in order to ensure a successful exchange.
+// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),
+// though the Google implementation also has informative documentation at
+// https://developers.google.com/accounts/docs/OAuth2
+//
+// WARNING: There is no binding between the channel over which the
+// authorization code or access token was obtained (typically https)
+// and the channel used to make the RPC (a veyron virtual circuit).
+// Thus, if Mallory possesses the authorization code or access token
+// associated with Alice's account, she may be able to obtain a blessing
+// with Alice's name on it.
+//
+// TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
+// then the "any" should be replaced by:
+// OneOf<wire.ChainPublicID, []wire.ChainPublicID>
+// where wire is from:
+// import "veyron2/security/wire"
 type OAuthBlesser interface {
-  // TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
-  // then the "any" should be replaced by:
-  // OneOf<wire.ChainPublicID, []wire.ChainPublicID>
-  // where wire is from:
-  // import "veyron2/security/wire"
-  Bless(authcode, redirecturl string) (blessing any, err error)
+  // BlessUsingAuthorizationCode exchanges the provided authorization code
+  // for an access token and then uses that access token to obtain an
+  // email address.
+  //
+  // The redirect URL used to obtain the authorization code must also
+  // be provided.
+  BlessUsingAuthorizationCode(authcode, redirecturl string) (blessing any, err error)
+
+  // BlessUsingAccessToken uses the provided access token to obtain the email
+  // address and returns a blessing.
+  BlessUsingAccessToken(token string) (blessing any, err error)
 }
\ No newline at end of file
diff --git a/services/identity/identity.vdl.go b/services/identity/identity.vdl.go
index 43d1fc1..2d18578 100644
--- a/services/identity/identity.vdl.go
+++ b/services/identity/identity.vdl.go
@@ -18,22 +18,40 @@
 // It corrects a bug where _gen_wiretype is unused in VDL pacakges where only bootstrap types are used on interfaces.
 const _ = _gen_wiretype.TypeIDInvalid
 
-// OAuthBlesser exchanges the provided authorization code for an email addres
-// from an OAuth-based identity provider and uses the email address as the
-// name to bless the client with.
+// OAuthBlesser exchanges OAuth authorization codes OR access tokens for
+// an email address from an OAuth-based identity provider and uses the email
+// address obtained to bless the client.
 //
-// The redirect URL used to obtain the authorization code must also be
-// provided in order to ensure a successful exchange.
+// OAuth is described in RFC 6749 (http://tools.ietf.org/html/rfc6749),
+// though the Google implementation also has informative documentation at
+// https://developers.google.com/accounts/docs/OAuth2
+//
+// WARNING: There is no binding between the channel over which the
+// authorization code or access token was obtained (typically https)
+// and the channel used to make the RPC (a veyron virtual circuit).
+// Thus, if Mallory possesses the authorization code or access token
+// associated with Alice's account, she may be able to obtain a blessing
+// with Alice's name on it.
+//
+// TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
+// then the "any" should be replaced by:
+// OneOf<wire.ChainPublicID, []wire.ChainPublicID>
+// where wire is from:
+// import "veyron2/security/wire"
 // OAuthBlesser is the interface the client binds and uses.
 // OAuthBlesser_ExcludingUniversal is the interface without internal framework-added methods
 // to enable embedding without method collisions.  Not to be used directly by clients.
 type OAuthBlesser_ExcludingUniversal interface {
-	// TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
-	// then the "any" should be replaced by:
-	// OneOf<wire.ChainPublicID, []wire.ChainPublicID>
-	// where wire is from:
-	// import "veyron2/security/wire"
-	Bless(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
+	// BlessUsingAuthorizationCode exchanges the provided authorization code
+	// for an access token and then uses that access token to obtain an
+	// email address.
+	//
+	// The redirect URL used to obtain the authorization code must also
+	// be provided.
+	BlessUsingAuthorizationCode(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
+	// BlessUsingAccessToken uses the provided access token to obtain the email
+	// address and returns a blessing.
+	BlessUsingAccessToken(ctx _gen_context.T, token string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error)
 }
 type OAuthBlesser interface {
 	_gen_ipc.UniversalServiceMethods
@@ -43,12 +61,16 @@
 // OAuthBlesserService is the interface the server implements.
 type OAuthBlesserService interface {
 
-	// TODO(ashankar,toddw): Once the "OneOf" type becomes available in VDL,
-	// then the "any" should be replaced by:
-	// OneOf<wire.ChainPublicID, []wire.ChainPublicID>
-	// where wire is from:
-	// import "veyron2/security/wire"
-	Bless(context _gen_ipc.ServerContext, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error)
+	// BlessUsingAuthorizationCode exchanges the provided authorization code
+	// for an access token and then uses that access token to obtain an
+	// email address.
+	//
+	// The redirect URL used to obtain the authorization code must also
+	// be provided.
+	BlessUsingAuthorizationCode(context _gen_ipc.ServerContext, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error)
+	// BlessUsingAccessToken uses the provided access token to obtain the email
+	// address and returns a blessing.
+	BlessUsingAccessToken(context _gen_ipc.ServerContext, token string) (reply _gen_vdlutil.Any, err error)
 }
 
 // BindOAuthBlesser returns the client stub implementing the OAuthBlesser
@@ -92,9 +114,20 @@
 	name   string
 }
 
-func (__gen_c *clientStubOAuthBlesser) Bless(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
+func (__gen_c *clientStubOAuthBlesser) BlessUsingAuthorizationCode(ctx _gen_context.T, authcode string, redirecturl string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
 	var call _gen_ipc.Call
-	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "Bless", []interface{}{authcode, redirecturl}, opts...); err != nil {
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "BlessUsingAuthorizationCode", []interface{}{authcode, redirecturl}, opts...); err != nil {
+		return
+	}
+	if ierr := call.Finish(&reply, &err); ierr != nil {
+		err = ierr
+	}
+	return
+}
+
+func (__gen_c *clientStubOAuthBlesser) BlessUsingAccessToken(ctx _gen_context.T, token string, opts ..._gen_ipc.CallOpt) (reply _gen_vdlutil.Any, err error) {
+	var call _gen_ipc.Call
+	if call, err = __gen_c.client.StartCall(ctx, __gen_c.name, "BlessUsingAccessToken", []interface{}{token}, opts...); err != nil {
 		return
 	}
 	if ierr := call.Finish(&reply, &err); ierr != nil {
@@ -148,7 +181,9 @@
 	// Note: This exhibits some weird behavior like returning a nil error if the method isn't found.
 	// This will change when it is replaced with Signature().
 	switch method {
-	case "Bless":
+	case "BlessUsingAuthorizationCode":
+		return []interface{}{}, nil
+	case "BlessUsingAccessToken":
 		return []interface{}{}, nil
 	default:
 		return nil, nil
@@ -157,7 +192,16 @@
 
 func (__gen_s *ServerStubOAuthBlesser) Signature(call _gen_ipc.ServerCall) (_gen_ipc.ServiceSignature, error) {
 	result := _gen_ipc.ServiceSignature{Methods: make(map[string]_gen_ipc.MethodSignature)}
-	result.Methods["Bless"] = _gen_ipc.MethodSignature{
+	result.Methods["BlessUsingAccessToken"] = _gen_ipc.MethodSignature{
+		InArgs: []_gen_ipc.MethodArgument{
+			{Name: "token", Type: 3},
+		},
+		OutArgs: []_gen_ipc.MethodArgument{
+			{Name: "blessing", Type: 65},
+			{Name: "err", Type: 66},
+		},
+	}
+	result.Methods["BlessUsingAuthorizationCode"] = _gen_ipc.MethodSignature{
 		InArgs: []_gen_ipc.MethodArgument{
 			{Name: "authcode", Type: 3},
 			{Name: "redirecturl", Type: 3},
@@ -192,7 +236,12 @@
 	return
 }
 
-func (__gen_s *ServerStubOAuthBlesser) Bless(call _gen_ipc.ServerCall, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error) {
-	reply, err = __gen_s.service.Bless(call, authcode, redirecturl)
+func (__gen_s *ServerStubOAuthBlesser) BlessUsingAuthorizationCode(call _gen_ipc.ServerCall, authcode string, redirecturl string) (reply _gen_vdlutil.Any, err error) {
+	reply, err = __gen_s.service.BlessUsingAuthorizationCode(call, authcode, redirecturl)
+	return
+}
+
+func (__gen_s *ServerStubOAuthBlesser) BlessUsingAccessToken(call _gen_ipc.ServerCall, token string) (reply _gen_vdlutil.Any, err error) {
+	reply, err = __gen_s.service.BlessUsingAccessToken(call, token)
 	return
 }
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index 39d3b9d..9c592b8 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -31,8 +31,10 @@
 	host          = flag.String("host", defaultHost(), "Hostname the HTTP server listens on. This can be the name of the host running the webserver, but if running behind a NAT or load balancer, this should be the host name that clients will connect to. For example, if set to 'x.com', Veyron identities will have the IssuerName set to 'x.com' and clients can expect to find the public key of the signer at 'x.com/pubkey/'.")
 	minExpiryDays = flag.Int("min_expiry_days", 365, "Minimum expiry time (in days) of identities issued by this server")
 
-	googleConfigWeb       = flag.String("google_config_web", "", "Path to the JSON-encoded file containing the ClientID for web applications registered with the Google Developer Console. (Use the 'Download JSON' link on the Google APIs console).")
-	googleConfigInstalled = flag.String("google_config_installed", "", "Path to the JSON-encoded file containing the ClientID for installed applications registered with the Google Developer console. (Use the 'Download JSON' link on the Google APIs console).")
+	// Configuration for various Google OAuth-based clients.
+	googleConfigWeb       = flag.String("google_config_web", "", "Path to JSON-encoded OAuth client configuration for the web application for generating PrivateIDs (Use the 'Download JSON' link on the Google APIs console).")
+	googleConfigInstalled = flag.String("google_config_installed", "", "Path to the JSON-encoded OAuth client configuration for installed client applications that obtain blessings (via the OAuthBlesser.BlessUsingAuthorizationCode RPC) from this server (like the 'identity' command like tool and its 'seekblessing' command.")
+	googleConfigChrome    = flag.String("google_config_chrome", "", "Path to the JSON-encoded OAuth client configuration for Chrome browser applications that obtain blessings from this server (via the OAuthBlesser.BlessUsingAccessToken RPC) from this server.")
 	googleDomain          = flag.String("google_domain", "", "An optional domain name. When set, only email addresses from this domain are allowed to authenticate via Google OAuth")
 )
 
@@ -50,12 +52,11 @@
 	http.HandleFunc("/bless/", handlers.Bless) // use a provided PrivateID to bless a provided PublicID
 
 	// Google OAuth
-	var ipcServer ipc.Server
-	if enabled, clientID, clientSecret := enableGoogleOAuth(*googleConfigInstalled); enabled {
-		var err error
-		if ipcServer, err = setupGoogleBlessingServer(r, clientID, clientSecret); err != nil {
-			vlog.Fatalf("failed to setup veyron services for blessing: %v", err)
-		}
+	ipcServer, err := setupGoogleBlessingServer(r)
+	if err != nil {
+		vlog.Fatalf("Failed to setup veyron services for blessing: %v", err)
+	}
+	if ipcServer != nil {
 		defer ipcServer.Stop()
 	}
 	if enabled, clientID, clientSecret := enableGoogleOAuth(*googleConfigWeb); enabled {
@@ -91,7 +92,25 @@
 	<-signals.ShutdownOnSignals()
 }
 
-func setupGoogleBlessingServer(r veyron2.Runtime, clientID, clientSecret string) (ipc.Server, error) {
+func setupGoogleBlessingServer(r veyron2.Runtime) (ipc.Server, error) {
+	var enable bool
+	params := blesser.GoogleParams{
+		R:                 r,
+		BlessingDuration:  time.Duration(*minExpiryDays) * 24 * time.Hour,
+		DomainRestriction: *googleDomain,
+	}
+	if authcode, clientID, clientSecret := enableGoogleOAuth(*googleConfigInstalled); authcode {
+		enable = true
+		params.AuthorizationCodeClient.ID = clientID
+		params.AuthorizationCodeClient.Secret = clientSecret
+	}
+	if accesstoken, clientID, _ := enableGoogleOAuth(*googleConfigChrome); accesstoken {
+		enable = true
+		params.AccessTokenClient.ID = clientID
+	}
+	if !enable {
+		return nil, nil
+	}
 	server, err := r.NewServer()
 	if err != nil {
 		return nil, fmt.Errorf("failed to create new ipc.Server: %v", err)
@@ -102,7 +121,7 @@
 	}
 	allowEveryoneACL := security.ACL{security.AllPrincipals: security.AllLabels}
 	objectname := fmt.Sprintf("identity/%s/google", r.Identity().PublicID().Names()[0])
-	if err := server.Serve(objectname, ipc.SoloDispatcher(blesser.NewGoogleOAuthBlesserServer(r, clientID, clientSecret, time.Duration(*minExpiryDays)*24*time.Hour, *googleDomain), security.NewACLAuthorizer(allowEveryoneACL))); err != nil {
+	if err := server.Serve(objectname, ipc.SoloDispatcher(blesser.NewGoogleOAuthBlesserServer(params), security.NewACLAuthorizer(allowEveryoneACL))); err != nil {
 		return nil, fmt.Errorf("failed to start Veyron service: %v", err)
 	}
 	vlog.Infof("Google blessing service enabled at endpoint %v and name %q", ep, objectname)
diff --git a/tools/identity/main.go b/tools/identity/main.go
index bdc007f..57659d3 100644
--- a/tools/identity/main.go
+++ b/tools/identity/main.go
@@ -214,7 +214,7 @@
 			for {
 				blesser, err := identity.BindOAuthBlesser(flagSeekBlessingFrom, r.Client())
 				if err == nil {
-					reply, err = blesser.Bless(r.NewContext(), authcode, redirectURL)
+					reply, err = blesser.BlessUsingAuthorizationCode(r.NewContext(), authcode, redirectURL)
 				}
 				if err != nil {
 					vlog.Infof("Failed to get blessing from %q: %v, will try again in %v", flagSeekBlessingFrom, err, wait)