mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-17 16:34:44 -05:00
Gracefully handle a missing crypt key in the Query Tool connection endpoints. (#10065)
After a backend/pod restart the in-memory crypt key is gone, so manager.connection() raises CryptKeyMissing. The new-connection endpoints (_check_server_connection_status and get_new_connection_*) swallowed it in a broad "except Exception", logged a full ERROR traceback, and returned a generic error the client cannot recognise. The standard recovery (a 503 CRYPTKEY_MISSING response that the client uses to transparently re-establish the key and retry) therefore never fired, leaving a spurious "Crypt key is missing" message in the Query Tool and noisy tracebacks in the log. Re-raise CryptKeyMissing (along with ConnectionLost / SSHTunnelConnectionLost) before the generic handler, matching the pattern already used by the query execution path, so these endpoints emit the standard CRYPTKEY_MISSING response and the client recovers gracefully. Closes #10027
This commit is contained in:
@@ -59,4 +59,5 @@ Bug fixes
|
||||
| `Issue #9984 <https://github.com/pgadmin-org/pgadmin4/issues/9984>`_ - Fix the Docker entrypoint mishandling a quoted PGADMIN_CONFIG_CONFIG_DATABASE_URI, which caused a SQLAlchemy parse error and silently skipped PGADMIN_DEFAULT_EMAIL/PASSWORD setup.
|
||||
| `Issue #9987 <https://github.com/pgadmin-org/pgadmin4/issues/9987>`_ - Fix "AttributeError: 'PgAdmin' object has no attribute 'login_manager'" crash when running setup.py user-management commands (add-user, update-user) from the CLI.
|
||||
| `Issue #9988 <https://github.com/pgadmin-org/pgadmin4/issues/9988>`_ - Provide an actionable error when 'openid' is in OAUTH2_SCOPE but OAUTH2_SERVER_METADATA_URL is not set, instead of a cryptic Authlib failure.
|
||||
| `Issue #10027 <https://github.com/pgadmin-org/pgadmin4/issues/10027>`_ - Fix the spurious "Crypt key is missing" error and logged traceback in the Query Tool new-connection endpoints after a backend restart, by surfacing it as the standard CRYPTKEY_MISSING response so the client recovers transparently.
|
||||
| `Issue #10059 <https://github.com/pgadmin-org/pgadmin4/issues/10059>`_ - Fix the generated SQL for editing a SQL-language function/procedure whose body contains the word "return" (e.g. a RETURNING clause), which was wrongly treated as a SQL-standard body and produced a statement without the AS $BODY$ wrapper.
|
||||
|
||||
@@ -2381,6 +2381,8 @@ def _check_server_connection_status(sgid, sid=None):
|
||||
}
|
||||
)
|
||||
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
@@ -2447,6 +2449,8 @@ def get_new_connection_data(sgid=None, sid=None):
|
||||
}
|
||||
)
|
||||
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
@@ -2521,6 +2525,8 @@ def get_new_connection_database(sgid, sid=None):
|
||||
}
|
||||
}
|
||||
)
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
@@ -2587,6 +2593,8 @@ def get_new_connection_user(sgid, sid=None):
|
||||
}
|
||||
}
|
||||
)
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
@@ -2651,6 +2659,8 @@ def get_new_connection_role(sgid, sid=None):
|
||||
}
|
||||
}
|
||||
)
|
||||
except (ConnectionLost, SSHTunnelConnectionLost, CryptKeyMissing):
|
||||
raise
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
from pgadmin.utils.route import BaseTestGenerator
|
||||
from regression import parent_node_dict
|
||||
from regression.test_setup import config_data
|
||||
from regression.python_test_utils import test_utils as utils
|
||||
from pgadmin.utils.exception import CryptKeyMissing
|
||||
|
||||
|
||||
class TestNewConnectionDialog(BaseTestGenerator):
|
||||
@@ -19,15 +19,19 @@ class TestNewConnectionDialog(BaseTestGenerator):
|
||||
('New connection dialog',
|
||||
dict(
|
||||
url="/sqleditor/new_connection_dialog/",
|
||||
is_positive_test=True,
|
||||
mocking_required=False,
|
||||
is_connect_server=False,
|
||||
test_data={},
|
||||
mock_data={},
|
||||
crypt_key_missing=False,
|
||||
expected_data={
|
||||
"status_code": 200
|
||||
}
|
||||
)),
|
||||
('New connection dialog when the crypt key is missing',
|
||||
dict(
|
||||
url="/sqleditor/new_connection_dialog/",
|
||||
crypt_key_missing=True,
|
||||
expected_data={
|
||||
"status_code": 503
|
||||
}
|
||||
)),
|
||||
]
|
||||
|
||||
def setUp(self):
|
||||
@@ -43,8 +47,20 @@ class TestNewConnectionDialog(BaseTestGenerator):
|
||||
return response
|
||||
|
||||
def runTest(self):
|
||||
if self.is_positive_test:
|
||||
if self.crypt_key_missing:
|
||||
# When the crypt key is missing (e.g. the backend was restarted),
|
||||
# the endpoint must surface CryptKeyMissing as a 503
|
||||
# CRYPTKEY_MISSING response so the client can transparently
|
||||
# recover, rather than swallowing it into a generic error and
|
||||
# logging a traceback. See issue #10027.
|
||||
with patch('pgadmin.utils.driver.psycopg3.server_manager.'
|
||||
'ServerManager.connection',
|
||||
side_effect=CryptKeyMissing()):
|
||||
response = self.new_connection()
|
||||
self.assertEqual(response.status_code,
|
||||
self.expected_data['status_code'])
|
||||
self.assertIn('CRYPTKEY_MISSING', response.data.decode('utf-8'))
|
||||
else:
|
||||
response = self.new_connection()
|
||||
actual_response_code = response.status_code
|
||||
expected_response_code = self.expected_data['status_code']
|
||||
self.assertEqual(actual_response_code, expected_response_code)
|
||||
self.assertEqual(response.status_code,
|
||||
self.expected_data['status_code'])
|
||||
|
||||
Reference in New Issue
Block a user