devise 3.4.1 → 3.5.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +26 -16
  3. data/CHANGELOG.md +131 -104
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +84 -85
  6. data/MIT-LICENSE +1 -1
  7. data/README.md +52 -32
  8. data/Rakefile +2 -1
  9. data/app/controllers/devise/confirmations_controller.rb +4 -0
  10. data/app/controllers/devise/omniauth_callbacks_controller.rb +4 -0
  11. data/app/controllers/devise/passwords_controller.rb +14 -4
  12. data/app/controllers/devise/registrations_controller.rb +10 -11
  13. data/app/controllers/devise/sessions_controller.rb +7 -2
  14. data/app/controllers/devise/unlocks_controller.rb +3 -0
  15. data/app/controllers/devise_controller.rb +34 -18
  16. data/app/views/devise/confirmations/new.html.erb +1 -1
  17. data/app/views/devise/passwords/edit.html.erb +3 -0
  18. data/app/views/devise/registrations/new.html.erb +1 -1
  19. data/gemfiles/Gemfile.rails-3.2-stable.lock +43 -43
  20. data/gemfiles/Gemfile.rails-4.0-stable.lock +45 -47
  21. data/gemfiles/Gemfile.rails-4.1-stable.lock +52 -53
  22. data/gemfiles/Gemfile.rails-4.2-stable +29 -0
  23. data/gemfiles/Gemfile.rails-4.2-stable.lock +191 -0
  24. data/lib/devise.rb +23 -28
  25. data/lib/devise/controllers/rememberable.rb +1 -1
  26. data/lib/devise/controllers/sign_in_out.rb +1 -1
  27. data/lib/devise/controllers/store_location.rb +3 -1
  28. data/lib/devise/controllers/url_helpers.rb +7 -9
  29. data/lib/devise/encryptor.rb +22 -0
  30. data/lib/devise/failure_app.rb +26 -10
  31. data/lib/devise/mapping.rb +1 -0
  32. data/lib/devise/models/authenticatable.rb +20 -26
  33. data/lib/devise/models/confirmable.rb +29 -7
  34. data/lib/devise/models/database_authenticatable.rb +6 -9
  35. data/lib/devise/models/recoverable.rb +22 -10
  36. data/lib/devise/models/rememberable.rb +16 -3
  37. data/lib/devise/models/trackable.rb +1 -2
  38. data/lib/devise/models/validatable.rb +3 -3
  39. data/lib/devise/rails.rb +1 -1
  40. data/lib/devise/rails/routes.rb +3 -3
  41. data/lib/devise/strategies/authenticatable.rb +5 -2
  42. data/lib/devise/strategies/database_authenticatable.rb +1 -1
  43. data/lib/devise/strategies/rememberable.rb +10 -0
  44. data/lib/devise/test_helpers.rb +2 -2
  45. data/lib/devise/version.rb +1 -1
  46. data/lib/generators/active_record/templates/migration.rb +1 -1
  47. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  48. data/lib/generators/templates/controllers/README +1 -1
  49. data/lib/generators/templates/controllers/omniauth_callbacks_controller.rb +1 -1
  50. data/lib/generators/templates/controllers/registrations_controller.rb +2 -2
  51. data/lib/generators/templates/controllers/sessions_controller.rb +1 -1
  52. data/lib/generators/templates/devise.rb +14 -8
  53. data/lib/generators/templates/simple_form_for/passwords/edit.html.erb +1 -1
  54. data/lib/generators/templates/simple_form_for/registrations/new.html.erb +1 -1
  55. data/test/controllers/custom_registrations_controller_test.rb +6 -1
  56. data/test/controllers/helpers_test.rb +5 -0
  57. data/test/controllers/inherited_controller_i18n_messages_test.rb +51 -0
  58. data/test/controllers/internal_helpers_test.rb +4 -4
  59. data/test/controllers/load_hooks_controller_test.rb +19 -0
  60. data/test/controllers/passwords_controller_test.rb +1 -1
  61. data/test/controllers/sessions_controller_test.rb +3 -3
  62. data/test/devise_test.rb +2 -2
  63. data/test/failure_app_test.rb +23 -0
  64. data/test/integration/database_authenticatable_test.rb +11 -0
  65. data/test/integration/omniauthable_test.rb +1 -1
  66. data/test/integration/recoverable_test.rb +13 -0
  67. data/test/integration/rememberable_test.rb +9 -0
  68. data/test/mapping_test.rb +6 -0
  69. data/test/models/confirmable_test.rb +47 -34
  70. data/test/models/lockable_test.rb +6 -6
  71. data/test/models/recoverable_test.rb +39 -7
  72. data/test/models/rememberable_test.rb +8 -2
  73. data/test/models/validatable_test.rb +5 -5
  74. data/test/rails_app/app/controllers/custom/registrations_controller.rb +10 -0
  75. data/test/rails_app/config/application.rb +1 -1
  76. data/test/rails_app/config/environments/production.rb +6 -2
  77. data/test/rails_app/config/environments/test.rb +7 -2
  78. data/test/rails_app/config/initializers/devise.rb +12 -15
  79. data/test/rails_app/lib/shared_user.rb +1 -1
  80. data/test/rails_test.rb +9 -0
  81. data/test/support/integration.rb +2 -2
  82. data/test/test_helpers_test.rb +22 -7
  83. data/test/test_models.rb +2 -2
  84. metadata +11 -2
