Skip to content

Conversation

@Pyodin
Copy link

@Pyodin Pyodin commented Jan 13, 2026

Related to issue: #9292
Depends on #9525 and #9534

This PR adds support for OAuth2 client authentication using federated Workload Identity, allowing pgAdmin to authenticate with Microsoft Entra ID without a client secret when running on Azure (e.g. AKS).

When OAUTH2_CLIENT_AUTH_METHOD is set to workload_identity, pgAdmin authenticates to the token endpoint using a JWT client assertion read from a projected service account token, as supported by Entra ID federated credentials.

The implementation is standards-based (client_assertion, JWT bearer), backward-compatible with existing OAuth2 configurations, and requires no Azure-specific SDK.

Summary by CodeRabbit

  • New Features

    • Added OpenID Connect (OIDC) support, PKCE handling for public clients, and workload-identity authentication (client-assertion/no-client-secret) for cloud-native deployments.
  • Documentation

    • Expanded auth docs with OIDC guidance, discovery/validation, ID-token vs userinfo semantics, public vs confidential client guidance, PKCE rules, workload-identity examples, and updated configuration examples.
  • Bug Fixes

    • Prevented unintended mutation of authentication source order during login.
  • Tests

    • Expanded OAuth2/OIDC test suite covering PKCE, public/confidential registrations, OIDC behaviors, and workload-identity scenarios.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

Walkthrough

Adds OIDC support and ID-token-first logic, implements workload identity (AKS) client-assertion flow, tightens PKCE/public/confidential validation and registration behavior, guards auth-source mutation to explicit POST button actions, and converts OAuth2 tests to a data-driven suite covering many scenarios.

Changes

Cohort / File(s) Summary
Docs: OAuth2/OIDC
docs/en_US/oauth2.rst
Renamed heading and expanded docs to cover OIDC discovery, ID token vs userinfo semantics, updated parameter names/semantics (OAUTH2_SERVER_METADATA_URL, OAUTH2_USERINFO_ENDPOINT, OAUTH2_SCOPE, OAUTH2_USERNAME_CLAIM, OAUTH2_ADDITIONAL_CLAIMS), PKCE/public vs confidential guidance, workload-identity (AKS) JWT client assertion examples, and multiple configuration examples.
Auth source guard
web/pgadmin/authenticate/__init__.py
update_auth_sources now early-returns unless request.method == 'POST' and only mutates auth_sources when explicit internal_button or oauth2_button form fields are present.
OAuth2/OIDC core
web/pgadmin/authenticate/oauth2.py
Adds workload identity support (constant for assertion type, token-file reader, client_assertion exchange), enforces workload_identity constraints (no PKCE, no client_secret), validates PKCE/public/confidential combinations, centralizes registration via register(**register_kwargs), adds OIDC detection, ID token extraction, _resolve_username (priority: ID token → profile → email/preferred_username/sub), _authorize_access_token, and extended logging/auditing and early-guards in login/profile flows.
Test harness base
web/pgadmin/browser/tests/__init__.py
Adds no-op setUp and tearDown methods to BrowserGenerateTestCase to provide lifecycle hooks.
OAuth2/OIDC tests
web/pgadmin/browser/tests/test_oauth2_with_mocking.py
Replaces flag-driven tests with data-driven harness and many helpers; adds numerous provider scenarios and assertions validating PKCE behavior, public/confidential registration, OIDC ID-token vs userinfo flows, workload-identity token-file and client-assertion flows, and session/auth expectations.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Browser as Browser (Login Page)
  participant Server as pgAdmin Server
  participant Provider as OAuth2/OIDC Provider
  participant UserInfo as UserInfo Endpoint
  Note over Browser,Server: Explicit login POST required to mutate auth sources
  User->>Browser: Click "Login with OAuth2"
  Browser->>Server: POST /authenticate (oauth2_button)
  Server->>Provider: Authorization Request (PKCE if configured)
  Provider-->>Browser: Redirect back with code
  Browser->>Server: Callback with code
  alt OIDC with sufficient ID token claims
    Server->>Provider: Token Request
    Provider-->>Server: ID Token + Access Token
    Server->>Server: Extract ID token claims, resolve username
  else Insufficient ID token claims or non-OIDC
    Server->>Provider: Token Request
    Provider-->>Server: Access/ID Tokens
    Server->>UserInfo: GET /userinfo
    UserInfo-->>Server: Profile JSON
    Server->>Server: Resolve username from profile or claims
  end
  Server-->>Browser: Session established
