clearance 2.0.0.beta1 → 2.2.1

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -14
  3. data/Appraisals +11 -3
  4. data/Gemfile +1 -3
  5. data/Gemfile.lock +91 -86
  6. data/NEWS.md +86 -4
  7. data/README.md +54 -28
  8. data/app/controllers/clearance/base_controller.rb +8 -1
  9. data/app/controllers/clearance/passwords_controller.rb +23 -5
  10. data/clearance.gemspec +15 -9
  11. data/config/locales/clearance.en.yml +1 -0
  12. data/config/routes.rb +1 -1
  13. data/gemfiles/rails_5.0.gemfile +3 -3
  14. data/gemfiles/rails_5.1.gemfile +3 -3
  15. data/gemfiles/rails_5.2.gemfile +3 -3
  16. data/gemfiles/{rails_4.2.gemfile → rails_6.0.gemfile} +5 -4
  17. data/lib/clearance/authentication.rb +1 -1
  18. data/lib/clearance/back_door.rb +1 -1
  19. data/lib/clearance/configuration.rb +30 -19
  20. data/lib/clearance/password_strategies.rb +5 -4
  21. data/lib/clearance/password_strategies/argon2.rb +23 -0
  22. data/lib/clearance/password_strategies/bcrypt.rb +17 -11
  23. data/lib/clearance/rack_session.rb +5 -1
  24. data/lib/clearance/session.rb +39 -3
  25. data/lib/clearance/testing/deny_access_matcher.rb +1 -5
  26. data/lib/clearance/user.rb +12 -3
  27. data/lib/clearance/version.rb +1 -1
  28. data/lib/generators/clearance/install/install_generator.rb +11 -7
  29. data/lib/generators/clearance/install/templates/README +10 -4
  30. data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb.erb +1 -1
  31. data/lib/generators/clearance/install/templates/db/migrate/create_users.rb.erb +1 -1
  32. data/lib/generators/clearance/routes/templates/routes.rb +1 -1
  33. data/spec/acceptance/clearance_installation_spec.rb +0 -4
  34. data/spec/app_templates/app/models/user.rb +1 -1
  35. data/spec/app_templates/testapp/app/controllers/home_controller.rb +1 -5
  36. data/spec/app_templates/testapp/app/views/layouts/application.html.erb +24 -0
  37. data/spec/clearance/back_door_spec.rb +12 -6
  38. data/spec/clearance/rack_session_spec.rb +2 -0
  39. data/spec/clearance/session_spec.rb +91 -16
  40. data/spec/clearance/testing/deny_access_matcher_spec.rb +32 -0
  41. data/spec/configuration_spec.rb +46 -15
  42. data/spec/controllers/passwords_controller_spec.rb +36 -0
  43. data/spec/controllers/permissions_controller_spec.rb +1 -1
  44. data/spec/dummy/app/controllers/application_controller.rb +1 -5
  45. data/spec/dummy/application.rb +7 -1
  46. data/spec/generators/clearance/install/install_generator_spec.rb +31 -6
  47. data/spec/generators/clearance/views/views_generator_spec.rb +0 -2
  48. data/spec/models/user_spec.rb +34 -5
  49. data/spec/password_strategies/argon2_spec.rb +79 -0
  50. data/spec/password_strategies/bcrypt_spec.rb +18 -1
  51. data/spec/requests/authentication_cookie_spec.rb +55 -0
  52. data/spec/requests/token_expiration_spec.rb +5 -0
  53. data/spec/spec_helper.rb +4 -7
  54. data/spec/support/generator_spec_helpers.rb +1 -9
  55. metadata +52 -26
  56. data/app/views/layouts/application.html.erb +0 -23
  57. data/spec/app_templates/app/models/rails5/user.rb +0 -5
  58. data/spec/support/environment.rb +0 -12
  59. data/spec/support/http_method_shim.rb +0 -25
@@ -78,12 +78,8 @@ module Clearance
78
78
  @controller.request.env[:clearance]
79
79
  end
80
80
 
81
- def flash_alert
82
- @controller.flash[:alert]
83
- end
84
-
85
81
  def flash_alert_value
86
- flash_alert.values.first
82
+ @controller.flash[:alert]
87
83
  end
88
84
 
89
85
  def redirects_to_url?
@@ -60,7 +60,7 @@ module Clearance
60
60
  # @see PasswordStrategies
61
61
  # @return [void]
62
62
  #
63
- # @!method authenticated?
63
+ # @!method authenticated?(password)
64
64
  # Check's the provided password against the user's encrypted password using
65
65
  # the configured password strategy. By default, this will be
66
66
  # {PasswordStrategies::BCrypt#authenticated?}, but can be changed with
@@ -117,11 +117,13 @@ module Clearance
117
117
  if password.present? && user.authenticated?(password)
118
118
  user
119
119
  end
120
+ else
121
+ prevent_timing_attack
120
122
  end
