clearance 1.8.0 → 1.16.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 (107) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +25 -6
  4. data/.yardopts +6 -0
  5. data/Appraisals +15 -4
  6. data/CONTRIBUTING.md +4 -1
  7. data/Gemfile +5 -3
  8. data/Gemfile.lock +102 -96
  9. data/NEWS.md +742 -311
  10. data/README.md +217 -339
  11. data/app/controllers/clearance/passwords_controller.rb +35 -21
  12. data/app/controllers/clearance/sessions_controller.rb +17 -4
  13. data/app/controllers/clearance/users_controller.rb +10 -4
  14. data/app/mailers/clearance_mailer.rb +2 -3
  15. data/app/views/clearance_mailer/change_password.html.erb +6 -3
  16. data/app/views/clearance_mailer/change_password.text.erb +5 -0
  17. data/app/views/layouts/application.html.erb +2 -2
  18. data/app/views/passwords/create.html.erb +1 -1
  19. data/app/views/passwords/edit.html.erb +2 -2
  20. data/app/views/passwords/new.html.erb +2 -2
  21. data/app/views/sessions/_form.html.erb +2 -2
  22. data/app/views/sessions/new.html.erb +1 -1
  23. data/app/views/users/new.html.erb +2 -2
  24. data/bin/setup +6 -2
  25. data/config/locales/clearance.en.yml +6 -0
  26. data/db/migrate/20110111224543_create_clearance_users.rb +1 -1
  27. data/gemfiles/{rails3.2.gemfile → rails32.gemfile} +4 -2
  28. data/gemfiles/{rails4.0.gemfile → rails40.gemfile} +6 -3
  29. data/gemfiles/{rails4.1.gemfile → rails41.gemfile} +6 -3
  30. data/gemfiles/{rails4.2.gemfile → rails42.gemfile} +6 -3
  31. data/gemfiles/rails50.gemfile +21 -0
  32. data/lib/clearance/authentication.rb +61 -2
  33. data/lib/clearance/authorization.rb +47 -4
  34. data/lib/clearance/back_door.rb +29 -6
  35. data/lib/clearance/configuration.rb +152 -15
  36. data/lib/clearance/constraints/signed_in.rb +21 -0
  37. data/lib/clearance/constraints/signed_out.rb +12 -0
  38. data/lib/clearance/constraints.rb +12 -0
  39. data/lib/clearance/controller.rb +13 -0
  40. data/lib/clearance/default_sign_in_guard.rb +17 -0
  41. data/lib/clearance/engine.rb +24 -4
  42. data/lib/clearance/password_strategies/bcrypt.rb +16 -21
  43. data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +19 -0
  44. data/lib/clearance/password_strategies/blowfish.rb +17 -0
  45. data/lib/clearance/password_strategies/sha1.rb +17 -0
  46. data/lib/clearance/password_strategies.rb +13 -0
  47. data/lib/clearance/rack_session.rb +13 -0
  48. data/lib/clearance/rspec.rb +15 -4
  49. data/lib/clearance/session.rb +46 -1
  50. data/lib/clearance/session_status.rb +7 -0
  51. data/lib/clearance/sign_in_guard.rb +65 -0
  52. data/lib/clearance/test_unit.rb +3 -3
  53. data/lib/clearance/testing/controller_helpers.rb +44 -0
  54. data/lib/clearance/testing/deny_access_matcher.rb +36 -2
  55. data/lib/clearance/testing/helpers.rb +9 -25
  56. data/lib/clearance/testing/view_helpers.rb +32 -0
  57. data/lib/clearance/token.rb +7 -0
  58. data/lib/clearance/user.rb +182 -4
  59. data/lib/clearance/version.rb +1 -1
  60. data/lib/clearance.rb +2 -0
  61. data/lib/generators/clearance/install/install_generator.rb +24 -5
  62. data/lib/generators/clearance/install/templates/clearance.rb +1 -0
  63. data/lib/generators/clearance/install/templates/db/migrate/add_clearance_to_users.rb +3 -3
  64. data/lib/generators/clearance/install/templates/db/migrate/create_users.rb +2 -2
  65. data/lib/generators/clearance/install/templates/user.rb.erb +3 -0
  66. data/lib/generators/clearance/routes/routes_generator.rb +23 -0
  67. data/lib/generators/clearance/routes/templates/routes.rb +7 -7
  68. data/lib/generators/clearance/specs/templates/features/clearance/user_signs_out_spec.rb.tt +1 -1
  69. data/lib/generators/clearance/specs/templates/features/clearance/visitor_resets_password_spec.rb.tt +12 -2
  70. data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_in_spec.rb.tt +1 -1
  71. data/lib/generators/clearance/specs/templates/features/clearance/visitor_signs_up_spec.rb.tt +1 -1
  72. data/lib/generators/clearance/specs/templates/features/clearance/visitor_updates_password_spec.rb.tt +1 -1
  73. data/spec/acceptance/clearance_installation_spec.rb +4 -1
  74. data/spec/app_templates/app/models/rails5/user.rb +5 -0
  75. data/spec/app_templates/config/initializers/clearance.rb +2 -0
  76. data/spec/app_templates/testapp/app/controllers/home_controller.rb +5 -1
  77. data/spec/app_templates/testapp/config/initializers/action_mailer.rb +1 -3
  78. data/spec/clearance/back_door_spec.rb +25 -6
  79. data/spec/clearance/controller_spec.rb +11 -0
  80. data/spec/clearance/rack_session_spec.rb +5 -5
  81. data/spec/clearance/session_spec.rb +2 -15
  82. data/spec/clearance/testing/{helpers_spec.rb → controller_helpers_spec.rb} +12 -12
  83. data/spec/clearance/testing/view_helpers_spec.rb +37 -0
  84. data/spec/configuration_spec.rb +94 -86
  85. data/spec/controllers/apis_controller_spec.rb +6 -2
  86. data/spec/controllers/forgeries_controller_spec.rb +6 -1
  87. data/spec/controllers/passwords_controller_spec.rb +17 -16
  88. data/spec/controllers/permissions_controller_spec.rb +13 -3
  89. data/spec/controllers/sessions_controller_spec.rb +4 -4
  90. data/spec/dummy/app/controllers/application_controller.rb +5 -1
  91. data/spec/dummy/application.rb +4 -0
  92. data/spec/generators/clearance/install/install_generator_spec.rb +29 -3
  93. data/spec/generators/clearance/routes/routes_generator_spec.rb +5 -1
  94. data/spec/generators/clearance/views/views_generator_spec.rb +11 -10
  95. data/spec/helpers/helper_helpers_spec.rb +10 -0
  96. data/spec/mailers/clearance_mailer_spec.rb +13 -19
  97. data/spec/password_strategies/bcrypt_migration_from_sha1_spec.rb +6 -0
  98. data/spec/password_strategies/blowfish_spec.rb +6 -0
  99. data/spec/password_strategies/sha1_spec.rb +6 -0
  100. data/spec/requests/csrf_rotation_spec.rb +33 -0
  101. data/spec/spec_helper.rb +11 -2
  102. data/spec/support/generator_spec_helpers.rb +13 -1
  103. data/spec/support/http_method_shim.rb +23 -0
  104. data/spec/user_spec.rb +9 -0
  105. data/spec/views/view_helpers_spec.rb +10 -0
  106. metadata +22 -9
  107. data/lib/generators/clearance/install/templates/user.rb +0 -3
