sparkly-auth 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +17 -0
  3. data/Rakefile +50 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/sparkly_accounts_controller.rb +59 -0
  6. data/app/controllers/sparkly_controller.rb +47 -0
  7. data/app/controllers/sparkly_sessions_controller.rb +52 -0
  8. data/app/models/password.rb +3 -0
  9. data/app/models/remembrance_token.rb +50 -0
  10. data/app/views/sparkly_accounts/edit.html.erb +24 -0
  11. data/app/views/sparkly_accounts/new.html.erb +24 -0
  12. data/app/views/sparkly_accounts/show.html.erb +0 -0
  13. data/app/views/sparkly_sessions/new.html.erb +22 -0
  14. data/dependencies.rb +1 -0
  15. data/generators/sparkly/USAGE +27 -0
  16. data/generators/sparkly/sparkly_generator.rb +76 -0
  17. data/generators/sparkly/templates/accounts_controller.rb +65 -0
  18. data/generators/sparkly/templates/accounts_helper.rb +2 -0
  19. data/generators/sparkly/templates/help_file.txt +56 -0
  20. data/generators/sparkly/templates/initializer.rb +30 -0
  21. data/generators/sparkly/templates/migrations/add_confirmed_to_sparkly_passwords.rb +9 -0
  22. data/generators/sparkly/templates/migrations/create_sparkly_passwords.rb +19 -0
  23. data/generators/sparkly/templates/migrations/create_sparkly_remembered_tokens.rb +15 -0
  24. data/generators/sparkly/templates/sessions_controller.rb +45 -0
  25. data/generators/sparkly/templates/sessions_helper.rb +2 -0
  26. data/generators/sparkly/templates/tasks/migrations.rb +1 -0
  27. data/generators/sparkly/templates/views/sparkly_accounts/edit.html.erb +24 -0
  28. data/generators/sparkly/templates/views/sparkly_accounts/new.html.erb +24 -0
  29. data/generators/sparkly/templates/views/sparkly_accounts/show.html.erb +0 -0
  30. data/generators/sparkly/templates/views/sparkly_sessions/new.html.erb +22 -0
  31. data/init.rb +44 -0
  32. data/lib/auth.rb +52 -0
  33. data/lib/auth/behavior/base.rb +64 -0
  34. data/lib/auth/behavior/core.rb +87 -0
  35. data/lib/auth/behavior/core/authenticated_model_methods.rb +52 -0
  36. data/lib/auth/behavior/core/controller_extensions.rb +52 -0
  37. data/lib/auth/behavior/core/controller_extensions/class_methods.rb +24 -0
  38. data/lib/auth/behavior/core/controller_extensions/current_user.rb +54 -0
  39. data/lib/auth/behavior/core/password_methods.rb +65 -0
  40. data/lib/auth/behavior/remember_me.rb +17 -0
  41. data/lib/auth/behavior/remember_me/configuration.rb +21 -0
  42. data/lib/auth/behavior/remember_me/controller_extensions.rb +66 -0
  43. data/lib/auth/behavior_lookup.rb +10 -0
  44. data/lib/auth/configuration.rb +328 -0
  45. data/lib/auth/encryptors/sha512.rb +20 -0
  46. data/lib/auth/generators/configuration_generator.rb +20 -0
  47. data/lib/auth/generators/controllers_generator.rb +34 -0
  48. data/lib/auth/generators/migration_generator.rb +32 -0
  49. data/lib/auth/generators/route_generator.rb +19 -0
  50. data/lib/auth/generators/views_generator.rb +26 -0
  51. data/lib/auth/model.rb +94 -0
  52. data/lib/auth/observer.rb +21 -0
  53. data/lib/auth/target_list.rb +5 -0
  54. data/lib/auth/tasks/migrations.rb +71 -0
  55. data/lib/auth/token.rb +10 -0
  56. data/lib/sparkly-auth.rb +1 -0
  57. data/rails/init.rb +17 -0
  58. data/rails/routes.rb +19 -0
  59. data/sparkly-auth.gemspec +143 -0
  60. data/spec/controllers/application_controller_spec.rb +13 -0
  61. data/spec/generators/sparkly_spec.rb +64 -0
  62. data/spec/lib/auth/behavior/core_spec.rb +184 -0
  63. data/spec/lib/auth/behavior/remember_me_spec.rb +127 -0
  64. data/spec/lib/auth/extensions/controller_spec.rb +32 -0
  65. data/spec/lib/auth/model_spec.rb +57 -0
  66. data/spec/lib/auth_spec.rb +32 -0
  67. data/spec/mocks/models/user.rb +3 -0
  68. data/spec/routes_spec.rb +24 -0
  69. data/spec/spec_helper.rb +61 -0
  70. data/spec/views_spec.rb +18 -0
  71. metadata +210 -0
