github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/third_party/code.google.com/p/goauth2/oauth/oauth.go (about)

     1  // Copyright 2011 The goauth2 Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // The oauth package provides support for making
     6  // OAuth2-authenticated HTTP requests.
     7  //
     8  // Example usage:
     9  //
    10  //	// Specify your configuration. (typically as a global variable)
    11  //	var config = &oauth.Config{
    12  //		ClientId:     YOUR_CLIENT_ID,
    13  //		ClientSecret: YOUR_CLIENT_SECRET,
    14  //		Scope:        "https://www.googleapis.com/auth/buzz",
    15  //		AuthURL:      "https://accounts.google.com/o/oauth2/auth",
    16  //		TokenURL:     "https://accounts.google.com/o/oauth2/token",
    17  //		RedirectURL:  "http://you.example.org/handler",
    18  //	}
    19  //
    20  //	// A landing page redirects to the OAuth provider to get the auth code.
    21  //	func landing(w http.ResponseWriter, r *http.Request) {
    22  //		http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
    23  //	}
    24  //
    25  //	// The user will be redirected back to this handler, that takes the
    26  //	// "code" query parameter and Exchanges it for an access token.
    27  //	func handler(w http.ResponseWriter, r *http.Request) {
    28  //		t := &oauth.Transport{Config: config}
    29  //		t.Exchange(r.FormValue("code"))
    30  //		// The Transport now has a valid Token. Create an *http.Client
    31  //		// with which we can make authenticated API requests.
    32  //		c := t.Client()
    33  //		c.Post(...)
    34  //		// ...
    35  //		// btw, r.FormValue("state") == "foo"
    36  //	}
    37  //
    38  package oauth
    39  
    40  import (
    41  	"encoding/json"
    42  	"io/ioutil"
    43  	"mime"
    44  	"net/http"
    45  	"net/url"
    46  	"os"
    47  	"time"
    48  )
    49  
    50  type OAuthError struct {
    51  	prefix string
    52  	msg    string
    53  }
    54  
    55  func (oe OAuthError) Error() string {
    56  	return "OAuthError: " + oe.prefix + ": " + oe.msg
    57  }
    58  
    59  // Cache specifies the methods that implement a Token cache.
    60  type Cache interface {
    61  	Token() (*Token, error)
    62  	PutToken(*Token) error
    63  }
    64  
    65  // CacheFile implements Cache. Its value is the name of the file in which
    66  // the Token is stored in JSON format.
    67  type CacheFile string
    68  
    69  func (f CacheFile) Token() (*Token, error) {
    70  	file, err := os.Open(string(f))
    71  	if err != nil {
    72  		return nil, OAuthError{"CacheFile.Token", err.Error()}
    73  	}
    74  	defer file.Close()
    75  	tok := &Token{}
    76  	if err := json.NewDecoder(file).Decode(tok); err != nil {
    77  		return nil, OAuthError{"CacheFile.Token", err.Error()}
    78  	}
    79  	return tok, nil
    80  }
    81  
    82  func (f CacheFile) PutToken(tok *Token) error {
    83  	file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
    84  	if err != nil {
    85  		return OAuthError{"CacheFile.PutToken", err.Error()}
    86  	}
    87  	if err := json.NewEncoder(file).Encode(tok); err != nil {
    88  		file.Close()
    89  		return OAuthError{"CacheFile.PutToken", err.Error()}
    90  	}
    91  	if err := file.Close(); err != nil {
    92  		return OAuthError{"CacheFile.PutToken", err.Error()}
    93  	}
    94  	return nil
    95  }
    96  
    97  // Config is the configuration of an OAuth consumer.
    98  type Config struct {
    99  	// ClientId is the OAuth client identifier used when communicating with
   100  	// the configured OAuth provider.
   101  	ClientId string
   102  
   103  	// ClientSecret is the OAuth client secret used when communicating with
   104  	// the configured OAuth provider.
   105  	ClientSecret string
   106  
   107  	// Scope identifies the level of access being requested. Multiple scope
   108  	// values should be provided as a space-delimited string.
   109  	Scope string
   110  
   111  	// AuthURL is the URL the user will be directed to in order to grant
   112  	// access.
   113  	AuthURL string
   114  
   115  	// TokenURL is the URL used to retrieve OAuth tokens.
   116  	TokenURL string
   117  
   118  	// RedirectURL is the URL to which the user will be returned after
   119  	// granting (or denying) access.
   120  	RedirectURL string
   121  
   122  	// TokenCache allows tokens to be cached for subsequent requests.
   123  	TokenCache Cache
   124  
   125  	AccessType string // Optional, "online" (default) or "offline", no refresh token if "online"
   126  
   127  	// ApprovalPrompt indicates whether the user should be
   128  	// re-prompted for consent. If set to "auto" (default) the
   129  	// user will be prompted only if they haven't previously
   130  	// granted consent and the code can only be exchanged for an
   131  	// access token.
   132  	// If set to "force" the user will always be prompted, and the
   133  	// code can be exchanged for a refresh token.
   134  	ApprovalPrompt string
   135  }
   136  
   137  // Token contains an end-user's tokens.
   138  // This is the data you must store to persist authentication.
   139  type Token struct {
   140  	AccessToken  string
   141  	RefreshToken string
   142  	Expiry       time.Time         // If zero the token has no (known) expiry time.
   143  	Extra        map[string]string // May be nil.
   144  }
   145  
   146  func (t *Token) Expired() bool {
   147  	if t.Expiry.IsZero() {
   148  		return false
   149  	}
   150  	return t.Expiry.Before(time.Now())
   151  }
   152  
   153  // Transport implements http.RoundTripper. When configured with a valid
   154  // Config and Token it can be used to make authenticated HTTP requests.
   155  //
   156  //	t := &oauth.Transport{config}
   157  //      t.Exchange(code)
   158  //      // t now contains a valid Token
   159  //	r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
   160  //
   161  // It will automatically refresh the Token if it can,
   162  // updating the supplied Token in place.
   163  type Transport struct {
   164  	*Config
   165  	*Token
   166  
   167  	// Transport is the HTTP transport to use when making requests.
   168  	// It will default to http.DefaultTransport if nil.
   169  	// (It should never be an oauth.Transport.)
   170  	Transport http.RoundTripper
   171  }
   172  
   173  // Client returns an *http.Client that makes OAuth-authenticated requests.
   174  func (t *Transport) Client() *http.Client {
   175  	return &http.Client{Transport: t}
   176  }
   177  
   178  func (t *Transport) transport() http.RoundTripper {
   179  	if t.Transport != nil {
   180  		return t.Transport
   181  	}
   182  	return http.DefaultTransport
   183  }
   184  
   185  // AuthCodeURL returns a URL that the end-user should be redirected to,
   186  // so that they may obtain an authorization code.
   187  func (c *Config) AuthCodeURL(state string) string {
   188  	url_, err := url.Parse(c.AuthURL)
   189  	if err != nil {
   190  		panic("AuthURL malformed: " + err.Error())
   191  	}
   192  	q := url.Values{
   193  		"response_type":   {"code"},
   194  		"client_id":       {c.ClientId},
   195  		"redirect_uri":    {c.RedirectURL},
   196  		"scope":           {c.Scope},
   197  		"state":           {state},
   198  		"access_type":     {c.AccessType},
   199  		"approval_prompt": {c.ApprovalPrompt},
   200  	}.Encode()
   201  	if url_.RawQuery == "" {
   202  		url_.RawQuery = q
   203  	} else {
   204  		url_.RawQuery += "&" + q
   205  	}
   206  	return url_.String()
   207  }
   208  
   209  // Exchange takes a code and gets access Token from the remote server.
   210  func (t *Transport) Exchange(code string) (*Token, error) {
   211  	if t.Config == nil {
   212  		return nil, OAuthError{"Exchange", "no Config supplied"}
   213  	}
   214  
   215  	// If the transport or the cache already has a token, it is
   216  	// passed to `updateToken` to preserve existing refresh token.
   217  	tok := t.Token
   218  	if tok == nil && t.TokenCache != nil {
   219  		tok, _ = t.TokenCache.Token()
   220  	}
   221  	if tok == nil {
   222  		tok = new(Token)
   223  	}
   224  	err := t.updateToken(tok, url.Values{
   225  		"grant_type":   {"authorization_code"},
   226  		"redirect_uri": {t.RedirectURL},
   227  		"scope":        {t.Scope},
   228  		"code":         {code},
   229  	})
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	t.Token = tok
   234  	if t.TokenCache != nil {
   235  		return tok, t.TokenCache.PutToken(tok)
   236  	}
   237  	return tok, nil
   238  }
   239  
   240  // RoundTrip executes a single HTTP transaction using the Transport's
   241  // Token as authorization headers.
   242  //
   243  // This method will attempt to renew the Token if it has expired and may return
   244  // an error related to that Token renewal before attempting the client request.
   245  // If the Token cannot be renewed a non-nil os.Error value will be returned.
   246  // If the Token is invalid callers should expect HTTP-level errors,
   247  // as indicated by the Response's StatusCode.
   248  func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
   249  	if t.Token == nil {
   250  		if t.Config == nil {
   251  			return nil, OAuthError{"RoundTrip", "no Config supplied"}
   252  		}
   253  		if t.TokenCache == nil {
   254  			return nil, OAuthError{"RoundTrip", "no Token supplied"}
   255  		}
   256  		var err error
   257  		t.Token, err = t.TokenCache.Token()
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  	}
   262  
   263  	// Refresh the Token if it has expired.
   264  	if t.Expired() {
   265  		if err := t.Refresh(); err != nil {
   266  			return nil, err
   267  		}
   268  	}
   269  
   270  	// To set the Authorization header, we must make a copy of the Request
   271  	// so that we don't modify the Request we were given.
   272  	// This is required by the specification of http.RoundTripper.
   273  	req = cloneRequest(req)
   274  	req.Header.Set("Authorization", "Bearer "+t.AccessToken)
   275  
   276  	// Make the HTTP request.
   277  	return t.transport().RoundTrip(req)
   278  }
   279  
   280  // cloneRequest returns a clone of the provided *http.Request.
   281  // The clone is a shallow copy of the struct and its Header map.
   282  func cloneRequest(r *http.Request) *http.Request {
   283  	// shallow copy of the struct
   284  	r2 := new(http.Request)
   285  	*r2 = *r
   286  	// deep copy of the Header
   287  	r2.Header = make(http.Header)
   288  	for k, s := range r.Header {
   289  		r2.Header[k] = s
   290  	}
   291  	return r2
   292  }
   293  
   294  // Refresh renews the Transport's AccessToken using its RefreshToken.
   295  func (t *Transport) Refresh() error {
   296  	if t.Token == nil {
   297  		return OAuthError{"Refresh", "no existing Token"}
   298  	}
   299  	if t.RefreshToken == "" {
   300  		return OAuthError{"Refresh", "Token expired; no Refresh Token"}
   301  	}
   302  	if t.Config == nil {
   303  		return OAuthError{"Refresh", "no Config supplied"}
   304  	}
   305  
   306  	err := t.updateToken(t.Token, url.Values{
   307  		"grant_type":    {"refresh_token"},
   308  		"refresh_token": {t.RefreshToken},
   309  	})
   310  	if err != nil {
   311  		return err
   312  	}
   313  	if t.TokenCache != nil {
   314  		return t.TokenCache.PutToken(t.Token)
   315  	}
   316  	return nil
   317  }
   318  
   319  func (t *Transport) updateToken(tok *Token, v url.Values) error {
   320  	v.Set("client_id", t.ClientId)
   321  	v.Set("client_secret", t.ClientSecret)
   322  	r, err := (&http.Client{Transport: t.transport()}).PostForm(t.TokenURL, v)
   323  	if err != nil {
   324  		return err
   325  	}
   326  	defer r.Body.Close()
   327  	if r.StatusCode != 200 {
   328  		return OAuthError{"updateToken", r.Status}
   329  	}
   330  	var b struct {
   331  		Access    string        `json:"access_token"`
   332  		Refresh   string        `json:"refresh_token"`
   333  		ExpiresIn time.Duration `json:"expires_in"`
   334  		Id        string        `json:"id_token"`
   335  	}
   336  
   337  	content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
   338  	switch content {
   339  	case "application/x-www-form-urlencoded", "text/plain":
   340  		body, err := ioutil.ReadAll(r.Body)
   341  		if err != nil {
   342  			return err
   343  		}
   344  		vals, err := url.ParseQuery(string(body))
   345  		if err != nil {
   346  			return err
   347  		}
   348  
   349  		b.Access = vals.Get("access_token")
   350  		b.Refresh = vals.Get("refresh_token")
   351  		b.ExpiresIn, _ = time.ParseDuration(vals.Get("expires_in") + "s")
   352  		b.Id = vals.Get("id_token")
   353  	default:
   354  		if err = json.NewDecoder(r.Body).Decode(&b); err != nil {
   355  			return err
   356  		}
   357  		// The JSON parser treats the unitless ExpiresIn like 'ns' instead of 's' as above,
   358  		// so compensate here.
   359  		b.ExpiresIn *= time.Second
   360  	}
   361  	tok.AccessToken = b.Access
   362  	// Don't overwrite `RefreshToken` with an empty value
   363  	if len(b.Refresh) > 0 {
   364  		tok.RefreshToken = b.Refresh
   365  	}
   366  	if b.ExpiresIn == 0 {
   367  		tok.Expiry = time.Time{}
   368  	} else {
   369  		tok.Expiry = time.Now().Add(b.ExpiresIn)
   370  	}
   371  	if b.Id != "" {
   372  		if tok.Extra == nil {
   373  			tok.Extra = make(map[string]string)
   374  		}
   375  		tok.Extra["id_token"] = b.Id
   376  	}
   377  	return nil
   378  }