mirror of https://github.com/go-gitea/gitea.git
migrate gplus to google oauth2 provider (#7885)
* migrate gplus to google oauth2 provider. this still provides support for old gplus connections. * Update models/oauth2.go Co-Authored-By: Antoine GIRARD <sapk@users.noreply.github.com> * make vendorpull/8182/head
parent
107d57a925
commit
7a8e299c7c
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,526 @@ |
||||
// Copyright 2014 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package metadata provides access to Google Compute Engine (GCE)
|
||||
// metadata and API service accounts.
|
||||
//
|
||||
// This package is a wrapper around the GCE metadata service,
|
||||
// as documented at https://developers.google.com/compute/docs/metadata.
|
||||
package metadata // import "cloud.google.com/go/compute/metadata"
|
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"runtime" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
// metadataIP is the documented metadata server IP address.
|
||||
metadataIP = "169.254.169.254" |
||||
|
||||
// metadataHostEnv is the environment variable specifying the
|
||||
// GCE metadata hostname. If empty, the default value of
|
||||
// metadataIP ("169.254.169.254") is used instead.
|
||||
// This is variable name is not defined by any spec, as far as
|
||||
// I know; it was made up for the Go package.
|
||||
metadataHostEnv = "GCE_METADATA_HOST" |
||||
|
||||
userAgent = "gcloud-golang/0.1" |
||||
) |
||||
|
||||
type cachedValue struct { |
||||
k string |
||||
trim bool |
||||
mu sync.Mutex |
||||
v string |
||||
} |
||||
|
||||
var ( |
||||
projID = &cachedValue{k: "project/project-id", trim: true} |
||||
projNum = &cachedValue{k: "project/numeric-project-id", trim: true} |
||||
instID = &cachedValue{k: "instance/id", trim: true} |
||||
) |
||||
|
||||
var ( |
||||
defaultClient = &Client{hc: &http.Client{ |
||||
Transport: &http.Transport{ |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 2 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
ResponseHeaderTimeout: 2 * time.Second, |
||||
}, |
||||
}} |
||||
subscribeClient = &Client{hc: &http.Client{ |
||||
Transport: &http.Transport{ |
||||
Dial: (&net.Dialer{ |
||||
Timeout: 2 * time.Second, |
||||
KeepAlive: 30 * time.Second, |
||||
}).Dial, |
||||
}, |
||||
}} |
||||
) |
||||
|
||||
// NotDefinedError is returned when requested metadata is not defined.
|
||||
//
|
||||
// The underlying string is the suffix after "/computeMetadata/v1/".
|
||||
//
|
||||
// This error is not returned if the value is defined to be the empty
|
||||
// string.
|
||||
type NotDefinedError string |
||||
|
||||
func (suffix NotDefinedError) Error() string { |
||||
return fmt.Sprintf("metadata: GCE metadata %q not defined", string(suffix)) |
||||
} |
||||
|
||||
func (c *cachedValue) get(cl *Client) (v string, err error) { |
||||
defer c.mu.Unlock() |
||||
c.mu.Lock() |
||||
if c.v != "" { |
||||
return c.v, nil |
||||
} |
||||
if c.trim { |
||||
v, err = cl.getTrimmed(c.k) |
||||
} else { |
||||
v, err = cl.Get(c.k) |
||||
} |
||||
if err == nil { |
||||
c.v = v |
||||
} |
||||
return |
||||
} |
||||
|
||||
var ( |
||||
onGCEOnce sync.Once |
||||
onGCE bool |
||||
) |
||||
|
||||
// OnGCE reports whether this process is running on Google Compute Engine.
|
||||
func OnGCE() bool { |
||||
onGCEOnce.Do(initOnGCE) |
||||
return onGCE |
||||
} |
||||
|
||||
func initOnGCE() { |
||||
onGCE = testOnGCE() |
||||
} |
||||
|
||||
func testOnGCE() bool { |
||||
// The user explicitly said they're on GCE, so trust them.
|
||||
if os.Getenv(metadataHostEnv) != "" { |
||||
return true |
||||
} |
||||
|
||||
ctx, cancel := context.WithCancel(context.Background()) |
||||
defer cancel() |
||||
|
||||
resc := make(chan bool, 2) |
||||
|
||||
// Try two strategies in parallel.
|
||||
// See https://github.com/googleapis/google-cloud-go/issues/194
|
||||
go func() { |
||||
req, _ := http.NewRequest("GET", "http://"+metadataIP, nil) |
||||
req.Header.Set("User-Agent", userAgent) |
||||
res, err := defaultClient.hc.Do(req.WithContext(ctx)) |
||||
if err != nil { |
||||
resc <- false |
||||
return |
||||
} |
||||
defer res.Body.Close() |
||||
resc <- res.Header.Get("Metadata-Flavor") == "Google" |
||||
}() |
||||
|
||||
go func() { |
||||
addrs, err := net.LookupHost("metadata.google.internal") |
||||
if err != nil || len(addrs) == 0 { |
||||
resc <- false |
||||
return |
||||
} |
||||
resc <- strsContains(addrs, metadataIP) |
||||
}() |
||||
|
||||
tryHarder := systemInfoSuggestsGCE() |
||||
if tryHarder { |
||||
res := <-resc |
||||
if res { |
||||
// The first strategy succeeded, so let's use it.
|
||||
return true |
||||
} |
||||
// Wait for either the DNS or metadata server probe to
|
||||
// contradict the other one and say we are running on
|
||||
// GCE. Give it a lot of time to do so, since the system
|
||||
// info already suggests we're running on a GCE BIOS.
|
||||
timer := time.NewTimer(5 * time.Second) |
||||
defer timer.Stop() |
||||
select { |
||||
case res = <-resc: |
||||
return res |
||||
case <-timer.C: |
||||
// Too slow. Who knows what this system is.
|
||||
return false |
||||
} |
||||
} |
||||
|
||||
// There's no hint from the system info that we're running on
|
||||
// GCE, so use the first probe's result as truth, whether it's
|
||||
// true or false. The goal here is to optimize for speed for
|
||||
// users who are NOT running on GCE. We can't assume that
|
||||
// either a DNS lookup or an HTTP request to a blackholed IP
|
||||
// address is fast. Worst case this should return when the
|
||||
// metaClient's Transport.ResponseHeaderTimeout or
|
||||
// Transport.Dial.Timeout fires (in two seconds).
|
||||
return <-resc |
||||
} |
||||
|
||||
// systemInfoSuggestsGCE reports whether the local system (without
|
||||
// doing network requests) suggests that we're running on GCE. If this
|
||||
// returns true, testOnGCE tries a bit harder to reach its metadata
|
||||
// server.
|
||||
func systemInfoSuggestsGCE() bool { |
||||
if runtime.GOOS != "linux" { |
||||
// We don't have any non-Linux clues available, at least yet.
|
||||
return false |
||||
} |
||||
slurp, _ := ioutil.ReadFile("/sys/class/dmi/id/product_name") |
||||
name := strings.TrimSpace(string(slurp)) |
||||
return name == "Google" || name == "Google Compute Engine" |
||||
} |
||||
|
||||
// Subscribe calls Client.Subscribe on a client designed for subscribing (one with no
|
||||
// ResponseHeaderTimeout).
|
||||
func Subscribe(suffix string, fn func(v string, ok bool) error) error { |
||||
return subscribeClient.Subscribe(suffix, fn) |
||||
} |
||||
|
||||
// Get calls Client.Get on the default client.
|
||||
func Get(suffix string) (string, error) { return defaultClient.Get(suffix) } |
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func ProjectID() (string, error) { return defaultClient.ProjectID() } |
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func NumericProjectID() (string, error) { return defaultClient.NumericProjectID() } |
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func InternalIP() (string, error) { return defaultClient.InternalIP() } |
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func ExternalIP() (string, error) { return defaultClient.ExternalIP() } |
||||
|
||||
// Email calls Client.Email on the default client.
|
||||
func Email(serviceAccount string) (string, error) { return defaultClient.Email(serviceAccount) } |
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func Hostname() (string, error) { return defaultClient.Hostname() } |
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func InstanceTags() ([]string, error) { return defaultClient.InstanceTags() } |
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func InstanceID() (string, error) { return defaultClient.InstanceID() } |
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func InstanceName() (string, error) { return defaultClient.InstanceName() } |
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func Zone() (string, error) { return defaultClient.Zone() } |
||||
|
||||
// InstanceAttributes calls Client.InstanceAttributes on the default client.
|
||||
func InstanceAttributes() ([]string, error) { return defaultClient.InstanceAttributes() } |
||||
|
||||
// ProjectAttributes calls Client.ProjectAttributes on the default client.
|
||||
func ProjectAttributes() ([]string, error) { return defaultClient.ProjectAttributes() } |
||||
|
||||
// InstanceAttributeValue calls Client.InstanceAttributeValue on the default client.
|
||||
func InstanceAttributeValue(attr string) (string, error) { |
||||
return defaultClient.InstanceAttributeValue(attr) |
||||
} |
||||
|
||||
// ProjectAttributeValue calls Client.ProjectAttributeValue on the default client.
|
||||
func ProjectAttributeValue(attr string) (string, error) { |
||||
return defaultClient.ProjectAttributeValue(attr) |
||||
} |
||||
|
||||
// Scopes calls Client.Scopes on the default client.
|
||||
func Scopes(serviceAccount string) ([]string, error) { return defaultClient.Scopes(serviceAccount) } |
||||
|
||||
func strsContains(ss []string, s string) bool { |
||||
for _, v := range ss { |
||||
if v == s { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// A Client provides metadata.
|
||||
type Client struct { |
||||
hc *http.Client |
||||
} |
||||
|
||||
// NewClient returns a Client that can be used to fetch metadata. All HTTP requests
|
||||
// will use the given http.Client instead of the default client.
|
||||
func NewClient(c *http.Client) *Client { |
||||
return &Client{hc: c} |
||||
} |
||||
|
||||
// getETag returns a value from the metadata service as well as the associated ETag.
|
||||
// This func is otherwise equivalent to Get.
|
||||
func (c *Client) getETag(suffix string) (value, etag string, err error) { |
||||
// Using a fixed IP makes it very difficult to spoof the metadata service in
|
||||
// a container, which is an important use-case for local testing of cloud
|
||||
// deployments. To enable spoofing of the metadata service, the environment
|
||||
// variable GCE_METADATA_HOST is first inspected to decide where metadata
|
||||
// requests shall go.
|
||||
host := os.Getenv(metadataHostEnv) |
||||
if host == "" { |
||||
// Using 169.254.169.254 instead of "metadata" here because Go
|
||||
// binaries built with the "netgo" tag and without cgo won't
|
||||
// know the search suffix for "metadata" is
|
||||
// ".google.internal", and this IP address is documented as
|
||||
// being stable anyway.
|
||||
host = metadataIP |
||||
} |
||||
u := "http://" + host + "/computeMetadata/v1/" + suffix |
||||
req, _ := http.NewRequest("GET", u, nil) |
||||
req.Header.Set("Metadata-Flavor", "Google") |
||||
req.Header.Set("User-Agent", userAgent) |
||||
res, err := c.hc.Do(req) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
defer res.Body.Close() |
||||
if res.StatusCode == http.StatusNotFound { |
||||
return "", "", NotDefinedError(suffix) |
||||
} |
||||
all, err := ioutil.ReadAll(res.Body) |
||||
if err != nil { |
||||
return "", "", err |
||||
} |
||||
if res.StatusCode != 200 { |
||||
return "", "", &Error{Code: res.StatusCode, Message: string(all)} |
||||
} |
||||
return string(all), res.Header.Get("Etag"), nil |
||||
} |
||||
|
||||
// Get returns a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
//
|
||||
// If the GCE_METADATA_HOST environment variable is not defined, a default of
|
||||
// 169.254.169.254 will be used instead.
|
||||
//
|
||||
// If the requested metadata is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
func (c *Client) Get(suffix string) (string, error) { |
||||
val, _, err := c.getETag(suffix) |
||||
return val, err |
||||
} |
||||
|
||||
func (c *Client) getTrimmed(suffix string) (s string, err error) { |
||||
s, err = c.Get(suffix) |
||||
s = strings.TrimSpace(s) |
||||
return |
||||
} |
||||
|
||||
func (c *Client) lines(suffix string) ([]string, error) { |
||||
j, err := c.Get(suffix) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s := strings.Split(strings.TrimSpace(j), "\n") |
||||
for i := range s { |
||||
s[i] = strings.TrimSpace(s[i]) |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
// ProjectID returns the current instance's project ID string.
|
||||
func (c *Client) ProjectID() (string, error) { return projID.get(c) } |
||||
|
||||
// NumericProjectID returns the current instance's numeric project ID.
|
||||
func (c *Client) NumericProjectID() (string, error) { return projNum.get(c) } |
||||
|
||||
// InstanceID returns the current VM's numeric instance ID.
|
||||
func (c *Client) InstanceID() (string, error) { return instID.get(c) } |
||||
|
||||
// InternalIP returns the instance's primary internal IP address.
|
||||
func (c *Client) InternalIP() (string, error) { |
||||
return c.getTrimmed("instance/network-interfaces/0/ip") |
||||
} |
||||
|
||||
// Email returns the email address associated with the service account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Email(serviceAccount string) (string, error) { |
||||
if serviceAccount == "" { |
||||
serviceAccount = "default" |
||||
} |
||||
return c.getTrimmed("instance/service-accounts/" + serviceAccount + "/email") |
||||
} |
||||
|
||||
// ExternalIP returns the instance's primary external (public) IP address.
|
||||
func (c *Client) ExternalIP() (string, error) { |
||||
return c.getTrimmed("instance/network-interfaces/0/access-configs/0/external-ip") |
||||
} |
||||
|
||||
// Hostname returns the instance's hostname. This will be of the form
|
||||
// "<instanceID>.c.<projID>.internal".
|
||||
func (c *Client) Hostname() (string, error) { |
||||
return c.getTrimmed("instance/hostname") |
||||
} |
||||
|
||||
// InstanceTags returns the list of user-defined instance tags,
|
||||
// assigned when initially creating a GCE instance.
|
||||
func (c *Client) InstanceTags() ([]string, error) { |
||||
var s []string |
||||
j, err := c.Get("instance/tags") |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := json.NewDecoder(strings.NewReader(j)).Decode(&s); err != nil { |
||||
return nil, err |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
// InstanceName returns the current VM's instance ID string.
|
||||
func (c *Client) InstanceName() (string, error) { |
||||
host, err := c.Hostname() |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return strings.Split(host, ".")[0], nil |
||||
} |
||||
|
||||
// Zone returns the current VM's zone, such as "us-central1-b".
|
||||
func (c *Client) Zone() (string, error) { |
||||
zone, err := c.getTrimmed("instance/zone") |
||||
// zone is of the form "projects/<projNum>/zones/<zoneName>".
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
return zone[strings.LastIndex(zone, "/")+1:], nil |
||||
} |
||||
|
||||
// InstanceAttributes returns the list of user-defined attributes,
|
||||
// assigned when initially creating a GCE VM instance. The value of an
|
||||
// attribute can be obtained with InstanceAttributeValue.
|
||||
func (c *Client) InstanceAttributes() ([]string, error) { return c.lines("instance/attributes/") } |
||||
|
||||
// ProjectAttributes returns the list of user-defined attributes
|
||||
// applying to the project as a whole, not just this VM. The value of
|
||||
// an attribute can be obtained with ProjectAttributeValue.
|
||||
func (c *Client) ProjectAttributes() ([]string, error) { return c.lines("project/attributes/") } |
||||
|
||||
// InstanceAttributeValue returns the value of the provided VM
|
||||
// instance attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// InstanceAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) InstanceAttributeValue(attr string) (string, error) { |
||||
return c.Get("instance/attributes/" + attr) |
||||
} |
||||
|
||||
// ProjectAttributeValue returns the value of the provided
|
||||
// project attribute.
|
||||
//
|
||||
// If the requested attribute is not defined, the returned error will
|
||||
// be of type NotDefinedError.
|
||||
//
|
||||
// ProjectAttributeValue may return ("", nil) if the attribute was
|
||||
// defined to be the empty string.
|
||||
func (c *Client) ProjectAttributeValue(attr string) (string, error) { |
||||
return c.Get("project/attributes/" + attr) |
||||
} |
||||
|
||||
// Scopes returns the service account scopes for the given account.
|
||||
// The account may be empty or the string "default" to use the instance's
|
||||
// main account.
|
||||
func (c *Client) Scopes(serviceAccount string) ([]string, error) { |
||||
if serviceAccount == "" { |
||||
serviceAccount = "default" |
||||
} |
||||
return c.lines("instance/service-accounts/" + serviceAccount + "/scopes") |
||||
} |
||||
|
||||
// Subscribe subscribes to a value from the metadata service.
|
||||
// The suffix is appended to "http://${GCE_METADATA_HOST}/computeMetadata/v1/".
|
||||
// The suffix may contain query parameters.
|
||||
//
|
||||
// Subscribe calls fn with the latest metadata value indicated by the provided
|
||||
// suffix. If the metadata value is deleted, fn is called with the empty string
|
||||
// and ok false. Subscribe blocks until fn returns a non-nil error or the value
|
||||
// is deleted. Subscribe returns the error value returned from the last call to
|
||||
// fn, which may be nil when ok == false.
|
||||
func (c *Client) Subscribe(suffix string, fn func(v string, ok bool) error) error { |
||||
const failedSubscribeSleep = time.Second * 5 |
||||
|
||||
// First check to see if the metadata value exists at all.
|
||||
val, lastETag, err := c.getETag(suffix) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := fn(val, true); err != nil { |
||||
return err |
||||
} |
||||
|
||||
ok := true |
||||
if strings.ContainsRune(suffix, '?') { |
||||
suffix += "&wait_for_change=true&last_etag=" |
||||
} else { |
||||
suffix += "?wait_for_change=true&last_etag=" |
||||
} |
||||
for { |
||||
val, etag, err := c.getETag(suffix + url.QueryEscape(lastETag)) |
||||
if err != nil { |
||||
if _, deleted := err.(NotDefinedError); !deleted { |
||||
time.Sleep(failedSubscribeSleep) |
||||
continue // Retry on other errors.
|
||||
} |
||||
ok = false |
||||
} |
||||
lastETag = etag |
||||
|
||||
if err := fn(val, ok); err != nil || !ok { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Error contains an error response from the server.
|
||||
type Error struct { |
||||
// Code is the HTTP response status code.
|
||||
Code int |
||||
// Message is the server response message.
|
||||
Message string |
||||
} |
||||
|
||||
func (e *Error) Error() string { |
||||
return fmt.Sprintf("compute: Received %d `%s`", e.Code, e.Message) |
||||
} |
@ -0,0 +1,10 @@ |
||||
// +build go1.9
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
goog "golang.org/x/oauth2/google" |
||||
) |
||||
|
||||
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||
var Endpoint = goog.Endpoint |
@ -0,0 +1,13 @@ |
||||
// +build !go1.9
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
// 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", |
||||
} |
@ -0,0 +1,38 @@ |
||||
// 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" |
||||
"time" |
||||
|
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error) |
||||
|
||||
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
|
||||
var appengineAppIDFunc func(c context.Context) string |
||||
|
||||
// AppEngineTokenSource returns a token source that fetches tokens from either
|
||||
// the current application's service account or from the metadata server,
|
||||
// depending on the App Engine environment. See below for environment-specific
|
||||
// details. If you are implementing a 3-legged OAuth 2.0 flow on App Engine that
|
||||
// involves user accounts, see oauth2.Config instead.
|
||||
//
|
||||
// First generation App Engine runtimes (<= Go 1.9):
|
||||
// AppEngineTokenSource returns a token source that fetches tokens issued to the
|
||||
// current App Engine application's service account. The provided context must have
|
||||
// come from appengine.NewContext.
|
||||
//
|
||||
// Second generation App Engine runtimes (>= Go 1.11) and App Engine flexible:
|
||||
// AppEngineTokenSource is DEPRECATED on second generation runtimes and on the
|
||||
// flexible environment. It delegates to ComputeTokenSource, and the provided
|
||||
// context and scopes are not used. Please use DefaultTokenSource (or ComputeTokenSource,
|
||||
// which DefaultTokenSource will use in this case) instead.
|
||||
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
return appEngineTokenSource(ctx, scope...) |
||||
} |
@ -0,0 +1,77 @@ |
||||
// Copyright 2018 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.
|
||||
|
||||
// +build appengine
|
||||
|
||||
// This file applies to App Engine first generation runtimes (<= Go 1.9).
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"sort" |
||||
"strings" |
||||
"sync" |
||||
|
||||
"golang.org/x/oauth2" |
||||
"google.golang.org/appengine" |
||||
) |
||||
|
||||
func init() { |
||||
appengineTokenFunc = appengine.AccessToken |
||||
appengineAppIDFunc = appengine.AppID |
||||
} |
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
scopes := append([]string{}, scope...) |
||||
sort.Strings(scopes) |
||||
return &gaeTokenSource{ |
||||
ctx: ctx, |
||||
scopes: scopes, |
||||
key: strings.Join(scopes, " "), |
||||
} |
||||
} |
||||
|
||||
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||
var ( |
||||
aeTokensMu sync.Mutex |
||||
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||
) |
||||
|
||||
type tokenLock struct { |
||||
mu sync.Mutex // guards t; held while fetching or updating t
|
||||
t *oauth2.Token |
||||
} |
||||
|
||||
type gaeTokenSource struct { |
||||
ctx context.Context |
||||
scopes []string |
||||
key string // to aeTokens map; space-separated scopes
|
||||
} |
||||
|
||||
func (ts *gaeTokenSource) Token() (*oauth2.Token, error) { |
||||
aeTokensMu.Lock() |
||||
tok, ok := aeTokens[ts.key] |
||||
if !ok { |
||||
tok = &tokenLock{} |
||||
aeTokens[ts.key] = tok |
||||
} |
||||
aeTokensMu.Unlock() |
||||
|
||||
tok.mu.Lock() |
||||
defer tok.mu.Unlock() |
||||
if tok.t.Valid() { |
||||
return tok.t, nil |
||||
} |
||||
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
tok.t = &oauth2.Token{ |
||||
AccessToken: access, |
||||
Expiry: exp, |
||||
} |
||||
return tok.t, nil |
||||
} |
@ -0,0 +1,27 @@ |
||||
// Copyright 2018 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.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible.
|
||||
|
||||
package google |
||||
|
||||
import ( |
||||
"context" |
||||
"log" |
||||
"sync" |
||||
|
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
var logOnce sync.Once // only spam about deprecation once
|
||||
|
||||
// See comment on AppEngineTokenSource in appengine.go.
|
||||
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource { |
||||
logOnce.Do(func() { |
||||
log.Print("google: AppEngineTokenSource is deprecated on App Engine standard second generation runtimes (>= Go 1.11) and App Engine flexible. Please use DefaultTokenSource or ComputeTokenSource.") |
||||
}) |
||||
return ComputeTokenSource("") |
||||
} |
@ -0,0 +1,154 @@ |
||||
// Copyright 2015 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" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
|
||||
"cloud.google.com/go/compute/metadata" |
||||
"golang.org/x/oauth2" |
||||
) |
||||
|
||||
// Credentials holds Google credentials, including "Application Default Credentials".
|
||||
// For more details, see:
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||
type Credentials struct { |
||||
ProjectID string // may be empty
|
||||
TokenSource oauth2.TokenSource |
||||
|
||||
// JSON contains the raw bytes from a JSON credentials file.
|
||||
// This field may be nil if authentication is provided by the
|
||||
// environment and not with a credentials file, e.g. when code is
|
||||
// running on Google Cloud Platform.
|
||||
JSON []byte |
||||
} |
||||
|
||||
// DefaultCredentials is the old name of Credentials.
|
||||
//
|
||||
// Deprecated: use Credentials instead.
|
||||
type DefaultCredentials = Credentials |
||||
|
||||
// DefaultClient returns an HTTP Client that uses the
|
||||
// DefaultTokenSource to obtain authentication 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 returns the token source for
|
||||
// "Application Default Credentials".
|
||||
// It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
|
||||
func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { |
||||
creds, err := FindDefaultCredentials(ctx, scope...) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return creds.TokenSource, nil |
||||
} |
||||
|
||||
// FindDefaultCredentials searches for "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 standard first generation runtimes (<= Go 1.9) it uses
|
||||
// the appengine.AccessToken function.
|
||||
// 4. On Google Compute Engine, Google App Engine standard second generation runtimes
|
||||
// (>= Go 1.11), and Google App Engine flexible environment, it fetches
|
||||
// credentials from the metadata server.
|
||||
func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) { |
||||
// First, try the environment variable.
|
||||
const envVar = "GOOGLE_APPLICATION_CREDENTIALS" |
||||
if filename := os.Getenv(envVar); filename != "" { |
||||
creds, err := readCredentialsFile(ctx, filename, scopes) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) |
||||
} |
||||
return creds, nil |
||||
} |
||||
|
||||
// Second, try a well-known file.
|
||||
filename := wellKnownFile() |
||||
if creds, err := readCredentialsFile(ctx, filename, scopes); err == nil { |
||||
return creds, nil |
||||
} else if !os.IsNotExist(err) { |
||||
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err) |
||||
} |
||||
|
||||
// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
|
||||
// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
|
||||
// and App Engine flexible use ComputeTokenSource and the metadata server.
|
||||
if appengineTokenFunc != nil { |
||||
return &DefaultCredentials{ |
||||
ProjectID: appengineAppIDFunc(ctx), |
||||
TokenSource: AppEngineTokenSource(ctx, scopes...), |
||||
}, nil |
||||
} |
||||
|
||||
// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
|
||||
// or App Engine flexible, use the metadata server.
|
||||
if metadata.OnGCE() { |
||||
id, _ := metadata.ProjectID() |
||||
return &DefaultCredentials{ |
||||
ProjectID: id, |
||||
TokenSource: ComputeTokenSource("", scopes...), |
||||
}, nil |
||||
} |
||||
|
||||
// None are found; return helpful error.
|
||||
const url = "https://developers.google.com/accounts/docs/application-default-credentials" |
||||
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url) |
||||
} |
||||
|
||||
// CredentialsFromJSON obtains Google credentials from a JSON value. The JSON can
|
||||
// represent either a Google Developers Console client_credentials.json file (as in
|
||||
// ConfigFromJSON) or a Google Developers service account key file (as in
|
||||
// JWTConfigFromJSON).
|
||||
func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { |
||||
var f credentialsFile |
||||
if err := json.Unmarshal(jsonData, &f); err != nil { |
||||
return nil, err |
||||
} |
||||
ts, err := f.tokenSource(ctx, append([]string(nil), scopes...)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &DefaultCredentials{ |
||||
ProjectID: f.ProjectID, |
||||
TokenSource: ts, |
||||
JSON: jsonData, |
||||
}, nil |
||||
} |
||||
|
||||
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 readCredentialsFile(ctx context.Context, filename string, scopes []string) (*DefaultCredentials, error) { |
||||
b, err := ioutil.ReadFile(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return CredentialsFromJSON(ctx, b, scopes...) |
||||
} |
@ -0,0 +1,40 @@ |
||||
// Copyright 2018 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 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.
|
||||
//
|
||||
// A brief overview of the package follows. For more information, please read
|
||||
// https://developers.google.com/accounts/docs/OAuth2
|
||||
// and
|
||||
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||
//
|
||||
// OAuth2 Configs
|
||||
//
|
||||
// Two functions in this package return golang.org/x/oauth2.Config values from Google credential
|
||||
// data. Google supports two JSON formats for OAuth2 credentials: one is handled by ConfigFromJSON,
|
||||
// the other by JWTConfigFromJSON. The returned Config can be used to obtain a TokenSource or
|
||||
// create an http.Client.
|
||||
//
|
||||
//
|
||||
// Credentials
|
||||
//
|
||||
// The Credentials type represents Google credentials, including Application Default
|
||||
// Credentials.
|
||||
//
|
||||
// Use FindDefaultCredentials to obtain Application Default Credentials.
|
||||
// FindDefaultCredentials looks in some well-known places for a credentials file, and
|
||||
// will call AppEngineTokenSource or ComputeTokenSource as needed.
|
||||
//
|
||||
// DefaultClient and DefaultTokenSource are convenience methods. They first call FindDefaultCredentials,
|
||||
// then use the credentials to construct an http.Client or an oauth2.TokenSource.
|
||||
//
|
||||
// Use CredentialsFromJSON to obtain credentials from either of the two JSON formats
|
||||
// described in OAuth2 Configs, above. The TokenSource in the returned value is the
|
||||
// same as the one obtained from the oauth2.Config returned from ConfigFromJSON or
|
||||
// JWTConfigFromJSON, but the Credentials may contain additional information
|
||||
// that is useful is some circumstances.
|
||||
package google // import "golang.org/x/oauth2/google"
|
@ -0,0 +1,209 @@ |
||||
// 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( |