You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/09/22 22:02:48 UTC

(superset) branch master updated: fix(views): return 401 for NoAuthorizationError in handle_api_exception (SC-121647) (#44533)

This is an automated email from the ASF dual-hosted git repository.

eschutho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 269b9f99fef fix(views): return 401 for NoAuthorizationError in handle_api_exception (SC-121647) (#44533)
269b9f99fef is described below

commit 269b9f99fef803ff154f71bd744f80998e36ac75
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Tue Sep 22 15:02:41 2026 -0700

    fix(views): return 401 for NoAuthorizationError in handle_api_exception (SC-121647) (#44533)
    
    Co-authored-by: Claude Opus 4.6 <no...@anthropic.com>
---
 superset/views/error_handling.py              |  6 +++++-
 tests/unit_tests/views/test_error_handling.py | 22 ++++++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/superset/views/error_handling.py b/superset/views/error_handling.py
index bf8456db12a..71c7bb8d12e 100644
--- a/superset/views/error_handling.py
+++ b/superset/views/error_handling.py
@@ -32,6 +32,7 @@ from flask import (
 )
 from flask_babel import gettext as _
 from flask_babel.speaklater import LazyString
+from flask_jwt_extended.exceptions import NoAuthorizationError
 from flask_wtf.csrf import CSRFError
 from sqlalchemy import exc
 from werkzeug.exceptions import HTTPException
@@ -138,7 +139,7 @@ def handle_api_exception(  # noqa: C901
     exceptions.
     """
 
-    def wraps(self: BaseSupersetView, *args: Any, **kwargs: Any) -> FlaskResponse:
+    def wraps(self: BaseSupersetView, *args: Any, **kwargs: Any) -> FlaskResponse:  # noqa: C901
         try:
             return f(self, *args, **kwargs)
         except SupersetSecurityException as ex:
@@ -175,6 +176,9 @@ def handle_api_exception(  # noqa: C901
             return json_error_response(utils.error_msg_from_exception(ex), status=422)
         except sshtunnel.BaseSSHTunnelForwarderError as ex:
             return handle_ssh_tunnel_error(ex)
+        except NoAuthorizationError as ex:
+            logger.warning("Api failed- no authorization", exc_info=True)
+            return json_error_response(str(ex), status=401)
         except Exception as ex:  # pylint: disable=broad-except
             logger.exception(ex)
             return json_error_response(utils.error_msg_from_exception(ex))
diff --git a/tests/unit_tests/views/test_error_handling.py b/tests/unit_tests/views/test_error_handling.py
index f9ce636a00d..7f7535ec44a 100644
--- a/tests/unit_tests/views/test_error_handling.py
+++ b/tests/unit_tests/views/test_error_handling.py
@@ -24,6 +24,7 @@ import pytest
 import sshtunnel
 from flask import Flask, Response, session
 from flask_babel import Babel
+from flask_jwt_extended.exceptions import NoAuthorizationError
 from flask_wtf.csrf import CSRFProtect, generate_csrf
 from freezegun import freeze_time
 from sqlalchemy.exc import IntegrityError, OperationalError, ProgrammingError
@@ -72,6 +73,27 @@ class TestHandleApiExceptionSSHTunnelError:
         )
 
 
+class TestHandleApiExceptionNoAuthorizationError:
+    def test_returns_401_and_logs_at_warning_not_error(
+        self, app, caplog: pytest.LogCaptureFixture
+    ):
+        @handle_api_exception
+        def view(self: object) -> FlaskResponse:
+            raise NoAuthorizationError("Missing JWT in cookies or headers")
+
+        with app.test_request_context():
+            with caplog.at_level(logging.WARNING):
+                response = cast(Response, view(self=object()))
+
+        assert response.status_code == 401
+        assert not any(record.levelno >= logging.ERROR for record in caplog.records)
+        assert any(
+            record.levelno == logging.WARNING
+            and "Api failed- no authorization" in record.message
+            for record in caplog.records
+        )
+
+
 class TestHandleApiExceptionDatabaseErrors:
     """
     `OperationalError` is a `DatabaseError` subclass, so it must be matched by