twurl 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,22 @@
1
+ module Twurl
2
+ class ConfigurationController < AbstractCommandController
3
+ UNRECOGNIZED_SETTING_MESSAGE = "Unknown configuration setting: '%s'"
4
+ def dispatch
5
+ case options.subcommands.first
6
+ when 'default'
7
+ if profile = case options.subcommands.size
8
+ when 2
9
+ OAuthClient.load_client_for_username(options.subcommands.last)
10
+ when 3
11
+ OAuthClient.load_client_for_username_and_consumer_key(*options.subcommands[-2, 2])
12
+ end
13
+
14
+ OAuthClient.rcfile.default_profile = profile
15
+ OAuthClient.rcfile.save
16
+ end
17
+ else
18
+ CLI.puts(UNRECOGNIZED_SETTING_MESSAGE % options.subcommands.first)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,157 @@
1
+ module Twurl
2
+ class OAuthClient
3
+ class << self
4
+ def rcfile(reload = false)
5
+ if reload || @rcfile.nil?
6
+ @rcfile = RCFile.new
7
+ end
8
+ @rcfile
9
+ end
10
+
11
+ def load_from_options(options)
12
+ if rcfile.has_oauth_profile_for_username_with_consumer_key?(options.username, options.consumer_key)
13
+ load_client_for_username_and_consumer_key(options.username, options.consumer_key)
14
+ else
15
+ options.username ? load_new_client_from_options(options) : load_default_client
16
+ end
17
+ end
18
+
19
+ def load_client_for_username_and_consumer_key(username, consumer_key)
20
+ user_profiles = rcfile[username]
21
+ if user_profiles && attributes = user_profiles[consumer_key]
22
+ new(attributes)
23
+ else
24
+ raise Exception, "No profile for #{username}"
25
+ end
26
+ end
27
+
28
+ def load_client_for_username(username)
29
+ if user_profiles = rcfile[username]
30
+ if user_profiles.values.size == 1
31
+ new(user_profiles.values.first)
32
+ else
33
+ raise Exception, "There is more than one consumer key associated with #{username}. Please specify which consumer key you want as well."
34
+ end
35
+ else
36
+ raise Exception, "No profile for #{username}"
37
+ end
38
+ end
39
+
40
+ def load_new_client_from_options(options)
41
+ new(options.oauth_client_options.merge('password' => options.password))
42
+ end
43
+
44
+ def load_default_client
45
+ raise Exception, "You must authorize first" unless rcfile.default_profile
46
+ load_client_for_username_and_consumer_key(*rcfile.default_profile)
47
+ end
48
+ end
49
+
50
+ OAUTH_CLIENT_OPTIONS = %w[username consumer_key consumer_secret token secret]
51
+ attr_reader *OAUTH_CLIENT_OPTIONS
52
+ attr_reader :password
53
+ def initialize(options = {})
54
+ @username = options['username']
55
+ @password = options['password']
56
+ @consumer_key = options['consumer_key']
57
+ @consumer_secret = options['consumer_secret']
58
+ @token = options['token']
59
+ @secret = options['secret']
60
+ configure_http!
61
+ end
62
+
63
+ [:get, :post, :put, :delete, :options, :head, :copy].each do |request_method|
64
+ class_eval(<<-EVAL, __FILE__, __LINE__)
65
+ def #{request_method}(url, options = {})
66
+ # configure_http!
67
+ access_token.#{request_method}(url, options)
68
+ end
69
+ EVAL
70
+ end
71
+
72
+ def perform_request_from_options(options)
73
+ send(options.request_method, options.path, options.data)
74
+ end
75
+
76
+ def exchange_credentials_for_access_token
77
+ response = begin
78
+ consumer.token_request(:post, consumer.access_token_path, nil, {}, client_auth_parameters)
79
+ rescue OAuth::Unauthorized
80
+ perform_pin_authorize_workflow
81
+ end
82
+ @token = response[:oauth_token]
83
+ @secret = response[:oauth_token_secret]
84
+ end
85
+
86
+ def client_auth_parameters
87
+ {:x_auth_username => username, :x_auth_password => password, :x_auth_mode => 'client_auth'}
88
+ end
89
+
90
+ def perform_pin_authorize_workflow
91
+ @request_token = consumer.get_request_token
92
+ CLI.puts("Go to #{generate_authorize_url} and paste in the supplied PIN")
93
+ pin = gets
94
+ access_token = @request_token.get_access_token(:oauth_verifier => pin.chomp)
95
+ {:oauth_token => access_token.token, :oauth_token_secret => access_token.secret}
96
+ end
97
+
98
+ def generate_authorize_url
99
+ request = consumer.create_signed_request(:get, consumer.authorize_path, @request_token, pin_auth_parameters)
100
+ params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map { |p|
101
+ k, v = p.split('=')
102
+ v =~ /"(.*?)"/
103
+ "#{k}=#{CGI::escape($1)}"
104
+ }.join('&')
105
+ "#{Twurl.options.base_url}#{request.path}?#{params}"
106
+ end
107
+
108
+ def pin_auth_parameters
109
+ {:oauth_callback => 'oob'}
110
+ end
111
+
112
+ def authorized?
113
+ oauth_response = access_token.get('/1/account/verify_credentials.json')
114
+ oauth_response.class == Net::HTTPOK
115
+ end
116
+
117
+ def needs_to_authorize?
118
+ token.nil? || secret.nil?
119
+ end
120
+
121
+ def save
122
+ self.class.rcfile << self
123
+ end
124
+
125
+ def to_hash
126
+ OAUTH_CLIENT_OPTIONS.inject({}) do |hash, attribute|
127
+ if value = send(attribute)
128
+ hash[attribute] = value
129
+ end
130
+ hash
131
+ end
132
+ end
133
+
134
+ def configure_http!
135
+ consumer.http.set_debug_output(Twurl.options.debug_output_io) if Twurl.options.trace
136
+ if Twurl.options.ssl?
137
+ consumer.http.use_ssl = true
138
+ consumer.http.verify_mode = OpenSSL::SSL::VERIFY_NONE
139
+ end
140
+ end
141
+
142
+ def consumer
143
+ @consumer ||=
144
+ begin
145
+ OAuth::Consumer.new(
146
+ consumer_key,
147
+ consumer_secret,
148
+ :site => Twurl.options.base_url
149
+ )
150
+ end
151
+ end
152
+
153
+ def access_token
154
+ @access_token ||= OAuth::AccessToken.new(consumer, token, secret)
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,93 @@
1
+ module Twurl
2
+ class RCFile
3
+ FILE = '.twurlrc'
4
+ @directory ||= ENV['HOME']
5
+ class << self
6
+ attr_accessor :directory
7
+
8
+ def file_path
9
+ File.join(directory, FILE)
10
+ end
11
+
12
+ def load
13
+ YAML.load_file(file_path)
14
+ rescue Errno::ENOENT
15
+ default_rcfile_structure
16
+ end
17
+
18
+ def default_rcfile_structure
19
+ {'profiles' => {}, 'configuration' => {}}
20
+ end
21
+ end
22
+
23
+ attr_reader :data
24
+ def initialize
25
+ @data = self.class.load
26
+ end
27
+
28
+ def empty?
29
+ data == self.class.default_rcfile_structure
30
+ end
31
+
32
+ def save
33
+ File.open(self.class.file_path, 'w') do |rcfile|
34
+ rcfile.write data.to_yaml
35
+ end
36
+ end
37
+
38
+ def [](username)
39
+ profiles[username]
40
+ end
41
+
42
+ def profiles
43
+ data['profiles']
44
+ end
45
+
46
+ def default_profile
47
+ configuration['default_profile']
48
+ end
49
+
50
+ def default_profile=(profile)
51
+ configuration['default_profile'] = [profile.username, profile.consumer_key]
52
+ end
53
+
54
+ def configuration
55
+ data['configuration']
56
+ end
57
+
58
+ def alias(name, path)
59
+ data['aliases'] ||= {}
60
+ data['aliases'][name] = path
61
+ save
62
+ end
63
+
64
+ def aliases
65
+ data['aliases']
66
+ end
67
+
68
+ def alias_from_options(options)
69
+ options.subcommands.each do |potential_alias|
70
+ if path = alias_from_name(potential_alias)
71
+ break path
72
+ end
73
+ end
74
+ end
75
+
76
+ def alias_from_name(name)
77
+ aliases[name]
78
+ end
79
+
80
+ def has_oauth_profile_for_username_with_consumer_key?(username, consumer_key)
81
+ user_profiles = self[username]
82
+ !user_profiles.nil? && !user_profiles[consumer_key].nil?
83
+ end
84
+
85
+ def <<(oauth_client)
86
+ client_from_file = self[oauth_client.username] || {}
87
+ client_from_file[oauth_client.consumer_key] = oauth_client.to_hash
88
+ (profiles[oauth_client.username] ||= {}).update(client_from_file)
89
+ self.default_profile = oauth_client unless default_profile
90
+ save
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,19 @@
1
+ module Twurl
2
+ class RequestController < AbstractCommandController
3
+ NO_URI_MESSAGE = "No URI specified"
4
+ def dispatch
5
+ if client.needs_to_authorize?
6
+ raise Exception, "You need to authorize first."
7
+ end
8
+ options.path ||= OAuthClient.rcfile.alias_from_options(options)
9
+ perform_request
10
+ end
11
+
12
+ def perform_request
13
+ response = client.perform_request_from_options(options)
14
+ CLI.puts response.body
15
+ rescue URI::InvalidURIError
16
+ CLI.puts NO_URI_MESSAGE
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module Twurl
2
+ module VERSION
3
+ MAJOR = '0'
4
+ MINOR = '6'
5
+ TINY = '0'
6
+ BETA = nil # Time.now.to_i.to_s
7
+ end
8
+
9
+ Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
10
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Twurl::AccountInformationController::DispatchWithNoAuthorizedAccountsTest < Test::Unit::TestCase
4
+ attr_reader :options, :client, :controller
5
+ def setup
6
+ @options = Twurl::Options.new
7
+ @client = Twurl::OAuthClient.load_new_client_from_options(options)
8
+ @controller = Twurl::AccountInformationController.new(client, options)
9
+ mock(Twurl::OAuthClient.rcfile).empty? { true }
10
+ end
11
+
12
+ def test_message_indicates_when_no_accounts_are_authorized
13
+ mock(Twurl::CLI).puts(Twurl::AccountInformationController::NO_AUTHORIZED_ACCOUNTS_MESSAGE).times(1)
14
+
15
+ controller.dispatch
16
+ end
17
+ end
18
+
19
+ class Twurl::AccountInformationController::DispatchWithOneAuthorizedAccountTest < Test::Unit::TestCase
20
+ attr_reader :options, :client, :controller
21
+ def setup
22
+ @options = Twurl::Options.test_exemplar
23
+ @client = Twurl::OAuthClient.load_new_client_from_options(options)
24
+ mock(Twurl::OAuthClient.rcfile).save.times(1)
25
+ Twurl::OAuthClient.rcfile << client
26
+ @controller = Twurl::AccountInformationController.new(client, options)
27
+ end
28
+
29
+ def test_authorized_account_is_displayed_and_marked_as_the_default
30
+ mock(Twurl::CLI).puts(client.username).times(1).ordered
31
+ mock(Twurl::CLI).puts(" #{client.consumer_key} (default)").times(1).ordered
32
+
33
+ controller.dispatch
34
+ end
35
+ end
36
+
37
+ class Twurl::AccountInformationController::DispatchWithOneUsernameThatHasAuthorizedMultipleAccountsTest < Test::Unit::TestCase
38
+ attr_reader :default_client_options, :default_client, :other_client_options, :other_client, :controller
39
+ def setup
40
+ @default_client_options = Twurl::Options.test_exemplar
41
+ @default_client = Twurl::OAuthClient.load_new_client_from_options(default_client_options)
42
+
43
+ @other_client_options = Twurl::Options.test_exemplar
44
+ other_client_options.consumer_key = default_client_options.consumer_key.reverse
45
+ @other_client = Twurl::OAuthClient.load_new_client_from_options(other_client_options)
46
+ mock(Twurl::OAuthClient.rcfile).save.times(2)
47
+
48
+ Twurl::OAuthClient.rcfile << default_client
49
+ Twurl::OAuthClient.rcfile << other_client
50
+
51
+ @controller = Twurl::AccountInformationController.new(other_client, other_client_options)
52
+ end
53
+
54
+ def test_authorized_account_is_displayed_and_marked_as_the_default
55
+ mock(Twurl::CLI).puts(default_client.username).times(1)
56
+ mock(Twurl::CLI).puts(" #{default_client.consumer_key} (default)").times(1)
57
+ mock(Twurl::CLI).puts(" #{other_client.consumer_key}").times(1)
58
+
59
+ controller.dispatch
60
+ end
61
+ end
@@ -0,0 +1,53 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Twurl::AliasesController::DispatchTest < Test::Unit::TestCase
4
+ attr_reader :options, :client
5
+ def setup
6
+ @options = Twurl::Options.test_exemplar
7
+ @client = Twurl::OAuthClient.test_exemplar
8
+
9
+ # Clean slate
10
+ if Twurl::OAuthClient.rcfile.aliases
11
+ Twurl::OAuthClient.rcfile.aliases.clear
12
+ end
13
+
14
+ stub(Twurl::OAuthClient.rcfile).save
15
+ end
16
+
17
+ def test_when_no_subcommands_are_provided_and_no_aliases_exist_nothing_is_displayed
18
+ assert options.subcommands.empty?
19
+ mock(Twurl::CLI).puts(Twurl::AliasesController::NO_ALIASES_MESSAGE).times(1)
20
+
21
+ controller = Twurl::AliasesController.new(client, options)
22
+ controller.dispatch
23
+ end
24
+
25
+ def test_when_no_subcommands_are_provided_and_aliases_exist_they_are_displayed
26
+ assert options.subcommands.empty?
27
+
28
+ Twurl::OAuthClient.rcfile.alias('h', '/1/statuses/home_timeline.xml')
29
+ mock(Twurl::CLI).puts("h: /1/statuses/home_timeline.xml").times(1)
30
+
31
+ controller = Twurl::AliasesController.new(client, options)
32
+ controller.dispatch
33
+ end
34
+
35
+ def test_when_alias_and_value_are_provided_they_are_added
36
+ options.subcommands = ['h']
37
+ options.path = '/1/statuses/home_timeline.xml'
38
+ mock(Twurl::OAuthClient.rcfile).alias('h', '/1/statuses/home_timeline.xml').times(1)
39
+
40
+ controller = Twurl::AliasesController.new(client, options)
41
+ controller.dispatch
42
+ end
43
+
44
+ def test_when_no_path_is_provided_nothing_happens
45
+ options.subcommands = ['a']
46
+ assert_nil options.path
47
+
48
+ mock(Twurl::CLI).puts(Twurl::AliasesController::NO_PATH_PROVIDED_MESSAGE).times(1)
49
+
50
+ controller = Twurl::AliasesController.new(client, options)
51
+ controller.dispatch
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class Twurl::AuthorizationController::DispatchTest < Test::Unit::TestCase
4
+ attr_reader :options, :client, :controller
5
+ def setup
6
+ @options = Twurl::Options.new
7
+ @client = Twurl::OAuthClient.load_new_client_from_options(options)
8
+ @controller = Twurl::AuthorizationController.new(client, options)
9
+ end
10
+
11
+ def test_successful_authentication_saves_retrieved_access_token
12
+ mock(client).exchange_credentials_for_access_token.times(1)
13
+ mock(client).save.times(1)
14
+ mock(controller).raise(Twurl::Exception, Twurl::AuthorizationController::AUTHORIZATION_FAILED_MESSAGE).never
15
+ mock(Twurl::CLI).puts(Twurl::AuthorizationController::AUTHORIZATION_SUCCEEDED_MESSAGE).times(1)
16
+
17
+ controller.dispatch
18
+ end
19
+
20
+ module ErrorCases
21
+ def test_failed_authorization_does_not_save_client
22
+ mock(client).exchange_credentials_for_access_token { raise OAuth::Unauthorized }
23
+ mock(client).save.never
24
+ mock(controller).raise(Twurl::Exception, Twurl::AuthorizationController::AUTHORIZATION_FAILED_MESSAGE).times(1)
25
+
26
+ controller.dispatch
27
+ end
28
+ end
29
+ include ErrorCases
30
+ end