millsb-twitter-auth 0.1.16
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.
- data/README.markdown +96 -0
- data/Rakefile +31 -0
- data/VERSION.yml +4 -0
- data/app/controllers/sessions_controller.rb +64 -0
- data/app/models/twitter_auth/basic_user.rb +63 -0
- data/app/models/twitter_auth/generic_user.rb +93 -0
- data/app/models/twitter_auth/oauth_user.rb +43 -0
- data/app/views/sessions/_login_form.html.erb +17 -0
- data/app/views/sessions/new.html.erb +5 -0
- data/config/routes.rb +6 -0
- data/generators/twitter_auth/USAGE +12 -0
- data/generators/twitter_auth/templates/migration.rb +48 -0
- data/generators/twitter_auth/templates/twitter_auth.yml +44 -0
- data/generators/twitter_auth/templates/user.rb +5 -0
- data/generators/twitter_auth/twitter_auth_generator.rb +34 -0
- data/lib/twitter_auth.rb +87 -0
- data/lib/twitter_auth/controller_extensions.rb +65 -0
- data/lib/twitter_auth/cryptify.rb +30 -0
- data/lib/twitter_auth/dispatcher/basic.rb +46 -0
- data/lib/twitter_auth/dispatcher/oauth.rb +26 -0
- data/lib/twitter_auth/dispatcher/shared.rb +40 -0
- data/rails/init.rb +8 -0
- data/spec/controllers/controller_extensions_spec.rb +162 -0
- data/spec/controllers/sessions_controller_spec.rb +221 -0
- data/spec/fixtures/config/twitter_auth.yml +17 -0
- data/spec/fixtures/factories.rb +18 -0
- data/spec/fixtures/fakeweb.rb +18 -0
- data/spec/fixtures/twitter.rb +5 -0
- data/spec/models/twitter_auth/basic_user_spec.rb +122 -0
- data/spec/models/twitter_auth/generic_user_spec.rb +142 -0
- data/spec/models/twitter_auth/oauth_user_spec.rb +94 -0
- data/spec/schema.rb +41 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/twitter_auth/cryptify_spec.rb +51 -0
- data/spec/twitter_auth/dispatcher/basic_spec.rb +83 -0
- data/spec/twitter_auth/dispatcher/oauth_spec.rb +72 -0
- data/spec/twitter_auth/dispatcher/shared_spec.rb +26 -0
- data/spec/twitter_auth_spec.rb +154 -0
- metadata +127 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
class TwitterAuthMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :users do |t|
|
4
|
+
t.string :login
|
5
|
+
<% if options[:oauth] -%>
|
6
|
+
t.string :access_token
|
7
|
+
t.string :access_secret
|
8
|
+
<% elsif options[:basic] -%>
|
9
|
+
t.binary :crypted_password
|
10
|
+
t.string :salt
|
11
|
+
<% end -%>
|
12
|
+
|
13
|
+
t.string :remember_token
|
14
|
+
t.datetime :remember_token_expires_at
|
15
|
+
|
16
|
+
# This information is automatically kept
|
17
|
+
# in-sync at each login of the user. You
|
18
|
+
# may remove any/all of these columns.
|
19
|
+
t.string :name
|
20
|
+
t.string :location
|
21
|
+
t.string :description
|
22
|
+
t.string :profile_image_url
|
23
|
+
t.string :url
|
24
|
+
t.boolean :protected
|
25
|
+
t.string :profile_background_color
|
26
|
+
t.string :profile_sidebar_fill_color
|
27
|
+
t.string :profile_link_color
|
28
|
+
t.string :profile_sidebar_border_color
|
29
|
+
t.string :profile_text_color
|
30
|
+
t.string :profile_background_image_url
|
31
|
+
t.boolean :profile_background_tiled
|
32
|
+
t.integer :friends_count
|
33
|
+
t.integer :statuses_count
|
34
|
+
t.integer :followers_count
|
35
|
+
t.integer :favourites_count
|
36
|
+
|
37
|
+
# Probably don't need both, but they're here.
|
38
|
+
t.integer :utc_offset
|
39
|
+
t.string :time_zone
|
40
|
+
|
41
|
+
t.timestamps
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.down
|
46
|
+
drop_table :users
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<% if options[:oauth] -%>
|
2
|
+
development:
|
3
|
+
strategy: oauth
|
4
|
+
oauth_consumer_key: devkey
|
5
|
+
oauth_consumer_secret: devsecret
|
6
|
+
base_url: "https://twitter.com"
|
7
|
+
api_timeout: 10
|
8
|
+
remember_for: 14 # days
|
9
|
+
oauth_callback: "http://localhost:3000/oauth_callback"
|
10
|
+
test:
|
11
|
+
strategy: oauth
|
12
|
+
oauth_consumer_key: testkey
|
13
|
+
oauth_consumer_secret: testsecret
|
14
|
+
base_url: "https://twitter.com"
|
15
|
+
api_timeout: 10
|
16
|
+
remember_for: 14 # days
|
17
|
+
oauth_callback: "http://localhost:3000/oauth_callback"
|
18
|
+
production:
|
19
|
+
strategy: oauth
|
20
|
+
oauth_consumer_key: prodkey
|
21
|
+
oauth_consumer_secret: prodsecret
|
22
|
+
base_url: "https://twitter.com"
|
23
|
+
api_timeout: 10
|
24
|
+
remember_for: 14 # days
|
25
|
+
<% else -%>
|
26
|
+
development:
|
27
|
+
strategy: basic
|
28
|
+
api_timeout: 10
|
29
|
+
base_url: "https://twitter.com"
|
30
|
+
# randomly generated key for encrypting Twitter passwords
|
31
|
+
encryption_key: "<%= key = ActiveSupport::SecureRandom.hex(12) %>"
|
32
|
+
remember_for: 14 # days
|
33
|
+
test:
|
34
|
+
strategy: basic
|
35
|
+
api_timeout: 10
|
36
|
+
base_url: "https://twitter.com"
|
37
|
+
encryption_key: "<%= key %>"
|
38
|
+
remember_for: 14 # days
|
39
|
+
production:
|
40
|
+
strategy: basic
|
41
|
+
api_timeout: 10
|
42
|
+
encryption_key: "<%= key %>"
|
43
|
+
remember_for: 14 # days
|
44
|
+
<% end %>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class TwitterAuthGenerator < Rails::Generator::Base
|
2
|
+
default_options :oauth => true, :basic => false
|
3
|
+
|
4
|
+
def manifest
|
5
|
+
record do |m|
|
6
|
+
m.class_collisions 'User'
|
7
|
+
|
8
|
+
m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => 'twitter_auth_migration'
|
9
|
+
m.template 'user.rb', File.join('app','models','user.rb')
|
10
|
+
m.template 'twitter_auth.yml', File.join('config','twitter_auth.yml')
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def banner
|
17
|
+
"Usage: #{$0} twitter_auth"
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_options!(opt)
|
21
|
+
opt.separator ''
|
22
|
+
opt.separator 'Options:'
|
23
|
+
|
24
|
+
opt.on('-O', '--oauth', 'Use the OAuth authentication strategy to connect to Twitter. (default)') { |v|
|
25
|
+
options[:oauth] = v
|
26
|
+
options[:basic] = !v
|
27
|
+
}
|
28
|
+
|
29
|
+
opt.on('-B', '--basic', 'Use the HTTP Basic authentication strategy to connect to Twitter.') { |v|
|
30
|
+
options[:basic] = v
|
31
|
+
options[:oauth] = !v
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
data/lib/twitter_auth.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
class Error < StandardError; end
|
3
|
+
|
4
|
+
def self.config(environment=RAILS_ENV)
|
5
|
+
@config ||= {}
|
6
|
+
@config[environment] ||= YAML.load(File.open(RAILS_ROOT + '/config/twitter_auth.yml').read)[environment]
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.base_url
|
10
|
+
config['base_url'] || 'https://twitter.com'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.path_prefix
|
14
|
+
URI.parse(base_url).path
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.api_timeout
|
18
|
+
config['api_timeout'] || 10
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.encryption_key
|
22
|
+
raise TwitterAuth::Cryptify::Error, 'You must specify an encryption_key in config/twitter_auth.yml' if config['encryption_key'].blank?
|
23
|
+
config['encryption_key']
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.oauth_callback?
|
27
|
+
config.key?('oauth_callback')
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.oauth_callback
|
31
|
+
config['oauth_callback']
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.remember_for
|
35
|
+
(config['remember_for'] || 14).to_i
|
36
|
+
end
|
37
|
+
|
38
|
+
# The authentication strategy employed by this
|
39
|
+
# application. Set in +config/twitter.yml+ as
|
40
|
+
# strategy; valid options are oauth or basic.
|
41
|
+
def self.strategy
|
42
|
+
strat = config['strategy']
|
43
|
+
raise ArgumentError, 'Invalid TwitterAuth Strategy: Valid strategies are oauth and basic.' unless %w(oauth basic).include?(strat)
|
44
|
+
strat.to_sym
|
45
|
+
rescue Errno::ENOENT
|
46
|
+
:oauth
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.oauth?
|
50
|
+
strategy == :oauth
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.basic?
|
54
|
+
strategy == :basic
|
55
|
+
end
|
56
|
+
|
57
|
+
# The OAuth consumer used by TwitterAuth for authentication. The consumer key and secret are set in your application's +config/twitter.yml+
|
58
|
+
def self.consumer
|
59
|
+
OAuth::Consumer.new(
|
60
|
+
config['oauth_consumer_key'],
|
61
|
+
config['oauth_consumer_secret'],
|
62
|
+
:site => TwitterAuth.base_url
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.net
|
67
|
+
uri = URI.parse(TwitterAuth.base_url)
|
68
|
+
net = Net::HTTP.new(uri.host, uri.port)
|
69
|
+
net.use_ssl = uri.scheme == 'https'
|
70
|
+
net.read_timeout = TwitterAuth.api_timeout
|
71
|
+
net
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
require 'twitter_auth/controller_extensions'
|
76
|
+
require 'twitter_auth/cryptify'
|
77
|
+
require 'twitter_auth/dispatcher/oauth'
|
78
|
+
require 'twitter_auth/dispatcher/basic'
|
79
|
+
require 'twitter_auth/dispatcher/shared'
|
80
|
+
|
81
|
+
module TwitterAuth
|
82
|
+
module Dispatcher
|
83
|
+
class Error < StandardError; end
|
84
|
+
class Unauthorized < Error; end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
# These methods borrow HEAVILY from Rick Olsen's
|
3
|
+
# Restful Authentication. All cleverness props
|
4
|
+
# go to him, not me.
|
5
|
+
module ControllerExtensions
|
6
|
+
def self.included(base)
|
7
|
+
base.send :helper_method, :current_user, :logged_in?, :authorized?
|
8
|
+
end
|
9
|
+
|
10
|
+
protected
|
11
|
+
|
12
|
+
def authentication_failed(message, destination='/')
|
13
|
+
flash[:error] = message
|
14
|
+
redirect_to destination
|
15
|
+
end
|
16
|
+
|
17
|
+
def authentication_succeeded(message = 'You have logged in successfully.', destination = '/')
|
18
|
+
flash[:notice] = message
|
19
|
+
redirect_to destination
|
20
|
+
end
|
21
|
+
|
22
|
+
def current_user
|
23
|
+
@current_user ||= User.find_by_id(session[:user_id]) || User.from_remember_token(cookies[:remember_token])
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_user=(new_user)
|
27
|
+
session[:user_id] = new_user.id
|
28
|
+
@current_user = new_user
|
29
|
+
end
|
30
|
+
|
31
|
+
def authorized?
|
32
|
+
!!current_user
|
33
|
+
end
|
34
|
+
|
35
|
+
def login_required
|
36
|
+
authorized? || access_denied
|
37
|
+
end
|
38
|
+
|
39
|
+
def access_denied
|
40
|
+
store_location
|
41
|
+
redirect_to login_path
|
42
|
+
end
|
43
|
+
|
44
|
+
def store_location
|
45
|
+
session[:return_to] = request.request_uri
|
46
|
+
end
|
47
|
+
|
48
|
+
def redirect_back_or_default(default)
|
49
|
+
redirect_to(session[:return_to] || default)
|
50
|
+
session[:return_to] = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def logged_in?
|
54
|
+
!!current_user
|
55
|
+
end
|
56
|
+
|
57
|
+
def logout_keeping_session!
|
58
|
+
@current_user = nil
|
59
|
+
session[:user_id] = nil
|
60
|
+
cookies.delete(:remember_token)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ActionController::Base.send(:include, TwitterAuth::ControllerExtensions)
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
module Cryptify
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def self.encrypt(data)
|
6
|
+
salt = generate_salt
|
7
|
+
{:encrypted_data => EzCrypto::Key.encrypt_with_password(TwitterAuth.encryption_key, salt, data), :salt => salt}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.decrypt(encrypted_data_or_hash, salt=nil)
|
11
|
+
case encrypted_data_or_hash
|
12
|
+
when String
|
13
|
+
encrypted_data = encrypted_data_or_hash
|
14
|
+
raise ArgumentError, 'Must provide a salt to decrypt.' unless salt
|
15
|
+
when Hash
|
16
|
+
encrypted_data = encrypted_data_or_hash[:encrypted_data]
|
17
|
+
salt = encrypted_data_or_hash[:salt]
|
18
|
+
else
|
19
|
+
raise ArgumentError, 'Must provide either an encrypted hash result or encrypted string and salt.'
|
20
|
+
end
|
21
|
+
|
22
|
+
EzCrypto::Key.decrypt_with_password(TwitterAuth.encryption_key, salt, encrypted_data)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.generate_salt
|
26
|
+
ActiveSupport::SecureRandom.hex(4)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module TwitterAuth
|
4
|
+
module Dispatcher
|
5
|
+
class Basic
|
6
|
+
include TwitterAuth::Dispatcher::Shared
|
7
|
+
|
8
|
+
attr_accessor :user
|
9
|
+
|
10
|
+
def initialize(user)
|
11
|
+
raise TwitterAuth::Error, 'Dispatcher must be initialized with a User.' unless user.is_a?(TwitterAuth::BasicUser)
|
12
|
+
self.user = user
|
13
|
+
end
|
14
|
+
|
15
|
+
def request(http_method, path, body=nil, *arguments)
|
16
|
+
path = TwitterAuth.path_prefix + path
|
17
|
+
path = append_extension_to(path)
|
18
|
+
|
19
|
+
response = TwitterAuth.net.start{ |http|
|
20
|
+
req = "Net::HTTP::#{http_method.to_s.capitalize}".constantize.new(path, *arguments)
|
21
|
+
req.basic_auth user.login, user.password
|
22
|
+
req.set_form_data(body) unless body.nil?
|
23
|
+
http.request(req)
|
24
|
+
}
|
25
|
+
|
26
|
+
handle_response(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(path, *arguments)
|
30
|
+
request(:get, path, *arguments)
|
31
|
+
end
|
32
|
+
|
33
|
+
def post(path, body='', *arguments)
|
34
|
+
request(:post, path, body, *arguments)
|
35
|
+
end
|
36
|
+
|
37
|
+
def put(path, body='', *arguments)
|
38
|
+
request(:put, path, body, *arguments)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete(path, *arguments)
|
42
|
+
request(:delete, path, *arguments)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'oauth'
|
2
|
+
|
3
|
+
module TwitterAuth
|
4
|
+
module Dispatcher
|
5
|
+
class Oauth < OAuth::AccessToken
|
6
|
+
include TwitterAuth::Dispatcher::Shared
|
7
|
+
|
8
|
+
attr_accessor :user
|
9
|
+
|
10
|
+
def initialize(user)
|
11
|
+
raise TwitterAuth::Error, 'Dispatcher must be initialized with a User.' unless user.is_a?(TwitterAuth::OauthUser)
|
12
|
+
self.user = user
|
13
|
+
super(TwitterAuth.consumer, user.access_token, user.access_secret)
|
14
|
+
end
|
15
|
+
|
16
|
+
def request(http_method, path, *arguments)
|
17
|
+
path = TwitterAuth.path_prefix + path
|
18
|
+
path = append_extension_to(path)
|
19
|
+
|
20
|
+
response = super
|
21
|
+
|
22
|
+
handle_response(response)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TwitterAuth
|
2
|
+
module Dispatcher
|
3
|
+
module Shared
|
4
|
+
def post!(status)
|
5
|
+
self.post('/statuses/update.json', :status => status)
|
6
|
+
end
|
7
|
+
|
8
|
+
def append_extension_to(path)
|
9
|
+
path, query_string = *(path.split("?"))
|
10
|
+
path << '.json' unless path.match(/\.(:?xml|json)\z/i)
|
11
|
+
"#{path}#{"?#{query_string}" if query_string}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def handle_response(response)
|
15
|
+
case response
|
16
|
+
when Net::HTTPOK
|
17
|
+
begin
|
18
|
+
JSON.parse(response.body)
|
19
|
+
rescue JSON::ParserError
|
20
|
+
response.body
|
21
|
+
end
|
22
|
+
when Net::HTTPUnauthorized
|
23
|
+
raise TwitterAuth::Dispatcher::Unauthorized, 'The credentials provided did not authorize the user.'
|
24
|
+
else
|
25
|
+
message = begin
|
26
|
+
JSON.parse(response.body)['error']
|
27
|
+
rescue JSON::ParserError
|
28
|
+
if match = response.body.match(/<error>(.*)<\/error>/)
|
29
|
+
match[1]
|
30
|
+
else
|
31
|
+
'An error occurred processing your Twitter request.'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
raise TwitterAuth::Dispatcher::Error, message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|