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.
Files changed (40) hide show
  1. data/README.markdown +96 -0
  2. data/Rakefile +31 -0
  3. data/VERSION.yml +4 -0
  4. data/app/controllers/sessions_controller.rb +64 -0
  5. data/app/models/twitter_auth/basic_user.rb +63 -0
  6. data/app/models/twitter_auth/generic_user.rb +93 -0
  7. data/app/models/twitter_auth/oauth_user.rb +43 -0
  8. data/app/views/sessions/_login_form.html.erb +17 -0
  9. data/app/views/sessions/new.html.erb +5 -0
  10. data/config/routes.rb +6 -0
  11. data/generators/twitter_auth/USAGE +12 -0
  12. data/generators/twitter_auth/templates/migration.rb +48 -0
  13. data/generators/twitter_auth/templates/twitter_auth.yml +44 -0
  14. data/generators/twitter_auth/templates/user.rb +5 -0
  15. data/generators/twitter_auth/twitter_auth_generator.rb +34 -0
  16. data/lib/twitter_auth.rb +87 -0
  17. data/lib/twitter_auth/controller_extensions.rb +65 -0
  18. data/lib/twitter_auth/cryptify.rb +30 -0
  19. data/lib/twitter_auth/dispatcher/basic.rb +46 -0
  20. data/lib/twitter_auth/dispatcher/oauth.rb +26 -0
  21. data/lib/twitter_auth/dispatcher/shared.rb +40 -0
  22. data/rails/init.rb +8 -0
  23. data/spec/controllers/controller_extensions_spec.rb +162 -0
  24. data/spec/controllers/sessions_controller_spec.rb +221 -0
  25. data/spec/fixtures/config/twitter_auth.yml +17 -0
  26. data/spec/fixtures/factories.rb +18 -0
  27. data/spec/fixtures/fakeweb.rb +18 -0
  28. data/spec/fixtures/twitter.rb +5 -0
  29. data/spec/models/twitter_auth/basic_user_spec.rb +122 -0
  30. data/spec/models/twitter_auth/generic_user_spec.rb +142 -0
  31. data/spec/models/twitter_auth/oauth_user_spec.rb +94 -0
  32. data/spec/schema.rb +41 -0
  33. data/spec/spec.opts +1 -0
  34. data/spec/spec_helper.rb +51 -0
  35. data/spec/twitter_auth/cryptify_spec.rb +51 -0
  36. data/spec/twitter_auth/dispatcher/basic_spec.rb +83 -0
  37. data/spec/twitter_auth/dispatcher/oauth_spec.rb +72 -0
  38. data/spec/twitter_auth/dispatcher/shared_spec.rb +26 -0
  39. data/spec/twitter_auth_spec.rb +154 -0
  40. 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,5 @@
1
+ class User < TwitterAuth::GenericUser
2
+ # Extend and define your user model as you see fit.
3
+ # All of the authentication logic is handled by the
4
+ # parent TwitterAuth::GenericUser class.
5
+ 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
@@ -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
@@ -0,0 +1,8 @@
1
+ # Gem Dependencies
2
+ config.gem 'oauth'
3
+ config.gem 'ezcrypto'
4
+
5
+ require 'json'
6
+ require 'twitter_auth'
7
+
8
+ RAILS_DEFAULT_LOGGER.info("** TwitterAuth initialized properly.")