Skip to content

Commit

Permalink
Merge pull request #3490 from open-formulieren/586-suwinet-prefill
Browse files Browse the repository at this point in the history
586 suwinet prefill
  • Loading branch information
sergei-maertens authored Oct 27, 2023
2 parents 7ebc30c + 1b7acf4 commit d59cef6
Show file tree
Hide file tree
Showing 680 changed files with 29,489 additions and 32 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion docs/configuration/prefill/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Prefill plugins
:maxdepth: 1

bag
haal_centraal
kvk
stuf_bg
haal_centraal
suwinet
76 changes: 76 additions & 0 deletions docs/configuration/prefill/suwinet.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
.. _configuration_prefill_suwinet:

=======
Suwinet
=======

`Suwinet`_ is a collection of SOAP services connecting municipalities and several social
services. You can configure Open Forms to use these services to prefill data of the
authenticated person filling out the form.

.. note::

This service contains sensitive data and requires a connection to an
external system, offered by `BKWI`_.

.. _`Suwinet`: https://www.bkwi.nl/producten/suwinet-services/
.. _`BKWI`: https://www.bkwi.nl

Configuration
=============

The Open Forms Suwinet plugin supports Suwinet Access via a Gateway server. To configure
it, you need:

A client certificate key pair
the plugin uses this for the mTLS with the Gateway *and* to sign all the requests
to the services that require WSSE. Make sure the services you plan to use have this
public certificate —and possibly the chain— for validation.
A server certificate
for the mTLS connection to the Gateway
URLs of the binding addresses
for the services you plan to use in your forms


1. In Open Forms, navigate to: **Miscellaneous** > **Suwinet configuration**
2. Add a SOAP service
3. In the new SOAP service popup fill in these details:

- **Label**: *Fill in a human readable label*, for example: ``My Suwinet``
- **URL**: *Leave **empty***
- **SOAP version**: SOAP 1.2
- **Client certificate**: Add your certificate+key-pair and give it a recognisable label.
- **Server certificate**: Add the gateway certificate and give it a recognisable label.

4. For each service you plan to use in forms, fill in the URL on your Gateway where Open
Forms can reach it.

.. image:: _assets/suwinet_config.png


Technical
=========

Open Forms uses these versions of the WSDL descriptions of the SOAP services:

==================================================== =========
Service Version
==================================================== =========
BRPDossierPersoonGSD v0200-b02
Bijstandsregelingen v0500-b04
DUODossierPersoonGSD v0300-b01
DUODossierStudiefinancieringGSD v0200-b01
GSDDossierReintegratie v0200-b04
IBVerwijsindex v0300-b01
KadasterDossierGSD v0300-b02
RDWDossierDigitaleDiensten v0200-b01
RDWDossierGSD v0200-b02
SVBDossierPersoonGSD v0200-b01
UWVDossierAanvraagUitkeringStatusGSD v0200-b01
UWVDossierInkomstenGSDDigitaleDiensten v0200-b01
UWVDossierInkomstenGSD v0200-b02
UWVDossierQuotumArbeidsbeperktenGSD v0300-b01
UWVDossierWerknemersverzekeringenGSDDigitaleDiensten v0200-b01
UWVDossierWerknemersverzekeringenGSD v0200-b01F
UWVWbDossierPersoonGSD v0200-b01
==================================================== =========
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/manual/forms/examples/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Voorbeelden
dynamic_options_2
dynamic_options_3
service_fetch
suwinet

======================
Verouderde voorbeelden
Expand Down
95 changes: 95 additions & 0 deletions docs/manual/forms/examples/suwinet.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
.. _example_suwinet:

=================================
Formulier met waarden uit Suwinet
=================================

In dit voorbeeld verwerken we informatie die via :ref:`Suwinet
<configuration_prefill_suwinet>` uit het Kadaster komt tot keuzeopties voor keuzevakjes.
We maken daarvoor de `kadastrale aanduiding`_, die de een perceel uniek identificeert.

De Suwinet plugin gebruikt het BSN van de invuller om informatie op te halen, daarom is
het vereist om een :ref:`Authenticatie plugin <configuration_authentication_index>` te
gebruiken die een BSN aanbiedt.

We gaan ervan uit dat u een :ref:`formulier met geavanceerde logica
<example_advanced_logic>` kunt maken.

.. _kadastrale aanduiding: https://nl.wikipedia.org/wiki/Kadastrale_aanduiding