@@ -3,23 +3,139 @@ require 'email_validator'
3
3
  require 'clearance/token'
4
4
 
5
5
  module Clearance
6
+ # Required to be included in your configued user class, which is `User` by
7
+ # default, but can be changed with {Configuration#user_model=}.
8
+ #
9
+ # class User
10
+ # include Clearance::User
11
+ #
12
+ # # ...
13
+ # end
14
+ #
15
+ # This will also include methods exposed by your password strategy, which can
16
+ # be configured with {Configuration#password_strategy=}. By default, this is
17
+ # {PasswordStrategies::BCrypt}.
18
+ #
19
+ # ## Validations
20
+ #
21
+ # These validations are added to the class that the {User} module is mixed
22
+ # into.
23
+ #
24
+ # * If {#email_optional?} is false, {#email} is validated for presence,
25
+ # uniqueness and email format (using the `email_validator` gem in strict
26
+ # mode).
27
+ # * If {#skip_password_validation?} is false, {#password} is validated
28
+ # for presence.
29
+ #
30
+ # ## Callbacks
31
+ #
32
+ # * {#normalize_email} will be called on `before_validation`
33
+ # * {#generate_remember_token} will be called on `before_create`
34
+ #
35
+ # @!attribute email
36
+ # @return [String] The user's email.
37
+ #
38
+ # @!attribute encrypted_password
39
+ # @return [String] The user's encrypted password.
40
+ #
41
+ # @!attribute remember_token
42
+ # @return [String] The value used to identify this user in their {Session}
43
+ # cookie.
44
+ #
45
+ # @!attribute confirmation_token
46
+ # @return [String] The value used to identify this user in the password
47
+ # reset link.
48
+ #
49
+ # @!attribute password_changing
50
+ # @deprecated Dirty tracking is now handled automatically.
51
+ #
52
+ # @!attribute [r] password
53
+ # @return [String] Transient (non-persisted) attribute that is set when
54
+ # updating a user's password. Only the {#encrypted_password} is persisted.
55
+ #
56
+ # @!method password=
57
+ # Sets the user's encrypted_password by using the configured Password
58
+ # Strategy's `password=` method. By default, this will be
59
+ # {PasswordStrategies::BCrypt#password=}, but can be changed with
60
+ # {Configuration#password_strategy}.
61
+ #
62
+ # @see PasswordStrategies
63
+ # @return [void]
64
+ #
65
+ # @!method authenticated?
66
+ # Check's the provided password against the user's encrypted password using
67
+ # the configured password strategy. By default, this will be
68
+ # {PasswordStrategies::BCrypt#authenticated?}, but can be changed with
69
+ # {Configuration#password_strategy}.
70
+ #
71
+ # @see PasswordStrategies
72
+ # @param [String] password
73
+ # The password to check.
74
+ # @return [Boolean]
75
+ # True if the password provided is correct for the user.
76
+ #
77
+ # @!method self.authenticate
78
+ # Finds the user with the given email and authenticates them with the
79
+ # provided password. If the email corresponds to a user and the provided
80
+ # password is correct for that user, this method will return that user.
81
+ # Otherwise it will return nil.
82
+ #
83
+ # @return [User, nil]
84
+ #
85
+ # @!method self.find_by_normalized_email
86
+ # Finds the user with the given email. The email with be normalized via
87
+ # {#normalize_email}.
88
+ #
89
+ # @return [User, nil]
90
+ #
91
+ # @!method self.normalize_email
92
+ # Normalizes the provided email by downcasing and removing all spaces.
93
+ # This is used by {find_by_normalized_email} and is also called when
94
+ # validating a user to ensure only normalized emails are stored in the
95
+ # database.
96
+ #
97
+ # @return [String]
98
+ #
6
99
  module User
