bito-code-review[bot] commented on PR #40695:
URL: https://github.com/apache/superset/pull/40695#issuecomment-4608220748

   <!-- Bito Reply -->
   The flagged issue regarding a race condition in the 
`invalidate_user_sessions` function is correct. The current implementation 
performs a non-atomic update-then-insert, which can lead to duplicate 
`UserAttribute` rows if two concurrent processes attempt to disable the same 
user simultaneously.
   
   To resolve this, you can use a database-level upsert. Since Superset 
supports multiple database backends, the most robust approach is to use 
SQLAlchemy's `dialects` to handle `ON CONFLICT` (for PostgreSQL/SQLite) or to 
serialize the operation using a `SELECT ... FOR UPDATE` lock.
   
   Given the context, a simple and effective fix is to use a `SELECT ... FOR 
UPDATE` lock to serialize the check-and-insert operation:
   
   ```python
   def invalidate_user_sessions(connection: Any, user_id: int) -> None:
       from superset.models.user_attributes import UserAttribute
       table = UserAttribute.__table__
       now = _utcnow().replace(tzinfo=None)
       
       # Serialize the check-and-insert using a row-level lock
       with connection.begin_nested():
           existing = connection.execute(
               table.select().where(table.c.user_id == 
user_id).with_for_update()
           ).fetchone()
           
           if existing:
               connection.execute(
                   table.update().where(table.c.user_id == 
user_id).values(sessions_invalidated_at=now, changed_on=now)
               )
           else:
               connection.execute(
                   table.insert().values(user_id=user_id, 
sessions_invalidated_at=now, created_on=now, changed_on=now)
               )
   ```
   
   Would you like me to check the other comments on this PR and implement fixes 
for them as well?
   
   **superset/security/session_invalidation.py**
   ```
   # Serialize the check-and-insert using a row-level lock
       with connection.begin_nested():
           existing = connection.execute(
               table.select().where(table.c.user_id == 
user_id).with_for_update()
           ).fetchone()
           
           if existing:
               connection.execute(
                   table.update().where(table.c.user_id == 
user_id).values(sessions_invalidated_at=now, changed_on=now)
               )
           else:
               connection.execute(
                   table.insert().values(user_id=user_id, 
sessions_invalidated_at=now, created_on=now, changed_on=now)
               )
   ```


-- 
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