clearance 1.17.0 → 2.2.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 (74) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -14
  3. data/Appraisals +11 -3
  4. data/Gemfile +3 -6
  5. data/Gemfile.lock +91 -87
  6. data/NEWS.md +233 -15
  7. data/README.md +54 -28
  8. data/app/controllers/clearance/base_controller.rb +8 -1
  9. data/app/controllers/clearance/passwords_controller.rb +35 -45
  10. data/app/controllers/clearance/sessions_controller.rb +3 -18
  11. data/app/controllers/clearance/users_controller.rb +2 -17
  12. data/clearance.gemspec +15 -9
  13. data/config/locales/clearance.en.yml +1 -0
  14. data/config/routes.rb +1 -1
  15. data/gemfiles/rails_5.0.gemfile +5 -6
  16. data/gemfiles/rails_5.1.gemfile +5 -6
  17. data/gemfiles/rails_5.2.gemfile +5 -6
  18. data/gemfiles/{rails_4.2.gemfile → rails_6.0.gemfile} +7 -7
  19. data/lib/clearance.rb +0 -8
  20. data/lib/clearance/authentication.rb +1 -9
  21. data/lib/clearance/authorization.rb +2 -11
  22. data/lib/clearance/back_door.rb +1 -1
  23. data/lib/clearance/configuration.rb +30 -19
  24. data/lib/clearance/password_strategies.rb +5 -4
  25. data/lib/clearance/password_strategies/argon2.rb +23 -0
  26. data/lib/clearance/password_strategies/bcrypt.rb +17 -11
  27. data/lib/clearance/rack_session.rb +5 -1
  28. data/lib/clearance/session.rb +40 -12
  29. data/lib/clearance/testing/deny_access_matcher.rb +10 -20
  30. data/lib/clearance/user.rb +3 -24
  31. data/lib/clearance/version.rb +1 -1
  32. data/lib/generators/clearance/install/install_generator.rb +12 -12
  33. data/lib/generators/clearance/install/templates/README +10 -4
  34. data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb.erb +1 -1
  35. data/lib/generators/clearance/install/templates/db/migrate/create_users.rb.erb +1 -1
  36. data/lib/generators/clearance/routes/templates/routes.rb +1 -1
  37. data/spec/acceptance/clearance_installation_spec.rb +0 -4
  38. data/spec/app_templates/app/models/user.rb +1 -1
  39. data/spec/app_templates/testapp/app/controllers/home_controller.rb +1 -5
  40. data/spec/app_templates/testapp/app/views/layouts/application.html.erb +24 -0
  41. data/spec/clearance/back_door_spec.rb +12 -6
  42. data/spec/clearance/rack_session_spec.rb +2 -0
  43. data/spec/clearance/session_spec.rb +91 -47
  44. data/spec/clearance/testing/deny_access_matcher_spec.rb +32 -0
  45. data/spec/configuration_spec.rb +46 -15
  46. data/spec/controllers/apis_controller_spec.rb +1 -5
  47. data/spec/controllers/forgeries_controller_spec.rb +1 -5
  48. data/spec/controllers/passwords_controller_spec.rb +41 -5
  49. data/spec/controllers/permissions_controller_spec.rb +3 -7
  50. data/spec/controllers/sessions_controller_spec.rb +1 -1
  51. data/spec/dummy/app/controllers/application_controller.rb +1 -5
  52. data/spec/dummy/application.rb +7 -3
  53. data/spec/generators/clearance/install/install_generator_spec.rb +33 -15
  54. data/spec/generators/clearance/views/views_generator_spec.rb +0 -2
  55. data/spec/models/user_spec.rb +5 -5
  56. data/spec/password_strategies/argon2_spec.rb +79 -0
  57. data/spec/password_strategies/bcrypt_spec.rb +18 -1
  58. data/spec/requests/authentication_cookie_spec.rb +55 -0
  59. data/spec/requests/token_expiration_spec.rb +5 -0
  60. data/spec/spec_helper.rb +4 -7
  61. data/spec/support/generator_spec_helpers.rb +1 -9
  62. metadata +51 -33
  63. data/app/views/layouts/application.html.erb +0 -23
  64. data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +0 -77
  65. data/lib/clearance/password_strategies/blowfish.rb +0 -61
  66. data/lib/clearance/password_strategies/sha1.rb +0 -59
  67. data/lib/clearance/testing.rb +0 -11
  68. data/lib/clearance/testing/helpers.rb +0 -15
  69. data/spec/app_templates/app/models/rails5/user.rb +0 -5
  70. data/spec/password_strategies/bcrypt_migration_from_sha1_spec.rb +0 -122
  71. data/spec/password_strategies/blowfish_spec.rb +0 -61
  72. data/spec/password_strategies/sha1_spec.rb +0 -59
  73. data/spec/support/environment.rb +0 -12
  74. data/spec/support/http_method_shim.rb +0 -25