Loading
sequenceDiagram
  participant Server as pgAdmin Server
  participant FS as Workload Identity Token File
  participant Provider as OAuth2 Token Endpoint
  Note over Server: Workload Identity (AKS) client_assertion flow
  Server->>FS: Read projected service account JWT (token file)
  FS-->>Server: JWT assertion
  Server->>Provider: Token Request (client_assertion & type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer)
  Provider-->>Server: Access/ID Tokens
  Server->>Server: Extract claims / resolve identity
Loading
sequenceDiagram
  actor User
  participant Browser as Browser (Login Page)
  participant Server as pgAdmin Server
  Note over Server: update_auth_sources only on POST with explicit button
  User->>Browser: Click specific login button
  Browser->>Server: POST with `internal_button` or `oauth2_button`
  alt internal_button
    Server->>Server: Remove KERBEROS, OAUTH2 from auth_sources
  else oauth2_button
    Server->>Server: Remove INTERNAL, LDAP from auth_sources
  end
  Server-->>Browser: Continue selected auth flow
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding OAuth2 Workload Identity support for Entra ID, which aligns with the documentation updates and code implementation across multiple files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
web/pgadmin/authenticate/oauth2.py (1)

254-261: Consider handling file read exceptions.

The file open operation could raise IOError or PermissionError if the token file becomes unreadable at runtime (e.g., permission changes, filesystem issues). These exceptions would propagate as unhandled errors rather than the descriptive ValueError messages used elsewhere.

📝 Suggested improvement
-        with open(expanded, 'r', encoding='utf-8') as fp:
-            token = fp.read().strip()
+        try:
+            with open(expanded, 'r', encoding='utf-8') as fp:
+                token = fp.read().strip()
+        except (IOError, PermissionError) as e:
+            raise ValueError(
+                f'OAuth2 provider "{provider_name}" workload identity '
+                f'token file could not be read: {e}'
+            ) from e

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e63dba and 4c106e2.

📒 Files selected for processing (5)
  • docs/en_US/oauth2.rst
  • web/pgadmin/authenticate/__init__.py
  • web/pgadmin/authenticate/oauth2.py
  • web/pgadmin/browser/tests/__init__.py
  • web/pgadmin/browser/tests/test_oauth2_with_mocking.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/pgadmin/browser/tests/init.py
🧰 Additional context used
🧬 Code graph analysis (2)
web/pgadmin/authenticate/__init__.py (1)
web/pgadmin/tools/user_management/__init__.py (1)
  • auth_sources (499-507)
web/pgadmin/browser/tests/test_oauth2_with_mocking.py (1)
web/pgadmin/authenticate/oauth2.py (2)
  • authenticate (666-678)
  • get_user_profile (580-664)
🪛 Ruff (0.14.11)
web/pgadmin/authenticate/oauth2.py

157-163: Avoid specifying long messages outside the exception class

(TRY003)


166-171: Avoid specifying long messages outside the exception class

(TRY003)


177-182: Avoid specifying long messages outside the exception class

(TRY003)


186-192: Avoid specifying long messages outside the exception class

(TRY003)


199-205: Avoid specifying long messages outside the exception class

(TRY003)


230-230: Possible hardcoded password assigned to: "token_endpoint_auth_method"

(S105)


239-243: Avoid specifying long messages outside the exception class

(TRY003)


247-252: Avoid specifying long messages outside the exception class

(TRY003)


258-261: Avoid specifying long messages outside the exception class

(TRY003)


295-295: Unused method argument: form

(ARG002)


438-438: Unused method argument: form

(ARG002)

web/pgadmin/browser/tests/test_oauth2_with_mocking.py

140-142: Avoid specifying long messages outside the exception class

(TRY003)


320-320: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)


356-356: Do not catch blind exception: Exception

(BLE001)


373-373: Unused function argument: self

(ARG001)


