From e72ebe82f6bbf35158cfa41d2d068a74c89cdcf3 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 1 Apr 2015 08:37:08 -0700 Subject: [PATCH] third_party: update oauth2 to 3046bc76d, but without tests Change-Id: I2c23d2ee93a3839690d9a8d86f4fef8953280e31 --- .../golang.org/x/oauth2/google/default.go | 154 ++++++++++++++++++ .../golang.org/x/oauth2/google/google.go | 54 +++--- third_party/golang.org/x/oauth2/oauth2.go | 84 +++++++--- 3 files changed, 246 insertions(+), 46 deletions(-) create mode 100644 third_party/golang.org/x/oauth2/google/default.go diff --git a/third_party/golang.org/x/oauth2/google/default.go b/third_party/golang.org/x/oauth2/google/default.go new file mode 100644 index 000000000..b57158bc1 --- /dev/null +++ b/third_party/golang.org/x/oauth2/google/default.go @@ -0,0 +1,154 @@ +// Copyright 2015 The oauth2 Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package google + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "runtime" + + "camlistore.org/third_party/golang.org/x/net/context" + "camlistore.org/third_party/golang.org/x/oauth2" + "camlistore.org/third_party/golang.org/x/oauth2/jwt" + "camlistore.org/third_party/google.golang.org/cloud/compute/metadata" +) + +// DefaultClient returns an HTTP Client that uses the +// DefaultTokenSource to obtain authentication credentials. +// +// This client should be used when developing services +// that run on Google App Engine or Google Compute Engine +// and use "Application Default Credentials." +// +// For more details, see: +// https://developers.google.com/accounts/application-default-credentials +// +func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { + ts, err := DefaultTokenSource(ctx, scope...) + if err != nil { + return nil, err + } + return oauth2.NewClient(ctx, ts), nil +} + +// DefaultTokenSource is a token source that uses +// "Application Default Credentials". +// +// It looks for credentials in the following places, +// preferring the first location found: +// +// 1. A JSON file whose path is specified by the +// GOOGLE_APPLICATION_CREDENTIALS environment variable. +// 2. A JSON file in a location known to the gcloud command-line tool. +// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json. +// On other systems, $HOME/.config/gcloud/application_default_credentials.json. +// 3. On Google App Engine it uses the appengine.AccessToken function. +// 4. On Google Compute Engine, it fetches credentials from the metadata server. +// (In this final case any provided scopes are ignored.) +// +// For more details, see: +// https://developers.google.com/accounts/application-default-credentials +// +func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { + // First, try the environment variable. + const envVar = "GOOGLE_APPLICATION_CREDENTIALS" + if filename := os.Getenv(envVar); filename != "" { + ts, err := tokenSourceFromFile(ctx, filename, scope) + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) + } + return ts, nil + } + + // Second, try a well-known file. + filename := wellKnownFile() + _, err := os.Stat(filename) + if err == nil { + ts, err2 := tokenSourceFromFile(ctx, filename, scope) + if err2 == nil { + return ts, nil + } + err = err2 + } else if os.IsNotExist(err) { + err = nil // ignore this error + } + if err != nil { + return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) + } + + // Third, if we're on Google App Engine use those credentials. + if appengineTokenFunc != nil { + return AppEngineTokenSource(ctx, scope...), nil + } + + // Fourth, if we're on Google Compute Engine use the metadata server. + if metadata.OnGCE() { + return ComputeTokenSource(""), nil + } + + // None are found; return helpful error. + const url = "https://developers.google.com/accounts/application-default-credentials" + return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) +} + +func wellKnownFile() string { + const f = "application_default_credentials.json" + if runtime.GOOS == "windows" { + return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) + } + return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) +} + +func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) { + b, err := ioutil.ReadFile(filename) + 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 { + 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) + } +} diff --git a/third_party/golang.org/x/oauth2/google/google.go b/third_party/golang.org/x/oauth2/google/google.go index 0ab113916..3f444d806 100644 --- a/third_party/golang.org/x/oauth2/google/google.go +++ b/third_party/golang.org/x/oauth2/google/google.go @@ -2,15 +2,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package google provides support for making -// OAuth2 authorized and authenticated HTTP requests -// to Google APIs. It supports Web server, client-side, -// service accounts, Google Compute Engine service accounts, -// and Google App Engine service accounts authorization -// and authentications flows: +// Package google provides support for making OAuth2 authorized and +// authenticated HTTP requests to Google APIs. +// It supports the Web server flow, client-side credentials, service accounts, +// Google Compute Engine service accounts, and Google App Engine service +// accounts. // // For more information, please read -// https://developers.google.com/accounts/docs/OAuth2. +// https://developers.google.com/accounts/docs/OAuth2 +// and +// https://developers.google.com/accounts/application-default-credentials. package google // import "camlistore.org/third_party/golang.org/x/oauth2/google" import ( @@ -34,35 +35,46 @@ var Endpoint = oauth2.Endpoint{ // JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow. const JWTTokenURL = "https://accounts.google.com/o/oauth2/token" -// JWTConfigFromJSON uses a Google Developers Console client_credentials.json +// ConfigFromJSON uses a Google Developers Console client_credentials.json // file to construct a config. // client_credentials.json can be downloadable from https://console.developers.google.com, // under "APIs & Auth" > "Credentials". Download the Web application credentials in the // JSON format and provide the contents of the file as jsonKey. func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) { + type cred struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectURIs []string `json:"redirect_uris"` + AuthURI string `json:"auth_uri"` + TokenURI string `json:"token_uri"` + } var j struct { - Web struct { - ClientID string `json:"client_id"` - ClientSecret string `json:"client_secret"` - RedirectURIs []string `json:"redirect_uris"` - AuthURI string `json:"auth_uri"` - TokenURI string `json:"token_uri"` - } `json:"web"` + Web *cred `json:"web"` + Installed *cred `json:"installed"` } if err := json.Unmarshal(jsonKey, &j); err != nil { return nil, err } - if len(j.Web.RedirectURIs) < 1 { + var c *cred + switch { + case j.Web != nil: + c = j.Web + case j.Installed != nil: + c = j.Installed + default: + return nil, fmt.Errorf("oauth2/google: no credentials found") + } + if len(c.RedirectURIs) < 1 { return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json") } return &oauth2.Config{ - ClientID: j.Web.ClientID, - ClientSecret: j.Web.ClientSecret, - RedirectURL: j.Web.RedirectURIs[0], + ClientID: c.ClientID, + ClientSecret: c.ClientSecret, + RedirectURL: c.RedirectURIs[0], Scopes: scope, Endpoint: oauth2.Endpoint{ - AuthURL: j.Web.AuthURI, - TokenURL: j.Web.TokenURI, + AuthURL: c.AuthURI, + TokenURL: c.TokenURI, }, }, nil } diff --git a/third_party/golang.org/x/oauth2/oauth2.go b/third_party/golang.org/x/oauth2/oauth2.go index f9f223845..289916ebe 100644 --- a/third_party/golang.org/x/oauth2/oauth2.go +++ b/third_party/golang.org/x/oauth2/oauth2.go @@ -79,22 +79,28 @@ var ( // result in your application obtaining a refresh token the // first time your application exchanges an authorization // code for a user. - AccessTypeOnline AuthCodeOption = setParam{"access_type", "online"} - AccessTypeOffline AuthCodeOption = setParam{"access_type", "offline"} + AccessTypeOnline AuthCodeOption = SetParam("access_type", "online") + AccessTypeOffline AuthCodeOption = SetParam("access_type", "offline") // ApprovalForce forces the users to view the consent dialog // and confirm the permissions request at the URL returned // from AuthCodeURL, even if they've already done so. - ApprovalForce AuthCodeOption = setParam{"approval_prompt", "force"} + ApprovalForce AuthCodeOption = SetParam("approval_prompt", "force") ) +// An AuthCodeOption is passed to Config.AuthCodeURL. +type AuthCodeOption interface { + setValue(url.Values) +} + type setParam struct{ k, v string } func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) } -// An AuthCodeOption is passed to Config.AuthCodeURL. -type AuthCodeOption interface { - setValue(url.Values) +// SetParam builds an AuthCodeOption which passes key/value parameters +// to a provider's authorization endpoint. +func SetParam(key, value string) AuthCodeOption { + return setParam{key, value} } // AuthCodeURL returns a URL to OAuth 2.0 provider's consent page @@ -155,7 +161,7 @@ func (c *Config) PasswordCredentialsToken(ctx context.Context, username, passwor // to the Redirect URI (the URL obtained from AuthCodeURL). // // The HTTP client to use is derived from the context. -// If nil, http.DefaultClient is used. +// If a client is not provided via the context, http.DefaultClient is used. // // The code will be in the *http.Request.FormValue("code"). Before // calling Exchange, be sure to validate FormValue("state"). @@ -308,7 +314,7 @@ func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) return nil, err } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - if !bustedAuth && c.ClientSecret != "" { + if !bustedAuth { req.SetBasicAuth(c.ClientID, c.ClientSecret) } r, err := hc.Do(req) @@ -374,11 +380,11 @@ func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) // tokenJSON is the struct representing the HTTP response from OAuth2 // providers returning a token in JSON form. type tokenJSON struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - RefreshToken string `json:"refresh_token"` - ExpiresIn int32 `json:"expires_in"` - Expires int32 `json:"expires"` // broken Facebook spelling of expires_in + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + RefreshToken string `json:"refresh_token"` + ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number + Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in } func (e *tokenJSON) expiry() (t time.Time) { @@ -391,6 +397,22 @@ func (e *tokenJSON) expiry() (t time.Time) { return } +type expirationTime int32 + +func (e *expirationTime) UnmarshalJSON(b []byte) error { + var n json.Number + err := json.Unmarshal(b, &n) + if err != nil { + return err + } + i, err := n.Int64() + if err != nil { + return err + } + *e = expirationTime(i) + return nil +} + func condVal(v string) []string { if v == "" { return nil @@ -398,6 +420,25 @@ func condVal(v string) []string { return []string{v} } +var brokenAuthHeaderProviders = []string{ + "https://accounts.google.com/", + "https://www.googleapis.com/", + "https://github.com/", + "https://api.instagram.com/", + "https://www.douban.com/", + "https://api.dropbox.com/", + "https://api.soundcloud.com/", + "https://www.linkedin.com/", + "https://api.twitch.tv/", + "https://oauth.vk.com/", + "https://api.odnoklassniki.ru/", + "https://connect.stripe.com/", + "https://api.pushbullet.com/", + "https://oauth.sandbox.trainingpeaks.com/", + "https://oauth.trainingpeaks.com/", + "https://www.strava.com/oauth/", +} + // providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL // implements the OAuth2 spec correctly // See https://code.google.com/p/goauth2/issues/detail?id=31 for background. @@ -407,18 +448,11 @@ func condVal(v string) []string { // - Google only accepts URL param (not spec compliant?), not Auth header // - Stripe only accepts client secret in Auth header with Bearer method, not Basic func providerAuthHeaderWorks(tokenURL string) bool { - if strings.HasPrefix(tokenURL, "https://accounts.google.com/") || - strings.HasPrefix(tokenURL, "https://www.googleapis.com/") || - strings.HasPrefix(tokenURL, "https://github.com/") || - strings.HasPrefix(tokenURL, "https://api.instagram.com/") || - strings.HasPrefix(tokenURL, "https://www.douban.com/") || - strings.HasPrefix(tokenURL, "https://api.dropbox.com/") || - strings.HasPrefix(tokenURL, "https://api.soundcloud.com/") || - strings.HasPrefix(tokenURL, "https://www.linkedin.com/") || - strings.HasPrefix(tokenURL, "https://api.twitch.tv/") || - strings.HasPrefix(tokenURL, "https://connect.stripe.com/") { - // Some sites fail to implement the OAuth2 spec fully. - return false + for _, s := range brokenAuthHeaderProviders { + if strings.HasPrefix(tokenURL, s) { + // Some sites fail to implement the OAuth2 spec fully. + return false + } } // Assume the provider implements the spec properly