clearance 2.2.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.erb-lint.yml +5 -0
  3. data/.github/workflows/tests.yml +52 -0
  4. data/Appraisals +14 -19
  5. data/Gemfile +11 -7
  6. data/Gemfile.lock +112 -65
  7. data/NEWS.md +48 -0
  8. data/README.md +25 -14
  9. data/RELEASING.md +25 -0
  10. data/Rakefile +6 -1
  11. data/app/controllers/clearance/passwords_controller.rb +1 -2
  12. data/app/views/clearance_mailer/change_password.html.erb +2 -2
  13. data/app/views/clearance_mailer/change_password.text.erb +2 -2
  14. data/app/views/passwords/edit.html.erb +1 -1
  15. data/gemfiles/rails_5.0.gemfile +10 -9
  16. data/gemfiles/rails_5.1.gemfile +11 -10
  17. data/gemfiles/rails_5.2.gemfile +11 -10
  18. data/gemfiles/rails_6.0.gemfile +11 -10
  19. data/gemfiles/rails_6.1.gemfile +21 -0
  20. data/lib/clearance/authorization.rb +7 -1
  21. data/lib/clearance/back_door.rb +2 -1
  22. data/lib/clearance/configuration.rb +19 -0
  23. data/lib/clearance/password_strategies.rb +0 -4
  24. data/lib/clearance/rack_session.rb +1 -1
  25. data/lib/clearance/session.rb +24 -12
  26. data/lib/clearance/user.rb +1 -1
  27. data/lib/clearance/version.rb +1 -1
  28. data/lib/generators/clearance/install/install_generator.rb +4 -1
  29. data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb.erb +5 -1
  30. data/spec/clearance/back_door_spec.rb +20 -4
  31. data/spec/clearance/rack_session_spec.rb +1 -2
  32. data/spec/clearance/session_spec.rb +116 -43
  33. data/spec/configuration_spec.rb +28 -0
  34. data/spec/controllers/sessions_controller_spec.rb +13 -0
  35. data/spec/generators/clearance/install/install_generator_spec.rb +8 -2
  36. data/spec/mailers/clearance_mailer_spec.rb +33 -0
  37. data/spec/models/user_spec.rb +2 -2
  38. data/spec/support/clearance.rb +11 -0
  39. data/spec/support/request_with_remember_token.rb +8 -6
  40. metadata +7 -4
  41. data/.travis.yml +0 -28
@@ -152,7 +152,7 @@ module Clearance
152
152
  validates :email,
153
153
  email: { strict_mode: true },
154
154
  presence: true,
155
- uniqueness: { allow_blank: true, case_sensitive: false },
155
+ uniqueness: { allow_blank: true, case_sensitive: true },
156
156
  unless: :email_optional?
157
157
 
158
158
  validates :password, presence: true, unless: :skip_password_validation?
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = "2.2.1".freeze
2
+ VERSION = "2.5.0".freeze
3
3
  end
@@ -129,7 +129,10 @@ module Clearance
129
129
  end
130
130
 
131
131
  def configured_key_type
132
- Rails.configuration.generators.active_record[:primary_key_type]
132
+ active_record = Rails.configuration.generators.active_record
133
+ active_record ||= Rails.configuration.generators.options[:active_record]
134
+
135
+ active_record[:primary_key_type]
133
136
  end
134
137
 
135
138
  def models_inherit_from
@@ -21,7 +21,11 @@ class AddClearanceToUsers < ActiveRecord::Migration<%= migration_version %>
21
21
  end
22
22
  end
23
23
 
24
- def self.down
24
+ def self.down
25
+ <% config[:new_indexes].values.each do |index| -%>
26
+ <%= index.gsub("add_index", "remove_index") %>
27
+ <% end -%>
28
+
25
29
  change_table :users do |t|
26
30
  <% if config[:new_columns].any? -%>
27
31
  t.remove <%= new_columns.keys.map { |column| ":#{column}" }.join(", ") %>
@@ -46,6 +46,18 @@ describe Clearance::BackDoor do
46
46
  end
47
47
  end
48
48
 
