From a99af9a0919490d76f06d7e416e2483a5184a104 Mon Sep 17 00:00:00 2001 From: Casey Primozic Date: Tue, 29 Oct 2019 03:15:57 -0700 Subject: [PATCH] Support both bearer + consumer tokens for Twitter modules * Add support to both the twitter and twitterstats modules for authenticating using both bearer tokens as well as consumer key + secret. * A bearer token is defaulted to if it's supplied * Add this support to both the twitterstats module as well as to the existing twitter module, modifying its functionality to re-use the same HTTP client and handle authentication upfront via oauth2 --- modules/twitter/client.go | 41 +++++++++++++++++++++++++------- modules/twitter/request.go | 14 ++--------- modules/twitter/settings.go | 16 ++++++++----- modules/twitterstats/client.go | 23 ++++++++++++++---- modules/twitterstats/settings.go | 2 ++ 5 files changed, 64 insertions(+), 32 deletions(-) diff --git a/modules/twitter/client.go b/modules/twitter/client.go index affe0a20..2da812dd 100644 --- a/modules/twitter/client.go +++ b/modules/twitter/client.go @@ -1,9 +1,14 @@ package twitter import ( + "context" "encoding/json" "fmt" + "net/http" "strconv" + + "golang.org/x/oauth2" + "golang.org/x/oauth2/clientcredentials" ) /* NOTE: Currently single application ONLY @@ -12,19 +17,37 @@ import ( // Client represents the data required to connect to the Twitter API type Client struct { - apiBase string - bearerToken string - count int - screenName string + apiBase string + count int + screenName string + httpClient *http.Client } // NewClient creates and returns a new Twitter client func NewClient(settings *Settings) *Client { + var httpClient *http.Client + // If a bearer token is supplied, use that directly. Otherwise, let the Oauth client fetch a token + // using the consumer key and secret. + if settings.bearerToken == "" { + conf := &clientcredentials.Config{ + ClientID: settings.consumerKey, + ClientSecret: settings.consumerSecret, + TokenURL: "https://api.twitter.com/oauth2/token", + } + httpClient = conf.Client(oauth2.NoContext) + } else { + ctx := context.Background() + httpClient = oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: settings.bearerToken, + TokenType: "Bearer", + })) + } + client := Client{ - apiBase: "https://api.twitter.com/1.1/", - count: settings.count, - screenName: "", - bearerToken: settings.bearerToken, + apiBase: "https://api.twitter.com/1.1/", + count: settings.count, + screenName: "", + httpClient: httpClient, } return &client @@ -53,7 +76,7 @@ func (client *Client) tweets() (tweets []Tweet, err error) { strconv.Itoa(client.count), ) - data, err := Request(client.bearerToken, apiURL) + data, err := Request(client.httpClient, apiURL) if err != nil { return tweets, err } diff --git a/modules/twitter/request.go b/modules/twitter/request.go index e20a306c..8c4ee915 100644 --- a/modules/twitter/request.go +++ b/modules/twitter/request.go @@ -2,21 +2,11 @@ package twitter import ( "bytes" - "fmt" "net/http" ) -func Request(bearerToken string, apiURL string) ([]byte, error) { - req, err := http.NewRequest("GET", apiURL, nil) - if err != nil { - return nil, err - } - - // Expected authorization format for single-application twitter dev accounts - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", bearerToken)) - - client := &http.Client{} - resp, err := client.Do(req) +func Request(httpClient *http.Client, apiURL string) ([]byte, error) { + resp, err := httpClient.Get(apiURL) if err != nil { return nil, err } diff --git a/modules/twitter/settings.go b/modules/twitter/settings.go index 60734759..3b4bb8ce 100644 --- a/modules/twitter/settings.go +++ b/modules/twitter/settings.go @@ -15,18 +15,22 @@ const ( type Settings struct { common *cfg.Common - bearerToken string - count int - screenNames []interface{} + bearerToken string + consumerKey string + consumerSecret string + count int + screenNames []interface{} } func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *config.Config) *Settings { settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), - bearerToken: ymlConfig.UString("bearerToken", os.Getenv("WTF_TWITTER_BEARER_TOKEN")), - count: ymlConfig.UInt("count", 5), - screenNames: ymlConfig.UList("screenName"), + bearerToken: ymlConfig.UString("bearerToken", os.Getenv("WTF_TWITTER_BEARER_TOKEN")), + consumerKey: ymlConfig.UString("consumerKey", os.Getenv("WTF_TWITTER_CONSUMER_KEY")), + consumerSecret: ymlConfig.UString("consumerSecret", os.Getenv("WTF_TWITTER_CONSUMER_SECRET")), + count: ymlConfig.UInt("count", 5), + screenNames: ymlConfig.UList("screenName"), } return &settings diff --git a/modules/twitterstats/client.go b/modules/twitterstats/client.go index 6a18a09a..0fc67d78 100644 --- a/modules/twitterstats/client.go +++ b/modules/twitterstats/client.go @@ -1,6 +1,7 @@ package twitterstats import ( + "context" "encoding/json" "fmt" "io/ioutil" @@ -15,6 +16,7 @@ import ( type Client struct { httpClient *http.Client screenNames []string + bearerToken string } // TwitterStats Represents a stats snapshot for a single Twitter user at a point in time @@ -37,12 +39,23 @@ func NewClient(settings *Settings) *Client { } } - conf := &clientcredentials.Config{ - ClientID: settings.consumerKey, - ClientSecret: settings.consumerSecret, - TokenURL: "https://api.twitter.com/oauth2/token", + var httpClient *http.Client + // If a bearer token is supplied, use that directly. Otherwise, let the Oauth client fetch a token + // using the consumer key and secret. + if settings.bearerToken == "" { + conf := &clientcredentials.Config{ + ClientID: settings.consumerKey, + ClientSecret: settings.consumerSecret, + TokenURL: "https://api.twitter.com/oauth2/token", + } + httpClient = conf.Client(oauth2.NoContext) + } else { + ctx := context.Background() + httpClient = oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: settings.bearerToken, + TokenType: "Bearer", + })) } - httpClient := conf.Client(oauth2.NoContext) client := Client{ httpClient: httpClient, diff --git a/modules/twitterstats/settings.go b/modules/twitterstats/settings.go index cd2122af..793ae917 100644 --- a/modules/twitterstats/settings.go +++ b/modules/twitterstats/settings.go @@ -15,6 +15,7 @@ const ( type Settings struct { common *cfg.Common + bearerToken string consumerKey string consumerSecret string screenNames []interface{} @@ -24,6 +25,7 @@ func NewSettingsFromYAML(name string, ymlConfig *config.Config, globalConfig *co settings := Settings{ common: cfg.NewCommonSettingsFromModule(name, defaultTitle, defaultFocusable, ymlConfig, globalConfig), + bearerToken: ymlConfig.UString("bearerToken", os.Getenv("WTF_TWITTER_BEARER_TOKEN")), consumerKey: ymlConfig.UString("consumerKey", os.Getenv("WTF_TWITTER_CONSUMER_KEY")), consumerSecret: ymlConfig.UString("consumerSecret", os.Getenv("WTF_TWITTER_CONSUMER_SECRET")),