veyron/services/identity: Add Android ClientID config as an option to identityd.

This is required for the Android OAuth code to work.

Change-Id: If3689b6d4101e763403c2b0e1375866bd83b408d
diff --git a/services/identity/blesser/oauth.go b/services/identity/blesser/oauth.go
index e865b30..fb35bb0 100644
--- a/services/identity/blesser/oauth.go
+++ b/services/identity/blesser/oauth.go
@@ -21,7 +21,7 @@
 type googleOAuth struct {
 	rt                 veyron2.Runtime
 	authcodeClient     struct{ ID, Secret string }
-	accessTokenClient  struct{ ID string }
+	accessTokenClients []struct{ ID string }
 	duration           time.Duration
 	domain             string
 	dischargerLocation string
@@ -36,8 +36,8 @@
 	AuthorizationCodeClient struct {
 		ID, Secret string
 	}
-	// The OAuth client ID for the chrome-extension that will make BlessUsingAccessToken RPCs.
-	AccessTokenClient struct {
+	// The OAuth client IDs for the clients of the BlessUsingAccessToken RPCs.
+	AccessTokenClients []struct {
 		ID string
 	}
 	// The duration for which blessings will be valid.
@@ -68,7 +68,7 @@
 	}
 	b.authcodeClient.ID = p.AuthorizationCodeClient.ID
 	b.authcodeClient.Secret = p.AuthorizationCodeClient.Secret
-	b.accessTokenClient.ID = p.AccessTokenClient.ID
+	b.accessTokenClients = p.AccessTokenClients
 	return identity.NewServerOAuthBlesser(b)
 }
 
@@ -85,7 +85,7 @@
 }
 
 func (b *googleOAuth) BlessUsingAccessToken(ctx ipc.ServerContext, accesstoken string) (vdlutil.Any, error) {
-	if len(b.accessTokenClient.ID) == 0 {
+	if len(b.accessTokenClients) == 0 {
 		return nil, fmt.Errorf("server not configured for blessing based on access tokens")
 	}
 	// URL from: https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken
@@ -110,8 +110,15 @@
 	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)
+	audienceMatch := false
+	for _, c := range b.accessTokenClients {
+		if token.Audience == c.ID {
+			audienceMatch = true
+			break
+		}
+	}
+	if !audienceMatch {
+		vlog.Infof("Got access token [%+v], wanted one of client ids %v", token, b.accessTokenClients)
 		return "", fmt.Errorf("token not meant for this purpose, confused deputy? https://developers.google.com/accounts/docs/OAuth2UserAgent#validatetoken")
 	}
 	if !token.VerifiedEmail {
@@ -122,7 +129,7 @@
 
 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)
+		return nil, fmt.Errorf("blessings for name %q are not allowed due to domain restriction", name)
 	}
 	self := b.rt.Identity()
 	var err error
diff --git a/services/identity/googleoauth/utils.go b/services/identity/googleoauth/utils.go
index dbd5026..6f5230a 100644
--- a/services/identity/googleoauth/utils.go
+++ b/services/identity/googleoauth/utils.go
@@ -6,32 +6,60 @@
 	"io"
 )
 