@@ -0,0 +1,64 @@
1
+ require 'spec_helper'
2
+
3
+ describe :sparkly do
4
+ before(:each) do
5
+ Auth.configure do |config|
6
+ config.authenticate :user
7
+ end
8
+ end
9
+
10
+ context "with :remember_me behavior" do
11
+ before(:each) { Auth.configure { |c| c.behaviors << :remember_me } }
12
+
13
+ with_args :migrations do
14
+ it "should generate a remembered_tokens migration" do
15
+ subject.should generate("db/migrate/002_create_sparkly_remembered_tokens.rb")
16
+ end
17
+ end
18
+ end
19
+
20
+
21
+ with_args :migrations do
22
+ it "should generate db/migrate" do
23
+ subject.should generate("db/migrate")
24
+ subject.should generate("db/migrate/001_create_sparkly_passwords.rb")
25
+ end
26
+ end
27
+
28
+ with_args :config do
29
+ it "should generate the sparkly initializers" do
30
+ subject.should generate('lib/tasks/sparkly_migration.rb')
31
+ subject.should generate('config/initializers/sparkly_authentication.rb')
32
+ end
33
+ end
34
+
35
+ with_args :controllers do
36
+ it "should generate the sparkly controllers" do
37
+ Auth.configuration.authenticate(:user, :accounts_controller => 'users', :sessions_controller => 'user_sessions')
38
+ Auth.kick!
39
+
40
+ subject.should generate("app/controllers/users_controller.rb")
41
+ subject.should generate("app/controllers/user_sessions_controller.rb")
42
+ subject.should generate("app/helpers/users_helper.rb")
43
+ subject.should generate("app/helpers/user_sessions_helper.rb")
44
+ end
45
+ end
46
+
47
+ with_args :views do
48
+ it "should generate the sparkly views with /users if given the users controller" do
49
+ Auth.configuration.authenticate(:user, :accounts_controller => 'users', :sessions_controller => 'user_sessions')
50
+ Auth.kick!
51
+
52
+ subject.should generate("app/views/users/edit.html.erb")
53
+ subject.should generate("app/views/users/new.html.erb")
54
+ subject.should generate("app/views/users/show.html.erb")
55
+ subject.should generate("app/views/user_sessions/new.html.erb")
56
+ end
57
+ end
58
+
59
+ with_args :help do
60
+ it "should generate the sparkly help" do
61
+ subject.should generate('doc/sparkly_authentication.txt')
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,184 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Behavior: Core" do
4
+ subject { Auth::Model.new(:user, :behaviors => [:core], :password_update_frequency => 30.days) }
5
+
6
+ before(:each) do
7
+ subject.apply_options!
8
+ end
9
+
10
+ context "user" do
11
+ it "should not be able to reuse a previous password" do
12
+ u = User.new(:email => "generic1@example.com")
13
+ u.password = u.password_confirmation = "Generic12"
14
+ u.save!
15
+ u.password = u.password_confirmation = "Generic13"
16
+ u.save!
17
+ u.password = u.password_confirmation = "Generic12"
18
+
19
+ proc { u.save! }.should raise_error(ActiveRecord::RecordInvalid)
20
+ end
21
+
22
+ it "should produce multiple password models" do
23
+ u = User.new(:email => "generic2@example.com")
24
+ u.password = u.password_confirmation = "Generic12"
25
+ u.save!
26
+ u.password = u.password_confirmation = "Generic13"
27
+ u.save!
28
+
29
+ u.passwords.length.should == 2
30
+ end
31
+
32
+ it "should not store more than 4 passwords" do
33
+ User.destroy_all
34
+ u = User.new(:email => "generic3@example.com")
35
+ %w(Generic12 Generic13 Generic14 Generic15 Generic16 Generic17 Generic18).each do |pw|
36
+ u.password = u.password_confirmation = pw
37
+ u.save!
38
+ end
39
+
40
+ u.passwords.length.should == 4
41
+ end
42
+
43
+ it "should not be valid if password and confirmation are skipped" do
44
+ u = User.new(:email => "generic@example.com")
45
+ u.should_not be_valid
46
+ end
47
+
48
+ it "should create a user that can have a password validated against it" do
49
+ email = "generic@example.com"
50
+ password = "Ab12345"
51
+
52
+ User.destroy_all
53
+ returning(User.new(:email => email)) { |user|
54
+ user.password = password
55
+ user.password_confirmation = password
56
+ user.save!
57
+ }
58
+
59
+ User.find_by_email(email).password_matches?(password).should == true
60
+ end
61
+
62
+ it "should not produce error messages that contain 'secret' or are duplicates" do
63
+ u = User.new(:email => "generic@example.com")
64
+ u.password = u.password_confirmation = nil
65
+ u.valid?
66
+ u.errors.to_a.should == u.errors.to_a.uniq
67
+ u.errors.to_a.collect { |ar| ar.first }.should_not include('secret')
68
+ u.errors.to_a.collect { |ar| ar.first }.should_not include('secret_confirmation')
69
+ end
70
+
71
+ it "should create #password" do
72
+ u = User.new
73
+ u.password.should be_blank
74
+ end
75
+
76
+ it "should create #password=" do
77
+ u = User.new
78
+ u.password = "hi there"
79
+ u.password.should_not be_blank
80
+ end
81
+
82
+ it "should encrypt password assignments" do
83
+ u = User.new
84
+ u.password = "hi there"
85
+ u.password.should_not == "hi there"
86
+ end
87
+
88
+ it "should create #password_confirmation" do
89
+ u = User.new
90
+ u.password_confirmation.should be_blank
91
+ end
92
+
93
+ it "should create #password_confirmation=" do
94
+ u = User.new
95
+ u.password_confirmation = "hi there"
96
+ u.password_confirmation.should_not be_blank
97
+ end
98
+
99
+ it "should encrypt password confirmation assignments" do
100
+ u = User.new
101
+ u.password_confirmation = "hi there"
102
+ u.password_confirmation.should_not == "hi there"
103
+ end
104
+
105
+ it "should delegate persistence token to password model" do
106
+ u = User.new
107
+ u.password = "hi there"
108
+ u.persistence_token.should_not be_blank
109
+ end
110
+ end
111
+
112
+ it "should reset the persistence token when password is assigned" do
113
+ pw = Password.new
114
+ pw.secret = "Hello12"
115
+ pw.persistence_token.should_not be_blank
116
+ end
117
+
118
+ it "should encrypt the :secret and :secret_confirmation upon assignment in Password" do
119
+ u = User.new
120
+ pw = u.passwords.build
121
+ pw.secret = "Hello12"
122
+ pw.secret_confirmation = "Hello12"
123
+
124
+ pw.secret.should_not == "Hello12" # ...but we don't know what it actually should equal, 'cause that's randomized.
125
+ pw.secret_confirmation.should_not == "Hello12"
126
+ pw.secret.should == pw.secret_confirmation
127
+ end
128
+
129
+ it "should force confirmation of the :secret on Password" do
130
+ u = User.new
131
+ pw = u.passwords.build
132
+ pw.secret = "Hello12"
133
+ pw.valid?
134
+ pw.errors.to_a.should_not be_empty
135
+
136
+ pw.secret_confirmation = "Hello1"
137
+ pw.valid?
138
+ pw.errors.on(:secret).should == "doesn't match confirmation"
139
+
140
+ pw.secret_confirmation = "Hello12"
141
+ pw.should be_valid
142
+ end
143
+
144
+ it 'should validate presence of :secret on Password' do
145
+ error_on(Password, :secret).should == "can't be blank"
146
+ end
147
+
148
+ it 'should validate password length >= 7' do
149
+ error_on(Password, :secret, "123456").should include("must be at least 7 characters")
150
+ end
151
+
152
+ it 'should validate password complexity' do
153
+ error_on(Password, :secret, "Ab12345").should == nil
154
+ error_on(Password, :secret, "1234567").should ==
155
+ "must contain at least 1 uppercase, 1 lowercase and 1 number"
156
+ error_on(Password, :secret, "abcdefg").should ==
157
+ "must contain at least 1 uppercase, 1 lowercase and 1 number"
158
+ error_on(Password, :secret, "ABCDEFG").should ==
159
+ "must contain at least 1 uppercase, 1 lowercase and 1 number"
160
+ end
161
+
162
+ it 'should add has_many :passwords to User' do
163
+ User.new.should respond_to(:passwords)
164
+ end
165
+
166
+ it "should expire passwords after 30 days or if no password is available" do
167
+ (u = User.new).password_expired?.should be_true
168
+ p = u.passwords.build(:created_at => 1.month.ago)
169
+ p.authenticatable = u # need to build the reverse relationship cause it's not saved yet... i hate that.
170
+ p.should be_expired
171
+ end
172
+
173
+ context "with password update frequency 90 days" do
174
+ subject { Auth::Model.new(:user, :behaviors => [:core], :password_update_frequency => 90.days) }
175
+
176
+ it "should expire passwords after 90 days or if no password is available" do
177
+ (u = User.new).password_expired?.should be_true # because there're no passwords yet
178
+
179
+ p = u.passwords.build(:created_at => 1.month.ago)
180
+ p.authenticatable = u # need to build the reverse relationship cause it's not saved yet... i hate that.
181
+ p.should_not be_expired
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+ require 'spec/rails'
3
+
4
+ describe "Behavior: Remember Me", :type => :controller do
5
+ controller_name :sparkly_sessions
6
+
7
+ def cookies
8
+ controller.send(:cookies)
9
+ end
10
+
11
+ def reset_auth!
12
+ # the lack of a current user will trigger authentication of various flavors, making these tests possible.
13
+ controller.instance_variable_set("@current_user", nil)
14
+ end
15
+
16
+ before(:each) do
17
+ Auth.configure do |c|
18
+ c.authenticate :user
19
+ c.behaviors = :core, :remember_me
20
+ c.remember_me.duration = 6.months
21
+ end
22
+
23
+ Auth.kick!
24
+ u = User.new(:email => "generic12@example.com")
25
+ u.password = u.password_confirmation = "Generic12"
26
+ u.save!
27
+ end
28
+
29
+ it "should set an auth token cookie upon successful login" do
30
+ post :create, { :model => "User", :user => { :email => "generic12@example.com", :password => "Generic12", :remember_me => true } }
31
+
32
+ session[:session_token].should_not be_blank
33
+
34
+ # There should be a token in the remebered_tokens table. We can use that data to decide
35
+ # what should be in the cookie.
36
+ RemembranceToken.count.should == 1
37
+
38
+ # We're looking for a string containing password model ID, series token, and auth token.
39
+ token = RemembranceToken.first
40
+
41
+ cookies[:remembrance_token].should == token.value
42
+ end
43
+
44
+ context "a user with a remember token" do
45
+ before(:each) do
46
+ post :create, { :model => "User", :user => { :email => "generic12@example.com", :password => "Generic12", :remember_me => true } }
47
+ end
48
+
49
+ shared_examples_for "an expired or missing session" do
50
+ it "should authenticate with the auth token cookie" do
51
+ controller.current_user.should_not be_nil
52
+ end
53
+
54
+ it "should generate a new auth token cookie" do
55
+ token = cookies[:remembrance_token]
56
+ controller.current_user
57
+ cookies[:remembrance_token].should_not == token
58
+ end
59
+
60
+ it "should not change the series identifier" do
61
+ controller.current_user
62
+ controller.current_user.remembrance_tokens.first.series_token == @series_identifier
63
+ end
64
+
65
+ context "and an invalid token id but valid series id" do
66
+ before(:each) do
67
+ cookies[:remembrance_token] = { :value => cookies[:remembrance_token]+"1", :expires => 6.months.from_now }
68
+ reset_auth!
69
+ end
70
+
71
+ it "should be considered a theft" do
72
+ # because the token is changed every time - if the wrong token is used it is due either to tampering or to
73
+ # using an expired token, indicating that someone has stolen and used the one-use token.
74
+ controller.current_user
75
+ flash[:error].should == Auth.remember_me.token_theft_message
76
+ end
77
+
78
+ it "should delete all remembrance tokens" do
79
+ controller.current_user.remembrance_tokens.count.should == 0
80
+ end
81
+ end
82
+
83
+ context "and token data is not present" do
84
+ before(:each) do
85
+ cookies[:remembrance_token] = { :value => "", :expires => 6.months.from_now }
86
+ reset_auth!
87
+ end
88
+
89
+ it "should not authenticate the user" do
90
+ controller.current_user.should == false
91
+ end
92
+ end
93
+
94
+ context "and token is missing" do
95
+ before(:each) do
96
+ cookies.delete(:remembrance_token)
97
+ #cookies[:remembrance_token] = nil
98
+ reset_auth!
99
+ end
100
+
101
+ it "should not authenticate the user" do
102
+ controller.current_user.should == false
103
+ end
104
+ end
105
+ end
106
+
107
+ context "and an expired session" do
108
+ before(:each) do
109
+ @series_identifier = controller.current_user.remembrance_tokens.first.series_token
110
+ session[:active_at] = 30.days.ago # i'm pretty sure this is past the session duration.
111
+ reset_auth!
112
+ end
113
+
114
+ it_should_behave_like "an expired or missing session"
115
+ end
116
+
117
+ context "and a missing session" do
118
+ before(:each) do
119
+ @series_identifier = controller.current_user.remembrance_tokens.first.series_token
120
+ session.clear
121
+ reset_auth!
122
+ end
123
+
124
+ it_should_behave_like "an expired or missing session"
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Auth::Behavior::Core::ControllerExtensions do
4
+ #subject { ApplicationController.new }
5
+ subject { ApplicationController.call(Rack::MockRequest.env_for("/").merge('REQUEST_URI' => '')).template.controller }
6
+
7
+ before(:each) do
8
+ Auth.configure do |config|
9
+ config.session_duration = nil
10
+ config.authenticate :user
11
+ end
12
+
13
+ Auth.kick!
14
+
15
+ unless User.count == 1
16
+ u = User.new(:email => "generic4@example.com")
17
+ u.password = u.password_confirmation = "Generic12"
18
+ u.save!
19
+ end
20
+ end
21
+
22
+ it "should let users authenticate with single access token" do
23
+ subject.params = { :single_access_token => User.first.single_access_token }
24
+ subject.current_user.should be_kind_of(User)
25
+ end
26
+
27
+ it "should not raise nil errors when Auth.session_duration is nil" do
28
+ subject.session = { :session_token => User.first.persistence_token }
29
+
30
+ subject.current_user.should be_kind_of(User)
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+
3
+ describe Auth::Model do
4
+ context "given nonexisting model name" do
5
+ subject { Auth::Model.new(:nonexisting_user) }
6
+
7
+ it "should fail silently during initialization because it might not have been generated yet" do
8
+ proc { subject }.should_not raise_error
9
+ end
10
+ end
11
+
12
+ context "with default options" do
13
+ subject { Auth::Model.new(:user) }
14
+
15
+ before(:each) do
16
+ Dispatcher.cleanup_application
17
+ Dispatcher.reload_application
18
+ subject.apply_options!
19
+ end
20
+
21
+ it "should validate presence of :email on User" do
22
+ error_on(User, :email).should == "can't be blank"
23
+ end
24
+
25
+ it "should use :core behavior" do
26
+ subject.behaviors.should include(:core)
27
+ end
28
+ end
29
+
30
+ context "with an empty :behaviors option" do
31
+ subject { Auth::Model.new(:user, :behaviors => []) }
32
+ before(:each) do
33
+ Dispatcher.cleanup_application
34
+ Dispatcher.reload_application
35
+ subject.apply_options!
36
+ end
37
+ it "should have no behaviors" do subject.behaviors.should be_empty end
38
+ end
39
+
40
+ context "with a hash for :with option" do
41
+ subject { Auth::Model.new(:user, :with => {
42
+ :secret => :passwd,
43
+ :format => /^.{8}$/,
44
+ :message => "must be exactly 8 characters"
45
+ })}
46
+
47
+ before(:each) do
48
+ Dispatcher.cleanup_application
49
+ Dispatcher.reload_application
50
+ subject.apply_options!
51
+ end
52
+
53
+ it "should validate presence of :email on User" do
54
+ error_on(User, :email).should == "can't be blank"
55
+ end
56
+ end
57
+ end