49
+ it "strips 'as' from the params" do
50
+ user_id = "123"
51
+ user = double("user")
52
+ allow(User).to receive(:find).with(user_id).and_return(user)
53
+ env = build_env(as: user_id, foo: :bar)
54
+ back_door = Clearance::BackDoor.new(mock_app)
55
+
56
+ back_door.call(env)
57
+
58
+ expect(env["QUERY_STRING"]).to eq("foo=bar")
59
+ end
60
+
49
61
  context "when the environments are disabled" do
50
62
  before do
51
63
  Clearance.configuration.allowed_backdoor_environments = nil
@@ -84,14 +96,18 @@ describe Clearance::BackDoor do
84
96
  env_for_user_id("")
85
97
  end
86
98
 
87
- def env_for_user_id(user_id)
99
+ def build_env(params)
100
+ query = Rack::Utils.build_query(params)
88
101
  clearance = double("clearance", sign_in: true)
89
- Rack::MockRequest.env_for("/?as=#{user_id}").merge(clearance: clearance)
102
+ Rack::MockRequest.env_for("/?#{query}").merge(clearance: clearance)
103
+ end
104
+
105
+ def env_for_user_id(user_id)
106
+ build_env(as: user_id)
90
107
  end
91
108
 
92
109
  def env_for_username(username)
93
- clearance = double("clearance", sign_in: true)
94
- Rack::MockRequest.env_for("/?as=#{username}").merge(clearance: clearance)
110
+ build_env(as: username)
95
111
  end
96
112
 
97
113
  def mock_app
@@ -20,7 +20,6 @@ describe Clearance::RackSession do
20
20
  response = Rack::MockResponse.new(*app.call(env))
21
21
 
22
22
  expect(response.body).to eq expected_session
23
- expect(expected_session).to have_received(:add_cookie_to_headers).
24
- with(hash_including(headers))
23
+ expect(expected_session).to have_received(:add_cookie_to_headers)
25
24
  end
26
25
  end
@@ -4,7 +4,6 @@ describe Clearance::Session do
4
4
  before { Timecop.freeze }
5
5
  after { Timecop.return }
6
6
 
7
- let(:headers) { {} }
8
7
  let(:session) { Clearance::Session.new(env_without_remember_token) }
9
8
  let(:user) { create(:user) }
10
9
 
@@ -35,9 +34,63 @@ describe Clearance::Session do
35
34
  Clearance.configuration.cookie_name = "custom_cookie_name"
36
35
 
37
36
  session.sign_in user
38
- session.add_cookie_to_headers(headers)
37
+ session.add_cookie_to_headers
39
38
 
40
- expect(headers["Set-Cookie"]).to match(/custom_cookie_name=.+;/)
39
+ expect(remember_token_cookie(session, "custom_cookie_name")).to be_present
40
+ end
41
+ end
42
+
43
+ context "with signed cookies == false" do
44
+ it "uses cookies.signed" do
45
+ Clearance.configuration.signed_cookie = true
46
+
47
+ cookie_jar = {}
48
+ expect(session).to receive(:cookies).and_return(cookie_jar)
49
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
50
+
51
+ session.sign_in user
52
+ end
53
+ end
54
+
55
+ context "with signed cookies == true" do
56
+ it "uses cookies.signed" do
57
+ Clearance.configuration.signed_cookie = true
58
+
59
+ cookie_jar = {}
60
+ expect(session).to receive(:cookies).and_return(cookie_jar)
61
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
62
+
63
+ session.sign_in user
64
+ end
65
+ end
66
+
67
+ context "with signed cookies == :migrate" do
68
+ before do
69
+ Clearance.configuration.signed_cookie = :migrate
70
+ end
71
+
72
+ context "signed cookie exists" do
73
+ it "uses cookies.signed[remember_token]" do
74
+ cookie_jar = { "remember_token" => "signed cookie" }
75
+ expect(session).to receive(:cookies).and_return(cookie_jar)
76
+ expect(cookie_jar).to receive(:signed).and_return(cookie_jar)
77
+
78
+ session.sign_in user
79
+ end
80
+ end
81
+
82
+ context "signed cookie does not exist yet" do
83
+ it "uses cookies[remember_token] instead" do
84
+ cookie_jar = { "remember_token" => "signed cookie" }
85
+ # first call will try to get the signed cookie
86
+ expect(session).to receive(:cookies).and_return(cookie_jar)
87
+ # ... but signed_cookie doesn't exist
88
+ expect(cookie_jar).to receive(:signed).and_return({})
89
+ # then it attempts to retrieve the unsigned cookie
90
+ expect(session).to receive(:cookies).and_return(cookie_jar)
91
+
92
+ session.sign_in user
93
+ end
41
94
  end
