From 8c52a7a288a9501b2ee4111cb446ef3ab741d055 Mon Sep 17 00:00:00 2001 From: Chris Cummer Date: Wed, 11 Jul 2018 17:33:49 -0700 Subject: [PATCH] Revert "gcal module improvements" --- gcal/client.go | 106 +++++++++++++++++++++++++++++++++++++++++++----- gcal/google.go | 107 ------------------------------------------------- wtf.go | 2 +- 3 files changed, 98 insertions(+), 117 deletions(-) delete mode 100644 gcal/google.go diff --git a/gcal/client.go b/gcal/client.go index b34a249d..4523ebb7 100644 --- a/gcal/client.go +++ b/gcal/client.go @@ -8,28 +8,43 @@ package gcal import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "os/user" + "path/filepath" "sort" "time" "github.com/senorprogrammer/wtf/wtf" + "golang.org/x/oauth2" + "golang.org/x/oauth2/google" "google.golang.org/api/calendar/v3" ) /* -------------------- Exported Functions -------------------- */ -func Fetch(oauthHandler OAuthUIHandler) ([]*CalEvent, error) { - secretFile, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.secretFile")) - tokenFile, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.tokenFile", "~/.gcal.token")) - config := OAuthClientConfig{ - SecretFile: secretFile, - TokenFile: tokenFile, - Scope: calendar.CalendarReadonlyScope, - } - client, err := BuildOAuthClient(config, oauthHandler) +func Fetch() ([]*CalEvent, error) { + ctx := context.Background() + + secretPath, _ := wtf.ExpandHomeDir(wtf.Config.UString("wtf.mods.gcal.secretFile")) + + b, err := ioutil.ReadFile(secretPath) if err != nil { return nil, err } + config, err := google.ConfigFromJSON(b, calendar.CalendarReadonlyScope) + if err != nil { + return nil, err + } + client := getClient(ctx, config) + srv, err := calendar.New(client) if err != nil { return nil, err @@ -85,6 +100,79 @@ func fromMidnight() time.Time { return time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) } +// getClient uses a Context and Config to retrieve a Token +// then generate a Client. It returns the generated Client. +func getClient(ctx context.Context, config *oauth2.Config) *http.Client { + 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 { + tok = getTokenFromWeb(config) + saveToken(cacheFile, tok) + } + return config.Client(ctx, tok) +} + +// getTokenFromWeb uses Config to request a Token. +// It returns the retrieved Token. +func getTokenFromWeb(config *oauth2.Config) *oauth2.Token { + authURL := config.AuthCodeURL("state-token", oauth2.AccessTypeOffline) + fmt.Printf("Go to the following link in your browser then type the "+ + "authorization code: \n%v\n", authURL) + + var code string + if _, err := fmt.Scan(&code); err != nil { + log.Fatalf("Unable to read authorization code %v", err) + } + + tok, err := config.Exchange(oauth2.NoContext, code) + if err != nil { + log.Fatalf("Unable to retrieve token from web %v", err) + } + return tok +} + +// 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("calendar-go-quickstart.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.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) +} + func getCalendarIdList(srv *calendar.Service) ([]string, error) { // Return single calendar if settings specify we should if !wtf.Config.UBool("wtf.mods.gcal.multiCalendar", false) { diff --git a/gcal/google.go b/gcal/google.go deleted file mode 100644 index 1c1dfa08..00000000 --- a/gcal/google.go +++ /dev/null @@ -1,107 +0,0 @@ -package gcal - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "os" - - "github.com/senorprogrammer/wtf/logger" - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" -) - -// OAuthClientConfig describes Google oauth2 client configuration -type OAuthClientConfig struct { - SecretFile string - TokenFile string - Scope string -} - -// OAuthUIHandler returns oauth2 authorization code, implements user interaction -type OAuthUIHandler = func(string, chan string) - -func BuildOAuthClient(config OAuthClientConfig, handler OAuthUIHandler) (*http.Client, error) { - ctx := context.Background() - - b, err := ioutil.ReadFile(config.SecretFile) - if err != nil { - logger.Log(fmt.Sprintf("Invalid secret file provided (%s): %v", config.SecretFile, err)) - return nil, err - } - - oauthConfig, err := google.ConfigFromJSON(b, config.Scope) - if err != nil { - logger.Log(fmt.Sprintf("Secret file is not readable as JSON (%s): %v", config.SecretFile, err)) - return nil, err - } - - tok, err := tokenFromFile(config.TokenFile) - if err != nil { - result := make(chan string) - go handler(oauthConfig.AuthCodeURL("state-token", oauth2.AccessTypeOffline), result) - code, ok := <-result - if !ok { - // Cancelled - return nil, err - } - tok, err = oauthConfig.Exchange(oauth2.NoContext, code) - if err != nil { - logger.Log(fmt.Sprintf("Invalid exchange code provided (%s): %v", code, err)) - return nil, err - } - err = saveToken(config.TokenFile, tok) - if err != nil { - logger.Log(fmt.Sprintf("Unable to store oauth token (%s): %v", config.TokenFile, err)) - } - - } - return oauthConfig.Client(ctx, tok), nil -} - -// CreateCodeInputDialog shows oauth2 code input dialog -func CreateCodeInputDialog(title string, widget *Widget) OAuthUIHandler { - return func(url string, result chan string) { - readInput := func() { - fmt.Printf("\r\nOAuth authorization [%s]\r\n", title) - fmt.Printf("Please open the following URL in your browser and paste authorization code below:\r\n") - fmt.Printf("%s\r\nAuthorization code: ", url) - var code string - if _, err := fmt.Scan(&code); err != nil { - logger.Log(fmt.Sprintf("Unable to read authorization code: %v", err)) - close(result) - } else { - result <- code - } - } - widget.app.Suspend(readInput) - } -} - -// 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) error { - // 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 { - return err - } - defer f.Close() - json.NewEncoder(f).Encode(token) - return nil -} diff --git a/wtf.go b/wtf.go index ad536aaa..c7709776 100644 --- a/wtf.go +++ b/wtf.go @@ -180,7 +180,7 @@ func addWidget(app *tview.Application, pages *tview.Pages, widgetName string) { case "cryptolive": Widgets = append(Widgets, cryptolive.NewWidget()) case "gcal": - Widgets = append(Widgets, gcal.NewWidget(app)) + Widgets = append(Widgets, gcal.NewWidget()) case "gerrit": Widgets = append(Widgets, gerrit.NewWidget(app, pages)) case "git":