Skip to content

Commit

Permalink
Merge pull request #3514 from open-formulieren/task/3477-set-dynamic-…
Browse files Browse the repository at this point in the history
…form-action-csp-directive

[#3477] Add dynamic form-action csp directive
  • Loading branch information
sergei-maertens authored Oct 26, 2023
2 parents 1a33fad + a378c38 commit 66d9bee
Show file tree
Hide file tree
Showing 21 changed files with 690 additions and 8 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ Changelog

.. note:: These release notes are in development!

**DigiD - Eherkenning Configuration**

The configuration concerning the digid and eherkenning has been updated according to the
new version (0.9) of the django-digid-eherkenning library. The XML metadata file is now
automatically retrieved so you have to edit the configuration via:
**Admin** > **Configuratie** > **DigiD-configuratie**
**Admin** > **Configuratie** > **EHerkenning/eIDAS-configuratie**
and add the XML metadata url. The identity provider and the metadata file fields will be
automatically populated if the url is valid.

Upgrade procedure
-----------------

Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ django-csp-reports==1.8.1
# via -r requirements/base.in
django-decorator-include==3.0
# via -r requirements/base.in
django-digid-eherkenning==0.8.2
django-digid-eherkenning==0.9.0
# via -r requirements/base.in
django-filter==23.2
# via -r requirements/base.in
Expand Down
2 changes: 1 addition & 1 deletion requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ django-decorator-include==3.0
# via
# -c requirements/base.txt
# -r requirements/base.txt
django-digid-eherkenning==0.8.2
django-digid-eherkenning==0.9.0
# via
# -c requirements/base.txt
# -r requirements/base.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ django-decorator-include==3.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
django-digid-eherkenning==0.8.2
django-digid-eherkenning==0.9.0
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/extensions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ django-decorator-include==3.0
# via
# -c requirements/base.in
# -r requirements/base.txt
django-digid-eherkenning==0.8.2
django-digid-eherkenning==0.9.0
# via
# -c requirements/base.in
# -r requirements/base.txt
Expand Down
3 changes: 3 additions & 0 deletions src/openforms/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
"openforms.logging.apps.LoggingAppConfig",
"openforms.contrib.bag.apps.BAGConfig", # TODO: remove once 2.4.0 is released
"openforms.contrib.brp",
"openforms.contrib.digid_eherkenning",
"openforms.contrib.haal_centraal",
"openforms.contrib.kadaster",
"openforms.contrib.kvk",
Expand Down Expand Up @@ -1099,6 +1100,8 @@
"'self'",
] + config("CSP_EXTRA_DEFAULT_SRC", default=[], split=True)

CSP_FORM_ACTION = ("'self'",)

# * service.pdok.nl serves the tiles for the Leaflet maps (PNGs) and must be whitelisted
# * the data: URIs are used by Leaflet (invisible pixel for memory management/image unloading)
# and the signature component which saves the image drawn on the canvas as data: URI
Expand Down
12 changes: 12 additions & 0 deletions src/openforms/config/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib import admin
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import ugettext_lazy as _

from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin
Expand Down Expand Up @@ -182,9 +184,11 @@ class RichTextColorAdmin(admin.ModelAdmin):

@admin.register(CSPSetting)
class CSPSettingAdmin(admin.ModelAdmin):
readonly_fields = ("content_type_link",)
fields = [
"directive",
"value",
"content_type_link",
]
list_display = [
"directive",
Expand All @@ -197,3 +201,11 @@ class CSPSettingAdmin(admin.ModelAdmin):
"directive",
"value",
]

def content_type_link(self, obj):
ct = obj.content_type
url = reverse(f"admin:{ct.app_label}_{ct.model}_change", args=(obj.object_id,))
link = format_html('<a href="{u}">{t}</a>', u=url, t=str(obj.content_object))
return link

content_type_link.short_description = _("Content type")
38 changes: 38 additions & 0 deletions src/openforms/config/migrations/0058_auto_20231026_1525.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Generated by Django 3.2.21 on 2023-10-26 13:25

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("config", "0057_globalconfiguration_recipients_email_digest"),
]

