Change `has_secure_token` default to `on: :initialize`

Follow-up to [#47420][]

With the changes made in [#47420][], `has_secure_token` declarations can
be configured to execute in an `after_initialize` callback. This commit
proposed a new Rails 7.1 default: generate all `has_secure_token` values
when their corresponding models are initialized.

To preserve pre-7.1 behavior, applications can set
`config.active_record.generate_secure_token_on = :create`.

By default, generate the value when the model is initialized:

```ruby
class User < ApplicationRecord
  has_secure_token
end

record = User.new
record.token # => "fwZcXX6SkJBJRogzMdciS7wf"
```

With `config.active_record.generate_secure_token_on = :create`, generate
the value when the model is created:

```ruby
 # config/application.rb
config.active_record.generate_secure_token_on = :create

 # app/models/user.rb
class User < ApplicationRecord
  has_secure_token on: :create
end

record = User.new
record.token # => nil
record.save!
record.token # => "fwZcXX6SkJBJRogzMdciS7wf"
```

[#47420]: https://github.com/rails/rails/pull/47420

Co-authored-by: Hartley McGuire <skipkayhil@gmail.com>
This commit is contained in:
Sean Doyle 2023-08-09 10:12:25 -04:00 committed by Rafael Mendonça França
parent 3bc64016af
commit e85a3ec624
No known key found for this signature in database
GPG Key ID: FC23B6D0F1EEE948
8 changed files with 78 additions and 3 deletions

View File

@ -1,3 +1,16 @@
* Change `has_secure_token` default to `on: :initialize`
Change the new default value from `on: :create` to `on: :initialize`
Can be controlled by the `config.active_record.generate_secure_token_on`
configuration:
```ruby
config.active_record.generate_secure_token_on = :create
```
*Sean Doyle*
* Fix `change_column` not setting `precision: 6` on `datetime` columns when
using 7.0+ Migrations and SQLite.

View File

@ -447,6 +447,13 @@ module ActiveRecord
singleton_class.attr_accessor :yaml_column_permitted_classes
self.yaml_column_permitted_classes = [Symbol]
##
# :singleton-method:
# Controls when to generate a value for <tt>has_secure_token</tt>
# declarations. Defaults to <tt>:create</tt>.
singleton_class.attr_accessor :generate_secure_token_on
self.generate_secure_token_on = :create
def self.marshalling_format_version
Marshalling.format_version
end

View File

@ -38,6 +38,7 @@ module ActiveRecord
config.active_record.cache_query_log_tags = false
config.active_record.raise_on_assign_to_attr_readonly = false
config.active_record.belongs_to_required_validates_foreign_key = true
config.active_record.generate_secure_token_on = :create
config.active_record.queues = ActiveSupport::InheritableOptions.new

View File

@ -40,9 +40,10 @@ module ActiveRecord
# The callback when the value is generated. When called with <tt>on:
# :initialize</tt>, the value is generated in an
# <tt>after_initialize</tt> callback, otherwise the value will be used
# in a <tt>before_</tt> callback. It will default to <tt>:create</tt>.
#
def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: :create)
# in a <tt>before_</tt> callback. When not specified, +:on+ will use the value of
# <tt>config.active_record.generate_secure_token_on</tt>, which defaults to +:initialize+
# starting in \Rails 7.1.
def has_secure_token(attribute = :token, length: MINIMUM_TOKEN_LENGTH, on: ActiveRecord.generate_secure_token_on)
if length < MINIMUM_TOKEN_LENGTH
raise MinimumLengthError, "Token requires a minimum length of #{MINIMUM_TOKEN_LENGTH} characters."
end

View File

@ -16,6 +16,15 @@ class SecureTokenTest < ActiveRecord::TestCase
assert_equal 36, @user.auth_token.size
end
def test_generating_token_on_initialize_does_not_affect_reading_from_the_column
token = "abc123"
@user.update! token: token
assert_equal token, @user.reload.token
assert_equal token, User.find(@user.id).token
end
def test_regenerating_the_secure_token
@user.save
old_token = @user.token

View File

@ -73,6 +73,7 @@ Below are the default values associated with each target version. In cases of co
- [`config.active_record.default_column_serializer`](#config-active-record-default-column-serializer): `nil`
- [`config.active_record.encryption.hash_digest_class`](#config-active-record-encryption-hash-digest-class): `OpenSSL::Digest::SHA256`
- [`config.active_record.encryption.support_sha1_for_non_deterministic_encryption`](#config-active-record-encryption-support-sha1-for-non-deterministic-encryption): `false`
- [`config.active_record.generate_secure_token_on`](#config-active-record-generate-secure-token-on): `:initialize`
- [`config.active_record.marshalling_format_version`](#config-active-record-marshalling-format-version): `7.1`
- [`config.active_record.query_log_tags_format`](#config-active-record-query-log-tags-format): `:sqlcommenter`
- [`config.active_record.raise_on_assign_to_attr_readonly`](#config-active-record-raise-on-assign-to-attr-readonly): `true`
@ -1513,6 +1514,44 @@ Defaults to `true`. Determines whether to raise an exception or not when
the PostgreSQL adapter is provided an integer that is wider than signed
64bit representation.
#### `config.active_record.generate_secure_token_on`
Controls when to generate a value for `has_secure_token` declarations. By
default, generate the value when the model is initialized:
```ruby
class User < ApplicationRecord
has_secure_token
end
record = User.new
record.token # => "fwZcXX6SkJBJRogzMdciS7wf"
```
With `config.active_record.generate_secure_token_on = :create`, generate the
value when the model is created:
```ruby
# config/application.rb
config.active_record.generate_secure_token_on = :create
# app/models/user.rb
class User < ApplicationRecord
has_secure_token on: :create
end
record = User.new
record.token # => nil
record.save!
record.token # => "fwZcXX6SkJBJRogzMdciS7wf"
```
| Starting with version | The default value is |
| --------------------- | -------------------- |
| (original) | `:create` |
| 7.1 | `:initialize` |
#### `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` and `ActiveRecord::ConnectionAdapters::TrilogyAdapter.emulate_booleans`
Controls whether the Active Record MySQL adapter will consider all `tinyint(1)` columns as booleans. Defaults to `true`.

View File

@ -291,6 +291,7 @@ module Rails
active_record.encryption.support_sha1_for_non_deterministic_encryption = false
active_record.marshalling_format_version = 7.1
active_record.run_after_transaction_callbacks_in_order_defined = true
active_record.generate_secure_token_on = :initialize
end
if respond_to?(:action_dispatch)

View File

@ -174,6 +174,10 @@
#
# Rails.application.config.active_record.commit_transaction_on_non_local_return = true
# Controls when to generate a value for <tt>has_secure_token</tt> declarations.
#
# Rails.application.config.active_record.generate_secure_token_on = :initialize
# ** Please read carefully, this must be configured in config/application.rb **
# Change the format of the cache entry.
# Changing this default means that all new cache entries added to the cache