42
95
  end
43
96
 
@@ -157,9 +210,9 @@ describe Clearance::Session do
157
210
  end
158
211
 
159
212
  it 'sets a httponly cookie' do
160
- session.add_cookie_to_headers(headers)
213
+ session.add_cookie_to_headers
161
214
 
162
- expect(headers['Set-Cookie']).to match(/remember_token=.+; HttpOnly/)
215
+ expect(remember_token_cookie(session)[:httponly]).to be_truthy
163
216
  end
164
217
  end
165
218
 
@@ -170,9 +223,9 @@ describe Clearance::Session do
170
223
  end
171
224
 
172
225
  it 'sets a standard cookie' do
173
- session.add_cookie_to_headers(headers)
226
+ session.add_cookie_to_headers
174
227
 
175
- expect(headers['Set-Cookie']).not_to match(/remember_token=.+; HttpOnly/)
228
+ expect(remember_token_cookie(session)[:httponly]).to be_falsey
176
229
  end
177
230
  end
178
231
 
@@ -183,9 +236,9 @@ describe Clearance::Session do
183
236
  end
184
237
 
185
238
  it "sets a same-site cookie" do
186
- session.add_cookie_to_headers(headers)
239
+ session.add_cookie_to_headers
187
240
 
188
- expect(headers["Set-Cookie"]).to match(/remember_token=.+; SameSite/)
241
+ expect(remember_token_cookie(session)[:same_site]).to eq(:lax)
189
242
  end
190
243
  end
191
244
 
@@ -195,9 +248,9 @@ describe Clearance::Session do
195
248
  end
196
249
 
197
250
  it "sets a standard cookie" do
198
- session.add_cookie_to_headers(headers)
251
+ session.add_cookie_to_headers
199
252
 
200
- expect(headers["Set-Cookie"]).to_not match(/remember_token=.+; SameSite/)
253
+ expect(remember_token_cookie(session)[:same_site]).to be_nil
201
254
  end
202
255
  end
203
256
 
@@ -205,15 +258,11 @@ describe Clearance::Session do
205
258
  context 'default configuration' do
206
259
  it 'is set to 1 year from now' do
207
260
  user = double("User", remember_token: "123abc")
208
- headers = {}
209
261
  session = Clearance::Session.new(env_without_remember_token)
210
262
  session.sign_in user
211
- session.add_cookie_to_headers headers
263
+ session.add_cookie_to_headers
212
264
 
213
- expect(headers).to set_cookie(
214
- 'remember_token',
215
- user.remember_token, 1.year.from_now
216
- )
265
+ expect(remember_token_cookie(session)[:expires]).to eq(1.year.from_now)
217
266
  end
218
267
  end
219
268
 
@@ -225,18 +274,14 @@ describe Clearance::Session do
225
274
  end
226
275
  with_custom_expiration expires_at do
227
276
  user = double("User", remember_token: "123abc")
228
- headers = {}
229
277
  environment = env_with_cookies(remember_me: 'true')
230
278
  session = Clearance::Session.new(environment)
231
279
  session.sign_in user
232
- session.add_cookie_to_headers headers
233
-
234
- expect(headers).to set_cookie(
235
- 'remember_token',
236
- user.remember_token,
237
- remembered_expires
238
- )
239
-
280
+ session.add_cookie_to_headers
281
+ expect(remember_token_cookie(session)[:expires]).to \
282
+ eq(remembered_expires)
283
+ expect(remember_token_cookie(session)[:value]).to \
284
+ eq(user.remember_token)
240
285
  end
241
286
  end
