From 00f7dbbd696c5f0fe75cb098ea6cbb2bca149b9e Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Tue, 26 Aug 2025 03:28:16 +0200 Subject: [PATCH] feat: raise a warning if a GSS connection is obtained using the libpq default Suggest the user to specify a 'gssencmode' setting explicitly so that, in the future, we will be able to change the setting default to 'disable'. See #1136 --- .flake8 | 1 + docs/news.rst | 10 ++++++++++ psycopg/psycopg/_conninfo_utils.py | 5 +++++ psycopg/psycopg/connection.py | 12 ++++++++++++ psycopg/psycopg/connection_async.py | 17 +++++++++++++++++ tests/test_connection.py | 14 ++++++++++++++ tests/test_connection_async.py | 14 ++++++++++++++ 7 files changed, 73 insertions(+) diff --git a/.flake8 b/.flake8 index 79e029388..0cc6c5ff5 100644 --- a/.flake8 +++ b/.flake8 @@ -12,6 +12,7 @@ per-file-ignores = psycopg/psycopg/errors.py: E125, E128, E302 # Allow concatenated string literals from async_to_sync + psycopg/psycopg/connection.py: E501 psycopg_pool/psycopg_pool/pool.py: E501 # Pytest's importorskip() getting in the way diff --git a/docs/news.rst b/docs/news.rst index d923b53e1..8b2717b47 100644 --- a/docs/news.rst +++ b/docs/news.rst @@ -17,6 +17,16 @@ Psycopg 3.2.10 (unreleased) (:ticket:`#1108`). - Add support for Python 3.14 (:ticket:`#1053`). - Fix `psycopg_binary.__version__`. +- Raise a warning if a GSSAPI connection is obtained using the + ``gssencmode=prefer`` libpq default (see :ticket:`#1136`). + + .. warning:: + + In a future Psycopg version the default in the binary package will be + changed to ``disable``. If you need to interact with the GSSAPI reliably + you should explicitly set the ``gssencmode`` parameter in the connection + string or the :envvar:`PGGSSENCMODE` environment variable to ``prefer`` or + ``require``. Current release diff --git a/psycopg/psycopg/_conninfo_utils.py b/psycopg/psycopg/_conninfo_utils.py index e959bbf15..cc5774e23 100644 --- a/psycopg/psycopg/_conninfo_utils.py +++ b/psycopg/psycopg/_conninfo_utils.py @@ -120,3 +120,8 @@ def is_ip_address(s: str) -> bool: except ValueError: return False return True + + +def gssapi_requested(params: ConnDict) -> bool: + """Return `true` if `gssencmode` was specified explicitly.""" + return bool(get_param(params, "gssencmode")) diff --git a/psycopg/psycopg/connection.py b/psycopg/psycopg/connection.py index 6d89114ee..ec149efcc 100644 --- a/psycopg/psycopg/connection.py +++ b/psycopg/psycopg/connection.py @@ -10,6 +10,7 @@ Psycopg connection object (sync version) from __future__ import annotations import logging +import warnings from time import monotonic from types import TracebackType from typing import TYPE_CHECKING, Any, Generator, Iterator, cast, overload @@ -32,6 +33,7 @@ from .generators import notifies from .transaction import Transaction from ._capabilities import capabilities from ._server_cursor import ServerCursor +from ._conninfo_utils import gssapi_requested from ._connection_base import BaseConnection, CursorRow, Notify if TYPE_CHECKING: @@ -124,6 +126,16 @@ class Connection(BaseConnection[Row]): lines.extend((f"- {descr}: {error}" for error, descr in conn_errors)) raise type(last_ex)("\n".join(lines)).with_traceback(None) + if ( + pq.version() >= 160000 + and rv.pgconn.used_gssapi + and (not gssapi_requested(params)) + ): + warnings.warn( + "the connection was obtained using the GSSAPI relying on the 'gssencmode=prefer' libpq default. In a future psycopg[binary] version this default will be changed to 'disable'. If you wish to interact with the GSSAPI reliably please set the 'gssencmode' parameter in the connection string or the 'PGGSSENCMODE' environment variable to 'prefer' or 'require'", + DeprecationWarning, + ) + rv._autocommit = bool(autocommit) if row_factory: rv.row_factory = row_factory diff --git a/psycopg/psycopg/connection_async.py b/psycopg/psycopg/connection_async.py index be7fda4e5..43dc8c0e8 100644 --- a/psycopg/psycopg/connection_async.py +++ b/psycopg/psycopg/connection_async.py @@ -7,6 +7,7 @@ Psycopg connection object (async version) from __future__ import annotations import logging +import warnings from time import monotonic from types import TracebackType from typing import TYPE_CHECKING, Any, AsyncGenerator, AsyncIterator, cast, overload @@ -27,6 +28,7 @@ from .generators import notifies from .transaction import AsyncTransaction from .cursor_async import AsyncCursor from ._capabilities import capabilities +from ._conninfo_utils import gssapi_requested from ._connection_base import BaseConnection, CursorRow, Notify from ._server_cursor_async import AsyncServerCursor @@ -144,6 +146,21 @@ class AsyncConnection(BaseConnection[Row]): lines.extend(f"- {descr}: {error}" for error, descr in conn_errors) raise type(last_ex)("\n".join(lines)).with_traceback(None) + if ( + pq.version() >= 160000 + and rv.pgconn.used_gssapi + and not gssapi_requested(params) + ): + warnings.warn( + "the connection was obtained using the GSSAPI relying on the" + " 'gssencmode=prefer' libpq default. In a future psycopg[binary]" + " version this default will be changed to 'disable'." + " If you wish to interact with the GSSAPI reliably please set the" + " 'gssencmode' parameter in the connection string or the" + " 'PGGSSENCMODE' environment variable to 'prefer' or 'require'", + DeprecationWarning, + ) + rv._autocommit = bool(autocommit) if row_factory: rv.row_factory = row_factory diff --git a/tests/test_connection.py b/tests/test_connection.py index 240bba0f9..8115cba0b 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -16,6 +16,7 @@ from psycopg import errors as e from psycopg import pq from psycopg.rows import tuple_row from psycopg.conninfo import conninfo_to_dict, timeout_from_conninfo +from psycopg._conninfo_utils import get_param from .acompat import skip_async, skip_sync, sleep from .fix_crdb import crdb_anydb @@ -961,3 +962,16 @@ def test_connect_tsa_bad(conn_cls, dsn, mode): params = conninfo_to_dict(dsn, target_session_attrs=mode) with pytest.raises(psycopg.OperationalError, match=mode): conn_cls.connect(**params) + + +@pytest.mark.libpq(">= 16") +@pytest.mark.skipif(pq.__impl__ != "python", reason="can't monkeypatch C module") +def test_implicit_gssapi_warning(conn_cls, dsn, recwarn, monkeypatch): + if get_param(conninfo_to_dict(dsn), "gssencmode"): + pytest.skip("gssencmode parameter explicitly set in test connection string") + + monkeypatch.setattr(pq.PGconn, "used_gssapi", lambda: True) + with conn_cls.connect(dsn): + pass + + assert "gssencmode" in str(recwarn.pop(DeprecationWarning).message) diff --git a/tests/test_connection_async.py b/tests/test_connection_async.py index 5583821fa..a62a7e627 100644 --- a/tests/test_connection_async.py +++ b/tests/test_connection_async.py @@ -13,6 +13,7 @@ from psycopg import errors as e from psycopg import pq from psycopg.rows import tuple_row from psycopg.conninfo import conninfo_to_dict, timeout_from_conninfo +from psycopg._conninfo_utils import get_param from .acompat import asleep, skip_async, skip_sync from .fix_crdb import crdb_anydb @@ -966,3 +967,16 @@ async def test_connect_tsa_bad(aconn_cls, dsn, mode): params = conninfo_to_dict(dsn, target_session_attrs=mode) with pytest.raises(psycopg.OperationalError, match=mode): await aconn_cls.connect(**params) + + +@pytest.mark.libpq(">= 16") +@pytest.mark.skipif(pq.__impl__ != "python", reason="can't monkeypatch C module") +async def test_implicit_gssapi_warning(aconn_cls, dsn, recwarn, monkeypatch): + if get_param(conninfo_to_dict(dsn), "gssencmode"): + pytest.skip("gssencmode parameter explicitly set in test connection string") + + monkeypatch.setattr(pq.PGconn, "used_gssapi", lambda: True) + async with await aconn_cls.connect(dsn): + pass + + assert "gssencmode" in str(recwarn.pop(DeprecationWarning).message) -- 2.47.3