123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- // Copyright 2011 Google Inc. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
- package main
- import (
- "encoding/gob"
- "errors"
- "flag"
- "fmt"
- "hash/fnv"
- "io/ioutil"
- "log"
- "net/http"
- "net/http/httptest"
- "net/url"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strings"
- "time"
- "golang.org/x/net/context"
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/google"
- )
- // Flags
- var (
- clientID = flag.String("clientid", "", "OAuth 2.0 Client ID. If non-empty, overrides --clientid_file")
- clientIDFile = flag.String("clientid-file", "clientid.dat",
- "Name of a file containing just the project's OAuth 2.0 Client ID from https://developers.google.com/console.")
- secret = flag.String("secret", "", "OAuth 2.0 Client Secret. If non-empty, overrides --secret_file")
- secretFile = flag.String("secret-file", "clientsecret.dat",
- "Name of a file containing just the project's OAuth 2.0 Client Secret from https://developers.google.com/console.")
- cacheToken = flag.Bool("cachetoken", true, "cache the OAuth 2.0 token")
- debug = flag.Bool("debug", false, "show HTTP traffic")
- )
- func usage() {
- fmt.Fprintf(os.Stderr, "Usage: go-api-demo <api-demo-name> [api name args]\n\nPossible APIs:\n\n")
- for n := range demoFunc {
- fmt.Fprintf(os.Stderr, " * %s\n", n)
- }
- os.Exit(2)
- }
- func main() {
- flag.Parse()
- if flag.NArg() == 0 {
- usage()
- }
- name := flag.Arg(0)
- demo, ok := demoFunc[name]
- if !ok {
- usage()
- }
- config := &oauth2.Config{
- ClientID: valueOrFileContents(*clientID, *clientIDFile),
- ClientSecret: valueOrFileContents(*secret, *secretFile),
- Endpoint: google.Endpoint,
- Scopes: []string{demoScope[name]},
- }
- ctx := context.Background()
- if *debug {
- ctx = context.WithValue(ctx, oauth2.HTTPClient, &http.Client{
- Transport: &logTransport{http.DefaultTransport},
- })
- }
- c := newOAuthClient(ctx, config)
- demo(c, flag.Args()[1:])
- }
- var (
- demoFunc = make(map[string]func(*http.Client, []string))
- demoScope = make(map[string]string)
- )
- func registerDemo(name, scope string, main func(c *http.Client, argv []string)) {
- if demoFunc[name] != nil {
- panic(name + " already registered")
- }
- demoFunc[name] = main
- demoScope[name] = scope
- }
- func osUserCacheDir() string {
- switch runtime.GOOS {
- case "darwin":
- return filepath.Join(os.Getenv("HOME"), "Library", "Caches")
- case "linux", "freebsd":
- return filepath.Join(os.Getenv("HOME"), ".cache")
- }
- log.Printf("TODO: osUserCacheDir on GOOS %q", runtime.GOOS)
- return "."
- }
- func tokenCacheFile(config *oauth2.Config) string {
- hash := fnv.New32a()
- hash.Write([]byte(config.ClientID))
- hash.Write([]byte(config.ClientSecret))
- hash.Write([]byte(strings.Join(config.Scopes, " ")))
- fn := fmt.Sprintf("go-api-demo-tok%v", hash.Sum32())
- return filepath.Join(osUserCacheDir(), url.QueryEscape(fn))
- }
- func tokenFromFile(file string) (*oauth2.Token, error) {
- if !*cacheToken {
- return nil, errors.New("--cachetoken is false")
- }
- f, err := os.Open(file)
- if err != nil {
- return nil, err
- }
- t := new(oauth2.Token)
- err = gob.NewDecoder(f).Decode(t)
- return t, err
- }
- func saveToken(file string, token *oauth2.Token) {
- f, err := os.Create(file)
- if err != nil {
- log.Printf("Warning: failed to cache oauth token: %v", err)
- return
- }
- defer f.Close()
- gob.NewEncoder(f).Encode(token)
- }
- func newOAuthClient(ctx context.Context, config *oauth2.Config) *http.Client {
- cacheFile := tokenCacheFile(config)
- token, err := tokenFromFile(cacheFile)
- if err != nil {
- token = tokenFromWeb(ctx, config)
- saveToken(cacheFile, token)
- } else {
- log.Printf("Using cached token %#v from %q", token, cacheFile)
- }
- return config.Client(ctx, token)
- }
- func tokenFromWeb(ctx context.Context, config *oauth2.Config) *oauth2.Token {
- ch := make(chan string)
- randState := fmt.Sprintf("st%d", time.Now().UnixNano())
- ts := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
- if req.URL.Path == "/favicon.ico" {
- http.Error(rw, "", 404)
- return
- }
- if req.FormValue("state") != randState {
- log.Printf("State doesn't match: req = %#v", req)
- http.Error(rw, "", 500)
- return
- }
- if code := req.FormValue("code"); code != "" {
- fmt.Fprintf(rw, "<h1>Success</h1>Authorized.")
- rw.(http.Flusher).Flush()
- ch <- code
- return
- }
- log.Printf("no code")
- http.Error(rw, "", 500)
- }))
- defer ts.Close()
- config.RedirectURL = ts.URL
- authURL := config.AuthCodeURL(randState)
- go openURL(authURL)
- log.Printf("Authorize this app at: %s", authURL)
- code := <-ch
- log.Printf("Got code: %s", code)
- token, err := config.Exchange(ctx, code)
- if err != nil {
- log.Fatalf("Token exchange error: %v", err)
- }
- return token
- }
- func openURL(url string) {
- try := []string{"xdg-open", "google-chrome", "open"}
- for _, bin := range try {
- err := exec.Command(bin, url).Run()
- if err == nil {
- return
- }
- }
- log.Printf("Error opening URL in browser.")
- }
- func valueOrFileContents(value string, filename string) string {
- if value != "" {
- return value
- }
- slurp, err := ioutil.ReadFile(filename)
- if err != nil {
- log.Fatalf("Error reading %q: %v", filename, err)
- }
- return strings.TrimSpace(string(slurp))
- }
|