door_mat 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +32 -0
- data/app/assets/javascripts/door_mat/application.js +13 -0
- data/app/assets/stylesheets/door_mat/application.css +15 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/door_mat/activities_controller.rb +106 -0
- data/app/controllers/door_mat/application_controller.rb +14 -0
- data/app/controllers/door_mat/change_password_controller.rb +32 -0
- data/app/controllers/door_mat/forgot_passwords_controller.rb +57 -0
- data/app/controllers/door_mat/manage_email_controller.rb +61 -0
- data/app/controllers/door_mat/password_less_session_controller.rb +121 -0
- data/app/controllers/door_mat/reconfirm_password_controller.rb +27 -0
- data/app/controllers/door_mat/sessions_controller.rb +17 -0
- data/app/controllers/door_mat/sign_in_controller.rb +60 -0
- data/app/controllers/door_mat/sign_up_controller.rb +59 -0
- data/app/controllers/door_mat/static_controller.rb +5 -0
- data/app/mailers/door_mat/activity_mailer.rb +18 -0
- data/app/mailers/door_mat/password_less_session_mailer.rb +12 -0
- data/app/models/door_mat/access_token.rb +315 -0
- data/app/models/door_mat/activity.rb +14 -0
- data/app/models/door_mat/activity_confirm_email.rb +45 -0
- data/app/models/door_mat/activity_download_recovery_key.rb +30 -0
- data/app/models/door_mat/activity_reset_password.rb +47 -0
- data/app/models/door_mat/actor.rb +149 -0
- data/app/models/door_mat/change_password.rb +12 -0
- data/app/models/door_mat/email.rb +58 -0
- data/app/models/door_mat/forgot_password.rb +12 -0
- data/app/models/door_mat/membership.rb +42 -0
- data/app/models/door_mat/session.rb +315 -0
- data/app/models/door_mat/sign_in.rb +31 -0
- data/app/models/door_mat/sign_up.rb +17 -0
- data/app/views/door_mat/activity_mailer/confirm_email.html.erb +11 -0
- data/app/views/door_mat/activity_mailer/confirm_email.text.erb +7 -0
- data/app/views/door_mat/activity_mailer/reset_password.html.erb +11 -0
- data/app/views/door_mat/activity_mailer/reset_password.text.erb +7 -0
- data/app/views/door_mat/change_password/new.html.erb +22 -0
- data/app/views/door_mat/forgot_passwords/choose_new_password.html.erb +34 -0
- data/app/views/door_mat/forgot_passwords/new.html.erb +14 -0
- data/app/views/door_mat/helpers/_errors_if_any.html.erb +10 -0
- data/app/views/door_mat/manage_email/new.html.erb +14 -0
- data/app/views/door_mat/password_less_session/access_token.html.erb +16 -0
- data/app/views/door_mat/password_less_session/new.html.erb +34 -0
- data/app/views/door_mat/password_less_session_mailer/send_token.html.erb +11 -0
- data/app/views/door_mat/password_less_session_mailer/send_token.text.erb +7 -0
- data/app/views/door_mat/reconfirm_password/new.html.erb +12 -0
- data/app/views/door_mat/sign_in/new.html.erb +30 -0
- data/app/views/door_mat/sign_up/new.html.erb +24 -0
- data/app/views/door_mat/static/add_email_success.html.erb +5 -0
- data/app/views/door_mat/static/change_password_success.html.erb +2 -0
- data/app/views/door_mat/static/confirm_email_success.html.erb +2 -0
- data/app/views/door_mat/static/email_confirmation_required.html.erb +17 -0
- data/app/views/door_mat/static/forgot_password_verification_mail_sent.html.erb +2 -0
- data/app/views/door_mat/static/reconfirm_password_success.html.erb +4 -0
- data/app/views/door_mat/static/sign_in_success.html.erb +5 -0
- data/app/views/door_mat/static/sign_out_success.html.erb +5 -0
- data/app/views/door_mat/static/sign_up_success.html.erb +4 -0
- data/bin/rails +12 -0
- data/config/locales/en.yml +73 -0
- data/config/routes.rb +48 -0
- data/db/migrate/20140616234935_create_door_mat_actors.rb +23 -0
- data/db/migrate/20140617233357_create_door_mat_sessions.rb +17 -0
- data/db/migrate/20140630043202_create_door_mat_emails.rb +12 -0
- data/db/migrate/20140702045729_create_door_mat_activities.rb +14 -0
- data/db/migrate/20141115183045_create_door_mat_access_tokens.rb +17 -0
- data/db/migrate/20141121191824_create_door_mat_memberships.rb +14 -0
- data/db/migrate/20150910182126_rename_session_guid_column.rb +5 -0
- data/db/migrate/20150918210831_add_access_token_rating_column.rb +5 -0
- data/door_mat.gemspec +37 -0
- data/lib/door_mat.rb +20 -0
- data/lib/door_mat/attr_asymmetric_store.rb +82 -0
- data/lib/door_mat/attr_symmetric_store.rb +82 -0
- data/lib/door_mat/configuration.rb +193 -0
- data/lib/door_mat/controller.rb +117 -0
- data/lib/door_mat/crypto.rb +49 -0
- data/lib/door_mat/crypto/asymmetric_store.rb +77 -0
- data/lib/door_mat/crypto/fast_hash.rb +17 -0
- data/lib/door_mat/crypto/password_hash.rb +39 -0
- data/lib/door_mat/crypto/secure_compare.rb +23 -0
- data/lib/door_mat/crypto/symmetric_store.rb +68 -0
- data/lib/door_mat/engine.rb +23 -0
- data/lib/door_mat/process/actor_password_change.rb +65 -0
- data/lib/door_mat/process/actor_sign_in.rb +38 -0
- data/lib/door_mat/process/actor_sign_up.rb +39 -0
- data/lib/door_mat/process/create_new_anonymous_actor.rb +36 -0
- data/lib/door_mat/process/manage_email.rb +42 -0
- data/lib/door_mat/process/reset_password.rb +50 -0
- data/lib/door_mat/regex.rb +17 -0
- data/lib/door_mat/test_helper.rb +58 -0
- data/lib/door_mat/url_protocol.rb +9 -0
- data/lib/door_mat/version.rb +3 -0
- data/lib/tasks/door_mat_tasks.rake +31 -0
- data/spec/controllers/door_mat/activities_controller_spec.rb +70 -0
- data/spec/controllers/door_mat/forgot_passwords_controller_spec.rb +57 -0
- data/spec/controllers/door_mat/manage_email_spec.rb +181 -0
- data/spec/controllers/door_mat/password_less_session_controller_spec.rb +344 -0
- data/spec/controllers/door_mat/sign_in_controller_spec.rb +211 -0
- data/spec/controllers/door_mat/sign_up_controller_spec.rb +90 -0
- data/spec/factories/door_mat_access_tokens.rb +6 -0
- data/spec/factories/door_mat_activitiess.rb +6 -0
- data/spec/factories/door_mat_actors.rb +23 -0
- data/spec/factories/door_mat_emails.rb +14 -0
- data/spec/factories/door_mat_memberships.rb +6 -0
- data/spec/factories/door_mat_sessions.rb +24 -0
- data/spec/features/password_less_session_spec.rb +165 -0
- data/spec/features/remember_me_spec.rb +672 -0
- data/spec/features/session_spec.rb +336 -0
- data/spec/lib/attr_store_spec.rb +237 -0
- data/spec/lib/crypto_spec.rb +130 -0
- data/spec/lib/process_spec.rb +159 -0
- data/spec/models/door_mat/access_token_spec.rb +134 -0
- data/spec/models/door_mat/activity_spec.rb +38 -0
- data/spec/models/door_mat/actor_spec.rb +56 -0
- data/spec/models/door_mat/email_spec.rb +25 -0
- data/spec/models/door_mat/session_spec.rb +69 -0
- data/spec/spec_helper.rb +223 -0
- data/spec/support/timecop/timecop_helper.rb +52 -0
- data/spec/test_app/README.rdoc +28 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/javascripts/application.js +13 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/controllers/account_controller.rb +28 -0
- data/spec/test_app/app/controllers/application_controller.rb +10 -0
- data/spec/test_app/app/controllers/password_less_sample_controller.rb +56 -0
- data/spec/test_app/app/controllers/static_controller.rb +7 -0
- data/spec/test_app/app/helpers/account_helper.rb +2 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/models/game.rb +62 -0
- data/spec/test_app/app/models/shared_data.rb +4 -0
- data/spec/test_app/app/models/shared_key.rb +8 -0
- data/spec/test_app/app/models/user_detail.rb +7 -0
- data/spec/test_app/app/views/account/show.html.erb +133 -0
- data/spec/test_app/app/views/door_mat/static/sign_out_success.html.erb +7 -0
- data/spec/test_app/app/views/layouts/application.html.erb +20 -0
- data/spec/test_app/app/views/password_less_sample/draw_results.html.erb +6 -0
- data/spec/test_app/app/views/password_less_sample/final_result.html.erb +7 -0
- data/spec/test_app/app/views/password_less_sample/play_game.html.erb +5 -0
- data/spec/test_app/app/views/password_less_sample/show_loosing_door.html.erb +10 -0
- data/spec/test_app/app/views/static/index.html.erb +12 -0
- data/spec/test_app/app/views/static/only_confirmed_email_allowed.html.erb +10 -0
- data/spec/test_app/app/views/static/page_that_require_password_reconfirmation.html.erb +16 -0
- data/spec/test_app/app/views/static/session_protected_page.html.erb +32 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/config/application.rb +29 -0
- data/spec/test_app/config/boot.rb +5 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +19 -0
- data/spec/test_app/config/environments/development.rb +50 -0
- data/spec/test_app/config/environments/production.rb +83 -0
- data/spec/test_app/config/environments/test.rb +48 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/test_app/config/initializers/door_mat.rb +72 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/session_store.rb +3 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +23 -0
- data/spec/test_app/config/routes.rb +42 -0
- data/spec/test_app/config/secrets.yml +31 -0
- data/spec/test_app/db/migrate/20140717182813_create_user_details.rb +10 -0
- data/spec/test_app/db/migrate/20140908225256_create_shared_data.rb +10 -0
- data/spec/test_app/db/migrate/20140908225604_create_shared_keys.rb +11 -0
- data/spec/test_app/db/migrate/20141121190714_create_games.rb +10 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/favicon.ico +0 -0
- 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
|