-
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: Refactor SQL engine username logic #19914
fix: Refactor SQL engine username logic #19914
Conversation
if not alive: | ||
raise DBAPIError(None, None, None) | ||
|
||
with override_user(self._actor): |
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.
Same logic as previously, though indented due to the context manager.
errors = database.db_engine_spec.extract_errors(ex, context) | ||
raise DatabaseTestConnectionFailedError(errors) from ex | ||
|
||
with override_user(self._actor): |
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.
Same logic as previously, though indented due to the context manager.
return database.get_sqla_engine( | ||
schema=schema, nullpool=True, user_name=user_name, source=source | ||
) | ||
return database.get_sqla_engine(schema=schema, source=source) |
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.
nullpool=True
is the default.
) | ||
return df | ||
|
||
with override_user( |
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.
Same logic as previously, though indented due to the context manager.
superset/sql_lab.py
Outdated
stats_logger.incr("error_sqllab_unhandled") | ||
query = get_query(query_id, session) | ||
return handle_query_error(ex, query, session) | ||
with override_user(current_app.app_builder.sm.find_user(username=username)): |
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.
Same logic as previously, though indented due to the context manager.
@@ -149,37 +156,35 @@ def get_sql_results( # pylint: disable=too-many-arguments | |||
rendered_query: str, | |||
return_results: bool = True, | |||
store_results: bool = False, | |||
user_name: Optional[str] = None, | |||
username: Optional[str] = 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.
We still need to pass in the username
to the async Celery task and then override.
405a7d1
to
c3f77b9
Compare
c3f77b9
to
7fc9f54
Compare
6e69f87
to
0fd917d
Compare
self.logout() | ||
self.login(username=(user_name or "admin")) | ||
self.login(username=username) |
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.
Given that on like #372 username
is truthy there's no need for the username or "admin"
logic.
0fd917d
to
77d90ff
Compare
Codecov Report
@@ Coverage Diff @@
## master #19914 +/- ##
=======================================
Coverage 66.36% 66.36%
=======================================
Files 1712 1712
Lines 64088 64088
Branches 6744 6744
=======================================
Hits 42529 42529
Misses 19846 19846
Partials 1713 1713
Flags with carried forward coverage won't be shown. Click here to find out more. Continue to review full report at Codecov.
|
00c93e0
to
31c405e
Compare
@@ -1400,6 +1401,30 @@ def get_username() -> Optional[str]: | |||
return None | |||
|
|||
|
|||
@contextmanager | |||
def override_user(user: Optional[User]) -> Iterator[Any]: |
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.
should it be def override_user(user: Optional[User]) -> Iterator[None]:
?
else: | ||
g.user = user | ||
yield | ||
delattr(g, "user") |
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.
g
is thread safe, it's created here: https://github.com/pallets/flask/blob/bd56d19b167822a9a23e2e9e2a07ccccc36baa8d/src/flask/globals.py#L59
Uses werkzeug Local: https://werkzeug.palletsprojects.com/en/1.0.x/local/#module-werkzeug.local
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. I've long been annoyed by the user_?name = g.user.username if g.user and hasattr(g.user, "username") else None
snippet that is littered around the codebase. Super happy to see it removed and centralized behind a single codepath.
1792826
to
8d6dfb6
Compare
8d6dfb6
to
7431fb6
Compare
Co-authored-by: John Bodley <[email protected]>
SUMMARY
This PR fixes a number of issues (some cosmetic) with the SQL username. Specifically:
username
anduser_name
. The inconsistency added undue complexity.g.user.username
was passed as theusername
argument of theget_sqla_engine
function (or similar) even when it was the default fallback. This added unnecessary complexity, i.e., from reading the code it wasn't apparent when/why the username would be overridden. In reality it mostly isn't, i.e., it should always be the logged in user unless the query is being executed from Celery—where there is no user logged in.get_df
for the alert was executed by the passed in user, if the underlying query used Jinja templating for fetching the the latest partition or similar those queries were executed by the user defined in the SQLAlchemy URI (if defined; falling back to the system user)—leveraging the SQLAlchemy inspector—per here.The fix for (1) was to rename
user_name
tousername
where possible. For (2) and (3) the combined fix was to actually remove overriding the username in functions given the level of nesting, etc. and rely simply on usingg.user.username
, i.e., the logged in user, as the user (or effective user) for all queries. Given we're using globals viaflask.g
—Flask's versions of globals which persist for the duration of the request—why not update it rather than passing around another username everywhere. For instances where there was no user logged in: alerting, async SQL Lab queries, etc. we simply temporarily override theg.user
using the provided username. Hopefully this approach simplified the code and brought more transparency.The one doubt I had was with how the app context works with Celery and whether
flask.g
is thread safe, though per here it seems we create a new app context for every task.BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
TESTING INSTRUCTIONS
CI. Updated existing and added new unit tests.
ADDITIONAL INFORMATION