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 }