clearance 1.11.0 → 1.12.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/.yardopts +3 -0
  4. data/Gemfile.lock +60 -60
  5. data/NEWS.md +17 -0
  6. data/config/locales/clearance.en.yml +1 -0
  7. data/lib/clearance/authentication.rb +49 -0
  8. data/lib/clearance/authorization.rb +44 -1
  9. data/lib/clearance/back_door.rb +1 -0
  10. data/lib/clearance/configuration.rb +2 -1
  11. data/lib/clearance/constraints.rb +12 -0
  12. data/lib/clearance/constraints/signed_in.rb +4 -0
  13. data/lib/clearance/constraints/signed_out.rb +2 -0
  14. data/lib/clearance/controller.rb +13 -0
  15. data/lib/clearance/default_sign_in_guard.rb +17 -0
  16. data/lib/clearance/engine.rb +16 -0
  17. data/lib/clearance/password_strategies/bcrypt.rb +3 -2
  18. data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +9 -0
  19. data/lib/clearance/password_strategies/blowfish.rb +8 -0
  20. data/lib/clearance/password_strategies/sha1.rb +8 -0
  21. data/lib/clearance/rack_session.rb +13 -0
  22. data/lib/clearance/session.rb +45 -0
  23. data/lib/clearance/session_status.rb +7 -0
  24. data/lib/clearance/sign_in_guard.rb +65 -0
  25. data/lib/clearance/testing/controller_helpers.rb +10 -1
  26. data/lib/clearance/testing/deny_access_matcher.rb +30 -0
  27. data/lib/clearance/testing/view_helpers.rb +1 -1
  28. data/lib/clearance/token.rb +7 -0
  29. data/lib/clearance/user.rb +159 -0
  30. data/lib/clearance/version.rb +1 -1
  31. data/lib/generators/clearance/install/install_generator.rb +1 -1
  32. data/lib/generators/clearance/routes/routes_generator.rb +15 -0
  33. data/lib/generators/clearance/routes/templates/routes.rb +10 -10
  34. data/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt +1 -1
  35. data/spec/acceptance/clearance_installation_spec.rb +2 -1
  36. data/spec/controllers/permissions_controller_spec.rb +6 -0
  37. metadata +3 -3
@@ -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,7 +1,11 @@
1
1
  module Clearance
2
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.
3
7
  module ControllerHelpers
4
- # @private
8
+ # @api private
5
9
  def setup_controller_request_and_response
6
10
  super
7
11
  @request.env[:clearance] = Clearance::Session.new(@request.env)
@@ -10,6 +14,7 @@ module Clearance
10
14
  # Signs in a user that is created using FactoryGirl.
11
15
  # The factory name is derrived from your `user_class` Clearance
12
16
  # configuration.
17
+ #
13
18
  # @raise [RuntimeError] if FactoryGirl is not defined.
14
19
  def sign_in
15
20
  unless defined?(FactoryGirl)
@@ -21,12 +26,16 @@ module Clearance
21
26
  end
22
27
 
23
28
  # Signs in the provided user.
29
+ #
30
+ # @return user
24
31
  def sign_in_as(user)
25
32
  @controller.sign_in user
26
33
  user
27
34
  end
28
35
 
29
36
  # Signs out a user that may be signed in.
37
+ #
38
+ # @return [void]
30
39
  def sign_out
31
40
  @controller.sign_out
32
41
  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
 
@@ -15,7 +15,7 @@ module Clearance
15
15
  view.current_user = user
16
16
  end
17
17
 
18
- # @private
18
+ # @api private
19
19
  module CurrentUser
20
20
  attr_accessor :current_user
21
21
 
@@ -1,5 +1,12 @@
1
1
  module Clearance
2
+ # Random token used for password reset tokens and password reset tokens.
3
+ # Clearance tokens are also public API and are inteded to be used anywhere you
4
+ # need a random token to correspond to a given user (e.g. you added an email
5
+ # confirmation token).
2
6
  class Token
7
+ # Generate a new random, 20 byte hex token.
8
+ #
9
+ # @return [String]
3
10
  def self.new
4
11
  SecureRandom.hex(20).encode('UTF-8')
5
12
  end
@@ -3,6 +3,102 @@ require 'email_validator'
3
3
  require 'clearance/token'
4
4
 
