Skip to content

Commit

Permalink
[#4825] Fixed prefill when authentication is needed
Browse files Browse the repository at this point in the history
In case a form needs authentication and uses prefill, we need to make
sure that we run the plugin only when the authentication type matches
the plugin's requirements.
  • Loading branch information
vaszig committed Jan 10, 2025
1 parent e2f5b43 commit 56f8a27
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const PluginWarning = ({loginRequired, configuration}) => {
const authPlugin = availableAuthPlugins.find(plugin => plugin.id === pluginName);
if (!authPlugin) break;

if (authPlugin.providesAuth.includes(requiredAuthAttribute)) {
if (requiredAuthAttribute.includes(authPlugin.providesAuth)) {
pluginProvidesAttribute = true;
break;
}
Expand All @@ -46,12 +46,12 @@ const PluginWarning = ({loginRequired, configuration}) => {
<FormattedMessage
description="Prefill plugin requires unavailable auth attribute warning"
defaultMessage={
'Component "{label}" uses a prefill that requires the "{requiredAuthAttribute}" attribute. \
Please select an authentication plugin that provides this attribute.'
'Component "{label}" uses a prefill that requires the "{requiredAuthAttribute}" attributes. \
Please select an authentication plugin that provides these attributes.'
}
values={{
label: configuration.label,
requiredAuthAttribute,
requiredAuthAttribute: requiredAuthAttribute.join(', '),
}}
/>
);
Expand Down
13 changes: 8 additions & 5 deletions src/openforms/prefill/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@


class PrefillPluginSerializer(PluginBaseSerializer):
requires_auth = serializers.CharField(
label=_("Required authentication attribute"),
help_text=_(
"The authentication attribute required for this plugin to lookup remote data."
requires_auth = serializers.ListField(
child=serializers.CharField(
label=_("Required authentication attribute"),
help_text=_(
"The authentication attribute required for this plugin to lookup remote data."
),
allow_null=True,
),
allow_null=True,
default=list,
)
configuration_context = serializers.JSONField(
label=_("Extra configuration context"),
Expand Down
16 changes: 8 additions & 8 deletions src/openforms/prefill/api/tests/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


class TestPrefill(BasePlugin):
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)
verbose_name = "Test"

def get_available_attributes(self):
Expand All @@ -26,7 +26,7 @@ def get_available_attributes(self):

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

Expand All @@ -36,7 +36,7 @@ def get_available_attributes(self):

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

Expand Down Expand Up @@ -111,19 +111,19 @@ def test_prefill_list(self):
{
"id": "test",
"label": "Test",
"requiresAuth": AuthAttribute.bsn,
"requiresAuth": (AuthAttribute.bsn,),
"configurationContext": None,
},
{
"id": "onlyvars",
"label": "Only Vars",
"requiresAuth": AuthAttribute.bsn,
"requiresAuth": (AuthAttribute.bsn,),
"configurationContext": None,
},
{
"id": "vanityplates",
"label": "Vanity Plates",
"requiresAuth": AuthAttribute.bsn,
"requiresAuth": (AuthAttribute.bsn,),
"configurationContext": None,
},
]
Expand All @@ -141,14 +141,14 @@ def test_prefill_list_for_component_type(self):
{
"id": "test",
"label": "Test",
"requiresAuth": AuthAttribute.bsn,
"requiresAuth": (AuthAttribute.bsn,),
"configurationContext": None,
},
# spec'd for licenseplate
{
"id": "vanityplates",
"label": "Vanity Plates",
"requiresAuth": AuthAttribute.bsn,
"requiresAuth": (AuthAttribute.bsn,),
"configurationContext": None,
},
]
Expand Down
6 changes: 4 additions & 2 deletions src/openforms/prefill/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from collections.abc import Collection
from typing import Any, Container, Iterable, TypedDict

from rest_framework import serializers
Expand Down Expand Up @@ -29,7 +30,7 @@ class Options(TypedDict):


class BasePlugin[OptionsT: Options](AbstractBasePlugin):
requires_auth: AuthAttribute | None = None
requires_auth: Collection[AuthAttribute] | None = None
for_components: Container[str] = AllComponentTypes()
options: SerializerCls = EmptyOptions

Expand Down Expand Up @@ -139,7 +140,8 @@ def get_identifier_value(

if (
identifier_role == IdentifierRoles.main
and submission.auth_info.attribute == cls.requires_auth
and cls.requires_auth
and submission.auth_info.attribute in cls.requires_auth
):
return submission.auth_info.value

Expand Down
18 changes: 13 additions & 5 deletions src/openforms/prefill/co_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""

import logging
from collections.abc import Collection

from openforms.authentication.service import AuthAttribute
from openforms.submissions.cosigning import CosignV1Data
Expand All @@ -26,11 +27,18 @@


def get_default_plugin_for_auth_attribute(
auth_attribute: AuthAttribute | None,
auth_attribute: Collection[AuthAttribute] | None,
) -> str | None:
if not auth_attribute or not (
config_field := AUTH_ATTRIBUTE_TO_CONFIG_FIELD.get(auth_attribute)
):
if not auth_attribute:
logger.info("No auth_attribute provided")
return

config_field = ""
for attribute in auth_attribute:
if AUTH_ATTRIBUTE_TO_CONFIG_FIELD.get(attribute):
config_field = AUTH_ATTRIBUTE_TO_CONFIG_FIELD[attribute]
break

logger.info("Unsupported auth_attribute '%s'", auth_attribute)
return

Expand All @@ -44,7 +52,7 @@ def get_default_plugin_for_auth_attribute(


def add_co_sign_representation(
submission: Submission, auth_attribute: AuthAttribute | None
submission: Submission, auth_attribute: Collection[AuthAttribute] | None
):
default_plugin = get_default_plugin_for_auth_attribute(auth_attribute)
# configuration may be incomplete, do nothing in that case!
Expand Down
5 changes: 3 additions & 2 deletions src/openforms/prefill/contrib/haalcentraal_brp/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def get_attributes_cls():
@register(PLUGIN_IDENTIFIER)
class HaalCentraalPrefill(BasePlugin):
verbose_name = _("Haal Centraal: BRP Personen Bevragen")
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)

@staticmethod
def get_available_attributes() -> list[tuple[str, str]]:
Expand Down Expand Up @@ -84,7 +84,8 @@ def get_identifier_value(

if (
identifier_role == IdentifierRoles.main
and submission.auth_info.attribute == cls.requires_auth
and cls.requires_auth
and submission.auth_info.attribute in cls.requires_auth
):
return submission.auth_info.value

Expand Down
5 changes: 3 additions & 2 deletions src/openforms/prefill/contrib/kvk/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def _select_address(items, type_):
class KVK_KVKNumberPrefill(BasePlugin):
verbose_name = _("KvK Company by KvK number")

requires_auth = AuthAttribute.kvk
requires_auth = (AuthAttribute.kvk,)

@staticmethod
def get_available_attributes() -> list[tuple[str, str]]:
Expand All @@ -50,7 +50,8 @@ def get_identifier_value(

if (
identifier_role == IdentifierRoles.main
and submission.auth_info.attribute == cls.requires_auth
and cls.requires_auth
and submission.auth_info.attribute in cls.requires_auth
):
return submission.auth_info.value

Expand Down
5 changes: 3 additions & 2 deletions src/openforms/prefill/contrib/stufbg/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
@register("stufbg")
class StufBgPrefill(BasePlugin):
verbose_name = _("StUF-BG")
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)

@staticmethod
def get_available_attributes() -> list[tuple[str, str]]:
Expand Down Expand Up @@ -138,7 +138,8 @@ def get_identifier_value(

if (
identifier_role == IdentifierRoles.main
and submission.auth_info.attribute == cls.requires_auth
and cls.requires_auth
and submission.auth_info.attribute in cls.requires_auth
):
return submission.auth_info.value

Expand Down
2 changes: 1 addition & 1 deletion src/openforms/prefill/contrib/suwinet/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def _get_client() -> SuwinetClient | None:

@register("suwinet")
class SuwinetPrefill(BasePlugin):
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)
verbose_name = _("Suwinet")
for_components = ()

Expand Down
2 changes: 1 addition & 1 deletion src/openforms/prefill/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def formfield(self, **kwargs):
def _get_plugin_choices(self):
choices = []
for plugin in register.iter_enabled_plugins():
if plugin.requires_auth != self.auth_attribute:
if plugin.requires_auth and self.auth_attribute not in plugin.requires_auth:
continue
choices.append((plugin.identifier, plugin.get_label()))
return choices
23 changes: 14 additions & 9 deletions src/openforms/prefill/sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)
from openforms.typing import JSONEncodable

from .base import BasePlugin
from .constants import IdentifierRoles
from .registry import Registry

Expand Down Expand Up @@ -51,10 +52,9 @@ def fetch_prefill_values_from_attribute(

@elasticapm.capture_span(span_type="app.prefill")
def invoke_plugin(
item: tuple[str, IdentifierRoles, list[dict[str, str]]]
item: tuple[BasePlugin, IdentifierRoles, list[dict[str, str]]]
) -> tuple[list[dict[str, str]], dict[str, JSONEncodable]]:
plugin_id, identifier_role, fields = item
plugin = register[plugin_id]
plugin, identifier_role, fields = item

if not plugin.is_enabled:
raise PluginNotEnabled()
Expand All @@ -70,17 +70,22 @@ def invoke_plugin(
if values:
logevent.prefill_retrieve_success(submission, plugin, fields)
else:
if (required_attribute := plugin.requires_auth) is None or (
(auth_info := getattr(submission, "auth_info", None))
and auth_info.attribute == required_attribute
):
logevent.prefill_retrieve_empty(submission, plugin, fields)
logevent.prefill_retrieve_empty(submission, plugin, fields)
return fields, values

invoke_plugin_args = []
for plugin_id, field_groups in grouped_fields.items():
plugin = register[plugin_id]

# check if we need to run the plugin when the user is authenticated
if (required_attribute := plugin.requires_auth) is not None and (
(auth_info := getattr(submission, "auth_info", None))
and auth_info not in required_attribute
):
continue

for identifier_role, fields in field_groups.items():
invoke_plugin_args.append((plugin_id, identifier_role, fields))
invoke_plugin_args.append((plugin, identifier_role, fields))

with parallel() as executor:
results = executor.map(invoke_plugin, invoke_plugin_args)
Expand Down
4 changes: 2 additions & 2 deletions src/openforms/prefill/tests/test_prefill_hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@ def test_demo_prefill_get_identifier_not_authenticated(self):

def test_demo_prefill_get_identifier_authenticated(self):
class TestPlugin(BasePlugin):
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)

plugin = TestPlugin(identifier="test")

Expand Down Expand Up @@ -646,7 +646,7 @@ def test_prefill_logging_with_mismatching_login_method(self):

@register("demo")
class MismatchPlugin(DemoPrefill):
requires_auth = AuthAttribute.bsn
requires_auth = (AuthAttribute.bsn,)

@staticmethod
def get_prefill_values(submission, attributes, identifier_role):
Expand Down

0 comments on commit 56f8a27

Please sign in to comment.