@@ -8,7 +8,7 @@ module Clearance
8
8
  module Matchers
9
9
  # The `deny_access` matcher is used to assert that a
10
10
  # request is denied access by clearance.
11
- # @option opts [String] :flash The expected flash notice message. Defaults
11
+ # @option opts [String] :flash The expected flash alert message. Defaults
12
12
  # to nil, which means the flash will not be checked.
13
13
  # @option opts [String] :redirect The expected redirect url. Defaults to
14
14
  # `'/'` if signed in or the `sign_in_url` if signed out.
@@ -78,16 +78,8 @@ module Clearance
78
78
  @controller.request.env[:clearance]
79
79
  end
80
80
 
81
- def flash_notice
82
- @controller.flash[:notice]
83
- end
84
-
85
- def flash_notice_value
86
- if flash_notice.respond_to?(:values)
87
- flash_notice.values.first
88
- else
89
- flash_notice
90
- end
81
+ def flash_alert_value
82
+ @controller.flash[:alert]
91
83
  end
92
84
 
93
85
  def redirects_to_url?
@@ -107,16 +99,14 @@ module Clearance
107
99
  def sets_the_flash?
108
100
  if @flash.blank?
109
101
  true
102
+ elsif flash_alert_value == @flash
103
+ @failure_message_when_negated <<
104
+ "Didn't expect to set the flash to #{@flash}"
105
+ true
110
106
  else
111
- if flash_notice_value == @flash
112
- @failure_message_when_negated <<
113
- "Didn't expect to set the flash to #{@flash}"
114
- true
115
- else
116
- @failure_message << "Expected the flash to be set to #{@flash} "\
117
- "but was #{flash_notice_value}"
118
- false
119
- end
107
+ @failure_message << "Expected the flash to be set to #{@flash} "\
108
+ "but was #{flash_alert_value}"
109
+ false
120
110
  end
121
111
  end
122
112
  end
@@ -47,9 +47,6 @@ module Clearance
47
47
  # @return [String] The value used to identify this user in the password
48
48
  # reset link.
49
49
  #
50
- # @!attribute password_changing
51
- # @deprecated Dirty tracking is now handled automatically.
52
- #
53
50
  # @!attribute [r] password
54
51
  # @return [String] Transient (non-persisted) attribute that is set when
55
52
  # updating a user's password. Only the {#encrypted_password} is persisted.
@@ -63,7 +60,7 @@ module Clearance
63
60
  # @see PasswordStrategies
64
61
  # @return [void]
65
62
  #
66
- # @!method authenticated?
63
+ # @!method authenticated?(password)
67
64
  # Check's the provided password against the user's encrypted password using
68
65
  # the configured password strategy. By default, this will be
69
66
  # {PasswordStrategies::BCrypt#authenticated?}, but can be changed with
@@ -111,24 +108,6 @@ module Clearance
111
108
  encrypted_password_will_change!
112
109
  super
113
110
  end
