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 {