door_mat 0.0.5

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 (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