114
-
115
- def password_changing
116
- warn "#{Kernel.caller.first}: [DEPRECATION] " \
117
- "The `password_changing` attribute is deprecated. Clearance uses " \
118
- "the dirty state of the `encrypted_password` field to track this " \
119
- "automatically."
120
-
121
- @password_changing
122
- end
123
-
124
- def password_changing=(value)
125
- warn "#{Kernel.caller.first}: [DEPRECATION] " \
126
- "The `password_changing` attribute is deprecated. Clearance uses " \
127
- "the dirty state of the `encrypted_password` field to track this " \
128
- "automatically."
129
-
130
- @password_changing = value
131
- end
132
111
  end
133
112
 
134
113
  # @api private
@@ -142,7 +121,7 @@ module Clearance
142
121
  end
143
122
 
144
123
  def find_by_normalized_email(email)
145
- find_by_email normalize_email(email)
124
+ find_by(email: normalize_email(email))
146
125
  end
147
126
 
148
127
  def normalize_email(email)
@@ -164,7 +143,7 @@ module Clearance
164
143
  validates :email,
165
144
  email: { strict_mode: true },
166
145
  presence: true,
167
- uniqueness: { allow_blank: true },
146
+ uniqueness: { allow_blank: true, case_sensitive: false },
168
147
  unless: :email_optional?
169
148
 
170
149
  validates :password, presence: true, unless: :skip_password_validation?
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = "1.17.0".freeze
2
+ VERSION = "2.2.0".freeze
3
3
  end
@@ -102,11 +102,7 @@ module Clearance
102
102
  end
103
103
 
104
104
  def users_table_exists?
105
- if ActiveRecord::Base.connection.respond_to?(:data_source_exists?)
106
- ActiveRecord::Base.connection.data_source_exists?(:users)
107
- else
108
- ActiveRecord::Base.connection.table_exists?(:users)
109
- end
105
+ ActiveRecord::Base.connection.data_source_exists?(:users)
110
106
  end
111
107
 
112
108
  def existing_users_columns
@@ -123,17 +119,21 @@ module Clearance
123
119
  end
124
120
 
125
121
  def migration_version
126
- if Rails.version >= "5.0.0"
127
- "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
122
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
123
+ end
124
+
125
+ def migration_primary_key_type_string
126
+ if configured_key_type
127
+ ", id: :#{configured_key_type}"
128
128
  end
129
129
  end
130
130
 
131
+ def configured_key_type
132
+ Rails.configuration.generators.active_record[:primary_key_type]
133
+ end
134
+
131
135
  def models_inherit_from
132
- if Rails.version >= "5.0.0"
133
- "ApplicationRecord"
134
- else
135
- "ActiveRecord::Base"
136
- end
136
+ "ApplicationRecord"
137
137
  end
138
138
  end
139
139
  end
@@ -8,9 +8,11 @@ Next steps:
8
8
  # config/environments/{development,test}.rb
9
9
  config.action_mailer.default_url_options = { host: 'localhost:3000' }
10
10
 
11
- In production it should be your app's domain name.
11
+ In the production environment it should be your application's full hostname.
12
12
 
13
- 2. Display user session and flashes. For example, in your application layout:
13
+ 2. Display user session status.
14
+
15
+ From somewhere in your layout, render sign in and sign out buttons:
14
16
 
15
17
  <% if signed_in? %>
16
18
  Signed in as: <%= current_user.email %>
@@ -19,14 +21,18 @@ Next steps:
19
21
  <%= link_to 'Sign in', sign_in_path %>
20
22
  <% end %>
21
23
 
24
+ 3. Render the flash contents.
25
+
26
+ Make sure the flash is being rendered in your views using something like:
27
+
22
28
  <div id="flash">
23
29
  <% flash.each do |key, value| %>
24
30
  <div class="flash <%= key %>"><%= value %></div>
25
31
  <% end %>
26
32
  </div>
27
33
 
28
- 3. Migrate:
34
+ 4. Migrate:
29
35
 
30
- rails db:migrate
36
+ Run `rails db:migrate` to add the clearance database changes.
31
37
 
32
38
  *******************************************************************************
@@ -13,7 +13,7 @@ class AddClearanceToUsers < ActiveRecord::Migration<%= migration_version %>
13
13
  users = select_all("SELECT id FROM users WHERE remember_token IS NULL")
14
14
 
