twitter-auth-with-mongo-mapper 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/CHANGELOG.markdown +11 -0
  2. data/README.markdown +5 -0
  3. data/Rakefile +31 -0
  4. data/VERSION.yml +4 -0
  5. data/app/controllers/sessions_controller.rb +81 -0
  6. data/app/models/twitter_auth/basic_user.rb +64 -0
  7. data/app/models/twitter_auth/generic_user.rb +135 -0
  8. data/app/models/twitter_auth/oauth_user.rb +50 -0
  9. data/app/views/sessions/_login_form.html.erb +17 -0
  10. data/app/views/sessions/new.html.erb +5 -0
  11. data/config/routes.rb +6 -0
  12. data/generators/twitter_auth/USAGE +12 -0
  13. data/generators/twitter_auth/templates/migration.rb +49 -0
  14. data/generators/twitter_auth/templates/twitter_auth.yml +47 -0
  15. data/generators/twitter_auth/templates/user.rb +5 -0
  16. data/generators/twitter_auth/twitter_auth_generator.rb +34 -0
  17. data/lib/twitter_auth.rb +100 -0
  18. data/lib/twitter_auth/controller_extensions.rb +82 -0
  19. data/lib/twitter_auth/cryptify.rb +31 -0
  20. data/lib/twitter_auth/dispatcher/basic.rb +46 -0
  21. data/lib/twitter_auth/dispatcher/oauth.rb +27 -0
  22. data/lib/twitter_auth/dispatcher/shared.rb +40 -0
  23. data/rails/init.rb +8 -0
  24. data/spec/controllers/controller_extensions_spec.rb +162 -0
  25. data/spec/controllers/sessions_controller_spec.rb +221 -0
  26. data/spec/fixtures/config/twitter_auth.yml +17 -0
  27. data/spec/fixtures/factories.rb +20 -0
  28. data/spec/fixtures/fakeweb.rb +18 -0
  29. data/spec/fixtures/twitter.rb +5 -0
  30. data/spec/models/twitter_auth/basic_user_spec.rb +138 -0
  31. data/spec/models/twitter_auth/generic_user_spec.rb +146 -0
  32. data/spec/models/twitter_auth/oauth_user_spec.rb +100 -0
  33. data/spec/schema.rb +42 -0
  34. data/spec/spec.opts +1 -0
  35. data/spec/spec_helper.rb +51 -0
  36. data/spec/twitter_auth/cryptify_spec.rb +51 -0
  37. data/spec/twitter_auth/dispatcher/basic_spec.rb +83 -0
  38. data/spec/twitter_auth/dispatcher/oauth_spec.rb +72 -0
  39. data/spec/twitter_auth/dispatcher/shared_spec.rb +26 -0
  40. data/spec/twitter_auth_spec.rb +160 -0
  41. metadata +151 -0
