From 84f03b3cd4c38a6c2151d05fa019e49b9568025e Mon Sep 17 00:00:00 2001 From: vasileios Date: Wed, 8 May 2024 16:26:47 +0200 Subject: [PATCH] [#3725] Added invalid certificates to email digest --- src/openforms/emails/digest.py | 71 ++++++++++++++- src/openforms/emails/tasks.py | 3 + .../emails/templates/emails/admin_digest.html | 30 +++++++ .../emails/tests/data/test.certificate | 32 +++++++ src/openforms/emails/tests/data/test.key | 52 +++++++++++ .../emails/tests/data/test2.certificate | 32 +++++++ .../emails/tests/test_digest_functions.py | 86 +++++++++++++++++++ .../emails/tests/test_tasks_integration.py | 39 ++++++++- test.key | 52 +++++++++++ 9 files changed, 392 insertions(+), 5 deletions(-) create mode 100644 src/openforms/emails/tests/data/test.certificate create mode 100644 src/openforms/emails/tests/data/test.key create mode 100644 src/openforms/emails/tests/data/test2.certificate create mode 100644 test.key diff --git a/src/openforms/emails/digest.py b/src/openforms/emails/digest.py index 85347d4991..dc1ab9a9a5 100644 --- a/src/openforms/emails/digest.py +++ b/src/openforms/emails/digest.py @@ -1,15 +1,18 @@ import uuid from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timedelta from itertools import groupby from typing import Iterable from django.contrib.contenttypes.models import ContentType +from django.db.models import Q from django.urls import reverse +from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django_yubin.models import Message from furl import furl +from simple_certmanager.models import Certificate from openforms.contrib.brk.service import check_brk_config_for_addressNL from openforms.contrib.kadaster.service import check_bag_config_for_address_fields @@ -70,6 +73,23 @@ class BrokenConfiguration: exception_message: str +@dataclass +class InvalidCertificate: + id: int + label: str + error_message: str + is_valid_pair: bool + expiry_date: datetime | None = None + + @property + def admin_link(self) -> str: + form_admin_url = reverse( + "admin:simple_certmanager_certificate_change", + kwargs={"object_id": self.id}, + ) + return form_admin_url + + def collect_failed_emails(since: datetime) -> Iterable[FailedEmail]: logs = TimelineLogProxy.objects.filter( timestamp__gt=since, @@ -188,3 +208,52 @@ def collect_broken_configurations() -> list[BrokenConfiguration]: ) return broken_configurations + + +def collect_invalid_certificates() -> list[InvalidCertificate]: + today = timezone.now() + error_messages = { + "expiring": _("will expire soon"), + "invalid": _("has invalid keypair"), + "invalid_expiring": _("invalid keypair, will expire soon"), + } + + invalid_certs = [] + # filter only on the certificates that are used by services + configured_certificates = Certificate.objects.filter( + Q(soap_services_client__isnull=False) + | Q(soap_services_server__isnull=False) + | Q(service_client__isnull=False) + | Q(service_server__isnull=False) + ) + for cert in configured_certificates: + time_until_expiry = cert.expiry_date - today + error_message = "" + is_valid_pair = False + + match (time_until_expiry <= timedelta(days=14), cert.is_valid_key_pair()): + + case (True, True) | (True, None): + error_message = error_messages["expiring"] + is_valid_pair = True + + case (False, False): + error_message = error_messages["invalid"] + is_valid_pair = False + + case (True, False): + error_message = error_messages["invalid_expiring"] + is_valid_pair = False + + if error_message: + invalid_certs.append( + InvalidCertificate( + id=cert.id, + label=cert.label, + error_message=error_message, + is_valid_pair=is_valid_pair, + expiry_date=cert.expiry_date, + ) + ) + + return invalid_certs diff --git a/src/openforms/emails/tasks.py b/src/openforms/emails/tasks.py index 2963fad61c..78bb4d4b8c 100644 --- a/src/openforms/emails/tasks.py +++ b/src/openforms/emails/tasks.py @@ -14,6 +14,7 @@ collect_failed_emails, collect_failed_prefill_plugins, collect_failed_registrations, + collect_invalid_certificates, ) from .utils import send_mail_html @@ -27,12 +28,14 @@ def get_context_data(self) -> dict[str, Any]: failed_registrations = collect_failed_registrations(self.since) failed_prefill_plugins = collect_failed_prefill_plugins(self.since) broken_configurations = collect_broken_configurations() + invalid_certificates = collect_invalid_certificates() return { "failed_emails": failed_emails, "failed_registrations": failed_registrations, "failed_prefill_plugins": failed_prefill_plugins, "broken_configurations": broken_configurations, + "invalid_certificates": invalid_certificates, } def render(self) -> str: diff --git a/src/openforms/emails/templates/emails/admin_digest.html b/src/openforms/emails/templates/emails/admin_digest.html index 474eee6f89..c13abdaa16 100644 --- a/src/openforms/emails/templates/emails/admin_digest.html +++ b/src/openforms/emails/templates/emails/admin_digest.html @@ -77,3 +77,33 @@
{% trans "Configuration problems" %}
{% endfor %} {% endif %} + +{% if invalid_certificates %} +
{% trans "Found invalid certificates" %}
+ +{% endif %} + diff --git a/src/openforms/emails/tests/data/test.certificate b/src/openforms/emails/tests/data/test.certificate new file mode 100644 index 0000000000..11f573a764 --- /dev/null +++ b/src/openforms/emails/tests/data/test.certificate @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUN3BAJ5vBOwpR7/1eqwtdL0sM5JIwDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CUFtc3RlcmRhbTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4X +DTI0MDQyMjEzMDI1NVoXDTI1MDQyMjEzMDI1NVowWTELMAkGA1UEBhMCTkwxEzAR +BgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCUFtc3RlcmRhbTEhMB8GA1UECgwY +SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA822RC6e9SZpvjKE+azidiplG+00ZHjo/10sL2flWN2sJcfPW1gXm +VvFq4s5Fq+jEwBOA8nulmPChML72PQYSC25m20ni53095/lU9wjNTcOWhQb2m9ST +LcTIkOgllC9rYQ9o8M01Sidat+dNeR51Qr4kROrbSlHENZaUuVCtWtTcP7HYl8DE +DOjYMgcH85937BEqgdMh702lsJTNFOKsRbojBvpl9W4FSal4+qcJNZB7yrNQCpu+ +lZ9XV6sz5hs/BESPjIUgXK7eyBgKQ38Q6cRzjzL5XakKUndb0kcoDG+Ah2uDs0sY +AKjSjN8ZaUVb0gIT8LvN3OD0P2RAYxEYRRw2zhCjtTUF10ZFTknnHtJpC7tP3K55 +pe59Trjbs9+bgnCmpv6A8NswGwpbs85piWVVXgoB8Sgx7pFjly3znA7MZmCNAH+u +V5yXuMM19drZ+RqO+0K5AQvgkcq/8CxeGOaO4nvWj2OjXNNHPF5VyqwYCg0d3GMm +IN+Bkas3ZHujPEDF2JjEEFHcgHm3zK8DqosYlGTJh73d/WAzUlSN1qk7k/s63rX1 +VEd/Hf7Cjd64HjJA3NTeRw1Llh8Sbd0Qzyw23cfCTn0LkN/jCNiP7V/So7lsATCo +FPno2J0TiSkgc7jfJzN+pdSMJxT+Wv6tpeIJOjWzsTx+aBCBVYdL9+ECAwEAAaNT +MFEwHQYDVR0OBBYEFPPf3Pd8wpF9S1ToYyXMpfa9o5teMB8GA1UdIwQYMBaAFPPf +3Pd8wpF9S1ToYyXMpfa9o5teMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggIBANLM6vebXmULO8te78cm8PkNw644cWn3O/u+798/PQ0SA5lNW+CVI2td +SPaoHa54cFSiaTDc8ZVId85aMyWiqvARi70lawZpfw6zperpdI+9xsmiPmeuX/nl +LeclBaPUj/YhSk5Q2jio4z7Vq7fsAxmC4aAUj94jrBL6Qtxrm8CfFamyHNfKYipJ +vMNcXJlfzXUAfYpTiW9c1yUKnvxo7MN075axP/bw4p5kFb1cp6Xqmu1JKNZNAuvT +3T2RUViSYCRmdTgLs4UO6VNCk00s0tHCpBA3nFzze7EOMi297CLSnss47fkAVEfG +Lw+S4S5JbQHgzJQ6d0PcT8qTMw9gGgOn91JX9C2gecVurOKR7WHcgOgMt65WDccT +g+9YnfiG1tR0IcHkwCisUgTMWL35Bjcehp9444Z40pwdB6NQPetI+ifC6bI9gbXi +2XRzKwxkaepdxeUo92gyk6NPOx7abuwtnBzVuRfVSNwMfNeLIU2OWF9y4jtCitSj +xsD5q9KnK1mKR4HF0JRcHxsZLhM5HCWT1lBSSF13FIV8/Snlhz439Kb1fjq3MIEK +iWHan+Zh0QzEECAL+HsNw69a7t4/CjrpdIPz3XhG6qYFUhce1BiifbtM81nXEOvl +KfwXScI0nr5/QtSQmzoEQxUfWiakeL6dL/syK2yJg3G1zLCWb2PR +-----END CERTIFICATE----- diff --git a/src/openforms/emails/tests/data/test.key b/src/openforms/emails/tests/data/test.key new file mode 100644 index 0000000000..0a7834c207 --- /dev/null +++ b/src/openforms/emails/tests/data/test.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +XZrv1E0zKKgpaZnoHzqBYFbTgXkMaeg= +-----END PRIVATE KEY----- diff --git a/src/openforms/emails/tests/data/test2.certificate b/src/openforms/emails/tests/data/test2.certificate new file mode 100644 index 0000000000..86e42d6240 --- /dev/null +++ b/src/openforms/emails/tests/data/test2.certificate @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFkzCCA3ugAwIBAgIUKffcxFiWoXNtGrzzkVTSu2AIwV0wDQYJKoZIhvcNAQEL +BQAwWTELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcM +CUFtc3RlcmRhbTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4X +DTI0MDQyMjEzMDUyNloXDTI1MDQyMjEzMDUyNlowWTELMAkGA1UEBhMCTkwxEzAR +BgNVBAgMClNvbWUtU3RhdGUxEjAQBgNVBAcMCUFtc3RlcmRhbTEhMB8GA1UECgwY +SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEAqPxvcE8VZ60+7WRuJqFGNlgWp8X3VfB/2mj7JL1cp07xrNgktmKm +hyFDgwkTO6W5ryoX4h5hyP7Q5DS/2uGROiehArzhmQzrCoSb/PJuz/PR4C+lrYvw +00JJn/KiHL5Z7jsyhSYvAgHHDbRojnB9/cHQYWq2LpkNI2kbnSlyhsGlpM17nkQc +zEv7dlJGySBzsokhBFcxID9leILrGNQtPBGBVsrt1oZ40XetJ1AWSu2naELktj56 +PhsfGmjpOzoStJZcysuwZon4+TjG/hE8R3NdLd2n8RBUehlR1y47auqVmn2Rius0 +P9RH9PFDkGXqbUUzvR9fAM2EI2FF19AdPaOZ1mPY9DgNHKn5eUIW5eHrsIO46M7f +A7zG0S1v6jM+iSfA2K7gCVoFTe9s+ycbGR9AEAfS6VkW8m2X1moK+d9UH4wa3ORm +CLMtCYct/Dc4k53HXMeegEk64NHfdAnrqNyGCMNdeELJaA8dFkfmOsQjqtIaBY3r +38HqG+fhvgDBVgqaMQ8VbmQSStT8xp8Stf4qLv7znmMaAewm98kmy46RwLYdIcyG +kXXImvUAqhMEPCc396J6m5/PZi1sIeLh3SojNcH/e8EgPn0mHNTkbmWk/ZhneF26 +QaDJ0RQkCBpHItgOpbDn42ONMsob/X3xBR+aefm4OHXd12TPfxJiD40CAwEAAaNT +MFEwHQYDVR0OBBYEFCZ6qq8VogQ/HhFFZ1U+QidOBsK/MB8GA1UdIwQYMBaAFCZ6 +qq8VogQ/HhFFZ1U+QidOBsK/MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL +BQADggIBACv8NhUCNzmUvVon7bvSbU1M/R3SoHlSec1JcFxOaidf7fgilj/gxoDr +Q1L32SeC9sbovDfsX/n/t+1hb6EvTJPqXa2u6EwuwOV8ubwdGQ1cVTUqZHoitG9B +NFLDdfEU3Va7tVwvDHBu2Nk77hyd2Z4mgdKXTuO5itbMUQrvPUxUumGoRr6Ztwte +RQJ4kgUQEzDVCZb6Ze7U0jnKtfQhjNzUGpn8mXgbZNlcQ9pBFKb5xRTRBGGpJeh+ +2O2N/NgIFOxP6/7NudFOEYLWWAUG3pmSvMB8ZQojmPXvzJviCvh427Qz+uEnzRhn +PdxYCyGisguQ4NwXFwvxcqBUrnmOK1mw5U8PJjFz3bqhCuw4fx3v7RQInSo8Kckh +aFAN2x+EGfd2GXmS5FdpDUEBFfHerV9cDuIZ7uNHP8WToVwwUvZB7DuqqCW6IVb6 +5ogee/Oab8AAXR3TBesh+bYGEfv8dzpWNXrpQnDKGdOYt2PgjETLzLuitcT/lN2x +txW6AXVRM1Co8P4Myl0mHwsZ5GQidcsd9vkxvMDCIoNTI6JgLur+Q0JSOqm7BM0k +CKVL43CaK37RRIQ7qjv2eXVfkzyC0OaWPPAq20By8KXRp/SoCB9sJAGa9gLs2DJo +PGXgwqFSxW3caeT4QTPmHWojrZVagIZrpu+SQ2yQqI+QHXwFEUNP +-----END CERTIFICATE----- diff --git a/src/openforms/emails/tests/test_digest_functions.py b/src/openforms/emails/tests/test_digest_functions.py index b1141a994f..8074728199 100644 --- a/src/openforms/emails/tests/test_digest_functions.py +++ b/src/openforms/emails/tests/test_digest_functions.py @@ -1,12 +1,15 @@ from datetime import datetime +from pathlib import Path from unittest.mock import patch +from django.core.files import File from django.test import TestCase, override_settings from django.utils.timezone import utc import requests_mock from django_yubin.models import Message from freezegun import freeze_time +from simple_certmanager.test.factories import CertificateFactory from zgw_consumers.test.factories import ServiceFactory from openforms.contrib.brk.models import BRKConfig @@ -25,9 +28,12 @@ collect_failed_emails, collect_failed_prefill_plugins, collect_failed_registrations, + collect_invalid_certificates, ) from ..tasks import Digest +TEST_FILES = Path(__file__).parent / "data" + @freeze_time("2023-02-02T12:30:00+01:00") class FailedEmailsTests(TestCase): @@ -392,3 +398,83 @@ def test_invalid_bag_configuration_without_required_property_is_not_collected( broken_configuration = collect_broken_configurations() self.assertEqual(broken_configuration, []) + + +@override_settings(LANGUAGE_CODE="en") +class InvalidCertificatesTests(TestCase): + + def test_expiring_certificates_not_used_by_a_service_are_not_collected(self): + with open(TEST_FILES / "test.certificate", "r") as client_certificate_f: + CertificateFactory.create( + label="Test certificate", + public_certificate=File(client_certificate_f, name="test.certificate"), + ) + + with freeze_time("2025-04-15T21:15:00Z"): + invalid_certificates = collect_invalid_certificates() + + self.assertEqual(len(invalid_certificates), 0) + + def test_not_expiring_and_valid_certificates_are_not_collected(self): + with open(TEST_FILES / "test.certificate", "r") as client_certificate_f: + certificate = CertificateFactory.create( + label="Test certificate", + public_certificate=File(client_certificate_f, name="test.certificate"), + ) + ServiceFactory.create(client_certificate=certificate) + + with freeze_time("2024-04-15T21:15:00Z"): + invalid_certificates = collect_invalid_certificates() + + self.assertEqual(len(invalid_certificates), 0) + + def test_expiring_certificates_are_collected(self): + with open(TEST_FILES / "test.certificate", "r") as client_certificate_f: + certificate = CertificateFactory.create( + label="Test certificate", + public_certificate=File(client_certificate_f, name="test.certificate"), + ) + ServiceFactory.create(client_certificate=certificate) + + with freeze_time("2025-04-15T21:15:00Z"): + invalid_certificates = collect_invalid_certificates() + + self.assertEqual(len(invalid_certificates), 1) + self.assertEqual(invalid_certificates[0].error_message, "will expire soon") + + def test_invalid_certificates_are_collected(self): + with ( + open(TEST_FILES / "test2.certificate", "r") as client_certificate_f, + open(TEST_FILES / "test.key", "r") as key_f, + ): + certificate = CertificateFactory.create( + label="Test certificate", + public_certificate=File(client_certificate_f, name="test.certificate"), + private_key=File(key_f, name="test.key"), + ) + ServiceFactory.create(client_certificate=certificate) + + invalid_certificates = collect_invalid_certificates() + + self.assertEqual(len(invalid_certificates), 1) + self.assertEqual(invalid_certificates[0].error_message, "has invalid keypair") + + def test_both_expiring_and_invalid_certificates_are_collected(self): + with ( + open(TEST_FILES / "test2.certificate", "r") as client_certificate_f, + open(TEST_FILES / "test.key", "r") as key_f, + ): + certificate = CertificateFactory.create( + label="Test certificate", + public_certificate=File(client_certificate_f, name="test.certificate"), + private_key=File(key_f, name="test.key"), + ) + ServiceFactory.create(client_certificate=certificate) + + with freeze_time("2025-04-15T21:15:00Z"): + invalid_certificates = collect_invalid_certificates() + + self.assertEqual(len(invalid_certificates), 1) + self.assertEqual( + invalid_certificates[0].error_message, "invalid keypair, will expire soon" + ) diff --git a/src/openforms/emails/tests/test_tasks_integration.py b/src/openforms/emails/tests/test_tasks_integration.py index de1b444074..199056555f 100644 --- a/src/openforms/emails/tests/test_tasks_integration.py +++ b/src/openforms/emails/tests/test_tasks_integration.py @@ -1,13 +1,17 @@ +from pathlib import Path from unittest.mock import patch from django.contrib.contenttypes.models import ContentType from django.core import mail +from django.core.files import File from django.test import TestCase, override_settings from django.urls import reverse from django_yubin.models import Message from freezegun import freeze_time from furl import furl +from simple_certmanager.test.factories import CertificateFactory +from zgw_consumers.test.factories import ServiceFactory from openforms.config.models import GlobalConfiguration from openforms.contrib.brk.models import BRKConfig @@ -21,6 +25,8 @@ from ..tasks import send_email_digest +TEST_FILES = Path(__file__).parent / "data" + @patch( "openforms.emails.tasks.GlobalConfiguration.get_solo", @@ -100,10 +106,11 @@ def test_no_email_sent_if_no_recipients(self, mock_global_config): def test_email_sent_when_there_are_failures(self, mock_global_config, brk_config): """Integration test for all the possible failures - - failed_emails - - failed_registrations - - failed_prefill_plugins - - broken_configurations + - failed emails + - failed registrations + - failed prefill_plugins + - broken configurations + - invalid certificates """ form = FormFactory.create( generate_minimal_setup=True, @@ -146,6 +153,19 @@ def test_email_sent_when_there_are_failures(self, mock_global_config, brk_config submission, hc_plugin, ["burgerservicenummer"] ) + with ( + open(TEST_FILES / "test2.certificate", "r") as client_certificate_f, + open(TEST_FILES / "test.key", "r") as key_f, + ): + certificate = CertificateFactory.create( + label="Test certificate", + public_certificate=File( + client_certificate_f, name="test.certificate" + ), + private_key=File(key_f, name="test.key"), + ) + ServiceFactory.create(client_certificate=certificate) + # send the email digest send_email_digest() sent_email = mail.outbox[-1] @@ -193,3 +213,14 @@ def test_email_sent_when_there_are_failures(self, mock_global_config, brk_config "The configuration for 'BRK Client' is invalid (KVK endpoint is not configured).", sent_email.body, ) + + with self.subTest("invalid certificates"): + admin_certificate_url = furl( + reverse( + "admin:simple_certmanager_certificate_change", + kwargs={"object_id": certificate.id}, + ) + ) + + self.assertIn("Test certificate: has invalid keypair.", sent_email.body) + self.assertIn(admin_certificate_url.url, sent_email.body) diff --git a/test.key b/test.key new file mode 100644 index 0000000000..c38819a9b1 --- /dev/null +++ b/test.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +c3BAUqAcHiZPQdNtfLw9UcEkbfgcIaI= +-----END PRIVATE KEY-----