mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-11-01 15:53:03 -06:00
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>
182 lines
6.7 KiB
Python
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))
|