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,117 @@
1
+
2
+ module DoorMat
3
+ module Controller
4
+
5
+ def sign_out
6
+ DoorMat::Session.clear_current_session
7
+ DoorMat::Session.destroy_if_linked_to(cookies)
8
+
9
+ DoorMat::AccessToken.clear_current_access_token
10
+ DoorMat::AccessToken.destroy_if_linked_to(cookies)
11
+ end
12
+
13
+ def lockdown(**options)
14
+ o = {
15
+ log_level: :error,
16
+ log_message: "LOCKDOWN: No log message specified",
17
+ redirect_to: nil
18
+ }
19
+ options = o.merge(options.to_h)
20
+
21
+ DoorMat.configuration.logger.send(options[:log_level] , options[:log_message])
22
+
23
+ sign_out
24
+
25
+ redirect_to options[:redirect_to] || config_url_redirect(:lockdown_default_redirect_url)
26
+ end
27
+
28
+ def handle_unverified_request
29
+ super
30
+ rescue ActionController::InvalidAuthenticityToken => e
31
+ raise e
32
+ ensure
33
+ lockdown(log_level: :warn, log_message: 'WARN: handle_unverified_request')
34
+ end
35
+
36
+ def require_valid_session
37
+ unless DoorMat::Session.current_session.valid?
38
+ DoorMat::Session.from(cookies, request)
39
+ else
40
+ DoorMat.configuration.logger.error "ERROR: are you calling require_valid_session more than once?"
41
+ end
42
+ unless DoorMat::Session.current_session.valid?
43
+ set_session_redirect_to
44
+
45
+ redirect_to door_mat.sign_in_url
46
+ end
47
+ end
48
+
49
+ def require_confirmed_email
50
+ unless DoorMat::Session.current_session.valid? && (DoorMat::Session.current_session.email.confirmed? || DoorMat::Session.current_session.email.primary?)
51
+ redirect_to door_mat.email_confirmation_required_url
52
+ end
53
+ end
54
+
55
+ # To assign a custom amount of delay for a specific filter,
56
+ # use as follow for a delay of 1 minute:
57
+ # before_filter -> {require_password_reconfirm(1)}
58
+ def require_password_reconfirm(minutes_old=nil)
59
+ minutes_old ||= DoorMat.configuration.password_reconfirm_delay
60
+
61
+ if DoorMat::Session.current_session.invalid? || DoorMat::Session.current_session.is_older_than(minutes_old)
62
+ set_session_redirect_to
63
+ redirect_to door_mat.reconfirm_password_url
64
+ end
65
+ end
66
+
67
+ def protected_by_password_less_session(pls_symbols)
68
+ pls_symbols = Array(pls_symbols)
69
+ redirect_url = send("#{pls_symbols.first}_url".to_sym)
70
+
71
+ if DoorMat::AccessToken.is_cookie_present? cookies
72
+ DoorMat::AccessToken.validate_from_cookie(cookies, request)
73
+ if DoorMat::AccessToken.current_access_token.valid? && pls_symbols.include?(DoorMat::AccessToken.current_access_token.token_for.to_sym)
74
+ return if DoorMat::AccessToken.current_access_token.used? || DoorMat::AccessToken.current_access_token.multiple_use?
75
+ end
76
+ DoorMat::AccessToken.destroy_if_linked_to(cookies)
77
+ end
78
+
79
+ set_session_redirect_to
80
+ redirect_to redirect_url
81
+ end
82
+
83
+ def update_session_last_activity_time
84
+
85
+ if DoorMat::Session.current_session.valid?
86
+ DoorMat::Session.current_session.updated_at = DateTime.current
87
+ DoorMat::Session.current_session.save
88
+ end
89
+
90
+ if DoorMat::AccessToken.current_access_token.valid?
91
+ DoorMat::AccessToken.current_access_token.updated_at = DateTime.current
92
+ DoorMat::AccessToken.current_access_token.save
93
+ end
94
+
95
+ end
96
+
97
+ def main_app_root_url
98
+ [:main_app, :root_url].inject(self) { |lhs, rhs| lhs.send(rhs) }
99
+ end
100
+
101
+ def config_url_redirect(url_token)
102
+ config_url = DoorMat.configuration.send(url_token)
103
+ config_url.inject(self) { |lhs, rhs| lhs.send(rhs) } || main_app_root_url
104
+ end
105
+
106
+ private
107
+
108
+ def set_session_redirect_to
109
+ if request.get? && DoorMat.configuration.allow_redirect_to_requested_url
110
+ session[:redirect_to] = request.url
111
+ else
112
+ session.delete(:redirect_to)
113
+ end
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,49 @@
1
+ require 'door_mat/crypto/secure_compare'
2
+ require 'door_mat/crypto/password_hash'
3
+ require 'door_mat/crypto/symmetric_store'
4
+ require 'door_mat/crypto/asymmetric_store'
5
+ require 'door_mat/crypto/fast_hash'
6
+
7
+ module DoorMat
8
+ module Crypto
9
+
10
+ def self.encrypt_shared(secrets, with_key)
11
+ Array(secrets).map do |s|
12
+ DoorMat::Crypto::SymmetricStore.encrypt(s, with_key)[:ciphertext]
13
+ end
14
+ end
15
+
16
+ def self.decrypt_shared(secrets, with_key)
17
+ Array(secrets).map do |s|
18
+ DoorMat::Crypto::SymmetricStore.decrypt(s, with_key)
19
+ end
20
+ end
21
+
22
+ def self.current_skip_crypto_callback
23
+ RequestStore.store[:current_skip_crypto_callback] ||= DoorMat::Crypto::SkipCallback.new
24
+ end
25
+
26
+ def self.skip_crypto_callback
27
+ DoorMat::Crypto.current_skip_crypto_callback.skip!
28
+ yield
29
+ ensure
30
+ DoorMat::Crypto.current_skip_crypto_callback.reset
31
+ end
32
+
33
+ class SkipCallback
34
+ def initialize
35
+ @skip = false
36
+ end
37
+ def skip?
38
+ @skip
39
+ end
40
+ def skip!
41
+ @skip = true
42
+ end
43
+ def reset
44
+ @skip = false
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,77 @@
1
+ require 'openssl'
2
+
3
+ module DoorMat
4
+ module Crypto
5
+ module AsymmetricStore
6
+
7
+ def encrypt(plaintext, public_key)
8
+ raise ArgumentError, 'Plaintext exceeds maximum length of 245 bytes' if plaintext.to_str.bytesize > 245
9
+ Base64.strict_encode64(public_key.public_encrypt(plaintext.to_str))
10
+ end
11
+ module_function :encrypt
12
+
13
+ def decrypt(ciphertext, private_key)
14
+ private_key.private_decrypt(Base64.strict_decode64(ciphertext.to_str))
15
+ end
16
+ module_function :decrypt
17
+
18
+ def generate_pem_encrypted_pkey_pair_and_key
19
+ pkey = OpenSSL::PKey::RSA.generate(2048)
20
+ c = cipher()
21
+ c.encrypt
22
+ c.random_iv
23
+ key = c.random_key
24
+ pem_encrypted_pkey = ''
25
+
26
+ begin
27
+
28
+ pem_encrypted_pkey = pkey.to_pem(c, key)
29
+
30
+ rescue OpenSSL::PKey::RSAError => e
31
+ DoorMat.configuration.logger.error "ERROR: spurious error - #{e} for key _#{key}_"
32
+ key = c.random_key
33
+ retry
34
+ end
35
+
36
+ {
37
+ key: Base64.strict_encode64(key),
38
+ pem_encrypted_pkey: pem_encrypted_pkey
39
+ }
40
+ end
41
+ module_function :generate_pem_encrypted_pkey_pair_and_key
42
+
43
+ def private_key_from_pem_encrypted_pkey_pair(pem_encrypted_pkey, key)
44
+ OpenSSL::PKey::RSA.new(pem_encrypted_pkey.to_str, decode_key(key.to_str))
45
+ end
46
+ module_function :private_key_from_pem_encrypted_pkey_pair
47
+
48
+ def public_key_from_pem_encrypted_pkey_pair(pem_encrypted_pkey, key)
49
+ OpenSSL::PKey::RSA.new(pem_encrypted_pkey.to_str, decode_key(key.to_str)).public_key
50
+ end
51
+ module_function :public_key_from_pem_encrypted_pkey_pair
52
+
53
+ def pem_public_key_from_pem_encrypted_pkey_pair(pem_encrypted_pkey, key)
54
+ public_key_from_pem_encrypted_pkey_pair(pem_encrypted_pkey.to_str, key.to_str).to_pem
55
+ end
56
+ module_function :pem_public_key_from_pem_encrypted_pkey_pair
57
+
58
+ def public_key_from_pem_public_key(pem_public_key)
59
+ OpenSSL::PKey::RSA.new(pem_public_key.to_str).public_key
60
+ end
61
+ module_function :public_key_from_pem_public_key
62
+
63
+ def cipher
64
+ OpenSSL::Cipher.new('DES-EDE3-CBC')
65
+ end
66
+ module_function :cipher
67
+
68
+ def decode_key(key)
69
+ Base64.strict_decode64(key.to_str).tap do |decoded_key|
70
+ raise ArgumentError, "Key must be exactly 24 bytes in length" if decoded_key.bytesize != 24
71
+ end
72
+ end
73
+ module_function :decode_key
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,17 @@
1
+ require 'openssl'
2
+
3
+ module DoorMat
4
+ module Crypto
5
+ module FastHash
6
+
7
+ def sha256(data)
8
+ sha256 = OpenSSL::Digest::SHA256.new
9
+ Base64.urlsafe_encode64(
10
+ sha256.digest(data.to_str)
11
+ )
12
+ end
13
+ module_function :sha256
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'openssl'
2
+ require 'bcrypt'
3
+
4
+ module DoorMat
5
+ module Crypto
6
+ module PasswordHash
7
+
8
+ def pbkdf2_salt(salt_length=nil, password_length=nil, iterations=nil)
9
+ salt_length ||= DoorMat.configuration.crypto_pbkdf2_salt_length
10
+ password_length ||= DoorMat.configuration.crypto_pbkdf2_password_length
11
+ iterations ||= DoorMat.configuration.crypto_pbkdf2_iterations
12
+
13
+ [password_length, iterations, OpenSSL::Random.random_bytes(salt_length)].map { |s| Base64.strict_encode64(s.to_s)}.join('--')
14
+ end
15
+ module_function :pbkdf2_salt
16
+
17
+ def pbkdf2_hash(password, salt)
18
+ length, iterations, salt = salt.to_str.split('--').map { |s| Base64.strict_decode64(s) }
19
+ Base64.strict_encode64(
20
+ OpenSSL::PKCS5.pbkdf2_hmac_sha1(password.to_str, salt, Integer(iterations), Integer(length))
21
+ )
22
+ end
23
+ module_function :pbkdf2_hash
24
+
25
+ def bcrypt_salt(cost=nil)
26
+ cost ||= DoorMat.configuration.crypto_bcrypt_cost
27
+ BCrypt::Engine.generate_salt(Integer(cost))
28
+ end
29
+ module_function :bcrypt_salt
30
+
31
+ def bcrypt_hash(password, salt=nil)
32
+ salt ||= bcrypt_salt
33
+ BCrypt::Engine.hash_secret(password.to_str, salt.to_str)
34
+ end
35
+ module_function :bcrypt_hash
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ require "securerandom"
2
+
3
+ module DoorMat
4
+ module Crypto
5
+
6
+ # http://groups.google.com/group/rubyonrails-security/browse_thread/thread/da57f883530352ee#
7
+ # constant-time comparison algorithm to prevent timing attacks
8
+ def secure_compare(lhs, rhs, constant_length=nil)
9
+ constant_length ||= DoorMat.configuration.crypto_secure_compare_default_length
10
+ constant_length = [constant_length.to_int, lhs.to_str.bytesize, rhs.to_str.bytesize].max
11
+ random_padding = SecureRandom.random_bytes(constant_length)
12
+
13
+ l = lhs.to_str.ljust(constant_length, random_padding).unpack "C#{constant_length}"
14
+ r = rhs.to_str.ljust(constant_length, random_padding).unpack "C#{constant_length}"
15
+
16
+ result = 0
17
+ l.zip(r) { |a,b| result |= a ^ b }
18
+ 0 == result
19
+ end
20
+ module_function :secure_compare
21
+
22
+ end
23
+ end
@@ -0,0 +1,68 @@
1
+ require 'openssl'
2
+
3
+ module DoorMat
4
+ module Crypto
5
+ module SymmetricStore
6
+
7
+ def encrypt(plaintext, key=nil)
8
+ c = cipher()
9
+ c.encrypt
10
+ key = if key.nil?
11
+ c.random_key
12
+ else
13
+ c.key = decode_key(key.to_str)
14
+ end
15
+ iv = c.random_iv
16
+ plaintext_str = plaintext.to_str
17
+ if plaintext_str.blank?
18
+ return {
19
+ key: Base64.strict_encode64(key),
20
+ ciphertext: ""
21
+ }
22
+ end
23
+ encrypted_string = c.update(plaintext_str) + c.final
24
+ encoding = plaintext_str.encoding.name
25
+
26
+ {
27
+ key: Base64.strict_encode64(key),
28
+ ciphertext: [encoding, c.auth_tag, iv, encrypted_string].map { |s| Base64.strict_encode64(s)}.join('--')
29
+ }
30
+ end
31
+ module_function :encrypt
32
+
33
+ def decrypt(ciphertext, key)
34
+ return "" if ciphertext.to_str.blank?
35
+
36
+ c = cipher()
37
+ c.decrypt
38
+ c.key = decode_key(key.to_str)
39
+ encoding, auth_tag, iv, encrypted_string = ciphertext.to_str.split('--').map { |s| Base64.strict_decode64(s) }
40
+ c.iv = iv
41
+ c.auth_tag = auth_tag
42
+ plaintext_str = c.update(encrypted_string) + c.final
43
+ plaintext_str.force_encoding(encoding)
44
+ end
45
+ module_function :decrypt
46
+
47
+ def cipher
48
+ OpenSSL::Cipher.new('aes-256-gcm')
49
+ end
50
+ module_function :cipher
51
+
52
+ def decode_key(key)
53
+ Base64.strict_decode64(key.to_str).tap do |decoded_key|
54
+ raise ArgumentError, "Key must be exactly 32 bytes in length" if decoded_key.bytesize != 32
55
+ end
56
+ end
57
+ module_function :decode_key
58
+
59
+ def random_key
60
+ c = cipher()
61
+ c.encrypt
62
+ Base64.strict_encode64(c.random_key)
63
+ end
64
+ module_function :random_key
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,23 @@
1
+ module DoorMat
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace DoorMat
4
+
5
+ initializer "door_mat.filter" do |app|
6
+ app.config.filter_parameters += [:password]
7
+ end
8
+
9
+ # http://pivotallabs.com/leave-your-migrations-in-your-rails-engines/
10
+ initializer :append_migrations do |app|
11
+ unless app.root.to_s.match root.to_s
12
+ config.paths["db/migrate"].expanded.each do |expanded_path|
13
+ app.config.paths["db/migrate"] << expanded_path
14
+ end
15
+ end
16
+ end
17
+
18
+ config.generators do |g|
19
+ g.test_framework :rspec, :fixture => false
20
+ g.fixture_replacement :factory_girl, :dir => 'spec/factories'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ module DoorMat
2
+ module Process
3
+ class ActorPasswordChange
4
+
5
+ def self.after_password_reset(actor, new_password, recovery_key)
6
+ Session.clear_current_session
7
+ return false unless Session.current_session.recovery_key_restore(actor, recovery_key)
8
+ with(actor, new_password, nil, false)
9
+ end
10
+
11
+ def self.with(actor, new_password, old_password='', save_session=true)
12
+ actor.with_lock do
13
+
14
+ unless old_password.nil?
15
+ return false unless actor.authenticate(old_password)
16
+ end
17
+
18
+ session = DoorMat::Session.current_session
19
+ if save_session
20
+ session = DoorMat::Session.current_session.reload
21
+ unless session.valid?
22
+ return false
23
+ end
24
+ end
25
+
26
+ actor.re_key_with(new_password)
27
+ session.re_key_with(actor, new_password)
28
+
29
+ pem_key = session.decrypt(actor.encrypted_pem_key)
30
+ actor.encrypted_pem_key = session.encrypt(pem_key)
31
+
32
+ actor.save!
33
+ session.save! if save_session
34
+
35
+ keys = Actor.reflections.keys.select {|item| Actor.reflections[item].klass.methods.include?(:attr_symmetric_store)}
36
+ keys.each do |key|
37
+ collection = actor.send(key)
38
+ unless collection.blank?
39
+ if collection.respond_to? :find_each
40
+ collection.find_each do |record|
41
+ record.save!
42
+ end
43
+ else
44
+ collection.save!
45
+ end
46
+ end
47
+ end
48
+
49
+ actor.keep_only_this_session! session
50
+
51
+ end
52
+
53
+ ActivityDownloadRecoveryKey.for(actor)
54
+ true
55
+ rescue ActiveRecord::RecordNotFound => e
56
+ DoorMat.configuration.logger.warn "WARN: Failed to change actor password - #{e}"
57
+ false
58
+ rescue Exception => e
59
+ DoorMat.configuration.logger.error "ERROR: Failed to change actor password - #{e}"
60
+ raise e
61
+ end
62
+
63
+ end
64
+ end
65
+ end