Formulier maken
===============

#. Maak een formulier aan met de volgende gegevens:

* **Naam**: Subsidieaanvraag duurzame energie en energiebesparing
* **Authenticatiemethode**: Een methode die BSN aanbiedt. Bijv. *Demo BSN(test)*

#. Klik op het tabblad **Stappen en velden**.
#. Klik aan de linkerkant op **Stap toevoegen** en selecteer **Maak een nieuwe
formulierdefinitie**.
#. Onder de sectie **(Herbruikbare) stapgegevens** vul het volgende in:

* **Naam**: Onderwerp aanvraag
* Vink **Vereist authenticatie** aan

#. Sleep een **Vrije tekst** component op het witte vlak en vul de volgende tekst in:

.. code-block:: django
U heeft deze percelen:
{% for perceel in onroerendeZaken.ClientSuwi.Eigendom.OnroerendeZaak %}
{{perceel.KadastraleAanduiding.KadastraleGemeentenaam}} {{perceel.KadastraleAanduiding.KadastraleSectie}} met perceelnummer: {{perceel.KadastraleAanduiding.KadastraalPerceelnr}}
{% endfor %}
.. image:: _assets/suwinet_free_text.png


#. Sleep een **Selectievakjes** component onder het vrije-tekst-component, vul de volgende
gegevens in en druk daarna op **Opslaan**:

* **Label**: Welke percelen gaat u verduurzamen?
* Scroll naar beneden en selecteer bij **Keuzeopties** ``variabele``
* Vul bij **Opties-expressie** het volgende in:

.. code-block:: json
{"map": [
{"var": "onroerendeZaken.ClientSuwi.Eigendom.OnroerendeZaak"},
{"cat": [
{"var": "KadastraleAanduiding.KadastraleGemeentenaam"},
" ",
{"var": "KadastraleAanduiding.KadastraleSectie"},
" ",
{"var": "KadastraleAanduiding.KadastraalPerceelnr"}
]}
]}
.. image:: _assets/suwinet_field.png

#. Klik op de **Variabelen** tab in het formuliermenu en vervolgens op de **Gebruikersvariabelen** tab

#. Klik op **Variabele toevoegen**
#. Voer bij **Naam** ``Onroerende Zaken``
#. Kies bij **Prefill-plugin** ``Suwinet``
#. Kies bij **Prefill-attribuut** ``KadasterDossierGSD > PersoonsInfo``
#. Kies bij **Datatype** ``Sleutel-waardepaar (object)``
#. Vink **Gevoelige gegevens** aan.

.. image:: _assets/suwinet_vars.png

De testdata van het Kadaster bevat voor BSN ``111111110`` zaken in Zwolle. Wanneer we
met dat BSN inloggen op het formulier zal het er zo uit zien:

.. image:: _assets/suwinet_form.png

.. note::
TIP: Je kunt een **Vrije tekst** component uit de **Opmaak** categorie gebruiken om de
structuur van het antwoord object in te zien::

Data:
{{onroerendeZaken}}

