jeremydurham-restful_authentication 1.1.2

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 (64) hide show
  1. data/CHANGELOG +68 -0
  2. data/LICENSE +20 -0
  3. data/README.textile +224 -0
  4. data/Rakefile +54 -0
  5. data/TODO +15 -0
  6. data/generators/authenticated/USAGE +1 -0
  7. data/generators/authenticated/authenticated_generator.rb +481 -0
  8. data/generators/authenticated/lib/insert_routes.rb +54 -0
  9. data/generators/authenticated/templates/_model_partial.html.erb +8 -0
  10. data/generators/authenticated/templates/activation.erb +3 -0
  11. data/generators/authenticated/templates/authenticated_system.rb +189 -0
  12. data/generators/authenticated/templates/authenticated_test_helper.rb +12 -0
  13. data/generators/authenticated/templates/controller.rb +43 -0
  14. data/generators/authenticated/templates/features/accounts.feature +63 -0
  15. data/generators/authenticated/templates/features/sessions.feature +77 -0
  16. data/generators/authenticated/templates/features/step_definitions/ra_env.rb +7 -0
  17. data/generators/authenticated/templates/features/step_definitions/user_steps.rb +31 -0
  18. data/generators/authenticated/templates/helper.rb +2 -0
  19. data/generators/authenticated/templates/login.html.erb +14 -0
  20. data/generators/authenticated/templates/machinist_spec.rb +5 -0
  21. data/generators/authenticated/templates/machinist_test.rb +5 -0
  22. data/generators/authenticated/templates/mailer.rb +25 -0
  23. data/generators/authenticated/templates/migration.rb +26 -0
  24. data/generators/authenticated/templates/model.rb +83 -0
  25. data/generators/authenticated/templates/model_controller.rb +85 -0
  26. data/generators/authenticated/templates/model_helper.rb +93 -0
  27. data/generators/authenticated/templates/model_helper_spec.rb +157 -0
  28. data/generators/authenticated/templates/observer.rb +11 -0
  29. data/generators/authenticated/templates/signup.html.erb +19 -0
  30. data/generators/authenticated/templates/signup_notification.erb +8 -0
  31. data/generators/authenticated/templates/site_keys.rb +38 -0
  32. data/generators/authenticated/templates/spec/blueprints/user.rb +6 -0
  33. data/generators/authenticated/templates/spec/controllers/access_control_spec.rb +89 -0
  34. data/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb +107 -0
  35. data/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb +138 -0
  36. data/generators/authenticated/templates/spec/controllers/users_controller_spec.rb +196 -0
  37. data/generators/authenticated/templates/spec/fixtures/users.yml +60 -0
  38. data/generators/authenticated/templates/spec/helpers/users_helper_spec.rb +141 -0
  39. data/generators/authenticated/templates/spec/models/user_spec.rb +298 -0
  40. data/generators/authenticated/templates/test/functional_test.rb +84 -0
  41. data/generators/authenticated/templates/test/mailer_test.rb +31 -0
  42. data/generators/authenticated/templates/test/model_functional_test.rb +91 -0
  43. data/generators/authenticated/templates/test/unit_test.rb +177 -0
  44. data/lib/authentication.rb +40 -0
  45. data/lib/authentication/by_cookie_token.rb +82 -0
  46. data/lib/authentication/by_password.rb +64 -0
  47. data/lib/authorization.rb +14 -0
  48. data/lib/authorization/aasm_roles.rb +63 -0
  49. data/lib/authorization/stateful_roles.rb +62 -0
  50. data/lib/restful_authentication.rb +3 -0
  51. data/lib/trustification.rb +14 -0
  52. data/lib/trustification/email_validation.rb +20 -0
  53. data/notes/AccessControl.txt +2 -0
  54. data/notes/Authentication.txt +5 -0
  55. data/notes/Authorization.txt +154 -0
  56. data/notes/RailsPlugins.txt +78 -0
  57. data/notes/SecurityFramework.graffle +0 -0
  58. data/notes/SecurityFramework.png +0 -0
  59. data/notes/SecurityPatterns.txt +163 -0
  60. data/notes/Tradeoffs.txt +126 -0
  61. data/notes/Trustification.txt +49 -0
  62. data/restful_authentication.gemspec +32 -0
  63. data/tasks/auth.rake +33 -0
  64. metadata +128 -0