operations = [
migrations.AddField(
model_name="cspsetting",
name="content_type",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="contenttypes.contenttype",
verbose_name="content type",
),
),
migrations.AddField(
model_name="cspsetting",
name="object_id",
field=models.TextField(blank=True, db_index=True, verbose_name="object id"),
),
migrations.AlterField(
model_name="cspsetting",
name="value",
field=models.CharField(
help_text="CSP header value", max_length=255, verbose_name="value"
),
),
]
45 changes: 42 additions & 3 deletions src/openforms/config/models.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import logging
from collections import defaultdict
from functools import partial

from django.contrib.admin.options import get_content_type_for_model
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.core.validators import (
FileExtensionValidator,
MaxValueValidator,
MinValueValidator,
RegexValidator,
)
from django.db import models
from django.db import models, transaction
from django.template.loader import render_to_string
from django.utils.encoding import force_str
from django.utils.safestring import mark_safe
Expand All @@ -32,6 +36,8 @@
from .constants import CSPDirective, UploadFileType
from .utils import verify_clamav_connection

logger = logging.getLogger(__name__)


@ensure_default_language()
def _render(filename):
Expand Down Expand Up @@ -649,6 +655,26 @@ def as_dict(self):
return {k: list(v) for k, v in ret.items()}


class CSPSettingManager(models.Manager.from_queryset(CSPSettingQuerySet)):
@transaction.atomic
def set_for(
self, obj: models.Model, settings: list[tuple[CSPDirective, str]]
) -> None:
"""
Deletes all the connected csp settings and creates new ones based on the new provided data.
"""
instances = [
CSPSetting(content_object=obj, directive=directive, value=value)
for directive, value in settings
]

CSPSetting.objects.filter(
content_type=get_content_type_for_model(obj), object_id=str(obj.id)
).delete()

self.bulk_create(instances)


class CSPSetting(models.Model):
directive = models.CharField(
_("directive"),
Expand All @@ -658,11 +684,24 @@ class CSPSetting(models.Model):
)
value = models.CharField(
_("value"),
max_length=128,
max_length=255,
help_text=_("CSP header value"),
)
content_type = models.ForeignKey(
ContentType,
verbose_name=_("content type"),
on_delete=models.SET_NULL,
blank=True,
null=True,
)
object_id = models.TextField(
verbose_name=_("object id"),
blank=True,
db_index=True,
)
content_object = GenericForeignKey("content_type", "object_id")

objects = CSPSettingQuerySet.as_manager()
objects = CSPSettingManager()

class Meta:
ordering = ("directive", "value")
Expand Down
28 changes: 28 additions & 0 deletions src/openforms/config/tests/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from django.contrib.admin.sites import AdminSite
from django.test import TestCase
from django.urls import reverse

from openforms.config.models import CSPSetting
from openforms.payments.contrib.ogone.tests.factories import OgoneMerchantFactory

from ..admin import CSPSettingAdmin


class TestCSPAdmin(TestCase):
def test_content_type_link(self):
OgoneMerchantFactory()

csp = CSPSetting.objects.get()

admin_site = AdminSite()
admin = CSPSettingAdmin(CSPSetting, admin_site)

expected_url = reverse(
"admin:payments_ogone_ogonemerchant_change",
kwargs={"object_id": str(csp.object_id)},
)
expected_link = f'<a href="{expected_url}">{str(csp.content_object)}</a>'

link = admin.content_type_link(csp)

self.assertEqual(link, expected_link)
12 changes: 12 additions & 0 deletions src/openforms/contrib/digid_eherkenning/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _


class DigidEherkenningApp(AppConfig):
name = "openforms.contrib.digid_eherkenning"
label = "contrib_digid_eherkenning"
verbose_name = _("DigiD/Eherkenning utilities")

def ready(self):
# register the signals
from .signals import trigger_csp_update # noqa
6 changes: 6 additions & 0 deletions src/openforms/contrib/digid_eherkenning/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from digid_eherkenning.models import DigidConfiguration, EherkenningConfiguration

ADDITIONAL_CSP_VALUES = {
DigidConfiguration: "https://digid.nl https://*.digid.nl",
EherkenningConfiguration: "",
}
14 changes: 14 additions & 0 deletions src/openforms/contrib/digid_eherkenning/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from django.db.models.signals import post_save
from django.dispatch import receiver

