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,48 @@
1
+ DoorMat::Engine.routes.draw do
2
+ if DoorMat.configuration.define_door_mat_routes
3
+
4
+ get '/sign_up' => 'sign_up#new', as: 'sign_up'
5
+ post '/sign_up' => 'sign_up#create'
6
+
7
+ get '/sign_in' => 'sign_in#new', as: 'sign_in'
8
+ post '/sign_in' => 'sign_in#create'
9
+ get '/sign_out' => 'sign_in#destroy', as: 'sign_out'
10
+
11
+ post '/terminate_session/:guid' => 'sessions#terminate', as: 'terminate_session'
12
+ get '/reconfirm_password' => 'reconfirm_password#new', as: 'reconfirm_password'
13
+ post '/reconfirm_password' => 'reconfirm_password#create'
14
+
15
+ get '/add_email' => 'manage_email#new', as: 'add_email'
16
+ post '/add_email' => 'manage_email#create'
17
+ post '/delete_email' => 'manage_email#destroy'
18
+ post '/set_primary_email' => 'manage_email#set_primary_email'
19
+
20
+ get '/email_confirmation_required' => 'static#email_confirmation_required', as: 'email_confirmation_required'
21
+ get '/confirm_email/:token/:email' => 'activities#confirm_email', as: 'confirm_email'
22
+ post '/resend_email_confirmation' => 'activities#resend_email_confirmation', as: 'resend_email_confirmation'
23
+ post '/download_recovery_key' => 'activities#download_recovery_key', as: 'download_recovery_key'
24
+
25
+ get '/change_password' => 'change_password#new', as: 'change_password'
26
+ post '/change_password' => 'change_password#create'
27
+
28
+ get '/sign_up_success' => 'static#sign_up_success', as: 'sign_up_success'
29
+ get '/sign_in_success' => 'static#sign_in_success', as: 'sign_in_success'
30
+ get '/add_email_success' => 'static#add_email_success', as: 'add_email_success'
31
+ get '/confirm_email_success' => 'static#confirm_email_success', as: 'confirm_email_success'
32
+ get '/change_password_success' => 'static#change_password_success', as: 'change_password_success'
33
+ get '/reconfirm_password_success' => 'static#reconfirm_password_success', as: 'reconfirm_password_success'
34
+
35
+
36
+ # skip_before_filter :require_valid_session for these routes
37
+ get '/access_token/:token_for(/:token)' => 'password_less_session#access_token', :as => 'access_token_token_for_token'
38
+ post '/access_token' => 'password_less_session#access_token_post'
39
+
40
+ get '/sign_out_success' => 'static#sign_out_success'
41
+ get '/forgot_password_verification_mail_sent' => 'static#forgot_password_verification_mail_sent'
42
+
43
+ get '/forgot_password' => 'forgot_passwords#new'
44
+ post '/forgot_password' => 'forgot_passwords#create'
45
+ get '/choose_new_password/:token/:email' => 'forgot_passwords#choose_new_password', as: 'choose_new_password'
46
+ post '/reset_password' => 'forgot_passwords#reset_password', as: 'reset_password'
47
+ end
48
+ end
@@ -0,0 +1,23 @@
1
+ class CreateDoorMatActors < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_actors do |t|
4
+ # The salt of the user's password derived key; used for symmetric encryption of data
5
+ t.text :key_salt, :default => '', :null => false
6
+ # The salt of the password
7
+ t.text :password_salt, :default => '', :null => false
8
+ # The resulting hash used for authentication
9
+ t.text :password_hash, :default => '', :null => false
10
+ # A symmetric encryption key used by the system to encrypt data before handing it to the user
11
+ t.text :system_key, :default => '', :null => false
12
+ # The encrypted user key, used to recover data in a password recovery scenario
13
+ t.text :recovery_key, :default => '', :null => false
14
+
15
+ # The key to decrypt the pem pbkey; it is encrypted using the user's password derived key
16
+ t.text :encrypted_pem_key, :default => '', :null => false
17
+ t.text :pem_encrypted_pkey, :default => '', :null => false
18
+ t.text :pem_public_key, :default => '', :null => false
19
+
20
+ t.timestamps :null => false
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ class CreateDoorMatSessions < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_sessions do |t|
4
+ t.belongs_to :actor, index: true
5
+ t.belongs_to :email
6
+ t.string :session_guid, :default => '', :null => false, index: true
7
+ t.text :type, :default => '', :null => false
8
+ t.text :agent, :default => '', :null => false
9
+ t.text :ip, :default => '', :null => false
10
+ t.text :encrypted_symmetric_actor_key, :default => '', :null => false
11
+ t.datetime :password_authenticated_at, :default => Date.new(2014,1,1), :null => false
12
+ t.integer :rating, :default => 0
13
+
14
+ t.timestamps :null => false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ class CreateDoorMatEmails < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_emails do |t|
4
+ t.belongs_to :actor, index: true
5
+ t.text :address_hash, :default => '', :null => false, index: true
6
+ t.text :address, :default => '', :null => false
7
+ t.integer :status, :default => 0
8
+
9
+ t.timestamps :null => false
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ class CreateDoorMatActivities < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_activities do |t|
4
+ t.belongs_to :actor, index: true
5
+ t.string :type, :default => '', :null => false
6
+ t.integer :notifier_id, :null => false
7
+ t.string :notifier_type, :default => '', :null => false
8
+ t.text :link_hash, :default => '', :null => false
9
+ t.integer :status, :default => 0
10
+
11
+ t.timestamps :null => false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ class CreateDoorMatAccessTokens < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_access_tokens do |t|
4
+
5
+ t.belongs_to :actor
6
+ t.text :hashed_token, :default => '', :null => false, index: true
7
+ t.text :name, :default => '', :null => false
8
+ t.integer :token_for, :default => 0
9
+ t.integer :status, :default => 0
10
+ t.text :identifier, :default => '', :null => false
11
+ t.text :data, :default => '', :null => false
12
+ t.integer :reference_id, :default => 0, :null => false
13
+
14
+ t.timestamps :null => false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ class CreateDoorMatMemberships < ActiveRecord::Migration
2
+ def change
3
+ create_table :door_mat_memberships do |t|
4
+ t.belongs_to :member, index: true, class_name: "DoorMat::Actor"
5
+ t.belongs_to :member_of, index: true, class_name: "DoorMat::Actor"
6
+ t.integer :sponsor, :default => 0
7
+ t.integer :owner, :default => 0
8
+ t.integer :permission, :default => 0
9
+ t.text :key, :default => '', :null => false
10
+
11
+ t.timestamps :null => false
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class RenameSessionGuidColumn < ActiveRecord::Migration
2
+ def change
3
+ rename_column :door_mat_sessions, :session_guid, :hashed_token
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddAccessTokenRatingColumn < ActiveRecord::Migration
2
+ def change
3
+ add_column :door_mat_access_tokens, :rating, :integer, :default => 0
4
+ end
5
+ end
@@ -0,0 +1,37 @@
1
+ $:.push File.expand_path('../lib', __FILE__)
2
+
3
+ require 'door_mat/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'door_mat'
7
+ s.version = DoorMat::VERSION
8
+ s.authors = ['Luc Lussier']
9
+ s.email = ['luc.lussier@gmail.com']
10
+ s.homepage = 'https://github.com/datamolecule/door_mat'
11
+ s.summary = 'User authentication and data encryption'
12
+ s.description = 'DoorMat is a Rails Engine that provides a solution for both user authentication and the encryption of user information. It aims to offer safe defaults so you can get going with what your website is really about.'
13
+ s.license = 'MIT'
14
+
15
+ s.files = Dir['{app,bin,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.md', '.rspec', 'door_mat.gemspec', 'Gemfile']
16
+ s.test_files = Dir['spec/**/*']
17
+
18
+ s.add_dependency 'rails', '~> 4.2'
19
+ s.add_dependency 'bcrypt', '~> 3.1' #https://github.com/codahale/bcrypt-ruby
20
+ s.add_dependency 'request_store', '~> 1.1'
21
+
22
+ s.add_development_dependency 'sprockets', '2.12.4' # to prevent https://github.com/rails/rails/issues/19853
23
+ s.add_development_dependency 'sqlite3'
24
+ s.add_development_dependency 'database_cleaner'
25
+ s.add_development_dependency 'rspec-rails', '~> 3.4.2'
26
+ s.add_development_dependency 'factory_girl_rails', '~> 4.4.1'
27
+ s.add_development_dependency 'email_spec', '~> 2.0'
28
+ s.add_development_dependency 'capybara'
29
+ s.add_development_dependency 'show_me_the_cookies'
30
+ s.add_development_dependency 'poltergeist'
31
+ s.add_development_dependency 'selenium-webdriver'
32
+ s.add_development_dependency 'byebug'
33
+ s.add_development_dependency 'timecop'
34
+ s.add_development_dependency 'better_errors'
35
+ s.add_development_dependency 'binding_of_caller'
36
+ s.add_development_dependency 'simplecov'
37
+ end
@@ -0,0 +1,20 @@
1
+ require 'request_store'
2
+
3
+ require 'door_mat/configuration'
4
+ require 'door_mat/engine'
5
+ require 'door_mat/crypto'
6
+ require 'door_mat/attr_symmetric_store'
7
+ require 'door_mat/attr_asymmetric_store'
8
+ require 'door_mat/controller'
9
+ require 'door_mat/url_protocol'
10
+ require 'door_mat/regex'
11
+
12
+ require 'door_mat/process/actor_password_change'
13
+ require 'door_mat/process/actor_sign_in'
14
+ require 'door_mat/process/actor_sign_up'
15
+ require 'door_mat/process/create_new_anonymous_actor'
16
+ require 'door_mat/process/manage_email'
17
+ require 'door_mat/process/reset_password'
18
+
19
+ module DoorMat
20
+ end
@@ -0,0 +1,82 @@
1
+ module DoorMat
2
+ module AttrAsymmetricStore
3
+ class AttrAsymmetricStoreWrapper
4
+
5
+ def initialize(attribute, actor_column)
6
+ @attribute = attribute
7
+ @actor_column = actor_column
8
+ end
9
+
10
+ def after_find(record)
11
+ return if DoorMat::Crypto.current_skip_crypto_callback.skip?
12
+
13
+ # leave attribute as-is if blank or the actor is not set
14
+ encrypted_attribute = record.send("#{@attribute}")
15
+ actor = record.send("#{@actor_column}")
16
+ return if encrypted_attribute.blank? || actor.blank?
17
+
18
+ clear_attribute = nil
19
+
20
+ DoorMat::Session.current_session.autoload_sesion_for(actor)
21
+ DoorMat::Session.current_session.with_session_for_actor(actor) do |session|
22
+ clear_attribute = actor.decrypt_shared_key(encrypted_attribute, session)
23
+ end
24
+
25
+ if clear_attribute.nil?
26
+ clear_attribute = '[ENCRYPTED SHARED KEY]'
27
+ end
28
+
29
+ record.send("#{@attribute}=", clear_attribute)
30
+ DoorMat.configuration.logger.debug "DEBUG: Decrypt #{@attribute}: #{encrypted_attribute} -> #{clear_attribute}" if Rails.env.development?
31
+ end
32
+
33
+ def around_save(record)
34
+ return yield if DoorMat::Crypto.current_skip_crypto_callback.skip?
35
+
36
+ clear_attribute = record.send("#{@attribute}")
37
+ actor = record.send("#{@actor_column}")
38
+
39
+ # leave attribute as-is if blank or the actor is not set
40
+ if clear_attribute.blank? || actor.blank?
41
+ yield
42
+ else
43
+ encrypted_attribute = actor.encrypt_shared_key(clear_attribute)
44
+
45
+ DoorMat.configuration.logger.debug "DEBUG: Encrypt #{@attribute}: #{clear_attribute} -> #{encrypted_attribute}" if Rails.env.development?
46
+ record.send("#{@attribute}=", encrypted_attribute)
47
+ yield
48
+ record.send("#{@attribute}=", clear_attribute)
49
+ DoorMat.configuration.logger.debug "DEBUG: Decrypt #{@attribute}: #{encrypted_attribute} -> #{clear_attribute}" if Rails.env.development?
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ def self.included(base)
56
+ base.extend ClassMethods
57
+ end
58
+
59
+ module ClassMethods
60
+
61
+ def attr_asymmetric_store(*args, **options)
62
+ return unless self.table_exists?
63
+
64
+ actor_column = options.fetch(:actor_column, :actor).to_s
65
+ unless self.attribute_names.include? "#{actor_column}_id"
66
+ raise ActiveRecord::ActiveRecordError, "attr_asymmetric_store records must belong to a DoorMat::Actor but could not find the actor column. Pass the actor_column: :actor_column_name option to specify it."
67
+ end
68
+
69
+ args.each do |arg|
70
+ column_type = (self.columns_hash[arg.to_s].cast_type.respond_to?(:type) && self.columns_hash[arg.to_s].cast_type.type) || self.columns_hash[arg.to_s].cast_type.to_s
71
+ if [:text, :string].include? column_type
72
+ after_find DoorMat::AttrAsymmetricStore::AttrAsymmetricStoreWrapper.new(arg.to_s, actor_column)
73
+ around_save DoorMat::AttrAsymmetricStore::AttrAsymmetricStoreWrapper.new(arg.to_s, actor_column)
74
+ else
75
+ raise ActiveRecord::ActiveRecordError, "attr_asymmetric_store only support text and string column types."
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,82 @@
1
+ module DoorMat
2
+ module AttrSymmetricStore
3
+ class AttrSymmetricStoreWrapper
4
+
5
+ def initialize(attribute, actor_column)
6
+ @attribute = attribute
7
+ @actor_column = actor_column
8
+ end
9
+
10
+ def after_find(record)
11
+ return if DoorMat::Crypto.current_skip_crypto_callback.skip?
12
+
13
+ encrypted_attribute = record.send("#{@attribute}")
14
+ actor = record.send("#{@actor_column}")
15
+ clear_attribute = nil
16
+
17
+ DoorMat::Session.current_session.autoload_sesion_for(actor)
18
+ DoorMat::Session.current_session.with_session_for_actor(actor) do |session|
19
+ clear_attribute = session.decrypt(encrypted_attribute)
20
+ end
21
+
22
+ if clear_attribute.nil?
23
+ clear_attribute = '[ENCRYPTED]'
24
+ record.readonly!
25
+ end
26
+
27
+ record.send("#{@attribute}=", clear_attribute)
28
+ DoorMat.configuration.logger.debug "DEBUG: Decrypt #{@attribute}: #{encrypted_attribute} -> #{clear_attribute}" if Rails.env.development?
29
+ end
30
+
31
+ def around_save(record)
32
+ return yield if DoorMat::Crypto.current_skip_crypto_callback.skip?
33
+
34
+ raise ActiveRecord::Rollback, "Record is read-only" if record.readonly?
35
+
36
+ clear_attribute = record.send("#{@attribute}")
37
+ actor = record.send("#{@actor_column}")
38
+ encrypted_attribute = nil
39
+
40
+ DoorMat::Session.current_session.autoload_sesion_for(actor)
41
+ DoorMat::Session.current_session.with_session_for_actor(actor) do |session|
42
+ encrypted_attribute = session.encrypt(clear_attribute)
43
+ end
44
+ raise ActiveRecord::Rollback, "DoorMat::Session is not valid" if encrypted_attribute.nil?
45
+
46
+ DoorMat.configuration.logger.debug "DEBUG: Encrypt #{@attribute}: #{clear_attribute} -> #{encrypted_attribute}" if Rails.env.development?
47
+ record.send("#{@attribute}=", encrypted_attribute)
48
+ yield
49
+ record.send("#{@attribute}=", clear_attribute)
50
+ DoorMat.configuration.logger.debug "DEBUG: Decrypt #{@attribute}: #{encrypted_attribute} -> #{clear_attribute}" if Rails.env.development?
51
+ end
52
+
53
+ end
54
+
55
+ def self.included(base)
56
+ base.extend ClassMethods
57
+ end
58
+
59
+ module ClassMethods
60
+
61
+ def attr_symmetric_store(*args, **options)
62
+ return unless self.table_exists?
63
+
64
+ actor_column = options.fetch(:actor_column, :actor).to_s
65
+ unless self.attribute_names.include? "#{actor_column}_id"
66
+ raise ActiveRecord::ActiveRecordError, "attr_symmetric_store records must belong to a DoorMat::Actor but could not find the actor column. Pass the actor_column: :actor_column_name option to specify it."
67
+ end
68
+
69
+ args.each do |arg|
70
+ column_type = (self.columns_hash[arg.to_s].cast_type.respond_to?(:type) && self.columns_hash[arg.to_s].cast_type.type) || self.columns_hash[arg.to_s].cast_type.to_s
71
+ if [:text, :string].include? column_type
72
+ after_find DoorMat::AttrSymmetricStore::AttrSymmetricStoreWrapper.new(arg.to_s, actor_column)
73
+ around_save DoorMat::AttrSymmetricStore::AttrSymmetricStoreWrapper.new(arg.to_s, actor_column)
74
+ else
75
+ raise ActiveRecord::ActiveRecordError, "attr_symmetric_store only support text and string column types."
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,193 @@
1
+ module DoorMat
2
+ class Configuration
3
+
4
+ attr_accessor \
5
+ :mailer_from_address,
6
+ :password_reconfirm_delay,
7
+ :public_computer_access_session_timeout,
8
+ :private_computer_access_session_timeout,
9
+ :forgot_password_link_request_delay_minutes,
10
+ :forgot_password_link_expiration_delay_minutes,
11
+ :allow_remember_me_feature,
12
+ :remember_me_require_private_computer_confirmation,
13
+ :remember_me_max_day_count,
14
+ :leak_email_address_at_reconfirm,
15
+ :plausible_deniability_count,
16
+ :max_email_count_per_actor,
17
+ :define_door_mat_routes,
18
+ :allow_redirect_to_requested_url,
19
+ :lockdown_default_redirect_url,
20
+ :sign_up_success_url,
21
+ :sign_in_success_url,
22
+ :add_email_success_url,
23
+ :destroy_email_redirect_url,
24
+ :set_primary_email_redirect_url,
25
+ :resend_email_confirmation_redirect_url,
26
+ :confirm_email_success_url,
27
+ :reconfirm_password_success_url,
28
+ :change_password_success_url,
29
+ :sign_out_success_url,
30
+ :forgot_password_verification_mail_sent_url,
31
+ :allow_sign_up,
32
+ :allow_sign_in_from_sign_up_form,
33
+ :transmit_cookies_only_over_https,
34
+ :crypto_pbkdf2_salt_length,
35
+ :crypto_pbkdf2_password_length,
36
+ :crypto_pbkdf2_iterations,
37
+ :crypto_bcrypt_cost,
38
+ :crypto_secure_compare_default_length,
39
+ :event_hook_before_sign_up,
40
+ :event_hook_after_sign_up,
41
+ :event_hook_after_failed_sign_up,
42
+ :event_hook_before_sign_in,
43
+ :event_hook_after_sign_in,
44
+ :event_hook_after_failed_sign_in,
45
+ :event_hook_before_confirm_email,
46
+ :event_hook_after_confirm_email,
47
+ :event_hook_after_failed_confirm_email,
48
+ :event_hook_before_download_recovery_key,
49
+ :event_hook_after_download_recovery_key,
50
+ :event_hook_after_failed_download_recovery_key,
51
+ :event_hook_before_sign_out,
52
+ :event_hook_after_sign_out,
53
+ :logger,
54
+ :password_less_sessions
55
+
56
+ def initialize
57
+ @mailer_from_address = "noreply@example.com"
58
+
59
+ # Controllers that require_password_reconfirm will only
60
+ # allow the user in without requesting an additional sign-in if the user password
61
+ # was last entered less than password_reconfirm_delay
62
+ # minutes ago.
63
+ # All sections of the site allowing access to or modification
64
+ # of sensitive information or settings should be protected this way.
65
+ # This includes operations resulting in
66
+ # a financial transaction using stored or pre-authorized payment methods.
67
+ @password_reconfirm_delay = 5
68
+
69
+ # A session from a public computer will only last
70
+ # until the browser is closed and will timeout
71
+ # after public_computer_access_session_timeout
72
+ # minutes of inactivity.
73
+ @public_computer_access_session_timeout = 30
74
+
75
+ # A session from a private computer will survive
76
+ # a browser restart but will expire in the
77
+ # browser and timeout on the system
78
+ # after private_computer_access_session_timeout
79
+ # minutes of inactivity.
80
+ @private_computer_access_session_timeout = 60
81
+
82
+
83
+ # To prevent email flooding, a new request for a recovery password
84
+ # links will only be sent after the specified delay
85
+ @forgot_password_link_request_delay_minutes = 30
86
+
87
+ # Password recovery links older than this delay become invalid
88
+ @forgot_password_link_expiration_delay_minutes = 30
89
+
90
+ # Does the system allow the remember me feature?
91
+ # High value target systems such as financial sites
92
+ # should not allow the remember me feature.
93
+ # Even when this feature is enabled, sensitive area of the site
94
+ # should require users to re-authenticate using a
95
+ # before_action -> {require_password_reconfirm()}
96
+ # filter
97
+ @allow_remember_me_feature = false
98
+
99
+ # As a safety reminder, the user must confirm that they
100
+ # are not loging in from a public computer before enabling
101
+ # the remember me feature
102
+ @remember_me_require_private_computer_confirmation = true
103
+
104
+ # A session from a private computer for which the
105
+ # cookie will remain for a number of days specified
106
+ # by remember_me_max_day_count and automatically
107
+ # renew the session for that period of time
108
+ @remember_me_max_day_count = 30
109
+
110
+ # Do not pre-populate the email address field
111
+ # in the sign_in form while doing a password reconfirmation
112
+ # as it could be considered to leak the information about which
113
+ # email address was used to login to the system before the reconfirmation request
114
+ @leak_email_address_at_reconfirm = false
115
+
116
+ # How many different accounts a single email address can be associated with on the system
117
+ @plausible_deniability_count = 1
118
+
119
+ # How many different emails can be linked to an actor
120
+ @max_email_count_per_actor = 2
121
+
122
+ # Production systems should eventually redefine their own routes explicitly
123
+ # instead of relying on those provided by the engine
124
+ @define_door_mat_routes = true
125
+
126
+ #
127
+ @allow_redirect_to_requested_url = true
128
+
129
+ # When specifying redirects in
130
+ # config/initializers/door_mat.rb you can use:
131
+ # [ :main_app, :__path__ ] or [:__engine_name_, :__path__] respectively to redirect to an
132
+ # existing path defined in your main application or loaded engine.
133
+ # [:main_app, :root_url] to redirect to the root of your main application.
134
+ # [ :request, :referer ] for an alternative to redirect_to :back.
135
+ @lockdown_default_redirect_url = [ :request, :referer ]
136
+ @sign_up_success_url = [ :sign_up_success_url ]
137
+ @sign_in_success_url = [ :sign_in_success_url ]
138
+ @add_email_success_url = [ :add_email_success_url ]
139
+ @destroy_email_redirect_url = [ :request, :referer ]
140
+ @set_primary_email_redirect_url = [ :request, :referer ]
141
+ @resend_email_confirmation_redirect_url = [ :request, :referer ]
142
+ @confirm_email_success_url = [ :confirm_email_success_url ]
143
+ @reconfirm_password_success_url = [ :reconfirm_password_success_url ]
144
+ @change_password_success_url = [ :change_password_success_url ]
145
+ @sign_out_success_url = [ :sign_out_success_url ]
146
+ @forgot_password_verification_mail_sent_url = [ :forgot_password_verification_mail_sent_url ]
147
+
148
+ @allow_sign_up = true
149
+ @allow_sign_in_from_sign_up_form = false
150
+
151
+ @transmit_cookies_only_over_https = true
152
+
153
+ @crypto_pbkdf2_salt_length = 32
154
+ @crypto_pbkdf2_password_length = 32
155
+ @crypto_pbkdf2_iterations = 10_000
156
+
157
+ @crypto_bcrypt_cost = 12
158
+
159
+ @crypto_secure_compare_default_length = 1024
160
+
161
+
162
+ @event_hook_before_sign_up = []
163
+ @event_hook_after_sign_up = []
164
+ @event_hook_after_failed_sign_up = []
165
+ @event_hook_before_sign_in = []
166
+ @event_hook_after_sign_in = []
167
+ @event_hook_after_failed_sign_in = []
168
+ @event_hook_before_confirm_email = []
169
+ @event_hook_after_confirm_email = [] # The confirmed DoorMat::Email is passed as function argument
170
+ @event_hook_after_failed_confirm_email = []
171
+ @event_hook_before_download_recovery_key = []
172
+ @event_hook_after_download_recovery_key = []
173
+ @event_hook_after_failed_download_recovery_key = []
174
+ @event_hook_before_sign_out = []
175
+ @event_hook_after_sign_out = []
176
+
177
+ @logger = Rails.logger
178
+
179
+ # By default, there are no password less sessions defined
180
+ # see test_app/config/initializers/door_mat.rb for sample usage
181
+ @password_less_sessions = {}
182
+
183
+ end
184
+ end
185
+
186
+ def self.configuration
187
+ @configuration ||= DoorMat::Configuration.new
188
+ end
189
+
190
+ def self.configure
191
+ yield configuration
192
+ end
193
+ end