5
5
  module Clearance
6
+ # Required to be included in your configued user class, which is `User` by
7
+ # default, but can be changed with {Configuration#user_model=}.
8
+ #
9
+ # class User
10
+ # include Clearance::User
11
+ #
12
+ # # ...
13
+ # end
14
+ #
15
+ # This will also include methods exposed by your password strategy, which can
16
+ # be configured with {Configuration#password_strategy=}. By default, this is
17
+ # {PasswordStrategies::BCrypt}.
18
+ #
19
+ # ## Validations
20
+ #
21
+ # These validations are added to the class that the {User} module is mixed
22
+ # into.
23
+ #
24
+ # * If {#email_optional?} is false, {#email} is validated for presence,
25
+ # uniqueness and email format (using the `email_validator` gem in strict
26
+ # mode).
27
+ # * If {#skip_password_validation?} is false, {#password} is validated
28
+ # for presence.
29
+ #
30
+ # ## Callbacks
31
+ #
32
+ # * {#normalize_email} will be called on `before_validation`
33
+ # * {#generate_remember_token} will be called on `before_create`
34
+ #
35
+ # @!attribute email
36
+ # @return [String] The user's email.
37
+ #
38
+ # @!attribute encrypted_password
39
+ # @return [String] The user's encrypted password.
40
+ #
41
+ # @!attribute remember_token
42
+ # @return [String] The value used to identify this user in their {Session}
43
+ # cookie.
44
+ #
45
+ # @!attribute confirmation_token
46
+ # @return [String] The value used to identify this user in the password
47
+ # reset link.
48
+ #
49
+ # @!attribute password_changing
50
+ # @return [String] Transient (non-persisted) attribute that is set to
51
+ # `true` when {#update_password} is called. This value is read by
52
+ # {#skip_password_validation?} to determine if password validations need
53
+ # to be run.
54
+ #
55
+ # @!attribute [r] password
56
+ # @return [String] Transient (non-persisted) attribute that is set when
57
+ # updating a user's password. Only the {#encrypted_password} is persisted.
58
+ #
59
+ # @!method password=
60
+ # Sets the user's encrypted_password by using the configured Password
61
+ # Strategy's `password=` method. By default, this will be
62
+ # {PasswordStrategies::BCrypt#password=}, but can be changed with
63
+ # {Configuration#password_strategy}.
64
+ #
65
+ # @see PasswordStrategies
66
+ # @return [void]
67
+ #
68
+ # @!method authenticated?
69
+ # Check's the provided password against the user's encrypted password using
70
+ # the configured password strategy. By default, this will be
71
+ # {PasswordStrategies::BCrypt#authenticated?}, but can be changed with
72
+ # {Configuration#password_strategy}.
73
+ #
74
+ # @see PasswordStrategies
75
+ # @param [String] password
76
+ # The password to check.
77
+ # @return [Boolean]
78
+ # True if the password provided is correct for the user.
79
+ #
80
+ # @!method self.authenticate
81
+ # Finds the user with the given email and authenticates them with the
82
+ # provided password. If the email corresponds to a user and the provided
83
+ # password is correct for that user, this method will return that user.
84
+ # Otherwise it will return nil.
85
+ #
86
+ # @return [User, nil]
87
+ #
88
+ # @!method self.find_by_normalized_email
89
+ # Finds the user with the given email. The email with be normalized via
90
+ # {#normalize_email}.
91
+ #
92
+ # @return [User, nil]
93
+ #
94
+ # @!method self.normalize_email
95
+ # Normalizes the provided email by downcasing and removing all spaces.
96
+ # This is used by {find_by_normalized_email} and is also called when
97
+ # validating a user to ensure only normalized emails are stored in the
98
+ # database.
99
+ #
100
+ # @return [String]
101
+ #
6
102
  module User
7
103
  extend ActiveSupport::Concern
8
104
 
@@ -15,6 +111,7 @@ module Clearance
15
111
  include password_strategy
16
112
  end
17
113
 
114
+ # @api private
18
115
  module ClassMethods
19
116
  def authenticate(email, password)
20
117
  if user = find_by_normalized_email(email)
@@ -39,6 +136,7 @@ module Clearance
39
136
  end
40
137
  end
41
138
 
139
+ # @api private
42
140
  module Validations
43
141
  extend ActiveSupport::Concern