15
15
  users.each do |user|
16
- update <<-SQL
16
+ update <<-SQL.squish
17
17
  UPDATE users
18
18
  SET remember_token = '#{Clearance::Token.new}'
19
19
  WHERE id = '#{user['id']}'
@@ -1,6 +1,6 @@
1
1
  class CreateUsers < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :users do |t|
3
+ create_table :users<%= migration_primary_key_type_string %> do |t|
4
4
  t.timestamps null: false
5
5
  t.string :email, null: false
6
6
  t.string :encrypted_password, limit: 128, null: false
@@ -4,7 +4,7 @@
4
4
  resources :users, controller: "clearance/users", only: [:create] do
5
5
  resource :password,
6
6
  controller: "clearance/passwords",
7
- only: [:create, :edit, :update]
7
+ only: [:edit, :update]
8
8
  end
9
9
 
10
10
  get "/sign_in" => "clearance/sessions#new", as: "sign_in"
@@ -36,9 +36,6 @@ describe "Clearance Installation" do
36
36
  --skip-keeps
37
37
  --skip-sprockets
38
38
  CMD
39
-
40
- FileUtils.rm_f("public/index.html")
41
- FileUtils.rm_f("app/views/layouts/application.html.erb")
42
39
  end
43
40
 
44
41
  def testapp_templates
@@ -47,7 +44,6 @@ describe "Clearance Installation" do
47
44
 
48
45
  def configure_test_app
49
46
  FileUtils.rm_f("public/index.html")
50
- FileUtils.rm_f("app/views/layouts/application.html.erb")
51
47
  FileUtils.cp_r(testapp_templates, "..")
52
48
  end
53
49
 
@@ -1,4 +1,4 @@
1
- class User < ActiveRecord::Base
1
+ class User < ApplicationRecord
2
2
  def previously_existed?
3
3
  true
4
4
  end
@@ -1,9 +1,5 @@
1
1
  class HomeController < ApplicationController
2
2
  def show
3
- if Rails::VERSION::MAJOR >= 5
4
- render html: "", layout: "application"
5
- else
6
- render text: "", layout: "application"
7
- end
3
+ render html: "", layout: "application"
8
4
  end
9
5
  end
@@ -0,0 +1,24 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <%= javascript_include_tag 'application' %>
5
+ <%= csrf_meta_tag %>
6
+ </head>
7
+ <body>
8
+ <div id="header">
9
+ <% if signed_in? -%>
10
+ <%= button_to t(".sign_out"), sign_out_path, method: :delete %>
11
+ <% else -%>
12
+ <%= link_to t(".sign_in"), sign_in_path %>
13
+ <% end -%>
14
+ </div>
15
+
16
+ <div id="flash">
17
+ <% flash.each do |key, value| -%>
18
+ <div id="flash_<%= key %>"><%=h value %></div>
19
+ <% end %>
20
+ </div>
21
+
22
+ <%= yield %>
23
+ </body>
24
+ </html>
@@ -1,9 +1,6 @@
1
1
  require "spec_helper"
2
- require "support/environment"
3
2
 
4
3
  describe Clearance::BackDoor do
5
- include EnvironmentSupport
6
-
7
4
  it "signs in as a given user" do
8
5
  user_id = "123"
9
6
  user = double("user")
@@ -42,7 +39,7 @@ describe Clearance::BackDoor do
42
39
  end
43
40
 
44
41
  it "can't be used outside the allowed environments" do
45
- with_environment("RAILS_ENV" => "production") do
42
+ with_environment("production") do
46
43
  expect { Clearance::BackDoor.new(mock_app) }.
47
44
  to raise_exception "Can't use auth backdoor outside of configured \
48
45
  environments (test, ci, development).".squish
@@ -55,7 +52,7 @@ describe Clearance::BackDoor do
55
52
  end
56
53
 
57
54
  it "raises an error for a default allowed env" do
58
- with_environment("RAILS_ENV" => "test") do
55
+ with_environment("test") do
59
56
  expect { Clearance::BackDoor.new(mock_app) }.
60
57
  to raise_exception "BackDoor auth is disabled."
