Make #increment_failed_attempts concurrency safe#4996
Merged
Merged
Conversation
As reported in #4981, the method `#increment_failed_attempts` of `Devise::Models::Lockable` was not concurrency safe. The increment operation was being done in two steps: first the value was read from the database, and then incremented by 1. This may result in wrong values if two requests try to update the value concurrently. For example: Browser1 -------> Read `failed_attempts` from DB (1) -------> Increment `failed_attempts` to 2 Browser2 -------> Read `failed_attempts` from DB (1) -------> Increment `failed_attempts` to 2 In the example above, `failed_attempts` should have been set to 3, but it will be set to 2. This commit handles this case by calling ActiveRecord's `#increment!` method, which will do both steps at once, reading the value straight from the database. More info: https://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-increment-21 Co-authored-by: Marcos Ferreira <marcos.ferreira@plataformatec.com.br>
`ActiveRecord::Persistence#increment!` started using `ActiveRecord::CounterCache.update_counters` only in Rails 5, which means we got some broken specs in Rails 4. It turns out that `ActiveRecord::CounterCache.increment_counter` exists since Rails 4 and it also calls `.update_counters`, so we're using it instead. This commit also adds a `ActiveRecord::AttributeMethods::Dirty#reload` call to ensure that the application gets the updated value - i.e. that other request might have updated.
ouranos
added a commit
to maestrano/devise
that referenced
this pull request
Mar 14, 2019
Backported from heartcombo#4996
|
Hello, @tegon
We are investigating similar or related behavior reported for latest version, so just in case.. |
Member
Author
|
Hi @glebsts, the code you mentioned is for the other case: resetting the failed attempts account - i.e. unlocking a user. Thanks! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
As reported in #4981, the method
#increment_failed_attemptsofDevise::Models::Lockablewasnot concurrency safe. The increment operation was being done in two steps: first the value was read from the database, and then incremented by 1. This may result in wrong values if two requests try to update the value concurrently. For example:
In the example above,
failed_attemptsshould have been set to 3, but it will be set to 2.This pull request handles this case by calling
ActiveRecord::CounterCache.increment_countermethod, which will do both steps at once, reading the value straight from the database.This pull request also adds a
ActiveRecord::AttributeMethods::Dirty#reloadcall to ensure that the application gets the updated value - i.e. that other request might have updated.Although this does not ensure that the value is in fact the most recent one - other request could've updated it after the
reloadcall - it seems good enough for this implementation.Even if a request does not locks the account because it has a stale value, the next one - that updated that value - will do it. That's why we decided not to use a pessimistic lock here.
Closes #4981.