242
287
  end
@@ -249,9 +294,9 @@ describe Clearance::Session do
249
294
  end
250
295
 
251
296
  it 'sets a standard cookie' do
252
- session.add_cookie_to_headers(headers)
297
+ session.add_cookie_to_headers
253
298
 
254
- expect(headers['Set-Cookie']).not_to match(/remember_token=.+; secure/)
299
+ expect(remember_token_cookie(session)[:secure]).to be_falsey
255
300
  end
256
301
  end
257
302
 
@@ -262,9 +307,9 @@ describe Clearance::Session do
262
307
  end
263
308
 
264
309
  it 'sets a secure cookie' do
265
- session.add_cookie_to_headers(headers)
310
+ session.add_cookie_to_headers
266
311
 
267
- expect(headers['Set-Cookie']).to match(/remember_token=.+; secure/)
312
+ expect(remember_token_cookie(session)[:secure]).to be_truthy
268
313
  end
269
314
  end
270
315
  end
@@ -280,9 +325,9 @@ describe Clearance::Session do
280
325
  let(:cookie_domain) { ".example.com" }
281
326
 
282
327
  it "sets a standard cookie" do
283
- session.add_cookie_to_headers(headers)
328
+ session.add_cookie_to_headers
284
329
 
285
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
330
+ expect(remember_token_cookie(session)[:domain]).to eq(cookie_domain)
286
331
  end
287
332
  end
288
333
 
@@ -290,9 +335,9 @@ describe Clearance::Session do
290
335
  let(:cookie_domain) { lambda { |_r| ".example.com" } }
291
336
 
292
337
  it "sets a standard cookie" do
293
- session.add_cookie_to_headers(headers)
338
+ session.add_cookie_to_headers
294
339
 
295
- expect(headers['Set-Cookie']).to match(/domain=\.example\.com; path/)
340
+ expect(remember_token_cookie(session)[:domain]).to eq(".example.com")
296
341
  end
297
342
  end
298
343
  end
@@ -301,9 +346,9 @@ describe Clearance::Session do
301
346
  before { session.sign_in(user) }
302
347
 
303
348
  it 'sets a standard cookie' do
304
- session.add_cookie_to_headers(headers)
349
+ session.add_cookie_to_headers
305
350
 
306
- expect(headers["Set-Cookie"]).not_to match(/domain=.+; path/)
351
+ expect(remember_token_cookie(session)[:domain]).to be_nil
307
352
  end
308
353
  end
309
354
  end
@@ -313,9 +358,9 @@ describe Clearance::Session do
313
358
  before { session.sign_in(user) }
314
359
 
315
360
  it 'sets a standard cookie' do
316
- session.add_cookie_to_headers(headers)
361
+ session.add_cookie_to_headers
317
362
 
318
- expect(headers["Set-Cookie"]).to_not match(/domain=.+; path/)
363
+ expect(remember_token_cookie(session)[:domain]).to be_nil
319
364
  end
320
365
  end
321
366
 
@@ -326,18 +371,17 @@ describe Clearance::Session do
326
371
  end
327
372
 
328
373
  it 'sets a standard cookie' do
329
- session.add_cookie_to_headers(headers)
374
+ session.add_cookie_to_headers
330
375
 
331
- expect(headers['Set-Cookie']).to match(/path=\/user; expires/)
376
+ expect(remember_token_cookie(session)[:path]).to eq("/user")
332
377
  end
333
378
  end
334
379
  end
335
380
 
336
381
  it 'does not set a remember token when signed out' do
337
- headers = {}
338
382
  session = Clearance::Session.new(env_without_remember_token)
339
- session.add_cookie_to_headers headers
340
- expect(headers["Set-Cookie"]).to be nil
383
+ session.add_cookie_to_headers
384
+ expect(remember_token_cookie(session)).to be_nil
341
385
  end
342
386
 
343
387
  describe "#sign_out" do
@@ -378,6 +422,35 @@ describe Clearance::Session do
378
422
  expect(cookie_jar.deleted?(:remember_token, domain: domain)).to be true
379
423
  end
380
424
  end
