From 33404685b1d8de14229f61ea5dfa1fbee6229916 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Tue, 8 Feb 2022 14:50:03 +0100 Subject: feature(matching): finalize matching by residence search Add ZMR communication and state validation for match-by-residence operation --- .../specific/modules/auth/eidas/v2/Constants.java | 4 +- .../auth/eidas/v2/clients/zmr/IZmrClient.java | 21 ++- .../auth/eidas/v2/clients/zmr/ZmrSoapClient.java | 96 +++++++++-- .../eidas/v2/controller/AdresssucheController.java | 11 ++ .../eidas/v2/service/RegisterSearchService.java | 28 ++-- .../ReceiveAustrianResidenceGuiResponseTask.java | 175 +++++++++++---------- .../messages/eidas_connector_message.properties | 4 + ...eceiveAustrianResidenceGuiResponseTaskTest.java | 127 ++++++++++----- 8 files changed, 323 insertions(+), 143 deletions(-) (limited to 'eidas_modules') diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java index 6da30299..40bcd27a 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java @@ -314,10 +314,12 @@ public class Constants { // UI options public static final String HTML_FORM_ADVANCED_MATCHING_FAILED = "advancedMatchingFailed"; - + // ProcessEngine context public static final String CONTEXT_FLAG_ADVANCED_MATCHING_FAILED = HTML_FORM_ADVANCED_MATCHING_FAILED; + public static final String CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON = + HTML_FORM_ADVANCED_MATCHING_FAILED + "Reason"; /** * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.CreateNewErnpEntryTask}. diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java index c4e8ece0..f3c32c96 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java @@ -29,6 +29,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr.ZmrSoapClient.ZmrRegisterResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController.AdresssucheOutput; 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.exception.EidasSAuthenticationException; @@ -79,6 +80,23 @@ public interface IZmrClient { @Nonnull PersonSuchenRequest personSearchDao, @Nonnull String citizenCountryCode) throws EidasSAuthenticationException; + /** + * Search person based on MDS information and Austrian residence. + * + * @param zmrProzessId zmrProzessId zmrProzessId ProcessId from ZMR or null if no processId exists + * @param givenName eIDAS given name + * @param familyName eIDAS principle name + * @param dateOfBirth eIDAS date-of-birth + * @param citizenCountryCode citizenCountryCode CountryCode of the eIDAS proxy-service + * @param address Address information for searching + * @return Search result but never null + * @throws EidasSAuthenticationException In case of a communication error + */ + @Nonnull + ZmrRegisterResult searchWithResidenceData(@Nullable BigInteger zmrProzessId, @Nonnull String givenName, + @Nonnull String familyName, @Nonnull String dateOfBirth, @Nonnull String citizenCountryCode, + @Nonnull AdresssucheOutput address) throws EidasSAuthenticationException; + /** * Update ZMR entry to KITT existing ZMR identity with this eIDAS authentication. * @@ -92,7 +110,4 @@ public interface IZmrClient { ZmrRegisterResult update(@Nullable BigInteger zmrProzessId, RegisterResult registerResult, SimpleEidasData eidData) throws EidasSAuthenticationException; - ZmrRegisterResult searchWithResidenceData(@Nullable BigInteger zmrProzessId, String givenName, String familyName, - String dateOfBirth, String zipcode, String city, String street); - } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java index 18a80c33..0f2f94cc 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java @@ -24,6 +24,7 @@ import org.springframework.lang.Nullable; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.AbstractSoapClient; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController.AdresssucheOutput; 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.exception.EidasSAuthenticationException; @@ -56,6 +57,8 @@ import at.gv.bmi.namespace.zmr_su.zmr._20040201.SuchkriterienType; import at.gv.e_government.reference.namespace.persondata.de._20040201.IdentificationType; import at.gv.e_government.reference.namespace.persondata.de._20040201.NatuerlichePersonTyp; import at.gv.e_government.reference.namespace.persondata.de._20040201.PersonenNameTyp; +import at.gv.e_government.reference.namespace.persondata.de._20040201.PostAdresseTyp; +import at.gv.e_government.reference.namespace.persondata.de._20040201.ZustelladresseTyp; import at.gv.egiz.eaaf.core.api.data.EaafConstants; import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; @@ -95,6 +98,7 @@ public class ZmrSoapClient extends AbstractSoapClient implements IZmrClient { "Searching " + Constants.eIDAS_ATTR_PERSONALIDENTIFIER; private static final String PROCESS_SEARCH_MDS_ONLY = "Searching with MDS only"; private static final String PROCESS_SEARCH_COUNTRY_SPECIFIC = "Searching {0} specific"; + private static final String PROCESS_SEARCH_BY_RESIDENCE = "Searching by residence"; private static final String PROCESS_KITT_GENERAL = "KITT general-processing"; private static final String PROCESS_KITT_IDENITIES_GET = "KITT get-latest-version"; @@ -173,15 +177,7 @@ public class ZmrSoapClient extends AbstractSoapClient implements IZmrClient { // set eIDAS person information final PersonSuchenRequest searchPersonReq = new PersonSuchenRequest(); req.setPersonSuchenRequest(searchPersonReq); - - final NatuerlichePersonTyp searchNatPerson = new NatuerlichePersonTyp(); - searchPersonReq.setNatuerlichePerson(searchNatPerson); - final PersonenNameTyp searchNatPersonName = new PersonenNameTyp(); - searchNatPerson.setPersonenName(searchNatPersonName); - - searchNatPersonName.setFamilienname(familyName); - searchNatPersonName.setVorname(givenName); - searchNatPerson.setGeburtsdatum(dateOfBirth); + searchPersonReq.setNatuerlichePerson(buildSearchNatPerson(givenName, familyName, dateOfBirth)); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_SEARCH, zmrProzessId)); @@ -296,12 +292,49 @@ public class ZmrSoapClient extends AbstractSoapClient implements IZmrClient { } } - + @Override public ZmrRegisterResult searchWithResidenceData(BigInteger zmrProzessId, String givenName, String familyName, - String dateOfBirth, String zipcode, String city, String street) { - // TODO Auto-generated method stub - return null; + String dateOfBirth, String citizenCountryCode, AdresssucheOutput address) + throws EidasSAuthenticationException { + try { + // build search request + final RequestType req = new RequestType(); + + // set person information + final PersonSuchenRequest searchPersonReq = new PersonSuchenRequest(); + req.setPersonSuchenRequest(searchPersonReq); + searchPersonReq.setNatuerlichePerson(buildSearchNatPerson(givenName, familyName, dateOfBirth)); + searchPersonReq.setPostAdresse(buildSearchAddress(address)); + + // set work-flow client information + req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_SEARCH_BY_RESIDENCE, zmrProzessId)); + req.setClientInfo(generateClientInfos()); + + // set additionl search parameters + searchPersonReq.setPersonensucheInfo(generateSearchCriteria( + PROCESS_SEARCH_BY_RESIDENCE, false, true, false)); + + // request ZMR + log.trace("Requesting ZMR for '{}' operation", PROCESS_SEARCH_BY_RESIDENCE); + final ResponseType resp = zmrClient.service(req, null); + + // parse ZMR response + return processZmrResponse(resp, citizenCountryCode, false, PROCESS_SEARCH_BY_RESIDENCE); + + } catch (final ServiceFault e) { + final String errorMsg = extractReasonFromError(e); + log.warn(LOGMSG_ZMR_ERROR, PROCESS_SEARCH_BY_RESIDENCE, errorMsg); + throw new ZmrCommunicationException(ERROR_MATCHING_01, new Object[] { errorMsg }, e); + + } catch (EidasSAuthenticationException e) { + throw e; + + } catch (final Exception e) { + log.warn(LOGMSG_ZMR_RESP_PROCESS, PROCESS_SEARCH_BY_RESIDENCE, e.getMessage()); + throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); + + } } @PostConstruct @@ -685,6 +718,43 @@ public class ZmrSoapClient extends AbstractSoapClient implements IZmrClient { } } + private NatuerlichePersonTyp buildSearchNatPerson(String givenName, String familyName, String dateOfBirth) { + final NatuerlichePersonTyp searchNatPerson = new NatuerlichePersonTyp(); + final PersonenNameTyp searchNatPersonName = new PersonenNameTyp(); + searchNatPerson.setPersonenName(searchNatPersonName); + searchNatPersonName.setFamilienname(familyName); + searchNatPersonName.setVorname(givenName); + searchNatPerson.setGeburtsdatum(dateOfBirth); + return searchNatPerson; + + } + + private PostAdresseTyp buildSearchAddress(AdresssucheOutput address) { + PostAdresseTyp postAdresse = new PostAdresseTyp(); + if (StringUtils.isNotBlank(address.getPostleitzahl())){ + postAdresse.setPostleitzahl(address.getPostleitzahl()); + } + if (StringUtils.isNotBlank(address.getMunicipality())) { + postAdresse.setGemeinde(address.getMunicipality()); + } + if (StringUtils.isNotBlank(address.getVillage())) { + postAdresse.setOrtschaft(address.getVillage()); + } + if (StringUtils.isNotBlank(address.getStreet()) || StringUtils.isNotBlank(address.getNumber())) { + ZustelladresseTyp zustelladresse = new ZustelladresseTyp(); + if (StringUtils.isNotBlank(address.getStreet())) { + zustelladresse.setStrassenname(address.getStreet()); + } + if (StringUtils.isNotBlank(address.getNumber())) { + zustelladresse.setOrientierungsnummer(address.getNumber()); + } + postAdresse.setZustelladresse(zustelladresse); + } + + return postAdresse; + + } + private Collection selectEidasDocumentsToAdd( PersonErgebnisType zmrPersonToKitt, SimpleEidasData eidData) { diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java index 8505f5d5..6dcd4879 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java @@ -58,6 +58,7 @@ import at.gv.egiz.eaaf.core.exceptions.EaafException; import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -166,18 +167,23 @@ public class AdresssucheController { number.replaceAll("[\r\n]", "")); try { pendingReqGeneration.validateAndGetPendingRequestId(pendingId); + } catch (PendingReqIdValidationException e) { log.warn("Search with pendingId '{}' is not valid", pendingId.replaceAll("[\r\n]", "")); return ResponseEntity.badRequest().build(); + } + try { Adressdaten searchInput = buildSearchInput(postleitzahl, municipality, village, street, number); ZmrAddressSoapClient.AddressInfo searchOutput = client.searchAddress(searchInput); AdresssucheResult output = buildResponse(searchOutput); return ResponseEntity.ok(output); + } catch (EidasSAuthenticationException e) { log.warn("Search failed", e); return ResponseEntity.badRequest().build(); + } } @@ -185,7 +191,9 @@ public class AdresssucheController { if (searchOutput.getPersonResult().isEmpty()) { log.warn("No result from ZMR"); return new AdresssucheResult(Collections.emptyList(), 0); + } + log.info("Result level is {}", searchOutput.getLevel()); Set result = searchOutput.getPersonResult().stream() .map(Adressdaten::getPostAdresse) @@ -195,6 +203,7 @@ public class AdresssucheController { // TODO Add configuration option for the limit of 30 List sorted = result.stream().sorted().limit(30).collect(Collectors.toList()); return new AdresssucheResult(sorted, result.size()); + } private Adressdaten buildSearchInput(String postleitzahl, @@ -225,6 +234,7 @@ public class AdresssucheController { Adressdaten searchInput = new Adressdaten(); searchInput.setPostAdresse(postAdresse); return searchInput; + } @Data @@ -236,6 +246,7 @@ public class AdresssucheController { @Data @AllArgsConstructor + @Builder public static class AdresssucheOutput implements Comparable { private final String postleitzahl; private final String municipality; diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java index 85ea942c..d95aadda 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java @@ -16,6 +16,7 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.ErnpRestCli import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.IErnpClient; 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.controller.AdresssucheController.AdresssucheOutput; 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.exception.EidasSAuthenticationException; @@ -165,18 +166,27 @@ public class RegisterSearchService { * Search with residence infos. * * @param operationStatus Current register-operation status that contains processing informations - * @param zipcode Provided Zipcode - * @param city Provided City - * @param street Provided street + * @param eidasData Receive eIDAS eID information + * @param address Address information provided by user * @return Results from ZMR or ERnP search + * @throws WorkflowException In case of a register interaction error */ - public RegisterStatusResults searchWithResidence(RegisterOperationStatus operationStatus, SimpleEidasData eidasData, - String zipcode, String city, String street) { - final ZmrRegisterResult resultsZmr = zmrClient.searchWithResidenceData( - operationStatus.getZmrProcessId(), eidasData.getGivenName(), eidasData.getFamilyName(), - eidasData.getDateOfBirth(), zipcode, city, street); - return RegisterStatusResults.fromZmr(resultsZmr); + public RegisterStatusResults searchWithResidence(RegisterOperationStatus operationStatus, SimpleEidasData eidasData, + AdresssucheOutput address) throws WorkflowException { + try { + final ZmrRegisterResult resultsZmr = zmrClient.searchWithResidenceData( + operationStatus.getZmrProcessId(), eidasData.getGivenName(), eidasData.getFamilyName(), + eidasData.getDateOfBirth(), eidasData.getCitizenCountryCode(), address); + + // ERnP search is not used here, because we only search for people with Austrian residence and they are in ZMR only + + return RegisterStatusResults.fromZmr(resultsZmr); + + } catch (final EidasSAuthenticationException e) { + throw new WorkflowException("searchWithResidenceInformation", e.getMessage(), + !(e instanceof ZmrCommunicationException), e); + } } /** diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java index acf469d3..67addf94 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java @@ -23,16 +23,24 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK; + import java.util.Enumeration; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import org.springframework.stereotype.Component; import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController.AdresssucheOutput; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController.AdresssucheOutput.AdresssucheOutputBuilder; 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.ManualFixNecessaryException; @@ -43,15 +51,9 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; -import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; +import at.gv.egiz.eaaf.core.impl.idp.controller.tasks.AbstractLocaleAuthServletTask; import lombok.extern.slf4j.Slf4j; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK; - /** * Task receives the response of {@link GenerateAustrianResidenceGuiTask} and handles it. @@ -67,7 +69,7 @@ import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSIT * * Transitions: *
    - *
  • {@link CreateNewErnpEntryTask} if no results from search with residency data in registers
  • + *
  • {@link GenerateOtherLoginMethodGuiTask} if no results from search with residency data in registers
  • *
  • {@link CreateIdentityLinkTask} if one exact match between initial register search (with MDS) and results * from search with residency data in registers exists
  • *
  • {@link GenerateOtherLoginMethodGuiTask} if a user input error has happened
  • @@ -79,71 +81,77 @@ import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSIT */ @Slf4j @Component("ReceiveAustrianResidenceGuiResponseTask") -public class ReceiveAustrianResidenceGuiResponseTask extends AbstractAuthServletTask { +public class ReceiveAustrianResidenceGuiResponseTask extends AbstractLocaleAuthServletTask { - public static final String PARAM_FORMER_RESIDENCE_AVAILABLE = "formerResidenceAvailable"; - public static final String PARAM_STREET = "street"; - public static final String PARAM_CITY = "city"; - public static final String PARAM_ZIPCODE = "zipcode"; + private static final String MSG_PROP_20 = "module.eidasauth.matching.20"; + private static final String MSG_PROP_21 = "module.eidasauth.matching.21"; + private static final String MSG_PROP_22 = "module.eidasauth.matching.22"; + + public static final String HTTP_PARAM_NO_RESIDENCE = "noResidence"; private final RegisterSearchService registerSearchService; public ReceiveAustrianResidenceGuiResponseTask(RegisterSearchService registerSearchService) { this.registerSearchService = registerSearchService; + } - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class UserInput { - private boolean formerResidenceAvailable; - private String zipcode; - private String city; - private String street; - } - + @Override - public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) - throws TaskExecutionException { + protected void executeWithLocale(ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException { log.trace("Starting ReceiveAustrianResidenceGuiResponseTask"); - UserInput input = parseHtmlInput(request); - if (!input.isFormerResidenceAvailable()) { - moveToNextTask(executionContext); - return; - - } - - if (input.getStreet().isEmpty() || input.getCity().isEmpty() || input.getZipcode().isEmpty()) { - // HTML form should ensure that mandatory fields are set => this should never happen - executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); - executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); - return; - - } + try { + //return to AuswahlScreen if HTTP_PARAM_NO_RESIDENCE was selected + final boolean forwardWithOutMandate = parseFlagFromHttpRequest(request, HTTP_PARAM_NO_RESIDENCE, false); + if (forwardWithOutMandate) { + log.debug("User selects 'no residence' button. Switch back to 'other matching' selection ... "); + executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); + + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_20); + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); + return; + + } + + //load search parameters from HTML form + AdresssucheOutput input = parseHtmlInput(request); + if (validateHtmlInput(input)) { + // HTML form should ensure that mandatory fields are set => this should never happen + log.warn("HTML form contains no residence information. Switch back to 'other matching' selection ... "); + executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); + + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_20); + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); + return; - try { + } + + // get pre-processed information SimpleEidasData eidasData = MatchingTaskUtils.getInitialEidasData(pendingReq); RegisterStatusResults initialSearchResult = MatchingTaskUtils.getIntermediateMatchingResult(pendingReq); + // search in register RegisterStatusResults residencyResult = - registerSearchService.searchWithResidence(initialSearchResult.getOperationStatus(), - eidasData, input.zipcode, input.city, input.street); - if (residencyResult.getResultCount() == 0) { - //TODO: her we should add a GUI step of result is zero to inform user an forward process by click - moveToNextTask(executionContext); - - } else if (residencyResult.getResultCount() == 1) { - compareSearchResultWithInitialData(executionContext, residencyResult, eidasData); + registerSearchService.searchWithResidence(initialSearchResult.getOperationStatus(), eidasData, input); + + // validate matching response from registers + if (residencyResult.getResultCount() != 1) { + log.info("Find {} match by using residence information. Forward user to 'other matching' selection ... ", + residencyResult.getResultCount() == 0 ? "no" : "more-than-one"); + executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); + + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_22); + executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); } else { - /*TODO: align with form generation task and to better error handling in case of more-than-one result. - * Maybe the user has to provide more information. - */ - throw new TaskExecutionException(pendingReq, - "Manual Fix necessary", new ManualFixNecessaryException(eidasData)); + log.debug("Find single match by using residence information. Starting data validation ... "); + compareSearchResultWithInitialData(executionContext, residencyResult, eidasData); } + } catch (WorkflowException e) { + throw new TaskExecutionException(pendingReq, "Search with residency data failed", e); + } catch (EaafStorageException e) { log.error("Search with residency data failed", e); throw new TaskExecutionException(pendingReq, "Search with residency data failed", e); @@ -151,58 +159,67 @@ public class ReceiveAustrianResidenceGuiResponseTask extends AbstractAuthServlet } } + private boolean validateHtmlInput(AdresssucheOutput input) { + return StringUtils.isEmpty(input.getMunicipality()) + && StringUtils.isEmpty(input.getNumber()) + && StringUtils.isEmpty(input.getPostleitzahl()) + && StringUtils.isEmpty(input.getStreet()) + && StringUtils.isEmpty(input.getVillage()); + } + private void compareSearchResultWithInitialData(ExecutionContext executionContext, RegisterStatusResults residencyResult, SimpleEidasData eidasData) throws TaskExecutionException, EaafStorageException { try { - if (eidasData.equalsRegisterData(residencyResult.getResult())) { + if (!eidasData.equalsRegisterData(residencyResult.getResult())) { // update register information - registerSearchService.step7aKittProcess(residencyResult, eidasData); + RegisterStatusResults updateResult = registerSearchService.step7aKittProcess(residencyResult, eidasData); - // store search result to re-used in CreateIdentityLink step, because there we need bPK and MDS + // store updated result to re-used in CreateIdentityLink step, because there we need bPK and MDS MatchingTaskUtils.storeFinalMatchingResult(pendingReq, MatchedPersonResult.generateFormMatchingResult( - residencyResult.getResult(), eidasData.getCitizenCountryCode())); + updateResult.getResult(), eidasData.getCitizenCountryCode())); } else { - moveToNextTask(executionContext); - + log.warn("Suspect state FOUND. Matching by residence was neccessary but NO register-update are required!"); + // no update required. Data can be used as it is. + MatchingTaskUtils.storeFinalMatchingResult(pendingReq, + MatchedPersonResult.generateFormMatchingResult( + residencyResult.getResult(), eidasData.getCitizenCountryCode())); + } - + } catch (WorkflowException e) { throw new TaskExecutionException(pendingReq, "Search failed", new ManualFixNecessaryException(eidasData)); } } - private void moveToNextTask(ExecutionContext executionContext) { - // Later on, this should transition to Step 20 - executionContext.put(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK, true); - - } - - private @NotNull UserInput parseHtmlInput(HttpServletRequest request) { + private @NotNull AdresssucheOutput parseHtmlInput(HttpServletRequest request) { Enumeration reqParamNames = request.getParameterNames(); - UserInput result = new UserInput(); + AdresssucheOutputBuilder resultBuilder = AdresssucheOutput.builder(); while (reqParamNames.hasMoreElements()) { final String paramName = reqParamNames.nextElement(); String escaped = StringEscapeUtils.escapeHtml(request.getParameter(paramName)); - if (PARAM_FORMER_RESIDENCE_AVAILABLE.equalsIgnoreCase(paramName)) { - result.setFormerResidenceAvailable(Boolean.parseBoolean(escaped)); + if (AdresssucheController.PARAM_MUNIPICALITY.equalsIgnoreCase(paramName)) { + resultBuilder.municipality(escaped); - } else if (PARAM_STREET.equalsIgnoreCase(paramName)) { - result.setStreet(escaped); + } else if (AdresssucheController.PARAM_NUMBER.equalsIgnoreCase(paramName)) { + resultBuilder.number(escaped); - } else if (PARAM_CITY.equalsIgnoreCase(paramName)) { - result.setCity(escaped); + } else if (AdresssucheController.PARAM_POSTLEITZAHL.equalsIgnoreCase(paramName)) { + resultBuilder.postleitzahl(escaped); - } else if (PARAM_ZIPCODE.equalsIgnoreCase(paramName)) { - result.setZipcode(escaped); + } else if (AdresssucheController.PARAM_STREET.equalsIgnoreCase(paramName)) { + resultBuilder.street(escaped); + + } else if (AdresssucheController.PARAM_VILLAGE.equalsIgnoreCase(paramName)) { + resultBuilder.village(escaped); } } - return result; - + + return resultBuilder.build(); } } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/resources/messages/eidas_connector_message.properties b/eidas_modules/authmodule-eIDAS-v2/src/main/resources/messages/eidas_connector_message.properties index 188c5a8f..f1bf2128 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/resources/messages/eidas_connector_message.properties +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/resources/messages/eidas_connector_message.properties @@ -23,5 +23,9 @@ module.eidasauth.matching.04=An error occurred while loading your data from offi module.eidasauth.matching.11=Matching failed, because of an ERnP communication error. Reason: {0} module.eidasauth.matching.12=Matching failed, because ERnP response contains historic information which is not supported. +module.eidasauth.matching.20=Matching be using residence information was canceled. Use another method for matching or create a new Austrian identity. +module.eidasauth.matching.21=Matching be using residence information failed by missing input information. Use another method for matching or create a new Austrian identity. +module.eidasauth.matching.22=Can not find an unique match by using residence information. Provide more or other data, use another method for matching, or create a new Austrian identity. + module.eidasauth.matching.99=Matching failed, because of an unexpected processing error. Reason: {0} 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 64bb0d48..15edce07 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 @@ -1,14 +1,9 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.test.tasks; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask.PARAM_CITY; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask.PARAM_FORMER_RESIDENCE_AVAILABLE; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask.PARAM_STREET; -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask.PARAM_ZIPCODE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.eq; -import static org.springframework.util.Assert.isInstanceOf; import java.math.BigInteger; import java.util.Arrays; @@ -21,7 +16,6 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.i18n.LocaleContextHolder; @@ -36,21 +30,22 @@ import com.google.common.collect.Lists; 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.controller.AdresssucheController; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.controller.AdresssucheController.AdresssucheOutput; +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.exception.ManualFixNecessaryException; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterOperationStatus; import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterStatusResults; import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAustrianResidenceGuiResponseTask.UserInput; import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; -import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; import at.gv.egiz.eaaf.core.impl.idp.module.test.TestRequestImpl; import at.gv.egiz.eaaf.core.impl.idp.process.ExecutionContextImpl; +import lombok.SneakyThrows; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { @@ -67,7 +62,7 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { private ReceiveAustrianResidenceGuiResponseTask task; - private final ExecutionContext executionContext = new ExecutionContextImpl(); + private ExecutionContext executionContext; private MockHttpServletRequest httpReq; private MockHttpServletResponse httpResp; private TestRequestImpl pendingReq; @@ -79,8 +74,9 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { */ @Before public void setUp() throws Exception { + executionContext = new ExecutionContextImpl(); task = new ReceiveAustrianResidenceGuiResponseTask(registerSearchService); - + httpReq = new MockHttpServletRequest("POST", "https://localhost/ms_connector"); httpResp = new MockHttpServletResponse(); RequestContextHolder.resetRequestAttributes(); @@ -93,9 +89,27 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { LocaleContextHolder.resetLocaleContext(); } + @Test + public void canceledByUser() throws Exception { + AdresssucheOutput userInput = setupUserInput(); + SimpleEidasData eidasData = setupEidasData(); + RegisterStatusResults registerSearchResult = buildEmptyResult(); + mockRegisterSearch(userInput, registerSearchResult, eidasData); + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); + httpReq.setParameter(ReceiveAustrianResidenceGuiResponseTask.HTTP_PARAM_NO_RESIDENCE, "true"); + + task.execute(pendingReq, executionContext); + + assertEquals("Transition To S9", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK)); + assertEquals("matching failed flag", true, executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)); + assertEquals("failed reason", "module.eidasauth.matching.20", executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON)); + assertNull("no final matching result", MatchingTaskUtils.getFinalMatchingResult(pendingReq)); + + } + @Test public void noRegisterResult() throws Exception { - UserInput userInput = setupUserInput(); + AdresssucheOutput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterStatusResults registerSearchResult = buildEmptyResult(); mockRegisterSearch(userInput, registerSearchResult, eidasData); @@ -103,56 +117,86 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { task.execute(pendingReq, executionContext); - assertEquals("Transition To S9", true, executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK)); + assertEquals("Transition To S9", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK)); + assertEquals("matching failed flag", true, executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)); + assertEquals("failed reason", "module.eidasauth.matching.22", executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON)); + assertNull("no final matching result", MatchingTaskUtils.getFinalMatchingResult(pendingReq)); + } @Test - public void exactlyOneRegisterResult_Matching() throws Exception { - UserInput userInput = setupUserInput(); + public void exactlyOneRegisterResult_NoUpdate() throws Exception { + AdresssucheOutput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterStatusResults registerSearchResult = buildResultWithOneMatch(buildMatchingRegisterResult(eidasData)); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); task.execute(pendingReq, executionContext); - + + // validate state assertNull("Transition To S9", executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK)); - Mockito.verify(registerSearchService).step7aKittProcess(eq(registerSearchResult), eq(eidasData)); - + MatchedPersonResult matchingResult = MatchingTaskUtils.getFinalMatchingResult(pendingReq); + assertNotNull("no final matching result", matchingResult); + validateMatchedPerson(matchingResult, registerSearchResult); + } @Test - public void exactlyOneRegisterResult_NotMatching() throws Exception { - UserInput userInput = setupUserInput(); + public void exactlyOneRegisterResult_UpdateRequired() throws Exception { + AdresssucheOutput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterStatusResults registerSearchResult = buildResultWithOneMatch(buildNotMatchingRegisterResult(eidasData)); + RegisterStatusResults registerUpdateResult = buildResultWithOneMatch(buildRandomRegisterResult()); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); - + Mockito.when(registerSearchService.step7aKittProcess(eq(registerSearchResult), eq(eidasData))) + .thenReturn(registerUpdateResult); + + // perform test task.execute(pendingReq, executionContext); - assertEquals("Transition To S9", true, executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK)); + // validate state + assertNull("Transition To S9", executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK)); + + MatchedPersonResult matchingResult = MatchingTaskUtils.getFinalMatchingResult(pendingReq); + assertNotNull("no final matching result", matchingResult); + validateMatchedPerson(matchingResult, registerUpdateResult); + } @Test public void moreThanOneRegisterResult() throws Exception { - UserInput userInput = setupUserInput(); + AdresssucheOutput userInput = setupUserInput(); SimpleEidasData eidasData = setupEidasData(); RegisterStatusResults registerSearchResult = buildResultWithTwoMatches(); MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerSearchResult); mockRegisterSearch(userInput, registerSearchResult, eidasData); - TaskExecutionException e = assertThrows(TaskExecutionException.class, - () -> task.execute(pendingReq, executionContext)); + task.execute(pendingReq, executionContext); - assertEquals(pendingReq.getPendingRequestId(), e.getPendingRequestID()); - isInstanceOf(ManualFixNecessaryException.class, e.getOriginalException()); - assertNull("Transition To S16", executionContext.get(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK)); + assertEquals("Transition To S9", true, executionContext.get(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK)); + assertEquals("matching failed flag", true, executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)); + assertEquals("failed reason", "module.eidasauth.matching.22", executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON)); + assertNull("no final matching result", MatchingTaskUtils.getFinalMatchingResult(pendingReq)); + } - private void mockRegisterSearch(UserInput userInput, RegisterStatusResults registerSearchResult, SimpleEidasData eidasData ) { + @SneakyThrows + private void validateMatchedPerson(MatchedPersonResult current, + RegisterStatusResults registerUpdateResult) { + RegisterResult expected = registerUpdateResult.getResult(); + assertEquals("familyName", expected.getFamilyName(), current.getFamilyName()); + assertEquals("givenName", expected.getGivenName(), current.getGivenName()); + assertEquals("birthday", expected.getDateOfBirth(), current.getDateOfBirth()); + assertEquals("bpk", expected.getBpk(), current.getBpk()); + + } + + @SneakyThrows + private void mockRegisterSearch(AdresssucheOutput userInput, RegisterStatusResults registerSearchResult, SimpleEidasData eidasData ) { Mockito.when(registerSearchService.searchWithResidence(eq(registerSearchResult.getOperationStatus()), eq(eidasData), - eq(userInput.getZipcode()), eq(userInput.getCity()), eq(userInput.getStreet()))).thenReturn(registerSearchResult); + eq(userInput))).thenReturn(registerSearchResult); } @NotNull @@ -216,11 +260,13 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { } - private void setHttpParameters(UserInput input) { - httpReq.setParameter(PARAM_FORMER_RESIDENCE_AVAILABLE, String.valueOf(input.isFormerResidenceAvailable())); - httpReq.setParameter(PARAM_STREET, input.getStreet()); - httpReq.setParameter(PARAM_CITY, input.getCity()); - httpReq.setParameter(PARAM_ZIPCODE, input.getZipcode()); + private void setHttpParameters(AdresssucheOutput input) { + httpReq.setParameter(AdresssucheController.PARAM_STREET, input.getStreet()); + httpReq.setParameter(AdresssucheController.PARAM_MUNIPICALITY, input.getMunicipality()); + httpReq.setParameter(AdresssucheController.PARAM_NUMBER, input.getNumber()); + httpReq.setParameter(AdresssucheController.PARAM_VILLAGE, input.getVillage()); + httpReq.setParameter(AdresssucheController.PARAM_POSTLEITZAHL, input.getPostleitzahl()); + } @NotNull @@ -237,8 +283,13 @@ public class ReceiveAustrianResidenceGuiResponseTaskTest { } @NotNull - private UserInput setupUserInput() { - UserInput result = new UserInput(true, RandomStringUtils.randomAlphabetic(8), RandomStringUtils.randomAlphabetic(8), RandomStringUtils.randomAlphabetic(8)); + private AdresssucheOutput setupUserInput() { + AdresssucheOutput result = new AdresssucheOutput( + RandomStringUtils.randomAlphabetic(8), + RandomStringUtils.randomAlphabetic(8), + RandomStringUtils.randomAlphabetic(8), + RandomStringUtils.randomAlphabetic(8), + RandomStringUtils.randomAlphabetic(8)); setHttpParameters(result); return result; } -- cgit v1.2.3