1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/instances/tests/test_console_settings.py
catborise f3003bb414 test: test files named by their topic, one file per topic
- console settings were split over four files; who sees the form, the
  listen address, and the settings themselves are test_console_settings,
  and who may open a console is test_console_access;
- the three template files are test_templates;
- the quota lock test joins test_quota;
- accounts' test_security_fixes held two topics: the SSH key tests are
  test_ssh_keys, the owner-row delete redirect is in test_views;
- instances' test_security_mutations, who may change a VM, is
  test_permissions.
The suite runs the same 2097 tests.
2026-11-06 13:48:04 +03:00

234 lines
12 KiB
Python

"""A VM's console settings: who sees the form; each change is saved to the
persistent definition, the password only by the action chosen for it, and
the VNC settings only while the saved console type is VNC; only
administrators choose where a console listens beyond localhost."""
from unittest.mock import patch
from computes.models import Compute
from django.contrib.auth import get_user_model
from django.contrib.messages import get_messages
from django.test import TestCase, override_settings
from django.urls import reverse
from instances.forms import ConsoleForm
from instances.models import Instance
from unittest.mock import PropertyMock
from accounts.models import UserInstance
from django.contrib.auth.models import Permission
VNC = {"console_type": "vnc", "listen_on": "127.0.0.1", "keymap": "auto", "password_action": "keep"}
class UpdateConsoleTestCase(TestCase):
def setUp(self):
admin = get_user_model().objects.create_superuser("console_admin", "c@example.com", "pw")
self.client.force_login(admin)
compute = Compute.objects.create(name="console-compute", hostname="127.0.0.1", login="", password="", type=4)
self.instance = Instance.objects.create(
compute=compute, name="console-vm", uuid="11111111-2222-3333-4444-555555555555"
)
def post(self, data, console_type="vnc"):
"""console_type: the persistent definition's; the live one is VNC."""
with patch("instances.models.wvmInstance") as wvm:
proxy = wvm.return_value
proxy.get_console_type.side_effect = lambda config=False: console_type if config else "vnc"
proxy.get_console_listener_addr.return_value = "127.0.0.1"
proxy.get_console_keymap.return_value = ""
proxy.set_console_passwd.return_value = True
response = self.client.post(reverse("instances:update_console", args=[self.instance.id]), data)
return proxy, [str(m) for m in get_messages(response.wsgi_request)]
def test_console_settings_are_applied(self):
proxy, _ = self.post({**VNC, "listen_on": "0.0.0.0", "keymap": "de"})
proxy.set_console_keymap.assert_called_once_with("de")
proxy.set_console_listener_addr.assert_called_once_with("0.0.0.0")
proxy.set_console_passwd.assert_not_called()
def test_the_password_changes_by_the_action_chosen(self):
for action, password in (("set", " a b c "), ("set", "éééé"), ("none", "")):
with self.subTest(action, password=password):
proxy, _ = self.post({**VNC, "password_action": action, "password": password})
proxy.set_console_passwd.assert_called_once_with(password)
proxy, _ = self.post({**VNC, "password_action": "generate"})
(generated,), _ = proxy.set_console_passwd.call_args
self.assertEqual(len(generated), 8)
def test_a_password_vnc_cannot_take_is_refused_and_nothing_is_saved(self):
for password in ("", "123456789", "ééééé"): # libvirt counts bytes: é is two
with self.subTest(password=password):
proxy, messages = self.post({**VNC, "keymap": "de", "password_action": "set", "password": password})
proxy.set_console_passwd.assert_not_called()
proxy.set_console_keymap.assert_not_called()
self.assertTrue(messages[0].startswith("Console settings were not saved"), messages)
@override_settings(SERIAL_CONSOLE_ENABLED=True)
def test_a_new_serial_console_ignores_the_vnc_settings(self):
# even ones that would be refused: an empty new password, an unknown keymap
for vnc in ({"keymap": "de", "password_action": "none"},
{"keymap": "xx", "password_action": "set", "password": ""}):
with self.subTest(vnc):
proxy, messages = self.post({**VNC, **vnc, "console_type": "pty"})
self.assertEqual(messages, [])
proxy.set_console_type.assert_called_once_with("pty")
proxy.set_console_keymap.assert_not_called()
proxy.set_console_passwd.assert_not_called()
@override_settings(SERIAL_CONSOLE_ENABLED=True)
def test_a_saved_serial_console_shows_and_takes_no_vnc_settings(self):
# the running VM still has VNC: the type to compare with is the saved one
proxy, messages = self.post({"console_type": "pty"}, console_type="pty")
self.assertEqual(messages, [])
proxy.set_console_type.assert_not_called()
proxy, _ = self.post({"console_type": "vnc", "keymap": "de"}, console_type="pty")
proxy.set_console_type.assert_called_once_with("vnc")
proxy.set_console_keymap.assert_not_called()
with patch("instances.models.wvmInstance") as wvm:
wvm.return_value.get_console_type.side_effect = lambda config=False: "pty" if config else "vnc"
form = ConsoleForm(instance=Instance.objects.get(pk=self.instance.pk), is_superuser=True)
self.assertEqual((form["console_type"].value(), sorted(form.fields)), ("pty", ["console_type"]))
@override_settings(SERIAL_CONSOLE_ENABLED=False)
def test_serial_is_not_offered_while_the_serial_console_is_off(self):
proxy, _ = self.post({**VNC, "console_type": "pty"})
proxy.set_console_type.assert_not_called()
class ConsoleListenerTests(TestCase):
def setUp(self):
User = get_user_model()
self.owner = User.objects.create_user("console-owner", password="pw")
self.admin = User.objects.create_superuser("console-admin", password="pw", email="ca@example.com")
compute = Compute.objects.create(name="cl", hostname="10.0.0.1", login="u", password="p", type=1)
self.vm = Instance.objects.create(compute=compute, name="cl-vm", uuid="78787878-0000-0000-0000-000000000000")
UserInstance.objects.create(user=self.owner, instance=self.vm, is_change=True, is_vnc=True)
def post(self, user, data, local=True):
self.client.force_login(user)
with patch("instances.models.wvmInstance") as wvm:
proxy = wvm.return_value
proxy.console_listens_locally.return_value = local
proxy.get_console_listener_addr.return_value = "127.0.0.1" if local else "0.0.0.0"
proxy.get_console_passwd.return_value = "Old-pass-1"
proxy.get_console_keymap.return_value = ""
proxy.set_console_passwd.return_value = True
response = self.client.post(reverse("instances:update_console", args=[self.vm.id]), data)
return proxy, [str(m) for m in get_messages(response.wsgi_request)]
def test_owner_cannot_change_the_listen_address(self):
proxy, _ = self.post(self.owner, {"listen_on": "0.0.0.0", "keymap": "auto", "password_action": "keep"})
proxy.set_console_listener_addr.assert_not_called()
def test_owner_cannot_remove_the_password_of_a_console_listening_beyond_localhost(self):
proxy, messages = self.post(self.owner, {"password_action": "none", "keymap": "de"}, local=False)
proxy.set_console_passwd.assert_not_called()
proxy.set_console_keymap.assert_not_called()
self.assertTrue(any("password" in m.lower() for m in messages), messages)
@override_settings(SERIAL_CONSOLE_ENABLED=True)
def test_owner_can_switch_a_console_listening_beyond_localhost_to_serial(self):
proxy, messages = self.post(self.owner, {"console_type": "pty", "password_action": "none", "keymap": "auto"},
local=False)
self.assertEqual(messages, [])
proxy.set_console_type.assert_called_once_with("pty")
proxy.set_console_passwd.assert_not_called()
def test_owner_can_remove_the_password_of_a_local_console(self):
proxy, _ = self.post(self.owner, {"password_action": "none", "keymap": "auto"})
proxy.set_console_passwd.assert_called_once_with("")
def test_owner_can_set_a_password_of_any_console(self):
proxy, _ = self.post(self.owner, {"password_action": "set", "password": "s3cret-1", "keymap": "auto"}, local=False)
proxy.set_console_passwd.assert_called_once_with("s3cret-1")
def test_superuser_can_open_the_console_to_all_interfaces(self):
proxy, _ = self.post(self.admin, {"listen_on": "0.0.0.0", "password_action": "none", "keymap": "auto"})
proxy.set_console_listener_addr.assert_called_once_with("0.0.0.0")
proxy.set_console_passwd.assert_called_once_with("")
def test_owner_sees_the_listen_address_but_cannot_edit_it(self):
self.client.force_login(self.owner)
with patch("instances.models.wvmInstance") as wvm, patch.object(
Compute, "status", new_callable=PropertyMock, return_value=True
):
proxy = wvm.return_value
proxy.get_memory.return_value = 1024
proxy.get_cur_memory.return_value = 1024
proxy.get_max_memory.return_value = 4096
proxy.get_console_listener_addr.return_value = "127.0.0.1"
for name in ("get_networks", "get_ifaces", "get_storages", "get_disk_devices", "get_media_devices", "get_net_devices"):
getattr(proxy, name).return_value = []
proxy.get_hostdevs.return_value = ([], [], [])
proxy.get_status.return_value = 5
response = self.client.get(reverse("instances:instance", args=[self.vm.id]))
form = response.context["console_form"]
self.assertTrue(form.fields["listen_on"].disabled)
VNC_PASSWORD = "s3cretVNCpass"
class ConsoleSettingsAccessTestCase(TestCase):
def setUp(self):
User = get_user_model()
view_perm = Permission.objects.get(codename="view_instances")
self.superuser = User.objects.create_superuser(
username="vncpw_super", email="vncpw_super@example.com", password="x"
)
self.staff_viewer = User.objects.create_user(
username="vncpw_staff_viewer", password="x", is_staff=True
)
self.staff_viewer.user_permissions.add(view_perm)
self.staff_owner = User.objects.create_user(
username="vncpw_staff_owner", password="x", is_staff=True
)
self.vnc_owner = User.objects.create_user(username="vncpw_vnc_owner", password="x")
self.compute = Compute.objects.create(
name="vncpw-compute", hostname="127.0.0.1:1", login="root", password="", type=1
)
self.instance = Instance.objects.create(
compute=self.compute,
name="vncpw-vm",
uuid="cccccccc-dddd-eeee-ffff-000000000000",
)
UserInstance.objects.create(
instance=self.instance, user=self.staff_owner, is_change=True, is_vnc=False
)
UserInstance.objects.create(
instance=self.instance, user=self.vnc_owner, is_change=True, is_vnc=True
)
def _detail_page(self, user):
self.client.force_login(user)
with patch("instances.models.wvmInstance") as mock_wvm, patch.object(
Compute, "status", new_callable=PropertyMock, return_value=True
):
proxy = mock_wvm.return_value
proxy.get_memory.return_value = 1024
proxy.get_cur_memory.return_value = 1024
proxy.get_max_memory.return_value = 4096
proxy.get_networks.return_value = []
proxy.get_ifaces.return_value = []
proxy.get_hostdevs.return_value = ([], [], [])
proxy.get_storages.return_value = []
proxy.get_console_passwd.return_value = VNC_PASSWORD
proxy.get_console_type.return_value = "vnc"
proxy.get_status.return_value = 5
proxy.get_disk_devices.return_value = []
proxy.get_media_devices.return_value = []
proxy.get_net_devices.return_value = []
response = self.client.get(
reverse("instances:instance", args=[self.instance.id])
)
self.assertEqual(response.status_code, 200)
return response.content.decode()
def test_only_users_who_may_change_the_console_see_its_settings(self):
form = reverse("instances:update_console", args=[self.instance.id])
for user, shown in ((self.staff_viewer, False), (self.staff_owner, False),
(self.vnc_owner, True), (self.superuser, True)):
with self.subTest(user.username):
page = self._detail_page(user)
self.assertEqual(form in page, shown)
self.assertNotIn(VNC_PASSWORD, page)