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
@@ -29,6 +29,7 @@ module Clearance
29
29
 
30
30
  private
31
31
 
32
+ # @api private
32
33
  def sign_in_through_the_back_door(env)
33
34
  params = Rack::Utils.parse_query(env['QUERY_STRING'])
34
35
  user_id = params['as']
@@ -138,7 +138,8 @@ module Clearance
138
138
  # This is called from the Clearance engine to reload the configured
139
139
  # user class during each request while in development mode, but only once
140
140
  # in production.
141
- # @private
141
+ #
142
+ # @api private
142
143
  def reload_user_model
143
144
  if @user_model.present?
144
145
  @user_model = @user_model.to_s.constantize
@@ -1,2 +1,14 @@
1
1
  require 'clearance/constraints/signed_in'
2
2
  require 'clearance/constraints/signed_out'
3
+
4
+ module Clearance
5
+ # Clearance provides Rails routing constraints that can control access and the
6
+ # visibility of routes at the routing layer. The {Constraints::SignedIn}
7
+ # constraint can be used to make routes visible only to signed in users. The
8
+ # {Constraints::SignedOut} constraint can be used to make routes visible only
9
+ # to signed out users.
10
+ #
11
+ # @see http://guides.rubyonrails.org/routing.html#advanced-constraints
12
+ module Constraints
13
+ end
14
+ end
@@ -29,18 +29,22 @@ module Clearance
29
29
 
30
30
  private
31
31
 
32
+ # @api private
32
33
  def clearance_session
33
34
  @request.env[:clearance]
34
35
  end
35
36
 
37
+ # @api private
36
38
  def current_user
37
39
  clearance_session.current_user
38
40
  end
39
41
 
42
+ # @api private
40
43
  def current_user_fulfills_additional_requirements?
41
44
  @block.call current_user
42
45
  end
43
46
 
47
+ # @api private
44
48
  def signed_in?
45
49
  clearance_session.present? && clearance_session.signed_in?
46
50
  end
@@ -18,10 +18,12 @@ module Clearance
18
18
 
19
19
  private
20
20
 
21
+ # @api private
21
22
  def clearance_session
22
23
  @request.env[:clearance]
23
24
  end
24
25
 
26
+ # @api private
25
27
  def missing_session?
26
28
  clearance_session.nil?
27
29
  end
@@ -2,6 +2,19 @@ require 'clearance/authentication'
2
2
  require 'clearance/authorization'
3
3
 
4
4
  module Clearance
5
+ # Adds clearance controller helpers to the controller it is mixed into.
6
+ #
7
+ # This exposes clearance controller and helper methods such as `current_user`.
8
+ # See {Authentication} and {Authorization} documentation for complete
9
+ # documentation on the methods.
10
+ #
11
+ # The `clearance:install` generator automatically adds this mixin to
12
+ # `ApplicationController`, which is the recommended configuration.
13
+ #
14
+ # class ApplicationController < ApplicationController
15
+ # include Clearance::Controller
16
+ # end
17
+ #
5
18
  module Controller
6
19
  extend ActiveSupport::Concern
7
20
 
@@ -1,5 +1,14 @@
1
1
  module Clearance
2
+ # Runs as the base {SignInGuard} for all requests, regardless of configured
3
+ # {Configuration#sign_in_guards}.
2
4
  class DefaultSignInGuard < SignInGuard
5
+ # Runs the default sign in guard.
6
+ #
7
+ # If there is a value set in the clearance session object, then the guard
8
+ # returns {SuccessStatus}. Otherwise, it returns {FailureStatus} with the
9
+ # message returned by {#default_failure_message}.
10
+ #
11
+ # @return [SuccessStatus, FailureStatus]
3
12
  def call
4
13
  if session.signed_in?
5
14
  success
@@ -8,6 +17,14 @@ module Clearance
8
17
  end
9
18
  end
10
19
 
20
+ # The default failure message pulled from the i18n framework.
21
+ #
22
+ # Will use the value returned from the following i18n keys, in this order:
23
+ #
24
+ # * `clearance.controllers.sessions.bad_email_or_password`
25
+ # * `flashes.failure_after_create`
26
+ #
27
+ # @return [String]
11
28
  def default_failure_message
