Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Protect FileVaultConfig from deletion #773

Merged
merged 1 commit into from
Aug 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion tests/mdm/test_api_filevault_configs_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from accounts.models import APIToken, User
from zentral.contrib.mdm.models import FileVaultConfig
from zentral.core.events.base import AuditEvent
from .utils import force_filevault_config
from .utils import force_blueprint, force_filevault_config


@override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
Expand Down Expand Up @@ -335,6 +335,14 @@ def test_delete_filevault_config_permission_denied(self):
response = self.delete(reverse("mdm_api:filevault_config", args=(fv_config.pk,)))
self.assertEqual(response.status_code, 403)

def test_delete_filevault_config_cannot_be_deleted(self):
fv_config = force_filevault_config()
force_blueprint(filevault_config=fv_config)
self.set_permissions("mdm.delete_filevaultconfig")
response = self.delete(reverse("mdm_api:filevault_config", args=(fv_config.pk,)))
self.assertEqual(response.status_code, 400)
self.assertEqual(response.json(), ["This FileVault configuration cannot be deleted"])

@patch("zentral.core.queues.backends.kombu.EventQueues.post_event")
def test_delete_filevault_config(self, post_event):
fv_config = force_filevault_config()
Expand Down
38 changes: 36 additions & 2 deletions tests/mdm/test_management_filevault_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.utils.crypto import get_random_string
from accounts.models import User
from zentral.core.events.base import AuditEvent
from .utils import force_filevault_config
from .utils import force_blueprint, force_filevault_config


@override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage')
Expand Down Expand Up @@ -51,13 +51,30 @@ def test_filevault_configurations_permission_denied(self):
response = self.client.get(reverse("mdm:filevault_configs"))
self.assertEqual(response.status_code, 403)

