mirror of
https://github.com/golang/oauth2.git
synced 2025-07-21 00:00:09 +08:00
Instead of maintaining a global map of which OAuth2 servers do which auth style and/or requiring the user to tell us, just try both ways and remember which way worked. But if users want to tell us in the Endpoint, this CL also add Endpoint.AuthStyle. Fixes golang/oauth2#111 Fixes golang/oauth2#365 Fixes golang/oauth2#362 Fixes golang/oauth2#357 Fixes golang/oauth2#353 Fixes golang/oauth2#345 Fixes golang/oauth2#326 Fixes golang/oauth2#352 Fixes golang/oauth2#268 Fixes https://go-review.googlesource.com/c/oauth2/+/58510 (... and surely many more ...) Change-Id: I7b4d98ba1900ee2d3e11e629316b0bf867f7d237 Reviewed-on: https://go-review.googlesource.com/c/157820 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ross Light <light@google.com>
194 lines
5.9 KiB
Go
194 lines
5.9 KiB
Go
// Copyright 2014 The Go 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 (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"cloud.google.com/go/compute/metadata"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/jwt"
|
|
)
|
|
|
|
// Endpoint is Google's OAuth 2.0 endpoint.
|
|
var Endpoint = oauth2.Endpoint{
|
|
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
|
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
|
AuthStyle: oauth2.AuthStyleInParams,
|
|
}
|
|
|
|
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
|
const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
|
|
|
// ConfigFromJSON uses a Google Developers Console client_credentials.json
|
|
// file to construct a config.
|
|
// client_credentials.json can be downloaded from
|
|
// https://console.developers.google.com, under "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 *cred `json:"web"`
|
|
Installed *cred `json:"installed"`
|
|
}
|
|
if err := json.Unmarshal(jsonKey, &j); err != nil {
|
|
return nil, err
|
|
}
|
|
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: c.ClientID,
|
|
ClientSecret: c.ClientSecret,
|
|
RedirectURL: c.RedirectURIs[0],
|
|
Scopes: scope,
|
|
Endpoint: oauth2.Endpoint{
|
|
AuthURL: c.AuthURI,
|
|
TokenURL: c.TokenURI,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
|
// the credentials that authorize and authenticate the requests.
|
|
// 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 f credentialsFile
|
|
if err := json.Unmarshal(jsonKey, &f); err != nil {
|
|
return nil, err
|
|
}
|
|
if f.Type != serviceAccountKey {
|
|
return nil, fmt.Errorf("google: read JWT from JSON credentials: 'type' field is %q (expected %q)", f.Type, serviceAccountKey)
|
|
}
|
|
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"`
|
|
ProjectID string `json:"project_id"`
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
// ComputeTokenSource returns a token source that fetches access tokens
|
|
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
|
// this token source if your program is running on a GCE instance.
|
|
// If no account is specified, "default" is used.
|
|
// Further information about retrieving access tokens from the GCE metadata
|
|
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
|
func ComputeTokenSource(account string) oauth2.TokenSource {
|
|
return oauth2.ReuseTokenSource(nil, computeSource{account: account})
|
|
}
|
|
|
|
type computeSource struct {
|
|
account string
|
|
}
|
|
|
|
func (cs computeSource) Token() (*oauth2.Token, error) {
|
|
if !metadata.OnGCE() {
|
|
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
|
|
}
|
|
acct := cs.account
|
|
if acct == "" {
|
|
acct = "default"
|
|
}
|
|
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var res struct {
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresInSec int `json:"expires_in"`
|
|
TokenType string `json:"token_type"`
|
|
}
|
|
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
|
|
}
|
|
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
|
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
|
|
}
|
|
return &oauth2.Token{
|
|
AccessToken: res.AccessToken,
|
|
TokenType: res.TokenType,
|
|
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
|
}, nil
|
|
}
|