from digid_eherkenning.models import DigidConfiguration, EherkenningConfiguration

from .utils import create_digid_eherkenning_csp_settings


@receiver(post_save, sender=DigidConfiguration)
@receiver(post_save, sender=EherkenningConfiguration)
def trigger_csp_update(
sender, instance: DigidConfiguration | EherkenningConfiguration, **kwargs
):
create_digid_eherkenning_csp_settings(instance)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ns0="urn:etoegang:1.13:metadata-extension" ID="ONELOGIN_123456" entityID="urn:etoegang:DV:00000001111111111000:entities:9000" ns0:version="1.13"><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
<ds:Reference URI="#_60dad314-6aaf-4567-9434-2f66e5e79aa1">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
<ds:DigestValue>mTSuC+50WptA9sO0wg/eFIyQtWofMlkbEYm5Zoj/GHo=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue> </ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<md:Extensions>
<mdrpi:PublicationInfo xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi" creationInstant="2020-09-23T13:48:27Z" publisher="https://eh01.connectis.nl/metadata/iwelcome/prod/urn_etoegang_HM_00000003520354760000_entities_1135_1.13.xml"/>
<attr:EntityAttributes xmlns:attr="urn:oasis:names:tc:SAML:metadata:attribute">
<saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:oasis:names:tc:SAML:attribute:assurance-certification" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>urn:etoegang:core:assurance-class:loa4</saml:AttributeValue>
</saml:Attribute>
</attr:EntityAttributes>
</md:Extensions>
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>Test key 0</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>Test key 1</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://test-iwelcome.nl/broker/ars/1.13" index="0"/>
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://test-iwelcome.nl/broker/ars/1.13" index="1"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://ehm01.iwelcome.nl/broker/slo/1.13"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://ehm01.iwelcome.nl/broker/slo/1.13"/>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://ehm01.iwelcome.nl/broker/slo/1.13"/>
<md:NameIDFormat>urn:etoegang:1.9:EntityConcernedID:KvKnr</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.9:IntermediateEntityID:KvKnr</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.9:EntityConcernedID:Pseudo</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.9:EntityConcernedID:RSIN</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.9:IntermediateEntityID:RSIN</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.11:EntityConcernedID:eIDASLegalIdentifier</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.12:EntityConcernedID:BSN</md:NameIDFormat>
<md:NameIDFormat>urn:etoegang:1.12:EntityConcernedID:PseudoID</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://test-iwelcome.nl/broker/sso/1.13"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://test-iwelcome.nl/broker/sso/1.13"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://test-iwelcome.nl/broker/sso/1.13"/>
</md:IDPSSODescriptor>
<md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>Test key 2</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:KeyName>Test key 3</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate> </ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://test-iwelcome.nl/broker/ars/1.13" index="0"/>
<md:ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://test-iwelcome.nl/broker/ars/1.13" index="1"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://test-iwelcome.nl/broker/acs/1.13" index="1" isDefault="true"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://test-iwelcome.nl/broker/acs/1.13" index="2" isDefault="false"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://test-iwelcome.nl/broker/acs/1.13" index="3" isDefault="false"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://test-iwelcome.nl/broker/acs/1.13" index="4" isDefault="false"/>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://test-iwelcome.nl/broker/acs/1.13" index="5" isDefault="false"/>
</md:SPSSODescriptor>
<md:Organization>
<md:OrganizationName xml:lang="en">Test-iWelcome</md:OrganizationName>
<md:OrganizationDisplayName xml:lang="en">Test-iWelcome</md:OrganizationDisplayName>
<md:OrganizationURL xml:lang="en">www.test-iwelcome.nl</md:OrganizationURL>
</md:Organization>
<md:ContactPerson contactType="administrative">
<md:EmailAddress>support@test-iwelcome.nl</md:EmailAddress>
<md:TelephoneNumber>000 222 111</md:TelephoneNumber>
</md:ContactPerson>
</md:EntityDescriptor>
Loading

0 comments on commit 66d9bee

Please sign in to comment.