From 10e7a36b7c46b7603fafd990fbf4d8adf9393969 Mon Sep 17 00:00:00 2001 From: Khushboo Vashi Date: Wed, 4 Mar 2026 16:03:41 +0530 Subject: [PATCH 1/2] Fixed an issue where OAuth2 authentication fails with 'object has no attribute' if OAUTH2_AUTO_CREATE_USER is False. #9279 --- web/pgadmin/authenticate/oauth2.py | 44 ++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/web/pgadmin/authenticate/oauth2.py b/web/pgadmin/authenticate/oauth2.py index 13946cb7205..9d6eb955c4f 100644 --- a/web/pgadmin/authenticate/oauth2.py +++ b/web/pgadmin/authenticate/oauth2.py @@ -437,9 +437,9 @@ def _resolve_username(self, id_token_claims, profile_dict): def login(self, form): if not self.oauth2_current_client: - error_msg = 'No OAuth2 provider available.' + error_msg = gettext('No OAuth2 provider available.') current_app.logger.error(error_msg) - return False, gettext(error_msg) + return False, error_msg profile = self.get_user_profile() profile_dict = self.get_profile_dict(profile) @@ -468,35 +468,35 @@ def login(self, form): not id_token_claims and not profile_dict ): - error_msg = "No profile data found from OIDC provider." + error_msg = gettext("No profile data found from OIDC provider.") current_app.logger.error(error_msg) - return False, gettext(error_msg) + return False, error_msg # For non-OIDC providers, profile is required if not self._is_oidc_provider() and not profile_dict: - error_msg = "No profile data found." + error_msg = gettext("No profile data found.") current_app.logger.error(error_msg) - return False, gettext(error_msg) + return False, error_msg # Resolve username using OIDC-aware logic username, email = self._resolve_username(id_token_claims, profile_dict) if not username: if self._is_oidc_provider(): - error_msg = ( + error_msg = gettext( 'Could not extract username from OIDC claims. ' 'Please ensure your OIDC provider returns standard ' 'claims (email, preferred_username, or sub).' ) else: - error_msg = ( + error_msg = gettext( 'An email id or OAUTH2_USERNAME_CLAIM is required to ' 'login into pgAdmin. Please update your OAuth2 profile ' 'for email id or set OAUTH2_USERNAME_CLAIM config ' 'parameter.' ) current_app.logger.error(error_msg) - return False, gettext(error_msg) + return False, error_msg additional_claims = None if 'OAUTH2_ADDITIONAL_CLAIMS' in self.oauth2_config[ @@ -678,10 +678,12 @@ def authenticate(self, form): self.oauth2_current_client].authorize_redirect(redirect_url) def __auto_create_user(self, username, email): - if config.OAUTH2_AUTO_CREATE_USER: - user = User.query.filter_by(username=username, - auth_source=OAUTH2).first() - if not user: + user = User.query.filter_by(username=username, + auth_source=OAUTH2).first() + if user: + return True, {'username': username} + else: + if config.OAUTH2_AUTO_CREATE_USER: create_msg = ("Creating user {0} with email {1} " "from auth source OAUTH2.") current_app.logger.info(create_msg.format(username, @@ -693,18 +695,20 @@ def __auto_create_user(self, username, email): 'active': True, 'auth_source': OAUTH2 }) - - return True, {'username': username} + else: + return False, gettext('No Email/Username found.' + ' Please contact your administrator.') def __is_any_claim_valid(self, identity, additional_claims): if additional_claims is None: - reason = "Additional claim config is None, no check to do." + reason = gettext("Additional claim config is None," + " no check to do.") return (True, reason) if not isinstance(additional_claims, dict): - reason = "Additional claim check config is not a dict." + reason = gettext("Additional claim check config is not a dict.") return (False, reason) if additional_claims.keys() is None: - reason = "Additional claim check config dict is empty." + reason = gettext("Additional claim check config dict is empty.") return (False, reason) for key in additional_claims.keys(): claim = identity.get(key) @@ -716,7 +720,7 @@ def __is_any_claim_valid(self, identity, additional_claims): if not isinstance(authorized_claims, list): authorized_claims = [authorized_claims] if any(item in authorized_claims for item in claim): - reason = "Claim match found. Authorized access." + reason = gettext("Claim match found. Authorized access.") return True, reason - reason = "No match was found." + reason = gettext("No match was found.") return False, reason From c6abfe05be6568dcab9faa0dba5099315459925a Mon Sep 17 00:00:00 2001 From: Khushboo Vashi Date: Wed, 4 Mar 2026 17:08:00 +0530 Subject: [PATCH 2/2] Code Cleanup. --- web/pgadmin/authenticate/oauth2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/pgadmin/authenticate/oauth2.py b/web/pgadmin/authenticate/oauth2.py index 9d6eb955c4f..155196e2dcd 100644 --- a/web/pgadmin/authenticate/oauth2.py +++ b/web/pgadmin/authenticate/oauth2.py @@ -707,7 +707,7 @@ def __is_any_claim_valid(self, identity, additional_claims): if not isinstance(additional_claims, dict): reason = gettext("Additional claim check config is not a dict.") return (False, reason) - if additional_claims.keys() is None: + if len(additional_claims.keys()) == 0: reason = gettext("Additional claim check config dict is empty.") return (False, reason) for key in additional_claims.keys():