diff --git a/tests/munki/test_api_views.py b/tests/munki/test_api_views.py index edfde69782..abea209cef 100644 --- a/tests/munki/test_api_views.py +++ b/tests/munki/test_api_views.py @@ -189,40 +189,77 @@ def test_create_configuration_default_values(self): self.assertFalse(configuration.auto_failed_install_incidents) self.assertEqual(configuration.version, 0) - def test_create_configuration(self): + @patch("zentral.core.queues.backends.kombu.EventQueues.post_event") + def test_create_configuration(self, post_event): self.set_permissions("munki.add_configuration") name = get_random_string(12) - response = self.post( - reverse("munki_api:configurations"), - {"name": name, - "description": "Description", - "inventory_apps_full_info_shard": 50, - "principal_user_detection_sources": ["google_chrome", "company_portal"], - "principal_user_detection_domains": ["zentral.io"], - "collected_condition_keys": ["yolo"], - "managed_installs_sync_interval_days": 1, - "script_checks_run_interval_seconds": 86400, - "auto_reinstall_incidents": True, - "auto_failed_install_incidents": True} - ) + with self.captureOnCommitCallbacks(execute=True) as callbacks: + response = self.post( + reverse("munki_api:configurations"), + {"name": name, + "description": "Description", + "inventory_apps_full_info_shard": 50, + "principal_user_detection_sources": ["google_chrome", "company_portal"], + "principal_user_detection_domains": ["zentral.io"], + "collected_condition_keys": ["yolo"], + "managed_installs_sync_interval_days": 1, + "script_checks_run_interval_seconds": 86400, + "auto_reinstall_incidents": True, + "auto_failed_install_incidents": True} + ) self.assertEqual(response.status_code, 201) + self.assertEqual(len(callbacks), 1) configuration = Configuration.objects.get(name=name) + event = post_event.call_args_list[0].args[0] + self.assertIsInstance(event, AuditEvent) + self.assertEqual( + event.payload, + { + "action": "created", + "object": { + "model": "munki.configuration", + "pk": str(configuration.pk), + "new_value": { + 'pk': configuration.pk, + 'name': name, + 'description': 'Description', + 'inventory_apps_full_info_shard': 50, + 'principal_user_detection_sources': ["google_chrome", "company_portal"], + 'principal_user_detection_domains': ["zentral.io"], + 'collected_condition_keys': ["yolo"], + 'managed_installs_sync_interval_days': 1, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': True, + 'auto_failed_install_incidents': True, + 'created_at': configuration.created_at, + 'updated_at': configuration.updated_at, + 'version': 0, + } + } + } + ) + + metadata = event.metadata.serialize() + self.assertEqual(metadata["objects"], {"munki_configuration": [str(configuration.pk)]}) + self.assertEqual(sorted(metadata["tags"]), ["munki", "zentral"]) self.assertEqual( response.json(), - {'id': configuration.pk, - 'name': name, - 'description': 'Description', - 'inventory_apps_full_info_shard': 50, - 'principal_user_detection_sources': ["google_chrome", "company_portal"], - 'principal_user_detection_domains': ["zentral.io"], - 'collected_condition_keys': ["yolo"], - 'managed_installs_sync_interval_days': 1, - 'script_checks_run_interval_seconds': 86400, - 'auto_reinstall_incidents': True, - 'auto_failed_install_incidents': True, - 'version': 0, - 'created_at': configuration.created_at.isoformat(), - 'updated_at': configuration.updated_at.isoformat()} + { + 'id': configuration.pk, + 'name': name, + 'description': 'Description', + 'inventory_apps_full_info_shard': 50, + 'principal_user_detection_sources': ["google_chrome", "company_portal"], + 'principal_user_detection_domains': ["zentral.io"], + 'collected_condition_keys': ["yolo"], + 'managed_installs_sync_interval_days': 1, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': True, + 'auto_failed_install_incidents': True, + 'version': 0, + 'created_at': configuration.created_at.isoformat(), + 'updated_at': configuration.updated_at.isoformat() + } ) self.assertEqual(configuration.name, name) self.assertEqual(configuration.description, "Description") @@ -282,41 +319,98 @@ def test_update_configuration_permission_denied(self): response = self.put(reverse("munki_api:configuration", args=(configuration.pk,)), {}) self.assertEqual(response.status_code, 403) - def test_update_configuration(self): + @patch("zentral.core.queues.backends.kombu.EventQueues.post_event") + def test_update_configuration(self, post_event): configuration = self.force_configuration() self.set_permissions("munki.change_configuration") + prev_name = configuration.name + prev_updated_at = configuration.updated_at name = get_random_string(12) - response = self.put( - reverse("munki_api:configuration", args=(configuration.pk,)), - {"name": name, - "description": "Description", - "inventory_apps_full_info_shard": 50, - "principal_user_detection_sources": ["google_chrome", "company_portal"], - "principal_user_detection_domains": ["zentral.io"], - "collected_condition_keys": ["yolo"], - "managed_installs_sync_interval_days": 1, - "script_checks_run_interval_seconds": 86400, - "auto_reinstall_incidents": True, - "auto_failed_install_incidents": True} - ) + with self.captureOnCommitCallbacks(execute=True) as callbacks: + response = self.put( + reverse("munki_api:configuration", args=(configuration.pk,)), + { + "name": name, + "description": "Description", + "inventory_apps_full_info_shard": 50, + "principal_user_detection_sources": ["google_chrome", "company_portal"], + "principal_user_detection_domains": ["zentral.io"], + "collected_condition_keys": ["yolo"], + "managed_installs_sync_interval_days": 1, + "script_checks_run_interval_seconds": 86400, + "auto_reinstall_incidents": True, + "auto_failed_install_incidents": True} + ) self.assertEqual(response.status_code, 200) + self.assertEqual(len(callbacks), 1) configuration.refresh_from_db() + event = post_event.call_args_list[0].args[0] + self.assertIsInstance(event, AuditEvent) + self.assertEqual( + event.payload, + { + "action": "updated", + "object": + { + "model": "munki.configuration", + "pk": str(configuration.pk), + "prev_value": { + 'pk': configuration.pk, + 'name': prev_name, + 'description': '', + 'inventory_apps_full_info_shard': 100, + 'principal_user_detection_sources': [], + 'principal_user_detection_domains': [], + 'collected_condition_keys': [], + 'managed_installs_sync_interval_days': 7, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': False, + 'auto_failed_install_incidents': False, + 'version': 0, + 'created_at': configuration.created_at, + 'updated_at': prev_updated_at + }, + "new_value": { + 'pk': configuration.pk, + 'name': name, + 'description': 'Description', + 'inventory_apps_full_info_shard': 50, + 'principal_user_detection_sources': ["google_chrome", "company_portal"], + 'principal_user_detection_domains': ["zentral.io"], + 'collected_condition_keys': ["yolo"], + 'managed_installs_sync_interval_days': 1, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': True, + 'auto_failed_install_incidents': True, + 'version': 1, + 'created_at': configuration.created_at, + 'updated_at': configuration.updated_at + } + } + } + ) + + metadata = event.metadata.serialize() + self.assertEqual(metadata["objects"], {"munki_configuration": [str(configuration.pk)]}) + self.assertEqual(sorted(metadata["tags"]), ["munki", "zentral"]) self.assertEqual( response.json(), - {'id': configuration.pk, - 'name': name, - 'description': 'Description', - 'inventory_apps_full_info_shard': 50, - 'principal_user_detection_sources': ["google_chrome", "company_portal"], - 'principal_user_detection_domains': ["zentral.io"], - 'collected_condition_keys': ["yolo"], - 'managed_installs_sync_interval_days': 1, - 'script_checks_run_interval_seconds': 86400, - 'auto_reinstall_incidents': True, - 'auto_failed_install_incidents': True, - 'version': 1, - 'created_at': configuration.created_at.isoformat(), - 'updated_at': configuration.updated_at.isoformat()} + { + 'id': configuration.pk, + 'name': name, + 'description': 'Description', + 'inventory_apps_full_info_shard': 50, + 'principal_user_detection_sources': ["google_chrome", "company_portal"], + 'principal_user_detection_domains': ["zentral.io"], + 'collected_condition_keys': ["yolo"], + 'managed_installs_sync_interval_days': 1, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': True, + 'auto_failed_install_incidents': True, + 'version': 1, + 'created_at': configuration.created_at.isoformat(), + 'updated_at': configuration.updated_at.isoformat() + } ) self.assertEqual(configuration.name, name) self.assertEqual(configuration.description, "Description") @@ -342,12 +436,47 @@ def test_delete_configuration_permission_denied(self): response = self.delete(reverse("munki_api:configuration", args=(configuration.pk,))) self.assertEqual(response.status_code, 403) - def test_delete_configuration(self): + @patch("zentral.core.queues.backends.kombu.EventQueues.post_event") + def test_delete_configuration(self, post_event): configuration = self.force_configuration() + prev_pk = configuration.pk self.set_permissions("munki.delete_configuration") - response = self.delete(reverse("munki_api:configuration", args=(configuration.pk,))) + with self.captureOnCommitCallbacks(execute=True) as callbacks: + response = self.delete(reverse("munki_api:configuration", args=(configuration.pk,))) self.assertEqual(response.status_code, 204) + self.assertEqual(len(callbacks), 1) self.assertEqual(Configuration.objects.filter(pk=configuration.pk).count(), 0) + event = post_event.call_args_list[0].args[0] + self.assertIsInstance(event, AuditEvent) + self.assertEqual( + event.payload, + { + "action": "deleted", + "object": { + "model": "munki.configuration", + "pk": str(prev_pk), + "prev_value": { + 'pk': prev_pk, + 'name': configuration.name, + 'description': '', + 'inventory_apps_full_info_shard': 100, + 'principal_user_detection_sources': [], + 'principal_user_detection_domains': [], + 'collected_condition_keys': [], + 'managed_installs_sync_interval_days': 7, + 'script_checks_run_interval_seconds': 86400, + 'auto_reinstall_incidents': False, + 'auto_failed_install_incidents': False, + 'version': 0, + 'created_at': configuration.created_at, + 'updated_at': configuration.updated_at + } + } + } + ) + metadata = event.metadata.serialize() + self.assertEqual(metadata["objects"], {"munki_configuration": [str(prev_pk)]}) + self.assertEqual(sorted(metadata["tags"]), ["munki", "zentral"]) # list enrollments diff --git a/tests/munki/test_setup_views.py b/tests/munki/test_setup_views.py index 1d1cecdf6c..55a5047f7b 100644 --- a/tests/munki/test_setup_views.py +++ b/tests/munki/test_setup_views.py @@ -9,14 +9,18 @@ from django.urls import reverse from django.utils.crypto import get_random_string from django.test import TestCase, override_settings +from unittest.mock import patch from zentral.contrib.inventory.models import MetaBusinessUnit, Tag from zentral.contrib.munki.models import Enrollment from accounts.models import User +from zentral.core.events.base import AuditEvent from .utils import force_configuration, force_enrollment, force_script_check, make_enrolled_machine @override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage') class MunkiSetupViewsTestCase(TestCase): + maxDiff = None + @classmethod def setUpTestData(cls): # user @@ -107,6 +111,55 @@ def test_configuration_enrollment_and_machine_count(self): self.assertEqual(response.context['object_list'][0].enrollment__count, 2) self.assertEqual(response.context['object_list'][0].enrollment__enrolledmachine__count, 3) + def test_configuration_without_event_links(self): + configuration = force_configuration() + self._login("munki.view_configuration") + response = self.client.get(configuration.get_absolute_url()) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "munki/configuration_detail.html") + self.assertNotContains(response, reverse("munki:configuration_events", + args=(configuration.pk,))) + + def test_configuration_with_event_links(self): + configuration = force_configuration() + self._login("munki.view_configuration", + "munki.view_enrollment") + response = self.client.get(configuration.get_absolute_url()) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "munki/configuration_detail.html") + self.assertContains(response, reverse("munki:configuration_events", + args=(configuration.pk,))) + + def test_configuration_events_redirect(self): + configuration = force_configuration() + self._login_redirect(reverse("munki:configuration_events", args=(configuration.pk,))) + + def test_configuration_events_permission_denied(self): + configuration = force_configuration() + self._login("munki.view_configuration") + response = self.client.get(reverse("munki:configuration_events", args=(configuration.pk,))) + self.assertEqual(response.status_code, 403) + + @patch("zentral.core.stores.backends.elasticsearch.EventStore.get_aggregated_object_event_counts") + def test_configuration_events_ok(self, get_aggregated_object_event_counts): + get_aggregated_object_event_counts.return_value = {} + configuration = force_configuration() + self._login("munki.view_configuration", + "munki.view_enrollment") + response = self.client.get(reverse("munki:configuration_events", args=(configuration.pk,))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "munki/configuration_events.html") + + @patch("zentral.core.stores.backends.elasticsearch.EventStore.fetch_object_events") + def test_fetch_configuration_events_ok(self, fetch_object_events): + fetch_object_events.return_value = {} + configuration = force_configuration() + self._login("munki.view_configuration", + "munki.view_enrollment") + response = self.client.get(reverse("munki:configuration_events", args=(configuration.pk,))) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "munki/configuration_events.html") + # create configuration def test_create_configuration_redirect(self): @@ -123,23 +176,26 @@ def test_create_configuration_get(self): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "munki/configuration_form.html") - def test_create_configuration_post(self): + @patch("zentral.core.queues.backends.kombu.EventQueues.post_event") + def test_create_configuration_post(self, post_event): self._login("munki.add_configuration", "munki.view_configuration") name = get_random_string(12) description = get_random_string(12) collected_condition_keys = sorted(get_random_string(12) for _ in range(3)) - response = self.client.post(reverse("munki:create_configuration"), - {"name": name, - "description": description, - "inventory_apps_full_info_shard": 17, - "principal_user_detection_sources": "logged_in_user", - "principal_user_detection_domains": "yolo.fr", - "collected_condition_keys": " , ".join(collected_condition_keys), - "managed_installs_sync_interval_days": 1, - "script_checks_run_interval_seconds": 7231, - "auto_reinstall_incidents": "on"}, - follow=True) + with self.captureOnCommitCallbacks(execute=True) as callbacks: + response = self.client.post(reverse("munki:create_configuration"), + {"name": name, + "description": description, + "inventory_apps_full_info_shard": 17, + "principal_user_detection_sources": "logged_in_user", + "principal_user_detection_domains": "yolo.fr", + "collected_condition_keys": " , ".join(collected_condition_keys), + "managed_installs_sync_interval_days": 1, + "script_checks_run_interval_seconds": 7231, + "auto_reinstall_incidents": "on"}, + follow=True) self.assertEqual(response.status_code, 200) + self.assertEqual(len(callbacks), 1) self.assertTemplateUsed(response, "munki/configuration_detail.html") self.assertContains(response, name) self.assertContains(response, description) @@ -153,6 +209,38 @@ def test_create_configuration_post(self): self.assertEqual(sorted(configuration.collected_condition_keys), collected_condition_keys) for condition_key in collected_condition_keys: self.assertContains(response, condition_key) + event = post_event.call_args_list[0].args[0] + self.assertIsInstance(event, AuditEvent) + self.assertEqual( + event.payload, + { + "action": "created", + "object": + { + "model": "munki.configuration", + "pk": str(configuration.pk), + "new_value": { + "pk": configuration.pk, + "name": name, + "description": description, + "inventory_apps_full_info_shard": 17, + "principal_user_detection_sources": ["logged_in_user"], + "principal_user_detection_domains": ["yolo.fr"], + "collected_condition_keys": collected_condition_keys, + "managed_installs_sync_interval_days": 1, + "script_checks_run_interval_seconds": 7231, + "auto_reinstall_incidents": True, + "auto_failed_install_incidents": False, + "created_at": configuration.created_at, + "updated_at": configuration.updated_at, + "version": 0, + } + } + } + ) + metadata = event.metadata.serialize() + self.assertEqual(metadata["objects"], {"munki_configuration": [str(configuration.pk)]}) + self.assertEqual(sorted(metadata["tags"]), ["munki", "zentral"]) # update configuration @@ -173,21 +261,27 @@ def test_update_configuration_get(self): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "munki/configuration_form.html") - def test_update_configuration_post(self): + @patch("zentral.core.queues.backends.kombu.EventQueues.post_event") + def test_update_configuration_post(self, post_event): configuration = force_configuration() + prev_updated_at = configuration.updated_at self._login("munki.change_configuration", "munki.view_configuration") collected_condition_keys = sorted(get_random_string(12) for _ in range(3)) - response = self.client.post(reverse("munki:update_configuration", args=(configuration.pk,)), - {"name": configuration.name, - "inventory_apps_full_info_shard": 17, - "principal_user_detection_sources": "logged_in_user", - "principal_user_detection_domains": "yolo.fr", - "collected_condition_keys": ",".join(collected_condition_keys), - "managed_installs_sync_interval_days": 2, - "script_checks_run_interval_seconds": 3600, - "auto_failed_install_incidents": "on"}, - follow=True) + with self.captureOnCommitCallbacks(execute=True) as callbacks: + response = self.client.post(reverse("munki:update_configuration", args=(configuration.pk,)), + { + "name": configuration.name, + "inventory_apps_full_info_shard": 17, + "principal_user_detection_sources": "logged_in_user", + "principal_user_detection_domains": "yolo.fr", + "collected_condition_keys": ",".join(collected_condition_keys), + "managed_installs_sync_interval_days": 2, + "script_checks_run_interval_seconds": 3600, + "auto_failed_install_incidents": "on"}, + follow=True + ) self.assertEqual(response.status_code, 200) + self.assertEqual(len(callbacks), 1) self.assertTemplateUsed(response, "munki/configuration_detail.html") configuration2 = response.context["object"] self.assertEqual(configuration2, configuration) @@ -200,6 +294,54 @@ def test_update_configuration_post(self): self.assertEqual(sorted(configuration2.collected_condition_keys), collected_condition_keys) for condition_key in collected_condition_keys: self.assertContains(response, condition_key) + event = post_event.call_args_list[0].args[0] + self.assertIsInstance(event, AuditEvent) + self.assertEqual( + event.payload, + { + "action": "updated", + "object": + { + "model": "munki.configuration", + "pk": str(configuration.pk), + "prev_value": { + "pk": configuration.pk, + "name": configuration.name, + "description": "", + "inventory_apps_full_info_shard": 100, + "principal_user_detection_sources": [], + "principal_user_detection_domains": [], + "collected_condition_keys": [], + "managed_installs_sync_interval_days": 7, + "script_checks_run_interval_seconds": 86400, + "auto_failed_install_incidents": False, + "auto_reinstall_incidents": False, + "created_at": configuration.created_at, + "updated_at": prev_updated_at, + "version": 0, + }, + "new_value": { + "pk": configuration2.pk, + "description": "", + "name": configuration2.name, + "inventory_apps_full_info_shard": 17, + "principal_user_detection_sources": ["logged_in_user"], + "principal_user_detection_domains": ["yolo.fr"], + "collected_condition_keys": collected_condition_keys, + "managed_installs_sync_interval_days": 2, + "script_checks_run_interval_seconds": 3600, + "auto_failed_install_incidents": True, + "auto_reinstall_incidents": False, + "created_at": configuration2.created_at, + "updated_at": configuration2.updated_at, + "version": configuration2.version, + } + } + } + ) + metadata = event.metadata.serialize() + self.assertEqual(metadata["objects"], {"munki_configuration": [str(configuration.pk)]}) + self.assertEqual(sorted(metadata["tags"]), ["munki", "zentral"]) # create enrollment diff --git a/zentral/contrib/munki/api_views.py b/zentral/contrib/munki/api_views.py index 0ec0482b24..68ca912b95 100644 --- a/zentral/contrib/munki/api_views.py +++ b/zentral/contrib/munki/api_views.py @@ -12,7 +12,7 @@ # configurations -class ConfigurationList(generics.ListCreateAPIView): +class ConfigurationList(ListCreateAPIViewWithAudit): queryset = Configuration.objects.all() serializer_class = ConfigurationSerializer permission_classes = [DefaultDjangoModelPermissions] @@ -20,7 +20,7 @@ class ConfigurationList(generics.ListCreateAPIView): filterset_fields = ("name",) -class ConfigurationDetail(generics.RetrieveUpdateDestroyAPIView): +class ConfigurationDetail(RetrieveUpdateDestroyAPIViewWithAudit): queryset = Configuration.objects.all() serializer_class = ConfigurationSerializer permission_classes = [DefaultDjangoModelPermissions] diff --git a/zentral/contrib/munki/models.py b/zentral/contrib/munki/models.py index c9ee326c16..d5bfa00c7d 100644 --- a/zentral/contrib/munki/models.py +++ b/zentral/contrib/munki/models.py @@ -87,6 +87,29 @@ def save(self, *args, **kwargs): self.version = F("version") + 1 super().save(*args, **kwargs) + def serialize_for_event(self, keys_only=False): + d = {"pk": self.pk, "name": self.name} + if not keys_only: + if not isinstance(self.version, int): + # version was updated with a CombinedExpression + # it needs to be fetched from the DB for the JSON serialization + self.refresh_from_db() + d.update({ + "description": self.description, + "inventory_apps_full_info_shard": self.inventory_apps_full_info_shard, + "principal_user_detection_sources": self.principal_user_detection_sources, + "principal_user_detection_domains": self.principal_user_detection_domains, + "collected_condition_keys": self.collected_condition_keys, + "managed_installs_sync_interval_days": self.managed_installs_sync_interval_days, + "script_checks_run_interval_seconds": self.script_checks_run_interval_seconds, + "auto_reinstall_incidents": self.auto_reinstall_incidents, + "auto_failed_install_incidents": self.auto_failed_install_incidents, + "created_at": self.created_at, + "updated_at": self.updated_at, + "version": self.version, + }) + return d + # enrollment @@ -100,6 +123,11 @@ def get_absolute_url(self): def get_description_for_distributor(self): return "Zentral pre/postflight" + def serialize_for_event(self): + enrollment_dict = super().serialize_for_event() + enrollment_dict["configuration"] = self.configuration.serialize_for_event(keys_only=True) + return enrollment_dict + class EnrolledMachine(models.Model): enrollment = models.ForeignKey(Enrollment, on_delete=models.CASCADE) diff --git a/zentral/contrib/munki/templates/munki/configuration_detail.html b/zentral/contrib/munki/templates/munki/configuration_detail.html index 6aacf6a6d7..fca2056dcf 100644 --- a/zentral/contrib/munki/templates/munki/configuration_detail.html +++ b/zentral/contrib/munki/templates/munki/configuration_detail.html @@ -13,10 +13,17 @@

{{ configuration }}

+
+ {% if show_events_link %} + {% url 'munki:configuration_events' object.pk as url %} + {% button 'EVENTS' url %} + {% endif %} +

Configuration

+ {% if perms.munki.change_configuration %} {% url 'munki:update_configuration' object.pk as url %} {% button 'UPDATE' url "Edit Configuration" %} diff --git a/zentral/contrib/munki/templates/munki/configuration_events.html b/zentral/contrib/munki/templates/munki/configuration_events.html new file mode 100644 index 0000000000..719cad5bb9 --- /dev/null +++ b/zentral/contrib/munki/templates/munki/configuration_events.html @@ -0,0 +1,29 @@ +{% extends 'base.html' %} + +{% block content %} + + + +

{{ configuration }}

+

Events

+
+
+ + {% include "core/stores/events_form.html" %} + +
+
+ +{% include "core/stores/events_table.html" %} + +{% endblock %} + +{% block extrajs %} +{% include "core/stores/events_script.html" %} +{% endblock %} diff --git a/zentral/contrib/munki/urls.py b/zentral/contrib/munki/urls.py index 0721f537d3..73f11851c4 100644 --- a/zentral/contrib/munki/urls.py +++ b/zentral/contrib/munki/urls.py @@ -10,6 +10,14 @@ path('configurations/', views.ConfigurationListView.as_view(), name='configurations'), path('configurations/create/', views.CreateConfigurationView.as_view(), name='create_configuration'), path('configurations//', views.ConfigurationView.as_view(), name='configuration'), + path('configurations//events/', views.ConfigurationEventsView.as_view(), name='configuration_events'), + path('configurations//events/fetch/', + views.FetchConfigurationEventsView.as_view(), + name='fetch_configuration_events'), + path('configurations//events/store_redirect/', + views.ConfigurationEventsStoreRedirectView.as_view(), + name='configuration_events_store_redirect'), + path('configurations//update/', views.UpdateConfigurationView.as_view(), name='update_configuration'), # enrollment diff --git a/zentral/contrib/munki/views.py b/zentral/contrib/munki/views.py index aecddb029e..91861cfd77 100644 --- a/zentral/contrib/munki/views.py +++ b/zentral/contrib/munki/views.py @@ -6,7 +6,7 @@ from django.db.models import F, Count from django.shortcuts import get_object_or_404, redirect from django.urls import reverse, reverse_lazy -from django.views.generic import CreateView, DeleteView, DetailView, FormView, ListView, TemplateView, UpdateView, View +from django.views.generic import DeleteView, DetailView, FormView, ListView, TemplateView, View from zentral.contrib.inventory.forms import EnrollmentSecretForm from zentral.core.compliance_checks.forms import ComplianceCheckForm from zentral.core.events.base import AuditEvent @@ -15,7 +15,7 @@ from zentral.core.stores.views import EventsView, FetchEventsView, EventsStoreRedirectView from zentral.utils.terraform import build_config_response from zentral.utils.text import encode_args -from zentral.utils.views import DeleteViewWithAudit, UserPaginationListView +from zentral.utils.views import CreateViewWithAudit, DeleteViewWithAudit, UpdateViewWithAudit, UserPaginationListView from .compliance_checks import MunkiScriptCheck from .forms import (CreateInstallProbeForm, ConfigurationForm, EnrollmentForm, ScriptCheckForm, ScriptCheckSearchForm, UpdateInstallProbeForm) @@ -68,7 +68,7 @@ def get_context_data(self, **kwargs): return ctx -class CreateConfigurationView(PermissionRequiredMixin, CreateView): +class CreateConfigurationView(PermissionRequiredMixin, CreateViewWithAudit): permission_required = "munki.add_configuration" model = Configuration form_class = ConfigurationForm @@ -99,15 +99,61 @@ def get_context_data(self, **kwargs): enrollments.append((enrollment, distributor, distributor_link)) ctx["enrollments"] = enrollments ctx["enrollment_count"] = enrollment_count + + # events + if self.request.user.has_perms(ConfigurationEventsView.permission_required): + ctx["show_events_link"] = frontend_store.object_events return ctx -class UpdateConfigurationView(PermissionRequiredMixin, UpdateView): +class UpdateConfigurationView(PermissionRequiredMixin, UpdateViewWithAudit): permission_required = "munki.change_configuration" model = Configuration form_class = ConfigurationForm +# events + +class EventsMixin: + store_method_scope = "object" + + def get_object(self, **kwargs): + return get_object_or_404(Configuration, pk=kwargs["pk"]) + + def get_fetch_kwargs_extra(self): + return {"key": "munki_configuration", "val": encode_args((self.object.pk,))} + + def get_fetch_url(self): + return reverse("munki:fetch_configuration_events", args=(self.object.pk,)) + + def get_redirect_url(self): + return reverse("munki:configuration_events", args=(self.object.pk,)) + + def get_store_redirect_url(self): + return reverse("munki:configuration_events_store_redirect", args=(self.object.pk,)) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx["configuration"] = self.object + return ctx + + +class ConfigurationEventsView(EventsMixin, EventsView): + permission_required = ("munki.view_configuration", + "munki.view_enrollment") + template_name = "munki/configuration_events.html" + + +class FetchConfigurationEventsView(EventsMixin, FetchEventsView): + permission_required = ("munki.view_configuration", + "munki.view_enrollment") + + +class ConfigurationEventsStoreRedirectView(EventsMixin, EventsStoreRedirectView): + permission_required = ("munki.view_configuration", + "munki.view_enrollment") + + # enrollment