1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-11 12:46:49 +00:00

fix(instances): a template is not power-cycled on, and deleted only by superusers and staff owners

Power cycle force-stopped and started a template, which power on refuses.
A non-staff owner with is_delete could delete a template that the
template rule otherwise leaves to superusers and staff owners;
get_instance(perm_type="delete") now refuses it, and the page offers
Destroy by the same rule.
This commit is contained in:
catborise 2026-10-07 23:57:10 +03:00
parent e481b24240
commit 70bed79c60
8 changed files with 98 additions and 16 deletions

View file

@ -0,0 +1,64 @@
"""
A template is not started (power on or power cycle, web or API) and is
changed or deleted only by superusers and staff owners.
"""
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse
from rest_framework.test import APIClient
from accounts.models import UserInstance
from computes.models import Compute
from instances.models import Instance
class TemplateRulesTestCase(TestCase):
def setUp(self):
compute = Compute.objects.create(name="tpl-c", hostname="192.0.2.5", login="root", password="", type=1)
self.template = Instance.objects.create(
compute=compute, name="tpl-vm", uuid="ffffffff-0000-1111-2222-333333333333", is_template=True
)
self.owner = get_user_model().objects.create_user("tpl_owner", password="x")
UserInstance.objects.create(instance=self.template, user=self.owner, is_delete=True)
self.staff = get_user_model().objects.create_user("tpl_staff", password="x", is_staff=True)
UserInstance.objects.create(instance=self.template, user=self.staff, is_delete=True)
def test_power_cycle_does_not_start_a_template(self):
api = APIClient()
api.force_login(self.owner)
self.client.force_login(self.owner)
for post in (
lambda: self.client.post(reverse("instances:powercycle", args=[self.template.id])),
lambda: api.post(reverse("compute-instance-powercycle", args=[self.template.compute_id, self.template.id])),
):
with patch("instances.models.wvmInstance") as wvm:
post()
self.assertFalse(wvm.return_value.start.called)
self.assertFalse(wvm.return_value.force_shutdown.called)
def test_only_staff_owners_delete_a_template(self):
self.client.force_login(self.owner)
with patch("instances.models.wvmInstance") as wvm:
response = self.client.post(reverse("instances:destroy", args=[self.template.id]))
self.assertEqual(response.status_code, 403)
self.assertFalse(wvm.return_value.delete.called)
self.client.force_login(self.staff)
with patch("instances.models.wvmInstance") as wvm:
wvm.return_value.instance.isActive.return_value = False
self.client.post(reverse("instances:destroy", args=[self.template.id]))
self.assertTrue(wvm.return_value.delete.called)
def test_the_destroy_confirmation_page_offers_the_form_to_who_may_delete(self):
url = reverse("instances:destroy", args=[self.template.id])
superuser = get_user_model().objects.create_superuser("tpl_root", "r@example.com", "x")
for user, status, form in ((superuser, 200, True), (self.staff, 200, True), (self.owner, 403, False)):
with self.subTest(user=user.username):
self.client.force_login(user)
with patch("instances.models.wvmInstance") as wvm:
wvm.return_value.get_status.return_value = 5
response = self.client.get(url)
self.assertEqual(response.status_code, status)
self.assertEqual('id="delete_form"' in response.content.decode(), form)