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
@@ -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
@@ -1,6 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe Clearance::Configuration do
4
+ let(:config) { Clearance.configuration }
5
+
4
6
  context "when no user_model_name is specified" do
5
7
  it "defaults to User" do
6
8
  expect(Clearance.configuration.user_model).to eq ::User
@@ -8,12 +10,47 @@ describe Clearance::Configuration do
8
10
  end
9
11
 
10
12
  context "when a custom user_model_name is specified" do
11
- it "is used instead of User" do
13
+ before(:each) do
12
14
  MyUser = Class.new
15
+ end
16
+
17
+ after(:each) do
18
+ Object.send(:remove_const, :MyUser)
19
+ end
20
+
21
+ it "is used instead of User" do
13
22
  Clearance.configure { |config| config.user_model = MyUser }
14
23
 
15
24
  expect(Clearance.configuration.user_model).to eq ::MyUser
16
25
  end
26
+
27
+ it "can be specified as a string to avoid triggering autoloading" do
28
+ Clearance.configure { |config| config.user_model = "MyUser" }
29
+
30
+ expect(Clearance.configuration.user_model).to eq ::MyUser
31
+ end
32
+ end
33
+
34
+ context "when no parent_controller is specified" do
35
+ it "defaults to ApplicationController" do
36
+ expect(config.parent_controller).to eq ::ApplicationController
37
+ end
38
+ end
39
+
40
+ context "when a custom parent_controller is specified" do
41
+ before(:each) do
42
+ MyController = Class.new
43
+ end
44
+
45
+ after(:each) do
46
+ Object.send(:remove_const, :MyController)
47
+ end
48
+
49
+ it "is used instead of ApplicationController" do
50
+ Clearance.configure { |config| config.parent_controller = MyController }
51
+
52
+ expect(config.parent_controller).to eq ::MyController
53
+ end
17
54
  end
18
55
 
19
56
  context "when secure_cookie is set to true" do
@@ -146,28 +183,22 @@ describe Clearance::Configuration do
146
183
  end
147
184
 
148
185
  describe "#rotate_csrf_on_sign_in?" do
149
- it "defaults to falsey and warns" do
150
- Clearance.configuration = Clearance::Configuration.new
151
- allow(Clearance.configuration).to receive(:warn)
152
-
153
- expect(Clearance.configuration.rotate_csrf_on_sign_in?).to be_falsey
154
- expect(Clearance.configuration).to have_received(:warn)
155
- end
156
-
157
- it "is true and does not warn when `rotate_csrf_on_sign_in` is true" do
186
+ it "is true when `rotate_csrf_on_sign_in` is set to true" do
158
187
  Clearance.configure { |config| config.rotate_csrf_on_sign_in = true }
159
- allow(Clearance.configuration).to receive(:warn)
160
188
 
161
189
  expect(Clearance.configuration.rotate_csrf_on_sign_in?).to be true
162
- expect(Clearance.configuration).not_to have_received(:warn)
163
190
  end
164
191
 
165
- it "is false and does not warn when `rotate_csrf_on_sign_in` is false" do
192
+ it "is false when `rotate_csrf_on_sign_in` is set to false" do
166
193
  Clearance.configure { |config| config.rotate_csrf_on_sign_in = false }
167
- allow(Clearance.configuration).to receive(:warn)
168
194
 
169
195
  expect(Clearance.configuration.rotate_csrf_on_sign_in?).to be false
170
- expect(Clearance.configuration).not_to have_received(:warn)
196
+ end
197
+
198
+ it "is false when `rotate_csrf_on_sign_in` is set to nil" do
199
+ Clearance.configure { |config| config.rotate_csrf_on_sign_in = nil }
200
+
201
+ expect(Clearance.configuration.rotate_csrf_on_sign_in?).to be false
171
202
  end
172
203
  end
173
204
  end
@@ -3,11 +3,7 @@ require 'spec_helper'
3
3
  class ApisController < ActionController::Base
4
4
  include Clearance::Controller
5
5
 
6
- if respond_to?(:before_action)
7
- before_action :require_login
8
- else
9
- before_filter :require_login
10
- end
6
+ before_action :require_login
11
7
 
12
8
  def show
13
9
  head :ok
@@ -5,11 +5,7 @@ class ForgeriesController < ActionController::Base
5
5
 
6
6
  protect_from_forgery
7
7
 
8
- if respond_to?(:before_action)
9
- before_action :require_login
10
- else
11
- before_filter :require_login
12
- end
8
+ before_action :require_login
13
9
 