@@ -2,7 +2,7 @@ module Devise
2
2
  module Controllers
3
3
  # A module that may be optionally included in a controller in order
4
4
  # to provide remember me behavior. Useful when signing in is done
5
- # through a callback, like in Omniauth.
5
+ # through a callback, like in OmniAuth.
6
6
  module Rememberable
7
7
  # Return default cookie values retrieved from session options.
8
8
  def self.cookie_values
@@ -6,7 +6,7 @@ module Devise
6
6
  # Return true if the given scope is signed in session. If no scope given, return
7
7
  # true if any scope is signed in. Does not run authentication hooks.
8
8
  def signed_in?(scope=nil)
9
- [ scope || Devise.mappings.keys ].flatten.any? do |_scope|
9
+ [scope || Devise.mappings.keys].flatten.any? do |_scope|
10
10
  warden.authenticate?(scope: _scope)
11
11
  end
12
12
  end
@@ -35,7 +35,9 @@ module Devise
35
35
  session_key = stored_location_key_for(resource_or_scope)
36
36
  uri = parse_uri(location)
37
37
  if uri
38
- session[session_key] = [uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
38
+ path = [uri.path.sub(/\A\/+/, '/'), uri.query].compact.join('?')
39
+ path = [path, uri.fragment].compact.join('#')
40
+ session[session_key] = path
39
41
  end
40
42
  end
41
43
 
@@ -42,16 +42,14 @@ module Devise
42
42
  [:path, :url].each do |path_or_url|
43
43
  actions.each do |action|
44
44
  action = action ? "#{action}_" : ""
45
- method = "#{action}#{module_name}_#{path_or_url}"
45
+ method = :"#{action}#{module_name}_#{path_or_url}"
46
46
 
47
- class_eval <<-URL_HELPERS, __FILE__, __LINE__ + 1
48
- def #{method}(resource_or_scope, *args)
49
- scope = Devise::Mapping.find_scope!(resource_or_scope)
50
- router_name = Devise.mappings[scope].router_name
51
- context = router_name ? send(router_name) : _devise_route_context
52
- context.send("#{action}\#{scope}_#{module_name}_#{path_or_url}", *args)
53
- end
54
- URL_HELPERS
47
+ define_method method do |resource_or_scope, *args|
48
+ scope = Devise::Mapping.find_scope!(resource_or_scope)
49
+ router_name = Devise.mappings[scope].router_name
50
+ context = router_name ? send(router_name) : _devise_route_context
51
+ context.send("#{action}#{scope}_#{module_name}_#{path_or_url}", *args)
52
+ end
55
53
  end
56
54
  end
57
55
  end
@@ -0,0 +1,22 @@
1
+ require 'bcrypt'
2
+
3
+ module Devise
4
+ module Encryptor
5
+ def self.digest(klass, password)
6
+ if klass.pepper.present?
7
+ password = "#{password}#{klass.pepper}"
8
+ end
9
+ ::BCrypt::Password.create(password, cost: klass.stretches).to_s
10
+ end
11
+
12
+ def self.compare(klass, encrypted_password, password)
13
+ return false if encrypted_password.blank?
14
+ bcrypt = ::BCrypt::Password.new(encrypted_password)
15
+ if klass.pepper.present?
16
+ password = "#{password}#{klass.pepper}"
17
+ end
18
+ password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
19
+ Devise.secure_compare(password, encrypted_password)
20
+ end
21
+ end
22
+ end
@@ -49,17 +49,19 @@ module Devise
49
49
 
50
50
  def recall
51
51
  env["PATH_INFO"] = attempted_path
52
- flash.now[:alert] = i18n_message(:invalid)
52
+ flash.now[:alert] = i18n_message(:invalid) if is_flashing_format?
53
53
  self.response = recall_app(warden_options[:recall]).call(env)
54
54
  end
55
55
 
56
56
  def redirect
57
57
  store_location!
58
- if flash[:timedout] && flash[:alert]
59
- flash.keep(:timedout)
60
- flash.keep(:alert)
61
- else
62
- flash[:alert] = i18n_message
58
+ if is_flashing_format?
59
+ if flash[:timedout] && flash[:alert]
60
+ flash.keep(:timedout)
61
+ flash.keep(:alert)
62
+ else
63
+ flash[:alert] = i18n_message
64
+ end
63
65
  end
64
66
  redirect_to redirect_url
65
67
  end
@@ -91,7 +93,7 @@ module Devise
91
93
 
92
94
  def redirect_url
93
95
  if warden_message == :timeout
94
- flash[:timedout] = true
96
+ flash[:timedout] = true if is_flashing_format?
95
97
 
96
98
  path = if request.get?
97
99
  attempted_path
@@ -105,15 +107,23 @@ module Devise
105
107
  end
106
108
  end
107
109
 
110
+ def route(scope)
111
+ :"new_#{scope}_session_url"
112
+ end
113
+
108
114
  def scope_url
109
115
  opts = {}
110
- route = :"new_#{scope}_session_url"
116
+ route = route(scope)
111
117
  opts[:format] = request_format unless skip_format?
112
118
 
113
119
  config = Rails.application.config
114
- opts[:script_name] = (config.relative_url_root if config.respond_to?(:relative_url_root))
115
120
 
116
- context = send(Devise.available_router_name)
121
+ if config.respond_to?(:relative_url_root) && config.relative_url_root.present?
122
+ opts[:script_name] = config.relative_url_root
123
+ end
124
+
125
+ router_name = Devise.mappings[scope].router_name || Devise.available_router_name
126
+ context = send(router_name)
117
127
 
118
128
  if context.respond_to?(route)
119
129
  context.send(route, opts)
@@ -205,6 +215,12 @@ module Devise
205
215
  Devise.navigational_formats.include?(request_format)
206
216
  end
207
217
 
218
+ # Check if flash messages should be emitted. Default is to do it on
219
+ # navigational formats
220
+ def is_flashing_format?
221
+ is_navigational_format?
222
+ end
223
+
208
224
  def request_format
209
225
  @request_format ||= request.format.try(:ref)
210
226
  end
@@ -31,6 +31,7 @@ module Devise
31
31
  # Receives an object and find a scope for it. If a scope cannot be found,
32
32
  # raises an error. If a symbol is given, it's considered to be the scope.
33
33
  def self.find_scope!(obj)
34
+ obj = obj.devise_scope if obj.respond_to?(:devise_scope)
34
35
  case obj
35
36
  when String, Symbol
36
37
  return obj.to_sym
@@ -1,3 +1,4 @@
1
+ require 'active_model/version'
1
2
  require 'devise/hooks/activatable'
2
3
  require 'devise/hooks/csrf_cleaner'
3
4
 
@@ -37,7 +38,7 @@ module Devise
37
38
  # calling model.active_for_authentication?. This method is overwritten by other devise modules. For instance,
38
39
  # :confirmable overwrites .active_for_authentication? to only return true if your model was confirmed.
39
40
  #
40
- # You overwrite this method yourself, but if you do, don't forget to call super:
41
+ # You can overwrite this method yourself, but if you do, don't forget to call super:
41
42
  #
42
43
  # def active_for_authentication?
43
44
  # super && special_condition_is_valid?
@@ -95,29 +96,22 @@ module Devise
95
96
  def authenticatable_salt
96
97
  end
97
98
 
98
- array = %w(serializable_hash)
99
- # to_xml does not call serializable_hash on 3.1
100
- array << "to_xml" if Rails::VERSION::STRING[0,3] == "3.1"
101
-
102
- array.each do |method|
103
- class_eval <<-RUBY, __FILE__, __LINE__
104
- # Redefine to_xml and serializable_hash in models for more secure defaults.
105
- # By default, it removes from the serializable model all attributes that
106
- # are *not* accessible. You can remove this default by using :force_except
107
- # and passing a new list of attributes you want to exempt. All attributes
108
- # given to :except will simply add names to exempt to Devise internal list.
109
- def #{method}(options=nil)
110
- options ||= {}
111
- options[:except] = Array(options[:except])
112
-
113
- if options[:force_except]
114
- options[:except].concat Array(options[:force_except])
115
- else
116
- options[:except].concat BLACKLIST_FOR_SERIALIZATION
117
- end
118
- super(options)
119
- end
120
- RUBY
99
+ # Redefine serializable_hash in models for more secure defaults.
100
+ # By default, it removes from the serializable model all attributes that
101
+ # are *not* accessible. You can remove this default by using :force_except
102
+ # and passing a new list of attributes you want to exempt. All attributes
103
+ # given to :except will simply add names to exempt to Devise internal list.
104
+ def serializable_hash(options = nil)
105
+ options ||= {}
106
+ options[:except] = Array(options[:except])
107
+
108
+ if options[:force_except]
109
+ options[:except].concat Array(options[:force_except])
110
+ else
111
+ options[:except].concat BLACKLIST_FOR_SERIALIZATION
112
+ end
113
+
114
+ super(options)
121
115
  end
122
116
 
123
117
  protected
@@ -252,12 +246,12 @@ module Devise
252
246
  to_adapter.find_first(devise_parameter_filter.filter(tainted_conditions).merge(opts))
253
247
  end
254
248
 
255
- # Find an initialize a record setting an error if it can't be found.
249
+ # Find or initialize a record setting an error if it can't be found.
256
250
  def find_or_initialize_with_error_by(attribute, value, error=:invalid) #:nodoc:
257
251
  find_or_initialize_with_errors([attribute], { attribute => value }, error)
258
252
  end
259
253
 
260
- # Find an initialize a group of attributes based on a list of required attributes.
254
+ # Find or initialize a record with group of attributes based on a list of required attributes.
261
255
  def find_or_initialize_with_errors(required_attributes, attributes, error=:invalid) #:nodoc:
262
256
  attributes = attributes.slice(*required_attributes).with_indifferent_access
263
257
  attributes.delete_if { |key, value| value.blank? }
@@ -5,6 +5,14 @@ module Devise
5
5
  # Confirmation instructions are sent to the user email after creating a
6
6
  # record and when manually requested by a new confirmation instruction request.
7
7
  #
8
+ # Confirmable tracks the following columns:
9
+ #
10
+ # * confirmation_token - An OpenSSL::HMAC.hexdigest of @raw_confirmation_token
11
+ # * confirmed_at - A timestamp when the user clicked the confirmation link
12
+ # * confirmation_sent_at - A timestamp when the confirmation_token was generated (not sent)
13
+ # * unconfirmed_email - An email address copied from the email attr. After confirmation
14
+ # this value is copied to the email attr then cleared
15
+ #
8
16
  # == Options
9
17
  #
10
18
  # Confirmable adds the following options to +devise+:
@@ -24,7 +32,7 @@ module Devise
24
32
  #
25
33
  # == Examples
26
34
  #
27
- # User.find(1).confirm! # returns true unless it's already confirmed
35
+ # User.find(1).confirm # returns true unless it's already confirmed
28
36
  # User.find(1).confirmed? # true/false
29
37
  # User.find(1).send_confirmation_instructions # manually send instructions
30
38
  #
@@ -56,7 +64,7 @@ module Devise
56
64
  # Confirm a user by setting it's confirmed_at to actual time. If the user
57
65
  # is already confirmed, add an error to email field. If the user is invalid
58
66
  # add errors
59
- def confirm!
67
+ def confirm(args={})
60
68
  pending_any_confirmation do
61
69
  if confirmation_period_expired?
62
70
  self.errors.add(:email, :confirmation_period_expired,
@@ -64,7 +72,6 @@ module Devise
64
72
  return false
65
73
  end
66
74
 
67
- self.confirmation_token = nil
68
75
  self.confirmed_at = Time.now.utc
69
76
 
70
77
  saved = if self.class.reconfirmable && unconfirmed_email.present?
@@ -75,7 +82,7 @@ module Devise
75
82
  # We need to validate in such cases to enforce e-mail uniqueness
76
83
  save(validate: true)
77
84
  else
78
- save(validate: false)
85
+ save(validate: args[:ensure_valid] == true)
79
86
  end
80
87
 
81
88
  after_confirmation if saved
@@ -83,6 +90,11 @@ module Devise
83
90
  end
84
91
  end
85
92
 
93
+ def confirm!(args={})
94
+ ActiveSupport::Deprecation.warn "confirm! is deprecated in favor of confirm"
95
+ confirm(args)
96
+ end
97
+
86
98
  # Verifies whether a user is confirmed or not
87
99
  def confirmed?
88
100
  !!confirmed_at
@@ -202,7 +214,7 @@ module Devise
202
214
  # confirmation_period_expired? # will always return false
203
215
  #
204
216
  def confirmation_period_expired?
205
- self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within )
217
+ self.class.confirm_within && (Time.now > self.confirmation_sent_at + self.class.confirm_within)
206
218
  end
207
219
 
208
220
  # Checks whether the record requires any confirmation.
@@ -216,7 +228,7 @@ module Devise
216
228
  end
217
229
 
218
230
  # Generates a new random token for confirmation, and stores
219
- # the time this token is being generated
231
+ # the time this token is being generated in confirmation_sent_at
220
232
  def generate_confirmation_token
221
233
  raw, enc = Devise.token_generator.generate(self.class, :confirmation_token)
222
234
  @raw_confirmation_token = raw
@@ -249,6 +261,16 @@ module Devise
249
261
  confirmation_required? && !@skip_confirmation_notification && self.email.present?
250
262
  end
251
263
 
264
+ # A callback initiated after successfully confirming. This can be
265
+ # used to insert your own logic that is only run after the user successfully
266
+ # confirms.
267
+ #
268
+ # Example:
269
+ #
270
+ # def after_confirmation
271
+ # self.update_attribute(:invite_code, nil)
272
+ # end
273
+ #
252
274
  def after_confirmation
253
275
  end
254
276
 
@@ -275,7 +297,7 @@ module Devise
275
297
  confirmation_token = Devise.token_generator.digest(self, :confirmation_token, confirmation_token)
276
298
 
277
299
  confirmable = find_or_initialize_with_error_by(:confirmation_token, confirmation_token)
278
- confirmable.confirm! if confirmable.persisted?
300
+ confirmable.confirm if confirmable.persisted?
279
301
  confirmable.confirmation_token = original_token
280
302
  confirmable
281
303
  end
@@ -1,10 +1,10 @@
1
1
  require 'devise/strategies/database_authenticatable'
2
- require 'bcrypt'
2
+ require 'devise/encryptor'
3
3
 
4
4
  module Devise
5
- # Digests the password using bcrypt.
6
5
  def self.bcrypt(klass, password)
7
- ::BCrypt::Password.create("#{password}#{klass.pepper}", cost: klass.stretches).to_s
6
+ ActiveSupport::Deprecation.warn "Devise.bcrypt is deprecated; use Devise::Encryptor.digest instead"
7
+ Devise::Encryptor.digest(klass, password)
8
8
  end
9
9
 
10
10
  module Models
@@ -42,12 +42,9 @@ module Devise
42
42
  self.encrypted_password = password_digest(@password) if @password.present?
43
43
  end
44
44
 
45
- # Verifies whether an password (ie from sign in) is the user password.
45
+ # Verifies whether a password (ie from sign in) is the user password.
46
46
  def valid_password?(password)
47
- return false if encrypted_password.blank?
48
- bcrypt = ::BCrypt::Password.new(encrypted_password)
49
- password = ::BCrypt::Engine.hash_secret("#{password}#{self.class.pepper}", bcrypt.salt)
50
- Devise.secure_compare(password, encrypted_password)
47
+ Devise::Encryptor.compare(self.class, encrypted_password, password)
51
48
  end
52
49
 
53
50
  # Set password and password confirmation to nil
@@ -145,7 +142,7 @@ module Devise
145
142
  # See https://github.com/plataformatec/devise-encryptable for examples
146
143
  # of other encryption engines.
147
144
  def password_digest(password)
148
- Devise.bcrypt(self.class, password)
145
+ Devise::Encryptor.digest(self.class, password)
149
146
  end
150
147
 
151
148
  module ClassMethods
@@ -8,11 +8,13 @@ module Devise
8
8
  # Recoverable adds the following options to devise_for:
9
9
  #
10
10
  # * +reset_password_keys+: the keys you want to use when recovering the password for an account
11
+ # * +reset_password_within+: the time period within which the password must be reset or the token expires.
12
+ # * +sign_in_after_reset_password+: whether or not to sign in the user automatically after a password reset.
11
13
  #
12
14
  # == Examples
13
15
  #
14
16
  # # resets the user password and save the record, true if valid passwords are given, otherwise false
15
- # User.find(1).reset_password!('password123', 'password123')
17
+ # User.find(1).reset_password('password123', 'password123')
16
18
  #
17
19
  # # only resets the user password, without saving the record
18
20
  # user = User.find(1)
@@ -28,20 +30,33 @@ module Devise
28
30
  [:reset_password_sent_at, :reset_password_token]
29
31
  end
30
32
 
33
+ included do
34
+ before_save do
35
+ if email_changed? || encrypted_password_changed?
36
+ clear_reset_password_token
37
+ end
38
+ end
39
+ end
40
+
31
41
  # Update password saving the record and clearing token. Returns true if
32
42
  # the passwords are valid and the record was saved, false otherwise.
33
- def reset_password!(new_password, new_password_confirmation)
43
+ def reset_password(new_password, new_password_confirmation)
34
44
  self.password = new_password
35
45
  self.password_confirmation = new_password_confirmation
36
46
 
37
- if valid?
38
- clear_reset_password_token
47
+ if respond_to?(:after_password_reset) && valid?
48
+ ActiveSupport::Deprecation.warn "after_password_reset is deprecated"
39
49
  after_password_reset
40
50
  end
41
51
 
42
52
  save
43
53
  end
44
54
 
55
+ def reset_password!(new_password, new_password_confirmation)
56
+ ActiveSupport::Deprecation.warn "reset_password! is deprecated in favor of reset_password"
57
+ reset_password(new_password, new_password_confirmation)
58
+ end
59
+
45
60
  # Resets reset password token and send reset password instructions by email.
46
61
  # Returns the token sent in the e-mail.
47
62
  def send_reset_password_instructions
@@ -83,9 +98,6 @@ module Devise
83
98
  self.reset_password_sent_at = nil
84
99
  end
85
100
 
86
- def after_password_reset
87
- end
88
-
89
101
  def set_reset_password_token
90
102
  raw, enc = Devise.token_generator.generate(self.class, :reset_password_token)
91
103
 
@@ -130,17 +142,17 @@ module Devise
130
142
 
131
143
  if recoverable.persisted?
132
144
  if recoverable.reset_password_period_valid?
133
- recoverable.reset_password!(attributes[:password], attributes[:password_confirmation])
145
+ recoverable.reset_password(attributes[:password], attributes[:password_confirmation])
134
146
  else
135
147
  recoverable.errors.add(:reset_password_token, :expired)
136
148
  end
137
149
  end
138
150
 
139
- recoverable.reset_password_token = original_token
151
+ recoverable.reset_password_token = original_token if recoverable.reset_password_token.present?
140
152
  recoverable
141
153
  end
142
154
 
143
- Devise::Models.config(self, :reset_password_keys, :reset_password_within)
155
+ Devise::Models.config(self, :reset_password_keys, :reset_password_within, :sign_in_after_reset_password)
144
156
  end
145
157
  end
146
158
  end