Skip to content

fix: SharedServer feature parity columns and write guards#9835

Merged
asheshv merged 1 commit intomasterfrom
audit/shared-server-issues
Apr 13, 2026
Merged

fix: SharedServer feature parity columns and write guards#9835
asheshv merged 1 commit intomasterfrom
audit/shared-server-issues

Conversation

@asheshv
Copy link
Copy Markdown
Contributor

@asheshv asheshv commented Apr 9, 2026

Summary

  • Adds 5 columns to SharedServer model (passexec_cmd, passexec_expiration, kerberos_conn, tags, post_connection_sql) for feature parity with Server, so non-owners get per-user values via overlay instead of inheriting/suppressing owner fields
  • Blocks non-owners from setting dangerous fields (passexec_cmd, passexec_expiration, post_connection_sql, db_res, db_res_type) via _owner_only_fields write guard in _set_valid_attr_value()
  • Retains driver-level suppression of passexec/post_connection_sql on ServerManager for non-owners as defense-in-depth, including re-suppression after manager.update() in the connect() endpoint
  • Adds null guard in get_shared_server_properties() for None sharedserver
  • Migration: orphan cleanup (deletes SharedServer rows referencing deleted Servers), column-exists guard, unique constraint idempotency guard

Builds on top of #9830.

Test plan

  • Verify migration runs cleanly on fresh DB (SQLite)
  • Verify migration runs cleanly on existing DB with orphaned SharedServer rows
  • Run cd web && python regression/runtests.py --pkg browser.server_groups.servers --modules test_shared_server_unit — all unit tests pass
  • In server mode: verify non-owner cannot set passexec_cmd or post_connection_sql via PUT to /browser/server/obj/
  • In server mode: verify non-owner connecting to shared server does NOT execute owner's passexec_cmd
  • In server mode: verify overlay correctly shows SharedServer's own kerberos_conn, tags, post_connection_sql values

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Enhanced shared server support with additional configuration options for connection management
    • Improved field accessibility for shared server users, allowing editing of connection settings on inactive shared servers
    • Database schema upgrade to persist expanded shared server properties

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 9, 2026

Walkthrough

Extends the SharedServer model with additional persisted fields (passexec_cmd, passexec_expiration, kerberos_conn, tags, post_connection_sql) and refactors the server property overlay logic to copy these fields from shared-server records onto user servers. Introduces field-level suppression for non-owners via an explicit allowlist, updates the migration system, and adjusts UI editability constraints.

Changes

