millsb-twitter-auth 0.1.16

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 +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,142 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe TwitterAuth::GenericUser do
4
+ should_validate_presence_of :login
5
+ should_validate_format_of :login, 'some_guy', 'awesome', 'cool_man'
6
+ should_not_validate_format_of :login, 'with-dashes', 'with.periods', 'with spaces'
7
+ should_validate_length_of :login, :in => 1..15
8
+
9
+ it 'should validate uniqueness of login' do
10
+ Factory.create(:twitter_oauth_user)
11
+ Factory.build(:twitter_oauth_user).should have_at_least(1).errors_on(:login)
12
+ end
13
+
14
+ it 'should validate uniqueness of remember_token' do
15
+ Factory.create(:twitter_oauth_user, :remember_token => 'abc')
16
+ Factory.build(:twitter_oauth_user, :remember_token => 'abc').should have_at_least(1).errors_on(:remember_token)
17
+ end
18
+
19
+ it 'should allow capital letters in the username' do
20
+ Factory.build(:twitter_oauth_user, :login => 'TwitterMan').should have(:no).errors_on(:login)
21
+ end
22
+
23
+ it 'should not allow the same login with different capitalization' do
24
+ Factory.create(:twitter_oauth_user, :login => 'twitterman')
25
+ Factory.build(:twitter_oauth_user, :login => 'TwitterMan').should have_at_least(1).errors_on(:login)
26
+ end
27
+
28
+ describe '.new_from_twitter_hash' do
29
+ it 'should raise an argument error if the hash does not have a screen_name attribute' do
30
+ lambda{User.new_from_twitter_hash({})}.should raise_error(ArgumentError, 'Invalid hash: must include screen_name.')
31
+ end
32
+
33
+ it 'should return a user' do
34
+ User.new_from_twitter_hash({'screen_name' => 'twitterman'}).should be_a(User)
35
+ end
36
+
37
+ it 'should assign login to the screen_name' do
38
+ User.new_from_twitter_hash({'screen_name' => 'twitterman'}).login.should == 'twitterman'
39
+ end
40
+
41
+ it 'should assign twitter attributes that are provided' do
42
+ u = User.new_from_twitter_hash({'screen_name' => 'twitterman', 'name' => 'Twitter Man', 'description' => 'Saving the world for all Tweet kind.'})
43
+ u.name.should == 'Twitter Man'
44
+ u.description.should == 'Saving the world for all Tweet kind.'
45
+ end
46
+ end
47
+
48
+ describe '#update_twitter_attributes' do
49
+ it 'should assign values to the user' do
50
+ user = Factory.create(:twitter_oauth_user, :name => "Dude", :description => "Awesome, man.")
51
+ user.update_twitter_attributes({'name' => 'Twitter Man', 'description' => 'Works.'})
52
+ user.reload
53
+ user.name.should == 'Twitter Man'
54
+ user.description.should == 'Works.'
55
+ end
56
+
57
+ it 'should not throw an error with extraneous info' do
58
+ user = Factory.create(:twitter_oauth_user, :name => "Dude", :description => "Awesome, man.")
59
+ lambda{user.update_twitter_attributes({'name' => 'Twitter Man', 'description' => 'Works.', 'whoopsy' => 'noworks.'})}.should_not raise_error
60
+ end
61
+ end
62
+
63
+ describe '#remember_me' do
64
+ before do
65
+ @user = Factory(:twitter_oauth_user)
66
+ end
67
+
68
+ it 'should check for the remember_token column' do
69
+ @user.should_receive(:respond_to?).with(:remember_token).and_return(false)
70
+ @user.remember_me
71
+ end
72
+
73
+ it 'should return nil if there is no remember_token column' do
74
+ @user.should_receive(:respond_to?).with(:remember_token).and_return(false)
75
+ @user.remember_me.should be_false
76
+ end
77
+
78
+ describe ' with proper columns' do
79
+ it 'should generate a secure random token' do
80
+ ActiveSupport::SecureRandom.should_receive(:hex).with(10).and_return('abcdef')
81
+ @user.remember_me
82
+ @user.remember_token.should == 'abcdef'
83
+ end
84
+
85
+ it 'should set the expiration to the current time plus the remember_for period' do
86
+ TwitterAuth.stub!(:remember_for).and_return(10)
87
+ time = Time.now
88
+ Time.stub!(:now).and_return(time)
89
+
90
+ @user.remember_me
91
+
92
+ @user.remember_token_expires_at.should == Time.now + 10.days
93
+ end
94
+
95
+ it 'should return a hash with a :value and :expires key' do
96
+ result = @user.remember_me
97
+ result.should be_a(Hash)
98
+ result.key?(:value).should be_true
99
+ result.key?(:expires).should be_true
100
+ end
101
+
102
+ it 'should return a hash with appropriate values' do
103
+ TwitterAuth.stub!(:remember_for).and_return(10)
104
+ time = Time.now
105
+ Time.stub!(:now).and_return(time)
106
+ ActiveSupport::SecureRandom.stub!(:hex).and_return('abcdef')
107
+
108
+ @user.remember_me.should == {:value => 'abcdef', :expires => (Time.now + 10.days)}
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#forget_me' do
114
+ it 'should reset remember_token and remember_token_expires_at' do
115
+ @user = Factory(:twitter_oauth_user, :remember_token => "abcdef", :remember_token_expires_at => Time.now + 10.days)
116
+ @user.forget_me
117
+ @user.reload
118
+ @user.remember_token.should be_nil
119
+ @user.remember_token_expires_at.should be_nil
120
+ end
121
+ end
122
+
123
+ describe '.from_remember_token' do
124
+ before do
125
+ @user = Factory(:twitter_oauth_user, :remember_token => 'abcdef', :remember_token_expires_at => (Time.now + 10.days))
126
+ end
127
+
128
+ it 'should find the user with the specified remember_token' do
129
+ User.from_remember_token('abcdef').should == @user
130
+ end
131
+
132
+ it 'should not find a user with an expired token' do
133
+ user2 = Factory(:twitter_oauth_user, :login => 'walker', :remember_token => 'ghijkl', :remember_token_expires_at => (Time.now - 10.days))
134
+ User.from_remember_token('ghijkl').should be_nil
135
+ end
136
+
137
+ it 'should not find a user with a nil token and an expiration' do
138
+ user = Factory(:twitter_oauth_user, :login => 'stranger', :remember_token => nil, :remember_token_expires_at => (Time.now + 10.days))
139
+ User.from_remember_token(nil).should be_nil
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,94 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe TwitterAuth::OauthUser do
4
+ before do
5
+ stub_oauth!
6
+ end
7
+
8
+ describe '.identify_or_create_from_access_token' do
9
+ before do
10
+ @token = OAuth::AccessToken.new(TwitterAuth.consumer, 'faketoken', 'fakesecret')
11
+ end
12
+
13
+ it 'should accept an OAuth::AccessToken' do
14
+ lambda{ User.identify_or_create_from_access_token(@token) }.should_not raise_error(ArgumentError)
15
+ end
16
+
17
+ it 'should accept two strings' do
18
+ lambda{ User.identify_or_create_from_access_token('faketoken', 'fakesecret') }.should_not raise_error(ArgumentError)
19
+ end
20
+
21
+ it 'should not accept one string' do
22
+ lambda{ User.identify_or_create_from_access_token('faketoken') }.should raise_error(ArgumentError, 'Must authenticate with an OAuth::AccessToken or the string access token and secret.')
23
+ end
24
+
25
+ it 'should make a call to verify_credentials' do
26
+ # this is in the before, just making it explicit
27
+ User.identify_or_create_from_access_token(@token)
28
+ end
29
+
30
+ it 'should try to find the user with that login' do
31
+ User.should_receive(:find_by_login).once.with('twitterman')
32
+ User.identify_or_create_from_access_token(@token)
33
+ end
34
+
35
+ it 'should return the user if he/she exists' do
36
+ user = Factory.create(:twitter_oauth_user, :login => 'twitterman')
37
+ user.reload
38
+ User.identify_or_create_from_access_token(@token).should == user
39
+ end
40
+
41
+ it 'should update the access_token and access_secret for the user if he/she exists' do
42
+ user = Factory.create(:twitter_oauth_user, :login => 'twitterman', :access_token => 'someothertoken', :access_secret => 'someothersecret')
43
+ User.identify_or_create_from_access_token(@token)
44
+ user.reload
45
+ user.access_token.should == @token.token
46
+ user.access_secret.should == @token.secret
47
+ end
48
+
49
+ it 'should update the user\'s attributes based on the twitter info' do
50
+ user = Factory.create(:twitter_oauth_user, :login => 'twitterman', :name => 'Not Twitter Man')
51
+ User.identify_or_create_from_access_token(@token).name.should == 'Twitter Man'
52
+ end
53
+
54
+ it 'should create a user if one does not exist' do
55
+ lambda{User.identify_or_create_from_access_token(@token)}.should change(User, :count).by(1)
56
+ end
57
+
58
+ it 'should assign the oauth access token and secret' do
59
+ user = User.identify_or_create_from_access_token(@token)
60
+ user.access_token.should == @token.token
61
+ user.access_secret.should == @token.secret
62
+ end
63
+ end
64
+
65
+ describe '#token' do
66
+ before do
67
+ @user = Factory.create(:twitter_oauth_user, :access_token => 'token', :access_secret => 'secret')
68
+ end
69
+
70
+ it 'should return an AccessToken' do
71
+ @user.token.should be_a(OAuth::AccessToken)
72
+ end
73
+
74
+ it "should use the user's access_token and secret" do
75
+ @user.token.token.should == @user.access_token
76
+ @user.token.secret.should == @user.access_secret
77
+ end
78
+ end
79
+
80
+ describe '#twitter' do
81
+ before do
82
+ @user = Factory.create(:twitter_oauth_user, :access_token => 'token', :access_secret => 'secret')
83
+ end
84
+
85
+ it 'should return a TwitterAuth::Dispatcher::Oauth' do
86
+ @user.twitter.should be_a(TwitterAuth::Dispatcher::Oauth)
87
+ end
88
+
89
+ it 'should use my token and secret' do
90
+ @user.twitter.token.should == @user.access_token
91
+ @user.twitter.secret.should == @user.access_secret
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,41 @@
1
+ ActiveRecord::Schema.define :version => 0 do
2
+ create_table :twitter_auth_users, :force => true do |t|
3
+ t.string :login
4
+
5
+ # OAuth fields
6
+ t.string :access_token
7
+ t.string :access_secret
8
+
9
+ # Basic fields
10
+ t.binary :crypted_password
11
+ t.string :salt
12
+
13
+ # Remember token fields
14
+ t.string :remember_token
15
+ t.datetime :remember_token_expires_at
16
+
17
+ # This information is automatically kept
18
+ # in-sync at each login of the user. You
19
+ # may remove any/all of these columns.
20
+ t.string :name
21
+ t.string :location
22
+ t.string :description
23
+ t.string :profile_image_url
24
+ t.string :url
25
+ t.boolean :protected
26
+ t.string :profile_background_color
27
+ t.string :profile_sidebar_fill_color
28
+ t.string :profile_link_color
29
+ t.string :profile_sidebar_border_color
30
+ t.string :profile_text_color
31
+ t.integer :friends_count
32
+ t.integer :statuses_count
33
+ t.integer :followers_count
34
+ t.integer :favourites_count
35
+ t.integer :utc_offset
36
+ t.string :time_zone
37
+
38
+ t.timestamps
39
+ end
40
+ end
41
+
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,51 @@
1
+ begin
2
+ require File.dirname(__FILE__) + '/../../../../spec/spec_helper'
3
+ rescue LoadError
4
+ puts "You need to install rspec in your base app"
5
+ exit
6
+ end
7
+
8
+ require File.dirname(__FILE__) + '/../app/models/twitter_auth/generic_user'
9
+
10
+ class TwitterAuth::GenericUser
11
+ def self.table_name; 'twitter_auth_users' end
12
+ end
13
+
14
+ class User < TwitterAuth::GenericUser; end
15
+
16
+ require 'remarkable'
17
+ require File.dirname(__FILE__) + '/fixtures/factories'
18
+ require File.dirname(__FILE__) + '/fixtures/fakeweb'
19
+ require File.dirname(__FILE__) + '/fixtures/twitter'
20
+
21
+ plugin_spec_dir = File.dirname(__FILE__)
22
+ ActiveRecord::Base.logger = Logger.new(plugin_spec_dir + "/debug.log")
23
+
24
+ load(File.dirname(__FILE__) + '/schema.rb')
25
+
26
+ def define_basic_user_class!
27
+ TwitterAuth::GenericUser.send :include, TwitterAuth::BasicUser
28
+ end
29
+
30
+ def define_oauth_user_class!
31
+ TwitterAuth::GenericUser.send :include, TwitterAuth::OauthUser
32
+ end
33
+
34
+ def stub_oauth!
35
+ TwitterAuth.stub!(:config).and_return({
36
+ 'strategy' => 'oauth',
37
+ 'oauth_consumer_key' => 'testkey',
38
+ 'oauth_consumer_secret' => 'testsecret'
39
+ })
40
+ define_oauth_user_class!
41
+ end
42
+
43
+ def stub_basic!
44
+ TwitterAuth.stub!(:config).and_return({
45
+ 'strategy' => 'basic',
46
+ 'encryption_key' => 'secretcode'
47
+ })
48
+ define_basic_user_class!
49
+ end
50
+
51
+ define_oauth_user_class!
@@ -0,0 +1,51 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe TwitterAuth::Cryptify do
4
+ before do
5
+ stub_basic!
6
+ end
7
+
8
+ it 'should have encrypt and decrypt methods' do
9
+ TwitterAuth::Cryptify.should respond_to(:encrypt)
10
+ TwitterAuth::Cryptify.should respond_to(:decrypt)
11
+ end
12
+
13
+ describe '.encrypt' do
14
+ it 'should return a hash with :encrypted_data and :salt keys' do
15
+ result = TwitterAuth::Cryptify.encrypt('some string')
16
+ result.should be_a(Hash)
17
+ result.key?(:encrypted_data).should be_true
18
+ result.key?(:salt).should be_true
19
+ end
20
+
21
+ it 'should make a call to EzCrypto::Key.encrypt_with_password' do
22
+ EzCrypto::Key.should_receive(:encrypt_with_password).once.and_return('gobbledygook')
23
+ TwitterAuth::Cryptify.encrypt('some string')
24
+ end
25
+
26
+ it 'should not have the same encrypted as plaintext data' do
27
+ TwitterAuth::Cryptify.encrypt('some string')[:encrypted_data].should_not == 'some string'
28
+ end
29
+ end
30
+
31
+ describe '.decrypt' do
32
+ before do
33
+ @salt = TwitterAuth::Cryptify.generate_salt
34
+ TwitterAuth::Cryptify.stub!(:generate_salt).and_return(@salt)
35
+ @string = 'decrypted string'
36
+ @encrypted = TwitterAuth::Cryptify.encrypt(@string)
37
+ end
38
+
39
+ it 'should return the original string' do
40
+ TwitterAuth::Cryptify.decrypt(@encrypted).should == @string
41
+ end
42
+
43
+ it 'should raise an argument error if encrypted data is provided without a salt' do
44
+ lambda{TwitterAuth::Cryptify.decrypt('asodiaoie2')}.should raise_error(ArgumentError)
45
+ end
46
+
47
+ it 'should raise an argument error if a string or hash are not provided' do
48
+ lambda{TwitterAuth::Cryptify.decrypt(23)}.should raise_error(ArgumentError)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,83 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe TwitterAuth::Dispatcher::Basic do
4
+ before do
5
+ stub_basic!
6
+ @user = Factory.create(:twitter_basic_user, :login => 'twitterman', :password => 'test')
7
+ end
8
+
9
+ it 'should require a user as the initialization argument' do
10
+ lambda{TwitterAuth::Dispatcher::Basic.new(nil)}.should raise_error(TwitterAuth::Error, 'Dispatcher must be initialized with a User.')
11
+ end
12
+
13
+ it 'should store the user in an attr_accessor' do
14
+ TwitterAuth::Dispatcher::Basic.new(@user).user.should == @user
15
+ end
16
+
17
+ describe '#request' do
18
+ before do
19
+ @dispatcher = TwitterAuth::Dispatcher::Basic.new(@user)
20
+ FakeWeb.register_uri('https://twitter.com:443/fake.json', :string => {'fake' => true}.to_json)
21
+ FakeWeb.register_uri('https://twitter.com:443/fake.xml', :string => '<fake>true</fake>')
22
+ end
23
+
24
+ it 'should automatically parse JSON if valid' do
25
+ @dispatcher.request(:get, '/fake.json').should == {'fake' => true}
26
+ end
27
+
28
+ it 'should return XML as a string' do
29
+ @dispatcher.request(:get, '/fake.xml').should == "<fake>true</fake>"
30
+ end
31
+
32
+ it 'should append .json to the path if no extension is provided' do
33
+ @dispatcher.request(:get, '/fake.json').should == @dispatcher.request(:get, '/fake')
34
+ end
35
+
36
+ %w(get post put delete).each do |method|
37
+ it "should build a #{method} class based on a :#{method} http_method" do
38
+ @req = "Net::HTTP::#{method.capitalize}".constantize.new('/fake.json')
39
+ "Net::HTTP::#{method.capitalize}".constantize.should_receive(:new).and_return(@req)
40
+ @dispatcher.request(method.to_sym, '/fake')
41
+ end
42
+ end
43
+
44
+ it 'should start the HTTP session' do
45
+ @net = TwitterAuth.net
46
+ TwitterAuth.stub!(:net).and_return(@net)
47
+ @net.should_receive(:start)
48
+ lambda{@dispatcher.request(:get, '/fake')}.should raise_error(NoMethodError)
49
+ end
50
+
51
+ it "should raise a TwitterAuth::Dispatcher::Error if response code isn't 200" do
52
+ FakeWeb.register_uri('https://twitter.com:443/bad_response.json', :string => {'error' => 'bad response'}.to_json, :status => ['401', 'Unauthorized'])
53
+ lambda{@dispatcher.request(:get, '/bad_response')}.should raise_error(TwitterAuth::Dispatcher::Error)
54
+ end
55
+
56
+ it 'should set the error message to the JSON message' do
57
+ FakeWeb.register_uri('https://twitter.com:443/bad_response.json', :string => {'error' => 'bad response'}.to_json, :status => ['403', 'Forbidden'])
58
+ lambda{@dispatcher.request(:get, '/bad_response')}.should raise_error(TwitterAuth::Dispatcher::Error, 'bad response')
59
+ end
60
+
61
+ it 'should raise a TwitterAuth::Dispatcher::Unauthorized on 401' do
62
+ FakeWeb.register_uri('https://twitter.com:443/unauthenticated_response.xml', :string => "<hash>\n<request>/unauthenticated_response.xml</request>\n<error>bad response</error>\n</hash>", :status => ['401', 'Unauthorized'])
63
+ lambda{@dispatcher.request(:get, '/unauthenticated_response.xml')}.should raise_error(TwitterAuth::Dispatcher::Unauthorized)
64
+ end
65
+
66
+ it 'should set the error message to the XML message' do
67
+ FakeWeb.register_uri('https://twitter.com:443/bad_response.xml', :string => "<hash>\n<request>/bad_response.xml</request>\n<error>bad response</error>\n</hash>", :status => ['403', 'Forbidden'])
68
+ lambda{@dispatcher.request(:get, '/bad_response')}.should raise_error(TwitterAuth::Dispatcher::Error, 'bad response')
69
+ end
70
+ end
71
+
72
+ %w(get post delete put).each do |method|
73
+ it "should have a ##{method} method that calls request(:#{method})" do
74
+ dispatcher = TwitterAuth::Dispatcher::Basic.new(@user)
75
+ if %w(get delete).include?(method)
76
+ dispatcher.should_receive(:request).with(method.to_sym, '/fake.json')
77
+ else
78
+ dispatcher.should_receive(:request).with(method.to_sym, '/fake.json', '')
79
+ end
80
+ dispatcher.send(method, '/fake.json')
81
+ end
82
+ end
83
+ end