From d5040cddfc0da40b408c9a1da4728662435176a9 Mon Sep 17 00:00:00 2001 From: Ross Light Date: Thu, 3 Nov 2016 15:50:36 -0700 Subject: [PATCH] google: refactor JWT parsing code internally The ADC code and the JWT-parsing function operate on the same data format, but were using separate code paths, each of which was missing things from the other. While this presents no change in API surface, JWTConfigFromJSON now strictly checks the "type" field in the JSON file before building a config. Change-Id: I2f593a16bf4591059fbf9002bccea06e41e5e161 Reviewed-on: https://go-review.googlesource.com/32678 Reviewed-by: Jaana Burcu Dogan --- google/default.go | 43 ++----------------------- google/google.go | 80 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 67 insertions(+), 56 deletions(-) diff --git a/google/default.go b/google/default.go index 565d731..c572d1a 100644 --- a/google/default.go +++ b/google/default.go @@ -6,7 +6,6 @@ package google import ( "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" @@ -17,7 +16,6 @@ import ( "cloud.google.com/go/compute/metadata" "golang.org/x/net/context" "golang.org/x/oauth2" - "golang.org/x/oauth2/jwt" ) // DefaultClient returns an HTTP Client that uses the @@ -112,44 +110,9 @@ func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) if err != nil { return nil, err } - var d struct { - // Common fields - Type string - ClientID string `json:"client_id"` - - // User Credential fields - ClientSecret string `json:"client_secret"` - RefreshToken string `json:"refresh_token"` - - // Service Account fields - ClientEmail string `json:"client_email"` - PrivateKeyID string `json:"private_key_id"` - PrivateKey string `json:"private_key"` - } - if err := json.Unmarshal(b, &d); err != nil { + var f credentialsFile + if err := json.Unmarshal(b, &f); err != nil { return nil, err } - switch d.Type { - case "authorized_user": - cfg := &oauth2.Config{ - ClientID: d.ClientID, - ClientSecret: d.ClientSecret, - Scopes: append([]string{}, scopes...), // copy - Endpoint: Endpoint, - } - tok := &oauth2.Token{RefreshToken: d.RefreshToken} - return cfg.TokenSource(ctx, tok), nil - case "service_account": - cfg := &jwt.Config{ - Email: d.ClientEmail, - PrivateKey: []byte(d.PrivateKey), - Scopes: append([]string{}, scopes...), // copy - TokenURL: JWTTokenURL, - } - return cfg.TokenSource(ctx), nil - case "": - return nil, errors.New("missing 'type' field in credentials") - default: - return nil, fmt.Errorf("unknown credential type: %q", d.Type) - } + return f.tokenSource(ctx, scopes) } diff --git a/google/google.go b/google/google.go index a48d5bf..0704732 100644 --- a/google/google.go +++ b/google/google.go @@ -22,6 +22,7 @@ import ( "time" "cloud.google.com/go/compute/metadata" + "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/jwt" ) @@ -85,26 +86,73 @@ func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { // Create a service account on "Credentials" for your project at // https://console.developers.google.com to download a JSON key file. func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) { - var key struct { - Email string `json:"client_email"` - PrivateKey string `json:"private_key"` - PrivateKeyID string `json:"private_key_id"` - TokenURL string `json:"token_uri"` - } - if err := json.Unmarshal(jsonKey, &key); err != nil { + var f credentialsFile + if err := json.Unmarshal(jsonKey, &f); err != nil { return nil, err } - config := &jwt.Config{ - Email: key.Email, - PrivateKey: []byte(key.PrivateKey), - PrivateKeyID: key.PrivateKeyID, - Scopes: scope, - TokenURL: key.TokenURL, + if f.Type != serviceAccountKey { + return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey) } - if config.TokenURL == "" { - config.TokenURL = JWTTokenURL + scope = append([]string(nil), scope...) // copy + return f.jwtConfig(scope), nil +} + +// JSON key file types. +const ( + serviceAccountKey = "service_account" + userCredentialsKey = "authorized_user" +) + +// credentialsFile is the unmarshalled representation of a credentials file. +type credentialsFile struct { + Type string `json:"type"` // serviceAccountKey or userCredentialsKey + + // Service Account fields + ClientEmail string `json:"client_email"` + PrivateKeyID string `json:"private_key_id"` + PrivateKey string `json:"private_key"` + TokenURL string `json:"token_uri"` + + // User Credential fields + // (These typically come from gcloud auth.) + ClientSecret string `json:"client_secret"` + ClientID string `json:"client_id"` + RefreshToken string `json:"refresh_token"` +} + +func (f *credentialsFile) jwtConfig(scopes []string) *jwt.Config { + cfg := &jwt.Config{ + Email: f.ClientEmail, + PrivateKey: []byte(f.PrivateKey), + PrivateKeyID: f.PrivateKeyID, + Scopes: scopes, + TokenURL: f.TokenURL, + } + if cfg.TokenURL == "" { + cfg.TokenURL = JWTTokenURL + } + return cfg +} + +func (f *credentialsFile) tokenSource(ctx context.Context, scopes []string) (oauth2.TokenSource, error) { + switch f.Type { + case serviceAccountKey: + cfg := f.jwtConfig(scopes) + return cfg.TokenSource(ctx), nil + case userCredentialsKey: + cfg := &oauth2.Config{ + ClientID: f.ClientID, + ClientSecret: f.ClientSecret, + Scopes: scopes, + Endpoint: Endpoint, + } + tok := &oauth2.Token{RefreshToken: f.RefreshToken} + return cfg.TokenSource(ctx, tok), nil + case "": + return nil, errors.New("missing 'type' field in credentials") + default: + return nil, fmt.Errorf("unknown credential type: %q", f.Type) } - return config, nil } // ComputeTokenSource returns a token source that fetches access tokens