14
10
  # This is off in test by default, but we need it for this test
15
11
  self.allow_forgery_protection = true
@@ -37,6 +37,30 @@ describe Clearance::PasswordsController do
37
37
  end
38
38
  end
39
39
 
40
+ context "email param is missing" do
41
+ it "displays flash error on new page" do
42
+ post :create, params: {
43
+ password: {},
44
+ }
45
+
46
+ expect(flash.now[:alert]).to match(/email can't be blank/i)
47
+ expect(response).to render_template(:new)
48
+ end
49
+ end
50
+
51
+ context "email param is blank" do
52
+ it "displays flash error on new page" do
53
+ post :create, params: {
54
+ password: {
55
+ email: "",
56
+ }
57
+ }
58
+
59
+ expect(flash.now[:alert]).to match(/email can't be blank/i)
60
+ expect(response).to render_template(:new)
61
+ end
62
+ end
63
+
40
64
  context "email does not belong to an existing user" do
41
65
  it "does not deliver an email" do
42
66
  ActionMailer::Base.deliveries.clear
@@ -94,19 +118,19 @@ describe Clearance::PasswordsController do
94
118
  end
95
119
 
96
120
  context "blank token is supplied" do
97
- it "renders the new password reset form with a flash notice" do
121
+ it "renders the new password reset form with a flash alert" do
98
122
  get :edit, params: {
99
123
  user_id: 1,
100
124
  token: "",
101
125
  }
102
126
 
103
127
  expect(response).to render_template(:new)
104
- expect(flash.now[:notice]).to match(/double check the URL/i)
128
+ expect(flash.now[:alert]).to match(/double check the URL/i)
105
129
  end
106
130
  end
107
131
 
108
132
  context "invalid token is supplied" do
109
- it "renders the new password reset form with a flash notice" do
133
+ it "renders the new password reset form with a flash alert" do
110
134
  user = create(:user, :with_forgotten_password)
111
135
 
112
136
  get :edit, params: {
@@ -115,7 +139,7 @@ describe Clearance::PasswordsController do
115
139
  }
116
140
 
117
141
  expect(response).to render_template(:new)
118
- expect(flash.now[:notice]).to match(/double check the URL/i)
142
+ expect(flash.now[:alert]).to match(/double check the URL/i)
119
143
  end
120
144
  end
121
145
 
@@ -166,6 +190,18 @@ describe Clearance::PasswordsController do
166
190
  expect(user.confirmation_token).to be_present
167
191
  end
168
192
 
193
+ it "does not raise NoMethodError from incomplete password_reset params" do
194
+ user = create(:user, :with_forgotten_password)
195
+
196
+ expect do
197
+ put :update, params: {
198
+ user_id: user,
199
+ token: user.confirmation_token,
200
+ password_reset: {},
201
+ }
202
+ end.not_to raise_error
203
+ end
204
+
169
205
  it "re-renders the password edit form" do
170
206
  user = create(:user, :with_forgotten_password)
171
207
 
@@ -174,7 +210,7 @@ describe Clearance::PasswordsController do
174
210
  new_password: "",
175
211
  )
176
212
 
177
- expect(flash.now[:notice]).to match(/password can't be blank/i)
213
+ expect(flash.now[:alert]).to match(/password can't be blank/i)
178
214
  expect(response).to render_template(:edit)
179
215
  expect(cookies[:remember_token]).to be_nil
180
216
  end
@@ -3,11 +3,7 @@ require 'spec_helper'
3
3
  class PermissionsController < ActionController::Base
4
4
  include Clearance::Controller
5
5
 
6
- if respond_to?(:before_action)
7
- before_action :require_login, only: :show
8
- else
9
- before_filter :require_login, only: :show
10
- end
6
+ before_action :require_login, only: :show
11
7
 
12
8
  def new
13
9
  head :ok
@@ -62,14 +58,14 @@ describe PermissionsController do
62
58
  it "denies access to show and display a flash message" do
63
59
  get :show
64
60
 
65
- expect(flash[:notice]).to match(/^Please sign in to continue/)
61
+ expect(flash[:alert]).to match(/^Please sign in to continue/)
66
62
  end
67
63
  end
68
64
 
69
65
  context 'when remember_token is blank' do
70
66
  it 'denies acess to show' do
71
67
  user = create(:user)
72
- user.update_attributes(remember_token: '')
68
+ user.update(remember_token: '')
73
69
  cookies[:remember_token] = ''
74
70
 
75
71
  get :show
@@ -33,7 +33,7 @@ describe Clearance::SessionsController do
33
33
  }
34
34
 
35
35
  expect(response).to render_template(:new)
36
- expect(flash[:notice]).to match(/^Bad email or password/)
36
+ expect(flash[:alert]).to match(/^Bad email or password/)
37
37
  end
38
38
  end
39
39
 
@@ -2,10 +2,6 @@ class ApplicationController < ActionController::Base
2
2
  include Clearance::Controller
3
3
 
4
4
  def show
5
- if Rails::VERSION::MAJOR >= 5
6
- render html: "", layout: "application"
7
- else
8
- render text: "", layout: "application"
9
- end
5
+ render inline: "Hello user #<%= current_user.id %>", layout: false
10
6
  end
11
7
  end
@@ -28,13 +28,17 @@ module Dummy
28
28
  config.secret_key_base = "SECRET_KEY_BASE"
29
29
 
30
30
  if config.active_record.sqlite3.respond_to?(:represent_boolean_as_integer)
31
- config.active_record.sqlite3.represent_boolean_as_integer = true
31
+ if Rails::VERSION::MAJOR < 6
32
+ config.active_record.sqlite3.represent_boolean_as_integer = true
33
+ end
32
34
  end
33
35
 
34
- if config.respond_to?(:active_job)
35
- config.active_job.queue_adapter = :inline
36
+ if Rails::VERSION::MAJOR >= 6
37
+ config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
36
38
  end
37
39
 
40
+ config.active_job.queue_adapter = :inline
41
+
38
42
  def require_environment!
39
43
  initialize!
40
44
  end
@@ -70,7 +70,30 @@ describe Clearance::Generators::InstallGenerator, :generator do
70
70
 
71
71
  expect(migration).to exist
72
72
  expect(migration).to have_correct_syntax
73
- expect(migration).to contain("create_table :users")
73
+ expect(migration).to contain("create_table :users do")
74
+ end
75
+
76
+ context "active record configured for uuid" do
77
+ around do |example|
78
+ preserve_original_primary_key_type_setting do
79
+ Rails.application.config.generators do |g|
80
+ g.orm :active_record, primary_key_type: :uuid
81
+ end
82
+ example.run
83
+ end
84
+ end
85
+
86
+ it "creates a migration to create the users table with key type set" do
87
+ provide_existing_application_controller
88
+ table_does_not_exist(:users)
89
+
90
+ run_generator
91
+ migration = migration_file("db/migrate/create_users.rb")
92
+
93
+ expect(migration).to exist
94
+ expect(migration).to have_correct_syntax
95
+ expect(migration).to contain("create_table :users, id: :uuid do")
96
+ end
74
97
  end
75
98
  end
76
99
 
@@ -118,16 +141,15 @@ describe Clearance::Generators::InstallGenerator, :generator do
118
141
 
119
142
  def table_does_not_exist(name)
120
143
  connection = ActiveRecord::Base.connection
144
+ allow(connection).to receive(:data_source_exists?).
145
+ with(name).
146
+ and_return(false)
147
+ end
121
148
 
122
- if connection.respond_to?(:data_source_exists?)
123
- allow(connection).to receive(:data_source_exists?).
124
- with(name).
125
- and_return(false)
126
- else
127
- allow(connection).to receive(:table_exists?).
128
- with(name).
129
- and_return(false)
130
- end
149
+ def preserve_original_primary_key_type_setting
150
+ original = Rails.configuration.generators.active_record[:primary_key_type]
151
+ yield
152
+ Rails.configuration.generators.active_record[:primary_key_type] = original
131
153
  end
132
154
 
133
155
  def contain_models_inherit_from
@@ -135,10 +157,6 @@ describe Clearance::Generators::InstallGenerator, :generator do
135
157
  end
136
158
 
137
159
  def models_inherit_from
138
- if Rails.version >= "5.0.0"
139
- "ApplicationRecord"
140
- else
141
- "ActiveRecord::Base"
142
- end
160
+ "ApplicationRecord"
143
161
  end
144
162
  end
@@ -8,7 +8,6 @@ describe Clearance::Generators::ViewsGenerator, :generator do
8
8
  views = %w(
9
9
  clearance_mailer/change_password.html.erb
10
10
  clearance_mailer/change_password.text.erb
11
- layouts/application.html.erb
12
11
  passwords/create.html.erb
13
12
  passwords/edit.html.erb
14
13
  passwords/new.html.erb
@@ -22,7 +21,6 @@ describe Clearance::Generators::ViewsGenerator, :generator do
22
21
 
23
22
  view_files.each do |each|
24
23
  expect(each).to exist
25
- expect(each).to have_correct_syntax
26
24
  end
27
25
  end
28
26
 
@@ -5,15 +5,15 @@ describe User do
5
5
  it { is_expected.to have_db_index(:remember_token) }
6
6
  it { is_expected.to validate_presence_of(:email) }
7
7
  it { is_expected.to validate_presence_of(:password) }
8
+ it { is_expected.to allow_value("foo;@example.com").for(:email) }
9
+ it { is_expected.to allow_value("foo@.example.com").for(:email) }
10
+ it { is_expected.to allow_value("foo@example..com").for(:email) }
8
11
  it { is_expected.to allow_value("foo@example.co.uk").for(:email) }
9
12
  it { is_expected.to allow_value("foo@example.com").for(:email) }
10
13
  it { is_expected.to allow_value("foo+bar@example.com").for(:email) }
11
- it { is_expected.not_to allow_value("foo@").for(:email) }
12
- it { is_expected.not_to allow_value("foo@example..com").for(:email) }
13
- it { is_expected.not_to allow_value("foo@.example.com").for(:email) }
14
- it { is_expected.not_to allow_value("foo").for(:email) }
15
14
  it { is_expected.not_to allow_value("example.com").for(:email) }
16
- it { is_expected.not_to allow_value("foo;@example.com").for(:email) }
15
+ it { is_expected.not_to allow_value("foo").for(:email) }
16
+ it { is_expected.not_to allow_value("foo@").for(:email) }
17
17
 
18
18
  describe "#email" do
19
19
  it "stores email in down case and removes whitespace" do
@@ -0,0 +1,79 @@
1
+ require "spec_helper"
2
+
3
+ describe Clearance::PasswordStrategies::Argon2 do
4
+ include FakeModelWithPasswordStrategy
5
+
6
+ describe "#password=" do
7
+ it "encrypts the password into encrypted_password" do
8
+ stub_argon2_password
9
+ model_instance = fake_model_with_argon2_strategy
10
+
11
+ model_instance.password = password
12
+
13
+ expect(model_instance.encrypted_password).to eq encrypted_password
14
+ end
15
+
16
+ it "encrypts with Argon2 using default cost in non test environments" do
17
+ hasher = stub_argon2_password
18
+ model_instance = fake_model_with_argon2_strategy
19
+ allow(Rails).to receive(:env).
20
+ and_return(ActiveSupport::StringInquirer.new("production"))
21
+
22
+ model_instance.password = password
23
+
24
+ expect(hasher).to have_received(:create).with(password)
25
+ end
26
+
27
+ it "encrypts with Argon2 using minimum cost in test environment" do
28
+ hasher = stub_argon2_password
29
+ model_instance = fake_model_with_argon2_strategy
30
+
31
+ model_instance.password = password
32
+
33
+ expect(hasher).to have_received(:create).with(password)
34
+ end
35
+
36
+ def stub_argon2_password
37
+ hasher = double(Argon2::Password)
38
+ allow(hasher).to receive(:create).and_return(encrypted_password)
39
+ allow(Argon2::Password).to receive(:new).and_return(hasher)
40
+ hasher
41
+ end
42
+
43
+ def encrypted_password
44
+ @encrypted_password ||= double("encrypted password")
45
+ end
46
+ end
47
+
48
+ describe "#authenticated?" do
49
+ context "given a password" do
50
+ it "is authenticated with Argon2" do
51
+ model_instance = fake_model_with_argon2_strategy
52
+
53
+ model_instance.password = password
54
+
55
+ expect(model_instance).to be_authenticated(password)
56
+ end
57
+ end
58
+
59
+ context "given no password" do
60
+ it "is not authenticated" do
61
+ model_instance = fake_model_with_argon2_strategy
62
+
63
+ password = nil
64
+
65
+ expect(model_instance).not_to be_authenticated(password)
66
+ end
67
+ end
68
+ end
69
+
70
+ def fake_model_with_argon2_strategy
71
+ @fake_model_with_argon2_strategy ||= fake_model_with_password_strategy(
72
+ Clearance::PasswordStrategies::Argon2,
73
+ )
74
+ end
75
+
76
+ def password
77
+ "password"
78
+ end
79
+ end