From e385456f79574dac9702b01a6722de444359fff1 Mon Sep 17 00:00:00 2001 From: Thomas <> Date: Tue, 16 Nov 2021 08:41:19 +0100 Subject: restructure matching step via alternative-eIDAS-authentication and add jUnit tests --- .../eidas/v2/service/RegisterSearchService.java | 112 ++++++++++++++------ .../auth/eidas/v2/tasks/AlternativeSearchTask.java | 117 +++++++++++++++------ .../auth/eidas/v2/utils/MatchingTaskUtils.java | 27 ++--- 3 files changed, 181 insertions(+), 75 deletions(-) (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main') 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 232b1d11..047d75ae 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 @@ -1,5 +1,16 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.service; +import java.math.BigInteger; +import java.util.Collections; +import java.util.List; + +import javax.annotation.Nonnull; + +import org.jetbrains.annotations.Nullable; +import org.springframework.stereotype.Service; + +import com.google.common.collect.Streams; + 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.RegisterResult; @@ -13,15 +24,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.ListUtils; -import org.jetbrains.annotations.Nullable; -import org.springframework.stereotype.Service; - -import javax.annotation.Nonnull; -import java.math.BigInteger; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; @Slf4j @Service("registerSearchService") @@ -57,9 +59,24 @@ public class RegisterSearchService { @Nonnull public RegisterStatusResults searchWithPersonIdentifier(SimpleEidasData eidasData) throws WorkflowException { + return searchWithPersonIdentifier(null, eidasData); + + } + + /** + * Search with Person Identifier (eIDAS Pseudonym) in ZMR and ERnP. + * + * @param operationStatus Current register-operation status that contains processing informations + * @param eidasData Received eIDAS data + * @throws WorkflowException In case of a register interaction error + */ + @Nonnull + public RegisterStatusResults searchWithPersonIdentifier(@Nullable RegisterOperationStatus operationStatus, + @Nonnull SimpleEidasData eidasData) throws WorkflowException { try { final ZmrRegisterResult resultsZmr = zmrClient.searchWithPersonIdentifier( - null, eidasData.getPseudonym(), eidasData.getCitizenCountryCode()); + operationStatus != null ? operationStatus.getZmrProcessId() : null, + eidasData.getPseudonym(), eidasData.getCitizenCountryCode()); final List resultsErnp = ernpClient.searchWithPersonIdentifier( eidasData.getPersonalIdentifier()); @@ -71,7 +88,7 @@ public class RegisterSearchService { } } - + /** * Search with MDS (Given Name, Family Name, Date of Birth) in ZMR and ERnP. * @@ -185,39 +202,70 @@ public class RegisterSearchService { } } + //TODO: check this method, because it's different to 'step7aKittProcess'??? /** * Automatic process to fix the register entries. * Called when the alternative eIDAS authn leads to a match in a register. * - * @param registerResult Result of last register search - * @param initialEidasData Received eidas data from initial authentication - * @param altEidasData Received eidas data from alternative authentication + * @param initialSearchResult Register results from initial authentication + * @param initialEidasData Received eIDAS data from initial authentication + * @param altSearchResult Register results from alternative authentication + * @param altEidasData Received eIDAS data from alternative authentication * @return */ - public RegisterStatusResults step7bKittProcess(RegisterStatusResults registerResult, - SimpleEidasData initialEidasData, SimpleEidasData altEidasData) - throws WorkflowException { + public RegisterStatusResults step7bKittProcess( + RegisterStatusResults initialSearchResult, SimpleEidasData initialEidasData, + RegisterStatusResults altSearchResult, SimpleEidasData altEidasData) throws WorkflowException { log.trace("Starting step7bKittProcess"); - // TODO verify with which data this method gets called - if (registerResult.getResultCount() != 1) { + + // check if alternative authentication ends in a single result + if (altSearchResult.getResultCount() != 1) { throw new WorkflowException("step7bKittProcess", "getResultCount() != 1"); + + } + + // check if alternative authentication result is part of initialSearchResults + if (!Streams.concat(initialSearchResult.getResultsZmr().stream(), initialSearchResult.getResultsErnp().stream()) + .filter(el -> { + try { + return altSearchResult.getResult().getBpk().equals(el.getBpk()); + + } catch (WorkflowException e1) { + //can not appear because it's already validated above. + return false; + } + }) + .findFirst() + .isPresent()) { + throw new WorkflowException("step7bKittProcess", + "Register result from alternativ authentication does not fit into intermediate state"); + } + + // perform KITT operations try { - if (registerResult.getResultsZmr().size() == 1) { - RegisterResult entryZmr = registerResult.getResultsZmr().get(0); - ZmrRegisterResult updateAlt = zmrClient - .update(registerResult.getOperationStatus().getZmrProcessId(), entryZmr, altEidasData); - ZmrRegisterResult updateInitial = zmrClient - .update(registerResult.getOperationStatus().getZmrProcessId(), entryZmr, initialEidasData); - return new RegisterStatusResults(registerResult.getOperationStatus(), - ListUtils.union(updateAlt.getPersonResult(), updateInitial.getPersonResult()), - Collections.emptyList()); + if (altSearchResult.getResultsZmr().size() == 1) { + RegisterResult entryZmr = altSearchResult.getResultsZmr().get(0); + + // update ZMR entry by using eIDAS information from initial authentication + zmrClient.update(altSearchResult.getOperationStatus().getZmrProcessId(), entryZmr, initialEidasData); + + // update ZMR entry by using eIDAS information from alternative authentication + ZmrRegisterResult updateAlt = zmrClient.update( + altSearchResult.getOperationStatus().getZmrProcessId(), entryZmr, altEidasData); + + return RegisterStatusResults.fromZmr(updateAlt); + } else { - RegisterResult entryErnp = registerResult.getResultsErnp().get(0); + RegisterResult entryErnp = altSearchResult.getResultsErnp().get(0); + + // update ZMR entry by using eIDAS information from initial authentication + ernpClient.update(entryErnp, initialEidasData); + + // update ZMR entry by using eIDAS information from alternative authentication RegisterResult updateAlt = ernpClient.update(entryErnp, altEidasData); - RegisterResult updateInitial = ernpClient.update(entryErnp, initialEidasData); - return new RegisterStatusResults(registerResult.getOperationStatus(), Collections.emptyList(), - Arrays.asList(updateAlt, updateInitial)); + + return RegisterStatusResults.fromErnp(altSearchResult.operationStatus, Collections.singletonList(updateAlt)); } } catch (final EidasSAuthenticationException e) { throw new WorkflowException("kittMatchedIdentitiess", e.getMessage(), diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java index 4705c56b..e0273d10 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java @@ -23,6 +23,17 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK; + +import java.util.Map; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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.dao.MatchedPersonResult; import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; @@ -40,15 +51,6 @@ import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import eu.eidas.auth.commons.light.ILightResponse; import lombok.extern.slf4j.Slf4j; -import org.jetbrains.annotations.NotNull; -import org.springframework.stereotype.Component; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.util.Map; -import java.util.Objects; - -import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK; /** * Searches registers (ERnP and ZMR) after alternative eIDAS authn, before adding person to SZR. @@ -96,74 +98,123 @@ public class AlternativeSearchTask extends AbstractAuthServletTask { public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { try { - final SimpleEidasData altEidasData = convertEidasAttrToSimpleData(); + final SimpleEidasData altEidasData = convertEidasAttrToSimpleData(); final SimpleEidasData initialEidasData = MatchingTaskUtils.getInitialEidasData(pendingReq); - verifyAlternativeEidasData(altEidasData, initialEidasData); - step11RegisterSearchWithPersonIdentifier(executionContext, altEidasData, initialEidasData); + final RegisterStatusResults intermediateMatchingState = + MatchingTaskUtils.getIntermediateMatchingResult(pendingReq); + + //pre-validation of eIDAS data + preVerifyAlternativeEidasData(altEidasData, initialEidasData, intermediateMatchingState); + + //perform register search operation based on alterantive eIDAS data + step11RegisterSearchWithPersonIdentifier(executionContext, altEidasData, + intermediateMatchingState, initialEidasData); + } catch (WorkflowException e) { throw new TaskExecutionException(pendingReq, "Initial search failed", e); + } catch (final Exception e) { log.error("Initial search failed", e); throw new TaskExecutionException(pendingReq, "Initial search failed with a generic error", e); + } } - private void verifyAlternativeEidasData(SimpleEidasData altEidasData, SimpleEidasData initialEidasData) - throws WorkflowException { + /** + * Pre-validation of eIDAS information. + * + *

Check if country-code and MDS (givenName, familyName, dateOfBirth) matches.

+ * + * @param altEidasData eIDAS data from alternative authentication + * @param initialEidasData eIDAS data from initial authentication + * @param intermediateMatchingState Intermediate matching result + * @throws WorkflowException In case of a validation error + */ + private void preVerifyAlternativeEidasData(SimpleEidasData altEidasData, SimpleEidasData initialEidasData, + RegisterStatusResults intermediateMatchingState) throws WorkflowException { if (initialEidasData == null) { - throw new WorkflowException("step11", "No initial eIDAS authn data"); + throw new WorkflowException("step11", "No initial eIDAS authn data", true); + + } + + if (intermediateMatchingState == null) { + throw new WorkflowException("step11", "No intermediate matching-state", true); + } + if (!Objects.equals(altEidasData.getCitizenCountryCode(), initialEidasData.getCitizenCountryCode())) { throw new WorkflowException("step11", "Country Code of alternative eIDAS authn not matching", true); + } + if (!altEidasData.equalsMds(initialEidasData)) { throw new WorkflowException("step11", "MDS of alternative eIDAS authn does not match initial authn", true); + } } private void step11RegisterSearchWithPersonIdentifier( - ExecutionContext executionContext, SimpleEidasData initialEidasData, SimpleEidasData altEidasData) + ExecutionContext executionContext, SimpleEidasData altEidasData, + RegisterStatusResults intermediateMatchingState, SimpleEidasData initialEidasData) throws WorkflowException, EaafStorageException { try { log.trace("Starting step11RegisterSearchWithPersonIdentifier"); - RegisterStatusResults searchResult = registerSearchService.searchWithPersonIdentifier(altEidasData); - int resultCount = searchResult.getResultCount(); + RegisterStatusResults altSearchResult = registerSearchService.searchWithPersonIdentifier( + intermediateMatchingState.getOperationStatus(), altEidasData); + + int resultCount = altSearchResult.getResultCount(); if (resultCount == 0) { - step12CountrySpecificSearch(executionContext, searchResult.getOperationStatus(), initialEidasData, - altEidasData); + step12CountrySpecificSearch(executionContext, intermediateMatchingState, initialEidasData, + altSearchResult.getOperationStatus(), altEidasData); + } else if (resultCount == 1) { - foundMatchFinalizeTask(searchResult, altEidasData); + log.debug("step11RegisterSearchWithPersonIdentifier find single result. Starting KITT operation ... "); + RegisterStatusResults matchtedResult = registerSearchService.step7bKittProcess( + intermediateMatchingState, initialEidasData, altSearchResult, altEidasData); + + log.debug("KITT operation finished. Finalize matching process ... "); + foundMatchFinalizeTask(matchtedResult, altEidasData); + } else { throw new WorkflowException("step11RegisterSearchWithPersonIdentifier", "More than one entry with unique personal-identifier", true); + } } catch (WorkflowException e) { - //TODO: what we do in case of a workflow error and manual matching are necessary?? log.warn("Workflow error during matching step: {}. Reason: {}", e.getProcessStepName(), e.getErrorReason()); throw e; + } } private void step12CountrySpecificSearch(ExecutionContext executionContext, - RegisterOperationStatus registerOperationStatus, + RegisterStatusResults intermediateMatchingState, SimpleEidasData initialEidasData, + RegisterOperationStatus registerOperationStatus, SimpleEidasData altEidasData) throws EaafStorageException, WorkflowException { - log.trace("Starting 'step12CountrySpecificSearch' ... "); - RegisterStatusResults searchResult = registerSearchService.searchWithCountrySpecifics( + log.trace("Starting 'step12CountrySpecificSearch' ... "); + RegisterStatusResults ccAltSearchResult = registerSearchService.searchWithCountrySpecifics( registerOperationStatus, altEidasData); - if (searchResult.getResultCount() == 0) { + + if (ccAltSearchResult.getResultCount() == 0) { log.trace("'step12CountrySpecificSearch' ends with no result. Forward to GUI based matching step ... "); log.debug("Forward to GUI based matching steps ... "); executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); - } else if (searchResult.getResultCount() == 1) { - log.trace("'step12CountrySpecificSearch' finds a person. Forward to 'step7aKittProcess' step ... "); - registerSearchService.step7bKittProcess(searchResult, initialEidasData, altEidasData); - foundMatchFinalizeTask(searchResult, altEidasData); + + } else if (ccAltSearchResult.getResultCount() == 1) { + log.debug("'step12CountrySpecificSearch' find single result. Starting KITT operation ... "); + RegisterStatusResults matchtedResult = registerSearchService.step7bKittProcess( + intermediateMatchingState, initialEidasData, ccAltSearchResult, altEidasData); + + log.debug("KITT operation finished. Finalize matching process ... "); + foundMatchFinalizeTask(matchtedResult, altEidasData); + } else { throw new WorkflowException("step12CountrySpecificSearch", "More than one entry with unique country-specific information", true); - } + + } } private void foundMatchFinalizeTask(RegisterStatusResults searchResult, SimpleEidasData eidasData) @@ -171,6 +222,10 @@ public class AlternativeSearchTask extends AbstractAuthServletTask { MatchedPersonResult result = MatchedPersonResult.generateFormMatchingResult( searchResult.getResult(), eidasData.getCitizenCountryCode()); MatchingTaskUtils.storeFinalMatchingResult(pendingReq, result); + + //remove intermediate matching-state + MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, null); + } @NotNull diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java index c7631f53..ad641841 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java @@ -1,5 +1,19 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import org.apache.commons.lang3.StringUtils; +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.springframework.lang.NonNull; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + 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; @@ -8,20 +22,9 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchSe import at.gv.egiz.eaaf.core.api.IRequest; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import eu.eidas.auth.commons.attribute.AttributeDefinition; import eu.eidas.auth.commons.attribute.AttributeValue; import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; -import org.slf4j.Logger; -import org.springframework.lang.NonNull; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; public class MatchingTaskUtils { @@ -64,7 +67,7 @@ public class MatchingTaskUtils { RegisterStatusResults.class); } - + /** * Store intermediate matching result into session. * -- cgit v1.2.3