clearance 1.10.1 → 1.17.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of clearance might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +20 -8
- data/.yardopts +3 -0
- data/Appraisals +13 -16
- data/Gemfile +7 -5
- data/Gemfile.lock +124 -130
- data/NEWS.md +171 -2
- data/README.md +99 -42
- data/app/controllers/clearance/passwords_controller.rb +35 -21
- data/app/controllers/clearance/sessions_controller.rb +17 -3
- data/app/controllers/clearance/users_controller.rb +10 -4
- data/app/mailers/clearance_mailer.rb +2 -3
- data/app/views/clearance_mailer/change_password.text.erb +1 -1
- data/app/views/layouts/application.html.erb +0 -1
- data/bin/setup +6 -2
- data/clearance.gemspec +5 -2
- data/config/locales/clearance.en.yml +9 -0
- data/gemfiles/rails_4.2.gemfile +20 -0
- data/gemfiles/rails_5.0.gemfile +21 -0
- data/gemfiles/rails_5.1.gemfile +21 -0
- data/gemfiles/rails_5.2.gemfile +21 -0
- data/lib/clearance/authentication.rb +63 -3
- data/lib/clearance/authorization.rb +48 -5
- data/lib/clearance/back_door.rb +55 -6
- data/lib/clearance/configuration.rb +50 -10
- data/lib/clearance/constraints/signed_in.rb +21 -0
- data/lib/clearance/constraints/signed_out.rb +12 -0
- data/lib/clearance/constraints.rb +12 -0
- data/lib/clearance/controller.rb +13 -0
- data/lib/clearance/default_sign_in_guard.rb +17 -0
- data/lib/clearance/engine.rb +18 -5
- data/lib/clearance/password_strategies/bcrypt.rb +16 -21
- data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +10 -0
- data/lib/clearance/password_strategies/blowfish.rb +10 -1
- data/lib/clearance/password_strategies/sha1.rb +9 -0
- data/lib/clearance/password_strategies.rb +13 -0
- data/lib/clearance/rack_session.rb +13 -0
- data/lib/clearance/rspec.rb +15 -4
- data/lib/clearance/session.rb +62 -13
- data/lib/clearance/session_status.rb +7 -0
- data/lib/clearance/sign_in_guard.rb +65 -0
- data/lib/clearance/test_unit.rb +3 -3
- data/lib/clearance/testing/controller_helpers.rb +57 -0
- data/lib/clearance/testing/deny_access_matcher.rb +36 -2
- data/lib/clearance/testing/helpers.rb +9 -25
- data/lib/clearance/testing/view_helpers.rb +32 -0
- data/lib/clearance/token.rb +7 -0
- data/lib/clearance/user.rb +183 -4
- data/lib/clearance/version.rb +1 -1
- data/lib/generators/clearance/install/install_generator.rb +28 -9
- data/lib/generators/clearance/install/templates/README +1 -1
- data/lib/generators/clearance/install/templates/clearance.rb +1 -0
- data/lib/generators/clearance/install/templates/db/migrate/{add_clearance_to_users.rb → add_clearance_to_users.rb.erb} +3 -3
- data/lib/generators/clearance/install/templates/db/migrate/{create_users.rb → create_users.rb.erb} +2 -2
- data/lib/generators/clearance/install/templates/user.rb.erb +3 -0
- data/lib/generators/clearance/routes/routes_generator.rb +23 -0
- data/lib/generators/clearance/routes/templates/routes.rb +7 -7
- data/lib/generators/clearance/specs/templates/factories/clearance.rb +2 -2
- data/lib/generators/clearance/specs/templates/features/clearance/user_signs_out_spec.rb.tt +1 -1
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt +12 -3
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_in_spec.rb.tt +3 -3
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_up_spec.rb.tt +1 -1
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt +2 -2
- data/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb +2 -2
- data/spec/acceptance/clearance_installation_spec.rb +15 -7
- data/spec/app_templates/app/models/rails5/user.rb +5 -0
- data/spec/app_templates/config/initializers/clearance.rb +2 -0
- data/spec/app_templates/testapp/Gemfile +1 -1
- data/spec/app_templates/testapp/app/controllers/home_controller.rb +5 -1
- data/spec/clearance/back_door_spec.rb +70 -6
- data/spec/clearance/session_spec.rb +4 -16
- data/spec/clearance/testing/controller_helpers_spec.rb +38 -0
- data/spec/clearance/testing/view_helpers_spec.rb +37 -0
- data/spec/configuration_spec.rb +79 -86
- data/spec/controllers/apis_controller_spec.rb +6 -2
- data/spec/controllers/forgeries_controller_spec.rb +12 -3
- data/spec/controllers/passwords_controller_spec.rb +74 -38
- data/spec/controllers/permissions_controller_spec.rb +13 -3
- data/spec/controllers/sessions_controller_spec.rb +40 -11
- data/spec/controllers/users_controller_spec.rb +16 -8
- data/spec/dummy/app/controllers/application_controller.rb +5 -1
- data/spec/dummy/application.rb +9 -11
- data/spec/factories.rb +5 -5
- data/spec/generators/clearance/install/install_generator_spec.rb +29 -3
- data/spec/generators/clearance/routes/routes_generator_spec.rb +5 -1
- data/spec/helpers/helper_helpers_spec.rb +10 -0
- data/spec/{user_spec.rb → models/user_spec.rb} +10 -1
- data/spec/password_strategies/blowfish_spec.rb +1 -1
- data/spec/requests/cookie_options_spec.rb +52 -0
- data/spec/requests/csrf_rotation_spec.rb +35 -0
- data/spec/requests/password_maintenance_spec.rb +18 -0
- data/spec/requests/token_expiration_spec.rb +54 -0
- data/spec/spec_helper.rb +22 -4
- data/spec/support/environment.rb +12 -0
- data/spec/support/generator_spec_helpers.rb +13 -1
- data/spec/support/http_method_shim.rb +25 -0
- data/spec/support/request_with_remember_token.rb +5 -0
- data/spec/views/view_helpers_spec.rb +10 -0
- metadata +69 -15
- data/gemfiles/rails3.2.gemfile +0 -18
- data/gemfiles/rails4.0.gemfile +0 -19
- data/gemfiles/rails4.1.gemfile +0 -18
- data/gemfiles/rails4.2.gemfile +0 -18
- data/lib/generators/clearance/install/templates/user.rb +0 -3
- data/spec/clearance/testing/helpers_spec.rb +0 -38
@@ -1,5 +1,6 @@
|
|
1
1
|
module Clearance
|
2
2
|
module PasswordStrategies
|
3
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies` gem
|
3
4
|
module BCryptMigrationFromSHA1
|
4
5
|
DEPRECATION_MESSAGE = "[DEPRECATION] The BCryptMigrationFromSha1 " \
|
5
6
|
"password strategy has been deprecated and will be removed from " \
|
@@ -8,6 +9,7 @@ module Clearance
|
|
8
9
|
"strategy, add clearance-deprecated_password_strategies to your " \
|
9
10
|
"Gemfile."
|
10
11
|
|
12
|
+
# @api private
|
11
13
|
class BCryptUser
|
12
14
|
include Clearance::PasswordStrategies::BCrypt
|
13
15
|
|
@@ -18,6 +20,7 @@ module Clearance
|
|
18
20
|
delegate :encrypted_password, :encrypted_password=, to: :@user
|
19
21
|
end
|
20
22
|
|
23
|
+
# @api private
|
21
24
|
class SHA1User
|
22
25
|
include Clearance::PasswordStrategies::SHA1
|
23
26
|
|
@@ -28,11 +31,15 @@ module Clearance
|
|
28
31
|
delegate :salt, :salt=, :encrypted_password, :encrypted_password=, to: :@user
|
29
32
|
end
|
30
33
|
|
34
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
35
|
+
# gem
|
31
36
|
def authenticated?(password)
|
32
37
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
33
38
|
authenticated_with_sha1?(password) || authenticated_with_bcrypt?(password)
|
34
39
|
end
|
35
40
|
|
41
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
42
|
+
# gem
|
36
43
|
def password=(new_password)
|
37
44
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
38
45
|
@password = new_password
|
@@ -41,6 +48,7 @@ module Clearance
|
|
41
48
|
|
42
49
|
private
|
43
50
|
|
51
|
+
# @api private
|
44
52
|
def authenticated_with_bcrypt?(password)
|
45
53
|
begin
|
46
54
|
BCryptUser.new(self).authenticated? password
|
@@ -49,6 +57,7 @@ module Clearance
|
|
49
57
|
end
|
50
58
|
end
|
51
59
|
|
60
|
+
# @api private
|
52
61
|
def authenticated_with_sha1?(password)
|
53
62
|
if sha1_password?
|
54
63
|
if SHA1User.new(self).authenticated? password
|
@@ -59,6 +68,7 @@ module Clearance
|
|
59
68
|
end
|
60
69
|
end
|
61
70
|
|
71
|
+
# @api private
|
62
72
|
def sha1_password?
|
63
73
|
self.encrypted_password =~ /^[a-f0-9]{40}$/
|
64
74
|
end
|
@@ -3,6 +3,7 @@ require 'base64'
|
|
3
3
|
|
4
4
|
module Clearance
|
5
5
|
module PasswordStrategies
|
6
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies` gem
|
6
7
|
module Blowfish
|
7
8
|
DEPRECATION_MESSAGE = "[DEPRECATION] The Blowfish password strategy " \
|
8
9
|
"has been deprecated and will be removed from Clearance 2.0. BCrypt " \
|
@@ -10,11 +11,15 @@ module Clearance
|
|
10
11
|
"provide your own. To continue using this strategy add " \
|
11
12
|
"clearance-deprecated_password_strategies to your Gemfile."
|
12
13
|
|
14
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
15
|
+
# gem
|
13
16
|
def authenticated?(password)
|
14
17
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
15
18
|
encrypted_password == encrypt(password)
|
16
19
|
end
|
17
20
|
|
21
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
22
|
+
# gem
|
18
23
|
def password=(new_password)
|
19
24
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
20
25
|
@password = new_password
|
@@ -27,23 +32,27 @@ module Clearance
|
|
27
32
|
|
28
33
|
protected
|
29
34
|
|
35
|
+
# @api private
|
30
36
|
def encrypt(string)
|
31
37
|
generate_hash("--#{salt}--#{string}--")
|
32
38
|
end
|
33
39
|
|
40
|
+
# @api private
|
34
41
|
def generate_hash(string)
|
35
42
|
cipher = OpenSSL::Cipher::Cipher.new('bf-cbc').encrypt
|
36
|
-
cipher.key = Digest::SHA256.digest(salt)
|
43
|
+
cipher.key = Digest::SHA256.digest(salt).first(16)
|
37
44
|
hash = cipher.update(string) << cipher.final
|
38
45
|
Base64.encode64(hash).encode('utf-8')
|
39
46
|
end
|
40
47
|
|
48
|
+
# @api private
|
41
49
|
def initialize_salt_if_necessary
|
42
50
|
if salt.blank?
|
43
51
|
self.salt = generate_salt
|
44
52
|
end
|
45
53
|
end
|
46
54
|
|
55
|
+
# @api private
|
47
56
|
def generate_salt
|
48
57
|
Base64.encode64(SecureRandom.hex(20)).encode('utf-8')
|
49
58
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Clearance
|
2
2
|
module PasswordStrategies
|
3
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies` gem
|
3
4
|
module SHA1
|
4
5
|
require 'digest/sha1'
|
5
6
|
|
@@ -11,11 +12,15 @@ module Clearance
|
|
11
12
|
|
12
13
|
extend ActiveSupport::Concern
|
13
14
|
|
15
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
16
|
+
# gem
|
14
17
|
def authenticated?(password)
|
15
18
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
16
19
|
encrypted_password == encrypt(password)
|
17
20
|
end
|
18
21
|
|
22
|
+
# @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
|
23
|
+
# gem
|
19
24
|
def password=(new_password)
|
20
25
|
warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
|
21
26
|
@password = new_password
|
@@ -28,20 +33,24 @@ module Clearance
|
|
28
33
|
|
29
34
|
private
|
30
35
|
|
36
|
+
# @api private
|
31
37
|
def encrypt(string)
|
32
38
|
generate_hash "--#{salt}--#{string}--"
|
33
39
|
end
|
34
40
|
|
41
|
+
# @api private
|
35
42
|
def generate_hash(string)
|
36
43
|
Digest::SHA1.hexdigest(string).encode 'UTF-8'
|
37
44
|
end
|
38
45
|
|
46
|
+
# @api private
|
39
47
|
def initialize_salt_if_necessary
|
40
48
|
if salt.blank?
|
41
49
|
self.salt = generate_salt
|
42
50
|
end
|
43
51
|
end
|
44
52
|
|
53
|
+
# @api private
|
45
54
|
def generate_salt
|
46
55
|
SecureRandom.hex(20).encode('UTF-8')
|
47
56
|
end
|
@@ -1,4 +1,17 @@
|
|
1
1
|
module Clearance
|
2
|
+
# Control how users are authenticated and how passwords are stored.
|
3
|
+
#
|
4
|
+
# The default password strategy is {Clearance::PasswordStrategies::BCrypt},
|
5
|
+
# but this can be overridden in {Clearance::Configuration}.
|
6
|
+
#
|
7
|
+
# You can supply your own password strategy by implementing a module that
|
8
|
+
# responds to the proper interface methods. Once this module is configured as
|
9
|
+
# your password strategy, Clearance will mix it into your Clearance User
|
10
|
+
# class. Thus, your module can access any methods or attributes on User.
|
11
|
+
#
|
12
|
+
# Password strategies need to respond to `authenticated?(password)` and
|
13
|
+
# `password=(new_password)`. For an example of how to implement these methods,
|
14
|
+
# see {Clearance::PasswordStrategies::BCrypt}.
|
2
15
|
module PasswordStrategies
|
3
16
|
autoload :BCrypt, 'clearance/password_strategies/bcrypt'
|
4
17
|
autoload :BCryptMigrationFromSHA1,
|
@@ -1,9 +1,22 @@
|
|
1
1
|
module Clearance
|
2
|
+
# Rack middleware that manages the Clearance {Session}. This middleware is
|
3
|
+
# automatically mounted by the Clearance {Engine}.
|
4
|
+
#
|
5
|
+
# * maintains the session cookie specified by your {Configuration}.
|
6
|
+
# * exposes previously cookied sessions to Clearance and your app at
|
7
|
+
# `request.env[:clearance]`, which {Authentication#current_user} pulls the
|
8
|
+
# user from.
|
9
|
+
#
|
10
|
+
# @see Session
|
11
|
+
# @see Configuration#cookie_name
|
12
|
+
#
|
2
13
|
class RackSession
|
3
14
|
def initialize(app)
|
4
15
|
@app = app
|
5
16
|
end
|
6
17
|
|
18
|
+
# Reads previously existing sessions from a cookie and maintains the cookie
|
19
|
+
# on each response.
|
7
20
|
def call(env)
|
8
21
|
session = Clearance::Session.new(env)
|
9
22
|
env[:clearance] = session
|
data/lib/clearance/rspec.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
1
|
+
require "rspec/rails"
|
2
|
+
require "clearance/testing/deny_access_matcher"
|
3
|
+
require "clearance/testing/controller_helpers"
|
4
|
+
require "clearance/testing/view_helpers"
|
4
5
|
|
5
6
|
RSpec.configure do |config|
|
6
7
|
config.include Clearance::Testing::Matchers, type: :controller
|
7
|
-
config.include Clearance::Testing::
|
8
|
+
config.include Clearance::Testing::ControllerHelpers, type: :controller
|
9
|
+
config.include Clearance::Testing::ViewHelpers, type: :view
|
10
|
+
config.include Clearance::Testing::ViewHelpers, type: :helper
|
11
|
+
|
12
|
+
config.before(:each, type: :view) do
|
13
|
+
view.extend Clearance::Testing::ViewHelpers::CurrentUser
|
14
|
+
end
|
15
|
+
|
16
|
+
config.before(:each, type: :helper) do
|
17
|
+
view.extend Clearance::Testing::ViewHelpers::CurrentUser
|
18
|
+
end
|
8
19
|
end
|
data/lib/clearance/session.rb
CHANGED
@@ -1,23 +1,34 @@
|
|
1
1
|
require 'clearance/default_sign_in_guard'
|
2
2
|
|
3
3
|
module Clearance
|
4
|
+
# Represents a clearance session, ultimately persisted in
|
5
|
+
# `request.env[:clearance]` by {RackSession}.
|
4
6
|
class Session
|
7
|
+
# @param env The current rack environment
|
5
8
|
def initialize(env)
|
6
9
|
@env = env
|
7
10
|
@current_user = nil
|
8
11
|
@cookies = nil
|
9
12
|
end
|
10
13
|
|
14
|
+
# Called by {RackSession} to add the Clearance session cookie to a response.
|
15
|
+
#
|
16
|
+
# @return [void]
|
11
17
|
def add_cookie_to_headers(headers)
|
12
|
-
if
|
18
|
+
if signed_in_with_remember_token?
|
13
19
|
Rack::Utils.set_cookie_header!(
|
14
20
|
headers,
|
15
21
|
remember_token_cookie,
|
16
|
-
|
22
|
+
cookie_options.merge(
|
23
|
+
value: current_user.remember_token,
|
24
|
+
),
|
17
25
|
)
|
18
26
|
end
|
19
27
|
end
|
20
28
|
|
29
|
+
# The current user represented by this session.
|
30
|
+
#
|
31
|
+
# @return [User, nil]
|
21
32
|
def current_user
|
22
33
|
if remember_token.present?
|
23
34
|
@current_user ||= user_from_remember_token(remember_token)
|
@@ -26,12 +37,28 @@ module Clearance
|
|
26
37
|
@current_user
|
27
38
|
end
|
28
39
|
|
40
|
+
# Sign the provided user in, if approved by the configured sign in guards.
|
41
|
+
# If the sign in guard stack returns {SuccessStatus}, the {#current_user}
|
42
|
+
# will be set and then remember token cookie will be set to the user's
|
43
|
+
# remember token. If the stack returns {FailureStatus}, {#current_user} will
|
44
|
+
# be nil.
|
45
|
+
#
|
46
|
+
# In either event, the resulting status will be yielded to a provided block,
|
47
|
+
# if provided. See {SessionsController#create} for an example of how this
|
48
|
+
# can be used.
|
49
|
+
#
|
50
|
+
# @param [User] user
|
51
|
+
# @yieldparam [SuccessStatus,FailureStatus] status Result of the sign in
|
52
|
+
# operation.
|
53
|
+
# @return [void]
|
29
54
|
def sign_in(user, &block)
|
30
55
|
@current_user = user
|
31
56
|
status = run_sign_in_stack
|
32
57
|
|
33
58
|
if status.success?
|
34
|
-
|
59
|
+
# Sign in succeeded, and when {RackSession} is run and calls
|
60
|
+
# {#add_cookie_to_headers} it will set the cookie with the
|
61
|
+
# remember_token for the current_user
|
35
62
|
else
|
36
63
|
@current_user = nil
|
37
64
|
end
|
@@ -41,6 +68,13 @@ module Clearance
|
|
41
68
|
end
|
42
69
|
end
|
43
70
|
|
71
|
+
# Invalidates the users remember token and removes the remember token cookie
|
72
|
+
# from the store. The invalidation of the remember token causes any other
|
73
|
+
# sessions that are signed in from other locations to also be invalidated on
|
74
|
+
# their next request. This is because all Clearance sessions for a given
|
75
|
+
# user share a remember token.
|
76
|
+
#
|
77
|
+
# @return [void]
|
44
78
|
def sign_out
|
45
79
|
if signed_in?
|
46
80
|
current_user.reset_remember_token!
|
@@ -50,24 +84,33 @@ module Clearance
|
|
50
84
|
cookies.delete remember_token_cookie
|
51
85
|
end
|
52
86
|
|
87
|
+
# True if {#current_user} is set.
|
88
|
+
#
|
89
|
+
# @return [Boolean]
|
53
90
|
def signed_in?
|
54
91
|
current_user.present?
|
55
92
|
end
|
56
93
|
|
94
|
+
# True if {#current_user} is not set
|
95
|
+
#
|
96
|
+
# @return [Boolean]
|
57
97
|
def signed_out?
|
58
98
|
! signed_in?
|
59
99
|
end
|
60
100
|
|
61
101
|
private
|
62
102
|
|
103
|
+
# @api private
|
63
104
|
def cookies
|
64
|
-
@cookies ||=
|
105
|
+
@cookies ||= ActionDispatch::Request.new(@env).cookie_jar
|
65
106
|
end
|
66
107
|
|
108
|
+
# @api private
|
67
109
|
def remember_token
|
68
110
|
cookies[remember_token_cookie]
|
69
111
|
end
|
70
112
|
|
113
|
+
# @api private
|
71
114
|
def remember_token_expires
|
72
115
|
if expires_configuration.arity == 1
|
73
116
|
expires_configuration.call(cookies)
|
@@ -80,23 +123,33 @@ module Clearance
|
|
80
123
|
end
|
81
124
|
end
|
82
125
|
|
126
|
+
# @api private
|
127
|
+
def signed_in_with_remember_token?
|
128
|
+
current_user&.remember_token
|
129
|
+
end
|
130
|
+
|
131
|
+
# @api private
|
83
132
|
def remember_token_cookie
|
84
133
|
Clearance.configuration.cookie_name.freeze
|
85
134
|
end
|
86
135
|
|
136
|
+
# @api private
|
87
137
|
def expires_configuration
|
88
138
|
Clearance.configuration.cookie_expiration
|
89
139
|
end
|
90
140
|
|
141
|
+
# @api private
|
91
142
|
def user_from_remember_token(token)
|
92
143
|
Clearance.configuration.user_model.where(remember_token: token).first
|
93
144
|
end
|
94
145
|
|
146
|
+
# @api private
|
95
147
|
def run_sign_in_stack
|
96
148
|
@stack ||= initialize_sign_in_guard_stack
|
97
149
|
@stack.call
|
98
150
|
end
|
99
151
|
|
152
|
+
# @api private
|
100
153
|
def initialize_sign_in_guard_stack
|
101
154
|
default_guard = DefaultSignInGuard.new(self)
|
102
155
|
guards = Clearance.configuration.sign_in_guards
|
@@ -106,20 +159,16 @@ module Clearance
|
|
106
159
|
end
|
107
160
|
end
|
108
161
|
|
109
|
-
|
110
|
-
|
162
|
+
# @api private
|
163
|
+
def cookie_options
|
164
|
+
{
|
165
|
+
domain: Clearance.configuration.cookie_domain,
|
111
166
|
expires: remember_token_expires,
|
112
167
|
httponly: Clearance.configuration.httponly,
|
113
168
|
path: Clearance.configuration.cookie_path,
|
114
169
|
secure: Clearance.configuration.secure_cookie,
|
115
|
-
value: remember_token
|
170
|
+
value: remember_token,
|
116
171
|
}
|
117
|
-
|
118
|
-
if Clearance.configuration.cookie_domain.present?
|
119
|
-
value[:domain] = Clearance.configuration.cookie_domain
|
120
|
-
end
|
121
|
-
|
122
|
-
value
|
123
172
|
end
|
124
173
|
end
|
125
174
|
end
|
@@ -1,17 +1,24 @@
|
|
1
1
|
module Clearance
|
2
|
+
# Indicates a user was successfully signed in, passing all {SignInGuard}s.
|
2
3
|
class SuccessStatus
|
4
|
+
# Is true, indicating that the sign in was successful.
|
3
5
|
def success?
|
4
6
|
true
|
5
7
|
end
|
6
8
|
end
|
7
9
|
|
10
|
+
# Indicates a failure in the {SignInGuard} stack which prevented successful
|
11
|
+
# sign in.
|
8
12
|
class FailureStatus
|
13
|
+
# The reason the sign in failed.
|
9
14
|
attr_reader :failure_message
|
10
15
|
|
16
|
+
# @param [String] failure_message The reason the sign in failed.
|
11
17
|
def initialize(failure_message)
|
12
18
|
@failure_message = failure_message
|
13
19
|
end
|
14
20
|
|
21
|
+
# Is false, indicating that the sign in was unsuccessful.
|
15
22
|
def success?
|
16
23
|
false
|
17
24
|
end
|
@@ -1,20 +1,83 @@
|
|
1
1
|
require 'clearance/session_status'
|
2
2
|
|
3
3
|
module Clearance
|
4
|
+
# The base class for {DefaultSignInGuard} and all custom sign in guards.
|
5
|
+
#
|
6
|
+
# Sign in guards provide you with fine-grained control over the process of
|
7
|
+
# signing in a user. Each guard is run in order and can do one of the
|
8
|
+
# following:
|
9
|
+
#
|
10
|
+
# * Fail the sign in process
|
11
|
+
# * Call the next guard in the stack
|
12
|
+
# * Short circuit all remaining guards, declaring sign in successfull.
|
13
|
+
#
|
14
|
+
# Sign In Guards could be used, for instance, to require that a user confirm
|
15
|
+
# their email address before being allowed to sign in.
|
16
|
+
#
|
17
|
+
# # in config/initializers/clearance.rb
|
18
|
+
# Clearance.configure do |config|
|
19
|
+
# config.sign_in_guards = [ConfirmationGuard]
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# # in lib/guards/confirmation_guard.rb
|
23
|
+
# class ConfirmationGuard < Clearance::SignInGuard
|
24
|
+
# def call
|
25
|
+
# if signed_in? && current_user.email_confirmed?
|
26
|
+
# next_guard
|
27
|
+
# else
|
28
|
+
# failure("You must confirm your email address.")
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# Calling `success` or `failure` in any guard short circuits all of the
|
34
|
+
# remaining guards in the stack. In most cases, you will want to either call
|
35
|
+
# `failure` or `next_guard`. The {DefaultSignInGuard} will always be the final
|
36
|
+
# guard called and will handle calling `success` if appropriate.
|
37
|
+
#
|
38
|
+
# The stack is designed such that calling `call` will eventually return
|
39
|
+
# {SuccessStatus} or {FailureStatus}, thus halting the chain.
|
4
40
|
class SignInGuard
|
41
|
+
# Creates an instance of a sign in guard.
|
42
|
+
#
|
43
|
+
# This is called by {Session} automatically using the array of guards
|
44
|
+
# configured in {Configuration#sign_in_guards} and the {DefaultSignInGuard}.
|
45
|
+
# There is no reason for users of Clearance to concern themselves with the
|
46
|
+
# initialization of each guard or the stack as a whole.
|
47
|
+
#
|
48
|
+
# @param [Session] session The current clearance session
|
49
|
+
# @param [[SignInGuard]] stack The sign in guards that come after this
|
50
|
+
# guard in the stack
|
5
51
|
def initialize(session, stack = [])
|
6
52
|
@session = session
|
7
53
|
@stack = stack
|
8
54
|
end
|
9
55
|
|
56
|
+
# Indicates the entire sign in operation is successful and that no further
|
57
|
+
# guards should be run.
|
58
|
+
#
|
59
|
+
# In most cases your guards will want to delegate this responsibility to the
|
60
|
+
# {DefaultSignInGuard}, allowing the entire stack to execute. In that case,
|
61
|
+
# your custom guard would likely want to call `next_guard` instead.
|
62
|
+
#
|
63
|
+
# @return [SuccessStatus]
|
10
64
|
def success
|
11
65
|
SuccessStatus.new
|
12
66
|
end
|
13
67
|
|
68
|
+
# Indicates this guard failed, and the entire sign in process should fail as
|
69
|
+
# a result.
|
70
|
+
#
|
71
|
+
# @param [String] message The reason the guard failed.
|
72
|
+
# @return [FailureStatus]
|
14
73
|
def failure(message)
|
15
74
|
FailureStatus.new(message)
|
16
75
|
end
|
17
76
|
|
77
|
+
# Passes off responsibility for determining success or failure to the next
|
78
|
+
# guard in the stack.
|
79
|
+
#
|
80
|
+
# @return [SuccessStatus, FailureStatus]
|
18
81
|
def next_guard
|
19
82
|
stack.call
|
20
83
|
end
|
@@ -23,10 +86,12 @@ module Clearance
|
|
23
86
|
|
24
87
|
attr_reader :stack, :session
|
25
88
|
|
89
|
+
# True if there is a currently a user stored in the clearance environment.
|
26
90
|
def signed_in?
|
27
91
|
session.signed_in?
|
28
92
|
end
|
29
93
|
|
94
|
+
# The user currently stored in the clearance environment.
|
30
95
|
def current_user
|
31
96
|
session.current_user
|
32
97
|
end
|
data/lib/clearance/test_unit.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "clearance/testing/deny_access_matcher"
|
2
|
+
require "clearance/testing/controller_helpers"
|
3
3
|
|
4
4
|
ActionController::TestCase.extend Clearance::Testing::Matchers
|
5
5
|
|
6
6
|
class ActionController::TestCase
|
7
|
-
include Clearance::Testing::
|
7
|
+
include Clearance::Testing::ControllerHelpers
|
8
8
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Testing
|
3
|
+
# Provides helpers to your controller specs.
|
4
|
+
# These are typically used in tests by requiring `clearance/rspec` or
|
5
|
+
# `clearance/test_unit` as appropriate in your `rails_helper.rb` or
|
6
|
+
# `test_helper.rb` files.
|
7
|
+
module ControllerHelpers
|
8
|
+
# @api private
|
9
|
+
def setup_controller_request_and_response
|
10
|
+
super
|
11
|
+
@request.env[:clearance] = Clearance::Session.new(@request.env)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Signs in a user that is created using FactoryGirl.
|
15
|
+
# The factory name is derrived from your `user_class` Clearance
|
16
|
+
# configuration.
|
17
|
+
#
|
18
|
+
# @raise [RuntimeError] if FactoryGirl is not defined.
|
19
|
+
def sign_in
|
20
|
+
constructor = factory_module("sign_in")
|
21
|
+
|
22
|
+
factory = Clearance.configuration.user_model.to_s.underscore.to_sym
|
23
|
+
sign_in_as constructor.create(factory)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Signs in the provided user.
|
27
|
+
#
|
28
|
+
# @return user
|
29
|
+
def sign_in_as(user)
|
30
|
+
@request.env[:clearance].sign_in(user)
|
31
|
+
user
|
32
|
+
end
|
33
|
+
|
34
|
+
# Signs out a user that may be signed in.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
def sign_out
|
38
|
+
@request.env[:clearance].sign_out
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determines the appropriate factory library
|
42
|
+
#
|
43
|
+
# @api private
|
44
|
+
# @raise [RuntimeError] if both FactoryGirl and FactoryBot are not
|
45
|
+
# defined.
|
46
|
+
def factory_module(provider)
|
47
|
+
if defined?(FactoryBot)
|
48
|
+
FactoryBot
|
49
|
+
elsif defined?(FactoryGirl)
|
50
|
+
FactoryGirl
|
51
|
+
else
|
52
|
+
raise("Clearance's `#{provider}` helper requires factory_bot")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -1,10 +1,40 @@
|
|
1
1
|
module Clearance
|
2
2
|
module Testing
|
3
|
+
# Provides matchers to be used in your controller specs.
|
4
|
+
# These are typically exposed to your controller specs by
|
5
|
+
# requiring `clearance/rspec` or `clearance/test_unit` as
|
6
|
+
# appropriate in your `rails_helper.rb` or `test_helper.rb`
|
7
|
+
# files.
|
3
8
|
module Matchers
|
9
|
+
# The `deny_access` matcher is used to assert that a
|
10
|
+
# request is denied access by clearance.
|
11
|
+
# @option opts [String] :flash The expected flash notice message. Defaults
|
12
|
+
# to nil, which means the flash will not be checked.
|
13
|
+
# @option opts [String] :redirect The expected redirect url. Defaults to
|
14
|
+
# `'/'` if signed in or the `sign_in_url` if signed out.
|
15
|
+
#
|
16
|
+
# class PostsController < ActionController::Base
|
17
|
+
# before_action :require_login
|
18
|
+
#
|
19
|
+
# def index
|
20
|
+
# @posts = Post.all
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# describe PostsController do
|
25
|
+
# describe "#index" do
|
26
|
+
# it "denies access to users not signed in" do
|
27
|
+
# get :index
|
28
|
+
#
|
29
|
+
# expect(controller).to deny_access
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
4
33
|
def deny_access(opts = {})
|
5
34
|
DenyAccessMatcher.new(self, opts)
|
6
35
|
end
|
7
36
|
|
37
|
+
# @api private
|
8
38
|
class DenyAccessMatcher
|
9
39
|
attr_reader :failure_message, :failure_message_when_negated
|
10
40
|
|
@@ -37,13 +67,17 @@ module Clearance
|
|
37
67
|
private
|
38
68
|
|
39
69
|
def denied_access_url
|
40
|
-
if
|
41
|
-
|
70
|
+
if clearance_session.signed_in?
|
71
|
+
Clearance.configuration.redirect_url
|
42
72
|
else
|
43
73
|
@controller.sign_in_url
|
44
74
|
end
|
45
75
|
end
|
46
76
|
|
77
|
+
def clearance_session
|
78
|
+
@controller.request.env[:clearance]
|
79
|
+
end
|
80
|
+
|
47
81
|
def flash_notice
|
48
82
|
@controller.flash[:notice]
|
49
83
|
end
|