authoritarian 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,83 @@
1
+ Authoritarian - Twitter OAuth Manager
2
+ =====================================
3
+
4
+ ## DESCRIPTION
5
+
6
+ Authoritarian is a command line interface for managing Twitter authorizations to
7
+ your application. God forbid Twitter give us a simple way to manually add
8
+ test accounts to our application.
9
+
10
+ Authoritarian follows the rules of [Semantic Versioning](http://semver.org/).
11
+
12
+
13
+ ## INSTALL
14
+
15
+ To install Authoritarian, simply install the gem:
16
+
17
+ $ [sudo] gem install authoritarian
18
+
19
+
20
+ ## TWITTER
21
+
22
+ ### APPLICATIONS
23
+
24
+ To authorize using Twitter's API, start by registering your application in
25
+ authoritarian:
26
+
27
+ $ authoritarian add application
28
+ Name: My Cool App
29
+ Consumer Key: HJvxyS06ykAA5AxYruY2p
30
+ Consumer Secret: 4wftaMYXpylkveupjQ4YDDZlarS35UJHR1ZHHSfjto
31
+
32
+ You can find your Consumer Key and Consumer Secret for your application by
33
+ going to the [Twitter Developer Applications](https://dev.twitter.com/apps) site
34
+ and clicking on the application you'd like to add. The key and secret are listed
35
+ under `OAuth 1.0a Settings`.
36
+
37
+ You can view your registered applications:
38
+
39
+ $ authoritarian show applications
40
+ $ authoritarian show applications --all
41
+
42
+ The `--all` option shows the consumer key and secret for each application.
43
+
44
+ Finally, you can remove your application by running:
45
+
46
+ $ authoritarian show applications
47
+
48
+ You'll be presented with prompts to select the application you wish to remove.
49
+
50
+ ### USERS
51
+
52
+ Once you've registered a Twitter application with Authoritarian, you can
53
+ authorize individual Twitter users to your application from the command line.
54
+
55
+ To authorize a user, simply type:
56
+
57
+ $ authoritarian add user
58
+
59
+ And to show a list of all users, type:
60
+
61
+ $ authoritarian show users
62
+ $ authoritarian show users --all
63
+
64
+ The `--all` option will append the OAuth token and OAuth token secret for each
65
+ user registered to Authoritarian.
66
+
67
+ To remove a user from Authoritarian (but not deauthorize), type:
68
+
69
+ $ authoritarian remove user
70
+
71
+
72
+ ## CONTRIBUTE
73
+
74
+ Contributions to Authoritarian are welcome! There is no mailing list currently
75
+ but you can add a feature you're interested in building to the GitHub Issues
76
+ page to discuss it.
77
+
78
+ If you do submit a pull request, please make sure you add test coverage and
79
+ send your request from a git topic branch.
80
+
81
+ You can find the repository here:
82
+
83
+ http://github.com/benbjohnson/authoritarian
@@ -0,0 +1,273 @@
1
+ #!/usr/bin/ruby
2
+
3
+ dir = File.join(File.dirname(File.expand_path(__FILE__)), '..', 'lib')
4
+ $:.unshift(dir)
5
+
6
+ require 'rubygems'
7
+ require 'authoritarian'
8
+ require 'commander/import'
9
+ require 'terminal-table/import'
10
+
11
+ program :name, 'Authoritarian'
12
+ program :version, Authoritarian::VERSION
13
+ program :description, 'An OAuth command line authorization tool.'
14
+
15
+
16
+ ################################################################################
17
+ # Initialization
18
+ ################################################################################
19
+
20
+ # Setup DataMapper
21
+ DataMapper.setup(:default, "sqlite://#{File.expand_path('~/authoritarian.db')}")
22
+ DataMapper.auto_upgrade!
23
+
24
+ # Catch CTRL-C and exit cleanly
25
+ trap("INT") do
26
+ puts
27
+ exit()
28
+ end
29
+
30
+ ################################################################################
31
+ # Helper Methods
32
+ ################################################################################
33
+
34
+ def ask_application(explicit=false, message='Please choose an application')
35
+ applications = Application.all
36
+ if applications.length == 0
37
+ puts "There are no applications. Please run 'authoritarian add application'."
38
+ exit()
39
+ elsif applications.length == 1 && !explicit
40
+ return applications[0].id
41
+ else
42
+ puts ""
43
+ puts "Applications"
44
+ applications.each_index do |index|
45
+ puts "#{index+1}. #{applications[index].name}"
46
+ end
47
+ index = ask("#{message} [1-#{applications.length}]: ", Integer) do
48
+ |q| q.in = 1..applications.length
49
+ end
50
+ puts ""
51
+ return applications[index-1].id
52
+ end
53
+ end
54
+
55
+ def ask_user(application, explicit=false, message='Please choose a user')
56
+ users = application.users
57
+ if users.length == 0
58
+ puts "There are no users. Please run 'authoritarian add user'."
59
+ exit()
60
+ elsif users.length == 1 && !explicit
61
+ return users[0].id
62
+ else
63
+ puts "Users"
64
+ users.each_index do |index|
65
+ puts "#{index+1}. #{users[index].username}"
66
+ end
67
+ index = ask("#{message} [1-#{users.length}]: ", Integer) do
68
+ |q| q.in = 1..users.length
69
+ end
70
+ puts ""
71
+ return users[index-1].id
72
+ end
73
+ end
74
+
75
+
76
+ ################################################################################
77
+ # Application Commands
78
+ ################################################################################
79
+
80
+ # Add application
81
+ command :'add application' do |c|
82
+ c.syntax = 'authoritarian add application'
83
+ c.description = 'Adds an application to the database.'
84
+ c.when_called do|args, options|
85
+ application = Application.new(:type => 'twitter')
86
+ application.name = ask("Name: ")
87
+ application.consumer_key = ask("Consumer Key: ")
88
+ application.consumer_secret = ask("Consumer Secret: ")
89
+ application.save()
90
+ end
91
+ end
92
+
93
+ # Remove Application
94
+ command :'remove application' do |c|
95
+ c.syntax = 'authoritarian remove application [options]'
96
+ c.description = 'Removes a registered application.'
97
+ c.option '--app-id ID', String, 'The identifier for the authorizing application.'
98
+ c.when_called do|args, options|
99
+ # Retrieve the application
100
+ options.app_id ||= ask_application(true, 'Please choose an application to delete')
101
+ application = Application.get(options.app_id)
102
+ raise "Cannot find application #{options.app_id}" if application.nil?
103
+
104
+ # Delete application
105
+ application.destroy
106
+ end
107
+ end
108
+
109
+ # Show applications
110
+ command :'show applications' do |c|
111
+ c.syntax = 'authoritarian show applications'
112
+ c.description = 'Displays all registered applications registered.'
113
+ c.option '--all', 'Shows all application data.'
114
+ c.when_called do|args, options|
115
+ applications = Application.all
116
+
117
+ if applications.length == 0
118
+ say "There are no registered applications."
119
+ else
120
+ application_table = table do |t|
121
+ t.headings = 'ID', 'Name'
122
+
123
+ # Optionally add consumer info
124
+ if options.all
125
+ t.headings << 'Consumer Key'
126
+ t.headings << 'Consumer Secret'
127
+ end
128
+
129
+ applications.each do |application|
130
+ row = [
131
+ application.id,
132
+ application.name[0..20],
133
+ ]
134
+
135
+ # Optionally add consumer info
136
+ if options.all
137
+ row << application.consumer_key
138
+ row << application.consumer_secret
139
+ end
140
+
141
+ t << row
142
+ end
143
+ end
144
+
145
+ puts application_table
146
+ end
147
+ end
148
+ end
149
+
150
+ # Show application
151
+ command :'show application' do |c|
152
+ c.syntax = 'authoritarian show application [options]'
153
+ c.description = 'Displays details of a single application.'
154
+ c.option '--id NUM', String, 'The application identifier.'
155
+ c.when_called do|args, options|
156
+ id = options.id || ask("ID: ")
157
+ application = Application.get(id)
158
+
159
+ if application.nil?
160
+ say "The specified application does not exist."
161
+ else
162
+ puts "Name: #{application.name}"
163
+ puts "Consumer Key: #{application.consumer_key}"
164
+ puts "Consumer Secret: #{application.consumer_secret}"
165
+ puts ""
166
+ end
167
+ end
168
+ end
169
+
170
+
171
+ ################################################################################
172
+ # User Commands
173
+ ################################################################################
174
+
175
+ # Add User
176
+ command :'add user' do |c|
177
+ c.syntax = 'authoritarian authorize user [options]'
178
+ c.description = 'Authorizes a user to an application.'
179
+ c.option '--app-id ID', String, 'The identifier for the authorizing application.'
180
+ c.option '--username STRING', String, 'The username of the authorizing user.'
181
+ c.option '--password STRING', String, 'The password of the authorizing user.'
182
+ c.when_called do|args, options|
183
+ options.app_id ||= ask_application()
184
+
185
+ # Retrieve the application
186
+ application = Application.get(options.app_id)
187
+ raise "Cannot find application #{options.app_id}" if application.nil?
188
+
189
+ # Retrieve authorization token
190
+ options.username ||= ask("Username: ")
191
+ options.password ||= ask("Password: ") {|q| q.echo = '*'}
192
+ authorizer = Authoritarian::Twitter.new(
193
+ application.consumer_key,
194
+ application.consumer_secret
195
+ )
196
+ auth = authorizer.authorize(options.username, options.password)
197
+
198
+ # Create a user
199
+ user = User.new(:application => application)
200
+ user.username = options.username
201
+ user.oauth_token = auth[:token]
202
+ user.oauth_token_secret = auth[:secret]
203
+ user.save
204
+ end
205
+ end
206
+
207
+ # Remove User
208
+ command :'remove user' do |c|
209
+ c.syntax = 'authoritarian remove user [options]'
210
+ c.description = 'Removes a user from a registered application.'
211
+ c.option '--app-id ID', String, 'The identifier for the authorizing application.'
212
+ c.when_called do|args, options|
213
+ # Retrieve the application
214
+ options.app_id ||= ask_application()
215
+ application = Application.get(options.app_id)
216
+ raise "Cannot find application #{options.app_id}" if application.nil?
217
+
218
+ # Retrieve the user to remove
219
+ options.user_id ||= ask_user(application, true, 'Please choose a user to remove')
220
+ user = User.get(options.user_id)
221
+ raise "Cannot find user #{options.user_id}" if user.nil?
222
+
223
+ # Delete user
224
+ user.destroy
225
+ end
226
+ end
227
+
228
+ # Show authorized users
229
+ command :'show users' do |c|
230
+ c.syntax = 'authoritarian show users'
231
+ c.description = 'Displays all users authorized to registered applications.'
232
+ c.option '--app-id ID', String, 'The identifier for the authorizing application.'
233
+ c.option '--all', 'Shows all user data.'
234
+ c.when_called do|args, options|
235
+ options.app_id ||= ask_application()
236
+
237
+ # Retrieve the application
238
+ application = Application.get(options.app_id)
239
+ raise "Cannot find application #{options.app_id}" if application.nil?
240
+ users = application.users
241
+
242
+ if users.length == 0
243
+ say "There are no users authorized to use #{application.name}."
244
+ else
245
+ user_table = table do |t|
246
+ t.headings = 'ID', 'Username'
247
+
248
+ # Optionally add oauth info
249
+ if options.all
250
+ t.headings << 'OAuth Token'
251
+ t.headings << 'OAuth Token Secret'
252
+ end
253
+
254
+ users.each do |user|
255
+ row = [
256
+ user.id,
257
+ user.username,
258
+ ]
259
+
260
+ # Optionally add oauth info
261
+ if options.all
262
+ row << user.oauth_token
263
+ row << user.oauth_token_secret
264
+ end
265
+
266
+ t << row
267
+ end
268
+ end
269
+
270
+ puts user_table
271
+ end
272
+ end
273
+ end
@@ -0,0 +1,16 @@
1
+ dir = File.dirname(File.expand_path(__FILE__))
2
+ $:.unshift(dir)
3
+
4
+ require 'oauth'
5
+ require 'mechanize'
6
+ require 'dm-core'
7
+ require 'dm-migrations'
8
+ require 'dm-timestamps'
9
+ require 'dm-validations'
10
+ require 'authoritarian/model'
11
+ require 'authoritarian/twitter'
12
+ require 'authoritarian/version'
13
+
14
+ module Authoritarian
15
+ class InvalidLoginError < StandardError; end
16
+ end
@@ -0,0 +1,9 @@
1
+ DataMapper::Model.raise_on_save_failure = true
2
+
3
+ DataMapper::Property::String.length(255)
4
+ DataMapper::Property::Boolean.allow_nil(false)
5
+
6
+ require 'authoritarian/model/application'
7
+ require 'authoritarian/model/user'
8
+
9
+ DataMapper.finalize
@@ -0,0 +1,16 @@
1
+ class Application
2
+ include ::DataMapper::Resource
3
+ has n, :users
4
+
5
+ property :id, Serial
6
+ property :type, String
7
+ property :name, String
8
+ property :consumer_key, String
9
+ property :consumer_secret, String
10
+ timestamps :at
11
+
12
+ validates_presence_of :type
13
+ validates_presence_of :name
14
+ validates_presence_of :consumer_key
15
+ validates_presence_of :consumer_secret
16
+ end
@@ -0,0 +1,12 @@
1
+ class User
2
+ include ::DataMapper::Resource
3
+ belongs_to :application
4
+
5
+ property :id, Serial
6
+ property :username, String
7
+ property :oauth_token, String
8
+ property :oauth_token_secret, String
9
+ timestamps :at
10
+
11
+ validates_presence_of :application_id
12
+ end
@@ -0,0 +1,103 @@
1
+ module Authoritarian
2
+ # This class performs OAuth authorization for Twitter user accounts.
3
+ class Twitter
4
+ ############################################################################
5
+ # Constructor
6
+ ############################################################################
7
+
8
+ # Creates a Twitter OAuth authorizer.
9
+ def initialize(consumer_key=nil, consumer_secret=nil)
10
+ self.consumer_key = consumer_key
11
+ self.consumer_secret = consumer_secret
12
+ end
13
+
14
+
15
+ ############################################################################
16
+ # Public Attributes
17
+ ############################################################################
18
+
19
+ # The consumer key for the application that is authorizing the user.
20
+ attr_accessor :consumer_key
21
+
22
+ # The consumer secret for the application that is authorizing the user.
23
+ attr_accessor :consumer_secret
24
+
25
+
26
+ ############################################################################
27
+ # Public Methods
28
+ ############################################################################
29
+
30
+ def authorize(username, password)
31
+ # Generate request token
32
+ request_token = consumer.get_request_token
33
+
34
+ # Create in-process browser
35
+ mechanize = Mechanize.new do |agent|
36
+ #agent.user_agent_alias = 'Authoritarian'
37
+ end
38
+
39
+ # Request authorization
40
+ mechanize.get(generate_authorize_url(request_token)) do |login_page|
41
+ # Login and allow
42
+ form = *login_page.forms
43
+ form.field_with(:name => 'session[username_or_email]').value = username
44
+ form.field_with(:name => 'session[password]').value = password
45
+ auth_page = form.submit(form.button_with(:value => 'Allow'))
46
+
47
+ # Verify returned page has no errors
48
+ error = auth_page.parser.css('p.oauth-errors').text.strip
49
+ if error != ''
50
+ raise Authoritarian::InvalidLoginError.new(error)
51
+ end
52
+
53
+ # If not then get access token from PIN
54
+ pin = auth_page.parser.css('div#oauth_pin').text.strip
55
+
56
+ # Create OAuth token from PIN
57
+ access_token = request_token.get_access_token(:oauth_verifier => pin)
58
+ return {:token => access_token.token, :secret => access_token.secret}
59
+ end
60
+
61
+ return nil
62
+ end
63
+
64
+
65
+ ############################################################################
66
+ # Private Methods
67
+ ############################################################################
68
+
69
+ protected
70
+
71
+ # Generates the authorization url to have the user log into.
72
+ def generate_authorize_url(request_token)
73
+ # Create request
74
+ request =
75
+ consumer.create_signed_request(
76
+ :get,
77
+ consumer.authorize_path,
78
+ request_token,
79
+ {:oauth_callback => 'oob'}
80
+ )
81
+
82
+ # Alter parameters
83
+ params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map { |p|
84
+ k, v = p.split('=')
85
+ v =~ /"(.*?)"/
86
+ "#{k}=#{CGI::escape($1)}"
87
+ }.join('&')
88
+
89
+ # Return authorize url
90
+ return "https://api.twitter.com#{request.path}?#{params}"
91
+ end
92
+
93
+ # The OAuth consumer object.
94
+ def consumer
95
+ @consumer ||=
96
+ ::OAuth::Consumer.new(
97
+ consumer_key,
98
+ consumer_secret,
99
+ :site => "https://api.twitter.com"
100
+ )
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,3 @@
1
+ module Authoritarian
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,218 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: authoritarian
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ben Johnson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-02-28 00:00:00 -07:00
19
+ default_executable: authoritarian
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: mechanize
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 23
30
+ segments:
31
+ - 1
32
+ - 0
33
+ - 0
34
+ version: 1.0.0
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: data_mapper
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 19
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 2
50
+ version: 1.0.2
51
+ type: :runtime
52
+ version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: dm-sqlite-adapter
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 19
62
+ segments:
63
+ - 1
64
+ - 0
65
+ - 2
66
+ version: 1.0.2
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: commander
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 57
78
+ segments:
79
+ - 4
80
+ - 0
81
+ - 3
82
+ version: 4.0.3
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: terminal-table
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 1
96
+ - 4
97
+ - 2
98
+ version: 1.4.2
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: rspec
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ hash: 31
110
+ segments:
111
+ - 2
112
+ - 4
113
+ - 0
114
+ version: 2.4.0
115
+ type: :development
116
+ version_requirements: *id006
117
+ - !ruby/object:Gem::Dependency
118
+ name: mocha
119
+ prerelease: false
120
+ requirement: &id007 !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ hash: 35
126
+ segments:
127
+ - 0
128
+ - 9
129
+ - 12
130
+ version: 0.9.12
131
+ type: :development
132
+ version_requirements: *id007
133
+ - !ruby/object:Gem::Dependency
134
+ name: unindentable
135
+ prerelease: false
136
+ requirement: &id008 !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ hash: 27
142
+ segments:
143
+ - 0
144
+ - 1
145
+ - 0
146
+ version: 0.1.0
147
+ type: :development
148
+ version_requirements: *id008
149
+ - !ruby/object:Gem::Dependency
150
+ name: fakeweb
151
+ prerelease: false
152
+ requirement: &id009 !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ hash: 27
158
+ segments:
159
+ - 1
160
+ - 3
161
+ - 0
162
+ version: 1.3.0
163
+ type: :development
164
+ version_requirements: *id009
165
+ description:
166
+ email:
167
+ - benbjohnson@yahoo.com
168
+ executables:
169
+ - authoritarian
170
+ extensions: []
171
+
172
+ extra_rdoc_files: []
173
+
174
+ files:
175
+ - lib/authoritarian/model/application.rb
176
+ - lib/authoritarian/model/user.rb
177
+ - lib/authoritarian/model.rb
178
+ - lib/authoritarian/twitter.rb
179
+ - lib/authoritarian/version.rb
180
+ - lib/authoritarian.rb
181
+ - README.md
182
+ - bin/authoritarian
183
+ has_rdoc: true
184
+ homepage: http://github.com/benbjohnson/authoritarian
185
+ licenses: []
186
+
187
+ post_install_message:
188
+ rdoc_options: []
189
+
190
+ require_paths:
191
+ - lib
192
+ required_ruby_version: !ruby/object:Gem::Requirement
193
+ none: false
194
+ requirements:
195
+ - - ">="
196
+ - !ruby/object:Gem::Version
197
+ hash: 3
198
+ segments:
199
+ - 0
200
+ version: "0"
201
+ required_rubygems_version: !ruby/object:Gem::Requirement
202
+ none: false
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ hash: 3
207
+ segments:
208
+ - 0
209
+ version: "0"
210
+ requirements: []
211
+
212
+ rubyforge_project:
213
+ rubygems_version: 1.3.7
214
+ signing_key:
215
+ specification_version: 3
216
+ summary: A command line OAuth authorization tool.
217
+ test_files: []
218
+