From f7f287756211d51da34def579d1abb2025f0897b Mon Sep 17 00:00:00 2001 From: JustAdam Date: Sun, 31 Aug 2014 09:55:56 +0200 Subject: [PATCH] Added interface for fetching/management of tokens, and moved token relevant code out of Authenticate. This token implementation implements storage of tokens in the filesystem, so the client doesn't have to worry about it. --- client.go | 55 +----------------- client_test.go | 39 ------------- test_data/tokens-empty.json | 1 + tokens.go | 108 ++++++++++++++++++++++++++++++++++++ tokens_test.go | 66 ++++++++++++++++++++++ 5 files changed, 178 insertions(+), 91 deletions(-) create mode 100644 test_data/tokens-empty.json create mode 100644 tokens.go create mode 100644 tokens_test.go diff --git a/client.go b/client.go index 48875bd..89d9d1b 100644 --- a/client.go +++ b/client.go @@ -6,12 +6,10 @@ package streamingtwitter import ( - "errors" "fmt" "github.com/garyburd/go-oauth/oauth" "net/http" "net/url" - "os" "time" ) @@ -34,14 +32,6 @@ type StreamClient struct { Finished chan struct{} } -// ClientTokens provide the relevant tokens the client needs to access Twitter. -type ClientTokens struct { - // Token for the actual application - App *oauth.Credentials - // Token for the user of the application - User *oauth.Credentials -} - // A TwitterAPIURL provides details on how to access Twitter API URLs. type TwitterAPIURL struct { // HTTP method which should be used to access the method (currently only get, post & custom is supported) @@ -201,54 +191,15 @@ func NewClient() (client *StreamClient) { ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize", TokenRequestURI: "https://api.twitter.com/oauth/access_token", } - client.Errors = make(chan error) client.Finished = make(chan struct{}) return } // Authenicate the app and user, with Twitter using the oauth client. -// You get a token for your App from Twitter. The user's token will be requested -// and returned if it is not supplied. -func (s *StreamClient) Authenticate(tokens *ClientTokens) (*oauth.Credentials, error) { - if tokens.App == nil { - return nil, errors.New("missing App token") - } - s.oauthClient.Credentials = *tokens.App - if s.oauthClient.Credentials.Token == "" || s.oauthClient.Credentials.Secret == "" { - return nil, errors.New("missing app's Token or Secret") - } - - // Check for token information from the user (they need to grant your app access for feed access) - var token *oauth.Credentials - if tokens.User == nil { - token = &oauth.Credentials{} - } else { - token = tokens.User - } - if token.Token == "" || token.Secret == "" { - tempCredentials, err := s.oauthClient.RequestTemporaryCredentials(http.DefaultClient, "oob", nil) - if err != nil { - return nil, err - } - - url := s.oauthClient.AuthorizationURL(tempCredentials, nil) - fmt.Fprintf(os.Stdout, "Before we can continue ...\nGo to:\n\n\t%s\n\nAuthorize the application and enter in the verification code: ", url) - - var authCode string - fmt.Scanln(&authCode) - - token, _, err := s.oauthClient.RequestToken(http.DefaultClient, tempCredentials, authCode) - if err != nil { - return nil, err - } - - s.token = token - return token, nil - } - - s.token = token - return nil, nil +func (s *StreamClient) Authenticate(t Tokener) (err error) { + s.token, err = t.Token(s.oauthClient) + return } // Send a request to Twitter. diff --git a/client_test.go b/client_test.go index 16fe1e6..31c347d 100644 --- a/client_test.go +++ b/client_test.go @@ -10,45 +10,6 @@ import ( "testing" ) -func TestAuthenticateMissingAppDataError(t *testing.T) { - client := NewClient() - - tokens := &ClientTokens{} - _, err := client.Authenticate(tokens) - if err.Error() != "missing App token" { - t.Errorf("Expecting error \"Missing App token\", got %v", err) - } -} - -func TestAuthenticateMissingAppTokenSecretError(t *testing.T) { - client := NewClient() - - tokens := &ClientTokens{App: &oauth.Credentials{}, User: &oauth.Credentials{}} - _, err := client.Authenticate(tokens) - if err.Error() != "missing app's Token or Secret" { - t.Errorf("Expecting error \"Missing app's Token or Secret\", got %v", err) - } -} - -func TestAuthenticateAccessTokenIsSetInFile(t *testing.T) { - client := NewClient() - - tokens := &ClientTokens{ - App: &oauth.Credentials{ - Token: "app-token", - Secret: "app-secret", - }, - User: &oauth.Credentials{ - Token: "user-token", - Secret: "user-secret", - }, - } - client.Authenticate(tokens) - if client.token.Token != "user-token" || client.token.Secret != "user-secret" { - t.Errorf("Client access token not set.") - } -} - func TestTwitterErrorOutput(t *testing.T) { err := &TwitterError{ ID: 101, diff --git a/test_data/tokens-empty.json b/test_data/tokens-empty.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test_data/tokens-empty.json @@ -0,0 +1 @@ +{} diff --git a/tokens.go b/tokens.go new file mode 100644 index 0000000..1ce14ad --- /dev/null +++ b/tokens.go @@ -0,0 +1,108 @@ +package streamingtwitter + +import ( + "encoding/json" + "fmt" + "github.com/garyburd/go-oauth/oauth" + "io/ioutil" + "net/http" + "os" +) + +var ( + // File permissions for the token file. + tokenFilePermission = os.FileMode(0600) +) + +type Tokener interface { + // Token returns a valid user access token to provide access to Twitter. + // This method also needs to set the app token so valid requests can be made. + Token(*oauth.Client) (*oauth.Credentials, error) +} + +type ClientTokens struct { + // Location to our token storage file (JSON format) + TokenFile string `json:"-"` + // Token for the actual application + App *oauth.Credentials + // Token for the user of the application + User *oauth.Credentials +} + +type ClientTokensError struct { + Msg string +} + +func (e ClientTokensError) Error() string { + return e.Msg +} + +// You get a token for your App from Twitter. Put this within the App section +// of the JSON token file. The user's token will be requested, then written +// and saved to this file. +func (t *ClientTokens) Token(oc *oauth.Client) (*oauth.Credentials, error) { + if t.TokenFile == "" { + return nil, &ClientTokensError{ + Msg: "no token file supplied", + } + } + + cf, err := ioutil.ReadFile(t.TokenFile) + if err != nil { + return nil, err + } + if err := json.Unmarshal(cf, t); err != nil { + return nil, err + } + + if t.App == nil { + return nil, &ClientTokensError{ + Msg: "missing \"App\" token", + } + } + + if t.App.Token == "" || t.App.Secret == "" { + return nil, &ClientTokensError{ + Msg: "missing app's Token or Secret", + } + } + oc.Credentials = *t.App + + var token *oauth.Credentials + if t.User == nil { + token = &oauth.Credentials{} + } else { + token = t.User + } + + if token.Token == "" || token.Secret == "" { + tempCredentials, err := oc.RequestTemporaryCredentials(http.DefaultClient, "oob", nil) + if err != nil { + return nil, err + } + + url := oc.AuthorizationURL(tempCredentials, nil) + fmt.Fprintf(os.Stdout, "Before we can continue ...\nGo to:\n\n\t%s\n\nAuthorize the application and enter in the verification code: ", url) + + var authCode string + fmt.Scanln(&authCode) + + token, _, err = oc.RequestToken(http.DefaultClient, tempCredentials, authCode) + if err != nil { + return nil, err + } + + // Save the user token within our token file + t.User = token + save, err := json.Marshal(t) + if err != nil { + return nil, err + } + + if err := ioutil.WriteFile(t.TokenFile, save, tokenFilePermission); err != nil { + return nil, err + } + } + + return token, nil +} diff --git a/tokens_test.go b/tokens_test.go new file mode 100644 index 0000000..393430d --- /dev/null +++ b/tokens_test.go @@ -0,0 +1,66 @@ +// Copyright 2014 JustAdam (adambell7@gmail.com). All rights reserved. +// License: MIT +package streamingtwitter + +import ( + "github.com/garyburd/go-oauth/oauth" + "testing" +) + +func TestTokenAbsentTokenFileStringError(t *testing.T) { + tokens := &ClientTokens{} + _, err := tokens.Token(&oauth.Client{}) + if err.Error() != "no token file supplied" { + t.Errorf("Expecting error \"no token file supplied\", got %v", err) + } +} + +func TestTokenMissingAppDataError(t *testing.T) { + tokens := &ClientTokens{ + TokenFile: "test_data/tokens-empty.json", + } + _, err := tokens.Token(&oauth.Client{}) + if err.Error() != "missing \"App\" token" { + t.Errorf("Expecting error \"missing \"App\" token\", got %v", err) + } +} + +func TestTokenMissingAppTokenSecretError(t *testing.T) { + tokens := &ClientTokens{ + TokenFile: "test_data/tokens-empty.json", + App: &oauth.Credentials{}, + User: &oauth.Credentials{}, + } + _, err := tokens.Token(&oauth.Client{}) + if err.Error() != "missing app's Token or Secret" { + t.Errorf("Expecting error \"Missing app's Token or Secret\", got %v", err) + } +} + +func TestTokenAccessTokenIsSetInFile(t *testing.T) { + tokens := &ClientTokens{ + TokenFile: "test_data/tokens-empty.json", + App: &oauth.Credentials{ + Token: "app-token", + Secret: "app-secret", + }, + User: &oauth.Credentials{ + Token: "user-token", + Secret: "user-secret", + }, + } + token, _ := tokens.Token(&oauth.Client{}) + if token.Token != "user-token" || token.Secret != "user-secret" { + t.Errorf("Client access token not set.") + } +} + +func TestTokenErrorOutput(t *testing.T) { + err := &ClientTokensError{ + Msg: "error message", + } + + if err.Error() != "error message" { + t.Errorf("Expecting \"error message\", got %v", err) + } +}