package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr; import java.math.BigInteger; import java.net.URL; import java.text.MessageFormat; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; import javax.xml.ws.BindingProvider; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.NonNull; 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; 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.utils.VersionHolder; import at.gv.bmi.namespace.zmr_su.base._20040201.ClientInfoType; import at.gv.bmi.namespace.zmr_su.base._20040201.Organisation; import at.gv.bmi.namespace.zmr_su.base._20040201.RequestType; import at.gv.bmi.namespace.zmr_su.base._20040201.ResponseType; import at.gv.bmi.namespace.zmr_su.base._20040201.WorkflowInfoClient; import at.gv.bmi.namespace.zmr_su.base._20040201.WorkflowInfoServer; import at.gv.bmi.namespace.zmr_su.base._20040201_.Service; import at.gv.bmi.namespace.zmr_su.base._20040201_.ServiceFault; import at.gv.bmi.namespace.zmr_su.base._20040201_.ServicePort; import at.gv.bmi.namespace.zmr_su.zmr._20040201.EidasIdentitaetAnlageType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.EidasIdentitaetErgebnisType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.EidasSuchdatenType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.ErgebniskriterienType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.NatuerlichePersonErgebnisType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonAendernInfoType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonAendernRequest; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonErgebnisSatzType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonErgebnisType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonReferenzType; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenResponse; import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonensucheInfoType; 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; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; /** * ZMR client implementation for eIDAS matching operations. * * @author tlenz * */ @Slf4j public class ZmrSoapClient extends AbstractSoapClient implements IZmrClient { private static final String ERROR_MATCHING_01 = "module.eidasauth.matching.01"; private static final String ERROR_MATCHING_02 = "module.eidasauth.matching.02"; private static final String ERROR_MATCHING_99 = "module.eidasauth.matching.99"; private static final String LOGMSG_MISSING_CONFIG = "Missing configuration with key: {0}"; private static final String LOGMSG_ZMR_ERROR = "Receive an error from ZMR during '{}' operation with msg: {}"; private static final String LOGMSG_ZMR_RESP_PROCESS = "Proces ZMR response during '{}' operation failes with msg: {}"; private static final String LOGMSG_ZMR_SOAP_ERROR = "ZMR anwser for transaction: {0} with code: {1} and message: {2}"; private static final String PROCESS_GENERAL = "GP_EIDAS"; private static final String PROCESS_TASK_SEARCH = "ZPR_VO_Person_suchen_Meldevorgang"; //private static final String PROCESS_TASK_ADD = "ZPR_VO_Person_anlegen"; private static final String PROCESS_TASK_UPDATE = "ZPR_VO_Person_aendern"; private static final String PROCESS_SEARCH_PERSONAL_IDENTIFIER = "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"; private static final String PROCESS_KITT_IDENITIES_UPDATE = "KITT update dataset"; private static final String CLIENT_DEFAULT = "ZMR Client"; @Autowired VersionHolder versionHolder; private ServicePort zmrClient; @AllArgsConstructor @Getter public static class ZmrRegisterResult { private final List personResult; private final BigInteger processId; } @Override public ZmrRegisterResult searchWithPersonIdentifier(BigInteger zmrProzessId, String personPseudonym, String citizenCountryCode) throws EidasSAuthenticationException { try { // build search request final RequestType req = new RequestType(); // set eIDAS person information final PersonSuchenRequest searchPersonReq = new PersonSuchenRequest(); req.setPersonSuchenRequest(searchPersonReq); final EidasSuchdatenType eidasInfos = new EidasSuchdatenType(); searchPersonReq.getEidasSuchdaten().add(eidasInfos); eidasInfos.setEidasArt(Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER); eidasInfos.setEidasWert(personPseudonym); eidasInfos.setStaatscode2(citizenCountryCode); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_SEARCH, zmrProzessId)); req.setClientInfo(generateClientInfos()); // set additionl search parameters searchPersonReq.setPersonensucheInfo(generateSearchCriteria( PROCESS_SEARCH_PERSONAL_IDENTIFIER, false, true, false)); // request ZMR log.trace("Requesting ZMR for '{}' operation", PROCESS_SEARCH_PERSONAL_IDENTIFIER); final ResponseType resp = zmrClient.service(req, null); // parse ZMR response return processZmrResponse(resp, citizenCountryCode, true, PROCESS_SEARCH_PERSONAL_IDENTIFIER); } catch (final ServiceFault e) { final String errorMsg = extractReasonFromError(e); log.warn(LOGMSG_ZMR_ERROR, PROCESS_SEARCH_PERSONAL_IDENTIFIER, 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_PERSONAL_IDENTIFIER, e.getMessage()); throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); } } @Override public ZmrRegisterResult searchWithMds(BigInteger zmrProzessId, String givenName, String familyName, String dateOfBirth, String citizenCountryCode) throws EidasSAuthenticationException { try { // build search request final RequestType req = new RequestType(); // set eIDAS person information final PersonSuchenRequest searchPersonReq = new PersonSuchenRequest(); req.setPersonSuchenRequest(searchPersonReq); searchPersonReq.setNatuerlichePerson(buildSearchNatPerson(givenName, familyName, dateOfBirth)); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_SEARCH, zmrProzessId)); req.setClientInfo(generateClientInfos()); // set additionl search parameters searchPersonReq.setPersonensucheInfo(generateSearchCriteria( PROCESS_SEARCH_MDS_ONLY, false, true, false)); // request ZMR log.trace("Requesting ZMR for '{}' operation", PROCESS_SEARCH_MDS_ONLY); final ResponseType resp = zmrClient.service(req, null); // parse ZMR response return processZmrResponse(resp, citizenCountryCode, false, PROCESS_SEARCH_MDS_ONLY); } catch (final ServiceFault e) { final String errorMsg = extractReasonFromError(e); log.warn(LOGMSG_ZMR_ERROR, PROCESS_SEARCH_MDS_ONLY, 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_MDS_ONLY, e.getMessage()); throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); } } @Override public ZmrRegisterResult searchCountrySpecific(BigInteger zmrProzessId, PersonSuchenRequest personSearchDao, String citizenCountryCode) throws EidasSAuthenticationException { final String friendlyMsg = MessageFormat.format(PROCESS_SEARCH_COUNTRY_SPECIFIC, citizenCountryCode); try { // build search request final RequestType req = new RequestType(); // set eIDAS person information req.setPersonSuchenRequest(personSearchDao); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_SEARCH, zmrProzessId)); req.setClientInfo(generateClientInfos()); // set additionl search parameters personSearchDao.setPersonensucheInfo(generateSearchCriteria(friendlyMsg, false, true, false)); // request ZMR log.trace("Requesting ZMR for '{}' operation", friendlyMsg); final ResponseType resp = zmrClient.service(req, null); // parse ZMR response return processZmrResponse(resp, citizenCountryCode, true, friendlyMsg); } catch (final ServiceFault e) { final String errorMsg = extractReasonFromError(e); log.warn(LOGMSG_ZMR_ERROR, friendlyMsg, 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, friendlyMsg, e.getMessage()); throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); } } @Override public ZmrRegisterResult update(BigInteger zmrProzessId, RegisterResult registerResult, SimpleEidasData eidData) throws EidasSAuthenticationException { try { //search person with register result, because update needs information from search response PersonErgebnisType zmrPersonToKitt = searchPersonForUpdate(zmrProzessId, registerResult); // select elements that have to be updated Collection eidasDocumentToAdd = selectEidasDocumentsToAdd(zmrPersonToKitt, eidData); if (eidasDocumentToAdd.isEmpty()) { log.info("Find no eIDAS document for update during: {}. Nothing todo on ZMR side", PROCESS_KITT_GENERAL); return new ZmrRegisterResult(Arrays.asList(registerResult), zmrProzessId); } else { log.info("Find #{} eIDAS documents for update during: {}", eidasDocumentToAdd.size(), PROCESS_KITT_GENERAL); // update entry based on selected update info's and results from search response return updatePersonInZmr(zmrProzessId, zmrPersonToKitt, eidasDocumentToAdd, eidData.getCitizenCountryCode()); } } catch (final ServiceFault e) { final String errorMsg = extractReasonFromError(e); log.warn(LOGMSG_ZMR_ERROR, PROCESS_KITT_GENERAL, 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_KITT_GENERAL, e.getMessage()); throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); } } @Override public ZmrRegisterResult searchWithResidenceData(BigInteger zmrProzessId, String givenName, String familyName, 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_TASK_SEARCH, 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 private void initialize() throws EaafConfigurationException { // set-up the ZMR client initializeTechnicalZmrClient(); // validate additional ZMR communication parameters valdiateAdditionalConfigParameters(); } private void initializeTechnicalZmrClient() throws EaafConfigurationException { log.info("Starting ZMR-Client initialization .... "); final URL url = ZmrSoapClient.class.getResource("/wsdl/zmr_client/wsdl/Service.wsdl"); final Service zmrService = new Service(url); zmrClient = zmrService.getService(); final String zmrServiceUrl = basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_ENDPOINT); if (StringUtils.isEmpty(zmrServiceUrl)) { log.error("No ZMR service-URL found. ZMR-Client initalisiation failed."); throw new RuntimeException("No ZMR service URL found. ZMR-Client initalisiation failed."); } // inject handler log.info("Use ZMR service-URL: " + zmrServiceUrl); injectBindingProvider((BindingProvider) zmrClient, CLIENT_DEFAULT, zmrServiceUrl, basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_DEBUG_TRACEMESSAGES, false)); // inject http parameters and SSL context log.debug("Inject HTTP client settings ... "); injectHttpClient(zmrClient, HttpClientConfig.builder() .clientName(CLIENT_DEFAULT) .clientType(CLIENT_DEFAULT) .clientUrl(zmrServiceUrl) .connectionTimeout(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_TIMEOUT_CONNECTION, Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) .responseTimeout(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_TIMEOUT_RESPONSE, Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) .keyStoreConfig(buildKeyStoreConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_TYPE, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_PATH, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_PASSWORD, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_NAME, "ZMR SSL Client-Authentication KeyStore")) .keyAlias(basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYS_ALIAS)) .keyPassword(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEY_PASSWORD)) .trustAll(false) .trustStoreConfig(buildKeyStoreConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_TYPE, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_PATH, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_PASSWORD, Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_NAME, "ZMR SSL Client-Authentication TrustStore")) .build()); } private void valdiateAdditionalConfigParameters() { checkConfigurationValue(Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_ORGANIZATION_NR); checkConfigurationValue(Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_UPDATE_REASON_CODE); } private void checkConfigurationValue(String key) { if (StringUtils.isEmpty(basicConfig.getBasicConfiguration(key))) { throw new RuntimeException(MessageFormat.format(LOGMSG_MISSING_CONFIG, key)); } } @Nonnull private WorkflowInfoClient generateWorkFlowInfos(@Nonnull String subStepName, @Nullable BigInteger prozessInstanzId) { final WorkflowInfoClient infos = new WorkflowInfoClient(); infos.setProzessName(PROCESS_GENERAL); infos.setVorgangName(subStepName); //set processId that we received from ZMR before, if already available if (prozessInstanzId != null) { infos.setProzessInstanzID(prozessInstanzId); } return infos; } @Nonnull private PersonensucheInfoType generateSearchCriteria(String infoElement, boolean searchInErnp, boolean searchInZmrHistory, boolean includeHistoryResults) { final PersonensucheInfoType personSearchInfo = new PersonensucheInfoType(); final SuchkriterienType searchCriteria = new SuchkriterienType(); final ErgebniskriterienType resultCriteria = new ErgebniskriterienType(); personSearchInfo.setBezugsfeld(infoElement); personSearchInfo.setSuchkriterien(searchCriteria); personSearchInfo.setErgebniskriterien(resultCriteria); // TODO: are these flags valid? searchCriteria.setInclusivERnP(searchInErnp); searchCriteria.setInclusivHistorie(searchInZmrHistory); // TODO: check 'processSearchPersonResponse' if we change this to 'true' resultCriteria.setInclusivHistorie(includeHistoryResults); // TODO: are these flags valid? personSearchInfo.setAnzahlSaetze(10); return personSearchInfo; } @Nonnull private ClientInfoType generateClientInfos() { final ClientInfoType clientInfo = new ClientInfoType(); final Organisation clientOrganisation = new Organisation(); clientInfo.setOrganisation(clientOrganisation); // set client information clientInfo.setClient(MessageFormat.format(Constants.CLIENT_INFO, versionHolder.getVersion())); // set Behoerdennummer as organization identifier clientOrganisation.setBehoerdenNr(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_ORGANIZATION_NR)); return clientInfo; } @Nonnull private String extractReasonFromError(ServiceFault e) { if (e.getFaultInfo() != null) { return MessageFormat.format(LOGMSG_ZMR_SOAP_ERROR, e.getFaultInfo().getServerTransaktionNr().toString(), e.getFaultInfo().getErrorCode(), e.getFaultInfo().getErrorMessage()); } else { log.error("ZMR response without error code", e); return e.getMessage(); } } @Nonnull private ZmrRegisterResult processZmrResponse(@Nonnull ResponseType resp, @Nonnull String citizenCountryCode, boolean forceSinglePersonMatch, @Nonnull String processStepFiendlyname) throws EaafAuthenticationException { final PersonSuchenResponse searchPersonResp = resp.getPersonSuchenResponse(); if (searchPersonResp.getPersonensuchergebnis() == null || searchPersonResp.getPersonensuchergebnis().getPersonErgebnisSatz().isEmpty()) { log.debug("ZMR result contains NO 'Personensuchergebnis' or 'PersonErgebnisSatz' is empty"); return new ZmrRegisterResult(Collections.emptyList(), extractZmrProcessId(resp.getWorkflowInfoServer())); } else { log.debug("Get #{} person results from '{}' operation", searchPersonResp.getPersonensuchergebnis().getGefundeneSaetze(), processStepFiendlyname); if (forceSinglePersonMatch) { return new ZmrRegisterResult(processSearchPersonResponseSingleResult( searchPersonResp.getPersonensuchergebnis().getPersonErgebnisSatz(), citizenCountryCode, processStepFiendlyname), extractZmrProcessId(resp.getWorkflowInfoServer())); } else { return new ZmrRegisterResult(processSearchPersonResponse( searchPersonResp.getPersonensuchergebnis().getPersonErgebnisSatz(), citizenCountryCode), extractZmrProcessId(resp.getWorkflowInfoServer())); } } } private BigInteger extractZmrProcessId(WorkflowInfoServer workflowInfoServer) { return workflowInfoServer != null ? workflowInfoServer.getProzessInstanzID() : null; } @Nonnull private List processSearchPersonResponse( @Nonnull List personErgebnisSatz, @Nonnull String citizenCountryCode) throws EaafAuthenticationException { return personErgebnisSatz.stream() .map(el -> { try { return processPersonResult(el, citizenCountryCode); } catch (final EaafAuthenticationException e) { log.warn("Skip ZMR person result by reason: {}", e.getMessage(), e); return null; } }) .filter(Objects::nonNull) .collect(Collectors.toList()); } @NonNull private List processSearchPersonResponseSingleResult( @Nonnull List personErgebnisSatz, @Nonnull String citizenCountryCode, String processStepFiendlyname) throws EaafAuthenticationException { if (personErgebnisSatz.size() > 1) { log.error("Find more-than-one ZMR entry with search criteria that has to be unique"); throw new WorkflowException(processStepFiendlyname, "Find more-than-one ZMR entry with search criteria that has to be unique", true); } else { return Arrays.asList(processPersonResult(personErgebnisSatz.get(0), citizenCountryCode)); } } @Nonnull private RegisterResult processPersonResult( @Nonnull PersonErgebnisSatzType personEl, @Nonnull String citizenCountryCode) throws EaafAuthenticationException { // TODO: maybe check on 'null' if ERnP data is also allowed log.debug("Find #{} data sets in person information", personEl.getPersonendaten().getPersonErgebnis().size()); if (personEl.getPersonendaten().getPersonErgebnis().size() > 1) { log.error("Find more than on PersoenErgebnis in Personendaten."); throw new EaafAuthenticationException(ERROR_MATCHING_02, null); } else { return mapZmrResponseToRegisterResult( personEl.getPersonendaten().getPersonErgebnis().get(0), citizenCountryCode); } } @Nonnull private RegisterResult mapZmrResponseToRegisterResult(@Nonnull PersonErgebnisType person, @Nonnull String citizenCountryCode) { // TODO: kann ich bei historischen daten davon ausgehen dass die Reihenfolge der // Ergebnisse von aktuell --> alt ist? // build result return RegisterResult.builder() .pseudonym(selectAllEidasDocument(person, citizenCountryCode, Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER)) .familyName(person.getNatuerlichePerson().getPersonenName().getFamilienname()) .givenName(person.getNatuerlichePerson().getPersonenName().getVorname()) .dateOfBirth(person.getNatuerlichePerson().getGeburtsdatum()) .bpk(extractBpkZp(person.getNatuerlichePerson())) .placeOfBirth(selectSingleEidasDocument(person, citizenCountryCode, Constants.eIDAS_ATTRURN_PLACEOFBIRTH)) .birthName(selectSingleEidasDocument(person, citizenCountryCode, Constants.eIDAS_ATTRURN_BIRTHNAME)) .build(); } private String extractBpkZp(NatuerlichePersonErgebnisType natuerlichePerson) { String bpk = natuerlichePerson.getIdentification().stream() .filter(el -> Constants.MATCHING_INTERNAL_BPK_TARGET.equals(el.getType())) .findFirst() .map(el -> el.getValue()) .orElse(null); if (StringUtils.isEmpty(bpk)) { //TODO: should we throw an error in that case? log.warn("ZMR response contains no 'bPK' for target: 'ZP'"); } return bpk; } /** * Get all eIDAS document with the specified country code and document type. * * @param person Person information from ZMR * @param citizenCountryCode Country code of the eIDAS attribute * @param eidasAttrurnPersonalidentifier eIDAS attribute identifier * @return {@link List} of eIDAS attribute values or an empty list if's not * found */ @NonNull private List selectAllEidasDocument(PersonErgebnisType person, String citizenCountryCode, String eidasAttrurnPersonalidentifier) { return person.getEidasIdentitaet().stream() .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getEidasArt()) && el.getStaatscode2().equals(citizenCountryCode)) .map(el -> el.getEidasWert()) .collect(Collectors.toList()); } /** * Get the first eIDAS document with the specified country code and document * type. * * @param person Person information from ZMR * @param citizenCountryCode Country code of the eIDAS attribute * @param eidasAttrurnPersonalidentifier eIDAS attribute identifier * @return Value of this eIDAS attribute or null if's not found */ @Nullable private String selectSingleEidasDocument(PersonErgebnisType person, String citizenCountryCode, String eidasAttrurnPersonalidentifier) { return person.getEidasIdentitaet().stream() .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getEidasArt()) && el.getStaatscode2().equals(citizenCountryCode)) .findFirst() .map(el -> el.getEidasWert()) .orElse(null); } private PersonErgebnisType searchPersonForUpdate(BigInteger zmrProzessId, RegisterResult registerResult) throws ServiceFault, WorkflowException { // build search request final RequestType req = new RequestType(); // set eIDAS person information final PersonSuchenRequest searchPersonReq = new PersonSuchenRequest(); req.setPersonSuchenRequest(searchPersonReq); NatuerlichePersonTyp natPersonInfos = new NatuerlichePersonTyp(); searchPersonReq.setNatuerlichePerson(natPersonInfos); PersonenNameTyp nameInfo = new PersonenNameTyp(); natPersonInfos.setPersonenName(nameInfo); IdentificationType bpkInfo = new IdentificationType(); natPersonInfos.getIdentification().add(bpkInfo); // set MDS nameInfo.setVorname(registerResult.getGivenName()); nameInfo.setFamilienname(registerResult.getFamilyName()); natPersonInfos.setGeburtsdatum(registerResult.getDateOfBirth()); //set bPK bpkInfo.setValue(registerResult.getBpk()); bpkInfo.setType(EaafConstants.URN_PREFIX_CDID + "ZP"); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_SEARCH, zmrProzessId)); req.setClientInfo(generateClientInfos()); // set additionl search parameters searchPersonReq.setPersonensucheInfo(generateSearchCriteria( PROCESS_KITT_IDENITIES_GET, false, true, false)); // request ZMR log.trace("Requesting ZMR for '{}' operation", PROCESS_KITT_IDENITIES_GET); ResponseType resp = zmrClient.service(req, null); log.trace("Receive response from ZMR for '{}' operation", PROCESS_KITT_IDENITIES_GET); return extractPersonResultForUpdaste(resp); } private PersonErgebnisType extractPersonResultForUpdaste(ResponseType resp) throws WorkflowException { final PersonSuchenResponse searchPersonResp = resp.getPersonSuchenResponse(); if (searchPersonResp.getPersonensuchergebnis() == null || searchPersonResp.getPersonensuchergebnis().getPersonErgebnisSatz().isEmpty()) { log.error("ZMR result contains NO 'Personensuchergebnis' or 'PersonErgebnisSatz' is empty"); throw new WorkflowException(PROCESS_KITT_IDENITIES_GET, "Find NO data-set with already matchted eID during ZMR KITT process"); } else { List personErgebnisSatz = searchPersonResp.getPersonensuchergebnis().getPersonErgebnisSatz(); if (personErgebnisSatz.size() > 1) { log.error("Find more than on person with aleady matched information."); throw new WorkflowException(PROCESS_KITT_IDENITIES_GET, "Find MORE-THAN-ONE data-sets with already matchted eID during ZMR KITT process"); } else { return personErgebnisSatz.get(0).getPersonendaten().getPersonErgebnis().get(0); } } } 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) { //TODO: maybe we should re-factor SimpleEidasData to a generic data-model to facilitate arbitrary eIDAS attributes Set result = new HashSet<>(); addEidasDocumentIfNotAvailable(result, zmrPersonToKitt, eidData.getCitizenCountryCode(), Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER, eidData.getPseudonym(), true); addEidasDocumentIfNotAvailable(result, zmrPersonToKitt, eidData.getCitizenCountryCode(), Constants.eIDAS_ATTRURN_PLACEOFBIRTH, eidData.getPlaceOfBirth(), false); addEidasDocumentIfNotAvailable(result, zmrPersonToKitt, eidData.getCitizenCountryCode(), Constants.eIDAS_ATTRURN_BIRTHNAME, eidData.getBirthName(), false); return result; } private void addEidasDocumentIfNotAvailable(Set result, PersonErgebnisType zmrPersonToKitt, String citizenCountryCode, String attrName, String attrValue, boolean allowMoreThanOneEntry) { if (StringUtils.isEmpty(attrValue)) { log.trace("No eIDAS document: {}. Nothing todo for KITT process ... ", attrName); return; } // check if eIDAS attribute is already includes an eIDAS-Document boolean alreadyExist = zmrPersonToKitt.getEidasIdentitaet().stream() .filter(el -> el.getEidasWert().equals(attrValue) && el.getEidasArt().equals(attrName) && el.getStaatscode2().equals(citizenCountryCode)) .findAny() .isPresent(); if (!alreadyExist) { // check eIDAS documents already contains a document with this pair of country-code and attribute-name Optional oneDocWithNameExists = zmrPersonToKitt.getEidasIdentitaet().stream() .filter(el -> el.getStaatscode2().equals(citizenCountryCode) && el.getEidasArt().equals(attrName)) .findAny(); if (!allowMoreThanOneEntry && oneDocWithNameExists.isPresent() && !oneDocWithNameExists.get().getEidasWert().equals(attrValue)) { log.warn("eIDAS document: {} already exists for country: {} but attribute-value does not match. " + "Skip update process because no multi-value allowed for this ... ", attrName, citizenCountryCode); } else { EidasIdentitaetAnlageType eidasDocToAdd = new EidasIdentitaetAnlageType(); eidasDocToAdd.setStaatscode2(citizenCountryCode); eidasDocToAdd.setEidasArt(attrName); eidasDocToAdd.setEidasWert(attrValue); log.info("Add eIDAS document: {} for country: {} to ZMR person", attrName, citizenCountryCode); result.add(eidasDocToAdd); } } else { log.debug("eIDAS document: {} already exists for country: {}. Skip update process for this ... ", attrName, citizenCountryCode); } } private ZmrRegisterResult updatePersonInZmr(BigInteger zmrProzessId, PersonErgebnisType zmrPersonToKitt, Collection eidasDocumentToAdd, String citizenCountryCode) throws ServiceFault { final RequestType req = new RequestType(); // set work-flow client information req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_UPDATE, zmrProzessId)); req.setClientInfo(generateClientInfos()); PersonAendernRequest updateReq = new PersonAendernRequest(); req.setPersonAendernRequest(updateReq); // set reference elements for person update PersonReferenzType updateRef = new PersonReferenzType(); updateRef.setTechnisch(zmrPersonToKitt.getEntityErgebnisReferenz().getTechnisch()); updateRef.setZMRZahl(zmrPersonToKitt.getZMRZahl()); updateReq.setPersonReferenz(updateRef); // set reason from this update PersonAendernInfoType updateInfo = new PersonAendernInfoType(); updateInfo.setGrundCode(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_UPDATE_REASON_CODE)); updateInfo.setGrundFreitext(basicConfig.getBasicConfiguration( Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_UPDATE_REASON_TEXT)); updateReq.setPersonAendernInfo(updateInfo); // add new eIDAS documents that should be added updateReq.getEidasIdentitaetAnlage().addAll(eidasDocumentToAdd); // request ZMR log.trace("Requesting ZMR for '{}' operation", PROCESS_KITT_IDENITIES_UPDATE); ResponseType resp = zmrClient.service(req, null); log.trace("Receive response from ZMR for '{}' operation", PROCESS_KITT_IDENITIES_UPDATE); return new ZmrRegisterResult(Arrays.asList( mapZmrResponseToRegisterResult(resp.getPersonAendernResponse().getPersonErgebnis(), citizenCountryCode)), extractZmrProcessId(resp.getWorkflowInfoServer())); } }