clearance 0.16.3 → 1.0.0.rc1
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.
- data/.gitignore +1 -0
- data/.travis.yml +0 -2
- data/Appraisals +2 -2
- data/CONTRIBUTING.md +10 -19
- data/Gemfile +1 -1
- data/Gemfile.lock +81 -82
- data/NEWS.md +17 -4
- data/README.md +176 -113
- data/app/controllers/clearance/passwords_controller.rb +44 -31
- data/app/controllers/clearance/sessions_controller.rb +11 -10
- data/app/controllers/clearance/users_controller.rb +8 -12
- data/app/mailers/clearance_mailer.rb +4 -5
- data/app/views/clearance_mailer/change_password.html.erb +2 -4
- data/app/views/layouts/application.html.erb +7 -5
- data/app/views/passwords/edit.html.erb +8 -7
- data/app/views/passwords/new.html.erb +6 -5
- data/app/views/sessions/_form.html.erb +7 -5
- data/app/views/sessions/new.html.erb +3 -2
- data/app/views/users/_form.html.erb +4 -3
- data/clearance.gemspec +29 -27
- data/config/routes.rb +10 -13
- data/db/migrate/20110111224543_create_clearance_users.rb +18 -0
- data/db/schema.rb +4 -5
- data/features/engine/visitor_resets_password.feature +0 -7
- data/features/engine/visitor_signs_in.feature +7 -0
- data/features/engine/visitor_signs_up.feature +2 -2
- data/features/integration.feature +0 -1
- data/features/integration_with_test_unit.feature +43 -0
- data/features/step_definitions/configuration_steps.rb +8 -15
- data/features/step_definitions/engine/clearance_steps.rb +38 -38
- data/features/support/clearance.rb +1 -1
- data/features/support/env.rb +4 -21
- data/gemfiles/{3.0.12.gemfile → 3.0.15.gemfile} +1 -1
- data/gemfiles/{3.0.12.gemfile.lock → 3.0.15.gemfile.lock} +75 -76
- data/gemfiles/{3.2.3.gemfile → 3.1.6.gemfile} +1 -1
- data/gemfiles/{3.1.4.gemfile.lock → 3.1.6.gemfile.lock} +79 -80
- data/gemfiles/{3.1.4.gemfile → 3.2.6.gemfile} +1 -1
- data/gemfiles/{3.2.3.gemfile.lock → 3.2.6.gemfile.lock} +80 -81
- data/lib/clearance.rb +1 -0
- data/lib/clearance/authentication.rb +37 -69
- data/lib/clearance/configuration.rb +3 -18
- data/lib/clearance/constraints.rb +2 -0
- data/lib/clearance/constraints/signed_in.rb +28 -0
- data/lib/clearance/constraints/signed_out.rb +9 -0
- data/lib/clearance/engine.rb +4 -4
- data/lib/clearance/password_strategies.rb +5 -1
- data/lib/clearance/password_strategies/bcrypt.rb +27 -0
- data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +52 -0
- data/lib/clearance/password_strategies/blowfish.rb +11 -15
- data/lib/clearance/password_strategies/fake.rb +23 -0
- data/lib/clearance/password_strategies/sha1.rb +15 -21
- data/lib/clearance/session.rb +28 -20
- data/lib/clearance/testing.rb +8 -3
- data/lib/clearance/testing/assertion_error.rb +2 -7
- data/lib/clearance/testing/deny_access_matcher.rb +27 -32
- data/lib/clearance/testing/helpers.rb +7 -8
- data/lib/clearance/user.rb +26 -92
- data/lib/clearance/version.rb +1 -1
- data/lib/generators/clearance/install/templates/db/migrate/upgrade_clearance_to_diesel.rb +24 -26
- data/spec/clearance/constraints/signed_in_spec.rb +51 -0
- data/spec/clearance/constraints/signed_out_spec.rb +15 -0
- data/spec/clearance/rack_session_spec.rb +8 -7
- data/spec/clearance/session_spec.rb +28 -27
- data/spec/configuration_spec.rb +7 -6
- data/spec/controllers/denies_controller_spec.rb +11 -10
- data/spec/controllers/flashes_controller_spec.rb +5 -5
- data/spec/controllers/forgeries_controller_spec.rb +9 -9
- data/spec/controllers/passwords_controller_spec.rb +42 -55
- data/spec/controllers/sessions_controller_spec.rb +26 -33
- data/spec/controllers/users_controller_spec.rb +16 -14
- data/spec/factories.rb +1 -3
- data/spec/mailers/clearance_mailer_spec.rb +4 -4
- data/spec/models/bcrypt_migration_from_sha1_spec.rb +71 -0
- data/spec/models/bcrypt_spec.rb +40 -0
- data/spec/models/blowfish_spec.rb +14 -13
- data/spec/models/{clearance_user_spec.rb → password_strategies_spec.rb} +5 -5
- data/spec/models/sha1_spec.rb +18 -13
- data/spec/models/user_spec.rb +58 -73
- data/spec/spec_helper.rb +5 -6
- data/spec/support/clearance.rb +0 -4
- data/spec/support/cookies.rb +25 -27
- data/spec/support/request_with_remember_token.rb +19 -0
- metadata +95 -90
- data/db/migrate/20110111224543_create_diesel_clearance_users.rb +0 -19
- data/init.rb +0 -1
data/lib/clearance/session.rb
CHANGED
@@ -1,18 +1,25 @@
|
|
1
1
|
module Clearance
|
2
2
|
class Session
|
3
|
-
REMEMBER_TOKEN_COOKIE =
|
3
|
+
REMEMBER_TOKEN_COOKIE = 'remember_token'.freeze
|
4
4
|
|
5
5
|
def initialize(env)
|
6
6
|
@env = env
|
7
7
|
end
|
8
8
|
|
9
|
-
def
|
10
|
-
|
9
|
+
def add_cookie_to_headers(headers)
|
10
|
+
if signed_in?
|
11
|
+
Rack::Utils.set_cookie_header!(
|
12
|
+
headers, REMEMBER_TOKEN_COOKIE,
|
13
|
+
:value => current_user.remember_token,
|
14
|
+
:expires => Clearance.configuration.cookie_expiration.call,
|
15
|
+
:path => '/'
|
16
|
+
)
|
17
|
+
end
|
11
18
|
end
|
12
19
|
|
13
20
|
def current_user
|
14
21
|
@current_user ||= with_remember_token do |token|
|
15
|
-
Clearance.configuration.user_model.find_by_remember_token
|
22
|
+
Clearance.configuration.user_model.find_by_remember_token token
|
16
23
|
end
|
17
24
|
end
|
18
25
|
|
@@ -21,35 +28,36 @@ module Clearance
|
|
21
28
|
end
|
22
29
|
|
23
30
|
def sign_out
|
24
|
-
|
31
|
+
if signed_in?
|
32
|
+
current_user.reset_remember_token!
|
33
|
+
end
|
34
|
+
|
25
35
|
@current_user = nil
|
26
|
-
cookies.delete
|
36
|
+
cookies.delete REMEMBER_TOKEN_COOKIE
|
27
37
|
end
|
28
38
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
:path => "/")
|
36
|
-
end
|
39
|
+
def signed_in?
|
40
|
+
current_user.present?
|
41
|
+
end
|
42
|
+
|
43
|
+
def signed_out?
|
44
|
+
! signed_in?
|
37
45
|
end
|
38
46
|
|
39
47
|
private
|
40
48
|
|
41
|
-
def
|
42
|
-
|
43
|
-
yield token
|
44
|
-
end
|
49
|
+
def cookies
|
50
|
+
@cookies ||= @env['action_dispatch.cookies'] || Rack::Request.new(@env).cookies
|
45
51
|
end
|
46
52
|
|
47
53
|
def remember_token
|
48
54
|
cookies[REMEMBER_TOKEN_COOKIE]
|
49
55
|
end
|
50
56
|
|
51
|
-
def
|
52
|
-
|
57
|
+
def with_remember_token
|
58
|
+
if token = remember_token
|
59
|
+
yield token
|
60
|
+
end
|
53
61
|
end
|
54
62
|
end
|
55
63
|
end
|
data/lib/clearance/testing.rb
CHANGED
@@ -2,9 +2,14 @@ require 'clearance/testing/assertion_error'
|
|
2
2
|
require 'clearance/testing/deny_access_matcher'
|
3
3
|
require 'clearance/testing/helpers'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
Clearance.configure do |config|
|
6
|
+
config.password_strategy = Clearance::PasswordStrategies::Fake
|
7
|
+
end
|
8
|
+
|
9
|
+
if defined?(ActionController::TestCase)
|
10
|
+
ActionController::TestCase.extend Clearance::Testing::Matchers
|
11
|
+
|
12
|
+
class ActionController::TestCase
|
8
13
|
include Clearance::Testing::Helpers
|
9
14
|
end
|
10
15
|
end
|
@@ -1,11 +1,6 @@
|
|
1
1
|
module Clearance
|
2
2
|
module Testing
|
3
|
-
|
4
|
-
|
5
|
-
AssertionError = MiniTest::Assertion
|
6
|
-
else
|
7
|
-
require 'test/unit/assertionfailederror'
|
8
|
-
AssertionError = Test::Unit::AssertionFailedError
|
9
|
-
end
|
3
|
+
require 'minitest/unit'
|
4
|
+
AssertionError = MiniTest::Assertion
|
10
5
|
end
|
11
6
|
end
|
@@ -1,12 +1,6 @@
|
|
1
1
|
module Clearance
|
2
2
|
module Testing
|
3
3
|
module Matchers
|
4
|
-
# Ensures a controller denied access.
|
5
|
-
#
|
6
|
-
# @example
|
7
|
-
# it { should deny_access }
|
8
|
-
# it { should deny_access(:flash => "Denied access.") }
|
9
|
-
# it { should deny_access(:redirect => sign_in_url) }
|
10
4
|
def deny_access(opts = {})
|
11
5
|
DenyAccessMatcher.new(self, opts)
|
12
6
|
end
|
@@ -16,11 +10,15 @@ module Clearance
|
|
16
10
|
|
17
11
|
def initialize(context, opts)
|
18
12
|
@context = context
|
19
|
-
@flash
|
20
|
-
@url
|
13
|
+
@flash = opts[:flash]
|
14
|
+
@url = opts[:redirect]
|
21
15
|
|
22
|
-
@failure_message
|
23
|
-
@negative_failure_message =
|
16
|
+
@failure_message = ''
|
17
|
+
@negative_failure_message = ''
|
18
|
+
end
|
19
|
+
|
20
|
+
def description
|
21
|
+
'deny access'
|
24
22
|
end
|
25
23
|
|
26
24
|
def matches?(controller)
|
@@ -28,26 +26,20 @@ module Clearance
|
|
28
26
|
sets_the_flash? && redirects_to_url?
|
29
27
|
end
|
30
28
|
|
31
|
-
def description
|
32
|
-
"deny access"
|
33
|
-
end
|
34
|
-
|
35
29
|
private
|
36
30
|
|
37
|
-
def
|
38
|
-
if @
|
39
|
-
|
31
|
+
def denied_access_url
|
32
|
+
if @controller.signed_in?
|
33
|
+
'/'
|
40
34
|
else
|
41
|
-
|
42
|
-
@negative_failure_message << "Didn't expect to set the flash to #{@flash}"
|
43
|
-
true
|
44
|
-
else
|
45
|
-
@failure_message << "Expected the flash to be set to #{@flash} but was #{flash_notice_value}"
|
46
|
-
false
|
47
|
-
end
|
35
|
+
@controller.sign_in_url
|
48
36
|
end
|
49
37
|
end
|
50
38
|
|
39
|
+
def flash_notice
|
40
|
+
@controller.flash[:notice]
|
41
|
+
end
|
42
|
+
|
51
43
|
def flash_notice_value
|
52
44
|
if flash_notice.respond_to?(:values)
|
53
45
|
flash_notice.values.first
|
@@ -56,12 +48,9 @@ module Clearance
|
|
56
48
|
end
|
57
49
|
end
|
58
50
|
|
59
|
-
def flash_notice
|
60
|
-
@controller.flash[:notice]
|
61
|
-
end
|
62
|
-
|
63
51
|
def redirects_to_url?
|
64
52
|
@url ||= denied_access_url
|
53
|
+
|
65
54
|
begin
|
66
55
|
@context.send(:assert_redirected_to, @url)
|
67
56
|
@negative_failure_message << "Didn't expect to redirect to #{@url}."
|
@@ -72,11 +61,17 @@ module Clearance
|
|
72
61
|
end
|
73
62
|
end
|
74
63
|
|
75
|
-
def
|
76
|
-
if @
|
77
|
-
|
64
|
+
def sets_the_flash?
|
65
|
+
if @flash.blank?
|
66
|
+
true
|
78
67
|
else
|
79
|
-
@
|
68
|
+
if flash_notice_value == @flash
|
69
|
+
@negative_failure_message << "Didn't expect to set the flash to #{@flash}"
|
70
|
+
true
|
71
|
+
else
|
72
|
+
@failure_message << "Expected the flash to be set to #{@flash} but was #{flash_notice_value}"
|
73
|
+
false
|
74
|
+
end
|
80
75
|
end
|
81
76
|
end
|
82
77
|
end
|
@@ -1,22 +1,21 @@
|
|
1
1
|
module Clearance
|
2
2
|
module Testing
|
3
3
|
module Helpers
|
4
|
-
def
|
5
|
-
|
6
|
-
|
4
|
+
def setup_controller_request_and_response
|
5
|
+
super
|
6
|
+
@request.env[:clearance] = Clearance::Session.new(@request.env)
|
7
7
|
end
|
8
8
|
|
9
9
|
def sign_in
|
10
10
|
sign_in_as FactoryGirl.create(:user)
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
14
|
-
@controller.current_user =
|
13
|
+
def sign_in_as(user)
|
14
|
+
@controller.current_user = user
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
|
19
|
-
@request.env[:clearance] = Clearance::Session.new(@request.env)
|
17
|
+
def sign_out
|
18
|
+
@controller.current_user = nil
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
data/lib/clearance/user.rb
CHANGED
@@ -4,155 +4,89 @@ module Clearance
|
|
4
4
|
module User
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
# Hook for all Clearance::User modules.
|
8
|
-
#
|
9
|
-
# If you need to override parts of Clearance::User,
|
10
|
-
# extend and include à la carte.
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# include Clearance::User::Callbacks
|
14
|
-
#
|
15
|
-
# @see Validations
|
16
|
-
# @see Callbacks
|
17
7
|
included do
|
18
8
|
attr_accessor :password_changing
|
19
9
|
attr_reader :password
|
20
10
|
|
21
11
|
include Validations
|
22
12
|
include Callbacks
|
23
|
-
|
24
|
-
|
13
|
+
include (Clearance.configuration.password_strategy ||
|
14
|
+
Clearance::PasswordStrategies::BCrypt)
|
25
15
|
end
|
26
16
|
|
27
17
|
module ClassMethods
|
28
|
-
# Authenticate with email and password.
|
29
|
-
#
|
30
|
-
# @param [String, String] email and password
|
31
|
-
# @return [User, nil] authenticated user or nil
|
32
|
-
# @example
|
33
|
-
# User.authenticate("email@example.com", "password")
|
34
18
|
def authenticate(email, password)
|
35
|
-
|
36
|
-
|
19
|
+
if user = find_by_email(email.to_s.downcase)
|
20
|
+
if user.authenticated? password
|
21
|
+
return user
|
22
|
+
end
|
23
|
+
end
|
37
24
|
end
|
38
25
|
end
|
39
26
|
|
40
27
|
module Validations
|
41
28
|
extend ActiveSupport::Concern
|
42
29
|
|
43
|
-
# Hook for validations.
|
44
|
-
#
|
45
|
-
# :email must be present, unique, formatted
|
46
|
-
#
|
47
|
-
# If password is required,
|
48
|
-
# :password must be present, confirmed
|
49
30
|
included do
|
50
|
-
validates_presence_of
|
51
|
-
validates_uniqueness_of
|
52
|
-
validates_format_of
|
31
|
+
validates_presence_of :email, :unless => :email_optional?
|
32
|
+
validates_uniqueness_of :email, :allow_blank => true
|
33
|
+
validates_format_of :email, :with => %r{^[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$}i, :allow_blank => true
|
53
34
|
|
54
|
-
validates_presence_of
|
35
|
+
validates_presence_of :password, :unless => :password_optional?
|
55
36
|
end
|
56
37
|
end
|
57
38
|
|
58
39
|
module Callbacks
|
59
40
|
extend ActiveSupport::Concern
|
60
41
|
|
61
|
-
# Hook for callbacks.
|
62
|
-
#
|
63
|
-
# salt, token, password encryption are handled before_save.
|
64
42
|
included do
|
65
43
|
before_validation :downcase_email
|
66
44
|
before_create :generate_remember_token
|
67
45
|
end
|
68
46
|
end
|
69
47
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
def remember_me!
|
74
|
-
warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
|
75
|
-
reset_remember_token!
|
48
|
+
def forgot_password!
|
49
|
+
generate_confirmation_token
|
50
|
+
save :validate => false
|
76
51
|
end
|
77
52
|
|
78
|
-
# Reset the remember token.
|
79
|
-
#
|
80
|
-
# @example
|
81
|
-
# user.reset_remember_token!
|
82
53
|
def reset_remember_token!
|
83
54
|
generate_remember_token
|
84
|
-
save
|
85
|
-
end
|
86
|
-
|
87
|
-
# Mark my account as forgotten password.
|
88
|
-
#
|
89
|
-
# @example
|
90
|
-
# user.forgot_password!
|
91
|
-
def forgot_password!
|
92
|
-
generate_confirmation_token
|
93
|
-
save(:validate => false)
|
55
|
+
save :validate => false
|
94
56
|
end
|
95
57
|
|
96
|
-
# Update my password.
|
97
|
-
#
|
98
|
-
# @return [true, false] password was updated or not
|
99
|
-
# @example
|
100
|
-
# user.update_password('new-password')
|
101
58
|
def update_password(new_password)
|
102
59
|
self.password_changing = true
|
103
|
-
self.password
|
60
|
+
self.password = new_password
|
61
|
+
|
104
62
|
if valid?
|
105
63
|
self.confirmation_token = nil
|
106
64
|
generate_remember_token
|
107
65
|
end
|
108
|
-
save
|
109
|
-
end
|
110
66
|
|
111
|
-
|
112
|
-
@password = unencrypted_password
|
113
|
-
encrypt_password
|
67
|
+
save
|
114
68
|
end
|
115
69
|
|
116
|
-
|
70
|
+
private
|
117
71
|
|
118
|
-
def
|
119
|
-
|
120
|
-
SecureRandom.hex(length).encode('UTF-8')
|
121
|
-
else
|
122
|
-
SecureRandom.hex(length)
|
123
|
-
end
|
72
|
+
def downcase_email
|
73
|
+
self.email = email.to_s.downcase
|
124
74
|
end
|
125
75
|
|
126
|
-
def
|
127
|
-
|
76
|
+
def email_optional?
|
77
|
+
false
|
128
78
|
end
|
129
79
|
|
130
80
|
def generate_confirmation_token
|
131
|
-
self.confirmation_token =
|
81
|
+
self.confirmation_token = SecureRandom.hex(20).encode('UTF-8')
|
132
82
|
end
|
133
83
|
|
134
|
-
|
135
|
-
|
136
|
-
# @return [Boolean] true if the email field be left blank for this user
|
137
|
-
def email_optional?
|
138
|
-
false
|
84
|
+
def generate_remember_token
|
85
|
+
self.remember_token = SecureRandom.hex(20).encode('UTF-8')
|
139
86
|
end
|
140
87
|
|
141
|
-
# True if the password has been set and the password is not being
|
142
|
-
# updated and we are not updating the password. Override to allow
|
143
|
-
# other forms of authentication (username, facebook, etc).
|
144
|
-
# @return [Boolean] true if the password field can be left blank for this user
|
145
88
|
def password_optional?
|
146
89
|
encrypted_password.present? && password.blank? && password_changing.blank?
|
147
90
|
end
|
148
|
-
|
149
|
-
def password_required?
|
150
|
-
# warn "[DEPRECATION] password_required?: use !password_optional? instead"
|
151
|
-
!password_optional?
|
152
|
-
end
|
153
|
-
|
154
|
-
def downcase_email
|
155
|
-
self.email = email.to_s.downcase
|
156
|
-
end
|
157
91
|
end
|
158
92
|
end
|
data/lib/clearance/version.rb
CHANGED
@@ -1,38 +1,36 @@
|
|
1
|
-
class UpgradeClearanceToDiesel < ActiveRecord::Migration
|
2
|
-
def self.up
|
3
1
|
<%
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
2
|
+
existing_columns = ActiveRecord::Base.connection.columns(:users).map(&:name)
|
3
|
+
new_columns = {
|
4
|
+
:email => 't.string :email',
|
5
|
+
:encrypted_password => 't.string :encrypted_password, :limit => 128',
|
6
|
+
:confirmation_token => 't.string :confirmation_token, :limit => 128',
|
7
|
+
:remember_token => 't.string :remember_token, :limit => 128'
|
8
|
+
}.reject { |column| existing_columns.include?(column.to_s) }
|
9
|
+
-%>
|
10
|
+
<%
|
11
|
+
existing_indexes = ActiveRecord::Base.connection.indexes(:users).map(&:name)
|
12
|
+
new_indexes = {
|
13
|
+
:index_users_on_email => 'add_index :users, :email',
|
14
|
+
:index_users_on_remember_token => 'add_index :users, :remember_token'
|
15
|
+
}.reject { |index| existing_indexes.include?(index.to_s) }
|
12
16
|
-%>
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
class UpgradeClearanceToDiesel < ActiveRecord::Migration
|
18
|
+
def self.up
|
19
|
+
change_table :users do |t|
|
20
|
+
<% new_columns.values.each do |column| -%>
|
21
|
+
<%= column %>
|
16
22
|
<% end -%>
|
17
23
|
end
|
18
24
|
|
19
|
-
<%
|
20
|
-
|
21
|
-
index_names = existing_indexes.collect { |each| each.name }
|
22
|
-
new_indexes = [
|
23
|
-
[:index_users_on_email, 'add_index :users, :email'],
|
24
|
-
[:index_users_on_remember_token, 'add_index :users, :remember_token']
|
25
|
-
].delete_if { |each| index_names.include?(each.first.to_s) }
|
26
|
-
-%>
|
27
|
-
<% new_indexes.each do |each| -%>
|
28
|
-
<%= each.last %>
|
25
|
+
<% new_indexes.values.each do |index| -%>
|
26
|
+
<%= index %>
|
29
27
|
<% end -%>
|
30
28
|
end
|
31
29
|
|
32
30
|
def self.down
|
33
|
-
change_table
|
34
|
-
<%
|
35
|
-
t.remove <%=
|
31
|
+
change_table :users do |t|
|
32
|
+
<% if new_columns.any? -%>
|
33
|
+
t.remove <%= new_columns.keys.map { |column| ":#{column}" }.join(',') %>
|
36
34
|
<% end -%>
|
37
35
|
end
|
38
36
|
end
|