diff options
Diffstat (limited to 'eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java')
-rw-r--r-- | eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java | 387 |
1 files changed, 387 insertions, 0 deletions
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 new file mode 100644 index 00000000..047d75ae --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/RegisterSearchService.java @@ -0,0 +1,387 @@ +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; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.ernp.IErnpClient; +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 lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Service("registerSearchService") +public class RegisterSearchService { + + 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 List<RegisterResult> resultsErnp = ernpClient.searchWithPersonIdentifier( + eidasData.getPersonalIdentifier()); + + 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 List<RegisterResult> resultsErnp = + ernpClient.searchWithMds(eidasData.getGivenName(), eidasData.getFamilyName(), eidasData + .getDateOfBirth()); + + 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()); + final ZmrRegisterResult resultsZmr = + zmrClient.searchCountrySpecific(operationStatus.getZmrProcessId(), + ccSpecificProcessor.generateSearchRequest(eidasData), + eidasData.getCitizenCountryCode()); + return RegisterStatusResults.fromZmr(resultsZmr); + + } else { + // TODO: add search procesfor for ERnP searching + return RegisterStatusResults.fromErnp(operationStatus, Collections.emptyList()); + + } + + } 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 zipcode Provided Zipcode + * @param city Provided City + * @param street Provided street + * @return Results from ZMR or ERnP search + */ + 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); + + } + + /** + * 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 + */ + public RegisterStatusResults step7aKittProcess(RegisterStatusResults registerResult, + SimpleEidasData initialEidasData) throws WorkflowException { + log.trace("Starting step7aKittProcess"); + // TODO verify with which data this method gets called + if (registerResult.getResultCount() != 1) { + throw new WorkflowException("step7aKittProcess", "getResultCount() != 1"); + } + 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); + RegisterResult updateErnp = ernpClient.update(entryErnp, initialEidasData); + return RegisterStatusResults.fromErnp(registerResult.operationStatus, Collections.singletonList(updateErnp)); + } + } catch (final EidasSAuthenticationException e) { + throw new WorkflowException("kittMatchedIdentitiess", e.getMessage(), + !(e instanceof ZmrCommunicationException), e); + } + } + + //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 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 + RegisterResult updateAlt = ernpClient.update(entryErnp, altEidasData); + + return RegisterStatusResults.fromErnp(altSearchResult.operationStatus, Collections.singletonList(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 { + + /** + * 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 { + /** + * 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, List<RegisterResult> resultsErnp) { + return new RegisterStatusResults(new RegisterOperationStatus(result.getProcessId()), + result.getPersonResult(), resultsErnp); + } + + static RegisterStatusResults fromErnp(RegisterOperationStatus status, List<RegisterResult> resultsErnp) { + return new RegisterStatusResults(status, Collections.emptyList(), resultsErnp); + } + + } + +} |