~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/go4/oauthutil/oauth.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
Copyright 2015 The Camlistore Authors
 
3
 
 
4
Licensed under the Apache License, Version 2.0 (the "License");
 
5
you may not use this file except in compliance with the License.
 
6
You may obtain a copy of the License at
 
7
 
 
8
     http://www.apache.org/licenses/LICENSE-2.0
 
9
 
 
10
Unless required by applicable law or agreed to in writing, software
 
11
distributed under the License is distributed on an "AS IS" BASIS,
 
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
See the License for the specific language governing permissions and
 
14
limitations under the License.
 
15
*/
 
16
 
 
17
// Package oauthutil contains OAuth 2 related utilities.
 
18
package oauthutil
 
19
 
 
20
import (
 
21
        "encoding/json"
 
22
        "errors"
 
23
        "fmt"
 
24
        "time"
 
25
 
 
26
        "github.com/juju/go4/wkfs"
 
27
        "golang.org/x/oauth2"
 
28
)
 
29
 
 
30
// TitleBarRedirectURL is the OAuth2 redirect URL to use when the authorization
 
31
// code should be returned in the title bar of the browser, with the page text
 
32
// prompting the user to copy the code and paste it in the application.
 
33
const TitleBarRedirectURL = "urn:ietf:wg:oauth:2.0:oob"
 
34
 
 
35
// ErrNoAuthCode is returned when Token() has not found any valid cached token
 
36
// and TokenSource does not have an AuthCode for getting a new token.
 
37
var ErrNoAuthCode = errors.New("oauthutil: unspecified TokenSource.AuthCode")
 
38
 
 
39
// TokenSource is an implementation of oauth2.TokenSource. It uses CacheFile to store and
 
40
// reuse the the acquired token, and AuthCode to provide the authorization code that will be
 
41
// exchanged for a token otherwise.
 
42
type TokenSource struct {
 
43
        Config *oauth2.Config
 
44
 
 
45
        // CacheFile is where the token will be stored JSON-encoded. Any call to Token
 
46
        // first tries to read a valid token from CacheFile.
 
47
        CacheFile string
 
48
 
 
49
        // AuthCode provides the authorization code that Token will exchange for a token.
 
50
        // It usually is a way to prompt the user for the code. If CacheFile does not provide
 
51
        // a token and AuthCode is nil, Token returns ErrNoAuthCode.
 
52
        AuthCode func() string
 
53
}
 
54
 
 
55
var errExpiredToken = errors.New("expired token")
 
56
 
 
57
// cachedToken returns the token saved in cacheFile. It specifically returns
 
58
// errTokenExpired if the token is expired.
 
59
func cachedToken(cacheFile string) (*oauth2.Token, error) {
 
60
        tok := new(oauth2.Token)
 
61
        tokenData, err := wkfs.ReadFile(cacheFile)
 
62
        if err != nil {
 
63
                return nil, err
 
64
        }
 
65
        if err = json.Unmarshal(tokenData, tok); err != nil {
 
66
                return nil, err
 
67
        }
 
68
        if !tok.Valid() {
 
69
                if tok != nil && time.Now().After(tok.Expiry) {
 
70
                        return nil, errExpiredToken
 
71
                }
 
72
                return nil, errors.New("invalid token")
 
73
        }
 
74
        return tok, nil
 
75
}
 
76
 
 
77
// Token first tries to find a valid token in CacheFile, and otherwise uses
 
78
// Config and AuthCode to fetch a new token. This new token is saved in CacheFile
 
79
// (if not blank). If CacheFile did not provide a token and AuthCode is nil,
 
80
// ErrNoAuthCode is returned.
 
81
func (src TokenSource) Token() (*oauth2.Token, error) {
 
82
        var tok *oauth2.Token
 
83
        var err error
 
84
        if src.CacheFile != "" {
 
85
                tok, err = cachedToken(src.CacheFile)
 
86
                if err == nil {
 
87
                        return tok, nil
 
88
                }
 
89
                if err != errExpiredToken {
 
90
                        fmt.Printf("Error getting token from %s: %v\n", src.CacheFile, err)
 
91
                }
 
92
        }
 
93
        if src.AuthCode == nil {
 
94
                return nil, ErrNoAuthCode
 
95
        }
 
96
        tok, err = src.Config.Exchange(oauth2.NoContext, src.AuthCode())
 
97
        if err != nil {
 
98
                return nil, fmt.Errorf("could not exchange auth code for a token: %v", err)
 
99
        }
 
100
        if src.CacheFile == "" {
 
101
                return tok, nil
 
102
        }
 
103
        tokenData, err := json.Marshal(&tok)
 
104
        if err != nil {
 
105
                return nil, fmt.Errorf("could not encode token as json: %v", err)
 
106
        }
 
107
        if err := wkfs.WriteFile(src.CacheFile, tokenData, 0600); err != nil {
 
108
                return nil, fmt.Errorf("could not cache token in %v: %v", src.CacheFile, err)
 
109
        }
 
110
        return tok, nil
 
111
}
 
112
 
 
113
// NewRefreshTokenSource returns a token source that obtains its initial token
 
114
// based on the provided config and the refresh token.
 
115
func NewRefreshTokenSource(config *oauth2.Config, refreshToken string) oauth2.TokenSource {
 
116
        var noInitialToken *oauth2.Token = nil
 
117
        return oauth2.ReuseTokenSource(noInitialToken, config.TokenSource(
 
118
                oauth2.NoContext, // TODO: maybe accept a context later.
 
119
                &oauth2.Token{RefreshToken: refreshToken},
 
120
        ))
 
121
}