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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/.yardopts +3 -0
- data/Gemfile.lock +60 -60
- data/NEWS.md +17 -0
- data/config/locales/clearance.en.yml +1 -0
- data/lib/clearance/authentication.rb +49 -0
- data/lib/clearance/authorization.rb +44 -1
- data/lib/clearance/back_door.rb +1 -0
- data/lib/clearance/configuration.rb +2 -1
- data/lib/clearance/constraints.rb +12 -0
- data/lib/clearance/constraints/signed_in.rb +4 -0
- data/lib/clearance/constraints/signed_out.rb +2 -0
- data/lib/clearance/controller.rb +13 -0
- data/lib/clearance/default_sign_in_guard.rb +17 -0
- data/lib/clearance/engine.rb +16 -0
- data/lib/clearance/password_strategies/bcrypt.rb +3 -2
- data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +9 -0
- data/lib/clearance/password_strategies/blowfish.rb +8 -0
- data/lib/clearance/password_strategies/sha1.rb +8 -0
- data/lib/clearance/rack_session.rb +13 -0
- data/lib/clearance/session.rb +45 -0
- data/lib/clearance/session_status.rb +7 -0
- data/lib/clearance/sign_in_guard.rb +65 -0
- data/lib/clearance/testing/controller_helpers.rb +10 -1
- data/lib/clearance/testing/deny_access_matcher.rb +30 -0
- data/lib/clearance/testing/view_helpers.rb +1 -1
- data/lib/clearance/token.rb +7 -0
- data/lib/clearance/user.rb +159 -0
- data/lib/clearance/version.rb +1 -1
- data/lib/generators/clearance/install/install_generator.rb +1 -1
- data/lib/generators/clearance/routes/routes_generator.rb +15 -0
- data/lib/generators/clearance/routes/templates/routes.rb +10 -10
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt +1 -1
- data/spec/acceptance/clearance_installation_spec.rb +2 -1
- data/spec/controllers/permissions_controller_spec.rb +6 -0
- metadata +3 -3
data/lib/clearance/back_door.rb
CHANGED
@@ -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
|
-
#
|
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
|
data/lib/clearance/controller.rb
CHANGED
@@ -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,
|
data/lib/clearance/engine.rb
CHANGED
@@ -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
|
data/lib/clearance/session.rb
CHANGED
@@ -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,
|