Files
pgadmin4/web/pgadmin/utils/tests/test_validate_json_data.py
T
KundanandDave Page d761942d0b Restrict sharing a server to administrators in the server API and the servers.json import. #10651
The server dialog only shows the Shared switch to administrators, and
the servers.json import only honoured it for them, but the create and
update endpoints stored whatever 'shared' and 'shared_username' they
were sent. Any user able to register a server could therefore share one
with every other user of a server mode installation. The import had a
similar gap: validate_json_data() removed a non-administrator's "Shared"
entries only after its loop, so an early validation error left them in
the data, and the web path ignored that error, so selecting a shared
entry in a file that also held an invalid one created a shared server.

A non-administrator's request that would share a server is now refused
with a 403. A 'shared' value that changes nothing is still accepted, so
editing a private server is unaffected, and 'shared_username' is dropped
rather than refused because the dialog sends it whenever the username
changes. The import prunes a non-administrator's shared entries before
validating and skips them again for each imported entry, only a JSON
true counts as shared, and validation errors are reported on the web
path too. Copy Server no longer sends shared for a non-administrator
owner.

The existing servers are cleared for "replace" only after the file has
validated, so a refused file no longer deletes them. Servers shared by
non-administrators before the upgrade stay shared until the owner
un-shares them or an administrator changes them; pgAdmin warns about
them at server-mode startup, and the documentation describes this.

Compatibility: a servers.json import in server mode that fails
validation is now refused as a whole on the web path, as it already was
in setup.py.

Co-authored-by: Dave Page <dpage@pgadmin.org>
2026-10-24 19:36:12 +05:30

182 lines
6.7 KiB
Python

##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2026, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
import copy
from pgadmin.utils import validate_json_data
from pgadmin.utils.route import BaseTestGenerator
BASE_SERVER = {
"Group": "Servers",
"Name": "Test server",
"Host": "127.0.0.1",
"Port": 5432,
"MaintenanceDB": "postgres"
}
def server(**kwargs):
"""Return a server definition with the given attributes merged in."""
obj = copy.deepcopy(BASE_SERVER)
obj.update(kwargs)
return obj
class TestValidateJsonData(BaseTestGenerator):
"""Validate the servers.json import validation, in particular the
username requirements for shared and non-shared servers."""
scenarios = [
('A non-shared server with a username is valid',
dict(
servers={"1": server(Username="postgres")},
is_admin=True,
expected_error=None,
expected_servers=["1"]
)),
('A non-shared server without a username is rejected',
dict(
servers={"1": server()},
is_admin=True,
expected_error="'Username' attribute not found",
expected_servers=["1"]
)),
('A non-shared server with an empty username is rejected',
dict(
servers={"1": server(Username="")},
is_admin=True,
expected_error="'Username' attribute not found",
expected_servers=["1"]
)),
('A non-shared server with a null username is rejected',
dict(
servers={"1": server(Username=None)},
is_admin=True,
expected_error="'Username' attribute not found",
expected_servers=["1"]
)),
('A shared server with only a shared username is valid',
dict(
servers={"1": server(Shared=True, SharedUsername="postgres")},
is_admin=True,
expected_error=None,
expected_servers=["1"]
)),
('A shared server with only a username is valid',
dict(
servers={"1": server(Shared=True, Username="postgres")},
is_admin=True,
expected_error=None,
expected_servers=["1"]
)),
('A shared server with neither username is rejected',
dict(
servers={"1": server(Shared=True)},
is_admin=True,
expected_error="'Username' or 'SharedUsername' attribute not "
"found",
expected_servers=["1"]
)),
('A shared server with an empty shared username is rejected',
dict(
servers={"1": server(Shared=True, SharedUsername="")},
is_admin=True,
expected_error="'Username' or 'SharedUsername' attribute not "
"found",
expected_servers=["1"]
)),
('A shared server with a null shared username is rejected',
dict(
servers={"1": server(Shared=True, SharedUsername=None)},
is_admin=True,
expected_error="'Username' or 'SharedUsername' attribute not "
"found",
expected_servers=["1"]
)),
('A shared server with an empty username is accepted when a shared '
'username is present',
dict(
servers={"1": server(Shared=True, Username="",
SharedUsername="postgres")},
is_admin=True,
expected_error=None,
expected_servers=["1"]
)),
('A server using a service does not require any username',
dict(
servers={"1": {"Group": "Servers", "Name": "Test server",
"Service": "pgsql", "MaintenanceDB": "postgres"}},
is_admin=True,
expected_error=None,
expected_servers=["1"]
)),
('A non-integer port is rejected',
dict(
servers={"1": server(Username="postgres", Port="5432")},
is_admin=True,
expected_error="Port must be integer",
expected_servers=["1"]
)),
('Shared servers are skipped, not rejected, for a non-admin user',
dict(
servers={"1": server(Shared=True, SharedUsername="postgres"),
"2": server(Username="postgres")},
is_admin=False,
expected_error=None,
expected_servers=["2"]
)),
('A non-admin shared server is dropped even when another server '
'fails validation',
dict(
servers={"1": server(Shared=True, SharedUsername="postgres"),
"2": {"Group": "Servers", "Name": "No host",
"Port": 5432, "Username": "postgres",
"MaintenanceDB": "postgres"}},
is_admin=False,
expected_error="'Host' or 'Service' attribute not found",
expected_servers=["2"]
)),
('A non-admin shared server without a name is dropped quietly',
dict(
servers={"1": {"Group": "Servers", "Shared": True,
"Host": "127.0.0.1", "Port": 5432,
"SharedUsername": "postgres",
"MaintenanceDB": "postgres"},
"2": server(Username="postgres")},
is_admin=False,
expected_error=None,
expected_servers=["2"]
)),
('An admin keeps the shared server when another one fails '
'validation',
dict(
servers={"1": server(Shared=True, SharedUsername="postgres"),
"2": {"Group": "Servers", "Name": "No host",
"Port": 5432, "Username": "postgres",
"MaintenanceDB": "postgres"}},
is_admin=True,
expected_error="'Host' or 'Service' attribute not found",
expected_servers=["1", "2"]
)),
]
def runTest(self):
data = {"Servers": copy.deepcopy(self.servers)}
errmsg = validate_json_data(data, self.is_admin)
if self.expected_error is None:
self.assertIsNone(errmsg)
else:
self.assertIsNotNone(
errmsg, "Expected the data to be rejected, but it was not")
self.assertIn(self.expected_error, errmsg)
self.assertEqual(sorted(data["Servers"].keys()),
sorted(self.expected_servers))