408-408: Unused function argument: self

(ARG001)

🔇 Additional comments (17)
web/pgadmin/authenticate/__init__.py (1)

229-250: LGTM! Well-guarded auth source mutation logic.

The explicit guards prevent unintended auth source mutations during non-login POSTs. The early return for non-POST requests and explicit button checks ensure auth sources are only modified during intentional user actions.

docs/en_US/oauth2.rst (3)

190-211: YAML indentation has been corrected.

The YAML example now uses proper 4-space indentation for the ServiceAccount and Deployment manifests, which will allow users to copy-paste correctly.


14-37: Clear OAuth2 vs OIDC differentiation.

The documentation effectively explains the difference between OAuth2 and OIDC, and clearly highlights the recommended approach with OAUTH2_SERVER_METADATA_URL for modern identity providers.


129-166: Comprehensive workload identity documentation.

The Azure Entra ID Workload Identity section provides clear configuration examples and explains the client assertion flow. The note about not requiring PKCE for workload identity is helpful.

web/pgadmin/authenticate/oauth2.py (7)

107-109: LGTM! Standard JWT bearer assertion type.

The constant correctly follows the RFC 7523 standard for JWT client assertions.


155-205: Comprehensive validation for OAuth2 client configurations.

The validation chain correctly enforces:

  • Workload identity cannot use PKCE
  • Workload identity cannot have a client secret
  • Workload identity requires a token file that exists
  • Public clients (no secret) must use PKCE

The detailed error messages are helpful for operators debugging misconfigurations.


265-284: LGTM! Clean delegation for token authorization.

The method correctly delegates to the standard OAuth2 flow or adds JWT client assertion for workload identity. Reading the token fresh on each call properly supports projected token rotation.


350-436: Well-structured username resolution with clear fallback hierarchy.

The _resolve_username method implements the documented priority order correctly:

  1. Custom OAUTH2_USERNAME_CLAIM (ID token first, then userinfo)
  2. For OIDC: emailpreferred_usernamesub
  3. For non-OIDC: email only

The debug logging at each decision point will aid troubleshooting.


438-443: Good defensive guard for missing OAuth2 client.

The early return with a clear error message prevents AttributeError or KeyError later in the flow when oauth2_current_client is not set.


598-641: Efficient OIDC optimization - skips userinfo when ID token suffices.

The logic correctly determines when the ID token contains sufficient claims for authentication and authorization, avoiding unnecessary network calls. The fallback to userinfo endpoint is properly triggered when custom username claims or additional claims are not present in the ID token.


666-678: LGTM! Safer form access prevents BadRequestKeyError.

Using request.form.get() with explicit error handling is more robust than direct dictionary access.

web/pgadmin/browser/tests/test_oauth2_with_mocking.py (6)

25-127: Comprehensive data-driven test scenarios.

The test matrix covers a good range of OAuth2/OIDC scenarios including:

  • External redirect handling
  • Login success/failure paths
  • PKCE and public client registration
  • Workload identity flows
  • OIDC ID token claim handling

This approach makes it easy to add new scenarios.


309-325: Good test isolation with state reset.

Properly clearing the singleton caches (AuthSourceRegistry._objects, _pgadmin_auth_sources, OAuth2Authentication class caches) ensures each test scenario starts with a clean slate.


346-360: Intentional broad exception catch for test client behavior.

The except Exception is appropriate here as the Flask test client raises a generic exception when attempting to follow external redirects. The assertion on the message validates the expected behavior.


617-664: Thorough workload identity client assertion test.

The test correctly:

  1. Mocks the token file existence and content
  2. Verifies the token exchange includes proper client_assertion_type and client_assertion parameters
  3. Validates the token content is trimmed of whitespace (matching the implementation behavior)

529-577: Good coverage of OIDC userinfo optimization.

The tests correctly verify:

  • When ID token has sufficient claims, userinfo endpoint is not called
  • When ID token lacks claims, userinfo endpoint is called as fallback

Using AssertionError as the side effect ensures the test fails fast if the optimization logic is broken.


696-708: Proper test cleanup restores original authentication state.