def test_filevault_configurations(self):
def test_filevault_configurations_no_links(self):
fv_config = force_filevault_config()
self._login("mdm.view_filevaultconfig")
response = self.client.get(reverse("mdm:filevault_configs"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/filevaultconfig_list.html")
self.assertContains(response, fv_config.name)
self.assertNotContains(response, reverse("mdm:update_filevault_config", args=(fv_config.pk,)))
self.assertNotContains(response, reverse("mdm:delete_filevault_config", args=(fv_config.pk,)))

def test_filevault_configurations_all_links(self):
fv_config1 = force_filevault_config()
force_blueprint(filevault_config=fv_config1)
fv_config2 = force_filevault_config()
self._login("mdm.view_filevaultconfig", "mdm.change_filevaultconfig", "mdm.delete_filevaultconfig")
response = self.client.get(reverse("mdm:filevault_configs"))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/filevaultconfig_list.html")
self.assertContains(response, fv_config1.name)
self.assertContains(response, fv_config2.name)
self.assertContains(response, reverse("mdm:update_filevault_config", args=(fv_config1.pk,)))
self.assertNotContains(response, reverse("mdm:delete_filevault_config", args=(fv_config1.pk,)))
self.assertContains(response, reverse("mdm:update_filevault_config", args=(fv_config2.pk,)))
self.assertContains(response, reverse("mdm:delete_filevault_config", args=(fv_config2.pk,)))

# create FileVault configuration

Expand Down Expand Up @@ -190,6 +207,16 @@ def test_filevault_configuration_get_no_perm_no_delete_link(self):
self.assertContains(response, fv_config.name)
self.assertNotContains(response, reverse("mdm:delete_filevault_config", args=(fv_config.pk,)))

def test_filevault_configuration_get_cannot_be_deleted_no_delete_link(self):
fv_config = force_filevault_config()
force_blueprint(filevault_config=fv_config)
self._login("mdm.view_filevaultconfig", "mdm.delete_filevaultconfig")
response = self.client.get(reverse("mdm:filevault_config", args=(fv_config.pk,)))
self.assertEqual(response.status_code, 200)
self.assertTemplateUsed(response, "mdm/filevaultconfig_detail.html")
self.assertContains(response, fv_config.name)
self.assertNotContains(response, reverse("mdm:delete_filevault_config", args=(fv_config.pk,)))

# update FileVault configuration

def test_update_filevault_configuration_redirect(self):
Expand Down Expand Up @@ -284,6 +311,13 @@ def test_delete_filevault_configuration_permission_denied(self):
response = self.client.get(reverse("mdm:delete_filevault_config", args=(fv_config.pk,)))
self.assertEqual(response.status_code, 403)

def test_delete_filevault_configuration_404(self):
fv_config = force_filevault_config()
force_blueprint(filevault_config=fv_config)
self._login("mdm.delete_filevaultconfig")
response = self.client.get(reverse("mdm:delete_filevault_config", args=(fv_config.pk,)))
self.assertEqual(response.status_code, 404)

def test_delete_filevault_configuration_get(self):
fv_config = force_filevault_config()
self._login("mdm.delete_filevaultconfig")
Expand Down
7 changes: 7 additions & 0 deletions zentral/contrib/mdm/api_views/filevault_configs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from rest_framework.exceptions import ValidationError
from zentral.utils.drf import ListCreateAPIViewWithAudit, RetrieveUpdateDestroyAPIViewWithAudit
from zentral.contrib.mdm.models import FileVaultConfig
from zentral.contrib.mdm.serializers import FileVaultConfigSerializer
Expand All @@ -18,3 +19,9 @@ class FileVaultConfigDetail(RetrieveUpdateDestroyAPIViewWithAudit):
"""
queryset = FileVaultConfig.objects.all()
serializer_class = FileVaultConfigSerializer

def perform_destroy(self, instance):
if not instance.can_be_deleted():
raise ValidationError('This FileVault configuration cannot be deleted')
else:
return super().perform_destroy(instance)
10 changes: 10 additions & 0 deletions zentral/contrib/mdm/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ def rewrap_secrets(self):
# FileVault


class FileVaultConfigManager(models.Manager):
def can_be_deleted(self):
return self.annotate(bp_count=Count("blueprint")).filter(bp_count=0)


class FileVaultConfig(models.Model):
name = models.CharField(max_length=256, unique=True)
escrow_location_display_name = models.CharField(
Expand Down Expand Up @@ -133,6 +138,8 @@ class FileVaultConfig(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

objects = FileVaultConfigManager()

class Meta:
ordering = ("name",)
verbose_name = "filevault config"
Expand All @@ -150,6 +157,9 @@ def uuid(self):
f"{self.destroy_key_on_standby}|{self.prk_rotation_interval_days}".encode("utf-8"))
return uuid.UUID(hex=h.hexdigest())

def can_be_deleted(self):
return FileVaultConfig.objects.can_be_deleted().filter(pk=self.pk).exists()

def serialize_for_event(self, keys_only=False):
d = {"pk": self.pk, "name": self.name}
if keys_only:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ <h3>{{ object }}</h3>
Edit
</a>
{% endif %}
{% if perms.mdm.delete_filevaultconfig %}
{% if perms.mdm.delete_filevaultconfig and object.can_be_deleted %}
<a class="btn btn-danger"
href="{% url 'mdm:delete_filevault_config' object.pk %}">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ <h3>{{ page_obj.paginator.count }} FileVault configuration{{ page_obj.paginator.
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>
</a>
{% endif %}
{% if perms.mdm.delete_filevaultconfig %}
{% if perms.mdm.delete_filevaultconfig and filevault_config.can_be_deleted %}
<a href="{% url 'mdm:delete_filevault_config' filevault_config.pk %}" class="btn btn-xs btn-danger">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
</a>
Expand Down
8 changes: 6 additions & 2 deletions zentral/contrib/mdm/views/management.py
Original file line number Diff line number Diff line change
Expand Up @@ -974,9 +974,11 @@ def form_valid(self, form):

class DeleteBlueprintView(PermissionRequiredMixin, DeleteViewWithAudit):
permission_required = "mdm.delete_blueprint"
queryset = Blueprint.objects.can_be_deleted()
success_url = reverse_lazy("mdm:blueprints")

def get_queryset(self):
return Blueprint.objects.can_be_deleted()


# FileVault Configurations

Expand Down Expand Up @@ -1012,9 +1014,11 @@ class UpdateFileVaultConfigView(PermissionRequiredMixin, UpdateViewWithAudit):

class DeleteFileVaultConfigView(PermissionRequiredMixin, DeleteViewWithAudit):
permission_required = "mdm.delete_filevaultconfig"
model = FileVaultConfig
success_url = reverse_lazy("mdm:filevault_configs")

def get_queryset(self):
return FileVaultConfig.objects.can_be_deleted()


# SCEP Configurations

Expand Down