xaviershay-twitter-auth 0.1.19

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README.markdown +119 -0
  2. data/Rakefile +31 -0
  3. data/VERSION.yml +4 -0
  4. data/app/controllers/sessions_controller.rb +66 -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 +44 -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 +47 -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 +99 -0
  17. data/lib/twitter_auth/controller_extensions.rb +69 -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 +250 -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 +101 -0
  32. data/spec/schema.rb +41 -0
  33. data/spec/spec.opts +1 -0
  34. data/spec/spec_helper.rb +53 -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 +160 -0
  40. metadata +127 -0
@@ -0,0 +1,6 @@
1
+ ActionController::Routing::Routes.draw do |map|
2
+ map.login '/login', :controller => 'sessions', :action => 'new'
3
+ map.logout '/logout', :controller => 'sessions', :action => 'destroy'
4
+ map.resource :session
5
+ map.oauth_callback '/oauth_callback', :controller => 'sessions', :action => 'oauth_callback'
6
+ end
@@ -0,0 +1,12 @@
1
+ TwitterAuth Generator
2
+ =====================
3
+
4
+ The TwitterAuth generator allows you to generate the components necessary to implement Twitter as a Single Sign-On provider for your site.
5
+
6
+ To run it, you simply need to call it:
7
+
8
+ script/generate twitter_auth
9
+
10
+ This will generate the migration necessary for the users table as well as generate a User model that extends the appropriate TwitterAuth model template and a config/twitter.yml that allows you to set your OAuth consumer key and secret.
11
+
12
+ By default, TwitterAuth uses OAuth as its authentication strategy. If you wish to use HTTP Basic you can pass in the --basic option.
@@ -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,47 @@
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
+ authorize_path: "/oauth/authenticate"
8
+ api_timeout: 10
9
+ remember_for: 14 # days
10
+ oauth_callback: "http://localhost:3000/oauth_callback"
11
+ test:
12
+ strategy: oauth
13
+ oauth_consumer_key: testkey
14
+ oauth_consumer_secret: testsecret
15
+ base_url: "https://twitter.com"
16
+ authorize_path: "/oauth/authenticate"
17
+ api_timeout: 10
18
+ remember_for: 14 # days
19
+ oauth_callback: "http://localhost:3000/oauth_callback"
20
+ production:
21
+ strategy: oauth
22
+ oauth_consumer_key: prodkey
23
+ oauth_consumer_secret: prodsecret
24
+ authorize_path: "/oauth/authenticate"
25
+ base_url: "https://twitter.com"
26
+ api_timeout: 10
27
+ remember_for: 14 # days
28
+ <% else -%>
29
+ development:
30
+ strategy: basic
31
+ api_timeout: 10
32
+ base_url: "https://twitter.com"
33
+ # randomly generated key for encrypting Twitter passwords
34
+ encryption_key: "<%= key = ActiveSupport::SecureRandom.hex(12) %>"
35
+ remember_for: 14 # days
36
+ test:
37
+ strategy: basic
38
+ api_timeout: 10
39
+ base_url: "https://twitter.com"
40
+ encryption_key: "<%= key %>"
41
+ remember_for: 14 # days
42
+ production:
43
+ strategy: basic
44
+ api_timeout: 10
45
+ encryption_key: "<%= key %>"
46
+ remember_for: 14 # days
47
+ <% 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,99 @@
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
+ options = {:site => TwitterAuth.base_url}
60
+ [ :authorize_path,
61
+ :request_token_path,
62
+ :access_token_path,
63
+ :scheme ].each do |oauth_option|
64
+ options[oauth_option] = TwitterAuth.config[oauth_option.to_s] if TwitterAuth.config[oauth_option.to_s]
65
+ end
66
+
67
+ OAuth::Consumer.new(
68
+ config['oauth_consumer_key'],
69
+ config['oauth_consumer_secret'],
70
+ options
71
+ )
72
+ end
73
+
74
+ def self.net
75
+ uri = URI.parse(TwitterAuth.base_url)
76
+ net = Net::HTTP.new(uri.host, uri.port)
77
+ net.use_ssl = uri.scheme == 'https'
78
+ net.read_timeout = TwitterAuth.api_timeout
79
+ net
80
+ end
81
+
82
+ def self.authorize_path
83
+ config['authorize_path'] || '/oauth/authorize'
84
+ end
85
+ end
86
+
87
+ require 'twitter_auth/controller_extensions'
88
+ require 'twitter_auth/cryptify'
89
+ require 'twitter_auth/dispatcher/oauth'
90
+ require 'twitter_auth/dispatcher/basic'
91
+ require 'twitter_auth/dispatcher/shared'
92
+
93
+ module TwitterAuth
94
+ module Dispatcher
95
+ class Error < StandardError; end
96
+ class Unauthorized < Error; end
97
+ end
98
+ end
99
+
@@ -0,0 +1,69 @@
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
+
63
+ def build_user(user)
64
+ # Hook for applications to set additional user attributes on create
65
+ end
66
+ end
67
+ end
68
+
69
+ 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