61
58
  end
@@ -68,7 +65,7 @@ describe Clearance::BackDoor do
68
65
  end
69
66
 
70
67
  it "can be used with configured allowed environments" do
71
- with_environment("RAILS_ENV" => "demo") do
68
+ with_environment("demo") do
72
69
  user_id = "123"
73
70
  user = double("user")
74
71
  allow(User).to receive(:find).with(user_id).and_return(user)
@@ -100,4 +97,13 @@ describe Clearance::BackDoor do
100
97
  def mock_app
101
98
  lambda { |env| [200, {}, ["okay"]] }
102
99
  end
100
+
101
+ def with_environment(environment)
102
+ original_env = Rails.env
103
+ Rails.env = environment
104
+
105
+ yield
106
+ ensure
107
+ Rails.env = original_env
108
+ end
103
109
  end
@@ -11,6 +11,8 @@ describe Clearance::RackSession do
11
11
  env = Rack::MockRequest.env_for('/')
12
12
  expected_session = "the session"
13
13
  allow(expected_session).to receive(:add_cookie_to_headers)
14
+ allow(expected_session).to receive(:authentication_successful?).
15
+ and_return(true)
14
16
  allow(Clearance::Session).to receive(:new).
15
17
  with(env).
16
18
  and_return(expected_session)
@@ -129,6 +129,12 @@ describe Clearance::Session do
129
129
 
130
130
  def stub_guard_class(guard)
131
131
  double("guard_class").tap do |guard_class|
132
+ allow(guard_class).to receive(:to_s).
133
+ and_return(guard_class)
134
+
135
+ allow(guard_class).to receive(:constantize).
136
+ and_return(guard_class)
137
+
132
138
  allow(guard_class).to receive(:new).
133
139
  with(session, stub_default_sign_in_guard).
134
140
  and_return(guard)
@@ -170,6 +176,31 @@ describe Clearance::Session do
170
176
  end
171
177
  end
172
178
 
179
+ context "if same_site is set" do
180
+ before do
181
+ Clearance.configuration.same_site = :lax
182
+ session.sign_in(user)
183
+ end
184
+
185
+ it "sets a same-site cookie" do
186
+ session.add_cookie_to_headers(headers)
187
+
188
+ expect(headers["Set-Cookie"]).to match(/remember_token=.+; SameSite/)
189
+ end
190
+ end
191
+
192
+ context "if same_site is not set" do
193
+ before do
194
+ session.sign_in(user)
195
+ end
196
+
197
+ it "sets a standard cookie" do
198
+ session.add_cookie_to_headers(headers)
199
+
200
+ expect(headers["Set-Cookie"]).to_not match(/remember_token=.+; SameSite/)
201
+ end
202
+ end
203
+
173
204
  describe 'remember token cookie expiration' do
174
205
  context 'default configuration' do
175
206
  it 'is set to 1 year from now' do
@@ -186,37 +217,6 @@ describe Clearance::Session do
186
217
  end
187
218
  end
188
219
 
189
- context 'configured with lambda taking no arguments' do
190
- it 'logs a deprecation warning' do
191
- expiration = -> { Time.now }
192
- with_custom_expiration expiration do
193
- session = Clearance::Session.new(env_without_remember_token)
194
- session.sign_in user
195
- allow(session).to receive(:warn)
196
- session.add_cookie_to_headers headers
197
-
198
- expect(session).to have_received(:warn).once
199
- end
200
- end
201
-
202
- it 'is set to the value of the evaluated lambda' do
203
- expires_at = -> { 1.day.from_now }
204
- with_custom_expiration expires_at do
205
- user = double("User", remember_token: "123abc")
206
- headers = {}
207
- session = Clearance::Session.new(env_without_remember_token)
208
- session.sign_in user
209
- allow(session).to receive(:warn)
210
- session.add_cookie_to_headers headers
211
-
212
- expect(headers).to set_cookie(
213
- 'remember_token',
214
- user.remember_token, expires_at.call
215
- )
216
- end
217
- end
218
- end
219
-
220
220
  context 'configured with lambda taking one argument' do
