door_mat 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (176) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +3 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +88 -0
  6. data/Rakefile +32 -0
  7. data/app/assets/javascripts/door_mat/application.js +13 -0
  8. data/app/assets/stylesheets/door_mat/application.css +15 -0
  9. data/app/assets/stylesheets/scaffold.css +56 -0
  10. data/app/controllers/door_mat/activities_controller.rb +106 -0
  11. data/app/controllers/door_mat/application_controller.rb +14 -0
  12. data/app/controllers/door_mat/change_password_controller.rb +32 -0
  13. data/app/controllers/door_mat/forgot_passwords_controller.rb +57 -0
  14. data/app/controllers/door_mat/manage_email_controller.rb +61 -0
  15. data/app/controllers/door_mat/password_less_session_controller.rb +121 -0
  16. data/app/controllers/door_mat/reconfirm_password_controller.rb +27 -0
  17. data/app/controllers/door_mat/sessions_controller.rb +17 -0
  18. data/app/controllers/door_mat/sign_in_controller.rb +60 -0
  19. data/app/controllers/door_mat/sign_up_controller.rb +59 -0
  20. data/app/controllers/door_mat/static_controller.rb +5 -0
  21. data/app/mailers/door_mat/activity_mailer.rb +18 -0
  22. data/app/mailers/door_mat/password_less_session_mailer.rb +12 -0
  23. data/app/models/door_mat/access_token.rb +315 -0
  24. data/app/models/door_mat/activity.rb +14 -0
  25. data/app/models/door_mat/activity_confirm_email.rb +45 -0
  26. data/app/models/door_mat/activity_download_recovery_key.rb +30 -0
  27. data/app/models/door_mat/activity_reset_password.rb +47 -0
  28. data/app/models/door_mat/actor.rb +149 -0
  29. data/app/models/door_mat/change_password.rb +12 -0
  30. data/app/models/door_mat/email.rb +58 -0
  31. data/app/models/door_mat/forgot_password.rb +12 -0
  32. data/app/models/door_mat/membership.rb +42 -0
  33. data/app/models/door_mat/session.rb +315 -0
  34. data/app/models/door_mat/sign_in.rb +31 -0
  35. data/app/models/door_mat/sign_up.rb +17 -0
  36. data/app/views/door_mat/activity_mailer/confirm_email.html.erb +11 -0
  37. data/app/views/door_mat/activity_mailer/confirm_email.text.erb +7 -0
  38. data/app/views/door_mat/activity_mailer/reset_password.html.erb +11 -0
  39. data/app/views/door_mat/activity_mailer/reset_password.text.erb +7 -0
  40. data/app/views/door_mat/change_password/new.html.erb +22 -0
  41. data/app/views/door_mat/forgot_passwords/choose_new_password.html.erb +34 -0
  42. data/app/views/door_mat/forgot_passwords/new.html.erb +14 -0
  43. data/app/views/door_mat/helpers/_errors_if_any.html.erb +10 -0
  44. data/app/views/door_mat/manage_email/new.html.erb +14 -0
  45. data/app/views/door_mat/password_less_session/access_token.html.erb +16 -0
  46. data/app/views/door_mat/password_less_session/new.html.erb +34 -0
  47. data/app/views/door_mat/password_less_session_mailer/send_token.html.erb +11 -0
  48. data/app/views/door_mat/password_less_session_mailer/send_token.text.erb +7 -0
  49. data/app/views/door_mat/reconfirm_password/new.html.erb +12 -0
  50. data/app/views/door_mat/sign_in/new.html.erb +30 -0
  51. data/app/views/door_mat/sign_up/new.html.erb +24 -0
  52. data/app/views/door_mat/static/add_email_success.html.erb +5 -0
  53. data/app/views/door_mat/static/change_password_success.html.erb +2 -0
  54. data/app/views/door_mat/static/confirm_email_success.html.erb +2 -0
  55. data/app/views/door_mat/static/email_confirmation_required.html.erb +17 -0
  56. data/app/views/door_mat/static/forgot_password_verification_mail_sent.html.erb +2 -0
  57. data/app/views/door_mat/static/reconfirm_password_success.html.erb +4 -0
  58. data/app/views/door_mat/static/sign_in_success.html.erb +5 -0
  59. data/app/views/door_mat/static/sign_out_success.html.erb +5 -0
  60. data/app/views/door_mat/static/sign_up_success.html.erb +4 -0
  61. data/bin/rails +12 -0
  62. data/config/locales/en.yml +73 -0
  63. data/config/routes.rb +48 -0
  64. data/db/migrate/20140616234935_create_door_mat_actors.rb +23 -0
  65. data/db/migrate/20140617233357_create_door_mat_sessions.rb +17 -0
  66. data/db/migrate/20140630043202_create_door_mat_emails.rb +12 -0
  67. data/db/migrate/20140702045729_create_door_mat_activities.rb +14 -0
  68. data/db/migrate/20141115183045_create_door_mat_access_tokens.rb +17 -0
  69. data/db/migrate/20141121191824_create_door_mat_memberships.rb +14 -0
  70. data/db/migrate/20150910182126_rename_session_guid_column.rb +5 -0
  71. data/db/migrate/20150918210831_add_access_token_rating_column.rb +5 -0
  72. data/door_mat.gemspec +37 -0
  73. data/lib/door_mat.rb +20 -0
  74. data/lib/door_mat/attr_asymmetric_store.rb +82 -0
  75. data/lib/door_mat/attr_symmetric_store.rb +82 -0
  76. data/lib/door_mat/configuration.rb +193 -0
  77. data/lib/door_mat/controller.rb +117 -0
  78. data/lib/door_mat/crypto.rb +49 -0
  79. data/lib/door_mat/crypto/asymmetric_store.rb +77 -0
  80. data/lib/door_mat/crypto/fast_hash.rb +17 -0
  81. data/lib/door_mat/crypto/password_hash.rb +39 -0
  82. data/lib/door_mat/crypto/secure_compare.rb +23 -0
  83. data/lib/door_mat/crypto/symmetric_store.rb +68 -0
  84. data/lib/door_mat/engine.rb +23 -0
  85. data/lib/door_mat/process/actor_password_change.rb +65 -0
  86. data/lib/door_mat/process/actor_sign_in.rb +38 -0
  87. data/lib/door_mat/process/actor_sign_up.rb +39 -0
  88. data/lib/door_mat/process/create_new_anonymous_actor.rb +36 -0
  89. data/lib/door_mat/process/manage_email.rb +42 -0
  90. data/lib/door_mat/process/reset_password.rb +50 -0
  91. data/lib/door_mat/regex.rb +17 -0
  92. data/lib/door_mat/test_helper.rb +58 -0
  93. data/lib/door_mat/url_protocol.rb +9 -0
  94. data/lib/door_mat/version.rb +3 -0
  95. data/lib/tasks/door_mat_tasks.rake +31 -0
  96. data/spec/controllers/door_mat/activities_controller_spec.rb +70 -0
  97. data/spec/controllers/door_mat/forgot_passwords_controller_spec.rb +57 -0
  98. data/spec/controllers/door_mat/manage_email_spec.rb +181 -0
  99. data/spec/controllers/door_mat/password_less_session_controller_spec.rb +344 -0
  100. data/spec/controllers/door_mat/sign_in_controller_spec.rb +211 -0
  101. data/spec/controllers/door_mat/sign_up_controller_spec.rb +90 -0
  102. data/spec/factories/door_mat_access_tokens.rb +6 -0
  103. data/spec/factories/door_mat_activitiess.rb +6 -0
  104. data/spec/factories/door_mat_actors.rb +23 -0
  105. data/spec/factories/door_mat_emails.rb +14 -0
  106. data/spec/factories/door_mat_memberships.rb +6 -0
  107. data/spec/factories/door_mat_sessions.rb +24 -0
  108. data/spec/features/password_less_session_spec.rb +165 -0
  109. data/spec/features/remember_me_spec.rb +672 -0
  110. data/spec/features/session_spec.rb +336 -0
  111. data/spec/lib/attr_store_spec.rb +237 -0
  112. data/spec/lib/crypto_spec.rb +130 -0
  113. data/spec/lib/process_spec.rb +159 -0
  114. data/spec/models/door_mat/access_token_spec.rb +134 -0
  115. data/spec/models/door_mat/activity_spec.rb +38 -0
  116. data/spec/models/door_mat/actor_spec.rb +56 -0
  117. data/spec/models/door_mat/email_spec.rb +25 -0
  118. data/spec/models/door_mat/session_spec.rb +69 -0
  119. data/spec/spec_helper.rb +223 -0
  120. data/spec/support/timecop/timecop_helper.rb +52 -0
  121. data/spec/test_app/README.rdoc +28 -0
  122. data/spec/test_app/Rakefile +6 -0
  123. data/spec/test_app/app/assets/javascripts/application.js +13 -0
  124. data/spec/test_app/app/assets/stylesheets/application.css +15 -0
  125. data/spec/test_app/app/controllers/account_controller.rb +28 -0
  126. data/spec/test_app/app/controllers/application_controller.rb +10 -0
  127. data/spec/test_app/app/controllers/password_less_sample_controller.rb +56 -0
  128. data/spec/test_app/app/controllers/static_controller.rb +7 -0
  129. data/spec/test_app/app/helpers/account_helper.rb +2 -0
  130. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  131. data/spec/test_app/app/models/game.rb +62 -0
  132. data/spec/test_app/app/models/shared_data.rb +4 -0
  133. data/spec/test_app/app/models/shared_key.rb +8 -0
  134. data/spec/test_app/app/models/user_detail.rb +7 -0
  135. data/spec/test_app/app/views/account/show.html.erb +133 -0
  136. data/spec/test_app/app/views/door_mat/static/sign_out_success.html.erb +7 -0
  137. data/spec/test_app/app/views/layouts/application.html.erb +20 -0
  138. data/spec/test_app/app/views/password_less_sample/draw_results.html.erb +6 -0
  139. data/spec/test_app/app/views/password_less_sample/final_result.html.erb +7 -0
  140. data/spec/test_app/app/views/password_less_sample/play_game.html.erb +5 -0
  141. data/spec/test_app/app/views/password_less_sample/show_loosing_door.html.erb +10 -0
  142. data/spec/test_app/app/views/static/index.html.erb +12 -0
  143. data/spec/test_app/app/views/static/only_confirmed_email_allowed.html.erb +10 -0
  144. data/spec/test_app/app/views/static/page_that_require_password_reconfirmation.html.erb +16 -0
  145. data/spec/test_app/app/views/static/session_protected_page.html.erb +32 -0
  146. data/spec/test_app/bin/bundle +3 -0
  147. data/spec/test_app/bin/rails +4 -0
  148. data/spec/test_app/bin/rake +4 -0
  149. data/spec/test_app/config.ru +4 -0
  150. data/spec/test_app/config/application.rb +29 -0
  151. data/spec/test_app/config/boot.rb +5 -0
  152. data/spec/test_app/config/database.yml +25 -0
  153. data/spec/test_app/config/environment.rb +19 -0
  154. data/spec/test_app/config/environments/development.rb +50 -0
  155. data/spec/test_app/config/environments/production.rb +83 -0
  156. data/spec/test_app/config/environments/test.rb +48 -0
  157. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  158. data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
  159. data/spec/test_app/config/initializers/door_mat.rb +72 -0
  160. data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
  161. data/spec/test_app/config/initializers/inflections.rb +16 -0
  162. data/spec/test_app/config/initializers/mime_types.rb +4 -0
  163. data/spec/test_app/config/initializers/session_store.rb +3 -0
  164. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  165. data/spec/test_app/config/locales/en.yml +23 -0
  166. data/spec/test_app/config/routes.rb +42 -0
  167. data/spec/test_app/config/secrets.yml +31 -0
  168. data/spec/test_app/db/migrate/20140717182813_create_user_details.rb +10 -0
  169. data/spec/test_app/db/migrate/20140908225256_create_shared_data.rb +10 -0
  170. data/spec/test_app/db/migrate/20140908225604_create_shared_keys.rb +11 -0
  171. data/spec/test_app/db/migrate/20141121190714_create_games.rb +10 -0
  172. data/spec/test_app/public/404.html +67 -0
  173. data/spec/test_app/public/422.html +67 -0
  174. data/spec/test_app/public/500.html +66 -0
  175. data/spec/test_app/public/favicon.ico +0 -0
  176. metadata +552 -0