7
100
  extend ActiveSupport::Concern
8
101
 
9
102
  included do
10
- attr_accessor :password_changing
11
103
  attr_reader :password
12
104
 
13
105
  include Validations
14
106
  include Callbacks
15
107
  include password_strategy
108
+
109
+ def password=(value)
110
+ encrypted_password_will_change!
111
+ super
112
+ end
113
+
114
+ def password_changing
115
+ warn "#{Kernel.caller.first}: [DEPRECATION] " \
116
+ "The `password_changing` attribute is deprecated. Clearance uses " \
117
+ "the dirty state of the `encrypted_password` field to track this " \
118
+ "automatically."
119
+
120
+ @password_changing
121
+ end
122
+
123
+ def password_changing=(value)
124
+ warn "#{Kernel.caller.first}: [DEPRECATION] " \
125
+ "The `password_changing` attribute is deprecated. Clearance uses " \
126
+ "the dirty state of the `encrypted_password` field to track this " \
127
+ "automatically."
128
+
129
+ @password_changing = value
130
+ end
16
131
  end
17
132
 
133
+ # @api private
18
134
  module ClassMethods
19
135
  def authenticate(email, password)
20
136
  if user = find_by_normalized_email(email)
21
137
  if password.present? && user.authenticated?(password)
22
- return user
138
+ user
23
139
  end
