Five small defensive fixes that were exposed by running the full
regression suite end-to-end:
1. utils/validation_utils.py: validate_email() now returns False
instead of raising TypeError when passed a non-str/bytes value
(e.g. None from a missing form field). Matches the wrapper's
contract that it only ever returns bool.
2. tools/user_management/__init__.py: list endpoint guarded against
users with no roles. u.roles[0].id -> u.roles[0].id if u.roles
else None. Triggered by ChangePasswordTestCase fixtures.
3. utils/preferences.py: control_props['tags'] / ['creatable']
replaced with .get(...) so preferences whose control_props omit
these keys do not raise KeyError on update.
4. browser/server_groups/servers/__init__.py (create endpoint):
convert_connection_parameter() is bidirectional (list<->dict).
The save path always wants the storage shape (dict). When input
is already a dict (internal callers / tests mimicking storage
form), skip conversion to avoid the dict->list round-trip that
breaks the MutableDict column. Same fix applied to the workspaces
save path.
5. misc/workspaces/__init__.py: same defensive handling for
convert_connection_parameter() on the save path.
These are all pre-existing master bugs surfaced by edge-case test
data; none are introduced by the 9.15 CVE work.
pgAdmin 4 in server mode had no data isolation between users — any
authenticated user could access other users' private servers,
background processes, and debugger state by guessing object IDs.
The shared server feature had 21 vulnerabilities including credential
leaks, privilege escalation via passexec_cmd, and owner data
corruption via SQLAlchemy session mutations.
Centralized access control:
- New server_access.py with get_server(), get_server_group(),
get_user_server_query() replacing ~20 unfiltered queries
- connection_manager() raises ObjectGone (HTTP 410) in server mode
when access is denied — fixes 155+ unguarded callers
- UserScopedMixin.for_user() on 10 models replaces scattered
user_id filters
Shared server isolation (all 21 audit issues):
- Expunge server from session before property merge to prevent
owner data corruption
- Suppress passexec_cmd, post_connection_sql for non-owners in
merge, API response, and ServerManager
- Override all 6 SSL/passfile connection_params keys from
SharedServer; strip owner-only keys; sanitize on creation
- _is_non_owner() helper centralises 15+ inline ownership checks
- SharedServer lookup uses (osid, user_id) not name
- Unique constraint on SharedServer(osid, user_id)
- Tunnel/DB password save, change_password, clear_saved_password,
clear_sshtunnel_password all branch on ownership
- Only owner can unshare (delete_shared_server guard)
- Session restore includes shared servers
- tunnel_port/tunnel_keep_alive copied from owner, not hardcoded
Tool/module hardening:
- All tool endpoints use get_server()
- Debugger function arguments scoped by user_id
- Background processes use Process.for_user()
- Workspace adhoc servers scoped to current user
Migration (schema version 49 -> 50):
- Add user_id to debugger_function_arguments composite PK
- Add indexes on server, sharedserver, servergroup
- Add unique constraint on sharedserver(osid, user_id)
2. Admin should be able to lock the user, as currently it only unlocks it via
user management dialog.
3. There were some indefinite login page loading issues when trying to log in
with invalid password, where it should redirect to the login page again instead.
refs #6337 (Initial patch by Khushboo Vashi)