4 changes: 4 additions & 0 deletions dotenv.example
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ TWO_FACTOR_FORCE_OTP_ADMIN=no
# ELASTIC_APM_SECRET_TOKEN=
# ELASTIC_APM_SERVER_URL=
# DISABLE_APM_IN_DEV=no
#
# Recording Suwinet VCR cassettes
# SUWINET_CLIENT_KEY=/path/to/privatekey.pem
# SUWINET_BASE_URL=https://url/of/gateway/suwiml
6 changes: 6 additions & 0 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3467,6 +3467,12 @@ paths:
operationId: prefill_plugins_list
description: List all prefill plugins that have been registered.
summary: List available prefill plugins
parameters:
- in: query
name: componentType
schema:
type: string
description: Only return plugins applicable for the specified component type.
tags:
- prefill
security:
Expand Down
2 changes: 2 additions & 0 deletions src/openforms/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@
"simple_certmanager",
"zgw_consumers",
"soap",
"suwinet",
"stuf",
"stuf.stuf_bg",
"stuf.stuf_zds",
Expand Down Expand Up @@ -230,6 +231,7 @@
"openforms.prefill.contrib.kvk.apps.KVKPrefillApp",
"openforms.prefill.contrib.stufbg.apps.StufBgApp",
"openforms.prefill.contrib.haalcentraal_brp.apps.HaalCentraalBRPApp",
"openforms.prefill.contrib.suwinet.apps.SuwinetApp",
"openforms.authentication",
"openforms.authentication.contrib.demo.apps.DemoApp",
"openforms.authentication.contrib.outage.apps.DemoOutageApp",
Expand Down
1 change: 1 addition & 0 deletions src/openforms/fixtures/admin_index_unlisted.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"stuf.StufService",
"stuf_bg.StufBGConfig",
"stuf_zds.StufZDSConfig",
"suwinet.SuwinetConfig",
"upgrades.VersionInfo",
"zgw_apis.ZGWApiGroupConfig",
"zgw_apis.ZgwConfig"
Expand Down
1 change: 1 addition & 0 deletions src/openforms/js/components/form/edit/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ const PREFILL = {
},
valueProperty: 'id',
template: '<span>{{ item.label }}</span>',
filter: 'componentType={{ data.type }}',
},
{
type: 'select',
Expand Down
5 changes: 4 additions & 1 deletion src/openforms/js/components/formio_builder/plugins.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {get} from 'utils/fetch';

export const getValidatorPlugins = async componentType => {
const resp = await get('/api/v2/validation/plugins');
const resp = await get(
'/api/v2/validation/plugins',
componentType ? {componentType: componentType} : {}
);
return resp.data;
};

Expand Down
28 changes: 28 additions & 0 deletions src/openforms/prefill/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

from django.utils.translation import gettext_lazy as _

from djangorestframework_camel_case.util import underscoreize
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
from rest_framework import serializers

from openforms.api.utils import underscore_to_camel
from openforms.plugins.api.serializers import PluginBaseSerializer


Expand All @@ -17,6 +21,30 @@ class PrefillPluginSerializer(PluginBaseSerializer):
)


class PrefillPluginQueryParameterSerializer(serializers.Serializer):
component_type = serializers.CharField(
required=False,
label=_("Form.io component type"),
help_text=_("Only return plugins applicable for the specified component type."),
)

def to_internal_value(self, data):
return super().to_internal_value(underscoreize(data))

@classmethod
def as_openapi_params(cls) -> list[OpenApiParameter]:
# FIXME: See openforms.validators.api.serializers.ValidatorsFilterSerializer
instance = cls()
ct_field = instance.fields["component_type"]
return [
OpenApiParameter(
underscore_to_camel(str(ct_field.field_name)),
OpenApiTypes.STR,
description=str(ct_field.help_text),
)
]


@dataclass
class ChoiceWrapper:
choice: tuple
Expand Down
55 changes: 54 additions & 1 deletion src/openforms/prefill/api/tests/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,26 @@ def get_available_attributes(self):
register("test")(TestPrefill)


@register("onlyvars")
class OnlyVarsPrefill(BasePlugin):
requires_auth = AuthAttribute.bsn
verbose_name = "Only Vars"
for_components = ()

def get_available_attributes(self):
return [("foo", "Foo"), ("bar", "Bar")]


@register("vanityplates")
class VanityPlatePrefill(BasePlugin):
requires_auth = AuthAttribute.bsn
verbose_name = "Vanity Plates"
for_components = {"licenseplate"}

def get_available_attributes(self):
return [("NOMODES", "Larry Tesler's plates")]


class AuthTests(APITestCase):
endpoints = [
reverse_lazy("api:prefill-plugin-list"),
Expand Down Expand Up @@ -92,7 +112,40 @@ def test_prefill_list(self):
"id": "test",
"label": "Test",
"requiresAuth": AuthAttribute.bsn,
}
},
{
"id": "onlyvars",
"label": "Only Vars",
"requiresAuth": AuthAttribute.bsn,
},
{
"id": "vanityplates",
"label": "Vanity Plates",
"requiresAuth": AuthAttribute.bsn,
},
]

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.json(), expected)

def test_prefill_list_for_component_type(self):
endpoint = reverse("api:prefill-plugin-list")

response = self.client.get(endpoint, {"componentType": "licenseplate"})

expected = [
# unspecified component set
{
"id": "test",
"label": "Test",
"requiresAuth": AuthAttribute.bsn,
},
# spec'd for licenseplate
{
"id": "vanityplates",
"label": "Vanity Plates",
"requiresAuth": AuthAttribute.bsn,
},
]

self.assertEqual(response.status_code, status.HTTP_200_OK)
Expand Down
Loading

0 comments on commit d59cef6

Please sign in to comment.