24
140
  end
25
141
  end
@@ -39,6 +155,7 @@ module Clearance
39
155
  end
40
156
  end
41
157
 
158
+ # @api private
42
159
  module Validations
43
160
  extend ActiveSupport::Concern
44
161
 
@@ -53,6 +170,7 @@ module Clearance
53
170
  end
54
171
  end
55
172
 
173
+ # @api private
56
174
  module Callbacks
57
175
  extend ActiveSupport::Concern
58
176
 
@@ -62,18 +180,48 @@ module Clearance
62
180
  end
63
181
  end
64
182
 
183
+ # Generates a {#confirmation_token} for the user, which allows them to reset
184
+ # their password via an email link.
185
+ #
186
+ # Calling `forgot_password!` will cause the user model to be saved without
187
+ # validations. Any other changes you made to this user instance will also
188
+ # be persisted, without validation. It is inteded to be called on an
189
+ # instance with no changes (`dirty? == false`).
190
+ #
191
+ # @return [Boolean] Was the save successful?
65
192
  def forgot_password!
66
193
  generate_confirmation_token
67
194
  save validate: false
68
195
  end
69
196
 
197
+ # Generates a new {#remember_token} for the user, which will have the effect
198
+ # of signing all of the user's current sessions out. This is called
199
+ # internally by {Session#sign_out}.
200
+ #
201
+ # Calling `reset_remember_token!` will cause the user model to be saved
202
+ # without validations. Any other changes you made to this user instance will
203
+ # also be persisted, without validation. It is inteded to be called on an
204
+ # instance with no changes (`dirty? == false`).
205
+ #
206
+ # @return [Boolean] Was the save successful?
70
207
  def reset_remember_token!
71
208
  generate_remember_token
72
209
  save validate: false
73
210
  end
74
211
 
212
+ # Sets the user's password to the new value, using the `password=` method on
213
+ # the configured password strategy. By default, this is
214
+ # {PasswordStrategies::BCrypt#password=}.
215
+ #
216
+ # This also has the side-effect of blanking the {#confirmation_token} and
217
+ # rotating the `#remember_token`.
218
+ #
219
+ # Validations will be run as part of this update. If the user instance is
220
+ # not valid, the password change will not be persisted, and this method will
221
+ # return `false`.
222
+ #
223
+ # @return [Boolean] Was the save successful?
75
224
  def update_password(new_password)
76
- self.password_changing = true
77
225
  self.password = new_password
78
226
 
79
227
  if valid?
@@ -86,28 +234,58 @@ module Clearance
86
234
 
87
235
  private
88
236
 
237
+ # Sets the email on this instance to the value returned by
238
+ # {.normalize_email}
239
+ #
240
+ # @return [String]
89
241
  def normalize_email
90
242
  self.email = self.class.normalize_email(email)
91
243
  end
92
244
 
245
+ # Always false. Override this method in your user model to allow for other
246
+ # forms of user authentication (username, Facebook, etc).
247
+ #
248
+ # @return [false]
93
249
  def email_optional?
94
250
  false
95
251
  end
96
252
 
253
+ # Always false. Override this method in your user model to allow for other
254
+ # forms of user authentication (username, Facebook, etc).
255
+ #
256
+ # @return [false]
97
257
  def password_optional?