221
221
  it 'it can use other cookies to set the value of the expires token' do
222
222
  remembered_expires = 12.hours.from_now
@@ -269,17 +269,31 @@ describe Clearance::Session do
269
269
  end
270
270
  end
271
271
 
272
- describe 'cookie domain option' do
273
- context 'when set' do
272
+ describe "cookie domain option" do
273
+ context "when set" do
274
274
  before do
275
- Clearance.configuration.cookie_domain = '.example.com'
275
+ Clearance.configuration.cookie_domain = cookie_domain
276
276
  session.sign_in(user)
277
277
  end
278
278
 
279
- it 'sets a standard cookie' do
280
- session.add_cookie_to_headers(headers)
279
+ context "with string" do
280
+ let(:cookie_domain) { ".example.com" }
281
+
282
+ it "sets a standard cookie" do
283
+ session.add_cookie_to_headers(headers)
284
+
285
+ expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
286
+ end
287
+ end
288
+
289
+ context "with lambda" do
290
+ let(:cookie_domain) { lambda { |_r| ".example.com" } }
281
291
 
282
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
292
+ it "sets a standard cookie" do
293
+ session.add_cookie_to_headers(headers)
294
+
295
+ expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
296
+ end
283
297
  end
284
298
  end
285
299
 
@@ -289,7 +303,7 @@ describe Clearance::Session do
289
303
  it 'sets a standard cookie' do
290
304
  session.add_cookie_to_headers(headers)
291
305
 
292
- expect(headers['Set-Cookie']).not_to match(/domain=.+; path/)
306
+ expect(headers["Set-Cookie"]).not_to match(/domain=.+; path/)
293
307
  end
294
308
  end
295
309
  end
@@ -301,7 +315,7 @@ describe Clearance::Session do
301
315
  it 'sets a standard cookie' do
302
316
  session.add_cookie_to_headers(headers)
303
317
 
304
- expect(headers['Set-Cookie']).to_not match(/domain=.+; path/)
318
+ expect(headers["Set-Cookie"]).to_not match(/domain=.+; path/)
305
319
  end
306
320
  end
307
321
 
@@ -326,14 +340,44 @@ describe Clearance::Session do
326
340
  expect(headers["Set-Cookie"]).to be nil
327
341
  end
328
342
 
329
- it 'signs out a user' do
330
- user = create(:user)
331
- old_remember_token = user.remember_token
332
- env = env_with_remember_token(old_remember_token)
333
- session = Clearance::Session.new(env)
334
- session.sign_out
335
- expect(session.current_user).to be_nil
336
- expect(user.reload.remember_token).not_to eq old_remember_token
343
+ describe "#sign_out" do
344
+ it "signs out a user" do
345
+ user = create(:user)
346
+ old_remember_token = user.remember_token
347
+ env = env_with_remember_token(old_remember_token)
348
+ session = Clearance::Session.new(env)
349
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar
350
+ expect(cookie_jar.deleted?(:remember_token)).to be false
351
+
352
+ session.sign_out
353
+
354
+ expect(cookie_jar.deleted?(:remember_token)).to be true
355
+ expect(session.current_user).to be_nil
356
+ expect(user.reload.remember_token).not_to eq old_remember_token
357
+ end
358
+
359
+ context "with custom cookie domain" do
360
+ let(:domain) { ".example.com" }
361
+
362
+ before do
363
+ Clearance.configuration.cookie_domain = domain
364
+ end
365
+
366
+ it "clears cookie" do
367
+ user = create(:user)
368
+ env = env_with_remember_token(
369
+ value: user.remember_token,
370
+ domain: domain,
371
+ )
372
+ session = Clearance::Session.new(env)
373
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar
374
+ expect(cookie_jar.deleted?(:remember_token, domain: domain)).to be false
375
+
376
+ session.sign_out
377
+
378
+ expect(cookie_jar.deleted?(:remember_token, domain: domain)).to be true
379
+ end
380
+ end
337
381
  end
338
382
 
339
383
  def env_with_cookies(cookies)