forked from kubernetes/kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
bump(code.google.com/p/goauth2/compute/serviceaccount): ef170e7cf161b…
…c5644976d13cadc67b285e73ee8
- Loading branch information
1 parent
721f657
commit 45397c4
Showing
1 changed file
with
172 additions
and
0 deletions.
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
third_party/src/code.google.com/p/goauth2/compute/serviceaccount/serviceaccount.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
// Copyright 2013 The goauth2 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 serviceaccount provides support for making OAuth2-authorized | ||
// HTTP requests from Google Compute Engine instances using service accounts. | ||
// | ||
// See: https://developers.google.com/compute/docs/authentication | ||
// | ||
// Example usage: | ||
// | ||
// client, err := serviceaccount.NewClient(&serviceaccount.Options{}) | ||
// if err != nil { | ||
// c.Errorf("failed to create service account client: %q", err) | ||
// return err | ||
// } | ||
// client.Post("https://www.googleapis.com/compute/...", ...) | ||
// client.Post("https://www.googleapis.com/bigquery/...", ...) | ||
// | ||
package serviceaccount | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
"sync" | ||
"time" | ||
|
||
"code.google.com/p/goauth2/oauth" | ||
) | ||
|
||
const ( | ||
metadataServer = "metadata" | ||
serviceAccountPath = "/computeMetadata/v1/instance/service-accounts" | ||
) | ||
|
||
// Options configures a service account Client. | ||
type Options struct { | ||
// Underlying transport of service account Client. | ||
// If nil, http.DefaultTransport is used. | ||
Transport http.RoundTripper | ||
|
||
// Service account name. | ||
// If empty, "default" is used. | ||
Account string | ||
} | ||
|
||
// NewClient returns an *http.Client authorized with the service account | ||
// configured in the Google Compute Engine instance. | ||
func NewClient(opt *Options) (*http.Client, error) { | ||
tr := http.DefaultTransport | ||
account := "default" | ||
if opt != nil { | ||
if opt.Transport != nil { | ||
tr = opt.Transport | ||
} | ||
if opt.Account != "" { | ||
account = opt.Account | ||
} | ||
} | ||
t := &transport{ | ||
Transport: tr, | ||
Account: account, | ||
} | ||
// Get the initial access token. | ||
if _, err := fetchToken(t); err != nil { | ||
return nil, err | ||
} | ||
return &http.Client{ | ||
Transport: t, | ||
}, nil | ||
} | ||
|
||
type tokenData struct { | ||
AccessToken string `json:"access_token"` | ||
ExpiresIn float64 `json:"expires_in"` | ||
TokenType string `json:"token_type"` | ||
} | ||
|
||
// transport is an oauth.Transport with a custom Refresh and RoundTrip implementation. | ||
type transport struct { | ||
Transport http.RoundTripper | ||
Account string | ||
|
||
mu sync.Mutex | ||
*oauth.Token | ||
} | ||
|
||
// Refresh renews the transport's AccessToken. | ||
// t.mu sould be held when this is called. | ||
func (t *transport) refresh() error { | ||
// https://developers.google.com/compute/docs/metadata | ||
// v1 requires "X-Google-Metadata-Request: True" header. | ||
tokenURL := &url.URL{ | ||
Scheme: "http", | ||
Host: metadataServer, | ||
Path: path.Join(serviceAccountPath, t.Account, "token"), | ||
} | ||
req, err := http.NewRequest("GET", tokenURL.String(), nil) | ||
if err != nil { | ||
return err | ||
} | ||
req.Header.Add("X-Google-Metadata-Request", "True") | ||
resp, err := http.DefaultClient.Do(req) | ||
if err != nil { | ||
return err | ||
} | ||
defer resp.Body.Close() | ||
d := json.NewDecoder(resp.Body) | ||
var token tokenData | ||
err = d.Decode(&token) | ||
if err != nil { | ||
return err | ||
} | ||
t.Token = &oauth.Token{ | ||
AccessToken: token.AccessToken, | ||
Expiry: time.Now().Add(time.Duration(token.ExpiresIn) * time.Second), | ||
} | ||
return nil | ||
} | ||
|
||
// Refresh renews the transport's AccessToken. | ||
func (t *transport) Refresh() error { | ||
t.mu.Lock() | ||
defer t.mu.Unlock() | ||
return t.refresh() | ||
} | ||
|
||
// Fetch token from cache or generate a new one if cache miss or expired. | ||
func fetchToken(t *transport) (*oauth.Token, error) { | ||
// Get a new token using Refresh in case of a cache miss of if it has expired. | ||
t.mu.Lock() | ||
defer t.mu.Unlock() | ||
if t.Token == nil || t.Expired() { | ||
if err := t.refresh(); err != nil { | ||
return nil, err | ||
} | ||
} | ||
return t.Token, nil | ||
} | ||
|
||
// cloneRequest returns a clone of the provided *http.Request. | ||
// The clone is a shallow copy of the struct and its Header map. | ||
func cloneRequest(r *http.Request) *http.Request { | ||
// shallow copy of the struct | ||
r2 := new(http.Request) | ||
*r2 = *r | ||
// deep copy of the Header | ||
r2.Header = make(http.Header) | ||
for k, s := range r.Header { | ||
r2.Header[k] = s | ||
} | ||
return r2 | ||
} | ||
|
||
// RoundTrip issues an authorized HTTP request and returns its response. | ||
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
token, err := fetchToken(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// To set the Authorization header, we must make a copy of the Request | ||
// so that we don't modify the Request we were given. | ||
// This is required by the specification of http.RoundTripper. | ||
newReq := cloneRequest(req) | ||
newReq.Header.Set("Authorization", "Bearer "+token.AccessToken) | ||
|
||
// Make the HTTP request. | ||
return t.Transport.RoundTrip(newReq) | ||
} |