98
258
  false
99
259
  end
100
260
 
261
+ # True if {#password_optional?} is true or if the user already has an
262
+ # {#encrypted_password} that is not changing.
263
+ #
264
+ # @return [Boolean]
101
265
  def skip_password_validation?
102
- password_optional? || (encrypted_password.present? && !password_changing)
266
+ password_optional? ||
267
+ (encrypted_password.present? && !encrypted_password_changed?)
103
268
  end
104
269
 
270
+ # Sets the {#confirmation_token} on the instance to a new value generated by
271
+ # {Token.new}. The change is not automatically persisted. If you would like
272
+ # to generate and save in a single method call, use {#forgot_password!}.
273
+ #
274
+ # @return [String] The new confirmation token
105
275
  def generate_confirmation_token
106
276
  self.confirmation_token = Clearance::Token.new
107
277
  end
108
278
 
279
+ # Sets the {#remember_token} on the instance to a new value generated by
280
+ # {Token.new}. The change is not automatically persisted. If you would like
281
+ # to generate and save in a single method call, use
282
+ # {#reset_remember_token!}.
283
+ #
284
+ # @return [String] The new remember token
109
285
  def generate_remember_token
110
286
  self.remember_token = Clearance::Token.new
111
287
  end
288
+
289
+ private_constant :Callbacks, :ClassMethods, :Validations
112
290
  end
113
291
  end
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = "1.8.0"
2
+ VERSION = "1.16.0".freeze
3
3
  end
data/lib/clearance.rb CHANGED
@@ -10,6 +10,8 @@ require 'clearance/password_strategies'
10
10
  require 'clearance/constraints'
11
11
 
12
12
  module Clearance
13
+ # @deprecated Use `Gem::Specification` API if you need to access Clearance's
14
+ # Gem root.
13
15
  def self.root
14
16
  warn "#{Kernel.caller.first}: [DEPRECATION] `Clearance.root` is " +
15
17
  "deprecated and will be removed in the next major release. If you need " +
@@ -23,11 +23,12 @@ module Clearance
23
23
  if File.exist? "app/models/user.rb"
24
24
  inject_into_file(
25
25
  "app/models/user.rb",
26
- "include Clearance::User\n\n",
27
- after: "class User < ActiveRecord::Base\n"
26
+ " include Clearance::User\n\n",
27
+ after: "class User < #{models_inherit_from}\n",
28
28
  )
29
29
  else
30
- copy_file 'user.rb', 'app/models/user.rb'
30
+ @inherit_from = models_inherit_from
31
+ template("user.rb.erb", "app/models/user.rb")
31
32
  end
32
33
  end
33
34
 
@@ -61,7 +62,7 @@ module Clearance
61
62
  migration_template(
62
63
  "db/migrate/#{migration_name}",
63
64
  "db/migrate/#{migration_name}",
64
- config
65
+ config.merge(migration_version: migration_version),
65
66
  )
66
67
  end
67
68
  end
@@ -101,7 +102,11 @@ module Clearance
101
102
  end
102
103
 
103
104
  def users_table_exists?
104
- ActiveRecord::Base.connection.table_exists?(:users)
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
110
  end
106
111
 
107
112
  def existing_users_columns
@@ -116,6 +121,20 @@ module Clearance
116
121
  def self.next_migration_number(dir)
117
122
  ActiveRecord::Generators::Base.next_migration_number(dir)
118
123
  end
124
+
125
+ def migration_version
126
+ if Rails.version >= "5.0.0"
127
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
128
+ end
129
+ end
130
+
131
+ def models_inherit_from
132
+ if Rails.version >= "5.0.0"
133
+ "ApplicationRecord"
134
+ else
135
+ "ActiveRecord::Base"
136
+ end
137
+ end
119
138
  end
120
139
  end
121
140
  end
@@ -1,3 +1,4 @@
1
1
  Clearance.configure do |config|
