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