-
Notifications
You must be signed in to change notification settings - Fork 3k
/
oauth2.go
223 lines (201 loc) · 7.33 KB
/
oauth2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
// This variable indicates whether the script should launch a web server to
// initiate the authorization flow or just display the URL in the terminal
// window. Note the following instructions based on this setting:
// * launchWebServer = true
// 1. Use OAuth2 credentials for a web application
// 2. Define authorized redirect URIs for the credential in the Google APIs
// Console and set the RedirectURL property on the config object to one
// of those redirect URIs. For example:
// config.RedirectURL = "http://localhost:8090"
// 3. In the startWebServer function below, update the URL in this line
// to match the redirect URI you selected:
// listener, err := net.Listen("tcp", "localhost:8090")
// The redirect URI identifies the URI to which the user is sent after
// completing the authorization flow. The listener then captures the
// authorization code in the URL and passes it back to this script.
// * launchWebServer = false
// 1. Use OAuth2 credentials for an installed application. (When choosing
// the application type for the OAuth2 client ID, select "Other".)
// 2. Set the redirect URI to "urn:ietf:wg:oauth:2.0:oob", like this:
// config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob"
// 3. When running the script, complete the auth flow. Then copy the
// authorization code from the browser and enter it on the command line.
const launchWebServer = false
const missingClientSecretsMessage = `
Please configure OAuth 2.0
To make this sample run, you need to populate the client_secrets.json file
found at:
%v
with information from the {{ Google Cloud Console }}
{{ https://cloud.google.com/console }}
For more information about the client_secrets.json file format, please visit:
https://developers.google.com/api-client-library/python/guide/aaa_client_secrets
`
// getClient uses a Context and Config to retrieve a Token
// then generate a Client. It returns the generated Client.
func getClient(scope string) *http.Client {
ctx := context.Background()
b, err := ioutil.ReadFile("client_secret.json")
if err != nil {
log.Fatalf("Unable to read client secret file: %v", err)
}
// If modifying the scope, delete your previously saved credentials
// at ~/.credentials/youtube-go.json
config, err := google.ConfigFromJSON(b, scope)
if err != nil {
log.Fatalf("Unable to parse client secret file to config: %v", err)
}
// Use a redirect URI like this for a web app. The redirect URI must be a
// valid one for your OAuth2 credentials.
config.RedirectURL = "http://localhost:8090"
// Use the following redirect URI if launchWebServer=false in oauth2.go
// config.RedirectURL = "urn:ietf:wg:oauth:2.0:oob"
cacheFile, err := tokenCacheFile()
if err != nil {
log.Fatalf("Unable to get path to cached credential file. %v", err)
}
tok, err := tokenFromFile(cacheFile)
if err != nil {
authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
if launchWebServer {
fmt.Println("Trying to get token from web")
tok, err = getTokenFromWeb(config, authURL)
} else {
fmt.Println("Trying to get token from prompt")
tok, err = getTokenFromPrompt(config, authURL)
}
if err == nil {
saveToken(cacheFile, tok)
}
}
return config.Client(ctx, tok)
}
// startWebServer starts a web server that listens on http://localhost:8080.
// The webserver waits for an oauth code in the three-legged auth flow.
func startWebServer() (codeCh chan string, err error) {
listener, err := net.Listen("tcp", "localhost:8090")
if err != nil {
return nil, err
}
codeCh = make(chan string)
go http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
code := r.FormValue("code")
codeCh <- code // send code to OAuth flow
listener.Close()
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code)
}))
return codeCh, nil
}
// openURL opens a browser window to the specified location.
// This code originally appeared at:
// http://stackoverflow.com/questions/10377243/how-can-i-launch-a-process-that-is-not-a-file-in-go
func openURL(url string) error {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", "http://localhost:4001/").Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("Cannot open URL %s on this platform", url)
}
return err
}
// Exchange the authorization code for an access token
func exchangeToken(config *oauth2.Config, code string) (*oauth2.Token, error) {
tok, err := config.Exchange(oauth2.NoContext, code)
if err != nil {
log.Fatalf("Unable to retrieve token %v", err)
}
return tok, nil
}
// getTokenFromPrompt uses Config to request a Token and prompts the user
// to enter the token on the command line. It returns the retrieved Token.
func getTokenFromPrompt(config *oauth2.Config, authURL string) (*oauth2.Token, error) {
var code string
fmt.Printf("Go to the following link in your browser. After completing " +
"the authorization flow, enter the authorization code on the command " +
"line: \n%v\n", authURL)
if _, err := fmt.Scan(&code); err != nil {
log.Fatalf("Unable to read authorization code %v", err)
}
fmt.Println(authURL)
return exchangeToken(config, code)
}
// getTokenFromWeb uses Config to request a Token.
// It returns the retrieved Token.
func getTokenFromWeb(config *oauth2.Config, authURL string) (*oauth2.Token, error) {
codeCh, err := startWebServer()
if err != nil {
fmt.Printf("Unable to start a web server.")
return nil, err
}
err = openURL(authURL)
if err != nil {
log.Fatalf("Unable to open authorization URL in web server: %v", err)
} else {
fmt.Println("Your browser has been opened to an authorization URL.",
" This program will resume once authorization has been provided.\n")
fmt.Println(authURL)
}
// Wait for the web server to get the code.
code := <-codeCh
return exchangeToken(config, code)
}
// tokenCacheFile generates credential file path/filename.
// It returns the generated credential path/filename.
func tokenCacheFile() (string, error) {
usr, err := user.Current()
if err != nil {
return "", err
}
tokenCacheDir := filepath.Join(usr.HomeDir, ".credentials")
os.MkdirAll(tokenCacheDir, 0700)
return filepath.Join(tokenCacheDir,
url.QueryEscape("youtube-go.json")), err
}
// tokenFromFile retrieves a Token from a given file path.
// It returns the retrieved Token and any read error encountered.
func tokenFromFile(file string) (*oauth2.Token, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
t := &oauth2.Token{}
err = json.NewDecoder(f).Decode(t)
defer f.Close()
return t, err
}
// saveToken uses a file path to create a file and store the
// token in it.
func saveToken(file string, token *oauth2.Token) {
fmt.Println("trying to save token")
fmt.Printf("Saving credential file to: %s\n", file)
f, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
log.Fatalf("Unable to cache oauth token: %v", err)
}
defer f.Close()
json.NewEncoder(f).Encode(token)
}