2
2
  config.mailer_sender = "reply@example.com"
3
+ config.rotate_csrf_on_sign_in = true
3
4
  end
@@ -1,6 +1,6 @@
1
- class AddClearanceToUsers < ActiveRecord::Migration
1
+ class AddClearanceToUsers < ActiveRecord::Migration<%= migration_version %>
2
2
  def self.up
3
- change_table :users do |t|
3
+ change_table :users do |t|
4
4
  <% config[:new_columns].values.each do |column| -%>
5
5
  <%= column %>
6
6
  <% end -%>
@@ -24,7 +24,7 @@ class AddClearanceToUsers < ActiveRecord::Migration
24
24
  def self.down
25
25
  change_table :users do |t|
26
26
  <% if config[:new_columns].any? -%>
27
- t.remove <%= new_columns.keys.map { |column| ":#{column}" }.join(",") %>
27
+ t.remove <%= new_columns.keys.map { |column| ":#{column}" }.join(", ") %>
28
28
  <% end -%>
29
29
  end
30
30
  end
@@ -1,6 +1,6 @@
1
- class CreateUsers < ActiveRecord::Migration
1
+ class CreateUsers < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
- create_table :users do |t|
3
+ create_table :users 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
@@ -0,0 +1,3 @@
1
+ class User < <%= @inherit_from %>
2
+ include Clearance::User
3
+ end
@@ -9,6 +9,14 @@ module Clearance
9
9
  route(clearance_routes)
10
10
  end
11
11
 
12
+ def disable_clearance_internal_routes
13
+ inject_into_file(
14
+ "config/initializers/clearance.rb",
15
+ " config.routes = false\n",
16
+ after: "Clearance.configure do |config|\n",
17
+ )
18
+ end
19
+
12
20
  private
13
21
 
14
22
  def clearance_routes
@@ -18,6 +26,21 @@ module Clearance
18
26
  def routes_file_path
19
27
  File.expand_path(find_in_source_paths('routes.rb'))
20
28
  end
29
+
30
+ def route(routing_code)
31
+ log :route, "all clearance routes"
32
+ sentinel = /\.routes\.draw do\s*\n/m
33
+
34
+ in_root do
35
+ inject_into_file(
36
+ "config/routes.rb",
37
+ routing_code,
38
+ after: sentinel,
39
+ verbose: false,
40
+ force: true,
41
+ )
42
+ end
43
+ end
21
44
  end
22
45
  end
23
46
  end
@@ -1,12 +1,12 @@
1
- resources :passwords, controller: 'clearance/passwords', only: [:create, :new]
2
- resource :session, controller: 'clearance/sessions', only: [:create]
1
+ resources :passwords, controller: "clearance/passwords", only: [:create, :new]
2
+ resource :session, controller: "clearance/sessions", only: [:create]
3
3
 
4
- resources :users, controller: 'clearance/users', only: [:create] do
4
+ resources :users, controller: "clearance/users", only: [:create] do
5
5
  resource :password,
6
- controller: 'clearance/passwords',
6
+ controller: "clearance/passwords",
7
7
  only: [:create, :edit, :update]
8
8
  end
9
9
 
10
- get '/sign_in' => 'clearance/sessions#new', as: 'sign_in'
11
- delete '/sign_out' => 'clearance/sessions#destroy', as: 'sign_out'
12
- get '/sign_up' => 'clearance/users#new', as: 'sign_up'
10
+ get "/sign_in" => "clearance/sessions#new", as: "sign_in"
11
+ delete "/sign_out" => "clearance/sessions#destroy", as: "sign_out"
12
+ get "/sign_up" => "clearance/users#new", as: "sign_up"
@@ -1,7 +1,7 @@
1
1
  require "<%= @helper_file %>"
2
2
  require "support/features/clearance_helpers"
3
3
 
4
- feature "User signs out" do
4
+ RSpec.feature "User signs out" do
5
5
  scenario "signs out" do
6
6
  sign_in
