authlogic 3.8.0 → 4.5.0
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.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +28 -0
- data/.github/ISSUE_TEMPLATE/feature_proposal.md +32 -0
- data/.github/triage.md +86 -0
- data/.gitignore +4 -3
- data/.rubocop.yml +109 -9
- data/.rubocop_todo.yml +38 -355
- data/.travis.yml +11 -35
- data/CHANGELOG.md +345 -2
- data/CONTRIBUTING.md +45 -14
- data/Gemfile +3 -2
- data/README.md +244 -90
- data/Rakefile +10 -10
- data/UPGRADING.md +22 -0
- data/authlogic.gemspec +34 -21
- data/doc/use_normal_rails_validation.md +82 -0
- data/gemfiles/Gemfile.rails-4.2.x +6 -0
- data/{test/gemfiles → gemfiles}/Gemfile.rails-5.1.x +2 -2
- data/{test/gemfiles → gemfiles}/Gemfile.rails-5.2.x +2 -2
- data/lib/authlogic/acts_as_authentic/base.rb +36 -24
- data/lib/authlogic/acts_as_authentic/email.rb +65 -31
- data/lib/authlogic/acts_as_authentic/logged_in_status.rb +14 -9
- data/lib/authlogic/acts_as_authentic/login.rb +61 -45
- data/lib/authlogic/acts_as_authentic/magic_columns.rb +6 -6
- data/lib/authlogic/acts_as_authentic/password.rb +267 -146
- data/lib/authlogic/acts_as_authentic/perishable_token.rb +24 -19
- data/lib/authlogic/acts_as_authentic/persistence_token.rb +10 -15
- data/lib/authlogic/acts_as_authentic/queries/find_with_case.rb +67 -0
- data/lib/authlogic/acts_as_authentic/restful_authentication.rb +50 -14
- data/lib/authlogic/acts_as_authentic/session_maintenance.rb +88 -60
- data/lib/authlogic/acts_as_authentic/single_access_token.rb +23 -11
- data/lib/authlogic/acts_as_authentic/validations_scope.rb +9 -6
- data/lib/authlogic/authenticates_many/association.rb +7 -7
- data/lib/authlogic/authenticates_many/base.rb +37 -21
- data/lib/authlogic/config.rb +21 -10
- data/lib/authlogic/controller_adapters/abstract_adapter.rb +38 -11
- data/lib/authlogic/controller_adapters/rack_adapter.rb +9 -5
- data/lib/authlogic/controller_adapters/rails_adapter.rb +12 -7
- data/lib/authlogic/controller_adapters/sinatra_adapter.rb +2 -2
- data/lib/authlogic/crypto_providers/aes256.rb +37 -32
- data/lib/authlogic/crypto_providers/bcrypt.rb +21 -15
- data/lib/authlogic/crypto_providers/md5.rb +4 -2
- data/lib/authlogic/crypto_providers/scrypt.rb +22 -17
- data/lib/authlogic/crypto_providers/sha1.rb +11 -5
- data/lib/authlogic/crypto_providers/sha256.rb +13 -9
- data/lib/authlogic/crypto_providers/sha512.rb +0 -21
- data/lib/authlogic/crypto_providers/wordpress.rb +32 -3
- data/lib/authlogic/crypto_providers.rb +91 -0
- data/lib/authlogic/i18n.rb +26 -19
- data/lib/authlogic/random.rb +10 -28
- data/lib/authlogic/regex.rb +59 -28
- data/lib/authlogic/session/activation.rb +10 -7
- data/lib/authlogic/session/active_record_trickery.rb +13 -9
- data/lib/authlogic/session/base.rb +15 -4
- data/lib/authlogic/session/brute_force_protection.rb +40 -33
- data/lib/authlogic/session/callbacks.rb +94 -46
- data/lib/authlogic/session/cookies.rb +130 -45
- data/lib/authlogic/session/existence.rb +21 -11
- data/lib/authlogic/session/foundation.rb +64 -14
- data/lib/authlogic/session/http_auth.rb +35 -28
- data/lib/authlogic/session/id.rb +9 -4
- data/lib/authlogic/session/klass.rb +15 -12
- data/lib/authlogic/session/magic_columns.rb +58 -55
- data/lib/authlogic/session/magic_states.rb +25 -19
- data/lib/authlogic/session/params.rb +42 -28
- data/lib/authlogic/session/password.rb +130 -120
- data/lib/authlogic/session/perishable_token.rb +5 -4
- data/lib/authlogic/session/persistence.rb +18 -12
- data/lib/authlogic/session/priority_record.rb +15 -12
- data/lib/authlogic/session/scopes.rb +51 -32
- data/lib/authlogic/session/session.rb +38 -28
- data/lib/authlogic/session/timeout.rb +13 -13
- data/lib/authlogic/session/unauthorized_record.rb +18 -13
- data/lib/authlogic/session/validation.rb +9 -9
- data/lib/authlogic/test_case/mock_controller.rb +5 -4
- data/lib/authlogic/test_case/mock_cookie_jar.rb +47 -3
- data/lib/authlogic/test_case/mock_request.rb +6 -3
- data/lib/authlogic/test_case/rails_request_adapter.rb +3 -2
- data/lib/authlogic/test_case.rb +70 -2
- data/lib/authlogic/version.rb +21 -0
- data/lib/authlogic.rb +51 -49
- data/test/acts_as_authentic_test/base_test.rb +3 -1
- data/test/acts_as_authentic_test/email_test.rb +43 -42
- data/test/acts_as_authentic_test/logged_in_status_test.rb +6 -4
- data/test/acts_as_authentic_test/login_test.rb +77 -80
- data/test/acts_as_authentic_test/magic_columns_test.rb +3 -1
- data/test/acts_as_authentic_test/password_test.rb +51 -37
- data/test/acts_as_authentic_test/perishable_token_test.rb +13 -5
- data/test/acts_as_authentic_test/persistence_token_test.rb +7 -1
- data/test/acts_as_authentic_test/restful_authentication_test.rb +14 -3
- data/test/acts_as_authentic_test/session_maintenance_test.rb +69 -15
- data/test/acts_as_authentic_test/single_access_test.rb +3 -1
- data/test/adapter_test.rb +23 -0
- data/test/authenticates_many_test.rb +3 -1
- data/test/config_test.rb +11 -9
- data/test/crypto_provider_test/aes256_test.rb +3 -1
- data/test/crypto_provider_test/bcrypt_test.rb +3 -1
- data/test/crypto_provider_test/scrypt_test.rb +3 -1
- data/test/crypto_provider_test/sha1_test.rb +3 -1
- data/test/crypto_provider_test/sha256_test.rb +3 -1
- data/test/crypto_provider_test/sha512_test.rb +3 -1
- data/test/crypto_provider_test/wordpress_test.rb +26 -0
- data/test/fixtures/companies.yml +2 -2
- data/test/fixtures/employees.yml +1 -1
- data/test/i18n_test.rb +6 -4
- data/test/libs/affiliate.rb +2 -0
- data/test/libs/company.rb +4 -2
- data/test/libs/employee.rb +2 -0
- data/test/libs/employee_session.rb +2 -0
- data/test/libs/ldaper.rb +2 -0
- data/test/libs/project.rb +2 -0
- data/test/libs/user.rb +2 -0
- data/test/libs/user_session.rb +4 -2
- data/test/random_test.rb +10 -38
- data/test/session_test/activation_test.rb +3 -1
- data/test/session_test/active_record_trickery_test.rb +7 -4
- data/test/session_test/brute_force_protection_test.rb +11 -9
- data/test/session_test/callbacks_test.rb +12 -4
- data/test/session_test/cookies_test.rb +48 -5
- data/test/session_test/existence_test.rb +18 -5
- data/test/session_test/foundation_test.rb +19 -1
- data/test/session_test/http_auth_test.rb +11 -7
- data/test/session_test/id_test.rb +3 -1
- data/test/session_test/klass_test.rb +3 -1
- data/test/session_test/magic_columns_test.rb +13 -13
- data/test/session_test/magic_states_test.rb +3 -1
- data/test/session_test/params_test.rb +13 -5
- data/test/session_test/password_test.rb +10 -8
- data/test/session_test/perishability_test.rb +3 -1
- data/test/session_test/persistence_test.rb +4 -1
- data/test/session_test/scopes_test.rb +16 -8
- data/test/session_test/session_test.rb +6 -4
- data/test/session_test/timeout_test.rb +4 -2
- data/test/session_test/unauthorized_record_test.rb +4 -2
- data/test/session_test/validation_test.rb +3 -1
- data/test/test_helper.rb +84 -45
- metadata +87 -73
- data/.github/ISSUE_TEMPLATE.md +0 -13
- data/test/gemfiles/Gemfile.rails-3.2.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.0.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.1.x +0 -7
- data/test/gemfiles/Gemfile.rails-4.2.x +0 -7
- data/test/gemfiles/Gemfile.rails-5.0.x +0 -6
@@ -1,27 +1,6 @@
|
|
1
1
|
require "digest/sha2"
|
2
2
|
|
3
3
|
module Authlogic
|
4
|
-
# The acts_as_authentic method has a crypto_provider option. This allows you
|
5
|
-
# to use any type of encryption you like. Just create a class with a class
|
6
|
-
# level encrypt and matches? method. See example below.
|
7
|
-
#
|
8
|
-
# === Example
|
9
|
-
#
|
10
|
-
# class MyAwesomeEncryptionMethod
|
11
|
-
# def self.encrypt(*tokens)
|
12
|
-
# # The tokens passed will be an array of objects, what type of object
|
13
|
-
# # is irrelevant, just do what you need to do with them and return a
|
14
|
-
# # single encrypted string. For example, you will most likely join all
|
15
|
-
# # of the objects into a single string and then encrypt that string.
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# def self.matches?(crypted, *tokens)
|
19
|
-
# # Return true if the crypted string matches the tokens. Depending on
|
20
|
-
# # your algorithm you might decrypt the string then compare it to the
|
21
|
-
# # token, or you might encrypt the tokens and make sure it matches the
|
22
|
-
# # crypted string, its up to you.
|
23
|
-
# end
|
24
|
-
# end
|
25
4
|
module CryptoProviders
|
26
5
|
# = Sha512
|
27
6
|
#
|
@@ -1,15 +1,44 @@
|
|
1
|
-
require
|
1
|
+
require "digest/md5"
|
2
|
+
|
3
|
+
::ActiveSupport::Deprecation.warn(
|
4
|
+
<<~EOS,
|
5
|
+
authlogic/crypto_providers/wordpress.rb is deprecated without replacement.
|
6
|
+
Yes, the entire file. Don't `require` it. Let us know ASAP if you are still
|
7
|
+
using it.
|
8
|
+
|
9
|
+
Reasons for deprecation: This file is not autoloaded by
|
10
|
+
`authlogic/crypto_providers.rb`. It's not documented. There are no tests.
|
11
|
+
So, it's likely used by a *very* small number of people, if any. It's never
|
12
|
+
had any contributions except by its original author, Jeffry Degrande, in
|
13
|
+
2009. It is unclear why it should live in the main authlogic codebase. It
|
14
|
+
could be in a separate gem, authlogic-wordpress, or it could just live in
|
15
|
+
Jeffry's codebase, if he still even needs it, in 2018, nine years later.
|
16
|
+
EOS
|
17
|
+
caller(1)
|
18
|
+
)
|
19
|
+
|
2
20
|
module Authlogic
|
3
21
|
module CryptoProviders
|
22
|
+
# Crypto provider to transition from wordpress user accounts. Written by
|
23
|
+
# Jeffry Degrande in 2009. First released in 2.1.3.
|
24
|
+
#
|
25
|
+
# Problems:
|
26
|
+
#
|
27
|
+
# - There are no tests.
|
28
|
+
# - We can't even figure out how to run this without it crashing.
|
29
|
+
# - Presumably it implements some spec, but it doesn't mention which.
|
30
|
+
# - It is not documented anywhere.
|
31
|
+
# - There is no PR associated with this, and no discussion about it could be found.
|
32
|
+
#
|
4
33
|
class Wordpress
|
5
34
|
class << self
|
6
|
-
ITOA64 =
|
35
|
+
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".freeze
|
7
36
|
|
8
37
|
def matches?(crypted, *tokens)
|
9
38
|
stretches = 1 << ITOA64.index(crypted[3, 1])
|
10
39
|
plain, salt = *tokens
|
11
40
|
hashed = Digest::MD5.digest(salt + plain)
|
12
|
-
stretches.times do
|
41
|
+
stretches.times do
|
13
42
|
hashed = Digest::MD5.digest(hashed + plain)
|
14
43
|
end
|
15
44
|
crypted[0, 12] + encode_64(hashed, 16) == crypted
|
@@ -1,4 +1,25 @@
|
|
1
1
|
module Authlogic
|
2
|
+
# The acts_as_authentic method has a crypto_provider option. This allows you
|
3
|
+
# to use any type of encryption you like. Just create a class with a class
|
4
|
+
# level encrypt and matches? method. See example below.
|
5
|
+
#
|
6
|
+
# === Example
|
7
|
+
#
|
8
|
+
# class MyAwesomeEncryptionMethod
|
9
|
+
# def self.encrypt(*tokens)
|
10
|
+
# # The tokens passed will be an array of objects, what type of object
|
11
|
+
# # is irrelevant, just do what you need to do with them and return a
|
12
|
+
# # single encrypted string. For example, you will most likely join all
|
13
|
+
# # of the objects into a single string and then encrypt that string.
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# def self.matches?(crypted, *tokens)
|
17
|
+
# # Return true if the crypted string matches the tokens. Depending on
|
18
|
+
# # your algorithm you might decrypt the string then compare it to the
|
19
|
+
# # token, or you might encrypt the tokens and make sure it matches the
|
20
|
+
# # crypted string, its up to you.
|
21
|
+
# end
|
22
|
+
# end
|
2
23
|
module CryptoProviders
|
3
24
|
autoload :MD5, "authlogic/crypto_providers/md5"
|
4
25
|
autoload :Sha1, "authlogic/crypto_providers/sha1"
|
@@ -7,5 +28,75 @@ module Authlogic
|
|
7
28
|
autoload :BCrypt, "authlogic/crypto_providers/bcrypt"
|
8
29
|
autoload :AES256, "authlogic/crypto_providers/aes256"
|
9
30
|
autoload :SCrypt, "authlogic/crypto_providers/scrypt"
|
31
|
+
# crypto_providers/wordpress.rb has never been autoloaded, and now it is
|
32
|
+
# deprecated.
|
33
|
+
|
34
|
+
# Guide users to choose a better crypto provider.
|
35
|
+
class Guidance
|
36
|
+
AES256_DEPRECATED = <<~EOS.freeze
|
37
|
+
You have selected AES256 as your authlogic crypto provider. This
|
38
|
+
choice is not suitable for password storage.
|
39
|
+
|
40
|
+
Authlogic will drop its AES256 crypto provider in the next major
|
41
|
+
version. If you're unable to transition away from AES256 please let us
|
42
|
+
know immediately.
|
43
|
+
|
44
|
+
We recommend using a one-way algorithm instead. There are many choices;
|
45
|
+
we recommend scrypt. Use the transition_from_crypto_providers option
|
46
|
+
to make this painless for your users.
|
47
|
+
EOS
|
48
|
+
BUILTIN_PROVIDER_PREFIX = "Authlogic::CryptoProviders::".freeze
|
49
|
+
NONADAPTIVE_ALGORITHM = <<~EOS.freeze
|
50
|
+
You have selected %s as your authlogic crypto provider. This algorithm
|
51
|
+
does not have any practical known attacks against it. However, there are
|
52
|
+
better choices.
|
53
|
+
|
54
|
+
Authlogic has no plans yet to deprecate this crypto provider. However,
|
55
|
+
we recommend transitioning to a more secure, adaptive hashing algorithm,
|
56
|
+
like scrypt. Adaptive algorithms are designed to slow down brute force
|
57
|
+
attacks, and over time the iteration count can be increased to make it
|
58
|
+
slower, so it remains resistant to brute-force search attacks even in
|
59
|
+
the face of increasing computation power.
|
60
|
+
|
61
|
+
Use the transition_from_crypto_providers option to make the transition
|
62
|
+
painless for your users.
|
63
|
+
EOS
|
64
|
+
VULNERABLE_ALGORITHM = <<~EOS.freeze
|
65
|
+
You have selected %s as your authlogic crypto provider. It is a poor
|
66
|
+
choice because there are known attacks against this algorithm.
|
67
|
+
|
68
|
+
Authlogic has no plans yet to deprecate this crypto provider. However,
|
69
|
+
we recommend transitioning to a secure hashing algorithm. We recommend
|
70
|
+
an adaptive algorithm, like scrypt.
|
71
|
+
|
72
|
+
Use the transition_from_crypto_providers option to make the transition
|
73
|
+
painless for your users.
|
74
|
+
EOS
|
75
|
+
|
76
|
+
def initialize(provider)
|
77
|
+
@provider = provider
|
78
|
+
end
|
79
|
+
|
80
|
+
def impart_wisdom
|
81
|
+
return unless @provider.is_a?(Class)
|
82
|
+
|
83
|
+
# We can only impart wisdom about our own built-in providers.
|
84
|
+
absolute_name = @provider.name
|
85
|
+
return unless absolute_name.start_with?(BUILTIN_PROVIDER_PREFIX)
|
86
|
+
|
87
|
+
# Inspect the string name of the provider, rather than using the
|
88
|
+
# constants in our `when` clauses. If we used the constants, we'd
|
89
|
+
# negate the benefits of the `autoload` above.
|
90
|
+
name = absolute_name.demodulize
|
91
|
+
case name
|
92
|
+
when "AES256"
|
93
|
+
::ActiveSupport::Deprecation.warn(AES256_DEPRECATED)
|
94
|
+
when "MD5", "Sha1"
|
95
|
+
warn(format(VULNERABLE_ALGORITHM, name))
|
96
|
+
when "Sha256", "Sha512"
|
97
|
+
warn(format(NONADAPTIVE_ALGORITHM, name))
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
10
101
|
end
|
11
102
|
end
|
data/lib/authlogic/i18n.rb
CHANGED
@@ -1,42 +1,48 @@
|
|
1
1
|
require "authlogic/i18n/translator"
|
2
2
|
|
3
3
|
module Authlogic
|
4
|
-
# This class allows any message in Authlogic to use internationalization. In
|
5
|
-
# versions of Authlogic each message was translated via configuration.
|
6
|
-
# the configuration and cluttered up Authlogic. So all
|
7
|
-
# out into this class. Now all messages pass
|
8
|
-
#
|
9
|
-
#
|
4
|
+
# This class allows any message in Authlogic to use internationalization. In
|
5
|
+
# earlier versions of Authlogic each message was translated via configuration.
|
6
|
+
# This cluttered up the configuration and cluttered up Authlogic. So all
|
7
|
+
# translation has been extracted out into this class. Now all messages pass
|
8
|
+
# through this class, making it much easier to implement in I18n library /
|
9
|
+
# plugin you want. Use this as a layer that sits between Authlogic and
|
10
|
+
# whatever I18n library you want to use.
|
10
11
|
#
|
11
|
-
# By default this uses the rails I18n library, if it exists. If it doesn't
|
12
|
-
# returns the default English message. The Authlogic I18n class
|
13
|
-
# rails I18n class. This is because the arguments are
|
12
|
+
# By default this uses the rails I18n library, if it exists. If it doesn't
|
13
|
+
# exist it just returns the default English message. The Authlogic I18n class
|
14
|
+
# works EXACTLY like the rails I18n class. This is because the arguments are
|
15
|
+
# delegated to this class.
|
14
16
|
#
|
15
17
|
# Here is how all messages are translated internally with Authlogic:
|
16
18
|
#
|
17
19
|
# Authlogic::I18n.t('error_messages.password_invalid', :default => "is invalid")
|
18
20
|
#
|
19
|
-
# If you use a different I18n library just replace the build-in
|
20
|
-
# with your own. For example:
|
21
|
+
# If you use a different I18n library just replace the build-in
|
22
|
+
# I18n::Translator class with your own. For example:
|
21
23
|
#
|
22
24
|
# class MyAuthlogicI18nTranslator
|
23
25
|
# def translate(key, options = {})
|
24
|
-
# # you will have key which will be something like:
|
25
|
-
# #
|
26
|
+
# # you will have key which will be something like:
|
27
|
+
# # "error_messages.password_invalid"
|
28
|
+
# # you will also have options[:default], which will be the default
|
29
|
+
# # English version of the message
|
26
30
|
# # do whatever you want here with the arguments passed to you.
|
27
31
|
# end
|
28
32
|
# end
|
29
33
|
#
|
30
34
|
# Authlogic::I18n.translator = MyAuthlogicI18nTranslator.new
|
31
35
|
#
|
32
|
-
# That it's! Here is a complete list of the keys that are passed. Just define
|
36
|
+
# That it's! Here is a complete list of the keys that are passed. Just define
|
37
|
+
# these however you wish:
|
33
38
|
#
|
34
39
|
# authlogic:
|
35
40
|
# error_messages:
|
36
41
|
# login_blank: can not be blank
|
37
42
|
# login_not_found: is not valid
|
38
43
|
# login_invalid: should use only letters, numbers, spaces, and .-_@+ please.
|
39
|
-
# consecutive_failed_logins_limit_exceeded:
|
44
|
+
# consecutive_failed_logins_limit_exceeded: >
|
45
|
+
# Consecutive failed logins limit exceeded, account is disabled.
|
40
46
|
# email_invalid: should look like an email address.
|
41
47
|
# email_invalid_international: should look like an international email address.
|
42
48
|
# password_blank: can not be blank
|
@@ -46,6 +52,7 @@ module Authlogic
|
|
46
52
|
# not_approved: Your account is not approved
|
47
53
|
# no_authentication_details: You did not provide any details for authentication.
|
48
54
|
# general_credentials_error: Login/Password combination is not valid
|
55
|
+
# session_invalid: Your session is invalid and has the following errors:
|
49
56
|
# models:
|
50
57
|
# user_session: UserSession (or whatever name you are using)
|
51
58
|
# attributes:
|
@@ -79,11 +86,11 @@ module Authlogic
|
|
79
86
|
@@translator = translator
|
80
87
|
end
|
81
88
|
|
82
|
-
# All message translation is passed to this method. The first argument is
|
83
|
-
# for the message. The second is options, see the rails I18n
|
84
|
-
# options used.
|
89
|
+
# All message translation is passed to this method. The first argument is
|
90
|
+
# the key for the message. The second is options, see the rails I18n
|
91
|
+
# library for a list of options used.
|
85
92
|
def translate(key, options = {})
|
86
|
-
translator.translate key, { :
|
93
|
+
translator.translate key, { scope: I18n.scope }.merge(options)
|
87
94
|
end
|
88
95
|
alias :t :translate
|
89
96
|
end
|
data/lib/authlogic/random.rb
CHANGED
@@ -1,34 +1,16 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
1
3
|
module Authlogic
|
2
|
-
#
|
3
|
-
# this and use it instead. SecureRandom comes with ActiveSupport. So if you are using
|
4
|
-
# this in a rails app you should have this library.
|
4
|
+
# Generates random strings using ruby's SecureRandom library.
|
5
5
|
module Random
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
(defined?(::ActiveSupport::SecureRandom) && ::ActiveSupport::SecureRandom)
|
10
|
-
|
11
|
-
if SecureRandom
|
12
|
-
def hex_token
|
13
|
-
SecureRandom.hex(64)
|
14
|
-
end
|
15
|
-
|
16
|
-
def friendly_token
|
17
|
-
# use base64url as defined by RFC4648
|
18
|
-
SecureRandom.base64(15).tr('+/=', '').strip.delete("\n")
|
19
|
-
end
|
20
|
-
else
|
21
|
-
def hex_token
|
22
|
-
Authlogic::CryptoProviders::Sha512.encrypt(Time.now.to_s + (1..10).collect { rand.to_s }.join)
|
23
|
-
end
|
24
|
-
|
25
|
-
FRIENDLY_CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
|
6
|
+
def self.hex_token
|
7
|
+
SecureRandom.hex(64)
|
8
|
+
end
|
26
9
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
10
|
+
# Returns a string in base64url format as defined by RFC-3548 and RFC-4648.
|
11
|
+
# We call this a "friendly" token because it is short and safe for URLs.
|
12
|
+
def self.friendly_token
|
13
|
+
SecureRandom.urlsafe_base64(15)
|
32
14
|
end
|
33
15
|
end
|
34
16
|
end
|
data/lib/authlogic/regex.rb
CHANGED
@@ -1,48 +1,79 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
module Authlogic
|
3
|
-
# This is a module the contains regular expressions used throughout Authlogic.
|
4
|
-
# them out into their own module is to make them
|
2
|
+
# This is a module the contains regular expressions used throughout Authlogic.
|
3
|
+
# The point of extracting them out into their own module is to make them
|
4
|
+
# easily available to you for other uses. Ex:
|
5
5
|
#
|
6
6
|
# validates_format_of :my_email_field, :with => Authlogic::Regex.email
|
7
7
|
module Regex
|
8
|
-
# A general email regular expression. It allows top level domains (TLD) to
|
9
|
-
# 24 in length. The decisions behind this regular expression
|
10
|
-
# the list of top-level domains maintained by IANA
|
11
|
-
#
|
12
|
-
# regular
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
8
|
+
# A general email regular expression. It allows top level domains (TLD) to
|
9
|
+
# be from 2 - 24 in length. The decisions behind this regular expression
|
10
|
+
# were made by analyzing the list of top-level domains maintained by IANA
|
11
|
+
# and by reading this website:
|
12
|
+
# http://www.regular-expressions.info/email.html, which is an excellent
|
13
|
+
# resource for regular expressions.
|
14
|
+
EMAIL = /
|
15
|
+
\A
|
16
|
+
[A-Z0-9_.&%+\-']+ # mailbox
|
17
|
+
@
|
18
|
+
(?:[A-Z0-9\-]+\.)+ # subdomains
|
19
|
+
(?:[A-Z]{2,25}) # TLD
|
20
|
+
\z
|
21
|
+
/ix
|
21
22
|
|
22
|
-
# A draft regular expression for internationalized email addresses.
|
23
|
-
#
|
24
|
-
# allowing specific characters for each part, it instead
|
23
|
+
# A draft regular expression for internationalized email addresses. Given
|
24
|
+
# that the standard may be in flux, this simply emulates @email_regex but
|
25
|
+
# rather than allowing specific characters for each part, it instead
|
26
|
+
# disallows the complement set of characters:
|
27
|
+
#
|
25
28
|
# - email_name_regex disallows: @[]^ !"#$()*,/:;<=>?`{|}~\ and control characters
|
26
29
|
# - domain_head_regex disallows: _%+ and all characters in email_name_regex
|
27
30
|
# - domain_tld_regex disallows: 0123456789- and all characters in domain_head_regex
|
31
|
+
#
|
28
32
|
# http://en.wikipedia.org/wiki/Email_address#Internationalization
|
29
33
|
# http://tools.ietf.org/html/rfc6530
|
30
34
|
# http://www.unicode.org/faq/idn.html
|
31
35
|
# http://ruby-doc.org/core-2.1.5/Regexp.html#class-Regexp-label-Character+Classes
|
32
36
|
# http://en.wikipedia.org/wiki/Unicode_character_property#General_Category
|
37
|
+
EMAIL_NONASCII = /
|
38
|
+
\A
|
39
|
+
[^[:cntrl:][@\[\]\^ \!"\#$\(\)*,\/:;<=>?`{|}~\\]]+ # mailbox
|
40
|
+
@
|
41
|
+
(?:[^[:cntrl:][@\[\]\^ \!\"\#$&\(\)*,\/:;<=>\?`{|}~\\_.%+']]+\.)+ # subdomains
|
42
|
+
(?:[^[:cntrl:][@\[\]\^ \!\"\#$&\(\)*,\/:;<=>\?`{|}~\\_.%+\-'0-9]]{2,25}) # TLD
|
43
|
+
\z
|
44
|
+
/x
|
45
|
+
|
46
|
+
# A simple regular expression that only allows for letters, numbers, spaces, and
|
47
|
+
# .-_@+. Just a standard login / username regular expression.
|
48
|
+
LOGIN = /\A[a-zA-Z0-9_][a-zA-Z0-9\.+\-_@ ]+\z/
|
49
|
+
|
50
|
+
# Accessing the above constants using the following methods is deprecated.
|
51
|
+
|
52
|
+
# @deprecated
|
53
|
+
def self.email
|
54
|
+
::ActiveSupport::Deprecation.warn(
|
55
|
+
"Authlogic::Regex.email is deprecated, use Authlogic::Regex::EMAIL",
|
56
|
+
caller(1)
|
57
|
+
)
|
58
|
+
EMAIL
|
59
|
+
end
|
60
|
+
|
61
|
+
# @deprecated
|
33
62
|
def self.email_nonascii
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
63
|
+
::ActiveSupport::Deprecation.warn(
|
64
|
+
"Authlogic::Regex.email_nonascii is deprecated, use Authlogic::Regex::EMAIL_NONASCII",
|
65
|
+
caller(1)
|
66
|
+
)
|
67
|
+
EMAIL_NONASCII
|
40
68
|
end
|
41
69
|
|
42
|
-
#
|
43
|
-
# regular expression.
|
70
|
+
# @deprecated
|
44
71
|
def self.login
|
45
|
-
|
72
|
+
::ActiveSupport::Deprecation.warn(
|
73
|
+
"Authlogic::Regex.login is deprecated, use Authlogic::Regex::LOGIN",
|
74
|
+
caller(1)
|
75
|
+
)
|
76
|
+
LOGIN
|
46
77
|
end
|
47
78
|
end
|
48
79
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "request_store"
|
2
2
|
|
3
3
|
module Authlogic
|
4
4
|
module Session
|
@@ -9,8 +9,11 @@ module Authlogic
|
|
9
9
|
# you are using a supported framework, Authlogic takes care of this for you.
|
10
10
|
module Activation
|
11
11
|
class NotActivatedError < ::StandardError # :nodoc:
|
12
|
-
def initialize
|
13
|
-
super(
|
12
|
+
def initialize
|
13
|
+
super(
|
14
|
+
"You must activate the Authlogic::Session::Base.controller with " \
|
15
|
+
"a controller object before creating objects"
|
16
|
+
)
|
14
17
|
end
|
15
18
|
end
|
16
19
|
|
@@ -55,15 +58,15 @@ module Authlogic
|
|
55
58
|
module InstanceMethods
|
56
59
|
# Making sure we are activated before we start creating objects
|
57
60
|
def initialize(*args)
|
58
|
-
raise NotActivatedError
|
61
|
+
raise NotActivatedError unless self.class.activated?
|
59
62
|
super
|
60
63
|
end
|
61
64
|
|
62
65
|
private
|
63
66
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
+
def controller
|
68
|
+
self.class.controller
|
69
|
+
end
|
67
70
|
end
|
68
71
|
end
|
69
72
|
end
|
@@ -1,16 +1,18 @@
|
|
1
1
|
module Authlogic
|
2
2
|
module Session
|
3
|
-
# Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not
|
4
|
-
# This is useful for the various rails
|
5
|
-
#
|
6
|
-
#
|
3
|
+
# Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not
|
4
|
+
# ActiveRecord. That's the goal here. This is useful for the various rails
|
5
|
+
# helper methods such as form_for, error_messages_for, or any method that
|
6
|
+
# expects an ActiveRecord object. The point is to disguise the object as an
|
7
|
+
# ActiveRecord object so we can take advantage of the many ActiveRecord
|
8
|
+
# tools.
|
7
9
|
module ActiveRecordTrickery
|
8
10
|
def self.included(klass)
|
9
11
|
klass.extend ActiveModel::Naming
|
10
12
|
klass.extend ActiveModel::Translation
|
11
13
|
|
12
14
|
# Support ActiveModel::Name#name for Rails versions before 4.0.
|
13
|
-
|
15
|
+
unless klass.model_name.respond_to?(:name)
|
14
16
|
ActiveModel::Name.module_eval do
|
15
17
|
alias_method :name, :to_s
|
16
18
|
end
|
@@ -21,11 +23,12 @@ module Authlogic
|
|
21
23
|
end
|
22
24
|
|
23
25
|
module ClassMethods
|
24
|
-
# How to name the class, works JUST LIKE ActiveRecord, except it uses
|
26
|
+
# How to name the class, works JUST LIKE ActiveRecord, except it uses
|
27
|
+
# the following namespace:
|
25
28
|
#
|
26
29
|
# authlogic.models.user_session
|
27
|
-
def human_name(*
|
28
|
-
I18n.t("models.#{name.underscore}",
|
30
|
+
def human_name(*)
|
31
|
+
I18n.t("models.#{name.underscore}", count: 1, default: name.humanize)
|
29
32
|
end
|
30
33
|
|
31
34
|
def i18n_scope
|
@@ -34,7 +37,8 @@ module Authlogic
|
|
34
37
|
end
|
35
38
|
|
36
39
|
module InstanceMethods
|
37
|
-
# Don't use this yourself, this is to just trick some of the helpers
|
40
|
+
# Don't use this yourself, this is to just trick some of the helpers
|
41
|
+
# since this is the method it calls.
|
38
42
|
def new_record?
|
39
43
|
new_session?
|
40
44
|
end
|
@@ -1,7 +1,18 @@
|
|
1
1
|
module Authlogic
|
2
2
|
module Session # :nodoc:
|
3
|
-
# This is the
|
4
|
-
#
|
3
|
+
# This is the most important class in Authlogic. You will inherit this class
|
4
|
+
# for your own eg. `UserSession`.
|
5
|
+
#
|
6
|
+
# Code is organized topically. Each topic is represented by a module. So, to
|
7
|
+
# learn about password-based authentication, read the `Password` module.
|
8
|
+
#
|
9
|
+
# It is common for methods (.initialize and #credentials=, for example) to
|
10
|
+
# be implemented in multiple mixins. Those methods will call `super`, so the
|
11
|
+
# order of `include`s here is important.
|
12
|
+
#
|
13
|
+
# Also, to fully understand such a method (like #credentials=) you will need
|
14
|
+
# to mentally combine all of its definitions. This is perhaps the primary
|
15
|
+
# disadvantage of topical organization using modules.
|
5
16
|
class Base
|
6
17
|
include Foundation
|
7
18
|
include Callbacks
|
@@ -15,8 +26,8 @@ module Authlogic
|
|
15
26
|
include Session
|
16
27
|
include HttpAuth
|
17
28
|
|
18
|
-
# Included in a specific order so magic states gets
|
19
|
-
# TODO: What does "magic states gets
|
29
|
+
# Included in a specific order so magic states gets run after a record is found
|
30
|
+
# TODO: What does "magic states gets run" mean? Be specific.
|
20
31
|
include Password
|
21
32
|
include UnauthorizedRecord
|
22
33
|
include MagicStates
|
@@ -25,8 +25,8 @@ module Authlogic
|
|
25
25
|
klass.class_eval do
|
26
26
|
extend Config
|
27
27
|
include InstanceMethods
|
28
|
-
validate :reset_failed_login_count, :
|
29
|
-
validate :validate_failed_logins, :
|
28
|
+
validate :reset_failed_login_count, if: :reset_failed_login_count?
|
29
|
+
validate :validate_failed_logins, if: :being_brute_force_protected?
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -73,47 +73,54 @@ module Authlogic
|
|
73
73
|
# with configuration. By default they will be banned for 2 hours. During
|
74
74
|
# that 2 hour period this method will return true.
|
75
75
|
def being_brute_force_protected?
|
76
|
-
exceeded_failed_logins_limit? &&
|
77
|
-
(
|
76
|
+
exceeded_failed_logins_limit? &&
|
77
|
+
(
|
78
|
+
failed_login_ban_for <= 0 ||
|
79
|
+
attempted_record.respond_to?(:updated_at) &&
|
80
|
+
attempted_record.updated_at >= failed_login_ban_for.seconds.ago
|
81
|
+
)
|
78
82
|
end
|
79
83
|
|
80
84
|
private
|
81
85
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
+
def exceeded_failed_logins_limit?
|
87
|
+
!attempted_record.nil? &&
|
88
|
+
attempted_record.respond_to?(:failed_login_count) &&
|
89
|
+
consecutive_failed_logins_limit > 0 &&
|
90
|
+
attempted_record.failed_login_count &&
|
91
|
+
attempted_record.failed_login_count >= consecutive_failed_logins_limit
|
92
|
+
end
|
86
93
|
|
87
|
-
|
88
|
-
|
89
|
-
|
94
|
+
def reset_failed_login_count?
|
95
|
+
exceeded_failed_logins_limit? && !being_brute_force_protected?
|
96
|
+
end
|
90
97
|
|
91
|
-
|
92
|
-
|
93
|
-
|
98
|
+
def reset_failed_login_count
|
99
|
+
attempted_record.failed_login_count = 0
|
100
|
+
end
|
94
101
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
)
|
102
|
+
def validate_failed_logins
|
103
|
+
# Clear all other error messages, as they are irrelevant at this point and can
|
104
|
+
# only provide additional information that is not needed
|
105
|
+
errors.clear
|
106
|
+
errors.add(
|
107
|
+
:base,
|
108
|
+
I18n.t(
|
109
|
+
"error_messages.consecutive_failed_logins_limit_exceeded",
|
110
|
+
default: "Consecutive failed logins limit exceeded, account has been" +
|
111
|
+
(failed_login_ban_for.zero? ? "" : " temporarily") +
|
112
|
+
" disabled."
|
107
113
|
)
|
108
|
-
|
114
|
+
)
|
115
|
+
end
|
109
116
|
|
110
|
-
|
111
|
-
|
112
|
-
|
117
|
+
def consecutive_failed_logins_limit
|
118
|
+
self.class.consecutive_failed_logins_limit
|
119
|
+
end
|
113
120
|
|
114
|
-
|
115
|
-
|
116
|
-
|
121
|
+
def failed_login_ban_for
|
122
|
+
self.class.failed_login_ban_for
|
123
|
+
end
|
117
124
|
end
|
118
125
|
end
|
119
126
|
end
|