The tearDown and tearDownClass methods correctly restore AUTHENTICATION_SOURCES and re-login the test account, ensuring subsequent tests in the suite aren't affected.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
web/pgadmin/authenticate/oauth2.py (2)

141-146: Consider simplifying PKCE detection.

The any() with a list works but could be simplified:

♻️ Optional simplification
-            pkce_is_configured = any(
-                [
-                    pkce_method is not None,
-                    pkce_response_type is not None
-                ]
-            )
+            pkce_is_configured = (
+                pkce_method is not None or
+                pkce_response_type is not None
+            )

645-656: Verify handling when userinfo endpoint is missing for non-OIDC providers.

When OAUTH2_USERINFO_ENDPOINT is not configured for a non-OIDC provider, the method returns an empty dict {}. This will cause the login to fail downstream with "No profile data found." which is appropriate, but the error message at line 654-655 could be more actionable.

Consider enhancing the error message to guide users:

♻️ Optional improvement
             else:
                 current_app.logger.error(
-                    'OAUTH2_USERINFO_ENDPOINT not configured for '
-                    'non-OIDC provider')
+                    'OAUTH2_USERINFO_ENDPOINT is required for non-OIDC '
+                    f'provider "{self.oauth2_current_client}". '
+                    'Please configure the userinfo endpoint.')
web/pgadmin/browser/tests/test_oauth2_with_mocking.py (1)

356-360: Consider catching a more specific exception type.

The broad Exception catch is intentional here to verify external redirect behavior, but catching RuntimeError or the specific werkzeug exception would be more precise.

♻️ Optional improvement
-            except Exception as e:
+            except RuntimeError as e:
                 self.assertEqual(
                     'Following external redirects is not supported.',
                     str(e)
                 )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 74c8b2d and ae88afa.

📒 Files selected for processing (5)
  • docs/en_US/oauth2.rst
  • web/pgadmin/authenticate/__init__.py
  • web/pgadmin/authenticate/oauth2.py
  • web/pgadmin/browser/tests/__init__.py
  • web/pgadmin/browser/tests/test_oauth2_with_mocking.py
🧰 Additional context used
🪛 Ruff (0.14.10)
web/pgadmin/browser/tests/test_oauth2_with_mocking.py

140-142: Avoid specifying long messages outside the exception class

(TRY003)


320-320: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)


356-356: Do not catch blind exception: Exception

(BLE001)


373-373: Unused function argument: self

(ARG001)


408-408: Unused function argument: self

(ARG001)

web/pgadmin/authenticate/oauth2.py

157-163: Avoid specifying long messages outside the exception class

(TRY003)


166-171: Avoid specifying long messages outside the exception class

(TRY003)


177-182: Avoid specifying long messages outside the exception class

(TRY003)


186-192: Avoid specifying long messages outside the exception class

(TRY003)


199-205: Avoid specifying long messages outside the exception class

(TRY003)


230-230: Possible hardcoded password assigned to: "token_endpoint_auth_method"

(S105)


239-243: Avoid specifying long messages outside the exception class

(TRY003)


247-252: Avoid specifying long messages outside the exception class

(TRY003)


258-261: Avoid specifying long messages outside the exception class

(TRY003)


295-295: Unused method argument: form

(ARG002)


438-438: Unused method argument: form

(ARG002)

🔇 Additional comments (13)
web/pgadmin/authenticate/__init__.py (1)

229-250: Well-structured guard for authentication source mutation.

The POST-check guard prevents unintended side-effects during non-form-button requests. The early returns after handling each button type are clean and prevent fall-through.

One minor observation: the comment block (lines 230-235) clearly documents the rationale, which is helpful for maintainability.

web/pgadmin/browser/tests/__init__.py (1)

14-22: LGTM - placeholder test lifecycle methods.

These no-op setUp and tearDown methods serve as smoke/placeholder hooks for test discovery. Consider using pass instead of return for slightly more idiomatic Python in methods that don't need to return anything, but this is a minor style preference.

docs/en_US/oauth2.rst (2)

129-216: Comprehensive workload identity documentation.

The Azure Entra ID Workload Identity section is thorough, covering:

  • Configuration parameters
  • App registration setup
  • Federated credential configuration
  • AKS ServiceAccount example