7
7
  sign_out
@@ -1,8 +1,17 @@
1
1
  require "<%= @helper_file %>"
2
2
  require "support/features/clearance_helpers"
3
3
 
4
- feature "Visitor resets password" do
4
+ RSpec.feature "Visitor resets password" do
5
5
  before { ActionMailer::Base.deliveries.clear }
6
+ <% if defined?(ActiveJob) -%>
7
+
8
+ around do |example|
9
+ original_adapter = ActiveJob::Base.queue_adapter
10
+ ActiveJob::Base.queue_adapter = :inline
11
+ example.run
12
+ ActiveJob::Base.queue_adapter = original_adapter
13
+ end
14
+ <% end -%>
6
15
 
7
16
  scenario "by navigating to the page" do
8
17
  visit sign_in_path
@@ -47,7 +56,8 @@ feature "Visitor resets password" do
47
56
  message = ActionMailer::Base.deliveries.any? do |email|
48
57
  email.to == [recipient] &&
49
58
  email.subject =~ /#{subject}/i &&
50
- email.body =~ /#{body}/
59
+ email.html_part.body =~ /#{body}/ &&
60
+ email.text_part.body =~ /#{body}/
51
61
  end
52
62
 
53
63
  expect(message).to be
@@ -1,7 +1,7 @@
1
1
  require "<%= @helper_file %>"
2
2
  require "support/features/clearance_helpers"
3
3
 
4
- feature "Visitor signs in" do
4
+ RSpec.feature "Visitor signs in" do
5
5
  scenario "with valid email and password" do
6
6
  create_user "user@example.com", "password"
7
7
  sign_in_with "user@example.com", "password"
@@ -1,7 +1,7 @@
1
1
  require "<%= @helper_file %>"
2
2
  require "support/features/clearance_helpers"
3
3
 
4
- feature "Visitor signs up" do
4
+ RSpec.feature "Visitor signs up" do
5
5
  scenario "by navigating to the page" do
6
6
  visit sign_in_path
7
7
 
@@ -1,7 +1,7 @@
1
1
  require "<%= @helper_file %>"
2
2
  require "support/features/clearance_helpers"
3
3
 
4
- feature "Visitor updates password" do
4
+ RSpec.feature "Visitor updates password" do
5
5
  scenario "with valid password" do
6
6
  user = user_with_reset_password
7
7
  update_password user, "newpassword"
@@ -10,6 +10,7 @@ describe "Clearance Installation" do
10
10
 
11
11
  it "can successfully run specs" do
12
12
  app_name = "testapp"
13
+
13
14
  generate_test_app(app_name)
14
15
 
15
16
  Dir.chdir(app_name) do
@@ -28,7 +29,8 @@ describe "Clearance Installation" do
28
29
  --skip-git \
29
30
  --skip-javascript \
30
31
  --skip-sprockets \
31
- --skip-keeps"
32
+ --skip-keeps \
33
+ --no-rc"
32
34
 
33
35
  FileUtils.rm_f("public/index.html")
34
36
  FileUtils.rm_f("app/views/layouts/application.html.erb")
@@ -70,6 +72,7 @@ describe "Clearance Installation" do
70
72
  end
71
73
 
72
74
  return_value = system("#{command} #{silencer}")
75
+
73
76
  expect(return_value).to eq true
74
77
  end
75
78
  end
@@ -0,0 +1,5 @@
1
+ class User < ApplicationRecord
2
+ def previously_existed?
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ Clearance.configure do |config|
2
+ end
@@ -1,5 +1,9 @@
1
1
  class HomeController < ApplicationController
2
2
  def show
3
- render text: "", layout: "application"
3
+ if Rails::VERSION::MAJOR >= 5
4
+ render html: "", layout: "application"
5
+ else
6
+ render text: "", layout: "application"
7
+ end
4
8
  end
5
9
  end
