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.

Files changed (106) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +20 -8
  4. data/.yardopts +3 -0
  5. data/Appraisals +13 -16
  6. data/Gemfile +7 -5
  7. data/Gemfile.lock +124 -130
  8. data/NEWS.md +171 -2
  9. data/README.md +99 -42
  10. data/app/controllers/clearance/passwords_controller.rb +35 -21
  11. data/app/controllers/clearance/sessions_controller.rb +17 -3
  12. data/app/controllers/clearance/users_controller.rb +10 -4
  13. data/app/mailers/clearance_mailer.rb +2 -3
  14. data/app/views/clearance_mailer/change_password.text.erb +1 -1
  15. data/app/views/layouts/application.html.erb +0 -1
  16. data/bin/setup +6 -2
  17. data/clearance.gemspec +5 -2
  18. data/config/locales/clearance.en.yml +9 -0
  19. data/gemfiles/rails_4.2.gemfile +20 -0
  20. data/gemfiles/rails_5.0.gemfile +21 -0
  21. data/gemfiles/rails_5.1.gemfile +21 -0
  22. data/gemfiles/rails_5.2.gemfile +21 -0
  23. data/lib/clearance/authentication.rb +63 -3
  24. data/lib/clearance/authorization.rb +48 -5
  25. data/lib/clearance/back_door.rb +55 -6
  26. data/lib/clearance/configuration.rb +50 -10
  27. data/lib/clearance/constraints/signed_in.rb +21 -0
  28. data/lib/clearance/constraints/signed_out.rb +12 -0
  29. data/lib/clearance/constraints.rb +12 -0
  30. data/lib/clearance/controller.rb +13 -0
  31. data/lib/clearance/default_sign_in_guard.rb +17 -0
  32. data/lib/clearance/engine.rb +18 -5
  33. data/lib/clearance/password_strategies/bcrypt.rb +16 -21
  34. data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +10 -0
  35. data/lib/clearance/password_strategies/blowfish.rb +10 -1
  36. data/lib/clearance/password_strategies/sha1.rb +9 -0
  37. data/lib/clearance/password_strategies.rb +13 -0
  38. data/lib/clearance/rack_session.rb +13 -0
  39. data/lib/clearance/rspec.rb +15 -4
  40. data/lib/clearance/session.rb +62 -13
  41. data/lib/clearance/session_status.rb +7 -0
  42. data/lib/clearance/sign_in_guard.rb +65 -0
  43. data/lib/clearance/test_unit.rb +3 -3
  44. data/lib/clearance/testing/controller_helpers.rb +57 -0
  45. data/lib/clearance/testing/deny_access_matcher.rb +36 -2
  46. data/lib/clearance/testing/helpers.rb +9 -25
  47. data/lib/clearance/testing/view_helpers.rb +32 -0
  48. data/lib/clearance/token.rb +7 -0
  49. data/lib/clearance/user.rb +183 -4
  50. data/lib/clearance/version.rb +1 -1
  51. data/lib/generators/clearance/install/install_generator.rb +28 -9
  52. data/lib/generators/clearance/install/templates/README +1 -1
  53. data/lib/generators/clearance/install/templates/clearance.rb +1 -0
  54. data/lib/generators/clearance/install/templates/db/migrate/{add_clearance_to_users.rb → add_clearance_to_users.rb.erb} +3 -3
  55. data/lib/generators/clearance/install/templates/db/migrate/{create_users.rb → create_users.rb.erb} +2 -2
  56. data/lib/generators/clearance/install/templates/user.rb.erb +3 -0
  57. data/lib/generators/clearance/routes/routes_generator.rb +23 -0
  58. data/lib/generators/clearance/routes/templates/routes.rb +7 -7
  59. data/lib/generators/clearance/specs/templates/factories/clearance.rb +2 -2
  60. data/lib/generators/clearance/specs/templates/features/clearance/user_signs_out_spec.rb.tt +1 -1
  61. data/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt +12 -3
  62. data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_in_spec.rb.tt +3 -3
  63. data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_up_spec.rb.tt +1 -1
  64. data/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt +2 -2
  65. data/lib/generators/clearance/specs/templates/support/features/clearance_helpers.rb +2 -2
  66. data/spec/acceptance/clearance_installation_spec.rb +15 -7
  67. data/spec/app_templates/app/models/rails5/user.rb +5 -0
  68. data/spec/app_templates/config/initializers/clearance.rb +2 -0
  69. data/spec/app_templates/testapp/Gemfile +1 -1
  70. data/spec/app_templates/testapp/app/controllers/home_controller.rb +5 -1
  71. data/spec/clearance/back_door_spec.rb +70 -6
  72. data/spec/clearance/session_spec.rb +4 -16
  73. data/spec/clearance/testing/controller_helpers_spec.rb +38 -0
  74. data/spec/clearance/testing/view_helpers_spec.rb +37 -0
  75. data/spec/configuration_spec.rb +79 -86
  76. data/spec/controllers/apis_controller_spec.rb +6 -2
  77. data/spec/controllers/forgeries_controller_spec.rb +12 -3
  78. data/spec/controllers/passwords_controller_spec.rb +74 -38
  79. data/spec/controllers/permissions_controller_spec.rb +13 -3
  80. data/spec/controllers/sessions_controller_spec.rb +40 -11
  81. data/spec/controllers/users_controller_spec.rb +16 -8
  82. data/spec/dummy/app/controllers/application_controller.rb +5 -1
  83. data/spec/dummy/application.rb +9 -11
  84. data/spec/factories.rb +5 -5
  85. data/spec/generators/clearance/install/install_generator_spec.rb +29 -3
  86. data/spec/generators/clearance/routes/routes_generator_spec.rb +5 -1
  87. data/spec/helpers/helper_helpers_spec.rb +10 -0
  88. data/spec/{user_spec.rb → models/user_spec.rb} +10 -1
  89. data/spec/password_strategies/blowfish_spec.rb +1 -1
  90. data/spec/requests/cookie_options_spec.rb +52 -0
  91. data/spec/requests/csrf_rotation_spec.rb +35 -0
  92. data/spec/requests/password_maintenance_spec.rb +18 -0
  93. data/spec/requests/token_expiration_spec.rb +54 -0
  94. data/spec/spec_helper.rb +22 -4
  95. data/spec/support/environment.rb +12 -0
  96. data/spec/support/generator_spec_helpers.rb +13 -1
  97. data/spec/support/http_method_shim.rb +25 -0
  98. data/spec/support/request_with_remember_token.rb +5 -0
  99. data/spec/views/view_helpers_spec.rb +10 -0
  100. metadata +69 -15
  101. data/gemfiles/rails3.2.gemfile +0 -18
  102. data/gemfiles/rails4.0.gemfile +0 -19
  103. data/gemfiles/rails4.1.gemfile +0 -18
  104. data/gemfiles/rails4.2.gemfile +0 -18
  105. data/lib/generators/clearance/install/templates/user.rb +0 -3
  106. 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
@@ -1,8 +1,19 @@
1
- require 'rspec/rails'
2
- require 'clearance/testing/deny_access_matcher'
3
- require 'clearance/testing/helpers'
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::Helpers, type: :controller
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
@@ -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 cookie_value[:value].present?
18
+ if signed_in_with_remember_token?
13
19
  Rack::Utils.set_cookie_header!(
14
20
  headers,
15
21
  remember_token_cookie,
16
- cookie_value
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
- cookies[remember_token_cookie] = user && user.remember_token
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 ||= @env['action_dispatch.cookies'] || Rack::Request.new(@env).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
- def cookie_value
110
- value = {
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
@@ -1,8 +1,8 @@
1
- require 'clearance/testing/deny_access_matcher'
2
- require 'clearance/testing/helpers'
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::Helpers
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 @controller.signed_in?
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