-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid tracking logins from Devise::Strategies::Rememberable #3867
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3867 +/- ##
=========================================
Coverage ? 97.77%
=========================================
Files ? 1286
Lines ? 77059
Branches ? 3807
=========================================
Hits ? 75343
Misses ? 1716
Partials ? 0 ☔ View full report in Codecov by Sentry. |
BenchmarksBenchmark execution time: 2024-09-18 13:26:08 Comparing candidate commit fa16b64 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 23 metrics, 2 unstable metrics. |
Feel free to ignore the failing memory leaks test, fix is in #3868 |
747d3ba
to
d3905a4
Compare
patch_registration_controller | ||
|
||
Patcher.instance_variable_set(:@patched, true) | ||
end | ||
|
||
def patch_authenticable_strategy | ||
def patch_authenticatable_strategy | ||
::Devise::Strategies::Authenticatable.alias_method(:__validate, :validate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest using a prefix/postfix that indicates the purpose and owner of this alias, e.g. something along the lines of __validate_datadog
or __validate_datadog_authenticatable
. With just a double-underscore prefix there is potential for collisions with other code and it's not clear what the purpose of the method is to someone who might be looking at it in their application for whatever reason (and has no knowledge of dd-trace-rb internals).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, I renamed the alias in f2fe86a
8ab07d8
to
5850767
Compare
return unless ::Devise::STRATEGIES.include?(:rememberable) | ||
|
||
# Rememberable strategy is required in autoloaded Rememberable model | ||
::Devise::Models::Rememberable # rubocop:disable Lint/Void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not fixable, but oh my, I hate autoloading so much :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, especially when autoloaded classes require other classes/modules
patch_registration_controller | ||
|
||
Patcher.instance_variable_set(:@patched, true) | ||
end | ||
|
||
def patch_authenticable_strategy | ||
def patch_authenticatable_strategy | ||
::Devise::Strategies::Authenticatable.alias_method(:__validate_datadog_authenticatable, :validate) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks dangerous, as mixing aliasing and prepending can easily lead to obscure issues such as recursive loops.
AIUI the reason this was introduced is that the call stack is:
Foo#validate # some app class that included ::Devise::Strategies::Rememberable and may implement validate that calls `super` or simply rely on it being provided by a parent
::Devise::Strategies::Rememberable#validate # hypothetical, not there upstream as of yet
::Datadog::AppSec::Contrib::Devise::Patcher::AuthenticatablePatch#validate
::Devise::Strategies::Authenticatable#validate
But just prepending a patch results in this:
Foo#validate # some app class that included ::Devise::Strategies::Rememberable and may implement validate that calls `super` or simply rely on it being provided by a parent
::Datadog::AppSec::Contrib::Devise::Patcher::RememberablePatch#validate
::Devise::Strategies::Rememberable#validate # hypothetical, not there upstream as of yet
::Datadog::AppSec::Contrib::Devise::Patcher::AuthenticatablePatch#validate
::Devise::Strategies::Authenticatable#validate
But we want to skip some/all of ::Datadog::AppSec::Contrib::Devise::Patcher::AuthenticatablePatch#validate
, so this attempts to walk around it with:
Foo#validate # some app class that included ::Devise::Strategies::Rememberable and may implement validate that calls `super` or simply rely on it being provided by a parent
::Datadog::AppSec::Contrib::Devise::Patcher::RememberablePatch#validate
::Devise::Strategies::Authenticatable#__validate_datadog_authenticatable
The problems are:
- suddenly
::Devise::Strategies::Authenticatable#validate
is not called at all (the method body is, but not the name). This can cause issues: a third party instrumenting::Devise::Strategies::Authenticatable#validate
as well would never be called, or if::Devise::Strategies::Authenticatable#validate
's source code callssuper
it will actually look for__validate_datadog_authenticatable
in a parent. - Between
::Datadog::AppSec::Contrib::Devise::Patcher::RememberablePatch#validate
and::Devise::Strategies::Authenticatable#__validate_datadog_authenticatable
the ancestor chain of calls is broken: Ruby "reenters" the instance and starts a new ancestor chain of calls, which creates the opportunity for infinite recursive looping.
I would favour a method where the patches collaborate through context passing: RememberablePatch
sets a flag in a context, and AuthenticatablePatch
checks for that flag.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! It is indeed better to use some variable for context. Fixed in 8e60850
6aeede4
to
c65e4b0
Compare
c65e4b0
to
fa52fd2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like it!
When logging in via devise Rememberable strategy, we disable login tracking by setting @_datadog_skip_track_login_event and checking for it in patched Authenticatable#validate
02d0df4
to
8e60850
Compare
What does this PR do?
In Devise integration
Devise::Strategies::Authenticatable#validate
is patched to track login successes and failures.This method is not only used by
Devise::Strategies::DatabaseAuthenticatable
, but also byDevise::Strategies::Rememberable
that is used for signing in the user using a remember token cookie.Motivation:
This should reduce the amount of false-positive Login Success events for Devise AppSec integration.
Additional Notes:
I added an alias method for
validate
to avoid callingsuper
fromRememberable
strategy, since the super method is thevalidate
from prependedAuthenticatablePatch
.How to test the change?
I tested this in a simple rails application with devise.
To test that regular login tracking works it's enough to log in via the sign-in form and observe a
users.login.success
tag on a trace.To test that logins from
Devise::Strategies::Rememberable
strategy are not tracked:users.login.success