Cohort / File(s) Summary
SharedServer Model & Migration
web/pgadmin/model/__init__.py, web/migrations/versions/sharedserver_feature_parity_.py
Added persisted columns to SharedServer schema: passexec_cmd, passexec_expiration, kerberos_conn, tags, post_connection_sql; removed db_res column. Migration performs idempotent column additions, orphan cleanup, and increments SCHEMA_VERSION to 51.
Server Properties & Overlay Logic
web/pgadmin/browser/server_groups/servers/__init__.py
Refactored field merging to copy passexec_cmd, passexec_expiration, kerberos_conn, tags, post_connection_sql from SharedServer to detached server object; introduced non-owner field-suppression list in _set_valid_attr_value to block owner-only fields; added post-update belt-and-suspenders passexec suppression for non-owners in connect and update methods; modified tag operations to read/write from shared-server record for non-owners.
Server Property Tests
web/pgadmin/browser/server_groups/servers/tests/test_shared_server_unit.py
Expanded _make_shared_server to explicitly initialize new fields; updated merge expectations to test field overlay behavior instead of suppression; added test_null_guard to verify null SharedServer handling; introduced TestOwnerOnlyFieldsGuard to validate non-owner restriction on passexec_cmd/db_res/db_res_type; added TestUpdateTagsBase to verify tag operations use correct record source per ownership.
Connection Restoration
web/pgadmin/utils/driver/psycopg3/__init__.py
Removed post_connection_sql = None suppression for non-owners in both _restore_connections_from_session and connection_manager; retained conditional passexec = None suppression guarded by config.SERVER_MODE.
UI Field Editability
web/pgadmin/browser/server_groups/servers/static/js/server.ui.js
Changed post_connection_sql readonly constraint from obj.isConnectedOrShared to obj.isConnected, allowing non-connected shared servers to edit this field.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #9830 — Modifies SharedServer handling and server-access logic with overlapping changes in server properties and schema.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.59% 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 'fix: SharedServer feature parity columns and write guards' directly and precisely summarizes the main changes: adding feature parity columns to SharedServer and implementing write guards to prevent non-owners from setting dangerous fields.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch audit/shared-server-issues

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
Copy Markdown

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/migrations/versions/add_user_id_to_debugger_func_args_.py`:
- Around line 161-164: The migration defines the 'kerberos_conn' column with
server_default='false'; change this to use SQLAlchemy's portable boolean literal
by setting server_default=sa.false() on the sa.Column(...) for 'kerberos_conn'
(and ensure sa.false is available from the imported sa namespace if not
already). This ensures the default renders as a proper SQL boolean literal
across DB backends.

In `@web/pgadmin/browser/server_groups/servers/__init__.py`:
- Around line 1182-1184: The current truthiness check for passexec_expiration
collapses an explicit 0 to None; change the conditional to test explicitly for
None (e.g. use "if server.passexec_expiration is not None else None") so that a
value of 0 is preserved when building the properties dict for
passexec_expiration and will round-trip correctly through the properties API.
- Around line 230-231: The update path currently computes tag deltas using
server.tags (the owner's tags) which causes non-owner SharedServer.tags to be
rebuilt from the owner; change the merge base so that when handling a non-owner
update (where SharedServer exists and you assign server.kerberos_conn =
sharedserver.kerberos_conn and server.tags = sharedserver.tags) you compute tag
diffs against sharedserver.tags (i.e. use SharedServer.tags as the base) instead
of server.tags inside update(), and apply only the delta to the non-owner
SharedServer record to avoid overwriting its existing tags.

In `@web/pgadmin/model/__init__.py`:
- Around line 563-569: The SCHEMA_VERSION constant was not incremented after
adding new SharedServer columns (passexec_cmd, passexec_expiration,
kerberos_conn, tags, post_connection_sql), so update the module's SCHEMA_VERSION
(e.g., from 50 to 51) where it is defined so upgrades detect and run the
migration for the SharedServer changes; ensure the new version is used by
migration/upgrade logic and any tests referencing SCHEMA_VERSION are updated
accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 49a1e807-6d76-4299-b568-5be7ba3c9c6e

📥 Commits

Reviewing files that changed from the base of the PR and between 9a76ed8 and 0fd0ac3.

📒 Files selected for processing (5)
  • web/migrations/versions/add_user_id_to_debugger_func_args_.py
  • web/pgadmin/browser/server_groups/servers/__init__.py
  • web/pgadmin/browser/server_groups/servers/tests/test_shared_server_unit.py
  • web/pgadmin/model/__init__.py
  • web/pgadmin/utils/driver/psycopg3/__init__.py

Add passexec_cmd, passexec_expiration, kerberos_conn, tags, and
post_connection_sql to SharedServer so non-owners get their own
per-user values instead of inheriting the owner's.  Drop the unused
db_res column which was never overlaid or writable by non-owners.

Key changes:
- New Alembic migration (sharedserver_feature_parity) adds 5 columns,
  drops db_res, cleans up orphaned records.  All operations idempotent.
- Overlay copies new fields from SharedServer instead of suppressing
- _owner_only_fields guard blocks non-owners from setting passexec_cmd,
  passexec_expiration, db_res, db_res_type via API
- Non-owners can set post_connection_sql (runs under their own creds)
- update_tags and flag_modified use sharedserver for non-owners
- update() response returns sharedserver tags for non-owners
- ServerManager passexec suppression with config.SERVER_MODE guard
- UI: post_connection_sql editable for non-owners (readonly only when
  connected, not when shared)
- SCHEMA_VERSION bumped to 51
- Comprehensive unit tests for overlay, write guards, and tag deltas
@asheshv asheshv force-pushed the audit/shared-server-issues branch from 0fd0ac3 to 4c1705e Compare April 13, 2026 08:29
Copy link
Copy Markdown

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/pgadmin/browser/server_groups/servers/__init__.py (1)

965-985: ⚠️ Potential issue | 🟡 Minor

Return the overlaid record in the update response.

For non-owner edits, this block still serializes most node fields from the owner server row. Only tags was switched to sharedserver or server, so updates to fields like username, role, or save_password come back stale until the next fetch.

🔧 Suggested fix
+        node_server = server
+        if sharedserver is not None:
+            node_server = ServerModule.get_shared_server_properties(
+                server, sharedserver)
+
         return jsonify(
             node=self.blueprint.generate_browser_node(
-                "%d" % (server.id), server.servergroup_id,
-                server.name,
+                "%d" % (node_server.id), node_server.servergroup_id,
+                node_server.name,
                 server_icon_and_background(
-                    connected, manager, sharedserver)
-                if _is_non_owner(server)
-                else server_icon_and_background(
-                    connected, manager, server),
+                    connected, manager, node_server),
                 True,
                 self.node_type,
                 connected=connected,
-                shared=server.shared,
-                user_id=server.user_id,
+                shared=node_server.shared,
+                user_id=node_server.user_id,
                 user=manager.user_info if connected else None,
                 server_type='pg',  # default server type
-                username=server.username,
-                role=server.role,
-                is_password_saved=bool(server.save_password),
-                description=server.comment,
-                tags=(sharedserver or server).tags
+                username=node_server.username,
+                role=node_server.role,
+                is_password_saved=bool(node_server.save_password),
+                description=node_server.comment,
+                tags=node_server.tags
             )
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/pgadmin/browser/server_groups/servers/__init__.py` around lines 965 -
985, The response uses owner `server` fields even for non-owner edits so
returned values (username, role, is_password_saved, description, tags, etc.) can
be stale; when _is_non_owner(server) is true, pick the overlaid record
(sharedserver or server) as the source and pass its attributes to
self.blueprint.generate_browser_node and related calls (e.g., username, role,
save_password -> is_password_saved, comment -> description, tags) and use that
same source for server_icon_and_background instead of mixing owner and overlay;
update the code around generate_browser_node to compute something like source =
(sharedserver or server) when _is_non_owner(server) and read source.username,
source.role, bool(source.save_password), source.comment, source.tags, and pass
source into server_icon_and_background.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@web/pgadmin/browser/server_groups/servers/__init__.py`:
- Around line 965-985: The response uses owner `server` fields even for
non-owner edits so returned values (username, role, is_password_saved,
description, tags, etc.) can be stale; when _is_non_owner(server) is true, pick
the overlaid record (sharedserver or server) as the source and pass its
attributes to self.blueprint.generate_browser_node and related calls (e.g.,
username, role, save_password -> is_password_saved, comment -> description,
tags) and use that same source for server_icon_and_background instead of mixing
owner and overlay; update the code around generate_browser_node to compute
something like source = (sharedserver or server) when _is_non_owner(server) and
read source.username, source.role, bool(source.save_password), source.comment,
source.tags, and pass source into server_icon_and_background.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0efc8367-6e92-495f-bcb0-eaee6d213a28

📥 Commits

Reviewing files that changed from the base of the PR and between 0fd0ac3 and 4c1705e.

📒 Files selected for processing (6)
  • web/migrations/versions/sharedserver_feature_parity_.py
  • web/pgadmin/browser/server_groups/servers/__init__.py
  • web/pgadmin/browser/server_groups/servers/static/js/server.ui.js
  • web/pgadmin/browser/server_groups/servers/tests/test_shared_server_unit.py
  • web/pgadmin/model/__init__.py
  • web/pgadmin/utils/driver/psycopg3/__init__.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/pgadmin/utils/driver/psycopg3/init.py
  • web/pgadmin/model/init.py

@asheshv asheshv merged commit e4edcf2 into master Apr 13, 2026
64 of 65 checks passed
@asheshv asheshv deleted the audit/shared-server-issues branch April 13, 2026 09:33
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.

1 participant