diff --git a/src/open_inwoner/accounts/signals.py b/src/open_inwoner/accounts/signals.py index 1e95fae0bd..c93f6ab526 100644 --- a/src/open_inwoner/accounts/signals.py +++ b/src/open_inwoner/accounts/signals.py @@ -47,6 +47,7 @@ def log_user_login(sender, user, request, *args, **kwargs): if brp_config.service: update_brp_data_in_db(user, initial=False) + if user.login_type in [LoginTypeChoices.digid, LoginTypeChoices.eherkenning]: if oc_config.klanten_service: update_user_from_klant(user) diff --git a/src/open_inwoner/accounts/tests/test_profile_views.py b/src/open_inwoner/accounts/tests/test_profile_views.py index 7696228e0a..223134204c 100644 --- a/src/open_inwoner/accounts/tests/test_profile_views.py +++ b/src/open_inwoner/accounts/tests/test_profile_views.py @@ -18,6 +18,7 @@ from open_inwoner.accounts.choices import StatusChoices from open_inwoner.cms.profile.cms_appconfig import ProfileConfig from open_inwoner.haalcentraal.tests.mixins import HaalCentraalMixin +from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.pdc.tests.factories import CategoryFactory from open_inwoner.plans.tests.factories import PlanFactory from open_inwoner.utils.logentry import LOG_ACTIONS @@ -281,7 +282,9 @@ def test_messages_enabled_disabled(self): self.assertEqual(link_text(), _("Stuur een bericht")) -@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") +@override_settings( + ROOT_URLCONF="open_inwoner.cms.tests.urls", MIDDLEWARE=PATCHED_MIDDLEWARE +) class EditProfileTests(AssertTimelineLogMixin, WebTest): def setUp(self): self.url = reverse("profile:edit") @@ -530,11 +533,59 @@ def test_modify_phone_and_email_updates_klant_api(self, m): "telefoonnummer": "0612345678", }, ) - self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog("retrieved klant for user") self.assertTimelineLog( "patched klant from user profile edit with fields: emailadres, telefoonnummer" ) + @requests_mock.Mocker() + def test_eherkenning_user_updates_klant_api(self, m): + MockAPIReadPatchData.setUpServices() + + for use_rsin_for_innNnpId_query_parameter in [True, False]: + with self.subTest( + use_rsin_for_innNnpId_query_parameter=use_rsin_for_innNnpId_query_parameter + ): + # NOTE Explicitly creating a new Mocker object here, because for some reason + # `m` is overridden somewhere, which causes issues when `MockAPIReadPatchData.install_mocks` + # is run for the second time + with requests_mock.Mocker() as m: + data = MockAPIReadPatchData().install_mocks_eherkenning( + m, use_rsin=use_rsin_for_innNnpId_query_parameter + ) + + config = OpenKlantConfig.get_solo() + config.use_rsin_for_innNnpId_query_parameter = ( + use_rsin_for_innNnpId_query_parameter + ) + config.save() + + response = self.app.get(self.url, user=data.eherkenning_user) + + # reset noise from signals + m.reset_mock() + self.clearTimelineLogs() + + form = response.forms["profile-edit"] + form["email"] = "new@example.com" + form["phonenumber"] = "0612345678" + form.submit() + + # user data tested in other cases + self.assertTrue(data.matchers[0].called) + klant_patch_data = data.matchers[1].request_history[0].json() + self.assertEqual( + klant_patch_data, + { + "emailadres": "new@example.com", + "telefoonnummer": "0612345678", + }, + ) + self.assertTimelineLog("retrieved klant for user") + self.assertTimelineLog( + "patched klant from user profile edit with fields: emailadres, telefoonnummer" + ) + @requests_mock.Mocker() def test_modify_phone_updates_klant_api_but_skips_unchanged(self, m): MockAPIReadPatchData.setUpServices() @@ -579,7 +630,7 @@ def test_modify_phone_updates_klant_api_but_skip_unchanged_email(self, m): "telefoonnummer": "0612345678", }, ) - self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog("retrieved klant for user") self.assertTimelineLog( "patched klant from user profile edit with fields: telefoonnummer" ) @@ -609,7 +660,7 @@ def test_modify_phone_updates_klant_api_but_skip_unchanged_phone(self, m): "emailadres": "new@example.com", }, ) - self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog("retrieved klant for user") self.assertTimelineLog( "patched klant from user profile edit with fields: emailadres" ) diff --git a/src/open_inwoner/accounts/views/profile.py b/src/open_inwoner/accounts/views/profile.py index c5623d8b01..4e889a311b 100644 --- a/src/open_inwoner/accounts/views/profile.py +++ b/src/open_inwoner/accounts/views/profile.py @@ -27,6 +27,7 @@ inbox_page_is_published, ) from open_inwoner.haalcentraal.utils import fetch_brp +from open_inwoner.openklant.wrap import get_fetch_parameters from open_inwoner.plans.models import Plan from open_inwoner.questionnaire.models import QuestionnaireStep from open_inwoner.utils.mixins import ExportMixin @@ -188,8 +189,9 @@ def form_valid(self, form): def update_klant_api(self, user_form_data: dict): user: User = self.request.user - if not user.bsn or user.login_type != LoginTypeChoices.digid: + if not user.bsn and not user.kvk: return + field_mapping = { "emailadres": "email", "telefoonnummer": "phonenumber", @@ -200,10 +202,11 @@ def update_klant_api(self, user_form_data: dict): if user_form_data.get(local_name) } if update_data: - klant = fetch_klant(user_bsn=user.bsn) + klant = fetch_klant(**get_fetch_parameters(user)) + if klant: self.log_system_action( - "retrieved klant for BSN-user", user=self.request.user + "retrieved klant for user", user=self.request.user ) klant = patch_klant(klant, update_data) if klant: diff --git a/src/open_inwoner/cms/cases/tests/test_contactform.py b/src/open_inwoner/cms/cases/tests/test_contactform.py index 4c66aaaa9b..926ec16b40 100644 --- a/src/open_inwoner/cms/cases/tests/test_contactform.py +++ b/src/open_inwoner/cms/cases/tests/test_contactform.py @@ -1,3 +1,4 @@ +from django.conf import settings from django.core import mail from django.test import override_settings from django.urls import reverse @@ -13,7 +14,10 @@ from zgw_consumers.constants import APITypes from zgw_consumers.test import generate_oas_component, mock_service_oas_get -from open_inwoner.accounts.tests.factories import DigidUserFactory +from open_inwoner.accounts.tests.factories import ( + DigidUserFactory, + eHerkenningUserFactory, +) from open_inwoner.openklant.constants import Status from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.openklant.tests.data import ( @@ -31,9 +35,17 @@ from open_inwoner.utils.test import ClearCachesMixin, paginated_response from open_inwoner.utils.tests.helpers import AssertMockMatchersMixin +PATCHED_MIDDLEWARE = [ + m + for m in settings.MIDDLEWARE + if m != "open_inwoner.kvk.middleware.KvKLoginMiddleware" +] + @requests_mock.Mocker() -@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") +@override_settings( + ROOT_URLCONF="open_inwoner.cms.tests.urls", MIDDLEWARE=PATCHED_MIDDLEWARE +) class CasesContactFormTestCase(AssertMockMatchersMixin, ClearCachesMixin, WebTest): def setUp(self): super().setUp() @@ -102,6 +114,32 @@ def setUp(self): "geslachtsnaam": "Bazz", }, ) + self.eherkenning_user_role = generate_oas_component( + "zrc", + "schemas/Rol", + url=f"{ZAKEN_ROOT}rollen/3ff7686f-db35-4181-8e48-57521220f887", + omschrijvingGeneriek=RolOmschrijving.initiator, + betrokkeneType=RolTypes.niet_natuurlijk_persoon, + betrokkeneIdentificatie={ + "innNnpId": "000000000", + "voornamen": "Foo Bar", + "voorvoegselGeslachtsnaam": "van der", + "geslachtsnaam": "Bazz", + }, + ) + self.eherkenning_user_role_kvk = generate_oas_component( + "zrc", + "schemas/Rol", + url=f"{ZAKEN_ROOT}rollen/5885531e-9b7f-46af-947e-f2278a2e72a8", + omschrijvingGeneriek=RolOmschrijving.initiator, + betrokkeneType=RolTypes.niet_natuurlijk_persoon, + betrokkeneIdentificatie={ + "innNnpId": "12345678", + "voornamen": "Foo Bar", + "voorvoegselGeslachtsnaam": "van der", + "geslachtsnaam": "Bazz", + }, + ) self.zaaktype = generate_oas_component( "ztc", "schemas/ZaakType", @@ -221,18 +259,10 @@ def _setUpMocks(self, m): self.zaaktype, self.status_finish, self.status_type_finish, - ]: - self.matchers.append(m.get(resource["url"], json=resource)) - - for resource in [ - self.zaak, - self.result, - self.zaaktype, self.status_type_new, self.status_type_in_behandeling, - self.status_type_finish, ]: - m.get(resource["url"], json=resource) + self.matchers.append(m.get(resource["url"], json=resource)) # mock `fetch_status_types_no_cache` m.get( @@ -243,7 +273,13 @@ def _setUpMocks(self, m): self.matchers += [ m.get( f"{ZAKEN_ROOT}rollen?zaak={self.zaak['url']}", - json=paginated_response([self.user_role]), + json=paginated_response( + [ + self.user_role, + self.eherkenning_user_role, + self.eherkenning_user_role_kvk, + ] + ), ), m.get( f"{ZAKEN_ROOT}zaakinformatieobjecten?zaak={self.zaak['url']}", @@ -407,6 +443,71 @@ def test_form_success_with_api(self, m): }, ) + def test_form_success_with_api_eherkenning_user(self, m): + self._setUpMocks(m) + self._setUpExtraMocks(m) + + for use_rsin_for_innNnpId_query_parameter in [True, False]: + with self.subTest( + use_rsin_for_innNnpId_query_parameter=use_rsin_for_innNnpId_query_parameter + ): + eherkenning_user = eHerkenningUserFactory( + kvk="12345678", rsin="000000000" + ) + + config = OpenKlantConfig.get_solo() + config.use_rsin_for_innNnpId_query_parameter = ( + use_rsin_for_innNnpId_query_parameter + ) + config.save() + + identifier = ( + eherkenning_user.rsin + if use_rsin_for_innNnpId_query_parameter + else eherkenning_user.kvk + ) + m.get( + f"{KLANTEN_ROOT}klanten?subjectNietNatuurlijkPersoon__innNnpId={identifier}", + json=paginated_response([self.klant]), + ), + + response = self.app.get(self.case_detail_url, user=eherkenning_user) + + form = response.forms["contact-form"] + form.action = reverse( + "cases:case_detail_contact_form", + kwargs={"object_id": self.zaak["uuid"]}, + ) + form["question"] = "Sample text" + response = form.submit() + + self.assertEqual( + response.headers["HX-Redirect"], + reverse( + "cases:case_detail", + kwargs={"object_id": str(self.zaak["uuid"])}, + ), + ) + + redirect = self.app.get(response.headers["HX-Redirect"]) + redirect_messages = list(redirect.context["messages"]) + + self.assertEqual(redirect_messages[0].message, _("Vraag verstuurd!")) + self.assertMockMatchersCalled(self.extra_matchers) + + payload = self.matcher_create_contactmoment.request_history[0].json() + self.assertEqual( + payload, + { + "bronorganisatie": "123456788", + "kanaal": "Internet", + "medewerkerIdentificatie": {"identificatie": "FooVonBar"}, + "onderwerp": "afdeling-x", + "tekst": "Sample text", + "type": "Melding", + }, + ) + def test_form_success_with_email(self, m): self._setUpMocks(m) self._setUpExtraMocks(m) diff --git a/src/open_inwoner/cms/cases/views/status.py b/src/open_inwoner/cms/cases/views/status.py index c7d3227321..d559677093 100644 --- a/src/open_inwoner/cms/cases/views/status.py +++ b/src/open_inwoner/cms/cases/views/status.py @@ -21,7 +21,12 @@ from zgw_consumers.api_models.constants import RolOmschrijving from open_inwoner.openklant.models import OpenKlantConfig -from open_inwoner.openklant.wrap import create_contactmoment, create_klant, fetch_klant +from open_inwoner.openklant.wrap import ( + create_contactmoment, + create_klant, + fetch_klant, + get_fetch_parameters, +) from open_inwoner.openzaak.api_models import Status, StatusType, Zaak from open_inwoner.openzaak.cases import ( connect_case_with_document, @@ -741,14 +746,12 @@ def register_by_api(self, form, config: OpenKlantConfig): except ObjectDoesNotExist: ztc = None - klant = fetch_klant(user_bsn=self.request.user.bsn) + klant = fetch_klant(**get_fetch_parameters(self.request.user)) if klant: - self.log_system_action( - "retrieved klant for BSN-user", user=self.request.user - ) + self.log_system_action("retrieved klant for user", user=self.request.user) else: self.log_system_action( - "could not retrieve klant for BSN-user", user=self.request.user + "could not retrieve klant for user", user=self.request.user ) data = { "bronorganisatie": config.register_bronorganisatie_rsin, @@ -767,7 +770,7 @@ def register_by_api(self, form, config: OpenKlantConfig): ) else: self.log_system_action( - "could not create klant for BSN-user", user=self.request.user + "could not create klant for user", user=self.request.user ) # create contact moment diff --git a/src/open_inwoner/openklant/services.py b/src/open_inwoner/openklant/services.py index 2f17a7ead2..228ab4a6bb 100644 --- a/src/open_inwoner/openklant/services.py +++ b/src/open_inwoner/openklant/services.py @@ -2,13 +2,15 @@ from open_inwoner.openklant.wrap import fetch_klant from open_inwoner.utils.logentry import system_action +from .wrap import get_fetch_parameters + def update_user_from_klant(user: User): - klant = fetch_klant(user_bsn=user.bsn) + klant = fetch_klant(**get_fetch_parameters(user)) if not klant: return - system_action("retrieved klant for BSN-user", content_object=user) + system_action("retrieved klant for user", content_object=user) update_data = {} diff --git a/src/open_inwoner/openklant/tests/data.py b/src/open_inwoner/openklant/tests/data.py index f372959ff6..026b2198ec 100644 --- a/src/open_inwoner/openklant/tests/data.py +++ b/src/open_inwoner/openklant/tests/data.py @@ -38,6 +38,11 @@ def __init__(self): email="old@example.com", phonenumber="0100000000", ) + self.eherkenning_user = eHerkenningUserFactory( + email="old2@example.com", + kvk="12345678", + rsin="000000000", + ) self.klant_old = generate_oas_component( "kc", @@ -71,6 +76,28 @@ def install_mocks(self, m) -> "MockAPIReadPatchData": ] return self + def install_mocks_eherkenning(self, m, use_rsin=True) -> "MockAPIReadPatchData": + self.setUpOASMocks(m) + if use_rsin: + first_eherkenning_matcher = m.get( + f"{KLANTEN_ROOT}klanten?subjectNietNatuurlijkPersoon__innNnpId={self.eherkenning_user.rsin}", + json=paginated_response([self.klant_old]), + ) + else: + first_eherkenning_matcher = m.get( + f"{KLANTEN_ROOT}klanten?subjectNietNatuurlijkPersoon__innNnpId={self.eherkenning_user.kvk}", + json=paginated_response([self.klant_old]), + ) + self.matchers = [ + first_eherkenning_matcher, + m.patch( + f"{KLANTEN_ROOT}klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + json=self.klant_updated, + status_code=200, + ), + ] + return self + class MockAPIReadData(MockAPIData): def __init__(self): diff --git a/src/open_inwoner/openklant/tests/test_signal.py b/src/open_inwoner/openklant/tests/test_signal.py index 1f67f11558..82ef93a8bd 100644 --- a/src/open_inwoner/openklant/tests/test_signal.py +++ b/src/open_inwoner/openklant/tests/test_signal.py @@ -7,7 +7,8 @@ from open_inwoner.accounts.choices import LoginTypeChoices from open_inwoner.accounts.models import User -from open_inwoner.accounts.tests.factories import UserFactory +from open_inwoner.accounts.tests.factories import UserFactory, eHerkenningUserFactory +from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.openklant.tests.data import KLANTEN_ROOT, MockAPIReadData from open_inwoner.utils.test import ( ClearCachesMixin, @@ -58,11 +59,66 @@ def test_update_user_after_login(self, m): self.assertEqual(user.email, "new@example.com") self.assertEqual(user.phonenumber, "0612345678") - self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog("retrieved klant for user") self.assertTimelineLog( "updated user from klant API with fields: email, phonenumber" ) + def test_update_eherkenning_user_after_login(self, m): + MockAPIReadData.setUpOASMocks(m) + + self.klant = generate_oas_component( + "kc", + "schemas/Klant", + uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + url=f"{KLANTEN_ROOT}klant/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", + emailadres="new@example.com", + telefoonnummer="0612345678", + ) + user = eHerkenningUserFactory( + phonenumber="0123456789", + email="old@example.com", + kvk="12345678", + rsin="000000000", + ) + + for use_rsin_for_innNnpId_query_parameter in [True, False]: + with self.subTest( + use_rsin_for_innNnpId_query_parameter=use_rsin_for_innNnpId_query_parameter + ): + user.email = "old@example.com" + user.phonenumber = "0123456789" + user.save() + self.clearTimelineLogs() + + config = OpenKlantConfig.get_solo() + config.use_rsin_for_innNnpId_query_parameter = ( + use_rsin_for_innNnpId_query_parameter + ) + config.save() + + identifier = ( + "000000000" if use_rsin_for_innNnpId_query_parameter else "12345678" + ) + m.get( + f"{KLANTEN_ROOT}klanten?subjectNietNatuurlijkPersoon__innNnpId={identifier}", + json=paginated_response([self.klant]), + ) + + request = RequestFactory().get("/dummy") + request.user = user + user_logged_in.send(User, user=user, request=request) + + user.refresh_from_db() + + self.assertEqual(user.email, "new@example.com") + self.assertEqual(user.phonenumber, "0612345678") + + self.assertTimelineLog("retrieved klant for user") + self.assertTimelineLog( + "updated user from klant API with fields: email, phonenumber" + ) + def test_update_user_after_login_skips_existing_email(self, m): MockAPIReadData.setUpOASMocks(m) @@ -101,5 +157,5 @@ def test_update_user_after_login_skips_existing_email(self, m): self.assertEqual(user.email, "old@example.com") self.assertEqual(user.phonenumber, "0612345678") - self.assertTimelineLog("retrieved klant for BSN-user") + self.assertTimelineLog("retrieved klant for user") self.assertTimelineLog("updated user from klant API with fields: phonenumber")