+// ClientIDFromJSON parses JSON-encoded API access information in 'r' and returns
+// the extracted ClientID.
+// This JSON-encoded data is typically available as a download from the Google
+// API Access console for your application
+// (https://code.google.com/apis/console).
+func ClientIDFromJSON(r io.Reader) (id string, err error) {
+	var data map[string]interface{}
+	var typ string
+	if data, typ, err = decodeAccessMapFromJSON(r); err != nil {
+		return
+	}
+	var ok bool
+	if id, ok = data["client_id"].(string); !ok {
+		err = fmt.Errorf("%s.client_id not found", typ)
+		return
+	}
+	return
+}
+
 // ClientIDAndSecretFromJSON parses JSON-encoded API access information in 'r'
 // and returns the extracted ClientID and ClientSecret.
 // This JSON-encoded data is typically available as a download from the Google
 // API Access console for your application
 // (https://code.google.com/apis/console).
 func ClientIDAndSecretFromJSON(r io.Reader) (id, secret string, err error) {
-	var full, x map[string]interface{}
+	var data map[string]interface{}
+	var typ string
+	if data, typ, err = decodeAccessMapFromJSON(r); err != nil {
+		return
+	}
+	var ok bool
+	if id, ok = data["client_id"].(string); !ok {
+		err = fmt.Errorf("%s.client_id not found", typ)
+		return
+	}
+	if secret, ok = data["client_secret"].(string); !ok {
+		err = fmt.Errorf("%s.client_secret not found", typ)
+		return
+	}
+	return
+}
+
+func decodeAccessMapFromJSON(r io.Reader) (data map[string]interface{}, typ string, err error) {
+	var full map[string]interface{}
 	if err = json.NewDecoder(r).Decode(&full); err != nil {
 		return
 	}
 	var ok bool
-	typ := "web"
-	if x, ok = full[typ].(map[string]interface{}); !ok {
+	typ = "web"
+	if data, ok = full[typ].(map[string]interface{}); !ok {
 		typ = "installed"
-		if x, ok = full[typ].(map[string]interface{}); !ok {
+		if data, ok = full[typ].(map[string]interface{}); !ok {
 			err = fmt.Errorf("web or installed configuration not found")
-			return
 		}
 	}
-	if id, ok = x["client_id"].(string); !ok {
-		err = fmt.Errorf("%s.client_id not found", typ)
-		return
-	}
-	if secret, ok = x["client_secret"].(string); !ok {
-		err = fmt.Errorf("%s.client_secret not found", typ)
-		return
-	}
 	return
 }
diff --git a/services/identity/identityd/main.go b/services/identity/identityd/main.go
index 20c40af..84b752b 100644
--- a/services/identity/identityd/main.go
+++ b/services/identity/identityd/main.go
@@ -47,6 +47,7 @@
 	googleConfigWeb       = flag.String("google_config_web", "", "Path to JSON-encoded OAuth client configuration for the web application that renders the audit log for blessings provided by this provider.")
 	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.")
+	googleConfigAndroid   = flag.String("google_config_android", "", "Path to the JSON-encoded OAuth client configuration for Android 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")
 
 	// Revoker/Discharger configuration
@@ -84,7 +85,7 @@
 	if ipcServer != nil {
 		defer ipcServer.Stop()
 	}
-	if enabled, clientID, clientSecret := enableGoogleOAuth(*googleConfigWeb); enabled && len(*auditprefix) > 0 {
+	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigWeb); ok && len(*auditprefix) > 0 {
 		n := "/google/"
 		http.Handle(n, googleoauth.NewHandler(googleoauth.HandlerArgs{
 			Addr:              fmt.Sprintf("%s%s", httpaddress(), n),
@@ -167,14 +168,18 @@
 		DomainRestriction: *googleDomain,
 		RevocationManager: revocationManager,
 	}
-	if authcode, clientID, clientSecret := enableGoogleOAuth(*googleConfigInstalled); authcode {
+	if clientID, clientSecret, ok := getOAuthClientIDAndSecret(*googleConfigInstalled); ok {
 		enable = true
 		params.AuthorizationCodeClient.ID = clientID
 		params.AuthorizationCodeClient.Secret = clientSecret
 	}
-	if accesstoken, clientID, _ := enableGoogleOAuth(*googleConfigChrome); accesstoken {
+	if clientID, ok := getOAuthClientID(*googleConfigChrome); ok {
 		enable = true
-		params.AccessTokenClient.ID = clientID
+		params.AccessTokenClients = append(params.AccessTokenClients, struct{ ID string }{clientID})
+	}
+	if clientID, ok := getOAuthClientID(*googleConfigAndroid); ok {
+		enable = true
+		params.AccessTokenClients = append(params.AccessTokenClients, struct{ ID string }{clientID})
 	}
 	if !enable {
 		return nil, nil, nil
@@ -199,12 +204,28 @@
 
 func enableTLS() bool { return len(*tlsconfig) > 0 }
 func enableRandomHandler() bool {
-	return len(*googleConfigInstalled)+len(*googleConfigWeb)+len(*googleConfigChrome) == 0
+	return len(*googleConfigInstalled)+len(*googleConfigWeb)+len(*googleConfigChrome)+len(*googleConfigAndroid) == 0
 }
-func enableGoogleOAuth(config string) (enabled bool, clientID, clientSecret string) {
+func getOAuthClientID(config string) (clientID string, ok bool) {
 	fname := config
 	if len(fname) == 0 {
-		return false, "", ""
+		return "", false
+	}
+	f, err := os.Open(fname)
+	if err != nil {
+		vlog.Fatalf("Failed to open %q: %v", fname, err)
+	}
+	defer f.Close()
+	clientID, err = googleoauth.ClientIDFromJSON(f)
+	if err != nil {
+		vlog.Fatalf("Failed to decode JSON in %q: %v", fname, err)
+	}
+	return clientID, true
+}
+func getOAuthClientIDAndSecret(config string) (clientID, clientSecret string, ok bool) {
+	fname := config
+	if len(fname) == 0 {
+		return "", "", false
 	}
 	f, err := os.Open(fname)
 	if err != nil {
@@ -215,9 +236,8 @@
 	if err != nil {
 		vlog.Fatalf("Failed to decode JSON in %q: %v", fname, err)
 	}
-	return true, clientID, clientSecret
+	return clientID, clientSecret, true
 }
-
 func runHTTPServer(addr string) {
 	if !enableTLS() {
 		if err := http.ListenAndServe(addr, nil); err != nil {