This provides clear guidance for users deploying pgAdmin on AKS with federated credentials.


273-302: Clear username resolution documentation.

The username resolution order for OIDC providers is well-documented:

  1. Custom OAUTH2_USERNAME_CLAIM (if configured)
  2. email claim
  3. preferred_username claim
  4. sub claim (last resort)

This matches the implementation in oauth2.py and provides clear guidance for users.

web/pgadmin/authenticate/oauth2.py (5)

107-109: LGTM - Standard JWT bearer assertion type constant.

The constant follows the OAuth2 RFC 7523 specification for JWT client assertions.


236-263: Secure token file handling with proper validation.

The method correctly:

  • Validates the path configuration
  • Expands environment variables and user paths
  • Checks file existence before reading
  • Validates the token content is non-empty

Reading the token fresh on each call is appropriate since Kubernetes projected tokens rotate.


350-436: Well-implemented OIDC-aware username resolution.

The resolution logic correctly implements the documented priority:

  1. Custom OAUTH2_USERNAME_CLAIM (ID token → userinfo)
  2. OIDC hierarchy: emailpreferred_usernamesub
  3. Non-OIDC: email only

The debug logging at each decision point aids troubleshooting.


438-442: Good defensive guard for missing provider.

The early return with a clear error message prevents confusing downstream errors if oauth2_current_client is not set.


666-671: Good defensive form access.

Using request.form.get('oauth2_button') instead of direct key access prevents BadRequestKeyError when the OAuth2 button wasn't clicked, providing a cleaner error path.

web/pgadmin/browser/tests/test_oauth2_with_mocking.py (4)

309-325: Good test isolation via state reset.

The _reset_oauth2_state method properly clears:

  • AuthSourceRegistry._objects
  • Per-app _pgadmin_auth_sources cache
  • OAuth2Authentication class-level caches

This prevents test pollution between scenarios.

Regarding the static analysis hint about setattr at line 320: this is necessary because the attribute may not exist, and the current approach is correct for initializing a potentially missing attribute.


362-397: Test helper methods are correctly structured.

The fake _fake_authenticate and _fake_get_user_profile methods correctly simulate OAuth2 flows. The self parameter is required since these replace instance methods, so the static analysis warnings are false positives.

The test properly simulates the OIDC token response by populating session['oauth2_token']['userinfo'] with ID token claims.


617-664: Comprehensive workload identity client assertion test.

The test correctly verifies that:

  1. The token file is read via mock_open
  2. The whitespace is stripped from the token
  3. authorize_access_token is called with the correct client_assertion_type and client_assertion parameters

25-127: Excellent test scenario coverage.

The data-driven scenarios comprehensively cover:

  • OAuth2 external redirects and login flows
  • PKCE for confidential and public clients
  • Workload identity registration and token exchange
  • OIDC claim extraction from ID tokens
  • Additional claims authorization (accept/reject)
  • Userinfo endpoint skip/call decisions

This provides good confidence in the OAuth2/OIDC implementation.

@yogeshmahajan-1903
Copy link
Contributor

@Pyodin
Can you please squash all commits? Also, it seems there total of 3 PR raised, which override changes from each other. Could you please keep a single PR and close the rest?

@Pyodin Pyodin force-pushed the entra-workload-identity branch from ae88afa to f80c276 Compare January 14, 2026 10:06
@Pyodin
Copy link
Author

Pyodin commented Jan 14, 2026

@yogeshmahajan-1903 Ok you're right. I squashed the commit from the two previous PR #9525 and #9534 and force pushed them into this PR.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/en_US/oauth2.rst`:
- Around line 190-212: The YAML example block is indented too far causing
copy-paste errors; remove the leading spaces so the YAML content (starting from
"apiVersion: v1") is flush left (column 0) inside the code block, keeping the
surrounding ".. code-block:: yaml" marker and maintaining the same lines for
ServiceAccount, metadata (name/namespace/annotations), and the
Deployment/spec/template/metadata/labels/serviceAccountName entries so those
keys (e.g., "apiVersion", "kind", "metadata", "annotations",
"azure.workload.identity/client-id", "Deployment", "labels",
"azure.workload.identity/use", "serviceAccountName") begin at column 0.
🧹 Nitpick comments (2)
web/pgadmin/authenticate/oauth2.py (1)

