From 79113697401765ed8b2c9afdd4a287789c418c31 Mon Sep 17 00:00:00 2001 From: kdeterme Date: Fri, 25 Sep 2026 22:53:32 +0200 Subject: [PATCH] feat: restrict intervention timeline history and filter controls to internal users --- .../detail/card_chronologie.html | 6 ++- .../tests/test_timeline_history.py | 42 +++++++++++++++++++ loko/interventions/views/detail.py | 7 ++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/loko/interventions/templates/interventions/detail/card_chronologie.html b/loko/interventions/templates/interventions/detail/card_chronologie.html index 0771ec2..f4c6288 100644 --- a/loko/interventions/templates/interventions/detail/card_chronologie.html +++ b/loko/interventions/templates/interventions/detail/card_chronologie.html @@ -13,7 +13,8 @@ - + + {% if is_user_intern %}
+ {% endif %}
@@ -42,6 +44,7 @@ {% if events_list %}
{% for event in events_list %} + {% if is_user_intern or event.is_default_visible %}
@@ -144,6 +147,7 @@ {% endif %}
+ {% endif %} {% endfor %}
{% else %} diff --git a/loko/interventions/tests/test_timeline_history.py b/loko/interventions/tests/test_timeline_history.py index 27e0c93..faa734d 100644 --- a/loko/interventions/tests/test_timeline_history.py +++ b/loko/interventions/tests/test_timeline_history.py @@ -477,5 +477,47 @@ class TimelineHistoryTests(TestCase): self.assertContains(response, 'history-status-circle') self.assertContains(response, 'history-status-transition') self.assertContains(response, 'bg-purple') + self.assertContains(response, 'id="btnHistoryFull"') + + def test_detail_view_hides_full_history_for_external_user(self): + """Test that external users (is_intern=False) do NOT see full history or detailed modification/note events.""" + User = get_user_model() + ext_user = User.objects.create_user( + username='prestataire_ext', + password='secret_password', + first_name='Pierre', + last_name='Martin' + ) + ext_config = UserConfig.objects.create(user=ext_user, is_intern=False) + ext_role, _ = Role.objects.get_or_create(name='external_manager') + ext_config.roles.add(ext_role) + self.intervention.assigned_provider = self.company + self.intervention.save() + + record_timeline_event(self.intervention, self.user, event_type='status_change', from_status='in_preparation', to_status='to_be_processed') + record_note_event(self.intervention, self.user, action='create', note_type='comment', content='Note interne confidentielle') + record_document_event(self.intervention, self.user, action='upload', filenames=['secret_doc.pdf']) + + self.client.login(username='prestataire_ext', password='secret_password') + url = reverse('interventions:intervention_detail', args=[self.intervention.id]) + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + + # Le filtre "Historique complet" ne doit PAS être affiché + self.assertNotContains(response, 'id="btnHistoryFull"') + self.assertNotContains(response, 'id="btnHistoryDefault"') + + # Aucun événement détaillé n'est dans le contexte + self.assertEqual(response.context['timeline_detailed_count'], 0) + self.assertTrue(all(e.is_default_visible for e in response.context['timeline_events'])) + + # Le contenu confidentiel des notes et documents n'est pas transmis dans l'historique + self.assertNotContains(response, 'Note interne confidentielle') + self.assertNotContains(response, 'secret_doc.pdf') + + # Les statuts et assignations restent parfaitement visibles + self.assertContains(response, 'history-status-circle') + self.assertContains(response, 'history-status-transition') + diff --git a/loko/interventions/views/detail.py b/loko/interventions/views/detail.py index 0c8eb03..7a0adfe 100644 --- a/loko/interventions/views/detail.py +++ b/loko/interventions/views/detail.py @@ -682,6 +682,13 @@ def intervention_detail(request, intervention_id): insert_idx = 1 if (timeline_events and timeline_events[0].is_creation) else 0 timeline_events.insert(insert_idx, synth_event) + # Sécurité / Confidentialité : l'historique complet (modifications d'attributs, notes, documents, liens) + # est strictement réservé aux agents internes. + # Les utilisateurs externes (prestataires) ne reçoivent que le flux opérationnel + # (statuts, assignations, créations, transferts). + if not is_user_intern: + timeline_events = [e for e in timeline_events if e.is_default_visible] + timeline_default_count = sum(1 for e in timeline_events if e.is_default_visible) timeline_detailed_count = sum(1 for e in timeline_events if not e.is_default_visible) timeline_modification_count = timeline_detailed_count