@@ -0,0 +1,336 @@
1
+ require 'spec_helper'
2
+ require 'tempfile'
3
+
4
+ module DoorMat
5
+
6
+ RSpec.describe 'Actor lifecycle', :type => :feature do
7
+ include EmailSpec::Helpers
8
+ include EmailSpec::Matchers
9
+
10
+ let(:user) { {email: 'user@example.com', password: 'k#dkvKfdj38g!', new_password: 'new_k#dkvKfdj38g!'} }
11
+
12
+ it 'shows the signed in user email if leak email address is true' do
13
+ DoorMat.configuration.leak_email_address_at_reconfirm = true
14
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
15
+
16
+ visit '/sign_in'
17
+ expect(page.find('#sign_in_email').value).to eq('')
18
+ fill_sign_in_form(user[:email], user[:password])
19
+ expect(page.current_path).to match(/session_protected_page/)
20
+
21
+ wait_less_than_public_computer_session_timeout
22
+ visit '/account/show'
23
+ expect(page.body).to have_content('Reconfirm Password')
24
+ expect(page.body).to have_content(user[:email])
25
+
26
+ DoorMat.configuration.leak_email_address_at_reconfirm = false
27
+ end
28
+
29
+ it 'does not show the user email if leak email is false' do
30
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
31
+
32
+ visit '/sign_in'
33
+ expect(page.find('#sign_in_email').value).to eq('')
34
+ fill_sign_in_form(user[:email], user[:password])
35
+ expect(page.current_path).to match(/session_protected_page/)
36
+
37
+ wait_less_than_public_computer_session_timeout
38
+ click_link 'Show Account'
39
+ expect(page.body).to have_content('Reconfirm Password')
40
+ expect(page.body).not_to have_content(user[:email])
41
+ end
42
+
43
+ it 'allows the user to change the account password' do
44
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
45
+
46
+ visit '/sign_in'
47
+ fill_sign_in_form(user[:email], user[:password])
48
+ click_link 'Show Account'
49
+
50
+ click_link 'Change Password'
51
+
52
+ expect(page.body).to have_content('New password confirmation')
53
+ fill_in 'change_password_old_password', with: 'wrong_password'
54
+ fill_in 'change_password_new_password', with: user[:new_password]
55
+ fill_in 'change_password_new_password_confirmation', with: user[:new_password]
56
+ click_button 'Change Password'
57
+
58
+ expect(page.body).to have_content('New password confirmation')
59
+ fill_in 'change_password_old_password', with: user[:password]
60
+ fill_in 'change_password_new_password', with: user[:new_password]
61
+ fill_in 'change_password_new_password_confirmation', with: user[:new_password]
62
+ click_button 'Change Password'
63
+ expect(page.body).to have_content('You have successfully changed your password')
64
+
65
+ visit '/account/show'
66
+ click_link 'Sign Out'
67
+
68
+ visit '/sign_in'
69
+ fill_sign_in_form(user[:email], user[:new_password], false)
70
+ expect(page.body).to have_content('Static#session_protected_page')
71
+ end
72
+
73
+ it 'times out a session if the user is not active for too long' do
74
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
75
+
76
+ visit '/sign_in'
77
+ fill_sign_in_form(user[:email], user[:password])
78
+ expect(page.body).to have_content('Static#session_protected_page')
79
+
80
+ wait_longer_than_public_computer_session_timeout
81
+ click_link 'Show Account'
82
+ expect(page.body).not_to have_content('Account#show')
83
+ expect(page.body).to have_content('Sign In')
84
+ end
85
+
86
+ it 'does not time out a session if the user if actively used but still require password reconfirm' do
87
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
88
+
89
+ visit '/sign_in'
90
+ fill_sign_in_form(user[:email], user[:password])
91
+ expect(page.body).to have_content('Static#session_protected_page')
92
+
93
+ wait_less_than_public_computer_session_timeout
94
+ click_link 'Show Account'
95
+ expect(page.body).to have_content('Reconfirm Password')
96
+
97
+ fill_in 'password', with: 'wrong_password'
98
+ click_button 'Reconfirm'
99
+ expect(page.body).not_to have_content('Account#show')
100
+ fill_in 'password', with: user[:password]
101
+ click_button 'Reconfirm'
102
+ expect(page.body).to have_content('Account#show')
103
+
104
+ wait_two_minutes
105
+ visit '/account/show'
106
+ expect(page.body).to have_content('Account#show')
107
+
108
+ wait_longer_than_public_computer_session_timeout
109
+ visit '/account/show'
110
+ expect(page.body).not_to have_content('Account#show')
111
+ expect(page.body).to have_content('Sign In')
112
+ end
113
+
114
+ it 'can terminate other session if logged in from many sessions at the same time' do
115
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
116
+
117
+ Capybara.session_name = 'device_1'
118
+ visit '/sign_in'
119
+ fill_sign_in_form(user[:email], user[:password])
120
+ click_link 'Show Account'
121
+ expect(page.body).to have_content('Account#show')
122
+ expect(page).not_to have_button('Terminate')
123
+
124
+ Capybara.session_name = 'device_2'
125
+ visit '/sign_in'
126
+ fill_sign_in_form(user[:email], user[:password])
127
+ click_link 'Show Account'
128
+ expect(page.body).to have_content('Account#show')
129
+
130
+ Capybara.session_name = 'device_1'
131
+ visit '/account/show'
132
+ expect(page.body).to have_content('Account#show')
133
+
134
+ Capybara.session_name = 'device_2'
135
+ visit '/account/show'
136
+ expect(page.body).to have_content('Account#show')
137
+ expect(page).to have_button('Terminate')
138
+ click_button 'Terminate'
139
+ reload_page
140
+
141
+ Capybara.session_name = 'device_1'
142
+ visit '/account/show'
143
+ expect(page).to have_content('Sign In')
144
+ end
145
+
146
+ it 'terminates a session where the session key is wrong' do
147
+ DoorMat::TestHelper.create_signed_up_actor_with_confirmed_email_address(user[:email], user[:password])
148
+
149
+ Capybara.session_name = 'device_1'
150
+ visit '/sign_in'
151
+ fill_sign_in_form(user[:email], user[:password])
152
+ click_link 'Show Account'
153
+ expect(page.body).to have_content('Account#show')
154
+ cookie_session_key = get_me_the_cookie('session_key') # steal session key
155
+ expect(DoorMat::Session.count).to eq(1)
156
+
157
+ Capybara.session_name = 'device_2'
158
+ visit '/sign_in'
159
+ fill_sign_in_form(user[:email], user[:password])
160
+ click_link 'Show Account'
161
+ expect(page.body).to have_content('Account#show')
162
+
163
+ expect(DoorMat::Session.count).to eq(2)
164
+ create_cookie('session_key', cookie_session_key[:value]) # try to use it with a different session
165
+
166
+ reload_page
167
+ expect(page).to have_content('Sign In')
168
+ expect(DoorMat::Session.count).to eq(1)
169
+ end
170
+
171
+ it 'Sign up, confirm email, download recovery key file and do password recovery' do
172
+ visit '/sign_up'
173
+ fill_sign_up_form(user[:email], user[:password])
174
+
175
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
176
+ e = open_last_email_for(user[:email])
177
+ confirm_email_url = links_in_email(e).select {|url| /confirm_email/.match(url)}.first
178
+ visit confirm_email_url
179
+
180
+ visit '/account/show'
181
+ click_link 'Sign Out'
182
+
183
+ visit '/sign_in'
184
+
185
+ # Obtain the recovery key 'inline' instead of from 'attachment'
186
+ # http://stackoverflow.com/questions/15739423/downloading-file-to-specific-folder-using-capybara-and-poltergeist-driver
187
+ # https://github.com/ariya/phantomjs/issues/10052
188
+ fill_sign_in_form(user[:email], user[:password], false)
189
+ click_link 'Show Account'
190
+ set_hidden_input_value('#disposition', 'inline')
191
+ click_button 'Download'
192
+ recovery_key = page.find(:css, 'pre').text
193
+
194
+ visit '/account/show'
195
+ click_link 'Sign Out'
196
+ click_link 'Sign In'
197
+ click_link 'Forgot your password?'
198
+ fill_in 'forgot_password_email', with: user[:email]
199
+ click_button 'Reset'
200
+ expect(page.body).to have_content('forgot_password_verification_mail_sent')
201
+ wait_two_minutes
202
+
203
+ # This new request before forgot_password_link_request_delay does not send a new email
204
+ visit '/sign_in'
205
+ click_link 'Forgot your password?'
206
+ fill_in 'forgot_password_email', with: user[:email]
207
+ click_button 'Reset'
208
+ expect(page.body).to have_content('forgot_password_verification_mail_sent')
209
+
210
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
211
+
212
+ e = open_last_email_for(user[:email])
213
+ confirm_email_url = links_in_email(e).select {|url| /choose_new_password/.match(url)}.first
214
+ visit confirm_email_url
215
+
216
+ fill_in 'forgot_password_email', with: user[:email]
217
+ fill_in 'forgot_password_password', with: user[:new_password]
218
+ fill_in 'forgot_password_password_confirmation', with: user[:new_password]
219
+
220
+ Tempfile.open('prefix', Rails.root.join('tmp') ) do |f|
221
+ f.print(recovery_key)
222
+ f.flush
223
+ f.close
224
+
225
+ attach_file('forgot_password_recovery_key', f.path)
226
+ click_button 'Reset'
227
+ end
228
+
229
+ fill_sign_in_form(user[:email], user[:new_password], false)
230
+ click_link 'Show Account'
231
+ expect(page.body).to have_content('Click below to download your recovery key')
232
+
233
+ end
234
+
235
+ it 'Sign up, confirm email, download recovery key file and do password recovery but wait too long before using the recovery link' do
236
+ visit '/sign_up'
237
+ fill_sign_up_form(user[:email], user[:password])
238
+
239
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
240
+ e = open_last_email_for(user[:email])
241
+ confirm_email_url = links_in_email(e).select {|url| /confirm_email/.match(url)}.first
242
+ visit confirm_email_url
243
+
244
+ visit '/account/show'
245
+ click_link 'Sign Out'
246
+
247
+ visit '/sign_in'
248
+
249
+ # Obtain the recovery key 'inline' instead of from 'attachment'
250
+ # http://stackoverflow.com/questions/15739423/downloading-file-to-specific-folder-using-capybara-and-poltergeist-driver
251
+ # https://github.com/ariya/phantomjs/issues/10052
252
+ fill_sign_in_form(user[:email], user[:password], false)
253
+ click_link 'Show Account'
254
+ set_hidden_input_value('#disposition', 'inline')
255
+ click_button 'Download'
256
+ recovery_key = page.find(:css, 'pre').text
257
+
258
+ visit '/account/show'
259
+ click_link 'Sign Out'
260
+ click_link 'Sign In'
261
+ click_link 'Forgot your password?'
262
+ fill_in 'forgot_password_email', with: user[:email]
263
+ click_button 'Reset'
264
+ expect(page.body).to have_content('forgot_password_verification_mail_sent')
265
+
266
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
267
+ e = open_last_email_for(user[:email])
268
+ confirm_email_url = links_in_email(e).select {|url| /choose_new_password/.match(url)}.first
269
+ wait_two_days
270
+ visit confirm_email_url
271
+
272
+ fill_in 'forgot_password_email', with: user[:email]
273
+ fill_in 'forgot_password_password', with: user[:new_password]
274
+ fill_in 'forgot_password_password_confirmation', with: user[:new_password]
275
+
276
+ Tempfile.open('prefix', Rails.root.join('tmp') ) do |f|
277
+ f.print(recovery_key)
278
+ f.flush
279
+ f.close
280
+
281
+ attach_file('forgot_password_recovery_key', f.path)
282
+ click_button 'Reset'
283
+ end
284
+
285
+ expect(page.body).to have_content('Please make a new request')
286
+
287
+ end
288
+
289
+ it 'Sign up and request a new email reconfirmation email, add a new email and attempt to use it to visit account/show before it is confirmed' do
290
+ visit '/sign_up'
291
+ fill_sign_up_form(user[:email], user[:password])
292
+
293
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
294
+ _ = open_last_email_for(user[:email])
295
+
296
+ click_link 'Show Account'
297
+ wait_longer_than_public_computer_session_timeout
298
+ click_button 'Resend confirmation email'
299
+ expect(page.current_path).to match(/sign_in/)
300
+
301
+ fill_sign_in_form(user[:email], user[:password])
302
+ click_link 'Show Account'
303
+ click_button 'Resend confirmation email'
304
+
305
+ expect(unread_emails_for(user[:email]).size).to eq(parse_email_count(1))
306
+ e = open_last_email_for(user[:email])
307
+ confirm_email_url = links_in_email(e).select {|url| /confirm_email/.match(url)}.first
308
+ visit confirm_email_url
309
+
310
+ visit '/account/show'
311
+ expect(DoorMat::Email.all.first.primary?).to be_truthy
312
+ expect(page.body).to match(/You are currently logged in as:.*user@example.com.*User Name/m)
313
+
314
+ click_link 'Add new email'
315
+ fill_in 'email_address', with: 'new_user@example.com'
316
+ click_button 'Add email'
317
+ click_link 'Sign Out'
318
+
319
+ visit '/sign_in'
320
+
321
+ fill_sign_in_form('new_user@example.com', user[:password])
322
+ click_link 'Show Account'
323
+ expect(page.body).to have_content('Please confirm your email address')
324
+
325
+ expect(unread_emails_for('new_user@example.com').size).to eq(parse_email_count(1))
326
+ e = open_last_email_for('new_user@example.com')
327
+ confirm_email_url = links_in_email(e).select {|url| /confirm_email/.match(url)}.first
328
+ visit confirm_email_url
329
+
330
+ visit '/account/show'
331
+ expect(DoorMat::Email.all.last.confirmed?).to be_truthy
332
+ expect(page.body).to match(/You are currently logged in as:.*new_user@example.com.*User Name/m)
333
+ end
334
+
335
+ end
336
+ end
@@ -0,0 +1,237 @@
1
+ require 'spec_helper'
2
+
3
+ module DoorMat
4
+ describe "DoorMat attr_store" do
5
+ it 'must belong to an Actor' do
6
+ allow(ActiveRecord::Base).to receive(:table_exists?).and_return(true)
7
+
8
+ expect {
9
+ class SomeRandomClassA < ActiveRecord::Base
10
+ include DoorMat::AttrSymmetricStore
11
+
12
+ def self.columns()
13
+ @columns ||= [];
14
+ end
15
+
16
+ def self.column(name, sql_type = nil, default = nil, null = true)
17
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
18
+ end
19
+
20
+ column :name, :string
21
+
22
+ attr_symmetric_store :name
23
+
24
+ end
25
+ }.to raise_error(ActiveRecord::ActiveRecordError, /attr_symmetric_store records must belong to a DoorMat::Actor/)
26
+ end
27
+ it 'must be a string or text field' do
28
+ allow(ActiveRecord::Base).to receive(:table_exists?).and_return(true)
29
+
30
+ expect {
31
+ class SomeRandomClassB < ActiveRecord::Base
32
+ include DoorMat::AttrSymmetricStore
33
+
34
+ def self.columns()
35
+ @columns ||= [];
36
+ end
37
+
38
+ def self.column(name, sql_type = nil, default = nil, null = true)
39
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
40
+ end
41
+
42
+ column :actor_id, :int
43
+ column :counter, :int
44
+
45
+ belongs_to :actor, class_name: 'DoorMat::Actor'
46
+ attr_symmetric_store :counter
47
+
48
+ end
49
+ }.to raise_error(ActiveRecord::ActiveRecordError, /attr_symmetric_store only support text and string column types/)
50
+ end
51
+ it "Encrypt and decrypt data when current session is valid" do
52
+ actor, session = TestHelper::create_signed_in_actor_with_confirmed_email_address
53
+ details = UserDetail.new
54
+ details.name = "Secret User Name"
55
+ actor.user_detail = details
56
+ actor.save!
57
+
58
+ confirm_details = UserDetail.all.first
59
+ expect(details.name).to eq(confirm_details.name)
60
+ expect(details.__id__).not_to eq(confirm_details.__id__)
61
+
62
+ encrypted_details_name = ''
63
+ DoorMat::Crypto.skip_crypto_callback { encrypted_details_name = UserDetail.all.first.name }
64
+ expect(encrypted_details_name).not_to eq("Secret User Name")
65
+ expect(encrypted_details_name).not_to eq(UserDetail.all.first.name)
66
+ expect(encrypted_details_name).not_to eq('')
67
+ end
68
+ it "Respects string encoding" do
69
+ actor, _ = TestHelper::create_signed_in_actor_with_confirmed_email_address
70
+ details = UserDetail.new
71
+ details.name = "Secret User Name"
72
+ encoding_before = details.name.encoding.name
73
+ actor.user_detail = details
74
+ actor.save!
75
+
76
+ confirm_details = UserDetail.all.first
77
+ encoding_after = confirm_details.name.encoding.name
78
+ expect(details.name).to eq(confirm_details.name)
79
+ expect(details.__id__).not_to eq(confirm_details.__id__)
80
+ expect(encoding_before).to eq(encoding_after)
81
+ end
82
+ end
83
+ describe "DoorMat attr_asymmetric_store" do
84
+
85
+ it 'must belong to an Actor' do
86
+ allow(ActiveRecord::Base).to receive(:table_exists?).and_return(true)
87
+
88
+ expect {
89
+ class SomeSharedKeyA < ActiveRecord::Base
90
+ include DoorMat::AttrAsymmetricStore
91
+
92
+ def self.columns()
93
+ @columns ||= [];
94
+ end
95
+
96
+ def self.column(name, sql_type = nil, default = nil, null = true)
97
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
98
+ end
99
+
100
+ column :key, :string
101
+
102
+
103
+ attr_asymmetric_store :key
104
+ end
105
+
106
+ }.to raise_error(ActiveRecord::ActiveRecordError, /attr_asymmetric_store records must belong to a DoorMat::Actor/)
107
+ end
108
+ it 'must be a string or text field' do
109
+ allow(ActiveRecord::Base).to receive(:table_exists?).and_return(true)
110
+
111
+ expect {
112
+ class SomeSharedKeyB < ActiveRecord::Base
113
+ include DoorMat::AttrAsymmetricStore
114
+
115
+ def self.columns()
116
+ @columns ||= [];
117
+ end
118
+
119
+ def self.column(name, sql_type = nil, default = nil, null = true)
120
+ columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
121
+ end
122
+
123
+ column :actor_id, :int
124
+ column :key, :int
125
+
126
+ belongs_to :actor, class_name: 'DoorMat::Actor'
127
+ attr_asymmetric_store :key
128
+
129
+ end
130
+ }.to raise_error(ActiveRecord::ActiveRecordError, /attr_asymmetric_store only support text and string column types/)
131
+ end
132
+ it "Encrypt and decrypt data when current session is valid" do
133
+
134
+ alice = TestHelper::create_signed_up_actor_with_confirmed_email_address("alice@example.com", 'pwd_alice_1')
135
+ bob, _ = TestHelper::create_signed_in_actor_with_confirmed_email_address("bob@example.com", 'pwd_bob_1')
136
+
137
+ random_key = DoorMat::Crypto::SymmetricStore.random_key
138
+
139
+ original_document = "fyi document"
140
+ original_exp_date = 2.days.from_now.strftime("%Y%m%d")
141
+ document, exp_date = DoorMat::Crypto.encrypt_shared([original_document, original_exp_date], random_key)
142
+ shared_data = SharedData.new
143
+ shared_data.document = document
144
+ shared_data.expiration_date = exp_date
145
+
146
+ shared_key_alice = SharedKey.new
147
+ shared_key_alice.actor = alice
148
+ shared_key_alice.shared_data = shared_data
149
+ shared_key_alice.key = random_key
150
+
151
+ shared_key_bob = SharedKey.new
152
+ shared_key_bob.actor = bob
153
+ shared_key_bob.shared_data = shared_data
154
+ shared_key_bob.key = random_key
155
+
156
+ shared_key_alice.save!
157
+ shared_key_bob.save!
158
+
159
+ expect(SharedKey.all.first.key).not_to eq(SharedKey.all.last.key)
160
+
161
+ Session.clear_current_session
162
+ alice, _ = TestHelper::sign_in_existing_actor("alice@example.com", 'pwd_alice_1')
163
+
164
+ shared_key = alice.shared_keys.first
165
+ expect(shared_key.key).to eq(random_key)
166
+ document, exp_date = DoorMat::Crypto.decrypt_shared([shared_key.shared_data.document, shared_key.shared_data.expiration_date], shared_key.key)
167
+ expect(original_document).to eq(document)
168
+ expect(original_exp_date).to eq(exp_date)
169
+
170
+ Session.clear_current_session
171
+ bob, _ = TestHelper::sign_in_existing_actor("bob@example.com", 'pwd_bob_1')
172
+ new_password = "new_password!"
173
+ DoorMat::Process::ActorPasswordChange.with(bob, new_password, 'pwd_bob_1')
174
+
175
+ Session.clear_current_session
176
+
177
+ bob, _ = TestHelper::sign_in_existing_actor("bob@example.com", new_password)
178
+ shared_key = bob.shared_keys.first
179
+ expect(shared_key.key).to eq(random_key)
180
+ document, exp_date = DoorMat::Crypto.decrypt_shared([shared_key.shared_data.document, shared_key.shared_data.expiration_date], shared_key.key)
181
+ expect(original_document).to eq(document)
182
+ expect(original_exp_date).to eq(exp_date)
183
+
184
+ expect(bob.id).not_to eq(alice.id)
185
+
186
+ end
187
+
188
+
189
+ it "leaves the attribute as is if the actor is not set" do
190
+ alice = TestHelper::create_signed_up_actor_with_confirmed_email_address("alice@example.com")
191
+
192
+ plain_text_message = 'Anybody can read this'
193
+
194
+ first_shared_key = SharedKey.new
195
+ first_shared_key.key = plain_text_message
196
+ first_shared_key.save
197
+ last_shared_key = SharedKey.new
198
+ last_shared_key.actor = alice
199
+ last_shared_key.key = plain_text_message
200
+ last_shared_key.save
201
+
202
+ shared_key_w_no_actor = nil
203
+ shared_key_w_actor = nil
204
+ DoorMat::Crypto.skip_crypto_callback { shared_key_w_no_actor = SharedKey.first }
205
+ DoorMat::Crypto.skip_crypto_callback { shared_key_w_actor = SharedKey.last }
206
+
207
+ expect(shared_key_w_no_actor.key).to eq(plain_text_message)
208
+ expect(shared_key_w_no_actor.key).not_to eq(shared_key_w_actor.key)
209
+ expect(shared_key_w_no_actor.id).not_to eq(shared_key_w_actor.id)
210
+ end
211
+
212
+ it "leaves the attribute as is if blank" do
213
+ alice = TestHelper::create_signed_up_actor_with_confirmed_email_address("alice@example.com")
214
+
215
+ blank_text_message = ''
216
+
217
+ first_shared_key = SharedKey.new
218
+ first_shared_key.key = blank_text_message
219
+ first_shared_key.save
220
+ last_shared_key = SharedKey.new
221
+ last_shared_key.actor = alice
222
+ last_shared_key.key = blank_text_message
223
+ last_shared_key.save
224
+
225
+ shared_key_w_no_actor = nil
226
+ shared_key_w_actor = nil
227
+ DoorMat::Crypto.skip_crypto_callback { shared_key_w_no_actor = SharedKey.first }
228
+ DoorMat::Crypto.skip_crypto_callback { shared_key_w_actor = SharedKey.last }
229
+
230
+ expect(shared_key_w_no_actor.key.blank?).to be true
231
+ expect(shared_key_w_actor.key.blank?).to be true
232
+ expect(shared_key_w_no_actor.id).not_to eq(shared_key_w_actor.id)
233
+ end
234
+
235
+
236
+ end
237
+ end