diff --git a/web/pgacloud/providers/google.py b/web/pgacloud/providers/google.py index f7c93ec7715..5d34b50c824 100644 --- a/web/pgacloud/providers/google.py +++ b/web/pgacloud/providers/google.py @@ -8,12 +8,23 @@ # ########################################################################## import json import os +import sys import time from utils.io import debug, error, output from utils.misc import get_my_ip, get_random_id from providers._abstract import AbsProvider +# pgAdmin only authenticates to Google via google-auth, never the +# long-deprecated oauth2client. googleapiclient still tries to import +# oauth2client optionally, and on packaged installs the venv inherits the +# system site-packages (--system-site-packages, see issue #7173) where a +# stale oauth2client and an incompatible pyOpenSSL may be present. That +# optional import drags in the broken pyOpenSSL and crashes with "module +# 'lib' has no attribute 'GEN_EMAIL'". Blocking the module here makes +# googleapiclient fall back to google-auth cleanly. See issue #10110. +sys.modules.setdefault('oauth2client', None) + from googleapiclient import discovery from googleapiclient.errors import HttpError from google.auth.transport.requests import Request diff --git a/web/pgadmin/misc/cloud/google/__init__.py b/web/pgadmin/misc/cloud/google/__init__.py index b6aafa72d24..20fb883593c 100644 --- a/web/pgadmin/misc/cloud/google/__init__.py +++ b/web/pgadmin/misc/cloud/google/__init__.py @@ -10,6 +10,7 @@ # Google Cloud Deployment Implementation import json import os +import sys from urllib.parse import unquote from config import root @@ -25,6 +26,18 @@ from flask_babel import gettext as _ from oauthlib.oauth2 import AccessDeniedError + +# pgAdmin only authenticates to Google via google-auth and +# google-auth-oauthlib, never the long-deprecated oauth2client. +# googleapiclient still tries to import oauth2client optionally, and on +# packaged installs the venv inherits the system site-packages +# (--system-site-packages, see issue #7173) where a stale oauth2client and +# an incompatible pyOpenSSL may be present. That optional import drags in +# the broken pyOpenSSL and aborts startup with "module 'lib' has no +# attribute 'GEN_EMAIL'". Blocking the module here makes googleapiclient +# fall back to google-auth cleanly. See issue #10110. +sys.modules.setdefault('oauth2client', None) + from googleapiclient import discovery from googleapiclient.errors import HttpError from google_auth_oauthlib.flow import InstalledAppFlow diff --git a/web/pgadmin/misc/cloud/google/tests/test_google_oauth2client_blocked.py b/web/pgadmin/misc/cloud/google/tests/test_google_oauth2client_blocked.py new file mode 100644 index 00000000000..14930ecec29 --- /dev/null +++ b/web/pgadmin/misc/cloud/google/tests/test_google_oauth2client_blocked.py @@ -0,0 +1,58 @@ +########################################################################## +# +# pgAdmin 4 - PostgreSQL Tools +# +# Copyright (C) 2013 - 2026, The pgAdmin Development Team +# This software is released under the PostgreSQL Licence +# +########################################################################## + +""" +Regression test for issue #10110. + +On packaged installs the venv inherits the system site-packages +(--system-site-packages, see issue #7173). When a stale ``oauth2client`` +and an incompatible ``pyOpenSSL`` live there, googleapiclient's optional +``import oauth2client`` drags in the broken pyOpenSSL and aborts pgAdmin +startup with "module 'lib' has no attribute 'GEN_EMAIL'". + +The cloud.google module guards against this by parking a ``None`` sentinel +under ``sys.modules['oauth2client']`` before importing googleapiclient, so +the optional import fails cleanly and google-auth is used instead. +""" + +import sys +import unittest + +from pgadmin.utils.route import BaseTestGenerator + + +class _SkipServerSetUpMixin: + """Skip BaseTestGenerator's Postgres connection — pure logic tests.""" + + def setUp(self): + unittest.TestCase.setUp(self) + + +class TestOauth2ClientBlockedOnGoogleImport( + _SkipServerSetUpMixin, BaseTestGenerator): + """Importing cloud.google must keep googleapiclient off oauth2client.""" + + scenarios = (('default', {}),) + + def runTest(self): + # Importing the module installs the sentinel as a side effect. + import pgadmin.misc.cloud.google # noqa: F401 + + # The sentinel must be in place so googleapiclient never imports + # the deprecated oauth2client (and the pyOpenSSL it would pull in). + self.assertIsNone( + sys.modules.get('oauth2client', 'missing'), + "cloud.google must park a None sentinel under " + "sys.modules['oauth2client'] to block the optional import") + + # googleapiclient must consequently report no oauth2client support. + import googleapiclient._auth as _auth + self.assertFalse( + _auth.HAS_OAUTH2CLIENT, + "googleapiclient must fall back to google-auth, not oauth2client")