mirror of
https://github.com/golang/oauth2.git
synced 2025-07-21 00:00:09 +08:00
This is required for the direct path feature, which only works with this token source. It's not currently possible to determine the token source type from the return value of FindDefaultCredentials. Another option is to add another field to the Credentials struct, which we could still do later, but direct path is currently pretty experimental and whitelisted/opt-in, so I don't want to add to the public API surface unnecessarily. This CL functionally blocks https://code-review.googlesource.com/c/google-api-go-client/+/40950 Change-Id: Ifb5fe9c6e5c6b33eebb87b45d3c70eebfca691b3 Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/175877 Reviewed-by: Chris Broadfoot <cbro@golang.org>
210 lines
6.6 KiB
Go
210 lines
6.6 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"
|
|
"net/url"
|
|
"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://oauth2.googleapis.com/token",
|
|
AuthStyle: oauth2.AuthStyleInParams,
|
|
}
|
|
|
|
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
|
const JWTTokenURL = "https://oauth2.googleapis.com/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.
|
|
// If no scopes are specified, a set of default scopes are automatically granted.
|
|
// 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, scope ...string) oauth2.TokenSource {
|
|
return oauth2.ReuseTokenSource(nil, computeSource{account: account, scopes: scope})
|
|
}
|
|
|
|
type computeSource struct {
|
|
account string
|
|
scopes []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"
|
|
}
|
|
tokenURI := "instance/service-accounts/" + acct + "/token"
|
|
if len(cs.scopes) > 0 {
|
|
v := url.Values{}
|
|
v.Set("scopes", strings.Join(cs.scopes, ","))
|
|
tokenURI = tokenURI + "?" + v.Encode()
|
|
}
|
|
tokenJSON, err := metadata.Get(tokenURI)
|
|
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")
|
|
}
|
|
tok := &oauth2.Token{
|
|
AccessToken: res.AccessToken,
|
|
TokenType: res.TokenType,
|
|
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
|
}
|
|
// NOTE(cbro): add hidden metadata about where the token is from.
|
|
// This is needed for detection by client libraries to know that credentials come from the metadata server.
|
|
// This may be removed in a future version of this library.
|
|
return tok.WithExtra(map[string]interface{}{
|
|
"oauth2.google.tokenSource": "compute-metadata",
|
|
"oauth2.google.serviceAccount": acct,
|
|
}), nil
|
|
}
|