-
Notifications
You must be signed in to change notification settings - Fork 14.3k
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
fix: Prevent cached bootstrap data from leaking between users w/ same first/last name #26023
fix: Prevent cached bootstrap data from leaking between users w/ same first/last name #26023
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## master #26023 +/- ##
==========================================
+ Coverage 69.05% 69.07% +0.02%
==========================================
Files 1938 1937 -1
Lines 75835 75835
Branches 8427 8427
==========================================
+ Hits 52366 52385 +19
+ Misses 21299 21280 -19
Partials 2170 2170
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
superset/views/base.py
Outdated
@@ -380,7 +380,9 @@ def menu_data(user: User) -> dict[str, Any]: | |||
|
|||
|
|||
@cache_manager.cache.memoize(timeout=60) | |||
def cached_common_bootstrap_data(user: User, locale: str) -> dict[str, Any]: | |||
def cached_common_bootstrap_data( # pylint: disable=unused-argument | |||
user: User, user_id: int | None, locale: str |
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.
Why do we need both User
and user_id
? Surely the former contains the ID which should then be used for the cache key given it's globally unique.
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.
While yes, User
"contains" the ID, it's __repr__
method that flask-caching uses to generate the cache key does not (see PR description).
The 2 alternatives to this fix as-is would be to:
- change the
__repr__
upstream on FAB to include the id. (cc @dpgaspar if this is a welcome change) - Subclass FAB's user class and add our own repr then update to use that user class everywhere, which seems like a pretty large change for this small fix
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.
@jfrag1 sorry I missed that detail when initially glancing over the PR description. Is a third alternative to just pass in user_id
instead of both user
and user_id
?
superset/views/base.py
Outdated
@@ -380,7 +380,9 @@ def menu_data(user: User) -> dict[str, Any]: | |||
|
|||
|
|||
@cache_manager.cache.memoize(timeout=60) | |||
def cached_common_bootstrap_data(user: User, locale: str) -> dict[str, Any]: | |||
def cached_common_bootstrap_data( # pylint: disable=unused-argument | |||
user: User, user_id: int | None, locale: str |
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.
@jfrag1 sorry I missed that detail when initially glancing over the PR description. Is a third alternative to just pass in user_id
instead of both user
and user_id
?
superset/views/base.py
Outdated
@@ -424,8 +426,9 @@ def cached_common_bootstrap_data(user: User, locale: str) -> dict[str, Any]: | |||
|
|||
|
|||
def common_bootstrap_payload(user: User) -> dict[str, Any]: | |||
user_id = user.id if hasattr(user, "id") else None |
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.
It seems like the FAB User
class will always have the id
attribute defined and thus simply passing in user.id
on line 431 should be safe.
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.
Grokking through the code it seems like this is always g.user
, thus rather than passing around the global, it seems clearer to remove the user
variable from said method and thus said logic simply becomes,
from superset.utils.core import get_user_id
def common_bootstrap_payload() -> dict[str, Any]:
return {
**cached_common_bootstrap_data(get_user_id(), get_locale()),
"flash_messages": get_flashed_messages(with_categories=True),
}
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.
Oh great point it is always g.user
, I'll make that change.
It seems like the FAB
User
class will always have theid
attribute defined and thus simply passing inuser.id
on line 431 should be safe.
The type hint was a bit of a lie since g.user
could also be AnonymousUserMixin
. I'd update it but it's getting removed instead
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.
@jfrag1 I think my comment is somewhat of a moot point given that it seems like we're going down the get_user_id()
route.
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.
LGTM
… first/last name (apache#26023) (cherry picked from commit 630734b)
🏷️ preset:2023.47 |
… first/last name (#26023)
SUMMARY
The caching mechanism for bootstrap data was not working as intended in the case where two users share the same first/last name. The cache key was dependent on the
User
object passed. When a non-primitive object is used to generate a cache key,flask-caching
callsrepr()
on it to determine the key: https://flask-caching.readthedocs.io/en/latest/index.html#memoization.The
User
class comes from flask appbuilder and has the following__repr__
:Therefore, when two users shared a first and last name, they would generate the same cache key and the cache could leak between the two users. This was noticed because it would result in affected users seeing menu results they shouldn't see, though it potentially had other subtle effects as well, particularly for anyone adding a user-dependent
COMMON_BOOTSTRAP_OVERRIDES_FUNC
.This PR fixes the issue by passing the actual user id to the memoized function so that it's included in the cache key.
BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
ADDITIONAL INFORMATION