12
29
  I18n.t(
13
30
  :bad_email_or_password,
@@ -2,6 +2,22 @@ require "clearance"
2
2
  require "rails"
3
3
 
4
4
  module Clearance
5
+ # Makes Clearance behavior available to Rails apps on initialization. By using
6
+ # a Rails Engine rather than a Railtie, Clearance can automatically expose its
7
+ # own routes and views to the hosting application.
8
+ #
9
+ # Requiring `clearance` (likely by having it in your `Gemfile`) will
10
+ # automatically require the engine. You can opt-out of Clearance's internal
11
+ # routes by using {Configuration#routes=}. You can override the Clearance
12
+ # views by running `rails generate clearance:views`.
13
+ #
14
+ # In addition to providing routes and views, the Clearance engine:
15
+ #
16
+ # * Ensures `password` and `token` parameters are filtered out of Rails logs.
17
+ # * Mounts the {RackSession} middleware in the appropriate location
18
+ # * Reloads classes referenced in your {Configuration} on every request in
19
+ # development mode.
20
+ #
5
21
  class Engine < Rails::Engine
6
22
  initializer "clearance.filter" do |app|
7
23
  app.config.filter_parameters += [:password, :token]
@@ -9,8 +9,6 @@ module Clearance
9
9
  module BCrypt
10
10
  require 'bcrypt'
11
11
 
12
- extend ActiveSupport::Concern
13
-
14
12
  def authenticated?(password)
15
13
  if encrypted_password.present?
16
14
  ::BCrypt::Password.new(encrypted_password) == password
@@ -27,10 +25,12 @@ module Clearance
27
25
 
28
26
  private
29
27
 
28
+ # @api private
30
29
  def encrypt(password)
31
30
  ::BCrypt::Password.create(password, cost: cost)
32
31
  end
33
32
 
33
+ # @api private
34
34
  def cost
35
35
  if test_environment?
36
36
  ::BCrypt::Engine::MIN_COST
@@ -39,6 +39,7 @@ module Clearance
39
39
  end
40
40
  end
41
41
 
42
+ # @api private
42
43
  def test_environment?
43
44
  defined?(::Rails) && ::Rails.env.test?
44
45
  end
@@ -9,6 +9,7 @@ module Clearance
9
9
  "strategy, add clearance-deprecated_password_strategies to your " \
10
10
  "Gemfile."
11
11
 
12
+ # @api private
12
13
  class BCryptUser
13
14
  include Clearance::PasswordStrategies::BCrypt
14
15
 
@@ -19,6 +20,7 @@ module Clearance
19
20
  delegate :encrypted_password, :encrypted_password=, to: :@user
20
21
  end
21
22
 
23
+ # @api private
22
24
  class SHA1User
23
25
  include Clearance::PasswordStrategies::SHA1
24
26
 
@@ -29,11 +31,15 @@ module Clearance
29
31
  delegate :salt, :salt=, :encrypted_password, :encrypted_password=, to: :@user
30
32
  end
31
33
 
34
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
35
+ # gem
32
36
  def authenticated?(password)
33
37
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
34
38
  authenticated_with_sha1?(password) || authenticated_with_bcrypt?(password)
35
39
  end
36
40
 
41
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
42
+ # gem
37
43
  def password=(new_password)
38
44
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
39
45
  @password = new_password
@@ -42,6 +48,7 @@ module Clearance
42
48
 
43
49
  private
44
50
 
51
+ # @api private
45
52
  def authenticated_with_bcrypt?(password)
46
53
  begin
47
54
  BCryptUser.new(self).authenticated? password
@@ -50,6 +57,7 @@ module Clearance
50
57
  end
51
58
  end
52
59
 
60
+ # @api private
53
61
  def authenticated_with_sha1?(password)
54
62
  if sha1_password?
55
63
  if SHA1User.new(self).authenticated? password
@@ -60,6 +68,7 @@ module Clearance
60
68
  end
61
69
  end
62
70
 
71
+ # @api private
63
72
  def sha1_password?
64
73
  self.encrypted_password =~ /^[a-f0-9]{40}$/
65
74
  end
@@ -11,11 +11,15 @@ module Clearance
11
11
  "provide your own. To continue using this strategy add " \
12
12
  "clearance-deprecated_password_strategies to your Gemfile."
13
13
 
14
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
15
+ # gem
14
16
  def authenticated?(password)
15
17
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
16
18
  encrypted_password == encrypt(password)
17
19
  end
18
20
 
21
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
22
+ # gem
19
23
  def password=(new_password)
20
24
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
21
25
  @password = new_password
@@ -28,10 +32,12 @@ module Clearance
28
32
 
29
33
  protected
30
34
 
35
+ # @api private
31
36
  def encrypt(string)
32
37
  generate_hash("--#{salt}--#{string}--")
33
38
  end
34
39
 
40
+ # @api private
35
41
  def generate_hash(string)
36
42
  cipher = OpenSSL::Cipher::Cipher.new('bf-cbc').encrypt
37
43
  cipher.key = Digest::SHA256.digest(salt)
@@ -39,12 +45,14 @@ module Clearance
39
45
  Base64.encode64(hash).encode('utf-8')
40
46
  end
41
47
 
48
+ # @api private
42
49
  def initialize_salt_if_necessary
43
50
  if salt.blank?
44
51
  self.salt = generate_salt
45
52
  end
46
53
  end
47
54
 
55
+ # @api private
48
56
  def generate_salt
49
57
  Base64.encode64(SecureRandom.hex(20)).encode('utf-8')
50
58
  end
@@ -12,11 +12,15 @@ module Clearance
12
12
 
13
13
  extend ActiveSupport::Concern
14
14
 
15
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
16
+ # gem
15
17
  def authenticated?(password)
16
18
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
17
19
  encrypted_password == encrypt(password)
18
20
  end
19
21
 
22
+ # @deprecated Use {BCrypt} or `clearance-deprecated_password_strategies`
23
+ # gem
20
24
  def password=(new_password)
21
25
  warn "#{Kernel.caller.first}: #{DEPRECATION_MESSAGE}"
22
26
  @password = new_password
@@ -29,20 +33,24 @@ module Clearance
29
33
 
30
34
  private
31
35
 
36
+ # @api private
32
37
  def encrypt(string)
33
38
  generate_hash "--#{salt}--#{string}--"
34
39
  end
35
40
 
41
+ # @api private
36
42
  def generate_hash(string)
37
43
  Digest::SHA1.hexdigest(string).encode 'UTF-8'
38
44
  end
39
45
 
46
+ # @api private
40
47
  def initialize_salt_if_necessary
41
48
  if salt.blank?
42
49
  self.salt = generate_salt
43
50
  end
44
51
  end
45
52
 
53
+ # @api private
46
54
  def generate_salt
47
55
  SecureRandom.hex(20).encode('UTF-8')
48
56
  end
@@ -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,13 +1,19 @@
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
18
  if cookie_value[:value].present?
13
19
  Rack::Utils.set_cookie_header!(
@@ -18,6 +24,9 @@ module Clearance
18
24
  end
19
25
  end
20
26
 
27
+ # The current user represented by this session.
28
+ #
29
+ # @return [User, nil]
21
30
  def current_user
22
31
  if remember_token.present?
23
32
  @current_user ||= user_from_remember_token(remember_token)
@@ -26,6 +35,20 @@ module Clearance
26
35
  @current_user
27
36
  end
28
37
 
38
+ # Sign the provided user in, if approved by the configured sign in guards.
39
+ # If the sign in guard stack returns {SuccessStatus}, the {#current_user}
40
+ # will be set and then remember token cookie will be set to the user's
41
+ # remember token. If the stack returns {FailureStatus}, {#current_user} will
42
+ # be nil.
43
+ #
44
+ # In either event, the resulting status will be yielded to a provided block,
45
+ # if provided. See {SessionsController#create} for an example of how this
46
+ # can be used.
47
+ #
48
+ # @param [User] user
49
+ # @yieldparam [SuccessStatus,FailureStatus] status Result of the sign in
50
+ # operation.
51
+ # @return [void]
29
52
  def sign_in(user, &block)
30
53
  @current_user = user
31
54
  status = run_sign_in_stack
@@ -41,6 +64,13 @@ module Clearance
41
64
  end
42
65
  end
43
66
 
67
+ # Invalidates the users remember token and removes the remember token cookie
68
+ # from the store. The invalidation of the remember token causes any other
69
+ # sessions that are signed in from other locations to also be invalidated on
70
+ # their next request. This is because all Clearance sessions for a given
71
+ # user share a remember token.
72
+ #
73
+ # @return [void]
44
74
  def sign_out
45
75
  if signed_in?
46
76
  current_user.reset_remember_token!
@@ -50,24 +80,33 @@ module Clearance
50
80
  cookies.delete remember_token_cookie
51
81
  end
52
82
 
83
+ # True if {#current_user} is set.
84
+ #
85
+ # @return [Boolean]
53
86
  def signed_in?
54
87
  current_user.present?
55
88
  end
56
89
 
90
+ # True if {#current_user} is not set
91
+ #
92
+ # @return [Boolean]
57
93
  def signed_out?
58
94
  ! signed_in?
59
95
  end
60
96
 
61
97
  private
62
98
 
99
+ # @api private
63
100
  def cookies
64
101
  @cookies ||= ActionDispatch::Request.new(@env).cookie_jar
65
102
  end
66
103
 
104
+ # @api private
67
105
  def remember_token
68
106
  cookies[remember_token_cookie]
69
107
  end
70
108
 
109
+ # @api private
71
110
  def remember_token_expires
72
111
  if expires_configuration.arity == 1
73
112
  expires_configuration.call(cookies)
@@ -80,23 +119,28 @@ module Clearance
80
119
  end
81
120
  end
82
121
 
122
+ # @api private
83
123
  def remember_token_cookie
84
124
  Clearance.configuration.cookie_name.freeze
85
125
  end
86
126
 
127
+ # @api private
87
128
  def expires_configuration
88
129
  Clearance.configuration.cookie_expiration
89
130
  end
90
131
 
132
+ # @api private
91
133
  def user_from_remember_token(token)
92
134
  Clearance.configuration.user_model.where(remember_token: token).first
93
135
  end
94
136
 
137
+ # @api private
95
138
  def run_sign_in_stack
96
139
  @stack ||= initialize_sign_in_guard_stack
97
140
  @stack.call
98
141
  end
99
142
 
143
+ # @api private
100
144
  def initialize_sign_in_guard_stack
101
145
  default_guard = DefaultSignInGuard.new(self)
102
146
  guards = Clearance.configuration.sign_in_guards
@@ -106,6 +150,7 @@ module Clearance
106
150
  end
107
151
  end
108
152
 
153
+ # @api private
109
154
  def cookie_value
110
155
  value = {
111
156
  expires: remember_token_expires,