139-213: Inconsistent handling of partial PKCE configuration.

pkce_is_configured is True if either OAUTH2_CHALLENGE_METHOD or OAUTH2_RESPONSE_TYPE is set (line 141-146). However:

  • The public client validation (line 197) correctly requires both to be set (pkce_method and pkce_response_type == 'code')
  • But the kwargs population (lines 209-213) only checks pkce_is_configured, so partial PKCE config will add incomplete kwargs (e.g., code_challenge_method='S256' with response_type=None)

This could lead to confusing behavior where the OAuth provider receives malformed PKCE parameters.

♻️ Suggested fix: Only add PKCE kwargs when both fields are properly configured
-            if pkce_is_configured:
+            if pkce_method and pkce_response_type:
                 client_kwargs.update({
                     'code_challenge_method': pkce_method,
                     'response_type': pkce_response_type
                 })
web/pgadmin/browser/tests/test_oauth2_with_mocking.py (1)

349-360: Broad exception catch is intentional but could be more robust.

The test catches Exception because Flask's test client raises a generic exception for external redirects. Consider using self.assertRaisesRegex for cleaner assertion if the test framework supports it, or add a fallback self.fail() to catch unexpected exceptions.

♻️ Alternative using assertRaisesRegex
-            try:
-                self.tester.login(
-                    email=None, password=None,
-                    _follow_redirects=True,
-                    headers=None,
-                    extra_form_data=dict(oauth2_button=provider)
-                )
-            except Exception as e:
-                self.assertEqual(
-                    'Following external redirects is not supported.',
-                    str(e)
-                )
+            with self.assertRaisesRegex(
+                Exception,
+                r'Following external redirects is not supported\.'
+            ):
+                self.tester.login(
+                    email=None, password=None,
+                    _follow_redirects=True,
+                    headers=None,
+                    extra_form_data=dict(oauth2_button=provider)
+                )
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae88afa and f80c276.

📒 Files selected for processing (5)
  • docs/en_US/oauth2.rst
  • web/pgadmin/authenticate/__init__.py
  • web/pgadmin/authenticate/oauth2.py
  • web/pgadmin/browser/tests/__init__.py
  • web/pgadmin/browser/tests/test_oauth2_with_mocking.py
🧰 Additional context used
🧬 Code graph analysis (2)
web/pgadmin/authenticate/__init__.py (1)
web/pgadmin/tools/user_management/__init__.py (1)
  • auth_sources (499-507)
web/pgadmin/authenticate/oauth2.py (2)
web/pgadmin/authenticate/internal.py (5)
  • get_friendly_name (40-41)
  • get_friendly_name (95-96)
  • validate (47-62)
  • validate (98-105)
  • login (64-83)
web/pgadmin/authenticate/__init__.py (3)
  • validate (273-283)
  • login (94-100)
  • login (309-323)
🪛 Ruff (0.14.11)
web/pgadmin/authenticate/oauth2.py

157-163: Avoid specifying long messages outside the exception class

(TRY003)


166-171: Avoid specifying long messages outside the exception class

(TRY003)


177-182: Avoid specifying long messages outside the exception class

(TRY003)


186-192: Avoid specifying long messages outside the exception class

(TRY003)


199-205: Avoid specifying long messages outside the exception class

(TRY003)


230-230: Possible hardcoded password assigned to: "token_endpoint_auth_method"

(S105)


239-243: Avoid specifying long messages outside the exception class

(TRY003)


247-252: Avoid specifying long messages outside the exception class

(TRY003)


258-261: Avoid specifying long messages outside the exception class

(TRY003)


295-295: Unused method argument: form

(ARG002)


438-438: Unused method argument: form

(ARG002)

web/pgadmin/browser/tests/test_oauth2_with_mocking.py

140-142: Avoid specifying long messages outside the exception class

(TRY003)


320-320: Do not call setattr with a constant attribute value. It is not any safer than normal property access.

Replace setattr with assignment

(B010)


356-356: Do not catch blind exception: Exception

(BLE001)


373-373: Unused function argument: self

(ARG001)


