codeant-ai-for-open-source[bot] commented on code in PR #40071:
URL: https://github.com/apache/superset/pull/40071#discussion_r3227835254


##########
superset/utils/oauth2.py:
##########
@@ -129,15 +129,26 @@ def refresh_oauth2_token(
     database_id: int,
     user_id: int,
     db_engine_spec: type[BaseEngineSpec],
-    token: DatabaseUserOAuth2Tokens,
 ) -> str | None:
+    # pylint: disable=import-outside-toplevel
+    from superset.models.core import DatabaseUserOAuth2Tokens
+
     # Use longer TTL for OAuth2 token refresh (may involve network calls)
     with DistributedLock(
         namespace="refresh_oauth2_token",
         ttl_seconds=30,
         user_id=user_id,
         database_id=database_id,
     ):
+        # Short circuit in case another request already deleted the token
+        token = (
+            db.session.query(DatabaseUserOAuth2Tokens)
+            .filter_by(user_id=user_id, database_id=database_id)
+            .one_or_none()
+        )
+        if token is None:
+            return None

Review Comment:
   **Suggestion:** After re-querying the token under the lock, the code only 
checks whether the row exists, but not whether `refresh_token` is still 
present. Since `refresh_token` is nullable and can legitimately be `None`, this 
can call `get_oauth2_fresh_token` with a null token and trigger an OAuth error 
path that deletes the row unnecessarily. Add a post-requery guard for missing 
`refresh_token` (and return `None` without refreshing) before invoking the 
engine spec. [incorrect condition logic]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ❌ Dashboard and SQL queries fail during concurrent OAuth2 refresh.
   - ⚠️ Users forced to re-authenticate despite recent OAuth2 login.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. Trigger a database connection using OAuth2 in 
`Database.get_sqla_engine()` at
   `superset/models/core.py:33-43` by loading a dashboard or chart backed by an
   OAuth2-enabled database; this calls `get_oauth2_access_token(oauth2_config, 
self.id,
   g.user.id, self.db_engine_spec)` at line 35.
   
   2. Ensure there is an existing row in `database_user_oauth2_tokens` with a 
valid
   `refresh_token` and an expired `access_token` for this `(user_id, 
database_id)` pair,
   created by `OAuth2StoreTokenCommand.run()` at
   `superset/commands/database/oauth2.py:48-58`, which stores
   `refresh_token=token_response.get("refresh_token")`.
   
   3. When `get_oauth2_access_token` runs at 
`superset/utils/oauth2.py:107-119`, it loads the
   token row, sees that `access_token` is expired, and since 
`token.refresh_token` is truthy
   at line 118, it calls `refresh_oauth2_token(config, database_id, user_id, 
db_engine_spec)`
   at line 119.
   
   4. In parallel, invoke the OAuth2 callback API `/api/v1/database/...` 
handled in
   `superset/databases/api.py:20-22`, which constructs 
`OAuth2StoreTokenCommand(parameters)`
   and calls `command.run()`. This command deletes any existing token via
   `DatabaseUserOAuth2TokensDAO.delete([existing])` at
   `superset/commands/database/oauth2.py:41-47` and creates a new token row with
   `refresh_token=token_response.get("refresh_token")` at lines 48-57; for 
providers that do
   not return a refresh token, this new row has `refresh_token=None`.
   
   5. Back in `refresh_oauth2_token` at `superset/utils/oauth2.py:48-71`, after 
acquiring the
   `DistributedLock`, the code re-queries the token (`token =
   db.session.query(DatabaseUserOAuth2Tokens)...one_or_none()` at lines 65-69) 
and only
   checks `if token is None: return None` at lines 70-71, not whether 
`token.refresh_token`
   is still present; in the concurrent scenario, this re-query can return the 
new row with
   `refresh_token=None`.
   
   6. `refresh_oauth2_token` then unconditionally calls
   `db_engine_spec.get_oauth2_fresh_token(config, token.refresh_token)` at
   `superset/utils/oauth2.py:73-77`. With `token.refresh_token` now `None`, the 
engine spec
   can raise its `oauth2_exception`, triggering the handler at lines 78-87 that 
logs the
   error, deletes the token row (`db.session.delete(token)`), flushes, and 
re-raises, causing
   the calling request's database connection attempt to fail and unnecessarily 
deleting the
   newly stored token.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Futils%2Foauth2.py%0A%2A%2ALine%3A%2A%2A%20143%3A150%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20After%20re-querying%20the%20token%20under%20the%20lock%2C%20the%20code%20only%20checks%20whether%20the%20row%20exists%2C%20but%20not%20whether%20%60refresh_token%60%20is%20still%20present.%20Since%20%60refresh_token%60%20is%20nullable%20and%20can%20legitimately%20be%20%60None%60%2C%20this%20can%20call%20%60get_oauth2_fresh_token%60%20with%20a%20null%20token%20and%20trigger%20an%20OAuth%20error%20path%20that%20deletes%20the%20row%20unnecessarily.%20Add%20a%20post-requery%20guard%20for%20missing%20%60refresh_token%60%20%28and%20return%20%60None%60%20without%20refreshing%29%20before%20invoking%20the%20engine%20spec.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20Ho
 
w%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Futils%2Foauth2.py%0A%2A%2ALine%3A%2A%2A%20143%3A150%0A%2A%2AComment%3A%2A%2A%0A%09%2AIncorrect%20Condition%20Logic%3A%20After%20re-querying%20the%20token%20under%20the%20lock%2C%20the%20code%20only%20checks%20whether%20the%20row%20exists%2C%20but%20not%20whether%20%60refresh_token%60%20is%20still%20present.%20Since%20%60refresh_token%60%20is%20nullable%20and%
 
20can%20legitimately%20be%20%60None%60%2C%20this%20can%20call%20%60get_oauth2_fresh_token%60%20with%20a%20null%20token%20and%20trigger%20an%20OAuth%20error%20path%20that%20deletes%20the%20row%20unnecessarily.%20Add%20a%20post-requery%20guard%20for%20missing%20%60refresh_token%60%20%28and%20return%20%60None%60%20without%20refreshing%29%20before%20invoking%20the%20engine%20spec.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/utils/oauth2.py
   **Line:** 143:150
   **Comment:**
        *Incorrect Condition Logic: After re-querying the token under the lock, 
the code only checks whether the row exists, but not whether `refresh_token` is 
still present. Since `refresh_token` is nullable and can legitimately be 
`None`, this can call `get_oauth2_fresh_token` with a null token and trigger an 
OAuth error path that deletes the row unnecessarily. Add a post-requery guard 
for missing `refresh_token` (and return `None` without refreshing) before 
invoking the engine spec.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40071&comment_hash=0cee6dd3144601665d64af98df8d7c344b34911c5bae74e07faf52ec96c9ab8c&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F40071&comment_hash=0cee6dd3144601665d64af98df8d7c344b34911c5bae74e07faf52ec96c9ab8c&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to