Skip to content

Commit

Permalink
Added interface for fetching/management of tokens, and moved token re…
Browse files Browse the repository at this point in the history
…levant code out of Authenticate. This token implementation implements storage of tokens in the filesystem, so the client doesn't have to worry about it.
  • Loading branch information
JustAdam committed Aug 31, 2014
1 parent 8806664 commit f7f2877
Show file tree
Hide file tree
Showing 5 changed files with 178 additions and 91 deletions.
55 changes: 3 additions & 52 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
package streamingtwitter

import (
"errors"
"fmt"
"github.com/garyburd/go-oauth/oauth"
"net/http"
"net/url"
"os"
"time"
)

Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
39 changes: 0 additions & 39 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions test_data/tokens-empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
108 changes: 108 additions & 0 deletions tokens.go
Original file line number Diff line number Diff line change
@@ -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
}
66 changes: 66 additions & 0 deletions tokens_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit f7f2877

Please sign in to comment.