425
+
426
+ context 'with callable cookie domain' do
427
+ it 'clears cookie' do
428
+ domain = '.example.com'
429
+ Clearance.configuration.cookie_domain = ->(_) { domain }
430
+ user = create(:user)
431
+ env = env_with_remember_token(
432
+ value: user.remember_token,
433
+ domain: domain
434
+ )
435
+ session = Clearance::Session.new(env)
436
+ cookie_jar = ActionDispatch::Request.new(env).cookie_jar
437
+ expect(cookie_jar.deleted?(:remember_token, domain: domain)).to be false
438
+
439
+ session.sign_out
440
+
441
+ expect(cookie_jar.deleted?(:remember_token, domain: domain)).to be true
442
+ end
443
+ end
444
+ end
445
+
446
+ # a bit of a hack to get the cookies that ActionDispatch sets inside session
447
+ def remember_token_cookie(session, cookie_name = "remember_token")
448
+ cookies = session.send(:cookies)
449
+ # see https://stackoverflow.com/a/21315095
450
+ set_cookies = cookies.instance_eval <<-RUBY, __FILE__, __LINE__ + 1
451
+ @set_cookies
452
+ RUBY
453
+ set_cookies[cookie_name]
381
454
  end
382
455
 
383
456
  def env_with_cookies(cookies)
@@ -66,6 +66,34 @@ describe Clearance::Configuration do
66
66
  end
67
67
  end
68
68
 
69
+ context "when signed_cookie is set to true" do
70
+ it "returns true" do
71
+ Clearance.configure { |config| config.signed_cookie = true }
72
+ expect(Clearance.configuration.signed_cookie).to eq true
73
+ end
74
+ end
75
+
76
+ context "when signed_cookie is not specified" do
77
+ it "defaults to false" do
78
+ expect(Clearance.configuration.signed_cookie).to eq false
79
+ end
80
+ end
81
+
82
+ context "when signed_cookie is set to :migrate" do
83
+ it "returns :migrate" do
84
+ Clearance.configure { |config| config.signed_cookie = :migrate }
85
+ expect(Clearance.configuration.signed_cookie).to eq :migrate
86
+ end
87
+ end
88
+
89
+ context "when signed_cookie is set to an unexpected value" do
90
+ it "returns :migrate" do
91
+ expect {
92
+ Clearance.configure { |config| config.signed_cookie = "unknown" }
93
+ }.to raise_exception(RuntimeError)
94
+ end
95
+ end
96
+
69
97
  context "when no redirect URL specified" do
70
98
  it 'returns "/" as redirect URL' do
71
99
  expect(Clearance::Configuration.new.redirect_url).to eq "/"
@@ -58,6 +58,19 @@ describe Clearance::SessionsController do
58
58
  end
59
59
 
60
60
  context "with good credentials and a session return url" do
61
+ it "redirects to the return URL removing leading slashes" do
62
+ user = create(:user)
63
+ url = "/url_in_the_session?foo=bar#baz"
64
+ return_url = "//////#{url}"
65
+ request.session[:return_to] = return_url
66
+
67
+ post :create, params: {
68
+ session: { email: user.email, password: user.password },
69
+ }
70
+
71
+ should redirect_to(url)
72
+ end
73
+
61
74
  it "redirects to the return URL maintaining query and fragment" do
62
75
  user = create(:user)
63
76
  return_url = "/url_in_the_session?foo=bar#baz"
@@ -147,9 +147,15 @@ describe Clearance::Generators::InstallGenerator, :generator do
147
147
  end
148
148
 
149
149
  def preserve_original_primary_key_type_setting
150
- original = Rails.configuration.generators.active_record[:primary_key_type]
150
+ active_record = Rails.configuration.generators.active_record
151
+ active_record ||= Rails.configuration.generators.options[:active_record]
152
+ original = active_record[:primary_key_type]
153
+
151
154
  yield
152
- Rails.configuration.generators.active_record[:primary_key_type] = original
155
+
156
+ Rails.application.config.generators do |g|
157
+ g.orm :active_record, primary_key_type: original
158
+ end
153
159
  end
154
160
 
155
161
  def contain_models_inherit_from