44
142
 
@@ -53,6 +151,7 @@ module Clearance
53
151
  end
54
152
  end
55
153
 
154
+ # @api private
56
155
  module Callbacks
57
156
  extend ActiveSupport::Concern
58
157
 
@@ -62,16 +161,47 @@ module Clearance
62
161
  end
63
162
  end
64
163
 
164
+ # Generates a {#confirmation_token} for the user, which allows them to reset
165
+ # their password via an email link.
166
+ #
167
+ # Calling `forgot_password!` will cause the user model to be saved without
168
+ # validations. Any other changes you made to this user instance will also
169
+ # be persisted, without validation. It is inteded to be called on an
170
+ # instance with no changes (`dirty? == false`).
171
+ #
172
+ # @return [Boolean] Was the save successful?
65
173
  def forgot_password!
66
174
  generate_confirmation_token
67
175
  save validate: false
68
176
  end
69
177
 
178
+ # Generates a new {#remember_token} for the user, which will have the effect
179
+ # of signing all of the user's current sessions out. This is called
180
+ # internally by {Session#sign_out}.
181
+ #
182
+ # Calling `reset_remember_token!` will cause the user model to be saved
183
+ # without validations. Any other changes you made to this user instance will
184
+ # also be persisted, without validation. It is inteded to be called on an
185
+ # instance with no changes (`dirty? == false`).
186
+ #
187
+ # @return [Boolean] Was the save successful?
70
188
  def reset_remember_token!
71
189
  generate_remember_token
72
190
  save validate: false
73
191
  end
74
192
 
193
+ # Sets the user's password to the new value, using the `password=` method on
194
+ # the configured password strategy. By default, this is
195
+ # {PasswordStrategies::BCrypt#password=}.
196
+ #
197
+ # This also has the side-effect of blanking the {#confirmation_token} and
198
+ # rotating the `#remember_token`.
199
+ #
200
+ # Validations will be run as part of this update. If the user instance is
201
+ # not valid, the password change will not be persisted, and this method will
202
+ # return `false`.
203
+ #
204
+ # @return [Boolean] Was the save successful?
75
205
  def update_password(new_password)
76
206
  self.password_changing = true
77
207
  self.password = new_password
@@ -86,28 +216,57 @@ module Clearance
86
216
 
87
217
  private
88
218
 
219
+ # Sets the email on this instance to the value returned by
220
+ # {.normalize_email}
221
+ #
222
+ # @return [String]
89
223
  def normalize_email
90
224
  self.email = self.class.normalize_email(email)
91
225
  end
92
226
 
227
+ # Always false. Override this method in your user model to allow for other
228
+ # forms of user authentication (username, Facebook, etc).
229
+ #
230
+ # @return [false]
93
231
  def email_optional?
94
232
  false
95
233
  end
96
234
 
235
+ # Always false. Override this method in your user model to allow for other
236
+ # forms of user authentication (username, Facebook, etc).
237
+ #
238
+ # @return [false]
97
239
  def password_optional?
98
240
  false
99
241
  end
100
242
 
243
+ # True if {#password_optional?} is true or if the user already has an
244
+ # {#encrypted_password} that is not changing.
245
+ #
246
+ # @return [Boolean]
101
247
  def skip_password_validation?
102
248
  password_optional? || (encrypted_password.present? && !password_changing)
103
249
  end
104
250
 
251
+ # Sets the {#confirmation_token} on the instance to a new value generated by
252
+ # {Token.new}. The change is not automatically persisted. If you would like
253
+ # to generate and save in a single method call, use {#forgot_password!}.
254
+ #
255
+ # @return [String] The new confirmation token
105
256
  def generate_confirmation_token
106
257
  self.confirmation_token = Clearance::Token.new
107
258
  end
108
259
 
260
+ # Sets the {#remember_token} on the instance to a new value generated by
261
+ # {Token.new}. The change is not automatically persisted. If you would like
262
+ # to generate and sace in a single method call, use
263
+ # {#reset_remember_token!}.
264
+ #
265
+ # @return [String] The new remember token
109
266
  def generate_remember_token
110
267
  self.remember_token = Clearance::Token.new
111
268
  end
269
+
270
+ private_constant :Callbacks, :ClassMethods, :Validations
112
271
  end
113
272
  end