| // 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 or at |
| // https://developers.google.com/open-source/licenses/bsd. |
| |
| // This file implements a http.RoundTripper that authenticates |
| // requests issued against api.github.com endpoint. |
| |
| package httputil |
| |
| import ( |
| "log" |
| "net/http" |
| "net/url" |
| "os" |
| |
| "cloud.google.com/go/compute/metadata" |
| ) |
| |
| // AuthTransport is an implementation of http.RoundTripper that authenticates |
| // with the GitHub API. |
| // |
| // When both a token and client credentials are set, the latter is preferred. |
| type AuthTransport struct { |
| UserAgent string |
| Token string |
| ClientID string |
| ClientSecret string |
| Base http.RoundTripper |
| } |
| |
| // NewAuthTransport gives new AuthTransport created with GitHub credentials |
| // read from GCE metadata when the metadata server is accessible (we're on GCE) |
| // or read from environment varialbes otherwise. |
| func NewAuthTransport(base http.RoundTripper) *AuthTransport { |
| if metadata.OnGCE() { |
| return NewAuthTransportFromMetadata(base) |
| } |
| return NewAuthTransportFromEnvironment(base) |
| } |
| |
| // NewAuthTransportFromEnvironment gives new AuthTransport created with GitHub |
| // credentials read from environment variables. |
| func NewAuthTransportFromEnvironment(base http.RoundTripper) *AuthTransport { |
| return &AuthTransport{ |
| UserAgent: os.Getenv("USER_AGENT"), |
| Token: os.Getenv("GITHUB_TOKEN"), |
| ClientID: os.Getenv("GITHUB_CLIENT_ID"), |
| ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"), |
| Base: base, |
| } |
| } |
| |
| // NewAuthTransportFromMetadata gives new AuthTransport created with GitHub |
| // credentials read from GCE metadata. |
| func NewAuthTransportFromMetadata(base http.RoundTripper) *AuthTransport { |
| return &AuthTransport{ |
| UserAgent: gceAttr("user-agent"), |
| Token: gceAttr("github-token"), |
| ClientID: gceAttr("github-client-id"), |
| ClientSecret: gceAttr("github-client-secret"), |
| Base: base, |
| } |
| } |
| |
| // RoundTrip implements the http.RoundTripper interface. |
| func (t *AuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
| var reqCopy *http.Request |
| if t.UserAgent != "" { |
| reqCopy = copyRequest(req) |
| reqCopy.Header.Set("User-Agent", t.UserAgent) |
| } |
| if req.URL.Host == "api.github.com" { |
| switch { |
| case t.ClientID != "" && t.ClientSecret != "": |
| if reqCopy == nil { |
| reqCopy = copyRequest(req) |
| } |
| if reqCopy.URL.RawQuery == "" { |
| reqCopy.URL.RawQuery = "client_id=" + t.ClientID + "&client_secret=" + t.ClientSecret |
| } else { |
| reqCopy.URL.RawQuery += "&client_id=" + t.ClientID + "&client_secret=" + t.ClientSecret |
| } |
| case t.Token != "": |
| if reqCopy == nil { |
| reqCopy = copyRequest(req) |
| } |
| reqCopy.Header.Set("Authorization", "token "+t.Token) |
| } |
| } |
| if reqCopy != nil { |
| return t.base().RoundTrip(reqCopy) |
| } |
| return t.base().RoundTrip(req) |
| } |
| |
| // CancelRequest cancels an in-flight request by closing its connection. |
| func (t *AuthTransport) CancelRequest(req *http.Request) { |
| type canceler interface { |
| CancelRequest(req *http.Request) |
| } |
| if cr, ok := t.base().(canceler); ok { |
| cr.CancelRequest(req) |
| } |
| } |
| |
| func (t *AuthTransport) base() http.RoundTripper { |
| if t.Base != nil { |
| return t.Base |
| } |
| return http.DefaultTransport |
| } |
| |
| func gceAttr(name string) string { |
| s, err := metadata.ProjectAttributeValue(name) |
| if err != nil { |
| log.Printf("error querying metadata for %q: %s", name, err) |
| return "" |
| } |
| return s |
| } |
| |
| func copyRequest(req *http.Request) *http.Request { |
| req2 := new(http.Request) |
| *req2 = *req |
| req2.URL = new(url.URL) |
| *req2.URL = *req.URL |
| req2.Header = make(http.Header, len(req.Header)) |
| for k, s := range req.Header { |
| req2.Header[k] = append([]string(nil), s...) |
| } |
| return req2 |
| } |