aboutsummaryrefslogtreecommitdiff
path: root/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java
diff options
context:
space:
mode:
Diffstat (limited to 'modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java')
-rw-r--r--modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java447
1 files changed, 447 insertions, 0 deletions
diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java
new file mode 100644
index 00000000..5e1e4839
--- /dev/null
+++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java
@@ -0,0 +1,447 @@
+package at.asitplus.eidas.specific.modules.auth.eidas.v2.service;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.util.Collections;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import org.jetbrains.annotations.Nullable;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Service;
+
+import com.google.common.collect.Streams;
+
+import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.ErnpRestClient.ErnpRegisterResult;
+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;
+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.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service("registerSearchService")
+public class RegisterSearchService {
+
+ private static final String LOG_MSG_RESULTS = "Matching operation: {} results: ZMR: {} | ERnP: {}";
+
+ private final IZmrClient zmrClient;
+ private final IErnpClient ernpClient;
+
+ private final List<CountrySpecificDetailSearchProcessor> handlers;
+
+ /**
+ * Service that combines ZMR and ERnP register search operations.
+ *
+ * @param handlers Available country-specific search processors
+ * @param zmrClient ZMR client
+ * @param ernpClient ERnP client
+ */
+ public RegisterSearchService(List<CountrySpecificDetailSearchProcessor> handlers, IZmrClient zmrClient,
+ IErnpClient ernpClient) {
+ this.zmrClient = zmrClient;
+ this.ernpClient = ernpClient;
+ this.handlers = handlers;
+ log.info("Init with #{} search services for country-specific details", handlers.size());
+
+ }
+
+ /**
+ * Search with Person Identifier (eIDAS Pseudonym) in ZMR and ERnP.
+ *
+ * @param eidasData Received eIDAS data
+ * @throws WorkflowException In case of a register interaction error
+ */
+ @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(
+ operationStatus != null ? operationStatus.getZmrProcessId() : null,
+ eidasData.getPseudonym(), eidasData.getCitizenCountryCode());
+ final ErnpRegisterResult resultsErnp = ernpClient.searchWithPersonIdentifier(
+ eidasData.getPseudonym(), eidasData.getCitizenCountryCode());
+
+ log.debug(LOG_MSG_RESULTS, "seachByPersonalId",
+ resultsZmr.getPersonResult().size(), resultsErnp.getPersonResult().size());
+
+ return RegisterStatusResults.fromZmrAndErnp(resultsZmr, resultsErnp);
+
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("searchWithPersonalIdentifier", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+
+ }
+ }
+
+ /**
+ * Search with MDS (Given Name, Family Name, Date of Birth) 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 searchWithMds(RegisterOperationStatus operationStatus, SimpleEidasData eidasData)
+ throws WorkflowException {
+ try {
+ final ZmrRegisterResult resultsZmr =
+ zmrClient.searchWithMds(operationStatus.getZmrProcessId(), eidasData.getGivenName(),
+ eidasData.getFamilyName(), eidasData.getDateOfBirth(), eidasData.getCitizenCountryCode());
+
+ final ErnpRegisterResult resultsErnp =
+ ernpClient.searchWithMds(eidasData.getGivenName(),
+ eidasData.getFamilyName(), eidasData.getDateOfBirth(), eidasData.getCitizenCountryCode());
+
+ log.debug(LOG_MSG_RESULTS, "seachByMDS",
+ resultsZmr.getPersonResult().size(), resultsErnp.getPersonResult().size());
+
+ return RegisterStatusResults.fromZmrAndErnp(resultsZmr, resultsErnp);
+
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("searchWithMDSOnly", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+
+ }
+ }
+
+ /**
+ * Search with country-specific parameters based on information from available
+ * {@link CountrySpecificDetailSearchProcessor} implementations.
+ *
+ * @param operationStatus Current register-operation status that contains processing informations
+ * @param eidasData Receive eIDAS eID information
+ * @return Results from ZMR or ERnP search
+ * @throws WorkflowException In case of a register interaction error
+ */
+ @Nonnull
+ public RegisterStatusResults searchWithCountrySpecifics(RegisterOperationStatus operationStatus,
+ SimpleEidasData eidasData) throws WorkflowException {
+ try {
+ @Nullable final CountrySpecificDetailSearchProcessor ccSpecificProcessor = findSpecificProcessor(eidasData);
+ if (ccSpecificProcessor != null) {
+ log.debug("Selecting country-specific search processor: {}", ccSpecificProcessor.getName());
+ PersonSuchenRequest ccSpecificSearchReq = ccSpecificProcessor.generateSearchRequest(eidasData);
+
+ // search in ZMR
+ final ZmrRegisterResult resultsZmr =
+ zmrClient.searchCountrySpecific(operationStatus.getZmrProcessId(),
+ ccSpecificSearchReq, eidasData.getCitizenCountryCode());
+
+ //search in ERnP
+ ErnpRegisterResult resultErnp = ernpClient.searchCountrySpecific(
+ ccSpecificSearchReq, eidasData.getCitizenCountryCode());
+
+ log.debug(LOG_MSG_RESULTS, "seachByCountrySpecifics",
+ resultsZmr.getPersonResult().size(), resultErnp.getPersonResult().size());
+
+ return RegisterStatusResults.fromZmrAndErnp(resultsZmr, resultErnp);
+
+ } else {
+ return RegisterStatusResults.fromEmpty(operationStatus);
+
+ }
+
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("searchWithCountrySpecifics", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+
+ }
+ }
+
+ /**
+ * Search with residence infos.
+ *
+ * @param operationStatus Current register-operation status that contains processing informations
+ * @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,
+ 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
+ */
+
+ log.debug(LOG_MSG_RESULTS, "seachByResidence",
+ resultsZmr.getPersonResult().size(), 0);
+
+ return RegisterStatusResults.fromZmr(resultsZmr);
+
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("searchWithResidenceInformation", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+
+ }
+ }
+
+ /**
+ * Automatic process to fix the register entries.
+ * Called when the initial eIDAS authn leads to a match in a register.
+ *
+ * @param registerResult Result of last register search
+ * @param initialEidasData Received eidas data from initial authn
+ * @return
+ */
+ @NonNull
+ public RegisterStatusResults step7aKittProcess(RegisterStatusResults registerResult,
+ SimpleEidasData initialEidasData) throws WorkflowException {
+ log.trace("Starting step7aKittProcess");
+
+ // check if only one single result was found
+ if (registerResult.getResultCount() != 1) {
+ throw new WorkflowException("step7aKittProcess", "getResultCount() != 1");
+
+ }
+
+ // perform updated operation in respect to register results
+ try {
+ if (registerResult.getResultsZmr().size() == 1) {
+ RegisterResult entryZmr = registerResult.getResultsZmr().get(0);
+ ZmrRegisterResult updateZmr = zmrClient
+ .update(registerResult.getOperationStatus().getZmrProcessId(), entryZmr, initialEidasData);
+ return RegisterStatusResults.fromZmr(updateZmr);
+
+ } else {
+ RegisterResult entryErnp = registerResult.getResultsErnp().get(0);
+ ErnpRegisterResult updateErnp = ernpClient.update(entryErnp, initialEidasData);
+ return RegisterStatusResults.fromErnp(registerResult.operationStatus, updateErnp);
+
+ }
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("kittMatchedIdentitiess", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+
+ }
+ }
+
+ /**
+ * Automatic process to fix the register entries.
+ * Called when the alternative eIDAS authn leads to a match in a register.
+ *
+ * <p>This method perform two additional operations:
+ * <ul>
+ * <li>Use bPK to check if <i>altSearchResult</i> is part of <i>initialSearchResult</i>.</li>
+ * <li>Update register entry twice, be using information from alternative authentication <i>altEidasData</i>
+ * and from initial authentication <i>initialEidasData</i>.</li>
+ * </ul>
+ * </p>
+ *
+ * @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 initialSearchResult, SimpleEidasData initialEidasData,
+ RegisterStatusResults altSearchResult, SimpleEidasData altEidasData) throws WorkflowException {
+ log.trace("Starting step7bKittProcess");
+
+ // 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 (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 = 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
+ ErnpRegisterResult updateAlt = ernpClient.update(entryErnp, altEidasData);
+
+ return RegisterStatusResults.fromErnp(altSearchResult.getOperationStatus(), updateAlt);
+ }
+ } catch (final EidasSAuthenticationException e) {
+ throw new WorkflowException("kittMatchedIdentitiess", e.getMessage(),
+ !(e instanceof ZmrCommunicationException), e);
+ }
+ }
+
+ @Nullable
+ private CountrySpecificDetailSearchProcessor findSpecificProcessor(SimpleEidasData eidasData) {
+ final String citizenCountry = eidasData.getCitizenCountryCode();
+ for (final CountrySpecificDetailSearchProcessor processor : handlers) {
+ if (processor.canHandle(citizenCountry, eidasData)) {
+ log.debug("Found suitable search handler for {} by using: {}", citizenCountry, processor.getName());
+ return processor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Register releated information that are needed for any request.
+ *
+ * @author tlenz
+ */
+ @AllArgsConstructor
+ @Getter
+ public static class RegisterOperationStatus implements Serializable {
+
+ private static final long serialVersionUID = -1037357883275379796L;
+
+ /**
+ * ZMR internal processId that is required for any further request in the same process.
+ */
+ private BigInteger zmrProcessId;
+
+
+ }
+
+
+ /**
+ * Response container for {@link RegisterSearchService} that holds a set of {@link RegisterResult}.
+ *
+ * @author tlenz
+ */
+ @Getter
+ @RequiredArgsConstructor
+ public static class RegisterStatusResults implements Serializable {
+
+ private static final long serialVersionUID = -2489125033838373511L;
+
+ /**
+ * Operation status for this result.
+ */
+ private final RegisterOperationStatus operationStatus;
+
+ /**
+ * Current ZMR search result.
+ */
+ private final List<RegisterResult> resultsZmr;
+
+ /**
+ * Current ERnP search result.
+ */
+ private final List<RegisterResult> resultsErnp;
+
+ /**
+ * Get sum of ZMR and ERnP results.
+ *
+ * @return number of results
+ */
+ public int getResultCount() {
+ return resultsZmr.size() + resultsErnp.size();
+ }
+
+ /**
+ * Verifies that there is only one match and returns the bpk.
+ *
+ * @return bpk bpk of the match
+ * @throws WorkflowException if multiple results have been found
+ */
+ public String getBpk() throws WorkflowException {
+ if (getResultCount() != 1) {
+ throw new WorkflowException("readRegisterResults", "getResultCount() != 1");
+
+ }
+ return getResult().getBpk();
+ }
+
+ /**
+ * Returns the results, if there is exactly one, throws exception otherwise.
+ *
+ * @return The result
+ * @throws WorkflowException Results does not contain exactly one result
+ */
+ public RegisterResult getResult() throws WorkflowException {
+ if (getResultCount() != 1) {
+ throw new WorkflowException("readRegisterResults", "getResultCount() != 1");
+ }
+ if (resultsZmr.size() == 1) {
+ return resultsZmr.get(0);
+
+ } else {
+ return resultsErnp.get(0);
+
+ }
+ }
+
+ static RegisterStatusResults fromZmr(ZmrRegisterResult result) {
+ return new RegisterStatusResults(new RegisterOperationStatus(result.getProcessId()),
+ result.getPersonResult(), Collections.emptyList());
+ }
+
+ static RegisterStatusResults fromZmrAndErnp(ZmrRegisterResult result, ErnpRegisterResult resultErnp) {
+ return new RegisterStatusResults(new RegisterOperationStatus(result.getProcessId()),
+ result.getPersonResult(), resultErnp.getPersonResult());
+ }
+
+ static RegisterStatusResults fromErnp(RegisterOperationStatus status, ErnpRegisterResult updateErnp) {
+ return new RegisterStatusResults(status, Collections.emptyList(), updateErnp.getPersonResult());
+ }
+
+ static RegisterStatusResults fromEmpty(RegisterOperationStatus status) {
+ return new RegisterStatusResults(status, Collections.emptyList(), Collections.emptyList());
+ }
+
+ }
+
+}