@@ -0,0 +1,60 @@
1
+ <%
2
+ ## this code must match that in templates/model.rb
3
+ require 'digest/sha1'
4
+ def make_fake_token
5
+ @fake_token_counter ||= 0
6
+ @fake_token_counter += 1
7
+ Digest::SHA1.hexdigest(@fake_token_counter.to_s)
8
+ end
9
+ salts = (1..2).map{ make_fake_token }
10
+ passwds = salts.map{ |salt| password_digest('monkey', salt) }
11
+ -%>
12
+
13
+ quentin:
14
+ id: 1
15
+ login: quentin
16
+ email: quentin@example.com
17
+ salt: <%= salts[0] %> # SHA1('0')
18
+ crypted_password: <%= passwds[0] %> # 'monkey'
19
+ created_at: <%%= 5.days.ago.to_s :db %>
20
+ remember_token_expires_at: <%%= 1.days.from_now.to_s %>
21
+ remember_token: <%= make_fake_token %>
22
+ <% if options[:include_activation] -%>
23
+ activation_code:
24
+ activated_at: <%%= 5.days.ago.to_s :db %>
25
+ <% end -%>
26
+ <% if options[:stateful] -%>
27
+ state: active
28
+ <% end -%>
29
+
30
+ aaron:
31
+ id: 2
32
+ login: aaron
33
+ email: aaron@example.com
34
+ salt: <%= salts[1] %> # SHA1('1')
35
+ crypted_password: <%= passwds[1] %> # 'monkey'
36
+ created_at: <%%= 1.days.ago.to_s :db %>
37
+ remember_token_expires_at:
38
+ remember_token:
39
+ <% if options[:include_activation] -%>
40
+ activation_code: <%= make_fake_token %>
41
+ activated_at:
42
+ <% end -%>
43
+ <% if options[:stateful] %>
44
+ state: pending
45
+ <% end -%>
46
+
47
+
48
+ old_password_holder:
49
+ id: 3
50
+ login: old_password_holder
51
+ email: salty_dog@example.com
52
+ salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
53
+ crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
54
+ created_at: <%%= 1.days.ago.to_s :db %>
55
+ <% if options[:include_activation] %>
56
+ activation_code:
57
+ activated_at: <%%= 5.days.ago.to_s :db %>
58
+ <% end %>
59
+ <% if options[:stateful] %>
60
+ state: active<% end %>
@@ -0,0 +1,141 @@
1
+ require File.dirname(__FILE__) + '<%= ('/..'*model_controller_class_nesting_depth) + '/../spec_helper' %>'
2
+ include ApplicationHelper
3
+ include <%= model_controller_class_name %>Helper
4
+ include AuthenticatedTestHelper
5
+
6
+ describe <%= model_controller_class_name %>Helper do
7
+ before do
8
+ @<%= file_name %> = <%= class_name %>.make
9
+ end
10
+
11
+ describe "if_authorized" do
12
+ it "yields if authorized" do
13
+ should_receive(:authorized?).with('a','r').and_return(true)
14
+ if_authorized?('a','r'){|action,resource| [action,resource,'hi'] }.should == ['a','r','hi']
15
+ end
16
+ it "does nothing if not authorized" do
17
+ should_receive(:authorized?).with('a','r').and_return(false)
18
+ if_authorized?('a','r'){ 'hi' }.should be_nil
19
+ end
20
+ end
21
+
22
+ describe "link_to_<%= file_name %>" do
23
+ it "should give an error on a nil <%= file_name %>" do
24
+ lambda { link_to_<%= file_name %>(nil) }.should raise_error('Invalid <%= file_name %>')
25
+ end
26
+ it "should link to the given <%= file_name %>" do
27
+ should_receive(:<%= model_controller_routing_name.singularize %>_path).at_least(:once).and_return('/<%= model_controller_file_path %>/1')
28
+ link_to_<%= file_name %>(@<%= file_name %>).should have_tag("a[href='/<%= model_controller_file_path %>/1']")
29
+ end
30
+ it "should use given link text if :content_text is specified" do
31
+ link_to_<%= file_name %>(@<%= file_name %>, :content_text => 'Hello there!').should have_tag("a", 'Hello there!')
32
+ end
33
+ it "should use the login as link text with no :content_method specified" do
34
+ link_to_<%= file_name %>(@<%= file_name %>).should have_tag("a", @<%= file_name %>.login)
35
+ end
36
+ it "should use the name as link text with :content_method => :name" do
37
+ link_to_<%= file_name %>(@<%= file_name %>, :content_method => :name).should have_tag("a", @<%= file_name %>.name)
38
+ end
39
+ it "should use the login as title with no :title_method specified" do
40
+ link_to_<%= file_name %>(@<%= file_name %>).should have_tag("a[title='#{@<%= file_name %>.login}']")
41
+ end
42
+ it "should use the name as link title with :content_method => :name" do
43
+ link_to_<%= file_name %>(@<%= file_name %>, :title_method => :name).should have_tag("a[title='#{@<%= file_name %>.name}']")
44
+ end
45
+ it "should have nickname as a class by default" do
46
+ link_to_<%= file_name %>(@<%= file_name %>).should have_tag("a.nickname")
47
+ end
48
+ it "should take other classes and no longer have the nickname class" do
49
+ result = link_to_<%= file_name %>(@<%= file_name %>, :class => 'foo bar')
50
+ result.should have_tag("a.foo")
51
+ result.should have_tag("a.bar")
52
+ end
53
+ end
54
+
55
+ describe "link_to_login_with_IP" do
56
+ it "should link to the login_path" do
57
+ link_to_login_with_IP().should have_tag("a[href='/login']")
58
+ end
59
+ it "should use given link text if :content_text is specified" do
60
+ link_to_login_with_IP('Hello there!').should have_tag("a", 'Hello there!')
61
+ end
62
+ it "should use the login as link text with no :content_method specified" do
63
+ link_to_login_with_IP().should have_tag("a", '0.0.0.0')
64
+ end
65
+ it "should use the ip address as title" do
66
+ link_to_login_with_IP().should have_tag("a[title='0.0.0.0']")
67
+ end
68
+ it "should by default be like school in summer and have no class" do
69
+ link_to_login_with_IP().should_not have_tag("a.nickname")
70
+ end
71
+ it "should have some class if you tell it to" do
72
+ result = link_to_login_with_IP(nil, :class => 'foo bar')
73
+ result.should have_tag("a.foo")
74
+ result.should have_tag("a.bar")
75
+ end
76
+ it "should have some class if you tell it to" do
77
+ result = link_to_login_with_IP(nil, :tag => 'abbr')
78
+ result.should have_tag("abbr[title='0.0.0.0']")
79
+ end
80
+ end
81
+
82
+ describe "link_to_current_<%= file_name %>, When logged in" do
83
+ before do
84
+ stub!(:current_<%= file_name %>).and_return(@<%= file_name %>)
85
+ end
86
+ it "should link to the given <%= file_name %>" do
87
+ should_receive(:<%= model_controller_routing_name.singularize %>_path).at_least(:once).and_return('/<%= model_controller_file_path %>/1')
88
+ link_to_current_<%= file_name %>().should have_tag("a[href='/<%= model_controller_file_path %>/1']")
89
+ end
90
+ it "should use given link text if :content_text is specified" do
91
+ link_to_current_<%= file_name %>(:content_text => 'Hello there!').should have_tag("a", 'Hello there!')
92
+ end
93
+ it "should use the login as link text with no :content_method specified" do
94
+ link_to_current_<%= file_name %>().should have_tag("a", @<%= file_name %>.login)
95
+ end
96
+ it "should use the name as link text with :content_method => :name" do
97
+ link_to_current_<%= file_name %>(:content_method => :name).should have_tag("a", @<%= file_name %>.name)
98
+ end
99
+ it "should use the login as title with no :title_method specified" do
100
+ link_to_current_<%= file_name %>().should have_tag("a[title='#{@<%= file_name %>.login}']")
101
+ end
102
+ it "should use the name as link title with :content_method => :name" do
103
+ link_to_current_<%= file_name %>(:title_method => :name).should have_tag("a[title='#{@<%= file_name %>.name}']")
104
+ end
105
+ it "should have nickname as a class" do
106
+ link_to_current_<%= file_name %>().should have_tag("a.nickname")
107
+ end
108
+ it "should take other classes and no longer have the nickname class" do
109
+ result = link_to_current_<%= file_name %>(:class => 'foo bar')
110
+ result.should have_tag("a.foo")
111
+ result.should have_tag("a.bar")
112
+ end
113
+ end
114
+
115
+ describe "link_to_current_<%= file_name %>, When logged out" do
116
+ before do
117
+ stub!(:current_<%= file_name %>).and_return(nil)
118
+ end
119
+ it "should link to the login_path" do
120
+ link_to_current_<%= file_name %>().should have_tag("a[href='/login']")
121
+ end
122
+ it "should use given link text if :content_text is specified" do
123
+ link_to_current_<%= file_name %>(:content_text => 'Hello there!').should have_tag("a", 'Hello there!')
124
+ end
125
+ it "should use 'not signed in' as link text with no :content_method specified" do
126
+ link_to_current_<%= file_name %>().should have_tag("a", 'not signed in')
127
+ end
128
+ it "should use the ip address as title" do
129
+ link_to_current_<%= file_name %>().should have_tag("a[title='0.0.0.0']")
130
+ end
131
+ it "should by default be like school in summer and have no class" do
132
+ link_to_current_<%= file_name %>().should_not have_tag("a.nickname")
133
+ end
134
+ it "should have some class if you tell it to" do
135
+ result = link_to_current_<%= file_name %>(:class => 'foo bar')
136
+ result.should have_tag("a.foo")
137
+ result.should have_tag("a.bar")
138
+ end
139
+ end
140
+
141
+ end
@@ -0,0 +1,298 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.dirname(__FILE__) + '<%= ('/..'*model_controller_class_nesting_depth) + '/../spec_helper' %>'
3
+
4
+ # Be sure to include AuthenticatedTestHelper in spec/spec_helper.rb instead.
5
+ # Then, you can remove it from this and the functional test.
6
+ include AuthenticatedTestHelper
7
+
8
+ describe <%= class_name %> do
9
+ describe 'being created' do
10
+ before do
11
+ @<%= file_name %> = nil
12
+ @creating_<%= file_name %> = lambda do
13
+ @<%= file_name %> = create_<%= file_name %>
14
+ violated "#{@<%= file_name %>.errors.full_messages.to_sentence}" if @<%= file_name %>.new_record?
15
+ end
16
+ end
17
+
18
+ it 'increments <%= class_name %>#count' do
19
+ @creating_<%= file_name %>.should change(<%= class_name %>, :count).by(1)
20
+ end
21
+ <% if options[:include_activation] %>
22
+ it 'initializes #activation_code' do
23
+ @creating_<%= file_name %>.call
24
+ @<%= file_name %>.reload
25
+ @<%= file_name %>.activation_code.should_not be_nil
26
+ end
27
+ <% end %><% if options[:stateful] %>
28
+ it 'starts in pending state' do
29
+ @creating_<%= file_name %>.call
30
+ @<%= file_name %>.reload
31
+ @<%= file_name %>.should be_pending
32
+ end
33
+ <% end %> end
34
+
35
+ #
36
+ # Validations
37
+ #
38
+
39
+ it 'requires login' do
40
+ lambda do
41
+ u = create_<%= file_name %>(:login => nil)
42
+ u.errors.on(:login).should_not be_nil
43
+ end.should_not change(<%= class_name %>, :count)
44
+ end
45
+
46
+ describe 'allows legitimate logins:' do
47
+ ['123', '1234567890_234567890_234567890_234567890',
48
+ 'hello.-_there@funnychar.com'].each do |login_str|
49
+ it "'#{login_str}'" do
50
+ lambda do
51
+ u = create_<%= file_name %>(:login => login_str)
52
+ u.errors.on(:login).should be_nil
53
+ end.should change(<%= class_name %>, :count).by(1)
54
+ end
55
+ end
56
+ end
57
+ describe 'disallows illegitimate logins:' do
58
+ ['12', '1234567890_234567890_234567890_234567890_', "tab\t", "newline\n",
59
+ "Iñtërnâtiônàlizætiøn hasn't happened to ruby 1.8 yet",
60
+ 'semicolon;', 'quote"', 'tick\'', 'backtick`', 'percent%', 'plus+', 'space '].each do |login_str|
61
+ it "'#{login_str}'" do
62
+ lambda do
63
+ u = create_<%= file_name %>(:login => login_str)
64
+ u.errors.on(:login).should_not be_nil
65
+ end.should_not change(<%= class_name %>, :count)
66
+ end
67
+ end
68
+ end
69
+
70
+ it 'requires password' do
71
+ lambda do
72
+ u = create_<%= file_name %>(:password => nil)
73
+ u.errors.on(:password).should_not be_nil
74
+ end.should_not change(<%= class_name %>, :count)
75
+ end
76
+
77
+ it 'requires password confirmation' do
78
+ lambda do
79
+ u = create_<%= file_name %>(:password_confirmation => nil)
80
+ u.errors.on(:password_confirmation).should_not be_nil
81
+ end.should_not change(<%= class_name %>, :count)
82
+ end
83
+
84
+ it 'requires email' do
85
+ lambda do
86
+ u = create_<%= file_name %>(:email => nil)
87
+ u.errors.on(:email).should_not be_nil
88
+ end.should_not change(<%= class_name %>, :count)
89
+ end
90
+
91
+ describe 'allows legitimate emails:' do
92
+ ['foo@bar.com', 'foo@newskool-tld.museum', 'foo@twoletter-tld.de', 'foo@nonexistant-tld.qq',
93
+ 'r@a.wk', '1234567890-234567890-234567890-234567890-234567890-234567890-234567890-234567890-234567890@gmail.com',
94
+ 'hello.-_there@funnychar.com', 'uucp%addr@gmail.com', 'hello+routing-str@gmail.com',
95
+ 'domain@can.haz.many.sub.doma.in', 'student.name@university.edu'
96
+ ].each do |email_str|
97
+ it "'#{email_str}'" do
98
+ lambda do
99
+ u = create_<%= file_name %>(:email => email_str)
100
+ u.errors.on(:email).should be_nil
101
+ end.should change(<%= class_name %>, :count).by(1)
102
+ end
103
+ end
104
+ end
105
+ describe 'disallows illegitimate emails' do
106
+ ['!!@nobadchars.com', 'foo@no-rep-dots..com', 'foo@badtld.xxx', 'foo@toolongtld.abcdefg',
107
+ 'Iñtërnâtiônàlizætiøn@hasnt.happened.to.email', 'need.domain.and.tld@de', "tab\t", "newline\n",
108
+ 'r@.wk', '1234567890-234567890-234567890-234567890-234567890-234567890-234567890-234567890-234567890@gmail2.com',
109
+ # these are technically allowed but not seen in practice:
110
+ 'uucp!addr@gmail.com', 'semicolon;@gmail.com', 'quote"@gmail.com', 'tick\'@gmail.com', 'backtick`@gmail.com', 'space @gmail.com', 'bracket<@gmail.com', 'bracket>@gmail.com'
111
+ ].each do |email_str|
112
+ it "'#{email_str}'" do
113
+ lambda do
114
+ u = create_<%= file_name %>(:email => email_str)
115
+ u.errors.on(:email).should_not be_nil
116
+ end.should_not change(<%= class_name %>, :count)
117
+ end
118
+ end
119
+ end
120
+
121
+ describe 'allows legitimate names:' do
122
+ ['Andre The Giant (7\'4", 520 lb.) -- has a posse',
123
+ '', '1234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890',
124
+ ].each do |name_str|
125
+ it "'#{name_str}'" do
126
+ lambda do
127
+ u = create_<%= file_name %>(:name => name_str)
128
+ u.errors.on(:name).should be_nil
129
+ end.should change(<%= class_name %>, :count).by(1)
130
+ end
131
+ end
132
+ end
133
+ describe "disallows illegitimate names" do
134
+ ["tab\t", "newline\n",
135
+ '1234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890_234567890_',
136
+ ].each do |name_str|
137
+ it "'#{name_str}'" do
138
+ lambda do
139
+ u = create_<%= file_name %>(:name => name_str)
140
+ u.errors.on(:name).should_not be_nil
141
+ end.should_not change(<%= class_name %>, :count)
142
+ end
143
+ end
144
+ end
145
+
146
+ it 'resets password' do
147
+ <%= file_name %> = <%= class_name %>.make
148
+ <%= file_name %>.update_attributes(:password => 'new password', :password_confirmation => 'new password')
149
+ <%= class_name %>.authenticate(<%= file_name %>.login, 'new password').should == <%= file_name %>
150
+ end
151
+
152
+ it 'does not rehash password' do
153
+ <%= file_name %> = <%= class_name %>.make
154
+ <%= file_name %>.update_attributes(:login => 'quentin2')
155
+ <%= class_name %>.authenticate('quentin2', <%= file_name %>.password).should == <%= file_name %>
156
+ end
157
+
158
+ #
159
+ # Authentication
160
+ #
161
+
162
+ it 'authenticates <%= file_name %>' do
163
+ <%= file_name %> = <%= class_name %>.make
164
+ <%= class_name %>.authenticate(<%= file_name %>.login, <%= file_name %>.password).should == <%= file_name %>
165
+ end
166
+
167
+ it "doesn't authenticate <%= file_name %> with bad password" do
168
+ <%= class_name %>.authenticate('quentin', 'invalid_password').should be_nil
169
+ end
170
+
171
+ if REST_AUTH_SITE_KEY.blank?
172
+ # old-school passwords
173
+ it "authenticates a <%= file_name %> against a hard-coded old-style password" do
174
+ <%= class_name %>.authenticate('old_password_holder', 'test').should == <%= table_name %>(:old_password_holder)
175
+ end
176
+ else
177
+ it "doesn't authenticate a <%= file_name %> against a hard-coded old-style password" do
178
+ <%= class_name %>.authenticate('old_password_holder', 'test').should be_nil
179
+ end
180
+
181
+ # New installs should bump this up and set REST_AUTH_DIGEST_STRETCHES to give a 10ms encrypt time or so
182
+ desired_encryption_expensiveness_ms = 0.1
183
+ it "takes longer than #{desired_encryption_expensiveness_ms}ms to encrypt a password" do
184
+ test_reps = 100
185
+ start_time = Time.now; test_reps.times{ <%= class_name %>.authenticate('quentin', 'monkey'+rand.to_s) }; end_time = Time.now
186
+ auth_time_ms = 1000 * (end_time - start_time)/test_reps
187
+ auth_time_ms.should > desired_encryption_expensiveness_ms
188
+ end
189
+ end
190
+
191
+ #
192
+ # Authentication
193
+ #
194
+
195
+ it 'sets remember token' do
196
+ <%= file_name %> = <%= class_name %>.make
197
+ <%= file_name %>.remember_me
198
+ <%= file_name %>.remember_token.should_not be_nil
199
+ <%= file_name %>.remember_token_expires_at.should_not be_nil
200
+ end
201
+
202
+ it 'unsets remember token' do
203
+ <%= file_name %> = <%= class_name %>.make
204
+ <%= file_name %>.remember_me
205
+ <%= file_name %>.remember_token.should_not be_nil
206
+ <%= file_name %>.forget_me
207
+ <%= file_name %>.remember_token.should be_nil
208
+ end
209
+
210
+ it 'remembers me for one week' do
211
+ <%= file_name %> = <%= class_name %>.make
212
+ before = 1.week.from_now.utc
213
+ <%= file_name %>.remember_me_for 1.week
214
+ after = 1.week.from_now.utc
215
+ <%= file_name %>.remember_token.should_not be_nil
216
+ <%= file_name %>.remember_token_expires_at.should_not be_nil
217
+ <%= file_name %>.remember_token_expires_at.between?(before, after).should be_true
218
+ end
219
+
220
+ it 'remembers me until one week' do
221
+ <%= file_name %> = <%= class_name %>.make
222
+ time = 1.week.from_now.utc
223
+ <%= file_name %>.remember_me_until time
224
+ <%= file_name %>.remember_token.should_not be_nil
225
+ <%= file_name %>.remember_token_expires_at.should_not be_nil
226
+ <%= file_name %>.remember_token_expires_at.should == time
227
+ end
228
+
229
+ it 'remembers me default two weeks' do
230
+ <%= file_name %> = <%= class_name %>.make
231
+ before = 2.weeks.from_now.utc
232
+ <%= file_name %>.remember_me
233
+ after = 2.weeks.from_now.utc
234
+ <%= file_name %>.remember_token.should_not be_nil
235
+ <%= file_name %>.remember_token_expires_at.should_not be_nil
236
+ <%= file_name %>.remember_token_expires_at.between?(before, after).should be_true
237
+ end
238
+ <% if options[:stateful] %>
239
+ it 'registers passive <%= file_name %>' do
240
+ <%= file_name %> = create_<%= file_name %>(:password => nil, :password_confirmation => nil)
241
+ <%= file_name %>.should be_passive
242
+ <%= file_name %>.update_attributes(:password => 'new password', :password_confirmation => 'new password')
243
+ <%= file_name %>.register!
244
+ <%= file_name %>.should be_pending
245
+ end
246
+
247
+ it 'suspends <%= file_name %>' do
248
+ <%= file_name %> = <%= class_name %>.make
249
+ <%= file_name %>.suspend!
250
+ <%= file_name %>.should be_suspended
251
+ end
252
+
253
+ it 'does not authenticate suspended <%= file_name %>' do
254
+ <%= file_name %> = <%= class_name %>.make
255
+ <%= file_name %>.suspend!
256
+ <%= class_name %>.authenticate('quentin', 'monkey').should_not == <%= file_name %>
257
+ end
258
+
259
+ it 'deletes <%= file_name %>' do
260
+ <%= file_name %> = <%= class_name %>.make
261
+ <%= file_name %>.deleted_at.should be_nil
262
+ <%= file_name %>.delete!
263
+ <%= file_name %>.deleted_at.should_not be_nil
264
+ <%= file_name %>.should be_deleted
265
+ end
266
+
267
+ describe "being unsuspended" do
268
+
269
+ before do
270
+ @<%= file_name %> = <%= class_name %>.make
271
+ @<%= file_name %>.suspend!
272
+ end
273
+
274
+ it 'reverts to active state' do
275
+ @<%= file_name %>.unsuspend!
276
+ @<%= file_name %>.should be_active
277
+ end
278
+
279
+ it 'reverts to passive state if activation_code and activated_at are nil' do
280
+ <%= class_name %>.update_all :activation_code => nil, :activated_at => nil
281
+ @<%= file_name %>.reload.unsuspend!
282
+ @<%= file_name %>.should be_passive
283
+ end
284
+
285
+ it 'reverts to pending state if activation_code is set and activated_at is nil' do
286
+ <%= class_name %>.update_all :activation_code => 'foo-bar', :activated_at => nil
287
+ @<%= file_name %>.reload.unsuspend!
288
+ @<%= file_name %>.should be_pending
289
+ end
290
+ end
291
+ <% end %>
292
+ protected
293
+ def create_<%= file_name %>(options = {})
294
+ record = <%= class_name %>.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire69', :password_confirmation => 'quire69' }.merge(options))
295
+ record.<% if options[:stateful] %>register! if record.valid?<% else %>save<% end %>
296
+ record
297
+ end
298
+ end