@@ -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,100 @@
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,
64
+ :signature_method ].each do |oauth_option|
65
+ options[oauth_option] = TwitterAuth.config[oauth_option.to_s] if TwitterAuth.config[oauth_option.to_s]
66
+ end
67
+
68
+ OAuth::Consumer.new(
69
+ config['oauth_consumer_key'],
70
+ config['oauth_consumer_secret'],
71
+ options
72
+ )
73
+ end
74
+
75
+ def self.net
76
+ uri = URI.parse(TwitterAuth.base_url)
77
+ net = Net::HTTP.new(uri.host, uri.port)
78
+ net.use_ssl = uri.scheme == 'https'
79
+ net.read_timeout = TwitterAuth.api_timeout
80
+ net
81
+ end
82
+
83
+ def self.authorize_path
84
+ config['authorize_path'] || '/oauth/authorize'
85
+ end
86
+ end
87
+
88
+ require 'twitter_auth/controller_extensions'
89
+ require 'twitter_auth/cryptify'
90
+ require 'twitter_auth/dispatcher/oauth'
91
+ require 'twitter_auth/dispatcher/basic'
92
+ require 'twitter_auth/dispatcher/shared'
93
+
94
+ module TwitterAuth
95
+ module Dispatcher
96
+ class Error < StandardError; end
97
+ class Unauthorized < Error; end
98
+ end
99
+ end
100
+
@@ -0,0 +1,82 @@
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_back_or_default destination
20
+ end
21
+
22
+ def onboard_user(message = 'Welcome to Vowch!', destination='/')
23
+ flash[:notice] = message
24
+ redirect_back_or_default destination
25
+ end
26
+
27
+ def current_user
28
+ @current_user ||=
29
+ if session[:user_id]
30
+ User.find_by_id(session[:user_id])
31
+ elsif cookies[:remember_token]
32
+ begin
33
+ User.from_remember_token(cookies[:remember_token])
34
+ rescue
35
+ cookies.delete(:remember_token)
36
+ false
37
+ end
38
+ else
39
+ false
40
+ end
41
+ end
42
+
43
+ def current_user=(new_user)
44
+ session[:user_id] = new_user.id
45
+ @current_user = new_user
46
+ end
47
+
48
+ def authorized?
49
+ !!current_user
50
+ end
51
+
52
+ def login_required
53
+ authorized? || access_denied
54
+ end
55
+
56
+ def access_denied
57
+ store_location
58
+ redirect_to login_path
59
+ end
60
+
61
+ def store_location
62
+ session[:return_to] = request.request_uri
63
+ end
64
+
65
+ def redirect_back_or_default(default)
66
+ redirect_to(session[:return_to] || default)
67
+ session[:return_to] = nil
68
+ end
69
+
70
+ def logged_in?
71
+ !!current_user
72
+ end
73
+
74
+ def logout_keeping_session!
75
+ session[:user_id] = nil
76
+ @current_user = nil
77
+ cookies.delete(:remember_token)
78
+ end
79
+ end
80
+ end
81
+
82
+ ActionController::Base.send(:include, TwitterAuth::ControllerExtensions)
@@ -0,0 +1,31 @@
1
+ module TwitterAuth
2
+ module Cryptify
3
+ class Error < StandardError; end
4
+
5
+ def self.encrypt(data)
6
+ salt = generate_salt
7
+ key = EzCrypto::Key.with_password(TwitterAuth.encryption_key, salt)
8
+ {:encrypted_data => key.encrypt64(data), :salt => salt}
9
+ end
10
+
11
+ def self.decrypt(encrypted_data_or_hash, salt=nil)
12
+ case encrypted_data_or_hash
13
+ when String
14
+ encrypted_data = encrypted_data_or_hash
15
+ raise ArgumentError, 'Must provide a salt to decrypt.' unless salt
16
+ when Hash
17
+ encrypted_data = encrypted_data_or_hash[:encrypted_data]
18
+ salt = encrypted_data_or_hash[:salt]
19
+ else
20
+ raise ArgumentError, 'Must provide either an encrypted hash result or encrypted string and salt.'
21
+ end
22
+ key = EzCrypto::Key.with_password(TwitterAuth.encryption_key, salt)
23
+ key.decrypt64(encrypted_data)
24
+ end
25
+
26
+ def self.generate_salt
27
+ ActiveSupport::SecureRandom.hex(4)
28
+ end
29
+ end
30
+ end
31
+
@@ -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,27 @@
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 = "http://api.twitter.com/1" + path
18
+ #path = TwitterAuth.path_prefix + path
19
+ path = append_extension_to(path)
20
+
21
+ response = super
22
+
23
+ handle_response(response)
24
+ end
25
+ end
26
+ end
27
+ 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
data/rails/init.rb ADDED
@@ -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.")
@@ -0,0 +1,162 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ ActionController::Routing::Routes.draw do |map|
4
+ map.connect ':controller/:action/:id'
5
+ end
6
+
7
+ class TwitterAuthTestController < ApplicationController
8
+ before_filter :login_required, :only => [:login_required_action]
9
+
10
+ def login_required_action
11
+ render :text => "You are logged in!"
12
+ end
13
+
14
+ def fail_auth
15
+ authentication_failed('Auth FAIL.')
16
+ end
17
+
18
+ def pass_auth
19
+ if params[:message]
20
+ authentication_succeeded(params[:message])
21
+ else
22
+ authentication_succeeded
23
+ end
24
+ end
25
+
26
+ def access_denied_action
27
+ access_denied
28
+ end
29
+
30
+ def redirect_back_action
31
+ redirect_back_or_default(params[:to] || '/')
32
+ end
33
+
34
+ def logout_keeping_session_action
35
+ logout_keeping_session!
36
+ redirect_back_or_default('/')
37
+ end
38
+
39
+ def current_user_action
40
+ @user = current_user
41
+ render :nothing => true
42
+ end
43
+ end
44
+
45
+ describe TwitterAuthTestController do
46
+ before do
47
+ controller.stub!(:cookies).and_return({})
48
+ end
49
+
50
+ %w(authentication_failed authentication_succeeded current_user authorized? login_required access_denied store_location redirect_back_or_default logout_keeping_session!).each do |m|
51
+ it "should respond to the extension method '#{m}'" do
52
+ controller.should respond_to(m)
53
+ end
54
+ end
55
+
56
+ describe "#authentication_failed" do
57
+ it 'should set the flash[:error] to the message passed in' do
58
+ get :fail_auth
59
+ flash[:error].should == 'Auth FAIL.'
60
+ end
61
+
62
+ it 'should redirect to the root' do
63
+ get :fail_auth
64
+ should redirect_to('/')
65
+ end
66
+ end
67
+
68
+ describe "#authentication_succeeded" do
69
+ it 'should set the flash[:notice] to a default success message' do
70
+ get :pass_auth
71
+ flash[:notice].should == 'You have logged in successfully.'
72
+ end
73
+
74
+ it 'should be able ot receive a custom message' do
75
+ get :pass_auth, :message => 'Eat at Joes.'
76
+ flash[:notice].should == 'Eat at Joes.'
77
+ end
78
+ end
79
+
80
+ describe '#current_user' do
81
+ it 'should find the user based on the session user_id' do
82
+ user = Factory.create(:twitter_oauth_user)
83
+ request.session[:user_id] = user.id
84
+ get(:current_user_action)
85
+ assigns[:user].should == user
86
+ end
87
+
88
+ it 'should log the user in through a cookie' do
89
+ user = Factory(:twitter_oauth_user, :remember_token => 'abc', :remember_token_expires_at => (Time.now + 10.days))
90
+ controller.stub!(:cookies).and_return({:remember_token => 'abc'})
91
+ get :current_user_action
92
+ assigns[:user].should == user
93
+ end
94
+
95
+ it 'should return nil if there is no user matching that id' do
96
+ request.session[:user_id] = 2345
97
+ get :current_user_action
98
+ assigns[:user].should be_nil
99
+ end
100
+ end
101
+
102
+ describe "#authorized?" do
103
+ it 'should be true if there is a current_user' do
104
+ user = Factory.create(:twitter_oauth_user)
105
+ controller.stub!(:current_user).and_return(user)
106
+ controller.send(:authorized?).should be_true
107
+ end
108
+
109
+ it 'should be false if there is not current_user' do
110
+ controller.stub!(:current_user).and_return(nil)
111
+ controller.send(:authorized?).should be_false
112
+ end
113
+ end
114
+
115
+ describe '#access_denied' do
116
+ it 'should redirect to the login path' do
117
+ get :access_denied_action
118
+ should redirect_to(login_path)
119
+ end
120
+
121
+ it 'should store the location first' do
122
+ controller.should_receive(:store_location).once
123
+ get :access_denied_action
124
+ end
125
+ end
126
+
127
+ describe '#redirect_back_or_default' do
128
+ it 'should redirect if there is a session[:return_to]' do
129
+ request.session[:return_to] = '/'
130
+ get :redirect_back_action, :to => '/notroot'
131
+ should redirect_to('/')
132
+ end
133
+
134
+ it 'should redirect to the default provided otherwise' do
135
+ get :redirect_back_action, :to => '/someurl'
136
+ should redirect_to('/someurl')
137
+ end
138
+ end
139
+
140
+ describe 'logout_keeping_session!' do
141
+ before do
142
+ @user = Factory.create(:twitter_oauth_user)
143
+ request.session[:user_id] = @user.id
144
+ end
145
+
146
+ it 'should unset session[:user_id]' do
147
+ get :logout_keeping_session_action
148
+ request.session[:user_id].should be_nil
149
+ end
150
+
151
+ it 'should unset current_user' do
152
+ controller.send(:current_user).should == @user
153
+ get :logout_keeping_session_action
154
+ controller.send(:current_user).should be_false
155
+ end
156
+
157
+ it 'should unset the cookie' do
158
+ controller.send(:cookies).should_receive(:delete).with(:remember_token)
159
+ get :logout_keeping_session_action
160
+ end
161
+ end
162
+ end