@@ -1,3 +1 @@
1
- Rails.application.config.action_mailer.default_url_options = {
2
- host: "localhost"
3
- }
1
+ ActionMailer::Base.default_url_options[:host] = "localhost"
@@ -1,8 +1,8 @@
1
- require 'spec_helper'
1
+ require "spec_helper"
2
2
 
3
3
  describe Clearance::BackDoor do
4
- it 'signs in as a given user' do
5
- user_id = '123'
4
+ it "signs in as a given user" do
5
+ user_id = "123"
6
6
  user = double("user")
7
7
  allow(User).to receive(:find).with(user_id).and_return(user)
8
8
  env = env_for_user_id(user_id)
@@ -14,7 +14,7 @@ describe Clearance::BackDoor do
14
14
  expect(result).to eq mock_app.call(env)
15
15
  end
16
16
 
17
- it 'delegates directly without a user' do
17
+ it "delegates directly without a user" do
18
18
  env = env_without_user_id
19
19
  back_door = Clearance::BackDoor.new(mock_app)
20
20
 
@@ -24,8 +24,22 @@ describe Clearance::BackDoor do
24
24
  expect(result).to eq mock_app.call(env)
25
25
  end
26
26
 
27
+ it "can set the user via a block" do
28
+ env = env_for_username("foo")
29
+ user = double("user")
30
+ allow(User).to receive(:find_by).with(username: "foo").and_return(user)
31
+ back_door = Clearance::BackDoor.new(mock_app) do |username|
32
+ User.find_by(username: username)
33
+ end
34
+
35
+ result = back_door.call(env)
36
+
37
+ expect(env[:clearance]).to have_received(:sign_in).with(user)
38
+ expect(result).to eq mock_app.call(env)
39
+ end
40
+
27
41
  def env_without_user_id
28
- env_for_user_id('')
42
+ env_for_user_id("")
29
43
  end
30
44
 
31
45
  def env_for_user_id(user_id)
@@ -33,7 +47,12 @@ describe Clearance::BackDoor do
33
47
  Rack::MockRequest.env_for("/?as=#{user_id}").merge(clearance: clearance)
34
48
  end
35
49
 
50
+ def env_for_username(username)
51
+ clearance = double("clearance", sign_in: true)
52
+ Rack::MockRequest.env_for("/?as=#{username}").merge(clearance: clearance)
53
+ end
54
+
36
55
  def mock_app
37
- lambda { |env| [200, {}, ['okay']] }
56
+ lambda { |env| [200, {}, ["okay"]] }
38
57
  end
39
58
  end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ describe Clearance::Controller, type: :controller do
4
+ controller(ActionController::Base) do
5
+ include Clearance::Controller
6
+ end
7
+
8
+ it "exposes no action methods" do
9
+ expect(controller.action_methods).to be_empty
10
+ end
11
+ end
@@ -2,21 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::RackSession do
4
4
  it 'injects a clearance session into the environment' do
5
- expected_session = 'the session'
6
- allow(expected_session).to receive(:add_cookie_to_headers)
7
- allow(Clearance::Session).to receive(:new).and_return(expected_session)
8
5
  headers = { 'X-Roaring-Lobster' => 'Red' }
9
-
10
6
  app = Rack::Builder.new do
11
7
  use Clearance::RackSession
12
8
  run lambda { |env| Rack::Response.new(env[:clearance], 200, headers).finish }
13
9
  end
14
10
 
15
11
  env = Rack::MockRequest.env_for('/')
12
+ expected_session = "the session"
13
+ allow(expected_session).to receive(:add_cookie_to_headers)
14
+ allow(Clearance::Session).to receive(:new).
15
+ with(env).
16
+ and_return(expected_session)
16
17
 
17
18
  response = Rack::MockResponse.new(*app.call(env))
18
19
 
19
- expect(Clearance::Session).to have_received(:new).with(env)
20
20
  expect(response.body).to eq expected_session
21
21
  expect(expected_session).to have_received(:add_cookie_to_headers).
22
22
  with(hash_including(headers))