408-408: Unused function argument: self

(ARG001)

🔇 Additional comments (12)
web/pgadmin/browser/tests/__init__.py (1)

14-24: LGTM!

The added setUp and tearDown lifecycle hooks are appropriate placeholders for test discovery compatibility. The docstring clearly explains the intent.

docs/en_US/oauth2.rst (1)

218-345: Documentation is comprehensive and well-aligned with the implementation.

The OIDC configuration examples, username resolution order, and legacy OAuth2 fallback behavior are clearly documented and match the code behavior in oauth2.py.

web/pgadmin/authenticate/oauth2.py (5)

236-284: Workload identity implementation is solid.

The helper methods properly validate the token file and handle the client assertion flow. The early existence check in __init__ combined with runtime validation provides good defense-in-depth.

One consideration: if the token file becomes unreadable (permissions change) after startup, the open() call at line 254 will raise PermissionError. This is acceptable since it would be an operational issue requiring admin intervention.


350-436: OIDC username resolution logic is well-implemented.

The fallback chain (custom claim → email → preferred_username → sub) matches the documentation and handles edge cases appropriately. The debug logging will help troubleshoot authentication issues in production.


438-578: Login flow properly handles OIDC and non-OIDC providers.

The early guard for missing oauth2_current_client prevents confusing errors downstream. The asymmetric profile requirements (OIDC can use ID token OR userinfo; non-OIDC requires userinfo) are correctly implemented. Audit logging includes claim names without values, which is appropriate for security.


598-664: OIDC userinfo optimization is well-reasoned.

The logic correctly determines when the userinfo endpoint can be skipped based on ID token claim sufficiency. This reduces unnecessary network calls while ensuring required claims are available.


666-678: Safer form access prevents BadRequestKeyError.

Using request.form.get() with a fallback error message is a good defensive improvement over direct dictionary access.

web/pgadmin/browser/tests/test_oauth2_with_mocking.py (4)

25-128: Comprehensive test scenario coverage.

The 17 scenarios cover the key OAuth2/OIDC flows including edge cases like rejected additional claims and missing token files. The kind-based dispatch pattern improves test organization and readability.


309-326: Test isolation logic is necessary but tightly coupled.

The state reset correctly clears singleton caches for test isolation. The direct access to AuthSourceRegistry._objects (line 312) couples the test to internal implementation details. This is acceptable for test code but may require updates if the registry implementation changes.


362-432: Login success and failure test helpers are well-structured.

The mock functions correctly simulate OAuth2 authentication flows. The self parameter is required for method signature compatibility with patch.object, so the static analysis warning about unused arguments is a false positive.


579-694: Workload identity tests provide excellent coverage.

The tests verify:

  1. Registration without client secret or PKCE (lines 579-616)
  2. Client assertion flow with correct JWT bearer assertion (lines 617-664)
  3. Fast-fail on missing token file (lines 666-694)

The mock_open usage correctly simulates the token file read, and the assertion value verification confirms whitespace is properly trimmed.

web/pgadmin/authenticate/__init__.py (1)

229-250: Guards for auth source mutation look correct.

The logic properly ensures auth sources are only filtered when an explicit button is clicked. The early return on non-POST requests prevents unintended mutations.

Note: kerberos_button does not exist in the codebase. While KERBEROS is a valid auth source, only internal_button and oauth2_button are provided in the login form, so the current handling is complete.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

…andling and configuration options

Change logging level from exception to error for OIDC profile data issues

Refactor debug logging in OAuth2 authentication to improve clarity and consistency

Add error handling for missing OAuth2 provider and enhance claims processing logic

Enhance OIDC ID token handling by implementing JWT parsing and updating tests to mock claims extraction

Refactor ID token claims extraction for OIDC providers and update tests to mock userinfo handling

Refactor OAuth2 configuration to use get method for optional URLs

Enhance OAuth2 documentation and implement PKCE support for public clients in authentication logic

Fix typo in OAUTH2 authentication documentation

Implement Azure Entra ID Workload Identity authentication support and add corresponding tests
@Pyodin Pyodin force-pushed the entra-workload-identity branch from 557823c to 4c106e2 Compare January 14, 2026 13:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants