diff --git a/panel/auth.py b/panel/auth.py index 04e009dd87..1f304643de 100644 --- a/panel/auth.py +++ b/panel/auth.py @@ -54,6 +54,7 @@ def decode_response_body(response): body = codecs.decode(response.body, 'ascii') except Exception: body = codecs.decode(response.body, 'utf-8') + body = re.sub("\'", '\\"', body) body = re.sub('"', '\"', body) body = re.sub("'", '"', body) body = json.loads(body) @@ -450,10 +451,17 @@ def _raise_error(self, response, body=None, status=400): log.warning(f"{provider} OAuth provider failed to fully " f"authenticate returning the following response:" f"{body}.") + if hasattr(body, "get"): + log_message = body.get('error_description', str(body)) + reason = body.get('error', 'Unknown error') + else: + log_message = str(response) + reason = 'Unknown Error' + raise HTTPError( status, - body.get('error_description', str(body)), - reason=body.get('error', 'Unknown error') + log_message=log_message, + reason=reason ) def write_error(self, status_code, **kwargs): diff --git a/panel/command/__init__.py b/panel/command/__init__.py index 7efd416cbf..595d66a530 100644 --- a/panel/command/__init__.py +++ b/panel/command/__init__.py @@ -91,7 +91,7 @@ def main(args: list[str] | None = None): try: ret = parsed_args.invoke(parsed_args) except Exception as e: - if config.dev: + if config.autoreload: raise e die("ERROR: " + str(e)) else: diff --git a/panel/tests/test_auth.py b/panel/tests/test_auth.py new file mode 100644 index 0000000000..2484d52042 --- /dev/null +++ b/panel/tests/test_auth.py @@ -0,0 +1,63 @@ +""" +This module contains tests for handling OAuth login and response decoding +using the Tornado HTTP client and Panel's OAuthLoginHandler. +""" + +from unittest.mock import Mock, patch + +import pytest + +from tornado.httpclient import HTTPRequest, HTTPResponse +from tornado.web import HTTPError + +from panel.auth import OAuthLoginHandler, decode_response_body + + +def _create_mock_response(body, code=401): + """ + Create a mock HTTPResponse object with the specified body and status code. + + Parameters + ---------- + body : bytes + The body content of the mock response. + code : int, optional + The HTTP status code of the mock response (default is 401). + + Returns + ------- + Mock + A mock HTTPResponse object with the specified attributes. + """ + mock_request = Mock(spec=HTTPRequest) + mock_response = Mock(spec=HTTPResponse) + mock_response.body = body + mock_response.request = mock_request + mock_response.code = code + return mock_response + +def test_decode_invalid_response_body(): + """ + Test the decode_response_body function with an invalid JSON response body. + + Ensures that the function can handle and correct invalid JSON containing + single quotes, which are not valid in JSON strings. + """ + # The body below from azure contains \' which is not valid json. + body = b'{"error_description":"... for a secret added to app \'some-value\'."}' + invalid_response = _create_mock_response(body) + result = decode_response_body(invalid_response) + assert result == {'error_description': '... for a secret added to app "some-value".'} + +def test_raise_error(): + """ + Test the _raise_error method of OAuthLoginHandler with an invalid JSON response. + + Mocks the OAuthLoginHandler to bypass initialization and verifies that + an HTTPError is raised when the response contains invalid JSON. + """ + response = _create_mock_response(b'{"invalid_json": "missing_end_quote}') + with patch.object(OAuthLoginHandler, '__init__', lambda self, *args, **kwargs: None): + handler = OAuthLoginHandler() + with pytest.raises(HTTPError): + handler._raise_error(response=response, body=None, status=401)