mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-08 19:26:48 +00:00
Since the VM create became a job (6f0c7300) every new VM was defined with
no VNC password, and a clone copied its source's password (or its lack
of one) exactly: every clone of a template shared it. Computes listen
for VNC on all their addresses (dev/libvirt-bootstrap.sh), so anyone who
could reach a compute's VNC ports opened any of these consoles directly,
without the panel; the README's firewall rule was the only guard.
The create job asks for create.py's random 8-character password again,
and a clone's VNC graphics get a new random one. The console page sends
the password to a user with passwordless_console, which the user create
form now ticks by default next to change_password; a user without it is
asked for the password, as the permission intends. Existing VMs keep
their settings.
Live create, clone and VM page tests pass on a TCP compute.
104 lines
5.1 KiB
Python
104 lines
5.1 KiB
Python
"""Creating a user in the admin pages: the password reaches the database only
|
|
hashed, and the configured password validators apply as on the change forms."""
|
|
|
|
import re
|
|
|
|
from django.contrib.auth.models import Group, Permission, User
|
|
from django.db import connection
|
|
from django.test import TestCase, override_settings
|
|
from django.urls import reverse
|
|
|
|
from accounts.models import UserInstance
|
|
from computes.models import Compute
|
|
from instances.models import Instance
|
|
|
|
RAW = "Raw-Passw0rd-in-the-form"
|
|
LIMITS = {"role": "user", "max_instances": 1, "max_cpus": 1, "max_memory": 1, "max_disk_size": 4}
|
|
|
|
|
|
class UserCreateTests(TestCase):
|
|
def setUp(self):
|
|
self.client.force_login(User.objects.create_superuser("uc-admin", "uc@example.com", "pw"))
|
|
self.change_password = Permission.objects.get(content_type__app_label="accounts", codename="change_password")
|
|
|
|
def create(self, **data):
|
|
return self.client.post(reverse("admin:user_create"), {"username": "newbie", "password": RAW, **LIMITS, **data})
|
|
|
|
def test_the_raw_password_is_never_written(self):
|
|
written = []
|
|
|
|
def record(execute, sql, params, many, context):
|
|
if RAW in repr(params):
|
|
written.append(sql)
|
|
return execute(sql, params, many, context)
|
|
|
|
with connection.execute_wrapper(record):
|
|
response = self.create()
|
|
self.assertRedirects(response, reverse("admin:user_list"))
|
|
self.assertEqual(written, [])
|
|
self.assertTrue(User.objects.get(username="newbie").check_password(RAW))
|
|
|
|
def test_a_new_user_may_change_the_password_and_open_consoles_unless_unticked(self):
|
|
# new VMs have a VNC password, which the console page sends only with passwordless_console
|
|
page = self.client.get(reverse("admin:user_create")).content.decode()
|
|
ticked = set(re.findall(r'<input[^>]*name="user_permissions"[^>]*value="(\d+)"[^>]*checked', page))
|
|
self.assertEqual(ticked, {str(self.change_password.pk), str(Permission.objects.get(codename="passwordless_console").pk)})
|
|
|
|
def test_groups_and_permissions_are_saved(self):
|
|
group = Group.objects.create(name="uc-group")
|
|
self.create(groups=[group.pk], user_permissions=[self.change_password.pk])
|
|
user = User.objects.get(username="newbie")
|
|
self.assertEqual(list(user.groups.all()), [group])
|
|
self.assertEqual(list(user.user_permissions.all()), [self.change_password])
|
|
|
|
def test_an_unticked_permission_stays_off(self):
|
|
self.create()
|
|
self.assertFalse(User.objects.get(username="newbie").user_permissions.exists())
|
|
|
|
@override_settings(
|
|
AUTH_PASSWORD_VALIDATORS=[
|
|
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 30}}
|
|
]
|
|
)
|
|
def test_configured_password_validators_apply(self):
|
|
response = self.create()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "at least 30 characters")
|
|
self.assertFalse(User.objects.filter(username="newbie").exists())
|
|
|
|
@override_settings(
|
|
AUTH_PASSWORD_VALIDATORS=[{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}]
|
|
)
|
|
def test_the_password_is_checked_against_the_new_username(self):
|
|
response = self.client.post(
|
|
reverse("admin:user_create"), {"username": "rumpelstiltskin", "password": "rumpelstiltskin1", **LIMITS}
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, "too similar to the username")
|
|
|
|
|
|
class DefaultInstancesTests(TestCase):
|
|
"""NEW_USER_DEFAULT_INSTANCES names VMs, and a name may repeat across hosts."""
|
|
|
|
def setUp(self):
|
|
self.client.force_login(User.objects.create_superuser("uc-admin", "uc@example.com", "pw"))
|
|
one, two = (Compute.objects.create(name=name, hostname="localhost", login="", password="", details="", type=4)
|
|
for name in ("uc-one", "uc-two"))
|
|
self.web = Instance.objects.create(compute=one, name="web", uuid="00000000-0000-0000-0000-000000000001")
|
|
for compute, uuid in ((one, "00000000-0000-0000-0000-000000000002"), (two, "00000000-0000-0000-0000-000000000003")):
|
|
Instance.objects.create(compute=compute, name="db", uuid=uuid)
|
|
|
|
def create(self):
|
|
return self.client.post(reverse("admin:user_create"), {"username": "newbie", "password": RAW, **LIMITS})
|
|
|
|
@override_settings(NEW_USER_DEFAULT_INSTANCES=["web"])
|
|
def test_the_new_user_owns_the_named_vm(self):
|
|
self.assertRedirects(self.create(), reverse("admin:user_list"))
|
|
self.assertEqual([o.instance for o in UserInstance.objects.filter(user__username="newbie")], [self.web])
|
|
|
|
def test_a_name_that_is_not_one_vm_is_refused_on_the_form(self):
|
|
for name in ("no-such-vm", "db"):
|
|
with self.subTest(name), override_settings(NEW_USER_DEFAULT_INSTANCES=["web", name]):
|
|
response = self.create()
|
|
self.assertContains(response, f"NEW_USER_DEFAULT_INSTANCES: {name} is not exactly one VM")
|
|
self.assertFalse(User.objects.filter(username="newbie").exists())
|