clearance 1.8.0 → 1.13.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/.gitignore +2 -0
- data/.travis.yml +24 -6
- data/.yardopts +6 -0
- data/Appraisals +12 -4
- data/CONTRIBUTING.md +4 -1
- data/Gemfile +2 -2
- data/Gemfile.lock +87 -85
- data/NEWS.md +672 -311
- data/README.md +185 -330
- data/app/controllers/clearance/passwords_controller.rb +25 -19
- data/app/controllers/clearance/sessions_controller.rb +17 -4
- data/app/controllers/clearance/users_controller.rb +10 -4
- data/app/mailers/clearance_mailer.rb +2 -3
- data/app/views/clearance_mailer/change_password.html.erb +6 -3
- data/app/views/clearance_mailer/change_password.text.erb +5 -0
- data/app/views/layouts/application.html.erb +2 -2
- data/app/views/passwords/create.html.erb +1 -1
- data/app/views/passwords/edit.html.erb +2 -2
- data/app/views/passwords/new.html.erb +2 -2
- data/app/views/sessions/_form.html.erb +2 -2
- data/app/views/sessions/new.html.erb +1 -1
- data/app/views/users/new.html.erb +2 -2
- data/bin/setup +6 -2
- data/config/locales/clearance.en.yml +6 -0
- data/db/migrate/20110111224543_create_clearance_users.rb +1 -1
- data/gemfiles/{rails3.2.gemfile → rails32.gemfile} +1 -1
- data/gemfiles/{rails4.0.gemfile → rails40.gemfile} +2 -2
- data/gemfiles/{rails4.1.gemfile → rails41.gemfile} +2 -2
- data/gemfiles/{rails4.2.gemfile → rails42.gemfile} +2 -2
- data/gemfiles/rails50.gemfile +19 -0
- data/lib/clearance/authentication.rb +52 -1
- data/lib/clearance/authorization.rb +47 -4
- data/lib/clearance/back_door.rb +1 -0
- data/lib/clearance/configuration.rb +127 -15
- 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 +24 -4
- data/lib/clearance/password_strategies/bcrypt.rb +9 -2
- data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +19 -0
- data/lib/clearance/password_strategies/blowfish.rb +17 -0
- data/lib/clearance/password_strategies/sha1.rb +17 -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 +46 -1
- 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 +44 -0
- data/lib/clearance/testing/deny_access_matcher.rb +35 -1
- 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 +159 -0
- data/lib/clearance/version.rb +1 -1
- data/lib/clearance.rb +2 -0
- data/lib/generators/clearance/install/install_generator.rb +13 -3
- data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb +3 -3
- data/lib/generators/clearance/install/templates/db/migrate/create_users.rb +2 -2
- 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/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 -2
- data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_in_spec.rb.tt +1 -1
- 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 +1 -1
- data/spec/acceptance/clearance_installation_spec.rb +4 -1
- data/spec/app_templates/config/initializers/clearance.rb +2 -0
- data/spec/app_templates/testapp/app/controllers/home_controller.rb +5 -1
- data/spec/app_templates/testapp/config/initializers/action_mailer.rb +1 -3
- data/spec/clearance/controller_spec.rb +11 -0
- data/spec/clearance/rack_session_spec.rb +5 -5
- data/spec/clearance/testing/{helpers_spec.rb → controller_helpers_spec.rb} +12 -12
- data/spec/clearance/testing/view_helpers_spec.rb +37 -0
- data/spec/configuration_spec.rb +24 -0
- data/spec/controllers/apis_controller_spec.rb +6 -2
- data/spec/controllers/forgeries_controller_spec.rb +6 -1
- data/spec/controllers/passwords_controller_spec.rb +1 -11
- data/spec/controllers/permissions_controller_spec.rb +13 -3
- data/spec/controllers/sessions_controller_spec.rb +4 -4
- data/spec/dummy/app/controllers/application_controller.rb +5 -1
- data/spec/dummy/application.rb +4 -0
- data/spec/generators/clearance/install/install_generator_spec.rb +15 -3
- data/spec/generators/clearance/routes/routes_generator_spec.rb +5 -1
- data/spec/generators/clearance/views/views_generator_spec.rb +11 -10
- data/spec/helpers/helper_helpers_spec.rb +10 -0
- data/spec/mailers/clearance_mailer_spec.rb +13 -19
- data/spec/password_strategies/bcrypt_migration_from_sha1_spec.rb +6 -0
- data/spec/password_strategies/blowfish_spec.rb +6 -0
- data/spec/password_strategies/sha1_spec.rb +6 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/generator_spec_helpers.rb +4 -0
- data/spec/support/http_method_shim.rb +23 -0
- data/spec/user_spec.rb +9 -0
- data/spec/views/view_helpers_spec.rb +10 -0
- metadata +19 -8
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
|
-
@cookies ||=
|
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,
|
@@ -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,44 @@
|
|
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
|
+
unless defined?(FactoryGirl)
|
21
|
+
raise("Clearance's `sign_in` helper requires factory_girl")
|
22
|
+
end
|
23
|
+
|
24
|
+
factory = Clearance.configuration.user_model.to_s.underscore.to_sym
|
25
|
+
sign_in_as FactoryGirl.create(factory)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Signs in the provided user.
|
29
|
+
#
|
30
|
+
# @return user
|
31
|
+
def sign_in_as(user)
|
32
|
+
@request.env[:clearance].sign_in(user)
|
33
|
+
user
|
34
|
+
end
|
35
|
+
|
36
|
+
# Signs out a user that may be signed in.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def sign_out
|
40
|
+
@request.env[:clearance].sign_out
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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
|
70
|
+
if clearance_session.signed_in?
|
41
71
|
'/'
|
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
|
@@ -1,31 +1,15 @@
|
|
1
|
+
require "clerance/testing/controller_helpers"
|
2
|
+
|
1
3
|
module Clearance
|
2
4
|
module Testing
|
5
|
+
# @deprecated Use Clearance::Testing::ControllerHelpers
|
3
6
|
module Helpers
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
unless defined?(FactoryGirl)
|
11
|
-
raise(
|
12
|
-
RuntimeError,
|
13
|
-
"Clearance's `sign_in` helper requires factory_girl"
|
14
|
-
)
|
15
|
-
end
|
16
|
-
|
17
|
-
factory = Clearance.configuration.user_model.to_s.underscore.to_sym
|
18
|
-
sign_in_as FactoryGirl.create(factory)
|
19
|
-
end
|
20
|
-
|
21
|
-
def sign_in_as(user)
|
22
|
-
@controller.sign_in user
|
23
|
-
user
|
24
|
-
end
|
25
|
-
|
26
|
-
def sign_out
|
27
|
-
@controller.sign_out
|
28
|
-
end
|
7
|
+
warn(
|
8
|
+
"#{Kernel.caller.first} [DEPRECATION] Clearance::Testing::Helpers is "\
|
9
|
+
"deprecated and has been replaced with " \
|
10
|
+
"Clearance::Testing::ControllerHelpers. Require " \
|
11
|
+
"clearance/testing/controller_helpers instead."
|
12
|
+
)
|
29
13
|
end
|
30
14
|
end
|
31
15
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Testing
|
3
|
+
# Provides helpers to your view and helper specs.
|
4
|
+
# Using these helpers makes `current_user`, `signed_in?` and `signed_out?`
|
5
|
+
# behave properly in view and helper specs.
|
6
|
+
module ViewHelpers
|
7
|
+
# Sets current_user on the view under test to a new instance of your user
|
8
|
+
# model.
|
9
|
+
def sign_in
|
10
|
+
view.current_user = Clearance.configuration.user_model.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Sets current_user on the view under test to the supplied user.
|
14
|
+
def sign_in_as(user)
|
15
|
+
view.current_user = user
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
module CurrentUser
|
20
|
+
attr_accessor :current_user
|
21
|
+
|
22
|
+
def signed_in?
|
23
|
+
current_user.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def signed_out?
|
27
|
+
!signed_in?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/clearance/token.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module Clearance
|
2
|
+
# Random token used for password reset and remember 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
|
data/lib/clearance/user.rb
CHANGED
@@ -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
|
data/lib/clearance/version.rb
CHANGED