121
123
  end
122
124
 
123
125
  def find_by_normalized_email(email)
124
- find_by_email normalize_email(email)
126
+ find_by(email: normalize_email(email))
125
127
  end
126
128
 
127
129
  def normalize_email(email)
@@ -130,6 +132,13 @@ module Clearance
130
132
 
131
133
  private
132
134
 
135
+ DUMMY_PASSWORD = "*"
136
+
137
+ def prevent_timing_attack
138
+ new(password: DUMMY_PASSWORD)
139
+ nil
140
+ end
141
+
133
142
  def password_strategy
134
143
  Clearance.configuration.password_strategy || PasswordStrategies::BCrypt
135
144
  end
@@ -143,7 +152,7 @@ module Clearance
143
152
  validates :email,
144
153
  email: { strict_mode: true },
145
154
  presence: true,
146
- uniqueness: { allow_blank: true },
155
+ uniqueness: { allow_blank: true, case_sensitive: false },
147
156
  unless: :email_optional?
148
157
 
149
158
  validates :password, presence: true, unless: :skip_password_validation?
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = "2.0.0.beta1".freeze
2
+ VERSION = "2.2.1".freeze
3
3
  end
@@ -119,17 +119,21 @@ module Clearance
119
119
  end
120
120
 
121
121
  def migration_version
122
- if Rails.version >= "5.0.0"
123
- "[#{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}"
124
128
  end
125
129
  end
126
130
 
131
+ def configured_key_type
132
+ Rails.configuration.generators.active_record[:primary_key_type]
133
+ end
134
+
127
135
  def models_inherit_from
128
- if Rails.version >= "5.0.0"
129
- "ApplicationRecord"
130
- else
131
- "ActiveRecord::Base"
132
- end
136
+ "ApplicationRecord"
133
137
  end
134
138
  end
135
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
@@ -238,17 +269,31 @@ describe Clearance::Session do
238
269
  end
239
270
  end
240
271
 
241
- describe 'cookie domain option' do
242
- context 'when set' do
272
+ describe "cookie domain option" do
273
+ context "when set" do
243
274
  before do
244
- Clearance.configuration.cookie_domain = '.example.com'
275
+ Clearance.configuration.cookie_domain = cookie_domain
245
276
  session.sign_in(user)
246
277
  end
247
278
 
248
- it 'sets a standard cookie' do
249
- session.add_cookie_to_headers(headers)
279
+ context "with string" do
280
+ let(:cookie_domain) { ".example.com" }
250
281
 
251
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
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" } }
291
+
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
252
297
  end
253
298
  end
254
299
 
@@ -258,7 +303,7 @@ describe Clearance::Session do
258
303
  it 'sets a standard cookie' do
259
304
  session.add_cookie_to_headers(headers)
260
305
 
261
- expect(headers['Set-Cookie']).not_to match(/domain=.+; path/)
306
+ expect(headers["Set-Cookie"]).not_to match(/domain=.+; path/)
262
307
  end
263
308
  end
264
309
  end
@@ -270,7 +315,7 @@ describe Clearance::Session do
270
315
  it 'sets a standard cookie' do
271
316
  session.add_cookie_to_headers(headers)
272
317
 
273
- expect(headers['Set-Cookie']).to_not match(/domain=.+; path/)
318
+ expect(headers["Set-Cookie"]).to_not match(/domain=.+; path/)
274
319
  end
275
320
  end
276
321
 
@@ -295,14 +340,44 @@ describe Clearance::Session do
295
340
  expect(headers["Set-Cookie"]).to be nil
296
341
  end
297
342
 
298
- it 'signs out a user' do
299
- user = create(:user)
300
- old_remember_token = user.remember_token
301
- env = env_with_remember_token(old_remember_token)
302
- session = Clearance::Session.new(env)
303
- session.sign_out
304
- expect(session.current_user).to be_nil
305
- 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
306
381
  end
307
382
 
308
383
  def env_with_cookies(cookies)
@@ -0,0 +1,32 @@
1
+ require "spec_helper"
2
+
3
+ class PretendFriendsController < ActionController::Base
4
+ include Clearance::Controller
5
+ before_action :require_login
6
+
7
+ def index
8
+ end
9
+ end
10
+
11
+ describe PretendFriendsController, type: :controller do
12
+ before do
13
+ Rails.application.routes.draw do
14
+ resources :pretend_friends, only: :index
15
+ get "/sign_in" => "clearance/sessions#new", as: "sign_in"
16
+ end
17
+ end
18
+
19
+ after do
20
+ Rails.application.reload_routes!
21
+ end
22
+
23
+ it "checks contents of deny access flash" do
24
+ get :index
25
+
26
+ expect(subject).to deny_access(flash: failure_message)
27
+ end
28
+
29
+ def failure_message
30
+ I18n.t("flashes.failure_when_not_signed_in")
31
+ end
32
+ end