From 6e1a69773284177a0f6c7233c4bcdf7f4bd96681 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Tue, 15 Jun 2021 18:15:19 +0200 Subject: further optimizations and bug fixing in matching code --- .../modules/auth/eidas/v2/test/dummy/DummyOA.java | 18 +- .../DeSpecificDetailSearchProcessorTest.java | 105 ++++++ .../ItSpecificDetailSearchProcessorTes.java | 84 +++++ .../tasks/CreateIdentityLinkTaskEidNewTest.java | 165 ++++++++- .../v2/test/tasks/CreateIdentityLinkTaskTest.java | 138 ++++++- .../eidas/v2/test/tasks/InitialSearchTaskTest.java | 409 ++++++++++++++------- ...eceiveAustrianResidenceGuiResponseTaskTest.java | 8 +- ...eceiveMobilePhoneSignatureResponseTaskTest.java | 4 +- 8 files changed, 771 insertions(+), 160 deletions(-) create mode 100644 eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/DeSpecificDetailSearchProcessorTest.java create mode 100644 eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/ItSpecificDetailSearchProcessorTes.java (limited to 'eidas_modules/authmodule-eIDAS-v2/src/test/java/at') diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/dummy/DummyOA.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/dummy/DummyOA.java index 2f7782ae..074dd0bb 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/dummy/DummyOA.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/dummy/DummyOA.java @@ -1,13 +1,19 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.dummy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +import at.gv.egiz.eaaf.core.impl.builder.BpkBuilder; import at.gv.egiz.eaaf.core.impl.data.Pair; -import at.gv.egiz.eaaf.core.impl.idp.auth.builder.BpkBuilder; import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; import lombok.Getter; import lombok.Setter; -import org.apache.commons.lang3.StringUtils; - -import java.util.*; public class DummyOA implements IAhSpConfiguration { @@ -115,13 +121,13 @@ public class DummyOA implements IAhSpConfiguration { } @Override - public List getTargetsWithNoBaseIdInternalProcessingRestriction() { + public Set getTargetsWithNoBaseIdInternalProcessingRestriction() { // TODO Auto-generated method stub return null; } @Override - public List getTargetsWithNoBaseIdTransferRestriction() { + public Set getTargetsWithNoBaseIdTransferRestriction() { // TODO Auto-generated method stub return null; } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/DeSpecificDetailSearchProcessorTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/DeSpecificDetailSearchProcessorTest.java new file mode 100644 index 00000000..21c9fd80 --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/DeSpecificDetailSearchProcessorTest.java @@ -0,0 +1,105 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.handler; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.DeSpecificDetailSearchProcessor; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +@RunWith(BlockJUnit4ClassRunner.class) +public class DeSpecificDetailSearchProcessorTest { + + private DeSpecificDetailSearchProcessor handler = new DeSpecificDetailSearchProcessor(); + + @Test + public void checkName() { + assertEquals("wrong handler name", "DeSpecificDetailSearchProcessor", handler.getName()); + + } + + @Test + public void canHandlerCheck_1() { + SimpleEidasData eidData = SimpleEidasData.builder() + .birthName(RandomStringUtils.randomAlphabetic(5)) + .placeOfBirth(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertFalse("wrong 'canHandle' flag", handler.canHandle("XX", eidData)); + + } + + @Test + public void canHandlerCheck_2() { + SimpleEidasData eidData = SimpleEidasData.builder() + .birthName(RandomStringUtils.randomAlphabetic(5)) + .placeOfBirth(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertTrue("wrong 'canHandle' flag", handler.canHandle("DE", eidData)); + + } + + @Test + public void canHandlerCheck_3() { + SimpleEidasData eidData = SimpleEidasData.builder() + .birthName(RandomStringUtils.randomAlphabetic(5)) + .placeOfBirth(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertTrue("wrong 'canHandle' flag", handler.canHandle("de", eidData)); + + } + + @Test + public void canHandlerCheck_4() { + SimpleEidasData eidData = SimpleEidasData.builder() + .birthName(null) + .placeOfBirth(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertFalse("wrong 'canHandle' flag", handler.canHandle("DE", eidData)); + + } + + @Test + public void canHandlerCheck_5() { + SimpleEidasData eidData = SimpleEidasData.builder() + .birthName(RandomStringUtils.randomAlphabetic(5)) + .placeOfBirth(null) + .build(); + assertFalse("wrong 'canHandle' flag", handler.canHandle("DE", eidData)); + + } + + @Test + public void generateZmrSearchRequest() { + SimpleEidasData eidData = SimpleEidasData.builder() + .citizenCountryCode("DE") + .givenName(RandomStringUtils.randomAlphabetic(5)) + .familyName(RandomStringUtils.randomAlphabetic(5)) + .dateOfBirth(RandomStringUtils.randomAlphabetic(5)) + .birthName(RandomStringUtils.randomAlphabetic(5)) + .placeOfBirth(RandomStringUtils.randomAlphabetic(5)) + .build(); + + // perform operation + PersonSuchenRequest req = handler.generateSearchRequest(eidData); + + //validate response + assertNotNull("no search request", req); + assertNotNull("no MDS", req.getNatuerlichePerson()); + assertNotNull("no MDS PersonName", req.getNatuerlichePerson().getPersonenName()); + assertEquals("familyName", eidData.getFamilyName(), req.getNatuerlichePerson().getPersonenName().getFamilienname()); + assertEquals("givenName", eidData.getGivenName(), req.getNatuerlichePerson().getPersonenName().getVorname()); + assertEquals("birthday", eidData.getDateOfBirth(), req.getNatuerlichePerson().getGeburtsdatum()); + + assertNotNull("no eIDAS documenst", req.getEidasSuchdaten()); + //TODO: add validation if we can add more than one eIDAS document + + } + +} diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/ItSpecificDetailSearchProcessorTes.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/ItSpecificDetailSearchProcessorTes.java new file mode 100644 index 00000000..9b638ee5 --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/handler/ItSpecificDetailSearchProcessorTes.java @@ -0,0 +1,84 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.handler; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.BlockJUnit4ClassRunner; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.ItSpecificDetailSearchProcessor; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +@RunWith(BlockJUnit4ClassRunner.class) +public class ItSpecificDetailSearchProcessorTes { + + private ItSpecificDetailSearchProcessor handler = new ItSpecificDetailSearchProcessor(); + + @Test + public void checkName() { + assertEquals("wrong handler name", "ItSpecificDetailSearchProcessor", handler.getName()); + + } + + @Test + public void canHandlerCheck_1() { + SimpleEidasData eidData = SimpleEidasData.builder() + .taxNumber(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertFalse("wrong 'canHandle' flag", handler.canHandle("XX", eidData)); + + } + + @Test + public void canHandlerCheck_2() { + SimpleEidasData eidData = SimpleEidasData.builder() + .taxNumber(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertTrue("wrong 'canHandle' flag", handler.canHandle("IT", eidData)); + + } + + @Test + public void canHandlerCheck_3() { + SimpleEidasData eidData = SimpleEidasData.builder() + .taxNumber(RandomStringUtils.randomAlphabetic(5)) + .build(); + assertTrue("wrong 'canHandle' flag", handler.canHandle("it", eidData)); + + } + + @Test + public void canHandlerCheck_4() { + SimpleEidasData eidData = SimpleEidasData.builder() + .taxNumber("") + .build(); + assertFalse("wrong 'canHandle' flag", handler.canHandle("IT", eidData)); + + } + + @Test + public void generateZmrSearchRequest() { + SimpleEidasData eidData = SimpleEidasData.builder() + .citizenCountryCode("IT") + .givenName(RandomStringUtils.randomAlphabetic(5)) + .familyName(RandomStringUtils.randomAlphabetic(5)) + .dateOfBirth(RandomStringUtils.randomAlphabetic(5)) + .taxNumber(RandomStringUtils.randomAlphabetic(5)) + .build(); + + // perform operation + PersonSuchenRequest req = handler.generateSearchRequest(eidData); + + //validate response + assertNotNull("no search request", req); + + //TODO: add validation if we can add more information about taxNumber from Italy + + } + +} diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java index 248b71d9..7af9706e 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskEidNewTest.java @@ -18,7 +18,9 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.joda.time.DateTime; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; import org.jose4j.jws.AlgorithmIdentifiers; @@ -39,16 +41,25 @@ import org.springframework.web.context.request.ServletRequestAttributes; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.skjolberg.mockito.soap.SoapServiceRule; import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; import at.asitplus.eidas.specific.connector.test.config.dummy.MsConnectorDummyConfigMap; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.MatchedPersonResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.CreateIdentityLinkTask; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.JoseUtils; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.JoseUtils.JwsResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IRequestStorage; import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; @@ -68,8 +79,10 @@ import at.gv.egiz.eaaf.core.impl.idp.module.test.TestRequestImpl; import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; import at.gv.egiz.eaaf.core.impl.utils.Random; import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap.Builder; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; import eu.eidas.auth.commons.protocol.impl.AuthenticationResponse; import lombok.val; import szrservices.JwsHeaderParam; @@ -96,6 +109,9 @@ public class CreateIdentityLinkTaskEidNewTest { @Autowired EaafKeyStoreFactory keyStoreFactory; + @Autowired + ICcSpecificEidProcessingService eidPostProcessor; + @Autowired private IRequestStorage requestStorage; @@ -123,9 +139,11 @@ public class CreateIdentityLinkTaskEidNewTest { /** * jUnit test set-up. + * @throws EidasAttributeException + * @throws EidPostProcessingException */ @Before - public void setUp() throws EaafStorageException, URISyntaxException { + public void setUp() throws EaafStorageException, URISyntaxException, EidPostProcessingException, EidasAttributeException { httpReq = new MockHttpServletRequest("POST", "https://localhost/authhandler"); httpResp = new MockHttpServletResponse(); @@ -144,8 +162,14 @@ public class CreateIdentityLinkTaskEidNewTest { response = buildDummyAuthResponse(false); pendingReq.getSessionData(AuthProcessDataWrapper.class) .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); - - + + final Map eidasAttributes = convertEidasAttrToSimpleMap( + response.getAttributes().getAttributeMap()); + final SimpleEidasData eidData = eidPostProcessor.postProcess(eidasAttributes); + MatchingTaskUtils.storeInitialEidasData(pendingReq, eidData); + + MatchingTaskUtils.storeFinalMatchingResult(pendingReq, null); + pendingReq.setSpConfig(oaParam); pendingReq.setPendingReqId(at.gv.egiz.eaaf.core.impl.utils.Random.nextProcessReferenceValue()); pendingReq.setAuthUrl("http://test.com/"); @@ -163,8 +187,10 @@ public class CreateIdentityLinkTaskEidNewTest { //initialize test response = buildDummyAuthResponse(true); pendingReq.getSessionData(AuthProcessDataWrapper.class) - .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); - + .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); + MatchingTaskUtils.storeInitialEidasData(pendingReq, eidPostProcessor.postProcess( + convertEidasAttrToSimpleMap(response.getAttributes().getAttributeMap()))); + String vsz = RandomStringUtils.randomNumeric(10); when(szrMock.getStammzahlEncrypted(any(), any())).thenReturn(vsz); val signContentResp = new SignContentResponseType(); @@ -223,7 +249,7 @@ public class CreateIdentityLinkTaskEidNewTest { verify(szrMock, times(1)).getStammzahlEncrypted(argument4.capture(), argument5.capture()); Boolean param5 = argument5.getValue(); - Assert.assertFalse("insertERnP flag", param5); + Assert.assertTrue("insertERnP flag", param5); PersonInfoType person = argument4.getValue(); Assert.assertEquals("FamilyName", response.getAttributes().getAttributeValuesByFriendlyName("FamilyName").getFirstValue( @@ -296,6 +322,82 @@ public class CreateIdentityLinkTaskEidNewTest { } + @Test + public void successfulProcessWithDataFromMatching() throws Exception { + //initialize test + String vsz = RandomStringUtils.randomNumeric(10); + when(szrMock.getStammzahlEncrypted(any(), any())).thenReturn(vsz); + val signContentResp = new SignContentResponseType(); + final SignContentEntry signContentEntry = new SignContentEntry(); + signContentEntry.setValue(RandomStringUtils.randomAlphanumeric(10)); + signContentResp.getOut().add(signContentEntry); + when(szrMock.signContent(any(), any(), any())).thenReturn(signContentResp); + + String randomTestSp = RandomStringUtils.randomAlphabetic(10); + pendingReq.setRawDataToTransaction(MsEidasNodeConstants.DATA_REQUESTERID, randomTestSp); + + MatchedPersonResult matchingInfos = MatchedPersonResult.builder() + .bpk(RandomStringUtils.randomAlphabetic(5)) + .givenName(RandomStringUtils.randomAlphabetic(5)) + .familyName(RandomStringUtils.randomAlphabetic(5)) + .dateOfBirth(RandomStringUtils.randomAlphabetic(5)) + .countryCode(RandomStringUtils.randomAlphabetic(2).toUpperCase()) + .build(); + + MatchingTaskUtils.storeFinalMatchingResult(pendingReq, matchingInfos); + + //perform test + task.execute(pendingReq, executionContext); + + + //validate state + // check if pendingRequest was stored + IRequest storedPendingReq = requestStorage.getPendingRequest(pendingReq.getPendingRequestId()); + Assert.assertNotNull("pendingReq not stored", storedPendingReq); + + //check data in session + final AuthProcessDataWrapper authProcessData = storedPendingReq.getSessionData(AuthProcessDataWrapper.class); + Assert.assertNotNull("AuthProcessData", authProcessData); + Assert.assertNotNull("eidasBind", authProcessData.getGenericDataFromSession(Constants.EIDAS_BIND, String.class)); + + String authBlock = authProcessData.getGenericDataFromSession(Constants.SZR_AUTHBLOCK, String.class); + Assert.assertNotNull("AuthBlock", authBlock); + + Assert.assertTrue("EID process", authProcessData.isEidProcess()); + Assert.assertTrue("foreigner process", authProcessData.isForeigner()); + Assert.assertEquals("EID-ISSUING_NATION", "LU", + authProcessData.getGenericDataFromSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class)); + + // check vsz request + ArgumentCaptor argument4 = ArgumentCaptor.forClass(PersonInfoType.class); + ArgumentCaptor argument5 = ArgumentCaptor.forClass(Boolean.class); + verify(szrMock, times(1)).getStammzahlEncrypted(argument4.capture(), argument5.capture()); + + Boolean param5 = argument5.getValue(); + Assert.assertFalse("insertERnP flag", param5); + PersonInfoType person = argument4.getValue(); + Assert.assertEquals("FamilyName", + matchingInfos.getFamilyName(), + person.getPerson().getName().getFamilyName()); + Assert.assertEquals("GivenName", + matchingInfos.getGivenName(), + person.getPerson().getName().getGivenName()); + Assert.assertEquals("DateOfBirth", + matchingInfos.getDateOfBirth(), + person.getPerson().getDateOfBirth()); + Assert.assertEquals("bPK", + matchingInfos.getBpk(), + person.getPerson().getIdentification().getValue()); + Assert.assertEquals("bPKType", + EaafConstants.URN_PREFIX_CDID + "ZP", + person.getPerson().getIdentification().getType()); + + + Assert.assertNull("PlaceOfBirth", person.getPerson().getPlaceOfBirth()); + Assert.assertNull("BirthName", person.getPerson().getAlternativeName()); + + } + @Test public void successfulProcessWithStandardInfos() throws Exception { //initialize test @@ -337,7 +439,7 @@ public class CreateIdentityLinkTaskEidNewTest { verify(szrMock, times(1)).getStammzahlEncrypted(argument4.capture(), argument5.capture()); Boolean param5 = argument5.getValue(); - Assert.assertFalse("insertERnP flag", param5); + Assert.assertTrue("insertERnP flag", param5); PersonInfoType person = argument4.getValue(); Assert.assertEquals("FamilyName", response.getAttributes().getAttributeValuesByFriendlyName("FamilyName").getFirstValue( @@ -456,4 +558,53 @@ public class CreateIdentityLinkTaskEidNewTest { .attributes(attributeMap.build()) .build(); } + + private Map convertEidasAttrToSimpleMap( + ImmutableMap, ImmutableSet>> attributeMap) { + final Map result = new HashMap<>(); + for (final AttributeDefinition el : attributeMap.keySet()) { + final Class parameterizedType = el.getParameterizedType(); + if (DateTime.class.equals(parameterizedType)) { + convertDateTime(attributeMap, result, el); + } else if (PostalAddress.class.equals(parameterizedType)) { + convertPostalAddress(attributeMap, result, el); + } else { + convertString(attributeMap, result, el); + } + } + return result; + } + + private void convertString(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final List natPersonIdObj = EidasResponseUtils + .translateStringListAttribute(el, attributeMap.get(el)); + final String stringAttr = natPersonIdObj.get(0); + if (StringUtils.isNotEmpty(stringAttr)) { + result.put(el.getFriendlyName(), stringAttr); + + } + } + + private void convertPostalAddress(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final PostalAddress addressAttribute = EidasResponseUtils + .translateAddressAttribute(el, attributeMap.get(el).asList()); + if (addressAttribute != null) { + result.put(el.getFriendlyName(), addressAttribute); + + } + } + + private void convertDateTime(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el).asList()); + if (attribute != null) { + result.put(el.getFriendlyName(), attribute); + + } + } } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskTest.java index 556bd2eb..0a2d4271 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/CreateIdentityLinkTaskTest.java @@ -5,6 +5,7 @@ import static org.mockito.ArgumentMatchers.any; import java.net.URISyntaxException; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.xml.bind.JAXBContext; @@ -12,7 +13,9 @@ import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; +import org.joda.time.DateTime; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -27,13 +30,22 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.skjolberg.mockito.soap.SoapServiceRule; import at.asitplus.eidas.specific.connector.MsEidasNodeConstants; import at.asitplus.eidas.specific.connector.test.config.dummy.MsConnectorDummyConfigMap; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.MatchedPersonResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.CreateIdentityLinkTask; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.api.IRequestStorage; import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; @@ -50,7 +62,9 @@ import at.gv.egiz.eaaf.core.impl.idp.module.test.TestRequestImpl; import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; import at.gv.egiz.eaaf.core.impl.utils.Random; import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; import eu.eidas.auth.commons.protocol.impl.AuthenticationResponse; import lombok.val; import szrservices.GetBPK; @@ -79,7 +93,10 @@ public class CreateIdentityLinkTaskTest { EaafKeyStoreFactory keyStoreFactory; @Autowired - private IRequestStorage requestStorage; + ICcSpecificEidProcessingService eidPostProcessor; + + @Autowired + IRequestStorage requestStorage; final ExecutionContext executionContext = new ExecutionContextImpl(); private MockHttpServletRequest httpReq; @@ -96,9 +113,11 @@ public class CreateIdentityLinkTaskTest { /** * jUnit test set-up. + * @throws EidasAttributeException + * @throws EidPostProcessingException */ @Before - public void setUp() throws EaafStorageException, URISyntaxException { + public void setUp() throws EaafStorageException, URISyntaxException, EidPostProcessingException, EidasAttributeException { httpReq = new MockHttpServletRequest("POST", "https://localhost/authhandler"); httpResp = new MockHttpServletResponse(); @@ -117,9 +136,15 @@ public class CreateIdentityLinkTaskTest { pendingReq = new TestRequestImpl(); response = buildDummyAuthResponse(); - + final Map eidasAttributes = convertEidasAttrToSimpleMap( + response.getAttributes().getAttributeMap()); + final SimpleEidasData eidData = eidPostProcessor.postProcess(eidasAttributes); + MatchingTaskUtils.storeInitialEidasData(pendingReq, eidData); pendingReq.getSessionData(AuthProcessDataWrapper.class) .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); + + MatchingTaskUtils.storeFinalMatchingResult(pendingReq, null); + pendingReq.setSpConfig(oaParam); pendingReq.setPendingReqId(at.gv.egiz.eaaf.core.impl.utils.Random.nextProcessReferenceValue()); pendingReq.setAuthUrl("http://test.com/"); @@ -181,6 +206,63 @@ public class CreateIdentityLinkTaskTest { } + @Test + public void successfulProcessWithDataFromMatching() throws Exception { + //initialize test + setSzrResponseIdentityLink("/data/szr/szr_resp_valid_1.xml"); + + String randomTestSp = RandomStringUtils.randomAlphabetic(10); + pendingReq.setRawDataToTransaction(MsEidasNodeConstants.DATA_REQUESTERID, randomTestSp); + + basicConfig.putConfigValue("eidas.ms.auth.eIDAS.szrclient.debug.useDummySolution", "false"); + + MatchedPersonResult matchingInfos = MatchedPersonResult.builder() + .bpk(RandomStringUtils.randomAlphabetic(5)) + .givenName(RandomStringUtils.randomAlphabetic(5)) + .familyName(RandomStringUtils.randomAlphabetic(5)) + .dateOfBirth(RandomStringUtils.randomAlphabetic(5)) + .countryCode(RandomStringUtils.randomAlphabetic(2).toUpperCase()) + .build(); + + MatchingTaskUtils.storeFinalMatchingResult(pendingReq, matchingInfos); + + //perform test + task.execute(pendingReq, executionContext); + + + //validate state + // check if pendingRequest was stored + IRequest storedPendingReq = requestStorage.getPendingRequest(pendingReq.getPendingRequestId()); + Assert.assertNotNull("pendingReq not stored", storedPendingReq); + + //check data in session + final AuthProcessDataWrapper authProcessData = storedPendingReq.getSessionData(AuthProcessDataWrapper.class); + Assert.assertNotNull("AuthProcessData", authProcessData); + Assert.assertNull("eidasBind", authProcessData.getGenericDataFromSession(Constants.EIDAS_BIND, String.class)); + + String authBlock = authProcessData.getGenericDataFromSession(Constants.SZR_AUTHBLOCK, String.class); + Assert.assertNull("AuthBlock", authBlock); + + Assert.assertFalse("EID process", authProcessData.isEidProcess()); + Assert.assertTrue("foreigner process", authProcessData.isForeigner()); + Assert.assertEquals("EID-ISSUING_NATION", "LU", + authProcessData.getGenericDataFromSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, String.class)); + + Assert.assertNotNull("IDL", authProcessData.getIdentityLink()); + checkElement("Mustermann", authProcessData.getIdentityLink().getFamilyName()); + checkElement("Hans", authProcessData.getIdentityLink().getGivenName()); + checkElement("1989-05-05", authProcessData.getIdentityLink().getDateOfBirth()); + checkElement("urn:publicid:gv.at:baseid", authProcessData.getIdentityLink().getIdentificationType()); + checkElement("k+zDM1BVpN1WJO4x7ZQ3ng==", authProcessData.getIdentityLink().getIdentificationValue()); + Assert.assertNotNull(authProcessData.getIdentityLink().getSerializedSamlAssertion()); + Assert.assertNotNull(authProcessData.getIdentityLink().getSamlAssertion()); + + Assert.assertNotNull("no bPK", authProcessData.getGenericDataFromSession(PvpAttributeDefinitions.BPK_NAME)); + Assert.assertEquals("wrong bPK", "XX:FkXtOaSSeR3elyL9KLLvijIYDMU=", + authProcessData.getGenericDataFromSession(PvpAttributeDefinitions.BPK_NAME)); + + } + @Test public void buildIdentityLinkWithWbpk() throws Exception { //initialize test @@ -444,4 +526,54 @@ public class CreateIdentityLinkTaskTest { .attributes(attributeMap) .build(); } + + private Map convertEidasAttrToSimpleMap( + ImmutableMap, ImmutableSet>> attributeMap) { + final Map result = new HashMap<>(); + for (final AttributeDefinition el : attributeMap.keySet()) { + final Class parameterizedType = el.getParameterizedType(); + if (DateTime.class.equals(parameterizedType)) { + convertDateTime(attributeMap, result, el); + } else if (PostalAddress.class.equals(parameterizedType)) { + convertPostalAddress(attributeMap, result, el); + } else { + convertString(attributeMap, result, el); + } + } + return result; + } + + private void convertString(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final List natPersonIdObj = EidasResponseUtils + .translateStringListAttribute(el, attributeMap.get(el)); + final String stringAttr = natPersonIdObj.get(0); + if (StringUtils.isNotEmpty(stringAttr)) { + result.put(el.getFriendlyName(), stringAttr); + + } + } + + private void convertPostalAddress(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final PostalAddress addressAttribute = EidasResponseUtils + .translateAddressAttribute(el, attributeMap.get(el).asList()); + if (addressAttribute != null) { + result.put(el.getFriendlyName(), addressAttribute); + + } + } + + private void convertDateTime(ImmutableMap, + ImmutableSet>> attributeMap, + Map result, AttributeDefinition el) { + final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el).asList()); + if (attribute != null) { + result.put(el.getFriendlyName(), attribute); + + } + } + } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/InitialSearchTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/InitialSearchTaskTest.java index 34bca782..bb732f1c 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/InitialSearchTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/InitialSearchTaskTest.java @@ -24,14 +24,18 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.tasks; import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import java.math.BigInteger; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -44,6 +48,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.jetbrains.annotations.NotNull; import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -62,6 +67,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr.IZmrClient; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr.ZmrSoapClient.ZmrRegisterResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.MatchedPersonResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.RegisterResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; import at.asitplus.eidas.specific.modules.auth.eidas.v2.ernp.IErnpClient; @@ -69,10 +75,12 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcess import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.ZmrCommunicationException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.CountrySpecificDetailSearchProcessor; import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.GenericEidProcessor; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterSearchResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.InitialSearchTask; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; @@ -100,11 +108,9 @@ public class InitialSearchTaskTest { private static final String EE = "EE"; private static final String DE = "DE"; - private static final String IT = "IT"; private static final String EE_ST = EE + "/ST/"; private static final String DE_ST = DE + "/ST/"; - private static final String IT_ST = IT + "/ST/"; @Mock private IZmrClient zmrClient; @@ -173,130 +179,215 @@ public class InitialSearchTaskTest { */ @Test @DirtiesContext - public void testNode100_UserIdentifiedUpdateNecessary_a() throws Exception { - String newFirstName = randomAlphabetic(10); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + public void singlePersonalIdMatchUpdateNecessary_Zmr() throws Exception { + String oldGivenName = randomAlphabetic(10); + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.singletonList( RegisterResult.builder() .bpk(randomBpk) .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(newFirstName) + .givenName(oldGivenName) .familyName(randomFamilyName) .dateOfBirth(randomBirthDate) .build()), generateRandomProcessId())); + + Mockito.when(zmrClient.searchCountrySpecific(any(), any(), any())).thenThrow( + new IllegalStateException("CountrySpecific search search should not be neccessary")); + Mockito.when(zmrClient.searchWithMds(any(), any(), any(), any(), any())).thenThrow( + new IllegalStateException("MDS search should not be neccessary")); + + // execute test task.execute(pendingReq, executionContext); - String bPk = readBpkFromSessionData(pendingReq); - - Assert.assertEquals("Wrong bpk", randomBpk, bPk); + + // validate state + //INFO: has to be the old givenName because ZMR allows no update of MDS information + checkMatchingSuccessState(pendingReq, randomBpk, randomFamilyName, oldGivenName, randomBirthDate, DE); + } + /** - * One match, but register update needed + * TODO: include again if ERnP update is implementet. Maybe we can update MDS based on ERnP. + * + * One match, but register update needed. * @throws EidasSAuthenticationException */ + @Ignore @Test @DirtiesContext - public void testNode100_UserIdentifiedUpdateNecessary_b() throws TaskExecutionException, EidasSAuthenticationException { - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + public void singlePersonalIdMatchUpdateNecessary_Ernp() throws TaskExecutionException, EidasSAuthenticationException { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), generateRandomProcessId())); - String newRandomGivenName = randomAlphabetic(10); + String oldRandomGivenName = randomAlphabetic(10); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.singletonList( RegisterResult.builder() .bpk(randomBpk) .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(newRandomGivenName) + .givenName(oldRandomGivenName) .familyName(randomFamilyName) .dateOfBirth(randomBirthDate) .build())); + // execute test task.execute(pendingReq, executionContext); - String bPk = readBpkFromSessionData(pendingReq); - - Assert.assertEquals("Wrong bpk", randomBpk, bPk); + + // validate state + checkMatchingSuccessState(pendingReq, randomBpk, randomFamilyName, randomGivenName, randomBirthDate, DE); } /** - * Two matches found in ZMR + * Two matches by PersonalId found in ZMR * @throws EidasSAuthenticationException */ @Test @DirtiesContext - public void testNode101_ManualFixNecessary_a() throws EidasSAuthenticationException { - ArrayList zmrResult = new ArrayList<>(); - zmrResult.add( - RegisterResult.builder() - .bpk(randomBpk) - .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(randomGivenName) - .familyName(randomFamilyName) - .dateOfBirth(randomBirthDate) - .build()); - String newRandomGivenName = randomGivenName + randomAlphabetic(2); - zmrResult.add( - RegisterResult.builder() - .bpk(randomBpk) - .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(newRandomGivenName) - .familyName(randomFamilyName) - .dateOfBirth(randomBirthDate) - .build()); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( - new ZmrRegisterResult(zmrResult, generateRandomProcessId())); - Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); + public void multiPersonalIdMatch_Zmr() throws EidasSAuthenticationException { + String newRandomGivenName = randomAlphabetic(10); + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( + new ZmrRegisterResult(Arrays.asList( + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(randomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build(), + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(newRandomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build() + ), + generateRandomProcessId())); + Mockito.when(ernpClient.searchWithPersonIdentifier( + randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); + // execute task TaskExecutionException exception = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); - Throwable origE = exception.getOriginalException(); - Assert.assertTrue("Wrong exception", (origE instanceof WorkflowException)); + + // validate state + assertTrue("Wrong exception", (exception.getOriginalException() instanceof WorkflowException)); + assertTrue("Wrong flag 'manualFixNeeded'", + ((WorkflowException)exception.getOriginalException()).isRequiresManualFix()); + } - /** - * Two matches found in ErnP + * Two matches by PersonalId found in ZMR * @throws EidasSAuthenticationException */ @Test @DirtiesContext - public void testNode101_ManualFixNecessary_b() throws EidasSAuthenticationException { - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( - new ZmrRegisterResult(Collections.emptyList(), generateRandomProcessId())); - ArrayList ernpResult = new ArrayList<>(); - ernpResult.add( - RegisterResult.builder() - .bpk(randomBpk) - .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(randomGivenName) - .familyName(randomFamilyName) - .dateOfBirth(randomBirthDate) - .build()); - String newRandomGivenName = randomGivenName + randomAlphabetic(2); - ernpResult.add( - RegisterResult.builder() - .bpk(randomBpk) - .pseudonym(Arrays.asList(randomPsydonym)) - .givenName(newRandomGivenName) - .familyName(randomFamilyName) - .dateOfBirth(randomBirthDate) - .build()); - Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(ernpResult); + public void withErrorFromZmr() throws EidasSAuthenticationException { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenThrow( + new ZmrCommunicationException("jUnit ZMR error", null)); + Mockito.when(ernpClient.searchWithPersonIdentifier( + randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); + // execute task TaskExecutionException exception = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq, executionContext)); - Throwable origE = exception.getOriginalException(); - Assert.assertTrue("Wrong exception", (origE instanceof WorkflowException)); + + // validate state + assertTrue("Wrong exception", (exception.getOriginalException() instanceof WorkflowException)); + assertFalse("Wrong flag 'manualFixNeeded'", + ((WorkflowException)exception.getOriginalException()).isRequiresManualFix()); + } /** - * One match, no register update needed + * Two matches by PersonalId found in ErnP + * @throws EidasSAuthenticationException */ @Test @DirtiesContext - public void testNode102_UserIdentified_a() throws Exception { - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + public void multiPersonalIdMatch_Ernp() throws EidasSAuthenticationException { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( + new ZmrRegisterResult(Collections.emptyList(), generateRandomProcessId())); + String newRandomGivenName = randomAlphabetic(10); + Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn( + Arrays.asList( + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(randomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build(), + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(newRandomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build() + )); + + // execute task + TaskExecutionException exception = assertThrows(TaskExecutionException.class, + () -> task.execute(pendingReq, executionContext)); + + // validate state + assertTrue("Wrong exception", (exception.getOriginalException() instanceof WorkflowException)); + assertTrue("Wrong flag 'manualFixNeeded'", + ((WorkflowException)exception.getOriginalException()).isRequiresManualFix()); + + } + + /** + * Two matches by PersonalId + * @throws EidasSAuthenticationException + */ + @Test + @DirtiesContext + public void multiPersonalIdMatch_ErnpAndZmr() throws EidasSAuthenticationException { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( + new ZmrRegisterResult(Arrays.asList( + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(randomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build() + ), generateRandomProcessId())); + String newRandomGivenName = randomAlphabetic(10); + Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn( + Arrays.asList( + RegisterResult.builder() + .bpk(randomBpk) + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(randomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build() + )); + + // execute task + TaskExecutionException exception = assertThrows(TaskExecutionException.class, + () -> task.execute(pendingReq, executionContext)); + + // validate state + assertTrue("Wrong exception", (exception.getOriginalException() instanceof WorkflowException)); + assertTrue("Wrong flag 'manualFixNeeded'", + ((WorkflowException)exception.getOriginalException()).isRequiresManualFix()); + + } + + /** + * One match by PersonalId, no register update needed + */ + @Test + @DirtiesContext + public void singlePersonalIdMatchNoUpdate_Ernp() throws Exception { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), generateRandomProcessId())); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.singletonList( RegisterResult.builder() @@ -307,18 +398,20 @@ public class InitialSearchTaskTest { .dateOfBirth(randomBirthDate) .build())); + // execute test task.execute(pendingReq, executionContext); - String bPk = readBpkFromSessionData(pendingReq); - Assert.assertEquals("Wrong bpk", randomBpk, bPk); + + // validate state + checkMatchingSuccessState(pendingReq, randomBpk, randomFamilyName, randomGivenName, randomBirthDate, DE); } /** - * One match, no register update needed + * One match by PersonalId, no register update needed */ @Test @DirtiesContext - public void testNode102_UserIdentified_b() throws Exception { - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + public void singlePersonalIdMatchNoUpdate_Zmr() throws Exception { + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.singletonList( RegisterResult.builder() .bpk(randomBpk) @@ -330,27 +423,27 @@ public class InitialSearchTaskTest { generateRandomProcessId())); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); + // execute test task.execute(pendingReq, executionContext); - - String bPk = readBpkFromSessionData(pendingReq); - Assert.assertEquals("Wrong bpk", randomBpk, bPk); + + // validate state + checkMatchingSuccessState(pendingReq, randomBpk, randomFamilyName, randomGivenName, randomBirthDate, DE); } /** - * Multiple matches found in ZMR and ErnP with detail search + * Find single person in ZMR by country specifics. */ @Test @DirtiesContext - public void testNode103_UserIdentified_DE() throws Exception { + public void singlePersonFindWithCountySpecifics_Zmr() throws Exception { final AuthenticationResponse response = buildDummyAuthResponseDE(randomGivenName, randomFamilyName, - randomPersonalIdentifier_DE, - randomBirthDate, randomPlaceOfBirth, randomBirthName); + randomPersonalIdentifier_DE, randomBirthDate, randomPlaceOfBirth, randomBirthName); TestRequestImpl pendingReq1 = new TestRequestImpl(); pendingReq1.getSessionData(AuthProcessDataWrapper.class) .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); BigInteger zmrProcessId = generateRandomProcessId(); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchCountrySpecific(eq(zmrProcessId), any(PersonSuchenRequest.class), eq(DE))).thenReturn( new ZmrRegisterResult(Collections.singletonList( @@ -364,23 +457,25 @@ public class InitialSearchTaskTest { .birthName(randomBirthName) .build()) ,zmrProcessId)); + Mockito.when(zmrClient.searchWithMds(any(), any(), any(), any(), any())).thenThrow( + new IllegalStateException("MDS search should not be neccessary")); + Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); // execute test task.execute(pendingReq1, executionContext); - + // validate state - String resultBpk = readBpkFromSessionData(pendingReq1); - Assert.assertEquals("Wrong bpk", randomBpk, resultBpk); + checkMatchingSuccessState(pendingReq1, randomBpk, randomFamilyName, randomGivenName, randomBirthDate, DE); } /** - * Multiple matches found in ZMR and ErnP with detail search + * Multiple matches found in ZMR by country specifics. */ @Test @DirtiesContext - public void testNode104_ManualFixNecessary_DE() throws Exception { + public void multiplePersonFindWithCountySpecifics_Zmr() throws Exception { String newRandomPseudonym = randomPersonalIdentifier_DE + RandomStringUtils.randomNumeric(2); String newRandomBpk = randomBpk + RandomStringUtils.randomNumeric(6); final AuthenticationResponse response = buildDummyAuthResponseDE(randomGivenName, randomFamilyName, @@ -391,9 +486,8 @@ public class InitialSearchTaskTest { .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, response); BigInteger zmrProcessId = generateRandomProcessId(); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( - new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); - Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( + new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchCountrySpecific(eq(zmrProcessId), any(PersonSuchenRequest.class), eq(DE))).thenReturn( new ZmrRegisterResult(Arrays.asList( RegisterResult.builder() @@ -416,14 +510,17 @@ public class InitialSearchTaskTest { .build()) ,zmrProcessId)); + Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); - // execute test + // execute task TaskExecutionException exception = assertThrows(TaskExecutionException.class, () -> task.execute(pendingReq1, executionContext)); - - // check error - Throwable origE = exception.getOriginalException(); - Assert.assertTrue("Wrong exception", (origE instanceof WorkflowException)); + + // validate state + assertTrue("Wrong exception", (exception.getOriginalException() instanceof WorkflowException)); + assertTrue("Wrong flag 'manualFixNeeded'", + ((WorkflowException)exception.getOriginalException()).isRequiresManualFix()); + } /** @@ -434,44 +531,51 @@ public class InitialSearchTaskTest { */ @Test @DirtiesContext - public void testNode505_TransitionToInsertErnbTask() throws TaskExecutionException, EidasSAuthenticationException, URISyntaxException, EaafStorageException { + public void noResultByAnySearch() throws TaskExecutionException, EidasSAuthenticationException, URISyntaxException, EaafStorageException { BigInteger zmrProcessId = generateRandomProcessId(); pendingReq.getSessionData(AuthProcessDataWrapper.class) .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, buildDummyAuthResponse(randomGivenName, randomFamilyName, randomPersonalIdentifier_EE, randomBirthDate)); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_EE)).thenReturn( + + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, EE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchWithMds(zmrProcessId, randomGivenName, randomFamilyName, randomBirthDate, EE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_EE)).thenReturn(Collections.emptyList()); + + // execute task task.execute(pendingReq, executionContext); - String bPk = readBpkFromSessionData(pendingReq); - Assert.assertNull("Wrong bpk", bPk); + + // validate state + assertNotNull("find no eIDAS inbut data", MatchingTaskUtils.getInitialEidasData(pendingReq)); + + assertNull("Find intermediate matching data but matching should be finished", + MatchingTaskUtils.getIntermediateMatchingResult(pendingReq)); + assertNull("Find final matching data but no match sould be found", + MatchingTaskUtils.getFinalMatchingResult(pendingReq)); Boolean transitionGUI = (Boolean) executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK); Assert.assertNull("Wrong transition", transitionGUI); Boolean transitionErnb = (Boolean) executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); Assert.assertTrue("Wrong transition", transitionErnb); + } /** - * NO match found in ZMR and ErnP with Initial search, one match with MDS search in Ernb - * @throws EidasSAuthenticationException - * @throws URISyntaxException - * @throws EaafStorageException + * Find one match with MDS search in ERnP. */ @Test @DirtiesContext - public void testNode505_TransitionToGUI_Ernb() throws TaskExecutionException, EidasSAuthenticationException, URISyntaxException, EaafStorageException { + public void resultByMdsSearch_Ernb() throws TaskExecutionException, EidasSAuthenticationException, URISyntaxException, EaafStorageException { BigInteger zmrProcessId = generateRandomProcessId(); pendingReq.getSessionData(AuthProcessDataWrapper.class) .setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, buildDummyAuthResponse(randomGivenName, randomFamilyName, randomPersonalIdentifier_EE, randomBirthDate)); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_EE)).thenReturn( + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, EE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchWithMds(zmrProcessId, randomGivenName, randomFamilyName, randomBirthDate, EE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); @@ -487,24 +591,22 @@ public class InitialSearchTaskTest { .dateOfBirth(randomBirthDate) .build())); + // execute test task.execute(pendingReq, executionContext); - assertThrows(WorkflowException.class, () -> readBpkFromSessionData(pendingReq)); - Boolean transitionGUI = (Boolean) executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK); - Assert.assertTrue("Wrong transition", transitionGUI); - Boolean transitionErnb = (Boolean) executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); - Assert.assertNull("Wrong transition", transitionErnb); + // validate state + checkIntermediateResult(1); + } /** - * NO match found in ZMR and ErnP with Initial search, one match with MDS search in ZMR - * @throws EidasSAuthenticationException + * Find one match with MDS search in ZMR. */ @Test @DirtiesContext - public void testNode505_TransitionToGUI_Zmr() throws TaskExecutionException, EidasSAuthenticationException { + public void resultByMdsSearch_Zmr() throws TaskExecutionException, EidasSAuthenticationException { BigInteger zmrProcessId = generateRandomProcessId(); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); @@ -518,29 +620,35 @@ public class InitialSearchTaskTest { .build()), zmrProcessId)); + // execute test task.execute(pendingReq, executionContext); - assertThrows(WorkflowException.class, () -> readBpkFromSessionData(pendingReq)); - Boolean transitionGUI = (Boolean) executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK); - Assert.assertTrue("Wrong transition", transitionGUI); - Boolean transitionErnb = (Boolean) executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); - Assert.assertNull("Wrong transition", transitionErnb); + // validate state + checkIntermediateResult(1); + } /** - * NO match found in ZMR and ErnP with Initial search, multiple matches found with MDS search - * @throws EidasSAuthenticationException + * resultByMdsSearch */ @Test @DirtiesContext - public void testNode505_TransitionToGUI_Ernb_multi() throws TaskExecutionException, EidasSAuthenticationException { + public void multipleResultsByMdsSearch() throws TaskExecutionException, EidasSAuthenticationException { BigInteger zmrProcessId = generateRandomProcessId(); - Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPersonalIdentifier_DE)).thenReturn( + Mockito.when(zmrClient.searchWithPersonIdentifier(null, randomPsydonym, DE)).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchCountrySpecific(eq(zmrProcessId), any(PersonSuchenRequest.class), any(String.class))).thenReturn( new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); Mockito.when(zmrClient.searchWithMds(zmrProcessId, randomGivenName, randomFamilyName, randomBirthDate, DE)).thenReturn( - new ZmrRegisterResult(Collections.emptyList(), zmrProcessId)); + new ZmrRegisterResult(Arrays.asList( + RegisterResult.builder() + .bpk(randomBpk + "2") + .pseudonym(Arrays.asList(randomPsydonym)) + .givenName(randomGivenName) + .familyName(randomFamilyName) + .dateOfBirth(randomBirthDate) + .build()), + zmrProcessId)); Mockito.when(ernpClient.searchWithPersonIdentifier(randomPersonalIdentifier_DE)).thenReturn(Collections.emptyList()); Mockito.when(ernpClient.searchWithMds(randomGivenName, randomFamilyName, randomBirthDate)).thenReturn( @@ -560,13 +668,12 @@ public class InitialSearchTaskTest { .dateOfBirth(randomBirthDate) .build())); + // execute test task.execute(pendingReq, executionContext); - assertThrows(WorkflowException.class, () -> readBpkFromSessionData(pendingReq)); - Boolean transitionGUI = (Boolean) executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK); - Assert.assertTrue("Wrong transition", transitionGUI); - Boolean transitionErnb = (Boolean) executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); - Assert.assertNull("Wrong transition", transitionErnb); + // validate state + checkIntermediateResult(3); + } @NotNull @@ -579,6 +686,38 @@ public class InitialSearchTaskTest { } + private void checkMatchingSuccessState(IRequest pendingReq, String bpk, String familyName, String givenName, + String birhday, String countryCode) { + assertNull("Find intermediate matching data but matching should be finished", + MatchingTaskUtils.getIntermediateMatchingResult(pendingReq)); + assertNotNull("find no eIDAS inbut data", MatchingTaskUtils.getInitialEidasData(pendingReq)); + + MatchedPersonResult personInfo = MatchingTaskUtils.getFinalMatchingResult(pendingReq); + assertNotNull("no final matching result", personInfo); + assertEquals("wrong bpk", bpk, personInfo.getBpk()); + assertEquals("wrong givenName", givenName, personInfo.getGivenName()); + assertEquals("wrong familyName", familyName, personInfo.getFamilyName()); + assertEquals("wrong dateOfBirth", birhday, personInfo.getDateOfBirth()); + assertEquals("wrong countryCode", countryCode, personInfo.getCountryCode()); + + } + + private void checkIntermediateResult(int resultSize) { + Boolean transitionGUI = (Boolean) executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK); + Assert.assertTrue("Wrong transition", transitionGUI); + Boolean transitionErnb = (Boolean) executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); + Assert.assertNull("Wrong transition", transitionErnb); + + assertNotNull("find no eIDAS inbut data", MatchingTaskUtils.getInitialEidasData(pendingReq)); + assertNull("Find final matching data but no match sould be found", + MatchingTaskUtils.getFinalMatchingResult(pendingReq)); + + RegisterSearchResult result = MatchingTaskUtils.getIntermediateMatchingResult(pendingReq); + assertNotNull("Find no intermediate matching data", result); + assertEquals("wrong intermediate result size", resultSize, result.getResultCount()); + + } + @NotNull private AuthenticationResponse buildDummyAuthResponse(String givenName, String familyName, String identifier, String dateOfBirth) throws URISyntaxException { @@ -646,10 +785,4 @@ public class InitialSearchTaskTest { .attributeValueMarshaller(marshaller).build(); } - private String readBpkFromSessionData(TestRequestImpl pendingReq) throws WorkflowException { - return MatchingTaskUtils.getInitialRegisterResult(pendingReq) != null - ? MatchingTaskUtils.getInitialRegisterResult(pendingReq).getBpk() - : null; - - } } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveAustrianResidenceGuiResponseTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveAustrianResidenceGuiResponseTaskTest.java index 281be36f..77c49bb4 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveAustrianResidenceGuiResponseTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveAustrianResidenceGuiResponseTaskTest.java @@ -101,7 +101,7 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { SimpleEidasData eidasData = setupEidasData(); RegisterSearchResult registerSearchResult = buildEmptyResult(); mockRegisterSearch(userInput, registerSearchResult, eidasData); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); @@ -113,7 +113,7 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { UserInput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterSearchResult registerSearchResult = buildResultWithOneMatch(buildMatchingRegisterResult(eidasData)); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); task.execute(pendingReq, executionContext); @@ -128,7 +128,7 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { UserInput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterSearchResult registerSearchResult = buildResultWithOneMatch(buildNotMatchingRegisterResult(eidasData)); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); task.execute(pendingReq, executionContext); @@ -141,7 +141,7 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { UserInput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterSearchResult registerSearchResult = buildResultWithTwoMatches(); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); TaskExecutionException e = assertThrows(TaskExecutionException.class, diff --git a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveMobilePhoneSignatureResponseTaskTest.java b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveMobilePhoneSignatureResponseTaskTest.java index 8c137bb2..51077e96 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveMobilePhoneSignatureResponseTaskTest.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/tasks/ReceiveMobilePhoneSignatureResponseTaskTest.java @@ -344,7 +344,7 @@ public class ReceiveMobilePhoneSignatureResponseTaskTest { authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); RegisterSearchResult registerSearchResult = new RegisterSearchResult(new RegisterOperationStatus(generateRandomProcessId()), Collections.emptyList(), Collections.emptyList()); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); @@ -363,7 +363,7 @@ public class ReceiveMobilePhoneSignatureResponseTaskTest { SimpleEidasData eidData = createEidasDataMatchingToSamlResponse().build(); authProcessData.setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidData); RegisterSearchResult registerSearchResult = buildResultWithOneMatch(); - MatchingTaskUtils.storeInitialRegisterResult(pendingReq, registerSearchResult); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); task.execute(pendingReq, executionContext); -- cgit v1.2.3