diff options
| author | Thomas <> | 2022-05-12 12:23:53 +0200 | 
|---|---|---|
| committer | Thomas <> | 2022-05-12 12:23:53 +0200 | 
| commit | cbcc63885156c0b4039d5e71f943e760faaa5d78 (patch) | |
| tree | f26bebf7071e10bf4987114c47148d9b6c4fe88c /modules/authmodule-eIDAS-v2/src/main/java | |
| parent | b3f78f57ff8da8a82af57377eaabea22031582e9 (diff) | |
| parent | bcfcee7879f65fe55911b566754b860a2454583a (diff) | |
| download | National_eIDAS_Gateway-cbcc63885156c0b4039d5e71f943e760faaa5d78.tar.gz National_eIDAS_Gateway-cbcc63885156c0b4039d5e71f943e760faaa5d78.tar.bz2 National_eIDAS_Gateway-cbcc63885156c0b4039d5e71f943e760faaa5d78.zip | |
Merge branch 'feature/matching_base' into nightlybuild_matching
# Conflicts:
#	modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrClient.java
#	modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java
#	modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java
#	modules/authmodule-eIDAS-v2/src/test/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/test/SzrClientTestProduction.java
Diffstat (limited to 'modules/authmodule-eIDAS-v2/src/main/java')
57 files changed, 8810 insertions, 1298 deletions
| diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java index 40b953b1..0b5d086d 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java @@ -27,11 +27,32 @@ import at.gv.egiz.eaaf.core.api.data.EaafConstants;  public class Constants { +  //TODO: should we make it configurable? +  public static final String MATCHING_INTERNAL_BPK_TARGET = EaafConstants.URN_PREFIX_CDID + "ZP"; + +  public static final String ERRORCODE_00 = "module.eidasauth.00"; +    public static final String DATA_REQUESTERID = "req_requesterId";    public static final String DATA_PROVIDERNAME = "req_providerName";    public static final String DATA_REQUESTED_LOA_LIST = "req_requestedLoA";    public static final String DATA_REQUESTED_LOA_COMPERISON = "req_requestedLoAComperision";    public static final String DATA_FULL_EIDAS_RESPONSE = "resp_fulleIDASResponse"; +  public static final String DATA_FULL_EIDAS_RESPONSE_ALTERNATIVE = "resp_fulleIDASResponseAlternative"; + +  /** +   * Stored before Step 2 from Matching Concept, input from user eIDAS authn. +   */ +  public static final String DATA_SIMPLE_EIDAS = "matching_simple_eidas_data"; + +  /** +   * Stored intermediate mathing results where matching is still on-going. +   */ +  public static final String DATA_INTERMEDIATE_RESULT = "matching_intermediate_result"; + +  /** +   * Stored after Step 8 from Matching Concept, results from search in registers with MDS. +   */ +  public static final String DATA_PERSON_MATCH_RESULT = "matching_result";    // templates for post-binding forwarding    public static final String TEMPLATE_POST_FORWARD_NAME = "eidas_node_forward.html"; @@ -43,10 +64,10 @@ public class Constants {    public static final String CONIG_PROPS_EIDAS_PREFIX = "auth.eIDAS";    public static final String CONIG_PROPS_EIDAS_WORKAROUND_STAGING_MS_CONNECTOR = -      CONIG_PROPS_EIDAS_PREFIX + ".workarounds.staging.msconnector.endpoint"; +      CONIG_PROPS_EIDAS_PREFIX + ".workarounds.staging.msconnector.endpoint";   +  public static final String CONIG_PROPS_EIDAS_IS_TEST_IDENTITY =  +      CONIG_PROPS_EIDAS_PREFIX  + ".eid.testidentity.default"; -  public static final String CONIG_PROPS_EIDAS_IS_TEST_IDENTITY = CONIG_PROPS_EIDAS_PREFIX  -      + ".eid.testidentity.default";    public static final String CONIG_PROPS_EIDAS_NODE = CONIG_PROPS_EIDAS_PREFIX + ".node_v2";    public static final String CONIG_PROPS_EIDAS_NODE_COUNTRYCODE = CONIG_PROPS_EIDAS_NODE + ".countrycode";    public static final String CONIG_PROPS_EIDAS_NODE_PUBLICSECTOR_TARGETS = CONIG_PROPS_EIDAS_NODE @@ -62,20 +83,20 @@ public class Constants {        CONIG_PROPS_EIDAS_NODE + ".attributes.requested.{0}.onlynatural";    public static final String CONIG_PROPS_EIDAS_NODE_ATTRIBUTES_REQUESTED_REPRESENTATION =        CONIG_PROPS_EIDAS_NODE + ".attributes.requested.representation"; -   +    public static final String CONIG_PROPS_EIDAS_NODE_REQUESTERID_USE_HASHED_VERSION =        CONIG_PROPS_EIDAS_NODE + ".requesterId.useHashedForm";    public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_USE_STATIC_REQUESTERID_FOR_LUX =        CONIG_PROPS_EIDAS_NODE + ".requesterId.lu.useStaticRequesterForAll"; -   +    public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_ADD_ALWAYS_PROVIDERNAME = -      CONIG_PROPS_EIDAS_NODE + ".workarounds.addAlwaysProviderName";   +      CONIG_PROPS_EIDAS_NODE + ".workarounds.addAlwaysProviderName";    public static final String CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER =        CONIG_PROPS_EIDAS_NODE + ".workarounds.useRequestIdAsTransactionIdentifier"; -   -  public static final String CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT =  + +  public static final String CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT =        CONIG_PROPS_EIDAS_NODE + ".requested.nameIdFormat"; -   +    public static final String CONIG_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP = CONIG_PROPS_EIDAS_NODE        + ".staticProviderNameForPublicSPs";    public static final String DEFAULT_PROPS_EIDAS_NODE_STATIC_PROVIDERNAME_FOR_PUBLIC_SP = "Austria"; @@ -83,6 +104,81 @@ public class Constants {    public static final String FORWARD_METHOD_POST = "POST";    public static final String FORWARD_METHOD_GET = "GET"; +   +  // Common SSL client configuration  +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT = CONIG_PROPS_EIDAS_PREFIX + ".client.common"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_PATH = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.keyStore.path"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_PASSWORD = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.keyStore.password"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_TYPE = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.keyStore.type"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_NAME = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.keyStore.name"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYS_ALIAS = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.key.alias"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEY_PASSWORD = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.key.password"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_TRUSTSTORE_PATH = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.trustStore.path"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_TRUSTSTORE_PASSWORD = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.trustStore.password"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_TRUSTSTORE_TYPE = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.trustStore.type"; +  public static final String CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_TRUSTSTORE_NAME = CONIG_PROPS_EIDAS_COMMON_CLIENT +      + ".ssl.trustStore.name"; +   +     +  // ZMR Client configuration properties +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT = CONIG_PROPS_EIDAS_PREFIX + ".zmrclient"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_ENDPOINT = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".endpoint"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_DEBUG_TRACEMESSAGES = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".debug.logfullmessages"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_TIMEOUT_CONNECTION = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".timeout.connection"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_TIMEOUT_RESPONSE = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".timeout.response"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_ORGANIZATION_NR = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".req.organisation.behoerdennr"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_UPDATE_REASON_CODE = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".req.update.reason.code"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_UPDATE_REASON_TEXT = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".req.update.reason.text"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_PATH = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.keyStore.path"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_PASSWORD = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.keyStore.password"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_TYPE = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.keyStore.type"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYSTORE_NAME = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.keyStore.name"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEYS_ALIAS = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.key.alias"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_KEY_PASSWORD = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.key.password"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_PATH = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.trustStore.path"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_PASSWORD = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.trustStore.password"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_TYPE = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.trustStore.type"; +  public static final String CONIG_PROPS_EIDAS_ZMRCLIENT_SSL_TRUSTSTORE_NAME = CONIG_PROPS_EIDAS_ZMRCLIENT +      + ".ssl.trustStore.name"; +   +  // ErnP Client configuration properties +  public static final String CONIG_PROPS_EIDAS_ERNPCLIENT = CONIG_PROPS_EIDAS_PREFIX + ".ernpclient"; +  public static final String CONIG_PROPS_EIDAS_ERNPCLIENT_ENDPOINT = CONIG_PROPS_EIDAS_ERNPCLIENT +      + ".endpoint"; +  public static final String CONIG_PROPS_EIDAS_ERNPCLIENT_TIMEOUT_CONNECTION = CONIG_PROPS_EIDAS_ERNPCLIENT +      + ".timeout.connection"; +  public static final String CONIG_PROPS_EIDAS_ERNPCLIENT_TIMEOUT_RESPONSE = CONIG_PROPS_EIDAS_ERNPCLIENT +      + ".timeout.response"; +  public static final String CONIG_PROPS_EIDAS_ERNPCLIENT_REQ_ORGANIZATION_NR = CONIG_PROPS_EIDAS_ERNPCLIENT +      + ".req.organisation.behoerdennr"; +   +   +  // SZR Client configuration properties    public static final String CONIG_PROPS_EIDAS_SZRCLIENT = CONIG_PROPS_EIDAS_PREFIX + ".szrclient";    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE = CONIG_PROPS_EIDAS_SZRCLIENT        + ".useTestService"; @@ -104,10 +200,22 @@ public class Constants {        + ".ssl.keyStore.path";    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD = CONIG_PROPS_EIDAS_SZRCLIENT        + ".ssl.keyStore.password"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_TYPE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.keyStore.type"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_NAME = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.keyStore.name"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYS_ALIAS = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.key.alias"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEY_PASSWORD = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.key.password";    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH = CONIG_PROPS_EIDAS_SZRCLIENT        + ".ssl.trustStore.path";    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD = CONIG_PROPS_EIDAS_SZRCLIENT        + ".ssl.trustStore.password"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_TYPE = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.trustStore.type"; +  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_NAME = CONIG_PROPS_EIDAS_SZRCLIENT +      + ".ssl.trustStore.name";    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE = CONIG_PROPS_EIDAS_SZRCLIENT        + ".params.documenttype"; @@ -137,10 +245,6 @@ public class Constants {    @Deprecated    public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_SQLLITEDATASTORE_ACTIVE =        CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.datastore.sqlite.active"; - -  public static final String CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_IDA_VSZ_IDL = -      CONIG_PROPS_EIDAS_SZRCLIENT + ".workarounds.use.getidentitylink.for.ida"; -    // http endpoint descriptions    public static final String eIDAS_HTTP_ENDPOINT_SP_POST = "/eidas/light/sp/post"; @@ -159,13 +263,33 @@ public class Constants {    public static final String eIDAS_ATTR_PLACEOFBIRTH = "PlaceOfBirth";    public static final String eIDAS_ATTR_BIRTHNAME = "BirthName";    public static final String eIDAS_ATTR_CURRENTADDRESS = "CurrentAddress"; +  public static final String eIDAS_ATTR_TAXREFERENCE = "TaxReference";    public static final String eIDAS_ATTR_LEGALPERSONIDENTIFIER = "LegalPersonIdentifier";    public static final String eIDAS_ATTR_LEGALNAME = "LegalName"; + +  //eIDAS attribute URN +  public static final String eIDAS_ATTRURN_PREFIX = "http://eidas.europa.eu/attributes/"; +  public static final String eIDAS_ATTRURN_PREFIX_NATURAL = eIDAS_ATTRURN_PREFIX + "naturalperson/"; + +  public static final String eIDAS_ATTRURN_PERSONALIDENTIFIER = +      eIDAS_ATTRURN_PREFIX_NATURAL + eIDAS_ATTR_PERSONALIDENTIFIER;   +  public static final String eIDAS_ATTRURN_CURRENTGIVENNAME = +      eIDAS_ATTRURN_PREFIX_NATURAL + "CurrentGivenName"; +  public static final String eIDAS_ATTRURN_CURRENTFAMILYNAME = +      eIDAS_ATTRURN_PREFIX_NATURAL + "CurrentFamilyName"; +  public static final String eIDAS_ATTRURN_DATEOFBIRTH = +      eIDAS_ATTRURN_PREFIX_NATURAL + eIDAS_ATTR_DATEOFBIRTH;   +  public static final String eIDAS_ATTRURN_PLACEOFBIRTH = +      eIDAS_ATTRURN_PREFIX_NATURAL + eIDAS_ATTR_PLACEOFBIRTH; +  public static final String eIDAS_ATTRURN_BIRTHNAME = +      eIDAS_ATTRURN_PREFIX_NATURAL + eIDAS_ATTR_BIRTHNAME; + +    public static final String eIDAS_REQ_PARAM_SECTOR_PUBLIC = "public";    public static final String eIDAS_REQ_PARAM_SECTOR_PRIVATE = "private"; -   +    public static final String POLICY_DEFAULT_ALLOWED_TARGETS =        EaafConstants.URN_PREFIX_CDID.replaceAll("\\.", "\\\\.").replaceAll("\\+", "\\\\+") + ".*"; @@ -178,8 +302,10 @@ public class Constants {    public static final String SZR_SCHEMA_LOCATIONS =        "urn:SZRServices" + " " + "/szr_client/szr.xsd"; -  // Default values for SZR communication +  // Default values for SZR / ZMR / ERnP communication    public static final String SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE = "ELEKTR_DOKUMENT"; +  public static final String CLIENT_INFO = "eIDAS MS-Connector v{0}"; +      // TODO remove!!!    public static final String SZR_CONSTANTS_DEFAULT_ISSUING_DATE = "2014-01-01"; @@ -189,4 +315,59 @@ public class Constants {        "AJZyj/+sdCMDRq9RkvbFcgSTVn/OfS8EUE81ddwP8MNuJ1kd1SWBUJPaQX2JLJHrL54mkOhrkhH2M/zcuOTu8nW9TOEg"        + "XGjrRB/0HpiYKpV+VDJViyyc/GacNLxN4Anw4pima6gHYaJIw9hQkL/nuO2hyh8PGJd7rxeFXJmbLy+X"; +  public static final String COUNTRY_CODE_DE = "DE"; +  public static final String COUNTRY_CODE_IT = "IT"; + + +  // UI options +  public static final String HTML_FORM_CREATE_NEW_ERNP_ENTRY = "createNewErnpEntry"; +  public static final String HTML_FORM_ADVANCED_MATCHING_FAILED = "advancedMatchingFailed"; +  public static final String HTML_FORM_ADVANCED_MATCHING_FAILED_REASON =  +      HTML_FORM_ADVANCED_MATCHING_FAILED + "Reason"; +   + +  // 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}. +   */ +  public static final String TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK = "TASK_CreateNewErnpEntryTask"; + +  /** +   * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.GenerateOtherLoginMethodGuiTask}. +   */ +  public static final String TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK = +      "TASK_GenerateOtherLoginMethodGuiTask"; + +  /** +   * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.GenerateAustrianResidenceGuiTask}. +   */ +  public static final String TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK = +      "TASK_GenerateAustrianResidenceGuiTask"; + +  /** +   * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.CreateNewErnpEntryTask}. +   */ +  public static final String TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK = "TASK_RequestingNewErnpEntryTask"; +    +   +  /** +   * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.GenerateMobilePhoneSignatureRequestTask}. +   */ +  public static final String TRANSITION_TO_GENERATE_MOBILE_PHONE_SIGNATURE_REQUEST_TASK = +      "TASK_GenerateMobilePhoneSignatureRequestTask"; + +  /** +   * {@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.GenerateAuthnRequestTask}. +   */ +  public static final String TRANSITION_TO_GENERATE_EIDAS_LOGIN = "TASK_GenerateAlternativeEidasAuthn"; + + +  /** +   * Stores login selection from user. +   */ +  public static final String REQ_SELECTED_LOGIN_METHOD_PARAMETER = "loginSelection";  } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java index 535e4f97..e5b10185 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/EidasAuthenticationSpringResourceProvider.java @@ -45,8 +45,10 @@ public class EidasAuthenticationSpringResourceProvider implements SpringResource    public Resource[] getResourcesToLoad() {      final ClassPathResource eidasAuthConfig = new ClassPathResource("/eidas_v2_auth.beans.xml",          EidasAuthenticationSpringResourceProvider.class); - -    return new Resource[] { eidasAuthConfig }; +    final ClassPathResource eidasRefImplConfig = new ClassPathResource("/eidas_v2_auth_ref_impl_config.beans.xml", +        EidasAuthenticationSpringResourceProvider.class); +            +    return new Resource[] { eidasRefImplConfig, eidasAuthConfig };    }  } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/AbstractSoapClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/AbstractSoapClient.java new file mode 100644 index 00000000..a039881c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/AbstractSoapClient.java @@ -0,0 +1,199 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients; + +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.SSLContext; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.handler.Handler; + +import org.apache.commons.lang3.StringUtils; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.jaxws.DispatchImpl; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.apache.http.ssl.SSLContextBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.lang.Nullable; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.LoggingHandler; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.HttpUtils; +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AbstractSoapClient { + +  @Autowired +  protected IConfiguration basicConfig; +  @Autowired +  EaafKeyStoreFactory keyStoreFactory; + +  @Builder +  @Getter +  public static class HttpClientConfig { + +    private final String clientName; + +    private final String clientUrl; +    private final String clientType; + +    private final String connectionTimeout; +    private final String responseTimeout; + +    private final KeyStoreConfiguration keyStoreConfig; +    private final String keyAlias; +    private final String keyPassword; + +    private final KeyStoreConfiguration trustStoreConfig; + +    @Builder.Default +    private final boolean trustAll = false; + +  } + +  /** +   * Build a validated KeyStore Configuration-Object from configuration keys. +   * +   * @param keyStoreTypeKey     Configuration key for type +   * @param keyStorePathKey     Configuration key for path +   * @param keyStorePasswordKey Configuration key for password +   * @param keyStoreNameKey     Configuration key for name +   * @param friendlyName        Friendlyname for logging and errorhandling +   * @return Valid KeyStore configuration or <code>null</code> if no type was +   *         defined +   * @throws EaafConfigurationException In case of validation error +   */ +  @Nullable +  protected KeyStoreConfiguration buildKeyStoreConfiguration(String keyStoreTypeKey, String keyStorePathKey, +      String keyStorePasswordKey, String keyStoreNameKey, String friendlyName) +      throws EaafConfigurationException { +    if (StringUtils.isNotEmpty(basicConfig.getBasicConfiguration(keyStoreTypeKey))) { +      log.debug("Starting configuration of: {} ... ", friendlyName); +      final KeyStoreConfiguration config = new KeyStoreConfiguration(); +      config.setFriendlyName(friendlyName); +      config.setKeyStoreType(basicConfig.getBasicConfiguration(keyStoreTypeKey, KeyStoreType.PKCS12.name())); +      config.setKeyStoreName(basicConfig.getBasicConfiguration(keyStoreNameKey)); +      config.setSoftKeyStoreFilePath(basicConfig.getBasicConfiguration(keyStorePathKey)); +      config.setSoftKeyStorePassword(basicConfig.getBasicConfiguration(keyStorePasswordKey)); + +      // validate keystore configuration +      config.validate(); + +      return config; + +    } else { +      log.info("Skipping configuration of: {}", friendlyName); +      return null; + +    } + +  } + +  protected void injectHttpClient(Object raw, HttpClientConfig config) { +    // extract client from implementation +    Client client; +    if (raw instanceof DispatchImpl<?>) { +      client = ((DispatchImpl<?>) raw).getClient(); +    } else if (raw instanceof Client) { +      client = ClientProxy.getClient(raw); +    } else { +      throw new RuntimeException("SOAP Client for SZR connection is of UNSUPPORTED type: " + raw.getClass() +          .getName()); +    } + +    // set basic connection policies +    final HTTPConduit http = (HTTPConduit) client.getConduit(); + +    // set timeout policy +    final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); +    httpClientPolicy.setConnectionTimeout(Integer.parseInt(config.getConnectionTimeout()) * 1000L); +    httpClientPolicy.setReceiveTimeout(Integer.parseInt(config.getResponseTimeout()) * 1000L); +    http.setClient(httpClientPolicy); + +    // inject SSL context in case of https +    if (config.getClientUrl().toLowerCase().startsWith("https")) { +      try { +        log.debug("Adding SSLContext to client: " + config.getClientType() + " ... "); + +        final TLSClientParameters tlsParams = new TLSClientParameters(); +        if (config.getKeyStoreConfig() != null) { +          final SSLContext sslContext = HttpUtils.buildSslContextWithSslClientAuthentication( +              keyStoreFactory.buildNewKeyStore(config.getKeyStoreConfig()), +              config.getKeyAlias(), +              config.getKeyPassword(), +              loadTrustStore(config.getTrustStoreConfig(), config.getClientName()), +              config.isTrustAll(), +              config.getClientName()); +          tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); + +        } else { +          log.debug( +              "No KeyStore for SSL Client Auth. found. Initializing SSLContext for: {} without authentication ... ", +              config.getClientName()); +          tlsParams.setSSLSocketFactory(SSLContextBuilder.create().build().getSocketFactory()); + +        } + +        http.setTlsClientParameters(tlsParams); +        log.info("SSLContext initialized for client: " + config.getClientType()); + +      } catch (EaafException | KeyManagementException | NoSuchAlgorithmException e) { +        log.error("SSLContext initialization FAILED.", e); +        throw new RuntimeException("SSLContext initialization FAILED.", e); + +      } +    } +  } + +  private Pair<KeyStore, Provider> loadTrustStore(KeyStoreConfiguration trustStoreConfig, String friendlyName) +      throws EaafException { +    if (trustStoreConfig != null) { +      log.info("Build custom SSL truststore for: {}", friendlyName); +      return keyStoreFactory.buildNewKeyStore(trustStoreConfig); + +    } else { +      log.info("Use default SSL truststore for: {}", friendlyName); +      return null; + +    } + +  } + +  protected void injectBindingProvider(BindingProvider bindingProvider, String clientType, String szrUrl, +      boolean enableTraceLogging) { +    final Map<String, Object> requestContext = bindingProvider.getRequestContext(); +    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, szrUrl); + +    log.trace("Adding JAX-WS request/response trace handler to client: " + clientType); +    List<Handler> handlerList = bindingProvider.getBinding().getHandlerChain(); +    if (handlerList == null) { +      handlerList = new ArrayList<>(); +      bindingProvider.getBinding().setHandlerChain(handlerList); + +    } + +    // add logging handler to trace messages if required +    if (enableTraceLogging) { +      final LoggingHandler loggingHandler = new LoggingHandler(); +      handlerList.add(loggingHandler); + +    } +    bindingProvider.getBinding().setHandlerChain(handlerList); +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java new file mode 100644 index 00000000..4c4e3d87 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java @@ -0,0 +1,857 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp; + +import java.io.IOException; +import java.text.MessageFormat; +import java.time.LocalDate; +import java.time.OffsetDateTime; +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 org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.HttpClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.web.client.ResponseErrorHandler; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +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.dao.SimpleEidasData.SimpleEidasDataBuilder; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.api.DefaultApi; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.invoker.ApiClient; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Aendern; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.AendernResponse; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Anlegen; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.AnlegenResponse; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Eidas; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.PartialDate; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Person; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.PersonAendern; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.PersonAnlegen; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.PersonSuchen; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Personendaten; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.PersonendatenErgebnis; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.SuchEidas; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Suchdaten; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.SuchenResponse; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Suchoptionen; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.Suchoptionen.HistorischEnum; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.ErnpRestCommunicationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.VersionHolder; +import at.gv.bmi.namespace.zmr_su.base._20040201_.ServiceFault; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.EidasSuchdatenType; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.http.HttpClientConfiguration; +import at.gv.egiz.eaaf.core.impl.http.HttpClientConfiguration.ClientAuthMode; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; +import at.gv.egiz.eaaf.core.impl.utils.TransactionIdUtils; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +/** + * Implements an ERnP client that uses REST API for communication. + * + * @author tlenz + * + */ +@Slf4j +public class ErnpRestClient implements IErnpClient { + +  private static final String ERROR_MATCHING_11 = "module.eidasauth.matching.11"; +  //private static final String ERROR_MATCHING_12 = "module.eidasauth.matching.12"; +  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_ERNP_ERROR = +      "Receive an error from ERnP during '{}' operation with msg: {}"; +  private static final String LOGMSG_ERNP_RESP_PROCESS = +      "Proces ERnP response during '{}' operation failes with msg: {}"; + +  //private static final String LOGMSG_ERNP_REST_ERROR = +  //    "ERnP anwser for transaction: {0} with code: {1} and message: {2}"; + +  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_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 PROCESS_ADD_IDENITY = "Add new person"; +   +  private static final String FRIENDLYNAME_HTTP_CLIENT = "ERnP Client"; + +  // HTTP header-names from ERnP response  +  private static final String ERNP_RESPONSE_HEADER_SERVER_ID = "Server-Request-Id"; +   +   +  @Autowired +  IConfiguration basicConfig; +  @Autowired +  EaafKeyStoreFactory keyStoreFactory; +  @Autowired +  IHttpClientFactory httpClientFactory; +  @Autowired +  VersionHolder versionHolder; + +  private DefaultApi ernpClient; + +  @Override +  public ErnpRegisterResult searchWithPersonIdentifier(String personIdentifier, String citizenCountryCode) +      throws EidasSAuthenticationException { +    try { +      // build generic request metadata +      final GenericRequestParams generic = buildGenericRequestParameters("stepId"); + +      // build search request +      final SuchEidas eidasInfos = new SuchEidas(); +      eidasInfos.setArt(Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER); +      eidasInfos.setWert(personIdentifier); +      eidasInfos.setStaatscode2(citizenCountryCode); + +      final PersonSuchen personSuchen = new PersonSuchen(); +      personSuchen.setSuchoptionen(generateSearchParameters()); +      personSuchen.setBegruendung(PROCESS_SEARCH_PERSONAL_IDENTIFIER); +      final Suchdaten searchInfos = new Suchdaten(); +      searchInfos.setEidas(Arrays.asList(eidasInfos)); +      personSuchen.setSuchdaten(searchInfos); + +      // request ERnP +      log.trace("Requesting ERnP for '{}' operation", PROCESS_SEARCH_PERSONAL_IDENTIFIER); +      final SuchenResponse resp = ernpClient.suchen(generic.getClientBehkz(), generic.clientName, +          generic.getClientRequestTime(), generic.getClientRequestId(), personSuchen); + +      // parse ZMR response +      return processErnpResponse(resp, citizenCountryCode, true, PROCESS_SEARCH_PERSONAL_IDENTIFIER); + +    } catch (RestClientException e) { +      log.warn(LOGMSG_ERNP_ERROR, PROCESS_SEARCH_PERSONAL_IDENTIFIER, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_11, new Object[] { e.getMessage() }, e); +       +    } catch (final EidasSAuthenticationException e) { +      throw e; + +    } catch (final Exception e) { +      log.warn(LOGMSG_ERNP_RESP_PROCESS, PROCESS_SEARCH_PERSONAL_IDENTIFIER, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); +    } + +  } + +  @Override +  public ErnpRegisterResult searchWithMds(String givenName, String familyName, String dateOfBirth, +      String citizenCountryCode) throws EidasSAuthenticationException { +    try { +      // build generic request metadata +      final GenericRequestParams generic = buildGenericRequestParameters("stepMDS"); + +      // build search request +      final Suchdaten searchInfos = new Suchdaten(); +      searchInfos.setFamilienname(familyName); +      searchInfos.setVorname(givenName); +      searchInfos.setGeburtsdatum(buildErnpBirthday(dateOfBirth)); +             +      final PersonSuchen personSuchen = new PersonSuchen(); +      personSuchen.setSuchoptionen(generateSearchParameters()); +      personSuchen.setBegruendung(PROCESS_SEARCH_MDS_ONLY);            +      personSuchen.setSuchdaten(searchInfos); + +      // request ERnP +      log.trace("Requesting ERnP for '{}' operation", PROCESS_SEARCH_MDS_ONLY); +      final SuchenResponse resp = ernpClient.suchen(generic.getClientBehkz(), generic.clientName, +          generic.getClientRequestTime(), generic.getClientRequestId(), personSuchen); + +      // parse ZMR response +      return processErnpResponse(resp, citizenCountryCode, false, PROCESS_SEARCH_MDS_ONLY); + +    } catch (RestClientException e) { +      log.warn(LOGMSG_ERNP_ERROR, PROCESS_SEARCH_MDS_ONLY, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_11, new Object[] { e.getMessage() }, e); +       +    } catch (final EidasSAuthenticationException e) { +      throw e; + +    } catch (final Exception e) { +      log.warn(LOGMSG_ERNP_RESP_PROCESS, PROCESS_SEARCH_MDS_ONLY, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); +    } +  } +   +  @Override +  public ErnpRegisterResult searchCountrySpecific(PersonSuchenRequest personSearchDao, +      String citizenCountryCode) throws EidasSAuthenticationException { +    String countrySearchMsg = MessageFormat.format(PROCESS_SEARCH_COUNTRY_SPECIFIC, citizenCountryCode); +     +    try {             +      // build generic request metadata +      final GenericRequestParams generic = buildGenericRequestParameters("stepCC"); + +      // build search request             +      final PersonSuchen personSuchen = new PersonSuchen(); +      personSuchen.setSuchoptionen(generateSearchParameters()); +      personSuchen.setBegruendung(countrySearchMsg);            +      personSuchen.setSuchdaten(mapCountrySpecificSearchData(personSearchDao)); + +      // request ERnP +      log.trace("Requesting ERnP for '{}' operation", countrySearchMsg); +      final SuchenResponse resp = ernpClient.suchen(generic.getClientBehkz(), generic.clientName, +          generic.getClientRequestTime(), generic.getClientRequestId(), personSuchen); + +      // parse ZMR response +      return processErnpResponse(resp, citizenCountryCode, true, countrySearchMsg); + +    } catch (RestClientException e) { +      log.warn(LOGMSG_ERNP_ERROR, countrySearchMsg, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_11, new Object[] { e.getMessage() }, e); +       +    } catch (final EidasSAuthenticationException e) { +      throw e; + +    } catch (final Exception e) { +      log.warn(LOGMSG_ERNP_RESP_PROCESS, countrySearchMsg, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); +       +    } +  } + +  @Override +  public ErnpRegisterResult update(RegisterResult registerResult, SimpleEidasData eidData) +      throws EidasSAuthenticationException { +    try { +      //search person with register result, because update needs information from search response +      Person ernpPersonToKitt = searchPersonForUpdate(registerResult); +             +      // select elements that have to be updated +      Collection<? extends Eidas> eidasDocumentToAdd =  +          selectEidasDocumentsToAdd(ernpPersonToKitt, eidData);       +      SimpleEidasData mdsToUpdate = selectMdsInformationToUpdate(ernpPersonToKitt, eidData);  +       +      if (eidasDocumentToAdd.isEmpty() && mdsToUpdate == null) { +        log.info("Find no eIDAS document or MDS for update during: {}. Nothing todo on ERnP side",  +            PROCESS_KITT_GENERAL); +        return new ErnpRegisterResult(Arrays.asList(registerResult)); +         +      } 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 updatePersonInErnp(ernpPersonToKitt, eidasDocumentToAdd, mdsToUpdate, eidData.getCitizenCountryCode());   +         +      } + +    } catch (RestClientException e) { +      log.warn(LOGMSG_ERNP_ERROR, PROCESS_KITT_GENERAL, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_11, new Object[] { e.getMessage() }, e); +       +    } catch (final EidasSAuthenticationException e) { +      throw e; + +    } catch (final Exception e) { +      log.warn(LOGMSG_ERNP_RESP_PROCESS, PROCESS_KITT_GENERAL, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); +       +    }     +  } + +  @Override +  public ErnpRegisterResult add(SimpleEidasData eidData) throws EidasSAuthenticationException { +    try { +      // build generic request metadata +      final GenericRequestParams generic = buildGenericRequestParameters("stepNew"); +       +      // build update request +      PersonAnlegen ernpReq = new PersonAnlegen(); +      ernpReq.setBegruendung(PROCESS_ADD_IDENITY); +           +      // inject person data +      Personendaten person = new Personendaten(); +      person.setFamilienname(eidData.getFamilyName()); +      person.setVorname(eidData.getGivenName()); +      person.setGeburtsdatum(buildErnpBirthday(eidData.getDateOfBirth()));        +      ernpReq.setPersonendaten(person); +       +      buildNewEidasDocumens(ernpReq, eidData); +              +      // request ERnP +      log.trace("Requesting ERnP for '{}' operation", PROCESS_ADD_IDENITY);        +      AnlegenResponse ernpResp = ernpClient.anlegen(generic.getClientBehkz(), generic.clientName, +          generic.getClientRequestTime(), generic.getClientRequestId(), ernpReq);         +      log.trace("Receive response from ERnP for '{}' operation", PROCESS_ADD_IDENITY); +       +      return  new ErnpRegisterResult(Arrays.asList( +          mapErnpResponseToRegisterResult(ernpResp.getPerson(), eidData.getCitizenCountryCode())));     +     +    } catch (RestClientException e) { +      log.warn(LOGMSG_ERNP_ERROR, PROCESS_ADD_IDENITY, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_11, new Object[] { e.getMessage() }, e); +       +    } catch (final Exception e) { +      log.warn(LOGMSG_ERNP_RESP_PROCESS, PROCESS_ADD_IDENITY, e.getMessage()); +      throw new EidasSAuthenticationException(ERROR_MATCHING_99, new Object[] { e.getMessage() }, e); +       +    }     +  } +   +  @Override +  public ErnpRegisterResult searchWithResidenceData(String givenName, String familyName, String dateOfBirth, +      String zipcode, String city, String street) {     +    log.warn("Matching with residence information is prohibited by design! This requests will be ignored"); +    return new ErnpRegisterResult(Collections.emptyList()); +     +  } +   +  @PostConstruct +  private void initialize() throws EaafException { +    // validate additional Ernp communication parameters +    valdiateAdditionalConfigParameters(); + +    // set-up the Ernp client +    final ApiClient baseClient = new ApiClient(buildRestClient()); +    baseClient.setBasePath(basicConfig.getBasicConfiguration( +        Constants.CONIG_PROPS_EIDAS_ERNPCLIENT_ENDPOINT)); +    ernpClient = new DefaultApi(baseClient); + +  } + +  private void valdiateAdditionalConfigParameters() { +    checkConfigurationValue(Constants.CONIG_PROPS_EIDAS_ERNPCLIENT_ENDPOINT); +    checkConfigurationValue(Constants.CONIG_PROPS_EIDAS_ERNPCLIENT_REQ_ORGANIZATION_NR); + +  } + +  private void checkConfigurationValue(String key) { +    if (StringUtils.isEmpty(basicConfig.getBasicConfiguration(key))) { +      throw new RuntimeException(MessageFormat.format(LOGMSG_MISSING_CONFIG, key)); + +    } +  } + +  private Suchoptionen generateSearchParameters() { +    final Suchoptionen options = new Suchoptionen(); +    options.setZmr(false); +    options.setHistorisch(HistorischEnum.AKTUELLUNDHISTORISCH); +    options.setSucheMitNamensteilen(false); +    options.setSuchwizard(false); +    return options; + +  } + +  @Nonnull +  private ErnpRegisterResult processErnpResponse(SuchenResponse resp, @Nonnull String citizenCountryCode, +      boolean forceSinglePersonMatch, @Nonnull String processStepFiendlyname) +      throws EaafAuthenticationException { +    if (resp.getPerson() == null +        || resp.getPerson().isEmpty()) { +      log.debug("ERnP result contains NO 'Person' or 'Person' is empty"); +      return new ErnpRegisterResult(Collections.emptyList()); + +    } else { +      log.debug("Get #{} person results from '{}' operation", +          resp.getPerson().size(), processStepFiendlyname); + +      if (forceSinglePersonMatch) { +        return new ErnpRegisterResult(processSearchPersonResponseSingleResult( +            resp.getPerson(), citizenCountryCode, processStepFiendlyname)); + +      } else { +        return new ErnpRegisterResult(processSearchPersonResponse( +            resp.getPerson(), citizenCountryCode)); + +      } +    } +  } + +  @Nonnull +  private List<RegisterResult> processSearchPersonResponse( +      @Nonnull List<Person> list, +      @Nonnull String citizenCountryCode) throws EaafAuthenticationException { +    return list.stream() +        .map(el -> mapErnpResponseToRegisterResult(el, citizenCountryCode)) +        .filter(Objects::nonNull) +        .collect(Collectors.toList()); + +  } + +  @NonNull +  private List<RegisterResult> processSearchPersonResponseSingleResult( +      @Nonnull List<Person> persons, +      @Nonnull String citizenCountryCode, String processStepFiendlyname) throws EaafAuthenticationException { +    if (persons.size() > 1) { +      log.error("Find more-than-one ERnP entry with search criteria that has to be unique"); +      throw new WorkflowException(processStepFiendlyname, +          "Find more-than-one ERnP entry with search criteria that has to be unique", true); + +    } else { +      return Arrays.asList(mapErnpResponseToRegisterResult(persons.get(0), citizenCountryCode)); + +    } +  } + +  @Nonnull +  private RegisterResult mapErnpResponseToRegisterResult(@Nonnull Person person, +      @Nonnull String citizenCountryCode) { +    // build result +    return RegisterResult.builder() +        .pseudonym(selectAllEidasDocument(person, citizenCountryCode, +            Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER)) +        .familyName(person.getPersonendaten().getFamilienname()) +        .givenName(person.getPersonendaten().getVorname()) +        .dateOfBirth(getTextualBirthday(person.getPersonendaten().getGeburtsdatum())) +        .bpk(person.getPersonendaten().getBpkZp()) +        .placeOfBirth(selectSingleEidasDocument(person, citizenCountryCode, +            Constants.eIDAS_ATTRURN_PLACEOFBIRTH)) +        .birthName(selectSingleEidasDocument(person, citizenCountryCode, +            Constants.eIDAS_ATTRURN_BIRTHNAME)) +        .build(); + +  } + +  private Suchdaten mapCountrySpecificSearchData(PersonSuchenRequest personSearchDao) {             +    final Suchdaten searchInfos = new Suchdaten(); +    searchInfos.setFamilienname(personSearchDao.getNatuerlichePerson().getPersonenName().getFamilienname());       +    searchInfos.setVorname(personSearchDao.getNatuerlichePerson().getPersonenName().getVorname()); +    searchInfos.setGeburtsdatum(buildErnpBirthday(personSearchDao.getNatuerlichePerson().getGeburtsdatum()));     +     +    // map all eIDAS documents into ERnP format +    searchInfos.setEidas(personSearchDao.getEidasSuchdaten().stream() +        .map(el -> buildErnpEidasDocument(el)) +        .collect(Collectors.toList())); +            +    return searchInfos; +     +  } +   +  private ErnpRegisterResult updatePersonInErnp(Person ernpPersonToKitt, +      Collection<? extends Eidas> eidasDocumentToAdd, SimpleEidasData mdsToUpdate, String citizenCountryCode)  +          throws ServiceFault { +    // build generic request metadata +    final GenericRequestParams generic = buildGenericRequestParameters("stepKittUpdate"); +     +    // build update request +    PersonAendern ernpReq = new PersonAendern(); +    ernpReq.setBegruendung(PROCESS_KITT_IDENITIES_UPDATE); +     +    // set reference elements for person update +    ernpReq.setEntityId(ernpPersonToKitt.getEntityId()); +    ernpReq.setVersion(ernpPersonToKitt.getVersion());     +     +    // add new eIDAS attributes +    if (!eidasDocumentToAdd.isEmpty()) { +      log.debug("Find eIDAS Documents to update. Injection update entries into ERnP request ... "); +      ernpReq.setAnlegen(new Anlegen());       +      eidasDocumentToAdd.stream().forEach(el -> ernpReq.getAnlegen().addEidasItem(el)); +       +    } +     +    // update MDS if required +    if (mdsToUpdate != null) { +      log.debug("Find MDS to update. Injection update entries into ERnP request ... "); +      ernpReq.setAendern(generateMdsChangeRequest(ernpPersonToKitt, mdsToUpdate)); +             +    } +     +    // request ERnP +    log.trace("Requesting ERnP for '{}' operation", PROCESS_KITT_IDENITIES_UPDATE);        +    AendernResponse ernpResp = ernpClient.aendern(generic.getClientBehkz(), generic.clientName, +        generic.getClientRequestTime(), generic.getClientRequestId(), ernpReq);         +    log.trace("Receive response from ERnP for '{}' operation", PROCESS_KITT_IDENITIES_UPDATE); +     +    return  new ErnpRegisterResult(Arrays.asList( +        mapErnpResponseToRegisterResult(ernpResp.getPerson(), citizenCountryCode)));     +     +  } +   +  private Collection<? extends Eidas> selectEidasDocumentsToAdd( +      Person ernpPersonToKitt, SimpleEidasData eidData) { + +    //TODO: maybe we should re-factor SimpleEidasData to a generic data-model to facilitate arbitrary eIDAS attributes   +    Set<Eidas> result = new HashSet<>();    +    addEidasDocumentIfNotAvailable(result, ernpPersonToKitt, eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER, eidData.getPseudonym(), true);     +    addEidasDocumentIfNotAvailable(result, ernpPersonToKitt, eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_PLACEOFBIRTH, eidData.getPlaceOfBirth(), false); +    addEidasDocumentIfNotAvailable(result, ernpPersonToKitt, eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_BIRTHNAME, eidData.getBirthName(), false); +         +    return result; +     +  } +   +  private void addEidasDocumentIfNotAvailable(Set<Eidas> result, +      Person ernpPersonToKitt, 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 = ernpPersonToKitt.getEidas().stream() +        .filter(el -> el.getWert().equals(attrValue)  +            && el.getArt().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<Eidas> oneDocWithNameExists = ernpPersonToKitt.getEidas().stream() +          .filter(el -> el.getStaatscode2().equals(citizenCountryCode)  +              && el.getArt().equals(attrName)) +          .findAny(); +       +      if (!allowMoreThanOneEntry && oneDocWithNameExists.isPresent() +          && !oneDocWithNameExists.get().getWert().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 { +         +        Eidas eidasDocToAdd = new Eidas(); +        eidasDocToAdd.setStaatscode2(citizenCountryCode); +        eidasDocToAdd.setArt(attrName); +        eidasDocToAdd.setWert(attrValue);         +        log.info("Add eIDAS document: {} for country: {} to ERnP person", attrName, citizenCountryCode); +        result.add(eidasDocToAdd); +           +      } +               +    } else { +      log.debug("eIDAS document: {} already exists for country: {}. Skip update process for this ... ", +          attrName, citizenCountryCode);   +       +    } +  } +   +  private Person searchPersonForUpdate(RegisterResult registerResult) throws WorkflowException { +    // build generic request metadata +    final GenericRequestParams generic = buildGenericRequestParameters("stepKittSearch"); + +    // build search request +    final Suchdaten searchInfos = new Suchdaten(); +    searchInfos.setBpkZp(registerResult.getBpk()); +    searchInfos.setFamilienname(registerResult.getFamilyName()); +    searchInfos.setVorname(registerResult.getGivenName()); +    searchInfos.setGeburtsdatum(buildErnpBirthday(registerResult.getDateOfBirth()));               +     +    final PersonSuchen personSuchen = new PersonSuchen(); +    personSuchen.setSuchoptionen(generateSearchParameters()); +    personSuchen.setBegruendung(PROCESS_KITT_IDENITIES_GET);            +    personSuchen.setSuchdaten(searchInfos); + +    // request ERnP +    log.trace("Requesting ERnP for '{}' operation", PROCESS_KITT_IDENITIES_GET); +    final SuchenResponse resp = ernpClient.suchen(generic.getClientBehkz(), generic.clientName, +        generic.getClientRequestTime(), generic.getClientRequestId(), personSuchen); + +    // perform shot validation of ERnP response +    if (resp.getPerson() == null || resp.getPerson().size() != 1) { +      log.error("ERnP result contains NO 'Person' or 'Person' is empty"); +      throw new WorkflowException(PROCESS_KITT_IDENITIES_GET,  +          "Find NO data-set with already matchted eID during ERnP KITT process"); +       +    } else { +      log.debug("Find person for '{}' operation", PROCESS_KITT_IDENITIES_GET); +      return resp.getPerson().get(0);      +       +    }         +  } +   +  private void buildNewEidasDocumens(PersonAnlegen ernpReq, SimpleEidasData eidData) { +    ernpReq.addEidasItem(buildNewEidasDocument(eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER, eidData.getPseudonym())); +     +    if (StringUtils.isNotEmpty(eidData.getPlaceOfBirth())) { +      ernpReq.addEidasItem(buildNewEidasDocument(eidData.getCitizenCountryCode(), +          Constants.eIDAS_ATTRURN_PLACEOFBIRTH, eidData.getPlaceOfBirth())); +         +    } +         +    if (StringUtils.isNotEmpty(eidData.getBirthName())) { +      ernpReq.addEidasItem(buildNewEidasDocument(eidData.getCitizenCountryCode(),  +          Constants.eIDAS_ATTRURN_BIRTHNAME, eidData.getBirthName())); +       +    }         +  } + +  private Eidas buildNewEidasDocument(String citizenCountryCode, String eidasAttrName, +      String eidasAddrValue) { +    Eidas el = new Eidas(); +    el.setArt(eidasAttrName); +    el.setWert(eidasAddrValue); +    el.setStaatscode2(citizenCountryCode); +    return el; +  } +     +  private SimpleEidasData selectMdsInformationToUpdate(Person ernpPersonToKitt, SimpleEidasData eidData) {     +    PersonendatenErgebnis person = ernpPersonToKitt.getPersonendaten(); +    SimpleEidasDataBuilder builder = SimpleEidasData.builder() +        .givenName(eidData.getGivenName()) +        .familyName(eidData.getFamilyName()) +        .dateOfBirth(eidData.getDateOfBirth()); +     +    boolean findMatch = person.getVorname().equals(eidData.getGivenName()) +        && person.getFamilienname().equals(eidData.getFamilyName()) +        && getTextualBirthday(person.getGeburtsdatum()).equals(eidData.getDateOfBirth());     +    return findMatch ? null : builder.build(); +     +  } +   +  private Aendern generateMdsChangeRequest(Person ernpPersonToKitt, SimpleEidasData mdsToUpdate) { +    Aendern el = new Aendern();     +    Personendaten person = new Personendaten(); +    person.setEntityId(ernpPersonToKitt.getPersonendaten().getEntityId()); +    el.setPersonendaten(person); +    person.setFamilienname(mdsToUpdate.getFamilyName()); +    person.setVorname(mdsToUpdate.getGivenName()); +    person.setGeburtsdatum(buildErnpBirthday(mdsToUpdate.getDateOfBirth()));                +    return el; +     +  } +   +  /** +   * Map an AT specific Date String 'yyyy-MM-dd' to ERnP birthday representation. +   *  +   * <p> +   *  <b>Info:</b> {@link LocalDate} can not be used, because '1940-00-00' is also +   * a valid birthday. +   * </p> +   *  +   * @param dateOfBirth in 'yyyy-MM-dd' format +   * @return ERnP birthday representation +   */ +  private PartialDate buildErnpBirthday(String dateOfBirth) {         +    String[] elements = dateOfBirth.split("-"); +    Assert.isTrue(elements.length == 3, "Find invalid dateOfBirth element: " + dateOfBirth); +     +    PartialDate result = new PartialDate();  +    result.setJahr(Integer.valueOf(elements[0])); +    result.setMonat(Integer.valueOf(elements[1])); +    result.setTag(Integer.valueOf(elements[2]));         +    return result; +     +  } + +  /** +   * Map eIDAS search-data from ZMR model into ERnP model. +   *  +   * @param daten eIDAS document as ZMR model +   * @return the same eIDAS document as an ERnP model +   */ +  private SuchEidas buildErnpEidasDocument(EidasSuchdatenType daten) {   +    return new SuchEidas() +        .art(daten.getEidasArt()) +        .wert(daten.getEidasWert()) +        .staatscode2(daten.getStaatscode2());    +  } +   +   +  /** +   * Build AT specific Date String 'yyyy-MM-dd' from ERnP birthday representation. +   * +   * <p> +   *  <b>Info:</b> {@link LocalDate} can not be used, because '1940-00-00' is also +   * a valid birthday on ERnP site. +   * </p> +   * +   * @param geburtsdatum ERnP birthday representation +   * @return birthday in 'yyyy-MM-dd' format +   */ +  private String getTextualBirthday(PartialDate geburtsdatum) { +    return MessageFormat.format("{0}-{1}-{2}", +        String.valueOf(geburtsdatum.getJahr()), +        String.format("%02d", geburtsdatum.getMonat()), +        String.format("%02d", geburtsdatum.getTag())); + +  } + +   +  /** +   * Get all eIDAS document with the specified country code and document type. +   * +   * @param person                         Person information from ERnP +   * @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<String> selectAllEidasDocument(Person person, String citizenCountryCode, +      String eidasAttrurnPersonalidentifier) { +    if (person.getEidas() != null) { +      return person.getEidas().stream() +          .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getArt()) +              && el.getStaatscode2().equals(citizenCountryCode)) +          .map(el -> el.getWert()) +          .collect(Collectors.toList()); + +    } else { +      return Collections.emptyList(); + +    } + +  } + +  /** +   * Get the first eIDAS document with the specified country code and document +   * type. +   * +   * @param person                         Person information from ERnP +   * @param citizenCountryCode             Country code of the eIDAS attribute +   * @param eidasAttrurnPersonalidentifier eIDAS attribute identifier +   * @return Value of this eIDAS attribute or <code>null</code> if's not found +   */ +  @Nullable +  private String selectSingleEidasDocument(Person person, String citizenCountryCode, +      String eidasAttrurnPersonalidentifier) { +    if (person.getEidas() != null) { +      return person.getEidas().stream() +          .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getArt()) +              && el.getStaatscode2().equals(citizenCountryCode)) +          .findFirst() +          .map(el -> el.getWert()) +          .orElse(null); + +    } else { +      return null; + +    } +  } + +  private RestTemplate buildRestClient() throws EaafException { +    log.debug("Building REST-Client for ERnP communication ... "); +    final HttpClient httpClient = httpClientFactory.getHttpClient(buildHttpClientConfiguration()); +    final ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient); +    final RestTemplate springClient = new RestTemplate(requestFactory); +    springClient.setErrorHandler(buildErrorHandler()); +    springClient.getMessageConverters().add(0, buildCustomJacksonObjectMapper()); +    return springClient; + +  } + +  private HttpMessageConverter<?> buildCustomJacksonObjectMapper() { +    final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); +    converter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_JSON)); +    converter.getObjectMapper().setSerializationInclusion(Include.NON_NULL); +     +    converter.getObjectMapper().registerModule(new JavaTimeModule()); +    converter.getObjectMapper().configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false); +    return converter; + +  } + +  @Nonnull +  private ResponseErrorHandler buildErrorHandler() { +    return new ResponseErrorHandler() { + +      @Override +      public boolean hasError(ClientHttpResponse response) throws IOException { +        return response.getStatusCode().is4xxClientError() +            || response.getStatusCode().is5xxServerError(); + +      } + +      @Override +      public void handleError(ClientHttpResponse response) throws IOException { +        // TODO: opimize errorHandling based on response info's from real ERnP + +        List<String> serverId = response.getHeaders().getOrEmpty(ERNP_RESPONSE_HEADER_SERVER_ID); +        log.warn("Receive http-error: {} from ERnP with serverTransactionId {}", +            response.getRawStatusCode(), serverId.isEmpty() ? "'not set'" : serverId.get(0)); +        log.warn("  Full ERnP response-body: {}", IOUtils.toString(response.getBody(), "UTF-8"));               +        throw new ErnpRestCommunicationException(response.getRawStatusCode()); +         +      } +    }; +  } + +  @Nonnull +  private HttpClientConfiguration buildHttpClientConfiguration() throws EaafException { +    final HttpClientConfiguration config = new HttpClientConfiguration(FRIENDLYNAME_HTTP_CLIENT); +    config.setAuthMode(ClientAuthMode.SSL.getMode()); + +    // Set keystore configuration +    config.buildKeyStoreConfig( +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_TYPE), +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_PATH), +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_PASSWORD), +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYSTORE_NAME)); + +    // Set key information +    config.setSslKeyAlias( +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEYS_ALIAS)); +    config.setSslKeyPassword( +        basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_COMMON_CLIENT_SSL_KEY_PASSWORD)); + +    // Set connection parameters +    // TODO: update EAAF-components to allow custom HTTP Connection-Timeouts + +    return config; +  } + +  @AllArgsConstructor +  @Getter +  public static class ErnpRegisterResult { +    private final List<RegisterResult> personResult; + +  } + +  private GenericRequestParams buildGenericRequestParameters(String operationIdentifier) { +    return GenericRequestParams.builder() +        .clientBehkz(basicConfig.getBasicConfiguration( +            Constants.CONIG_PROPS_EIDAS_ZMRCLIENT_REQ_ORGANIZATION_NR)) +        .clientName(MessageFormat.format(Constants.CLIENT_INFO, versionHolder.getVersion())) +        .clientRequestTime(OffsetDateTime.now()) +        .clientRequestId(TransactionIdUtils.getTransactionId() + "_" + operationIdentifier) +        .build(); + +  } + +  @Builder +  @Getter +  private static class GenericRequestParams { +    String clientBehkz; +    String clientName; +    OffsetDateTime clientRequestTime; +    String clientRequestId; + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/IErnpClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/IErnpClient.java new file mode 100644 index 00000000..7a957531 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/IErnpClient.java @@ -0,0 +1,114 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp; + +import javax.annotation.Nonnull; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.ErnpRestClient.ErnpRegisterResult; +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.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +public interface IErnpClient { + +  /** +   * Search person based on eIDAS personal identifier. +   *  +   * @param personIdentifier Full eIDAS personal identifier with prefix +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ErnpRegisterResult searchWithPersonIdentifier(@Nonnull String personIdentifier,  +      @Nonnull String citizenCountryCode) throws EidasSAuthenticationException; + +  /** +   * Search person based on eIDSA MDS information. +   *  +   * @param givenName eIDAS given name +   * @param familyName eIDAS principle name +   * @param dateOfBirth eIDAS date-of-birth +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ErnpRegisterResult searchWithMds(@Nonnull String givenName, @Nonnull String familyName,  +      @Nonnull String dateOfBirth, @Nonnull String citizenCountryCode)  +      throws EidasSAuthenticationException; + +  /** +   * Search person based on country-specific natural person set. +   *  +   * @param personSearchDao Specific set of natural person informations. +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ErnpRegisterResult searchCountrySpecific(@Nonnull PersonSuchenRequest personSearchDao,  +      @Nonnull String citizenCountryCode) throws EidasSAuthenticationException; + +  /** +   * Update ERnP entry to KITT existing ERnP identity with this eIDAS authentication. +   *  +   * @param registerResult Already matched eIDAS identity that should be KITT +   * @param eidData eIDAS eID information from current authentication process  +   * @return Update result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ErnpRegisterResult update(RegisterResult registerResult, SimpleEidasData eidData) +      throws EidasSAuthenticationException; + +   +  /** +   * Add new entry into ERnP by using identity from this eIDAS authentication. +   *  +   * @param eidData eIDAS eID information from current authentication process  +   * @return Update result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ErnpRegisterResult add(SimpleEidasData eidData) throws EidasSAuthenticationException; +   +   +  /** +   * Search person based on address information. +   *  +   * @param givenName eIDAS given name +   * @param familyName eIDAS principle name +   * @param dateOfBirth eIDAS date-of-birth +   * @param zipcode ZipCode +   * @param city City +   * @param street Street +   * @return Search result but never <code>null</code> +   */ +  @Nonnull +  ErnpRegisterResult searchWithResidenceData(String givenName, String familyName,  +      String dateOfBirth, String zipcode, String city, String street); + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrClient.java new file mode 100644 index 00000000..8c294c97 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrClient.java @@ -0,0 +1,479 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.szr; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.PostConstruct; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.Dispatch; + +import org.apache.commons.lang3.StringUtils; +import org.apache.xpath.XPathAPI; +import org.springframework.stereotype.Service; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +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.clients.AbstractSoapClient.HttpClientConfig.HttpClientConfigBuilder; +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.SzrCommunicationException; +import at.gv.e_government.reference.namespace.persondata._20020228.AlternativeNameType; +import at.gv.e_government.reference.namespace.persondata._20020228.IdentificationType; +import at.gv.e_government.reference.namespace.persondata._20020228.PersonNameType; +import at.gv.e_government.reference.namespace.persondata._20020228.PhysicalPersonType; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.DomUtils; +import lombok.extern.slf4j.Slf4j; +import szrservices.GetBPK; +import szrservices.GetBPKResponse; +import szrservices.GetIdentityLinkEidas; +import szrservices.GetIdentityLinkEidasResponse; +import szrservices.IdentityLinkType; +import szrservices.JwsHeaderParam; +import szrservices.ObjectFactory; +import szrservices.PersonInfoType; +import szrservices.SZR; +import szrservices.SZRException_Exception; +import szrservices.SignContent; +import szrservices.SignContentEntry; +import szrservices.SignContentResponseType; +import szrservices.TravelDocumentType; + + +@Slf4j +@Service("SZRClientForeIDAS") +public class SzrClient extends AbstractSoapClient { + +  private static final String CLIENT_DEFAULT = "DefaultClient"; +  private static final String CLIENT_RAW = "RawClient"; + +  private static final String ATTR_NAME_VSZ = "urn:eidgvat:attributes.vsz.value"; +  private static final String ATTR_NAME_PUBKEYS = "urn:eidgvat:attributes.user.pubkeys"; +  private static final String ATTR_NAME_STATUS = "urn:eidgvat:attributes.eid.status"; +  private static final String KEY_BC_BIND = "bcBindReq"; +  private static final String JOSE_HEADER_USERCERTPINNING_TYPE = "urn:at.gv.eid:bindtype"; +  private static final String JOSE_HEADER_USERCERTPINNING_EIDASBIND = "urn:at.gv.eid:eidasBind"; +  public static final String ATTR_NAME_MDS = "urn:eidgvat:mds"; +   +  // client for anything, without identitylink +  private SZR szr = null; + +  // RAW client is needed for identitylink +  private Dispatch<Source> dispatch = null; + +  final ObjectMapper mapper = new ObjectMapper(); + + +  /** +   * Get IdentityLink of a person. +   * +   * +   * @param matchedPersonData eID information of an already matched person. +   * @return IdentityLink +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public IdentityLinkType getIdentityLinkInRawMode(MatchedPersonResult matchedPersonData)  +      throws SzrCommunicationException { +    try { +      final GetIdentityLinkEidas getIdl = new GetIdentityLinkEidas(); +      getIdl.setPersonInfo(generateSzrRequest(matchedPersonData)); +       +      return getIdentityLinkGeneric(getIdl); + +    } catch (final Exception e) { +      log.warn("SZR communication FAILED for operation: {} Reason: {}",  +          "GetIdentityLinkEidas", e.getMessage(), e); +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); + +    } +  } +     +  /** +   * Get bPK of person. +   * +   * +   * @param eidData    Minimum dataset of person +   * @param target     requested bPK target +   * @param vkz        Verfahrenskennzeichen +   * @return bPK for this person +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public List<String> getBpk(SimpleEidasData eidData, String target, String vkz) +      throws SzrCommunicationException { +    try { +      final GetBPK parameters = new GetBPK(); +      parameters.setPersonInfo(generateSzrRequest(eidData)); +      parameters.getBereichsKennung().add(target); +      parameters.setVKZ(vkz); +      final GetBPKResponse result = this.szr.getBPK(parameters); + +      return result.getGetBPKReturn(); + +    } catch (final SZRException_Exception e) { +      log.warn("SZR communication FAILED for operation: {} Reason: {}",  +          "GetBPK", e.getMessage(), e); +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); + +    } + +  } +   +  /** +   * Request a encrypted baseId from SZR. +   *  +   * @param matchedPersonData eID information of an already matched person. +   * @return encrypted baseId +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public String getEncryptedStammzahl(MatchedPersonResult matchedPersonData) throws SzrCommunicationException { +    final String resp; +    try { +      resp = this.szr.getStammzahlEncrypted(generateSzrRequest(matchedPersonData), false); +       +    } catch (SZRException_Exception e) { +      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); +       +    } + +    if (StringUtils.isEmpty(resp)) { +      throw new SzrCommunicationException("ernb.01", new Object[]{"Stammzahl response empty"}); // TODO error handling +       +    } + +    return resp;   +  } +       +  /** +   * Sign an eidasBind data-structure that combines vsz with user's pubKey and E-ID status. +   * +   * @param vsz encrypted baseId +   * @param bindingPubKey  binding PublicKey as PKCS1# (ASN.1) container +   * @param eidStatus Status of the E-ID +   * @param eidData eID information that was used for ERnP registration +   * @return bPK for this person +   * @throws SzrCommunicationException In case of a SZR error +   */ +  public String getEidasBind(final String vsz, final String bindingPubKey, final String eidStatus, +                             SimpleEidasData eidData)throws SzrCommunicationException { + +    final Map<String, Object> eidsaBindMap = new HashMap<>(); +    eidsaBindMap.put(ATTR_NAME_VSZ, vsz); +    eidsaBindMap.put(ATTR_NAME_STATUS, eidStatus); +    eidsaBindMap.put(ATTR_NAME_PUBKEYS, Collections.singletonList(bindingPubKey)); +    eidsaBindMap.put(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, eidData.getCitizenCountryCode()); +    injectMdsIfAvailableAndActive(eidsaBindMap, eidData); + +    try { +      final String serializedEidasBind = mapper.writeValueAsString(eidsaBindMap); +      final SignContent req = new SignContent(); +      final SignContentEntry eidasBindInfo = new SignContentEntry(); +      eidasBindInfo.setKey(KEY_BC_BIND); +      eidasBindInfo.setValue(serializedEidasBind); +      req.getIn().add(eidasBindInfo); +      req.setAppendCert(false); +      final JwsHeaderParam eidasBindJoseHeader = new JwsHeaderParam(); +      eidasBindJoseHeader.setKey(JOSE_HEADER_USERCERTPINNING_TYPE); +      eidasBindJoseHeader.setValue(JOSE_HEADER_USERCERTPINNING_EIDASBIND); +      req.getJWSHeaderParam().add(eidasBindJoseHeader); + +      log.trace("Requesting SZR to sign bcBind datastructure ... "); +      final SignContentResponseType resp = szr.signContent(req.isAppendCert(), req.getJWSHeaderParam(), req.getIn()); +      log.trace("Receive SZR response on bcBind siging operation "); + +      if (resp == null || resp.getOut() == null +          || resp.getOut().isEmpty() +          || StringUtils.isEmpty(resp.getOut().get(0).getValue())) { +        throw new SzrCommunicationException("ernb.01", new Object[]{"BcBind response empty"}); +      } + +      return resp.getOut().get(0).getValue(); + +    } catch (final JsonProcessingException | SZRException_Exception e) { +      log.warn("SZR communication FAILED for operation: {} Reason: {}",  +          "SignContent", e.getMessage(), e); +      throw new SzrCommunicationException("ernb.02", +          new Object[]{e.getMessage()}, e); +    } +  } + +  private PersonInfoType generateSzrRequest(MatchedPersonResult matchedPersonData) { +    log.trace("Starting connecting SZR Gateway"); +    final PersonInfoType personInfo = new PersonInfoType(); +    final PersonNameType personName = new PersonNameType(); +    final PhysicalPersonType naturalPerson = new PhysicalPersonType(); +    IdentificationType bpk = new IdentificationType(); +     +    naturalPerson.setName(personName); +    personInfo.setPerson(naturalPerson); +    naturalPerson.setIdentification(bpk); +     +    // person information +    personName.setFamilyName(matchedPersonData.getFamilyName()); +    personName.setGivenName(matchedPersonData.getGivenName()); +    naturalPerson.setDateOfBirth(matchedPersonData.getDateOfBirth()); +    bpk.setValue(matchedPersonData.getBpk()); +    bpk.setType(EaafConstants.URN_PREFIX_CDID + "ZP");     +     +    return personInfo; +  } +   +  private PersonInfoType generateSzrRequest(SimpleEidasData eidData) { +    log.trace("Starting connecting SZR Gateway"); +    final PersonInfoType personInfo = new PersonInfoType(); +    final PersonNameType personName = new PersonNameType(); +    final PhysicalPersonType naturalPerson = new PhysicalPersonType(); +    final TravelDocumentType eDocument = new TravelDocumentType(); + +    naturalPerson.setName(personName); +    personInfo.setPerson(naturalPerson); +    personInfo.setTravelDocument(eDocument); + +    // person information +    personName.setFamilyName(eidData.getFamilyName()); +    personName.setGivenName(eidData.getGivenName()); +    naturalPerson.setDateOfBirth(eidData.getDateOfBirth()); +     +    //TODO: need to be updated to new eIDAS document interface!!!! +    eDocument.setIssuingCountry(eidData.getCitizenCountryCode()); +    eDocument.setDocumentNumber(eidData.getPseudonym()); + +    // eID document information +    String documentType = basicConfig +        .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE, +                               Constants.SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE); +    eDocument.setDocumentType(documentType); + +    // set PlaceOfBirth if available +    if (eidData.getPlaceOfBirth() != null) { +      log.trace("Find 'PlaceOfBirth' attribute: " + eidData.getPlaceOfBirth()); +      boolean setPlaceOfBirth = basicConfig +          .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETPLACEOFBIRTHIFAVAILABLE, true); +      if (setPlaceOfBirth) { +        naturalPerson.setPlaceOfBirth(eidData.getPlaceOfBirth()); +        log.trace("Adding 'PlaceOfBirth' to ERnB request ... "); +      } +    } + +    // set BirthName if available +    if (eidData.getBirthName() != null) { +      log.trace("Find 'BirthName' attribute: " + eidData.getBirthName()); +      boolean setBirthName = basicConfig +          .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETBIRTHNAMEIFAVAILABLE, true); +      if (setBirthName) { +        final AlternativeNameType alternativeName = new AlternativeNameType(); +        naturalPerson.setAlternativeName(alternativeName); +        alternativeName.setFamilyName(eidData.getBirthName()); +        log.trace("Adding 'BirthName' to ERnB request ... "); +      } +    } + +    return personInfo; +  } + +  private IdentityLinkType getIdentityLinkGeneric(GetIdentityLinkEidas getIdl) throws Exception { +    final JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class); +    final Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); + +    final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); +    jaxbMarshaller.marshal(getIdl, outputStream); +    outputStream.flush(); + +    final Source source = new StreamSource(new ByteArrayInputStream(outputStream.toByteArray())); +    outputStream.close(); + +    log.trace("Requesting SZR ... "); +    final Source response = dispatch.invoke(source); +    log.trace("Receive RAW response from SZR"); + +    final byte[] szrResponse = sourceToByteArray(response); +    final GetIdentityLinkEidasResponse jaxbElement = (GetIdentityLinkEidasResponse) jaxbContext +        .createUnmarshaller().unmarshal(new ByteArrayInputStream(szrResponse)); + +    // build response +    log.trace(new String(szrResponse, StandardCharsets.UTF_8)); + +    // ok, we have success +    final Document doc = DomUtils.parseDocument( +        new ByteArrayInputStream(szrResponse), +        true, +        XmlNamespaceConstants.ALL_SCHEMA_LOCATIONS + " " + Constants.SZR_SCHEMA_LOCATIONS, +        null, null); +    final String xpathExpression = "//saml:Assertion"; +    final Element nsNode = doc.createElementNS("urn:oasis:names:tc:SAML:1.0:assertion", "saml:NSNode"); + +    log.trace("Selecting signed doc " + xpathExpression); +    final Element documentNode = (Element) XPathAPI.selectSingleNode(doc, +        xpathExpression, nsNode); +    log.trace("Signed document: " + DomUtils.serializeNode(documentNode)); + +    final IdentityLinkType idl = new IdentityLinkType(); +    idl.setAssertion(documentNode); +    idl.setPersonInfo(jaxbElement.getGetIdentityLinkReturn().getPersonInfo()); + +    return idl; +     +  } +   +  @PostConstruct +  private void initialize() throws EaafConfigurationException { +    log.info("Starting SZR-Client initialization .... "); +    final URL url = SzrClient.class.getResource("/wsdl/szr_client/SZR_v4.0.wsdl"); + +    final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, +        true); + +    SzrService szrService; +    QName qname; +    String szrUrl; +    if (useTestSzr) { +      log.debug("Initializing SZR test environment configuration."); +      qname = SzrService.SZRTestumgebung; +      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); +      szr = szrService.getSzrTestumgebung(); +      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_TEST); + +    } else { +      log.debug("Initializing SZR productive configuration."); +      qname = SzrService.SZRProduktionsumgebung; +      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); +      szr = szrService.getSzrProduktionsumgebung(); +      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_PROD); + +    } + +    // create raw client; +    dispatch = szrService.createDispatch(qname, Source.class, javax.xml.ws.Service.Mode.PAYLOAD); + +    if (StringUtils.isEmpty(szrUrl)) { +      log.error("No SZR service-URL found. SZR-Client initalisiation failed."); +      throw new RuntimeException("No SZR service URL found. SZR-Client initalisiation failed."); + +    } + +    // check if Clients can be initialized +    if (szr == null) { +      log.error("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); +      throw new RuntimeException("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); + +    } +    if (dispatch == null) { +      log.error("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); +      throw new RuntimeException("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); + +    } + +    // inject handler +    log.info("Use SZR service-URL: " + szrUrl); +    injectBindingProvider((BindingProvider) szr, CLIENT_DEFAULT, szrUrl, +        basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, false)); +    injectBindingProvider(dispatch, CLIENT_RAW, szrUrl,  +        basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, false)); + +    // inject http parameters and SSL context +    log.debug("Inject HTTP client settings ... ");     +    HttpClientConfigBuilder httpClientBuilder = HttpClientConfig.builder() +        .clientName("SZR Client") +        .clientUrl(szrUrl) +        .connectionTimeout(basicConfig.getBasicConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION,  +            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) +        .responseTimeout(basicConfig.getBasicConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE,  +            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) +        .keyStoreConfig(buildKeyStoreConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_TYPE, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_NAME, +            "SZR SSL Client-Authentication KeyStore")) +        .keyAlias(basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYS_ALIAS)) +        .keyPassword(basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEY_PASSWORD)) +        .trustAll(false) +        .trustStoreConfig(buildKeyStoreConfiguration( +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_TYPE, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD, +            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_NAME, +            "SZR SSL Client-Authentication KeyStore")); +                         +    injectHttpClient(szr, httpClientBuilder.clientType(CLIENT_DEFAULT).build()); +    injectHttpClient(dispatch, httpClientBuilder.clientType(CLIENT_RAW).build()); + +    log.info("SZR-Client initialization successfull"); +  } + +  private void injectMdsIfAvailableAndActive(Map<String, Object> eidsaBindMap, SimpleEidasData eidData) { +    if (basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SET_MDS_TO_EIDASBIND, false)) { +      log.info("Injecting MDS into eidasBind ... "); +      final Map<String, Object> mds = new HashMap<>(); +      mds.put(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, eidData.getFamilyName()); +      mds.put(PvpAttributeDefinitions.GIVEN_NAME_NAME, eidData.getGivenName()); +      mds.put(PvpAttributeDefinitions.BIRTHDATE_NAME, eidData.getDateOfBirth()); +      eidsaBindMap.put(ATTR_NAME_MDS, mds); + +    } +  } + +  private byte[] sourceToByteArray(Source result) throws TransformerException { +    final TransformerFactory factory = TransformerFactory.newInstance(); +    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); +    final Transformer transformer = factory.newTransformer(); +    transformer.setOutputProperty("omit-xml-declaration", "yes"); +    transformer.setOutputProperty("method", "xml"); +    final ByteArrayOutputStream out = new ByteArrayOutputStream(); +    final StreamResult streamResult = new StreamResult(); +    streamResult.setOutputStream(out); +    transformer.transform(result, streamResult); +    return out.toByteArray(); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrService.java index dde868b1..590f88a4 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrService.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/szr/SzrService.java @@ -21,7 +21,7 @@   * that you distribute must include a readable copy of the "NOTICE" text file.  */ -package at.asitplus.eidas.specific.modules.auth.eidas.v2.szr; +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.szr;  import java.net.URL; @@ -52,7 +52,7 @@ public class SzrService extends Service {        "SZRBusinesspartnerTestumgebung");    static { -    URL url = SzrService.class.getResource("./src/main/resources/szr_client/SZR-1.WSDL"); +    URL url = SzrService.class.getResource("./src/main/resources/wsdl/szr_client/SZR-1.WSDL");      if (url == null) {        url = SzrService.class.getClassLoader().getResource("/szr_client/SZR-1.WSDL");      } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java new file mode 100644 index 00000000..f3c32c96 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/IZmrClient.java @@ -0,0 +1,113 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr; + +import java.math.BigInteger; + +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; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +public interface IZmrClient { + +  /** +   * Search person based on eIDAS personal identifier. +   *  +   * @param zmrProzessId ProcessId from ZMR or <code>null</code> if no processId exists +   * @param personIdentifier Full eIDAS personal identifier with prefix +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ZmrRegisterResult searchWithPersonIdentifier(@Nullable BigInteger zmrProzessId, @Nonnull String personIdentifier,  +      @Nonnull String citizenCountryCode) throws EidasSAuthenticationException; + +  /** +   * Search person based on eIDSA MDS information. +   *  +   * @param zmrProzessId ProcessId from ZMR or <code>null</code> if no processId exists +   * @param givenName eIDAS given name +   * @param familyName eIDAS principle name +   * @param dateOfBirth eIDAS date-of-birth +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ZmrRegisterResult searchWithMds(@Nullable BigInteger zmrProzessId, @Nonnull String givenName,  +      @Nonnull String familyName, @Nonnull String dateOfBirth, @Nonnull String citizenCountryCode)  +          throws EidasSAuthenticationException; + +  /** +   * Search person based on country-specific natural person set. +   *  +   * @param zmrProzessId ProcessId from ZMR or <code>null</code> if no processId exists +   * @param personSearchDao Specific set of natural person informations. +   * @param citizenCountryCode CountryCode of the eIDAS proxy-service +   * @return Search result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ZmrRegisterResult searchCountrySpecific(@Nullable BigInteger zmrProzessId,  +      @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 <code>null</code> 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 <code>null</code> +   * @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. +   *  +   * @param zmrProzessId zmrProzessId ProcessId from ZMR or <code>null</code> if no processId exists +   * @param registerResult Already matched eIDAS identity that should be KITT +   * @param eidData eIDAS eID information from current authentication process  +   * @return Update result but never <code>null</code> +   * @throws EidasSAuthenticationException In case of a communication error  +   */ +  @Nonnull +  ZmrRegisterResult update(@Nullable BigInteger zmrProzessId, RegisterResult registerResult, SimpleEidasData eidData) +      throws EidasSAuthenticationException; + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrAddressSoapClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrAddressSoapClient.java new file mode 100644 index 00000000..6e146ddf --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrAddressSoapClient.java @@ -0,0 +1,286 @@ +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.List; + +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.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.exception.EidasSAuthenticationException; +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_.address.ClientInfoType; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.Organisation; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.RequestType; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.ResponseType; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.Service; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.ServiceFault_Exception; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.ServicePort; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.WorkflowInfoClient; +import at.gv.bmi.namespace.zmr_su.base._20040201_.address.WorkflowInfoServer; +import at.gv.bmi.namespace.zmr_su.zrm._20040201_.address.Adressdaten; +import at.gv.bmi.namespace.zmr_su.zrm._20040201_.address.AdresssucheInfoType; +import at.gv.bmi.namespace.zmr_su.zrm._20040201_.address.AdresssucheRequest; +import at.gv.bmi.namespace.zmr_su.zrm._20040201_.address.AdresssuchergebnisType; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +/** + * ZMR SOAP client for search-address operations. + * + * @author tlenz + * + */ +@Slf4j +public class ZmrAddressSoapClient extends AbstractSoapClient { + +  private static final String CLIENT_DEFAULT = "ZMR-AddressSearch Client"; +  private static final String CLIENT_INFO = "eIDAS MS-Connector v{0}"; + +  private static final String LOGMSG_ZMR_SOAP_ERROR = +      "ZMR anwser for transaction: {0} with code: {1} and message: {2}"; +  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 ERROR_MATCHING_07 = "module.eidasauth.matching.07"; +  private static final String ERROR_MATCHING_99 = "module.eidasauth.matching.99"; + +  private static final String PROCESS_GENERAL = "GP_Abfragen"; +  private static final String PROCESS_TASK_ADDRESS_WIZZARD = "ZMR_VO_Adresssuche_im_GWR__6"; + +  private static final String PROCESS_TASK_RESPONSE_LEVEL_CITY = "Ortschaft"; +  private static final String PROCESS_TASK_RESPONSE_LEVEL_STREET = "Strassenname"; +  private static final String PROCESS_TASK_RESPONSE_LEVEL_NUMBER = "Orientierungsnummer"; + + +  private static final String PROCESS_ADDRESS_WIZZARD = "PROCESS_SEARCH_WITH_ADDRESS_WIZZARD"; + +  private static final String SEARCH_TYPE = "ADRESSSUCHE"; + + +  @Autowired VersionHolder versionHolder; +  private ServicePort zmrClient; + +  @Getter +  @AllArgsConstructor +  public static class AddressInfo { +    private final BigInteger processId; +    private final List<Adressdaten> personResult; +    private final DetailLevel level; + +  } + +  public enum DetailLevel { CITY, STREET, NUMBER, UNKNOWN } + +  /** +   * Get address information based on ZMR data. +   * +   * @param addressInfo Search parameters +   * @return Address data +   * @throws EidasSAuthenticationException In case of an error +   */ +  public AddressInfo searchAddress(@NonNull Adressdaten addressInfo) +      throws EidasSAuthenticationException { +    return searchAddress(addressInfo, null); + +  } + +  /** +   * Get address information based on ZMR data. +   * +   * @param addressInfo Search parameters +   * @param prozessInstanzId processId in case of associated requests +   * @return Address data +   * @throws EidasSAuthenticationException In case of an error +   */ +  public AddressInfo searchAddress(@NonNull Adressdaten addressInfo, @Nullable BigInteger prozessInstanzId) +      throws EidasSAuthenticationException { +    try { +      RequestType req = new RequestType(); + +      // set generic informations +      req.setClientInfo(generateClientInfos()); +      req.setWorkflowInfoClient(generateWorkFlowInfos(PROCESS_TASK_ADDRESS_WIZZARD, null)); + +      AdresssucheRequest search = new AdresssucheRequest(); +      req.setAdresssucheRequest(search); + +      // set static search type +      AdresssucheInfoType searchType = new AdresssucheInfoType(); +      searchType.setSuchart(SEARCH_TYPE); +      search.setAdresssucheInfo(searchType); + +      // set search parameters +      search.setAdressdaten(addressInfo); + +      // request ZMR address services +      log.debug("Requesting ZMR for adddress search ...."); +      ResponseType resp = zmrClient.service(req, null); +      log.debug("Receice response for address search with #{} elements", +          resp.getAdresssucheResponse().getAdresssuchergebnis().getGefundeneSaetze()); + +      return new AddressInfo( +          extractZmrProcessId(resp.getWorkflowInfoServer()), +          resp.getAdresssucheResponse().getAdresssuchergebnis().getAdressdaten(), +          extractAddressDetailLevel(resp.getAdresssucheResponse().getAdresssuchergebnis())); + +    } catch (final ServiceFault_Exception e) { +      final String errorMsg = extractReasonFromError(e); +      log.warn(LOGMSG_ZMR_ERROR, PROCESS_ADDRESS_WIZZARD, errorMsg); +      throw new ZmrCommunicationException(ERROR_MATCHING_07, new Object[] { errorMsg }, e); + +    } catch (final Exception e) { +      log.warn(LOGMSG_ZMR_RESP_PROCESS, PROCESS_ADDRESS_WIZZARD, 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(); + +  } + +  private void initializeTechnicalZmrClient() throws EaafConfigurationException { +    log.info("Starting ZMR-AddressSearch Client initialization .... "); +    final URL url = ZmrAddressSoapClient.class.getResource("/wsdl/addresssearching_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-AddressSearch service-URL found. ZMR-AddressSearch-Client initalisiation failed."); +      throw new RuntimeException( +          "No ZMR-AddressSearch service URL found. ZMR-AddressSearch-Client initalisiation failed."); + +    } + +    // inject handler +    log.info("Use ZMR-AddressSearch 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-AddressSearch 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-AddressSearch SSL Client-Authentication TrustStore")) +        .build()); + +  } + +  @Nonnull +  private ClientInfoType generateClientInfos() { +    final ClientInfoType clientInfo = new ClientInfoType(); +    final Organisation clientOrganisation = new Organisation(); +    clientInfo.setOrganisation(clientOrganisation); + +    // set client information +    clientInfo.setClient(MessageFormat.format(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 static String extractReasonFromError(ServiceFault_Exception 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 static 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; + +  } + +  private static BigInteger extractZmrProcessId(WorkflowInfoServer workflowInfoServer) { +    return workflowInfoServer != null ? workflowInfoServer.getProzessInstanzID() : null; + +  } + +  private static DetailLevel extractAddressDetailLevel(AdresssuchergebnisType value) { +    if (value.getDetailgrad() == null) { +      return DetailLevel.UNKNOWN; +    } +    switch (value.getDetailgrad()) { +      case PROCESS_TASK_RESPONSE_LEVEL_CITY: +        return DetailLevel.CITY; + +      case PROCESS_TASK_RESPONSE_LEVEL_STREET: +        return DetailLevel.STREET; + +      case PROCESS_TASK_RESPONSE_LEVEL_NUMBER: +        return DetailLevel.NUMBER; + +      default: +        return DetailLevel.UNKNOWN; + +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java new file mode 100644 index 00000000..8dbd0632 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/zmr/ZmrSoapClient.java @@ -0,0 +1,874 @@ +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<RegisterResult> 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<? extends EidasIdentitaetAnlageType> eidasDocumentToAdd =  +          selectEidasDocumentsToAdd(zmrPersonToKitt, eidData); +       +      /*TODO: Is there a requirement to change 'eIDAS-Documents'? +       *      We add MDS information as 'eIDAS-Documents' too. Maybe, we should update that in a later version.   +       */ +       +      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<RegisterResult> processSearchPersonResponse( +      @Nonnull List<PersonErgebnisSatzType> 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<RegisterResult> processSearchPersonResponseSingleResult( +      @Nonnull List<PersonErgebnisSatzType> 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<String> 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 <code>null</code> 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<PersonErgebnisSatzType> 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<? extends EidasIdentitaetAnlageType> selectEidasDocumentsToAdd( +      PersonErgebnisType zmrPersonToKitt, SimpleEidasData eidData) { + +    //TODO: maybe we should re-factor SimpleEidasData to a generic data-model to facilitate arbitrary eIDAS attributes   +    Set<EidasIdentitaetAnlageType> 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); +     +    // add MDS attributes as 'eIDAS-Documents' too, because ZMR does not allow a MDS update on regular places. +    addEidasDocumentIfNotAvailable(result, zmrPersonToKitt, eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_CURRENTGIVENNAME, eidData.getGivenName(), false); +    addEidasDocumentIfNotAvailable(result, zmrPersonToKitt, eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_CURRENTFAMILYNAME, eidData.getFamilyName(), false); +    addEidasDocumentIfNotAvailable(result, zmrPersonToKitt,  eidData.getCitizenCountryCode(),  +        Constants.eIDAS_ATTRURN_DATEOFBIRTH, eidData.getDateOfBirth(), false); +         +    return result; +     +  } +   +  private void addEidasDocumentIfNotAvailable(Set<EidasIdentitaetAnlageType> 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<EidasIdentitaetErgebnisType> 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<? extends EidasIdentitaetAnlageType> 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())); +     +  } +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/config/EidasConnectorMessageSource.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/config/EidasConnectorMessageSource.java new file mode 100644 index 00000000..7a9f472a --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/config/EidasConnectorMessageSource.java @@ -0,0 +1,21 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.config; + +import java.util.Arrays; +import java.util.List; + +import at.gv.egiz.eaaf.core.api.logging.IMessageSourceLocation; + +/** + * Inject eIDAS Connector specific messages into Spring based message-source. + *  + * @author tlenz + * + */ +public class EidasConnectorMessageSource implements IMessageSourceLocation { + +  @Override +  public List<String> getMessageSourceLocation() { +    return Arrays.asList("classpath:/messages/eidas_connector_message"); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java new file mode 100644 index 00000000..6f49c700 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/controller/AdresssucheController.java @@ -0,0 +1,195 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.controller; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.builder.CompareToBuilder; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.zmr.ZmrAddressSoapClient; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.gv.bmi.namespace.zmr_su.zrm._20040201_.address.Adressdaten; +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.api.utils.IPendingRequestIdGenerationStrategy; +import at.gv.egiz.eaaf.core.exceptions.PendingReqIdValidationException; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +/** + * Default process-engine signaling controller. + * + * @author tlenz + */ +@Controller +@Slf4j +public class AdresssucheController { + +  public static final String PARAM_POSTLEITZAHL = "postleitzahl"; +  public static final String PARAM_MUNIPICALITY = "municipality"; +  public static final String PARAM_VILLAGE = "village"; +  public static final String PARAM_STREET = "street"; +  public static final String PARAM_NUMBER = "number"; +     +  @Autowired +  private ZmrAddressSoapClient client; + +  @Autowired +  private IPendingRequestIdGenerationStrategy pendingReqGeneration; +   +  /** +   * Performs search for addresses in ZMR. +   */ +  @RequestMapping(value = {MsEidasNodeConstants.ENDPOINT_RESIDENCY_SEARCH}, method = {RequestMethod.POST}) +  public ResponseEntity<AdresssucheResult> search( +      @RequestParam(PARAM_POSTLEITZAHL) String postleitzahl, +      @RequestParam(PARAM_MUNIPICALITY) String municipality, +      @RequestParam(PARAM_VILLAGE) String village, +      @RequestParam(PARAM_STREET) String street, +      @RequestParam(PARAM_NUMBER) String number, +      @RequestParam(EaafConstants.PARAM_HTTP_TARGET_PENDINGREQUESTID) String pendingId) { +    log.info("Search with '{}', '{}', '{}', '{}', '{}'", +        postleitzahl.replaceAll("[\r\n]", ""), +        municipality.replaceAll("[\r\n]", ""), +        village.replaceAll("[\r\n]", ""), +        street.replaceAll("[\r\n]", ""), +        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(); +       +    } +  } + +  private AdresssucheResult buildResponse(ZmrAddressSoapClient.AddressInfo searchOutput) { +    if (searchOutput.getPersonResult().isEmpty()) { +      log.warn("No result from ZMR"); +      return new AdresssucheResult(Collections.emptyList(), 0); +       +    } +     +    log.info("Result level is {}", searchOutput.getLevel()); +    Set<AdresssucheOutput> result = searchOutput.getPersonResult().stream() +        .map(Adressdaten::getPostAdresse) +        .map(it -> new AdresssucheOutput(it.getPostleitzahl(), it.getGemeinde(), it.getOrtschaft(), +            it.getZustelladresse().getStrassenname(), it.getZustelladresse().getOrientierungsnummer())) +        .collect(Collectors.toSet()); +    // TODO Add configuration option for the limit of 30 +    List<AdresssucheOutput> sorted = result.stream().sorted().limit(30).collect(Collectors.toList()); +    return new AdresssucheResult(sorted, result.size()); +     +  } + +  private Adressdaten buildSearchInput(String postleitzahl, +                                       String municipality, +                                       String village, +                                       String street, +                                       String number) { +    PostAdresseTyp postAdresse = new PostAdresseTyp(); +    if (StringUtils.isNotBlank(postleitzahl)) { +      postAdresse.setPostleitzahl(postleitzahl); +    } +    if (StringUtils.isNotBlank(municipality)) { +      postAdresse.setGemeinde(municipality); +    } +    if (StringUtils.isNotBlank(village)) { +      postAdresse.setOrtschaft(village); +    } +    if (StringUtils.isNotBlank(street) || StringUtils.isNotBlank(number)) { +      ZustelladresseTyp zustelladresse = new ZustelladresseTyp(); +      if (StringUtils.isNotBlank(street)) { +        zustelladresse.setStrassenname(street); +      } +      if (StringUtils.isNotBlank(number)) { +        zustelladresse.setOrientierungsnummer(number); +      } +      postAdresse.setZustelladresse(zustelladresse); +    } +    Adressdaten searchInput = new Adressdaten(); +    searchInput.setPostAdresse(postAdresse); +    return searchInput; +     +  } + +  @Data +  @AllArgsConstructor +  public static class AdresssucheResult { +    private final Collection<AdresssucheOutput> results; +    private final int resultCount; +  } + +  @Data +  @AllArgsConstructor +  @Builder +  public static class AdresssucheOutput implements Comparable<AdresssucheOutput> { +    private final String postleitzahl; +    private final String municipality; +    private final String village; +    private final String street; +    private final String number; + +    @Override +    public int compareTo(@NotNull AdresssucheOutput o) { +      return new CompareToBuilder() +          .append(this.postleitzahl, o.postleitzahl) +          .append(this.municipality, o.municipality) +          .append(this.village, o.village) +          .append(this.street, o.street) +          .append(this.number, o.number) +          .toComparison(); +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/MatchedPersonResult.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/MatchedPersonResult.java new file mode 100644 index 00000000..1dcea7fc --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/MatchedPersonResult.java @@ -0,0 +1,45 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.dao; + +import java.io.Serializable; + +import lombok.Builder; +import lombok.Getter; + +/** + * Information about a natural person that is already matched. + *  + * @author tlenz + * + */ +@Getter +@Builder +public class MatchedPersonResult implements Serializable { + +  private static final long serialVersionUID = 9110998952621456281L; + +  /** +   * Matched person result from matching result. +   *  +   * @param matchingResult Result of the matching process +   * @param citizenCountryCode Country-Code of the eIDAS Proxy-Service  +   */ +  public static MatchedPersonResult generateFormMatchingResult(RegisterResult matchingResult,  +      String citizenCountryCode) { +    return MatchedPersonResult.builder() +        .familyName(matchingResult.getFamilyName()) +        .givenName(matchingResult.getGivenName()) +        .dateOfBirth(matchingResult.getDateOfBirth()) +        .bpk(matchingResult.getBpk()) +        .countryCode(citizenCountryCode) +        .build();                 +  } +     +  private final String countryCode; +  private final String givenName; +  private final String familyName; +  private final String dateOfBirth; +  private final String bpk; +   +  private String vsz; +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/RegisterResult.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/RegisterResult.java new file mode 100644 index 00000000..e5878ff3 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/RegisterResult.java @@ -0,0 +1,54 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.dao; + +import java.io.Serializable; +import java.util.List; + +import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +public class RegisterResult implements Serializable { +     +  private static final long serialVersionUID = 762728480185716130L; + +  // MDS +  private final List<String> pseudonym; +  private final String givenName; +  private final String familyName; +  private final String dateOfBirth; + +  // additional attributes +  private String placeOfBirth; +  private String birthName; +  private String taxNumber; +  private PostalAddressType address; + +  private String bpk; + +     +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SelectedLoginMethod.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SelectedLoginMethod.java new file mode 100644 index 00000000..70904e4f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SelectedLoginMethod.java @@ -0,0 +1,5 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.dao; + +public enum SelectedLoginMethod { +  EIDAS_LOGIN, MOBILE_PHONE_SIGNATURE_LOGIN, NO_OTHER_LOGIN, ADD_ME_AS_NEW +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleEidasData.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleEidasData.java new file mode 100644 index 00000000..aca5025f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleEidasData.java @@ -0,0 +1,108 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.dao; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.EqualsBuilder; + +import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder(toBuilder = true) +public class SimpleEidasData implements Serializable { + +  private static final long serialVersionUID = 2848914124372968418L; + +  /** +   * Full eIDAS personal identifier with prefix. +   */ +  private final String personalIdentifier; + +  /** +   * Citizen country-code from eIDAS personal-identifier. +   */ +  private final String citizenCountryCode; + +  // MDS +  /** +   * eIDAS personal identifier without prefix. +   */ +  private final String pseudonym; +  private final String givenName; +  private final String familyName; +  private final String dateOfBirth; + +  // additional attributes +  private final String placeOfBirth; +  private final String birthName; +  private final PostalAddressType address; +  private final String taxNumber; + +  /** +   * Compares the register result with the EIDAS data (given name, family name, date of birth, personal identifier). +   * +   * @param result The register data to use for comparison +   * @return whether the data (given name, family name, date of birth, personal identifier) match +   */ +  public boolean equalsRegisterData(RegisterResult result) { +    return new EqualsBuilder() +        .append(result.getGivenName(), givenName) +        .append(result.getFamilyName(), familyName) +        .append(result.getDateOfBirth(), dateOfBirth) +        .appendSuper(result.getPseudonym().stream().anyMatch(el -> el.equals(pseudonym))) +        .appendSuper(checkOptionalAttributes(result.getPlaceOfBirth(), placeOfBirth)) +        .appendSuper(checkOptionalAttributes(result.getBirthName(), birthName)) +        .isEquals(); +         +  } + +  /** +   * Checks if the MDS (<code>givenName</code>, <code>familyName</code>, +   * <code>dateOfBirth</code>) matches. +   */ +  public boolean equalsMds(SimpleEidasData other) { +    return new EqualsBuilder() +        .append(other.givenName, givenName) +        .append(other.familyName, familyName) +        .append(other.dateOfBirth, dateOfBirth) +        .isEquals(); +  } + +  /** +   * Check if eIDAS attribute is available. +   *  +   * @param registerData Attribute value from register +   * @param eidasData Attribute value from eIDAS +   * @return <code>true</code> if eidasData is <code>null</code> or eidasData does not match to register value,  +   *     otherwise <code>false</code>  +   */ +  private static boolean checkOptionalAttributes(String registerData, String eidasData) { +    return eidasData == null || eidasData.equals(registerData); +     +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleMobileSignatureData.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleMobileSignatureData.java new file mode 100644 index 00000000..54cb3f31 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/dao/SimpleMobileSignatureData.java @@ -0,0 +1,57 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.dao; + +import java.io.Serializable; + +import org.apache.commons.lang3.builder.EqualsBuilder; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class SimpleMobileSignatureData implements Serializable { + +  private static final long serialVersionUID = 7775305733438275312L; + +  private final String bpk; +  private final String givenName; +  private final String familyName; +  private final String dateOfBirth; + +  /** +   * Compares the received authentication data from the mobile phone signature with the eid data received via eIDAS. +   * +   * @param simpleEidasData The extracted eIDAS data +   * @return Returns true, if the eIDAS data matches the mobile phone signature data and false otherwise. +   */ +  public boolean equalsSimpleEidasData(SimpleEidasData simpleEidasData) { +    return new EqualsBuilder() +        .append(simpleEidasData.getGivenName(), givenName) +        .append(simpleEidasData.getFamilyName(), familyName) +        .append(simpleEidasData.getDateOfBirth(), dateOfBirth) +        .isEquals(); +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/ernp/DummyErnpClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/ernp/DummyErnpClient.java new file mode 100644 index 00000000..dabb73dc --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/ernp/DummyErnpClient.java @@ -0,0 +1,81 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.ernp; + +import java.util.Collections; + +import org.springframework.stereotype.Service; + +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.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.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +@Service("ErnbClientForeIDAS") +public class DummyErnpClient implements IErnpClient { + +   +  @Override +  public ErnpRegisterResult searchWithPersonIdentifier(String personIdentifier, String citizenCountryCode) +      throws EidasSAuthenticationException { +    return buildEmptyResult(); +  } + +  @Override +  public ErnpRegisterResult searchWithMds(String givenName, String familyName, String dateOfBirth, +      String citizenCountryCode) throws EidasSAuthenticationException { +    return buildEmptyResult(); +  } + +  @Override +  public ErnpRegisterResult searchCountrySpecific(PersonSuchenRequest personSearchDao, +      String citizenCountryCode) throws EidasSAuthenticationException { +    return buildEmptyResult(); +  } + +  @Override +  public ErnpRegisterResult update(RegisterResult registerResult, SimpleEidasData eidData) +      throws EidasSAuthenticationException { +    return buildEmptyResult(); +  } + +  @Override +  public ErnpRegisterResult searchWithResidenceData(String givenName, String familyName, String dateOfBirth, +      String zipcode, String city, String street) { +    return buildEmptyResult(); +  } + +  private static ErnpRegisterResult buildEmptyResult() { +    return new ErnpRegisterResult(Collections.emptyList()); +     +  } + +  @Override +  public ErnpRegisterResult add(SimpleEidasData eidData) throws EidasSAuthenticationException { +    return buildEmptyResult(); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java new file mode 100644 index 00000000..566a8fa4 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java @@ -0,0 +1,29 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.exception; + +import java.io.IOException; + +import lombok.Getter; + +/** + * ERnP exception in case of a REST communication error. + *  + * @author tlenz + * + */ +public class ErnpRestCommunicationException extends IOException { +  private static final long serialVersionUID = -3178689122077228976L; +   +  @Getter +  int httpStatusCode; +   +  /** +   * ERnP communication error on HTTP REST level. +   *  +   * @param rawStatusCode HTTP statuscode +   */ +  public ErnpRestCommunicationException(int rawStatusCode) { +    super("ERnP service answers with an error and HTTP status-code: " + rawStatusCode); +    this.httpStatusCode = rawStatusCode;     +     +  }   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ManualFixNecessaryException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ManualFixNecessaryException.java new file mode 100644 index 00000000..cf69bd2c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ManualFixNecessaryException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. +*/ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.exception; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; + +public class ManualFixNecessaryException extends EidasSAuthenticationException { +  private static final long serialVersionUID = 1L; + +  //TODO: should we pass some infos?   +  public ManualFixNecessaryException(String personIdentifier) { +    super("module.eidasauth.matching.04", new Object[] { personIdentifier }); +  } + +  public ManualFixNecessaryException(SimpleEidasData eidData) { +    super("module.eidasauth.matching.04", new Object[] { eidData.getPseudonym() }); +  } +   +  public ManualFixNecessaryException(SimpleEidasData eidData, Throwable e) { +    super("module.eidasauth.matching.04", new Object[] { eidData.getPseudonym() }, e); +  } +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/WorkflowException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/WorkflowException.java new file mode 100644 index 00000000..795b4386 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/WorkflowException.java @@ -0,0 +1,94 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.exception; + +import lombok.Getter; + +@Getter +public class WorkflowException extends EidasSAuthenticationException { +  private static final long serialVersionUID = 1L; + +  private String processStepName; +  private String errorReason; +  private boolean requiresManualFix = false; +   +  /** +   * In case of a error during matching work-flow. +   *  +   * @param processStep Matching step identifier +   * @param errorReason Reason for this error +   */ +  public WorkflowException(String processStep, String errorReason) { +    super("module.eidasauth.matching.03", new Object[]{processStep, errorReason});     +    this.processStepName = processStep; +    this.errorReason = errorReason; +     +  } + +  /** +   * In case of a error during matching work-flow. +   *  +   * @param processStep Matching step identifier +   * @param errorReason Reason for this error +   * @param e Catched exception +   */ +  public WorkflowException(String processStep, String errorReason, Throwable e) { +    super("module.eidasauth.matching.03", new Object[]{processStep, errorReason}, e);     +    this.processStepName = processStep; +    this.errorReason = errorReason; +     +  } +   +  /** +   * In case of a error during matching work-flow. +   *  +   * @param processStep Matching step identifier +   * @param errorReason Reason for this error +   * @param needsManualFix Mark this work-flow as manually fixable  +   */ +  public WorkflowException(String processStep, String errorReason, boolean needsManualFix) { +    super("module.eidasauth.matching.03", new Object[]{processStep, errorReason});     +    this.processStepName = processStep; +    this.errorReason = errorReason; +    this.requiresManualFix = needsManualFix; +     +  } +   +  /** +   * In case of a error during matching work-flow. +   *  +   * @param processStep Matching step identifier +   * @param errorReason Reason for this error +   * @param needsManualFix Mark this work-flow as manually fixable  +   * @param e Catched exception +   */ +  public WorkflowException(String processStep, String errorReason, boolean needsManualFix, Throwable e) { +    super("module.eidasauth.matching.03", new Object[]{processStep, errorReason}, e);     +    this.processStepName = processStep; +    this.errorReason = errorReason; +    this.requiresManualFix = needsManualFix; +     +  } +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ZmrCommunicationException.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ZmrCommunicationException.java new file mode 100644 index 00000000..a6978458 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ZmrCommunicationException.java @@ -0,0 +1,38 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. +*/ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.exception; + +public class ZmrCommunicationException extends EidasSAuthenticationException { + +  private static final long serialVersionUID = 1L; + +  public ZmrCommunicationException(String internalMsgId, Object[] params) { +    super(internalMsgId, params); +  } + +  public ZmrCommunicationException(String internalMsgId, Object[] params, Throwable e) { +    super(internalMsgId, params, e); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java index 323a37e2..60138027 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/AbstractEidProcessor.java @@ -23,6 +23,11 @@  package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + + +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils.processCountryCode; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils.processDateOfBirthToString; +  import java.nio.charset.StandardCharsets;  import java.security.MessageDigest;  import java.security.NoSuchAlgorithmException; @@ -33,15 +38,13 @@ import java.util.regex.Pattern;  import org.apache.commons.lang3.StringUtils;  import org.joda.time.DateTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.lang.NonNull;  import com.google.common.collect.ImmutableSortedSet;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; @@ -51,15 +54,14 @@ import at.gv.egiz.eaaf.core.api.IRequest;  import at.gv.egiz.eaaf.core.api.data.EaafConstants;  import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP;  import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; -import at.gv.egiz.eaaf.core.impl.data.Triple;  import eu.eidas.auth.commons.attribute.AttributeDefinition;  import eu.eidas.auth.commons.attribute.ImmutableAttributeMap;  import eu.eidas.auth.commons.light.impl.LightRequest.Builder;  import eu.eidas.auth.commons.protocol.eidas.SpType; -import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; +import lombok.extern.slf4j.Slf4j; +@Slf4j  public abstract class AbstractEidProcessor implements INationalEidProcessor { -  private static final Logger log = LoggerFactory.getLogger(AbstractEidProcessor.class);    @Autowired    protected EidasAttributeRegistry attrRegistry; @@ -72,39 +74,40 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {      buildLevelOfAssurance(pendingReq.getServiceProviderConfiguration(), authnRequestBuilder);      buildProviderNameAndRequesterIdAttribute(pendingReq, authnRequestBuilder);      buildRequestedAttributes(authnRequestBuilder); -    }    @Override -  public final ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +  public final SimpleEidasData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException,        EidasAttributeException { -    final ErnbEidData result = new ErnbEidData(); - -    final Object eIdentifierObj = eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); -    final Triple<String, String, String> eIdentifier = -        EidasResponseUtils.parseEidasPersonalIdentifier((String) eIdentifierObj); -    result.setCitizenCountryCode(eIdentifier.getFirst()); - -    // MDS attributes -    result.setPseudonym(processPseudonym(eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))); -    result.setFamilyName(processFamilyName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTFAMILYNAME))); -    result.setGivenName(processGivenName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTGIVENNAME))); -    result.setDateOfBirth(processDateOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_DATEOFBIRTH))); - -    // additional attributes -    result.setPlaceOfBirth(processPlaceOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_PLACEOFBIRTH))); -    result.setBirthName(processBirthName(eidasAttrMap.get(Constants.eIDAS_ATTR_BIRTHNAME))); -    result.setAddress(processAddress(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTADDRESS))); - -    return result; - +    SimpleEidasData.SimpleEidasDataBuilder builder = SimpleEidasData.builder() +        .personalIdentifier(EidasResponseUtils.processPersonalIdentifier( +            eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))) +         +        // MDS attributes +        .citizenCountryCode(processCountryCode(eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))) +        .pseudonym(processPseudonym(eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER))) +        .familyName(processFamilyName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTFAMILYNAME))) +        .givenName(processGivenName(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTGIVENNAME))) +        .dateOfBirth(processDateOfBirthToString(eidasAttrMap.get(Constants.eIDAS_ATTR_DATEOFBIRTH))) + +        // additional attributes +        .placeOfBirth(processPlaceOfBirth(eidasAttrMap.get(Constants.eIDAS_ATTR_PLACEOFBIRTH))) +        .birthName(processBirthName(eidasAttrMap.get(Constants.eIDAS_ATTR_BIRTHNAME))) +        .address(processAddress(eidasAttrMap.get(Constants.eIDAS_ATTR_CURRENTADDRESS))); +     +    if (eidasAttrMap.containsKey(Constants.eIDAS_ATTR_TAXREFERENCE)) { +      builder.taxNumber(EidasResponseUtils.processTaxReference(eidasAttrMap.get(Constants.eIDAS_ATTR_TAXREFERENCE))); +       +    } +     +    return builder.build();    } -   +    /**     * Get a Map of country-specific requested attributes. -   *  +   *     * @return     */    @NonNull @@ -112,7 +115,7 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {    /**     * Post-Process the eIDAS CurrentAddress attribute. -   *  +   *     * @param currentAddressObj eIDAS current address information     * @return current address or null if no attribute is available     * @throws EidPostProcessingException if post-processing fails @@ -120,34 +123,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected PostalAddressType processAddress(Object currentAddressObj) throws EidPostProcessingException,        EidasAttributeException { - -    if (currentAddressObj != null) { -      if (currentAddressObj instanceof PostalAddress) { -        final PostalAddressType result = new PostalAddressType(); -        result.setPostalCode(((PostalAddress) currentAddressObj).getPostCode()); -        result.setMunicipality(((PostalAddress) currentAddressObj).getPostName()); - -        // TODO: add more mappings -         -        return result; - -      } else { -        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_CURRENTADDRESS + " is of WRONG type"); -        throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTADDRESS); - -      } - -    } else { -      log.debug("NO '" + Constants.eIDAS_ATTR_CURRENTADDRESS + "' attribute. Post-Processing skipped ... "); -    } - -    return null; - +    return EidasResponseUtils.processAddress(currentAddressObj);    }    /**     * Post-Process the eIDAS BirthName attribute. -   *  +   *     * @param birthNameObj eIDAS birthname information     * @return birthName or null if no attribute is available     * @throws EidPostProcessingException if post-processing fails @@ -155,27 +136,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected String processBirthName(Object birthNameObj) throws EidPostProcessingException,        EidasAttributeException { -    if (birthNameObj != null) { -      if (birthNameObj instanceof String) { -        return (String) birthNameObj; - -      } else { -        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_BIRTHNAME + " is of WRONG type"); -        throw new EidasAttributeException(Constants.eIDAS_ATTR_BIRTHNAME); - -      } - -    } else { -      log.debug("NO '" + Constants.eIDAS_ATTR_BIRTHNAME + "' attribute. Post-Processing skipped ... "); -    } - -    return null; - +    return EidasResponseUtils.processBirthName(birthNameObj);    }    /**     * Post-Process the eIDAS PlaceOfBirth attribute. -   *  +   *     * @param placeOfBirthObj eIDAS Place-of-Birth information     * @return place of Birth or null if no attribute is available     * @throws EidPostProcessingException if post-processing fails @@ -183,27 +149,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected String processPlaceOfBirth(Object placeOfBirthObj) throws EidPostProcessingException,        EidasAttributeException { -    if (placeOfBirthObj != null) { -      if (placeOfBirthObj instanceof String) { -        return (String) placeOfBirthObj; - -      } else { -        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_PLACEOFBIRTH + " is of WRONG type"); -        throw new EidasAttributeException(Constants.eIDAS_ATTR_PLACEOFBIRTH); - -      } - -    } else { -      log.debug("NO '" + Constants.eIDAS_ATTR_PLACEOFBIRTH + "' attribute. Post-Processing skipped ... "); -    } - -    return null; - +    return EidasResponseUtils.processPlaceOfBirth(placeOfBirthObj);    }    /**     * Post-Process the eIDAS DateOfBirth attribute. -   *  +   *     * @param dateOfBirthObj eIDAS date-of-birth attribute information     * @return formated user's date-of-birth     * @throws EidasAttributeException    if NO attribute is available @@ -211,17 +162,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected DateTime processDateOfBirth(Object dateOfBirthObj) throws EidPostProcessingException,        EidasAttributeException { -    if (dateOfBirthObj == null || !(dateOfBirthObj instanceof DateTime)) { -      throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); -    } - -    return (DateTime) dateOfBirthObj; - +    return EidasResponseUtils.processDateOfBirth(dateOfBirthObj);    }    /**     * Post-Process the eIDAS GivenName attribute. -   *  +   *     * @param givenNameObj eIDAS givenName attribute information     * @return formated user's givenname     * @throws EidasAttributeException    if NO attribute is available @@ -229,17 +175,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected String processGivenName(Object givenNameObj) throws EidPostProcessingException,        EidasAttributeException { -    if (givenNameObj == null || !(givenNameObj instanceof String)) { -      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTGIVENNAME); -    } - -    return (String) givenNameObj; - +    return EidasResponseUtils.processGivenName(givenNameObj);    }    /**     * Post-Process the eIDAS FamilyName attribute. -   *  +   *     * @param familyNameObj eIDAS familyName attribute information     * @return formated user's familyname     * @throws EidasAttributeException    if NO attribute is available @@ -247,17 +188,12 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected String processFamilyName(Object familyNameObj) throws EidPostProcessingException,        EidasAttributeException { -    if (familyNameObj == null || !(familyNameObj instanceof String)) { -      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTFAMILYNAME); -    } - -    return (String) familyNameObj; - +    return EidasResponseUtils.processFamilyName(familyNameObj);    }    /**     * Post-Process the eIDAS pseudonym to ERnB unique identifier. -   *  +   *     * @param personalIdObj eIDAS PersonalIdentifierAttribute     * @return Unique personal identifier without country-code information     * @throws EidasAttributeException    if NO attribute is available @@ -265,15 +201,7 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {     */    protected String processPseudonym(Object personalIdObj) throws EidPostProcessingException,        EidasAttributeException { -    if (personalIdObj == null || !(personalIdObj instanceof String)) { -      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); -    } - -    final Triple<String, String, String> eIdentifier = -        EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); - -    return eIdentifier.getThird(); - +    return EidasResponseUtils.processPseudonym(personalIdObj);    }    /** @@ -391,6 +319,7 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {    }    protected void buildLevelOfAssurance(ISpConfiguration spConfig, Builder authnRequestBuilder) { +      // TODO: set matching mode if eIDAS ref. impl. support this method      // TODO: update if eIDAS ref. impl. supports exact matching for non-notified LoA @@ -412,7 +341,7 @@ public abstract class AbstractEidProcessor implements INationalEidProcessor {      log.debug("Request eIdAS node with LoA: " + loa);      authnRequestBuilder.levelOfAssurance(loa); -     +    }  } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/CountrySpecificDetailSearchProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/CountrySpecificDetailSearchProcessor.java new file mode 100644 index 00000000..c2a62f5c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/CountrySpecificDetailSearchProcessor.java @@ -0,0 +1,56 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + +import javax.annotation.Nonnull; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +public interface CountrySpecificDetailSearchProcessor { + +  /** +   * Get a friendlyName of this post-processor implementation. +   */ +  String getName(); + +  /** +   * Check if this postProcessor is sensitive for a specific country. +   * +   * @param countryCode of the eID data that should be processed +   * @param eidData eID information from eIDAS Proxy-Service +   * @return true if this implementation can handle the country, otherwise false +   */ +  boolean canHandle(String countryCode, SimpleEidasData eidData); + +  /** +   * Builds a country-specific search person request for ZMR.  +   *  +   * @param eidData eID information from eIDAS Proxy-Service +   * @return {@link PersonSuchenRequest} but never <code>null</code> +   */ +  @Nonnull +  PersonSuchenRequest generateSearchRequest(SimpleEidasData eidData); + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeSpecificDetailSearchProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeSpecificDetailSearchProcessor.java new file mode 100644 index 00000000..e05fe86b --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/DeSpecificDetailSearchProcessor.java @@ -0,0 +1,82 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + +import org.apache.commons.lang3.StringUtils; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.EidasSuchdatenType; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; +import at.gv.e_government.reference.namespace.persondata.de._20040201.NatuerlichePersonTyp; +import at.gv.e_government.reference.namespace.persondata.de._20040201.PersonenNameTyp; + +public class DeSpecificDetailSearchProcessor implements CountrySpecificDetailSearchProcessor { + +  @Override +  public String getName() { +    return this.getClass().getSimpleName(); +  } + +  @Override +  public boolean canHandle(String countryCode, SimpleEidasData eidData) { +    return countryCode.equalsIgnoreCase(Constants.COUNTRY_CODE_DE)  +        && StringUtils.isNotEmpty(eidData.getBirthName())  +        && StringUtils.isNotEmpty(eidData.getPlaceOfBirth()); +     +  } + +  @Override +  public PersonSuchenRequest generateSearchRequest(SimpleEidasData eidData) {     +    PersonSuchenRequest req = new PersonSuchenRequest();     +         +    //set basic MDS information +    final NatuerlichePersonTyp searchNatPerson = new NatuerlichePersonTyp(); +    req.setNatuerlichePerson(searchNatPerson); +    final PersonenNameTyp searchNatPersonName = new PersonenNameTyp(); +    searchNatPerson.setPersonenName(searchNatPersonName); +    searchNatPersonName.setFamilienname(eidData.getFamilyName()); +    searchNatPersonName.setVorname(eidData.getGivenName()); +    searchNatPerson.setGeburtsdatum(eidData.getDateOfBirth()); +     +    //add addtional eIDAS attributes from DE +    req.getEidasSuchdaten().add(buildEidasSuchData( +        Constants.eIDAS_ATTRURN_PLACEOFBIRTH, eidData.getPlaceOfBirth())); +    req.getEidasSuchdaten().add(buildEidasSuchData( +        Constants.eIDAS_ATTRURN_BIRTHNAME, eidData.getBirthName())); +   +    return req; +     +  } + +  private EidasSuchdatenType buildEidasSuchData(String attrName, String attrValue) { +    EidasSuchdatenType eidasInfos = new EidasSuchdatenType(); +    eidasInfos.setStaatscode2(Constants.COUNTRY_CODE_DE); +    eidasInfos.setEidasArt(attrName); +    eidasInfos.setEidasWert(attrValue); +    return eidasInfos; +     +  } +  +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java index 577efbcd..79a261fe 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/INationalEidProcessor.java @@ -25,7 +25,7 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler;  import java.util.Map; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException;  import at.gv.egiz.eaaf.core.api.IRequest; @@ -36,7 +36,7 @@ public interface INationalEidProcessor {    /**     * Get a friendlyName of this post-processor implementation. -   *  +   *     * @return     */    String getName(); @@ -46,34 +46,34 @@ public interface INationalEidProcessor {     * If more than one Post-Processor implementations can handle the eID data, the     * post-processor with the highest priority are selected. The Default-Processor     * has priority '0' -   *  +   *     * @return Priority of this handler     */    int getPriority();    /**     * Check if this postProcessor is sensitive for a specific country. -   *  +   *     * @param countryCode of the eID data that should be processed     * @return true if this implementation can handle the country, otherwise false -   *  +   *     */    boolean canHandle(String countryCode);    /**     * Post-Process eIDAS eID data into national format. -   *  +   *     * @param eidasAttrMap Map of eIDAS attributes in format friendlyName and     *                     attribute     * @throws EidPostProcessingException In case of a post-processing error     * @throws EidasAttributeException In case of an invalid eIDAS attribute     */ -  ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +  SimpleEidasData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException,        EidasAttributeException;    /**     * Pre-Process eIDAS Request to national requirements. -   *  +   *     * @param pendingReq          current pending request     * @param authnRequestBuilder eIDAS {@link ILightRequest} builder     */ diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/ItSpecificDetailSearchProcessor.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/ItSpecificDetailSearchProcessor.java new file mode 100644 index 00000000..b49c355d --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/handler/ItSpecificDetailSearchProcessor.java @@ -0,0 +1,53 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.handler; + +import org.apache.commons.lang3.StringUtils; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.gv.bmi.namespace.zmr_su.zmr._20040201.PersonSuchenRequest; + +public class ItSpecificDetailSearchProcessor implements CountrySpecificDetailSearchProcessor { + +  @Override +  public String getName() { +    return this.getClass().getSimpleName(); +  } + +  @Override +  public boolean canHandle(String countryCode, SimpleEidasData eidData) { +    return countryCode.equalsIgnoreCase(Constants.COUNTRY_CODE_IT)  +        &&  StringUtils.isNotEmpty(eidData.getTaxNumber()); + +  } + +  @Override +  public PersonSuchenRequest generateSearchRequest(SimpleEidasData eidData) { + +    //TODO: add IT specific search request if TaxNumber attribute is defined by IT         +    return new PersonSuchenRequest(); +     +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthConstants.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthConstants.java new file mode 100644 index 00000000..67dfd7d8 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthConstants.java @@ -0,0 +1,102 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.impl.data.Triple; + + +public class IdAustriaClientAuthConstants { + +  private IdAustriaClientAuthConstants() { + +  } +   +  public static final String ERRORCODE_02 = "module.eidasauth.idaustria.02"; +  public static final String ERRORCODE_05 = "module.eidasauth.idaustria.05";   +   +  public static final String SAML2_STATUSCODE_USERSTOP = "1005"; + +  public static final String MODULE_NAME_FOR_LOGGING = "ID Austria Client"; + +  public static final int METADATA_VALIDUNTIL_IN_HOURS = 24; + +  //  public static final String HTTP_PARAM_CENTRAL_EIDAS_AUTH_SELECTION = +  //      AuthHandlerConstants.HTTP_PARAM_EIDAS_PROCESS; + +  public static final String ENDPOINT_POST = "/sp/idaustria/post"; +  public static final String ENDPOINT_REDIRECT = "/sp/idaustria/redirect"; +  public static final String ENDPOINT_METADATA = "/sp/idaustria/metadata"; + +  public static final String CONFIG_PROPS_PREFIX = "modules.idaustriaclient."; +  public static final String CONFIG_PROPS_KEYSTORE_TYPE = CONFIG_PROPS_PREFIX + "keystore.type"; +  public static final String CONFIG_PROPS_KEYSTORE_NAME = CONFIG_PROPS_PREFIX + "keystore.name"; +  public static final String CONFIG_PROPS_KEYSTORE_PATH = CONFIG_PROPS_PREFIX + "keystore.path"; +  public static final String CONFIG_PROPS_KEYSTORE_PASSWORD = CONFIG_PROPS_PREFIX + "keystore.password"; +  public static final String CONFIG_PROPS_SIGN_METADATA_KEY_PASSWORD = CONFIG_PROPS_PREFIX +      + "metadata.sign.password"; +  public static final String CONFIG_PROPS_SIGN_METADATA_ALIAS = CONFIG_PROPS_PREFIX +      + "metadata.sign.alias"; +  public static final String CONFIG_PROPS_SIGN_SIGNING_KEY_PASSWORD = CONFIG_PROPS_PREFIX +      + "request.sign.password"; +  public static final String CONFIG_PROPS_SIGN_SIGNING_ALIAS = CONFIG_PROPS_PREFIX +      + "request.sign.alias"; +  public static final String CONFIG_PROPS_ENCRYPTION_KEY_PASSWORD = CONFIG_PROPS_PREFIX +      + "response.encryption.password"; +  public static final String CONFIG_PROPS_ENCRYPTION_ALIAS = CONFIG_PROPS_PREFIX +      + "response.encryption.alias"; + +  public static final String CONFIG_PROPS_TRUSTSTORE_TYPE = CONFIG_PROPS_PREFIX + "truststore.type"; +  public static final String CONFIG_PROPS_TRUSTSTORE_NAME = CONFIG_PROPS_PREFIX + "truststore.name"; +  public static final String CONFIG_PROPS_TRUSTSTORE_PATH = CONFIG_PROPS_PREFIX + "truststore.path"; +  public static final String CONFIG_PROPS_TRUSTSTORE_PASSWORD = CONFIG_PROPS_PREFIX + "truststore.password"; + +  public static final String CONFIG_PROPS_REQUIRED_PVP_ATTRIBUTES_LIST = CONFIG_PROPS_PREFIX +      + "required.additional.attributes"; +  public static final String CONFIG_PROPS_REQUIRED_LOA = CONFIG_PROPS_PREFIX +      + "required.loa"; +  public static final String CONFIG_PROPS_ID_AUSTRIA_ENTITYID = CONFIG_PROPS_PREFIX + "idaustria.idp.entityId"; +  public static final String CONFIG_PROPS_ID_AUSTRIA_METADATAURL = CONFIG_PROPS_PREFIX + "idaustria.idp.metadataUrl"; +   +  public static final String CONFIG_DEFAULT_LOA_EIDAS_LEVEL = EaafConstants.EIDAS_LOA_HIGH; + +  public static final List<Triple<String, String, Boolean>> DEFAULT_REQUIRED_PVP_ATTRIBUTES = +      Collections.unmodifiableList(new ArrayList<Triple<String, String, Boolean>>() { +        private static final long serialVersionUID = 1L; + +        { +          // entity metadata information +          add(Triple.newInstance(PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME, +              PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_FRIENDLY_NAME, true)); +          add(Triple.newInstance(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, +              PvpAttributeDefinitions.EID_ISSUING_NATION_FRIENDLY_NAME, true)); + +          add(Triple.newInstance(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, +              PvpAttributeDefinitions.PRINCIPAL_NAME_FRIENDLY_NAME, true)); +          add(Triple.newInstance(PvpAttributeDefinitions.GIVEN_NAME_NAME, +              PvpAttributeDefinitions.GIVEN_NAME_FRIENDLY_NAME, true)); +          add(Triple.newInstance(PvpAttributeDefinitions.BIRTHDATE_NAME, +              PvpAttributeDefinitions.BIRTHDATE_FRIENDLY_NAME, true)); +          add(Triple.newInstance(PvpAttributeDefinitions.BPK_NAME, +              PvpAttributeDefinitions.BPK_FRIENDLY_NAME, true)); + +        } +      }); + +  public static final List<String> DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES = + +      Collections.unmodifiableList(new ArrayList<String>() { +        private static final long serialVersionUID = 1L; + +        { +          for (final Triple<String, String, Boolean> el : DEFAULT_REQUIRED_PVP_ATTRIBUTES) { +            add(el.getFirst()); +          } +        } +      }); + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthEventConstants.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthEventConstants.java new file mode 100644 index 00000000..03e570fc --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthEventConstants.java @@ -0,0 +1,7 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient; + +public class IdAustriaClientAuthEventConstants { + +  public static final int AUTHPROCESS_ID_AUSTRIA_RESPONSE_RECEIVED = 6202; +  public static final int AUTHPROCESS_ID_AUSTRIA_RESPONSE_RECEIVED_ERROR = 6203; +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthMetadataConfiguration.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthMetadataConfiguration.java new file mode 100644 index 00000000..4527ced4 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthMetadataConfiguration.java @@ -0,0 +1,463 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient; + + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.opensaml.saml.saml2.core.Attribute; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.metadata.ContactPerson; +import org.opensaml.saml.saml2.metadata.Organization; +import org.opensaml.saml.saml2.metadata.RequestedAttribute; +import org.opensaml.security.credential.Credential; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthCredentialProvider; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.data.Triple; +import at.gv.egiz.eaaf.modules.pvp2.api.IPvp2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataBuilderConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpAttributeBuilder; +import lombok.extern.slf4j.Slf4j; + +/** + * Configuration object to generate PVP S-Profile metadata for SAML2 client. + * + * @author tlenz + * + */ +@Slf4j +public class IdAustriaClientAuthMetadataConfiguration implements IPvpMetadataBuilderConfiguration { + +  private Collection<RequestedAttribute> additionalAttributes = null; + +  private final String authUrl; +  private final IdAustriaClientAuthCredentialProvider credentialProvider; +  private final IPvp2BasicConfiguration pvpConfiguration; + +  /** +   * Configuration object to create PVP2 S-Profile metadata information. +   * +   * @param authUrl            Public URL prefix of the application +   * @param credentialProvider Credentials used by PVP2 S-Profile end-point +   * @param pvpConfiguration   Basic PVP2 S-Profile end-point configuration +   */ +  public IdAustriaClientAuthMetadataConfiguration(String authUrl, +                                                  IdAustriaClientAuthCredentialProvider credentialProvider, +                                                  IPvp2BasicConfiguration pvpConfiguration) { +    this.authUrl = authUrl; +    this.credentialProvider = credentialProvider; +    this.pvpConfiguration = pvpConfiguration; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getMetadataValidUntil() +   */ +  @Override +  public int getMetadataValidUntil() { +    return IdAustriaClientAuthConstants.METADATA_VALIDUNTIL_IN_HOURS; + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * buildEntitiesDescriptorAsRootElement() +   */ +  @Override +  public boolean buildEntitiesDescriptorAsRootElement() { +    return false; + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * buildIDPSSODescriptor() +   */ +  @Override +  public boolean buildIdpSsoDescriptor() { +    return false; + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * buildSPSSODescriptor() +   */ +  @Override +  public boolean buildSpSsoDescriptor() { +    return true; + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getEntityIDPostfix() +   */ +  @Override +  public String getEntityID() { +    return authUrl + IdAustriaClientAuthConstants.ENDPOINT_METADATA; + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getEntityFriendlyName() +   */ +  @Override +  public String getEntityFriendlyName() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getContactPersonInformation() +   */ +  @Override +  public List<ContactPerson> getContactPersonInformation() { +    try { +      return pvpConfiguration.getIdpContacts(); + +    } catch (final EaafException e) { +      log.warn("Can not load Metadata entry: Contect Person", e); +      return null; + +    } + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getOrgansiationInformation() +   */ +  @Override +  public Organization getOrgansiationInformation() { +    try { +      return pvpConfiguration.getIdpOrganisation(); + +    } catch (final EaafException e) { +      log.warn("Can not load Metadata entry: Organisation", e); +      return null; + +    } +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getMetadataSigningCredentials() +   */ +  @Override +  public EaafX509Credential getMetadataSigningCredentials() throws CredentialsNotAvailableException { +    return credentialProvider.getMetaDataSigningCredential(); + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getRequestorResponseSigningCredentials() +   */ +  @Override +  public Credential getRequestorResponseSigningCredentials() throws CredentialsNotAvailableException { +    return credentialProvider.getMessageSigningCredential(); + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getEncryptionCredentials() +   */ +  @Override +  public Credential getEncryptionCredentials() throws CredentialsNotAvailableException { +    return credentialProvider.getMessageEncryptionCredential(); + +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPWebSSOPostBindingURL() +   */ +  @Override +  public String getIdpWebSsoPostBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPWebSSORedirectBindingURL() +   */ +  @Override +  public String getIdpWebSsoRedirectBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPSLOPostBindingURL() +   */ +  @Override +  public String getIdpSloPostBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPSLORedirectBindingURL() +   */ +  @Override +  public String getIdpSloRedirectBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPAssertionConsumerServicePostBindingURL() +   */ +  @Override +  public String getSpAssertionConsumerServicePostBindingUrl() { +    return authUrl + IdAustriaClientAuthConstants.ENDPOINT_POST; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPAssertionConsumerServiceRedirectBindingURL() +   */ +  @Override +  public String getSpAssertionConsumerServiceRedirectBindingUrl() { +    return authUrl + IdAustriaClientAuthConstants.ENDPOINT_REDIRECT; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPSLOPostBindingURL() +   */ +  @Override +  public String getSpSloPostBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPSLORedirectBindingURL() +   */ +  @Override +  public String getSpSloRedirectBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPSLOSOAPBindingURL() +   */ +  @Override +  public String getSpSloSoapBindingUrl() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPPossibleAttributes() +   */ +  @Override +  public List<Attribute> getIdpPossibleAttributes() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getIDPPossibleNameITTypes() +   */ +  @Override +  public List<String> getIdpPossibleNameIdTypes() { +    return null; +  } + + + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPRequiredAttributes() +   */ +  @Override +  public Collection<RequestedAttribute> getSpRequiredAttributes() { +    final Map<String, RequestedAttribute> requestedAttributes = new HashMap<>(); + + +    log.trace("Build required attributes for ID Austria operaton ... "); +    injectDefinedAttributes(requestedAttributes, +        IdAustriaClientAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTES); + + + +    if (additionalAttributes != null) { +      log.trace("Add additional PVP attributes into metadata ... "); +      for (final RequestedAttribute el : additionalAttributes) { +        if (requestedAttributes.containsKey(el.getName())) { +          log.debug("Attribute " + el.getName() +              + " is already added by default configuration. Overwrite it by user configuration"); +        } +        requestedAttributes.put(el.getName(), el); +      } +    } + +    return requestedAttributes.values(); + +  } + + + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.builder.AbstractPVPMetadataBuilder# +   * getSPAllowedNameITTypes() +   */ +  @Override +  public List<String> getSpAllowedNameIdTypes() { +    return Arrays.asList(NameIDType.PERSISTENT); + +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPMetadataBuilderConfiguration#getSPNameForLogging() +   */ +  @Override +  public String getSpNameForLogging() { +    return IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPMetadataBuilderConfiguration#wantAssertionSigned() +   */ +  @Override +  public boolean wantAssertionSigned() { +    return false; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPMetadataBuilderConfiguration#wantAuthnRequestSigned() +   */ +  @Override +  public boolean wantAuthnRequestSigned() { +    return true; +  } + +  /** +   * Add additonal PVP attributes that are required by this deployment. +   * +   * @param additionalAttr List of PVP attribute name and isRequired flag +   */ +  public void setAdditionalRequiredAttributes(List<Pair<String, Boolean>> additionalAttr) { +    if (additionalAttr != null && !additionalAttr.isEmpty()) { +      additionalAttributes = new ArrayList<>(); +      for (final Pair<String, Boolean> el : additionalAttr) { +        final Attribute attributBuilder = PvpAttributeBuilder.buildEmptyAttribute(el.getFirst()); +        if (attributBuilder != null) { +          additionalAttributes.add( +              PvpAttributeBuilder.buildReqAttribute( +                  attributBuilder.getName(), +                  attributBuilder.getFriendlyName(), +                  el.getSecond())); + +        } else { +          log.info("NO PVP attribute with name: " + el.getFirst()); +        } + +      } +    } +  } + +  private void injectDefinedAttributes(Map<String, RequestedAttribute> requestedAttributes, +                                       List<Triple<String, String, Boolean>> attributes) { +    for (final Triple<String, String, Boolean> el : attributes) { +      requestedAttributes.put(el.getFirst(), PvpAttributeBuilder.buildReqAttribute(el.getFirst(), el +          .getSecond(), el.getThird())); + +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthRequestBuilderConfiguration.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthRequestBuilderConfiguration.java new file mode 100644 index 00000000..65b6a198 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/IdAustriaClientAuthRequestBuilderConfiguration.java @@ -0,0 +1,300 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient; + +import java.util.List; + +import at.gv.egiz.eaaf.modules.pvp2.api.credential.EaafX509Credential; +import at.gv.egiz.eaaf.modules.pvp2.api.reqattr.EaafRequestedAttribute; +import at.gv.egiz.eaaf.modules.pvp2.sp.api.IPvpAuthnRequestBuilderConfiguruation; + +import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.w3c.dom.Element; + +public class IdAustriaClientAuthRequestBuilderConfiguration implements IPvpAuthnRequestBuilderConfiguruation { + +  private boolean isPassive; +  private String spEntityId; +  private String qaaLevel; +  private EntityDescriptor idpEntity; +  private EaafX509Credential signCred; +  private String scopeRequesterId; +  private String providerName; +  private List<EaafRequestedAttribute> requestedAttributes; +  private String reqId; + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#isPassivRequest() +   */ +  @Override +  public Boolean isPassivRequest() { +    return this.isPassive; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getAssertionConsumerServiceId() +   */ +  @Override +  public Integer getAssertionConsumerServiceId() { +    return 0; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getEntityID() +   */ +  @Override +  public String getSpEntityID() { +    return this.spEntityId; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getNameIDPolicy() +   */ +  @Override +  public String getNameIdPolicyFormat() { +    return NameIDType.PERSISTENT; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getNameIDPolicy() +   */ +  @Override +  public boolean getNameIdPolicyAllowCreation() { +    return true; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getAuthnContextClassRef() +   */ +  @Override +  public String getAuthnContextClassRef() { +    return this.qaaLevel; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getAuthnContextComparison() +   */ +  @Override +  public AuthnContextComparisonTypeEnumeration getAuthnContextComparison() { +    return AuthnContextComparisonTypeEnumeration.MINIMUM; +  } + +  /** +   * Set isPassive flag in SAML2 request. +   * +   * @param isPassive the isPassive to set. +   */ +  public void setPassive(boolean isPassive) { +    this.isPassive = isPassive; +  } + +  /** +   * Set the requester EntityId. +   * +   * @param spEntityId EntityId of SP +   */ +  public void setSpEntityID(String spEntityId) { +    this.spEntityId = spEntityId; +  } + +  /** +   * Set required LoA. +   * +   * @param loa the LoA to set. +   */ +  public void setRequestedLoA(String loa) { +    qaaLevel = loa; +  } + +  /** +   * Set EntityId of IDP. +   * +   * @param idpEntity the idpEntity to set. +   */ +  public void setIdpEntity(EntityDescriptor idpEntity) { +    this.idpEntity = idpEntity; +  } + +  /** +   * Set message signing credentials. +   * +   * @param signCred the signCred to set. +   */ +  public void setSignCred(EaafX509Credential signCred) { +    this.signCred = signCred; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getAuthnRequestSigningCredential() +   */ +  @Override +  public EaafX509Credential getAuthnRequestSigningCredential() { +    return this.signCred; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getIDPEntityDescriptor() +   */ +  @Override +  public EntityDescriptor getIdpEntityDescriptor() { +    return this.idpEntity; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameID() +   */ +  @Override +  public String getSubjectNameID() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSPNameForLogging() +   */ +  @Override +  public String getSpNameForLogging() { +    return IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameIDFormat() +   */ +  @Override +  public String getSubjectNameIdFormat() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getRequestID() +   */ +  @Override +  public String getRequestID() { +    return this.reqId; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSubjectNameIDQualifier() +   */ +  @Override +  public String getSubjectNameIdQualifier() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSubjectConformationMethode() +   */ +  @Override +  public String getSubjectConformationMethode() { +    return null; +  } + +  /* +   * (non-Javadoc) +   * +   * @see at.gv.egovernment.moa.id.protocols.pvp2x.config. +   * IPVPAuthnRequestBuilderConfiguruation#getSubjectConformationDate() +   */ +  @Override +  public Element getSubjectConformationDate() { +    return null; +  } + +  @Override +  public List<EaafRequestedAttribute> getRequestedAttributes() { +    return this.requestedAttributes; + +  } + +  @Override +  public String getProviderName() { +    return this.providerName; +  } + +  @Override +  public String getScopeRequesterId() { +    return this.scopeRequesterId; +  } + +  /** +   * Set the entityId of the SP that requests the proxy for eIDAS authentication. +   * +   * @param scopeRequesterId RequestId in SAML2 Proxy extension +   */ +  public void setScopeRequesterId(String scopeRequesterId) { +    this.scopeRequesterId = scopeRequesterId; +  } + +  /** +   * Set a friendlyName for the SP that requests the proxy for eIDAS +   * authentication. +   * +   * @param providerName SAML2 provider-name attribute-value +   */ +  public void setProviderName(String providerName) { +    this.providerName = providerName; +  } + +  /** +   * Set a Set of PVP attributes that a requested by using requested attributes. +   * +   * @param requestedAttributes Requested SAML2 attributes +   */ +  public void setRequestedAttributes(List<EaafRequestedAttribute> requestedAttributes) { +    this.requestedAttributes = requestedAttributes; +  } + +  /** +   * Set a RequestId for this Authn. Request. +   * +   * @param reqId SAML2 message requestId +   */ +  public void setRequestId(String reqId) { +    this.reqId = reqId; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthMetadataController.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthMetadataController.java new file mode 100644 index 00000000..1e4b27f7 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthMetadataController.java @@ -0,0 +1,122 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.controller; + + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import com.google.common.net.MediaType; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthMetadataConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthCredentialProvider; +import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.http.HttpUtils; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; +import at.gv.egiz.eaaf.modules.pvp2.api.IPvp2BasicConfiguration; +import at.gv.egiz.eaaf.modules.pvp2.impl.builder.PvpMetadataBuilder; +import lombok.extern.slf4j.Slf4j; + +/** + * Controller that generates SAML2 metadata for eIDAS authentication client. + * + * @author tlenz + * + */ +@Slf4j +@Controller +public class IdAustriaClientAuthMetadataController extends AbstractController { + +  private static final String ERROR_CODE_INTERNAL_00 = "eaaf.core.00"; + +  @Autowired +  PvpMetadataBuilder metadatabuilder; +  @Autowired +  IdAustriaClientAuthCredentialProvider credentialProvider; +  @Autowired +  IPvp2BasicConfiguration pvpConfiguration; + +  /** +   * Default construction with logging. +   * +   */ +  public IdAustriaClientAuthMetadataController() { +    super(); +    log.debug("Registering servlet " + getClass().getName() +        + " with mappings '" + IdAustriaClientAuthConstants.ENDPOINT_METADATA +        + "'."); + +  } + +  /** +   * End-point that produce PVP2 metadata for eIDAS authentication client. +   * +   * @param req  http Request +   * @param resp http Response +   * @throws IOException   In case of an I/O error +   * @throws EaafException In case of a metadata generation error +   */ +  @RequestMapping(value = IdAustriaClientAuthConstants.ENDPOINT_METADATA, +      method = { RequestMethod.GET }) +  public void getSpMetadata(HttpServletRequest req, HttpServletResponse resp) throws IOException, +      EaafException { +    // check PublicURL prefix +    try { +      final String authUrl = getAuthUrlFromHttpContext(req); + +      // initialize metadata builder configuration +      final IdAustriaClientAuthMetadataConfiguration metadataConfig = +          new IdAustriaClientAuthMetadataConfiguration(authUrl, credentialProvider, pvpConfiguration); +      // metadataConfig.setAdditionalRequiredAttributes(getAdditionalRequiredAttributes()); + +      // build metadata +      final String xmlMetadata = metadatabuilder.buildPvpMetadata(metadataConfig); + +      // write response +      final byte[] content = xmlMetadata.getBytes("UTF-8"); +      resp.setStatus(HttpServletResponse.SC_OK); +      resp.setContentLength(content.length); +      resp.setContentType(MediaType.XML_UTF_8.toString()); +      resp.getOutputStream().write(content); + +    } catch (final Exception e) { +      log.warn("Build federated-authentication PVP metadata FAILED.", e); +      protAuthService.handleErrorNoRedirect(e, req, resp, false); + +    } + +  } + +  private String getAuthUrlFromHttpContext(HttpServletRequest req) throws EaafException { +    // check if End-Point is valid +    final String authUrlString = HttpUtils.extractAuthUrlFromRequest(req); +    URL authReqUrl; +    try { +      authReqUrl = new URL(authUrlString); + +    } catch (final MalformedURLException e) { +      log.warn("Requested URL: {} is not a valid URL.", authUrlString); +      throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }, e); + +    } + +    final String idpAuthUrl = authConfig.validateIdpUrl(authReqUrl); +    if (idpAuthUrl == null) { +      log.warn("Requested URL: {} is NOT found in configuration.", authReqUrl); +      throw new EaafAuthenticationException(ERROR_CODE_INTERNAL_00, new Object[] { authUrlString }); + +    } + +    return idpAuthUrl; +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthSignalController.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthSignalController.java new file mode 100644 index 00000000..eca21683 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/controller/IdAustriaClientAuthSignalController.java @@ -0,0 +1,95 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.controller; + + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractProcessEngineSignalController; +import lombok.extern.slf4j.Slf4j; + +/** + * IdAustria client controller that receives the response from ID Austria system. + * + * @author tlenz + * + */ +@Slf4j +@Controller +public class IdAustriaClientAuthSignalController extends AbstractProcessEngineSignalController { + +  public static final String HTTP_PARAM_RELAYSTATE = "RelayState"; + +  /** +   * Default constructor with logging. +   * +   */ +  public IdAustriaClientAuthSignalController() { +    super(); +    log.debug("Registering servlet " + getClass().getName() +        + " with mappings '" + IdAustriaClientAuthConstants.ENDPOINT_POST +        + "' and '" + IdAustriaClientAuthConstants.ENDPOINT_REDIRECT + "'."); + +  } + +  /** +   * HTTP end-point for incoming SAML2 Response from ID Austria system. +   * +   * @param req HTTP request +   * @param resp HTTP response +   * @throws IOException In case of a HTTP communication error +   * @throws EaafException In case of a state-validation problem +   */ +  @RequestMapping(value = { IdAustriaClientAuthConstants.ENDPOINT_POST, +      IdAustriaClientAuthConstants.ENDPOINT_REDIRECT }, +      method = { RequestMethod.POST, RequestMethod.GET }) +  public void performAuthentication(HttpServletRequest req, HttpServletResponse resp) +      throws IOException, EaafException { +    signalProcessManagement(req, resp); + +  } + +  /** +   * Read the PendingRequestId from SAML2 RelayState parameter. +   */ +  @Override +  public String getPendingRequestId(HttpServletRequest request) { +    String relayState = StringEscapeUtils.escapeHtml4(request.getParameter(HTTP_PARAM_RELAYSTATE)); +    if (StringUtils.isNotEmpty(relayState)) { +      try { +        String pendingReqId = transactionStorage.get(relayState, String.class); +        if (StringUtils.isNotEmpty(pendingReqId)) { + +          return pendingReqId; + +        } else { +          log.info("SAML2 RelayState from request is unknown. Can NOT restore session ... "); + +        } + +      } catch (EaafException e) { +        log.error("Can NOT map SAML2 RelayState to pendingRequestId", e); + +      } finally { +        transactionStorage.remove(relayState); + +      } + +    } else { +      log.info("No SAML2 relaystate. Can NOT restore session ... "); + +    } + +    return null; + +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthCredentialProvider.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthCredentialProvider.java new file mode 100644 index 00000000..5b6085c1 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthCredentialProvider.java @@ -0,0 +1,132 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider; + +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.AbstractCredentialProvider; + +/** + * Credential provider for eIDAS PVP S-Profile client. + * + * @author tlenz + * + */ +public class IdAustriaClientAuthCredentialProvider extends AbstractCredentialProvider { + +  @Autowired +  IConfiguration authConfig; + +  private static final String FRIENDLYNAME = "ID Austria authentication"; + +  @Override +  public KeyStoreConfiguration getBasicKeyStoreConfig() throws EaafConfigurationException { +    final KeyStoreConfiguration keyStoreConfig = new KeyStoreConfiguration(); +    keyStoreConfig.setFriendlyName(FRIENDLYNAME); +    keyStoreConfig.setKeyStoreType( +        authConfig.getBasicConfiguration(IdAustriaClientAuthConstants.CONFIG_PROPS_KEYSTORE_TYPE, +            KeyStoreType.PKCS12.getKeyStoreType())); +    keyStoreConfig.setKeyStoreName( +        authConfig.getBasicConfiguration(IdAustriaClientAuthConstants.CONFIG_PROPS_KEYSTORE_NAME)); +    keyStoreConfig.setSoftKeyStoreFilePath(getKeyStoreFilePath()); +    keyStoreConfig.setSoftKeyStorePassword( +        authConfig.getBasicConfiguration(IdAustriaClientAuthConstants.CONFIG_PROPS_KEYSTORE_PASSWORD)); + +    return keyStoreConfig; + +  } + +  private String getKeyStoreFilePath() throws EaafConfigurationException { +    final String path = authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_KEYSTORE_PATH); +    if (path == null) { +      throw new EaafConfigurationException(Constants.ERRORCODE_00, +          new Object[] { IdAustriaClientAuthConstants.CONFIG_PROPS_KEYSTORE_PATH }); + +    } +    return path; +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getMetadataKeyAlias() +   */ +  @Override +  public String getMetadataKeyAlias() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_SIGN_METADATA_ALIAS); +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getMetadataKeyPassword() +   */ +  @Override +  public String getMetadataKeyPassword() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_SIGN_METADATA_KEY_PASSWORD); +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getSignatureKeyAlias() +   */ +  @Override +  public String getSignatureKeyAlias() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_SIGN_SIGNING_ALIAS); +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getSignatureKeyPassword() +   */ +  @Override +  public String getSignatureKeyPassword() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_SIGN_SIGNING_KEY_PASSWORD); +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getEncryptionKeyAlias() +   */ +  @Override +  public String getEncryptionKeyAlias() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_ENCRYPTION_ALIAS); +  } + +  /* +   * (non-Javadoc) +   * +   * @see +   * at.gv.egovernment.moa.id.protocols.pvp2x.signer.AbstractCredentialProvider# +   * getEncryptionKeyPassword() +   */ +  @Override +  public String getEncryptionKeyPassword() { +    return authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_ENCRYPTION_KEY_PASSWORD); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthHealthCheck.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthHealthCheck.java new file mode 100644 index 00000000..bd5e220b --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthHealthCheck.java @@ -0,0 +1,80 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.HealthIndicator; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.resolver.ResolverException; + +/** + * Spring Actuator HealthCheck for ID Austria client that evaluates the current status of  + * ID Austria SAML2 metadata that are loaded into MS-Connector.  + *  + * @author tlenz + * + */ +@Slf4j +public class IdAustriaClientAuthHealthCheck implements HealthIndicator { + +  private static final int DEADLINE = 3; +   +  @Autowired IConfiguration authConfig; +  @Autowired IdAustriaClientAuthMetadataProvider metadataService; +   +  @Override +  public Health health() { +    String msNodeEntityID = authConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID); +     +    if (StringUtils.isEmpty(msNodeEntityID)) { +      log.trace("No ID Austria EntityId in configuration. Skipping tests ... "); +      return Health.unknown().build(); + +    } +     +    CompletableFuture<Health> asynchTestOperation = new CompletableFuture<>(); +    Executors.newCachedThreadPool().submit(() -> runConnectionTest(asynchTestOperation, msNodeEntityID));     +    try { +      return asynchTestOperation.get(DEADLINE, TimeUnit.SECONDS); +       +    } catch (InterruptedException | ExecutionException | TimeoutException e) { +      log.info("Receive no respose from Health-Check after {} seconds.", DEADLINE);       +      return Health.outOfService().withException(e).build(); +       +    } + +     +  } +   +   +  private void runConnectionTest(CompletableFuture<Health> completableFuture, String entityId) { +    try { +      EntityDescriptor connectorMetadata =  +          metadataService.getEntityDescriptor(entityId); +      if (connectorMetadata != null) { +        completableFuture.complete(Health.up().build()); +         +      } else { +        completableFuture.complete(Health.outOfService().withDetail("Reason", "No SAML2 metadata").build()); +         +      } +             +    } catch (ResolverException e) { +      completableFuture.complete(Health.down(e).build()); +       +    } +     +  } +   +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthMetadataProvider.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthMetadataProvider.java new file mode 100644 index 00000000..7c87548f --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/idaustriaclient/provider/IdAustriaClientAuthMetadataProvider.java @@ -0,0 +1,169 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.Provider; +import java.security.cert.CertificateException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml.metadata.resolver.MetadataResolver; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; +import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.http.IHttpClientFactory; +import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; +import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.AbstractChainingMetadataProvider; +import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.PvpMetadataResolverFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SchemaValidationFilter; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.metadata.SimpleMetadataSignatureVerificationFilter; +import lombok.extern.slf4j.Slf4j; + +/** + * SAML2 metadata-provider implementation for ID Austria client. + * + * @author tlenz + * + */ +@Slf4j +public class IdAustriaClientAuthMetadataProvider extends AbstractChainingMetadataProvider { + +  private static final String FRIENDLYNAME_METADATA_TRUSTSTORE = "'ID Austria client metadata truststore'"; +  private static final String PROVIDER_ID_PATTERN = "eIDAS resolver: {0}"; +  public static final String PROVIDER_ID = "'ID Austria client metadata-provider'"; + +  @Autowired +  private IConfiguration basicConfig; + +  @Autowired +  private PvpMetadataResolverFactory metadataProviderFactory; +  @Autowired +  private IHttpClientFactory httpClientFactory; + +  @Autowired +  private EaafKeyStoreFactory keyStoreFactory; + +  private Pair<KeyStore, Provider> metadataSigningTrustStore; + +  @Override +  protected String getMetadataUrl(String entityId) throws EaafConfigurationException { +    log.trace("ID Austria. uses SAML2 well-known location approach. EntityId is Metadata-URL"); +    return entityId; + +  } + +  @Override +  protected MetadataResolver createNewMetadataProvider(String entityId) throws EaafConfigurationException, +      IOException, CertificateException { +    final List<MetadataFilter> filterList = new ArrayList<>(); +    filterList.add(new SchemaValidationFilter(true)); +    filterList.add(new SimpleMetadataSignatureVerificationFilter( +        metadataSigningTrustStore.getFirst(), entityId)); + +    final MetadataFilterChain filter = new MetadataFilterChain(); +    filter.setFilters(filterList); + +    try { +      return metadataProviderFactory.createMetadataProvider(getMetadataUrl(entityId), +          filter, +          MessageFormat.format(PROVIDER_ID_PATTERN, entityId), +          httpClientFactory.getHttpClient()); + +    } catch (final Pvp2MetadataException e) { +      log.info("Can NOT build metadata provider for entityId: {}", entityId); +      throw new EaafConfigurationException(IdAustriaClientAuthConstants.ERRORCODE_05, +          new Object[] { entityId, e.getMessage() }, e); + +    } +  } + +  @Override +  protected List<String> getAllMetadataUrlsFromConfiguration() throws EaafConfigurationException { +    return Collections.emptyList(); + +  } + +  @Override +  protected String getMetadataProviderId() { +    return PROVIDER_ID; + +  } + +  @Override +  public void runGarbageCollector() { +    log.trace("Garbage collection is NOT supported by: {}", getId()); +  } + +  @Override +  public void doDestroy() { +    super.fullyDestroy(); + +  } + +  @PostConstruct +  private void initialize() throws EaafException { +    // initialize truststore to validate metadata signing certificates +    initializeTrustStore(); + +    // load metadata with metadataURL, as backup +    initializeFileSystemMetadata(); + +  } + +  private void initializeFileSystemMetadata() { +    try { +      final String metadataUrl = basicConfig.getBasicConfiguration( +          IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_METADATAURL); +      if (StringUtils.isNotEmpty(metadataUrl)) { +        log.info("Use not recommended metadata-provider initialization!" +            + " SAML2 'Well-Known-Location' is the preferred methode."); +        log.info("Initialize 'ms-specific eIDAS node' metadata-provider with URL: {}", metadataUrl); + +        addMetadataResolverIntoChain(createNewMetadataProvider(metadataUrl)); +      } + +    } catch (final EaafConfigurationException | CertificateException | IOException e) { +      log.warn("Can NOT inject static eIDAS Node metadata-soure.", e); +      log.warn("eIDAS Node communication can be FAIL."); + +    } +  } + +  private void initializeTrustStore() throws EaafException { +    // set configuration +    final KeyStoreConfiguration trustStoreConfig = new KeyStoreConfiguration(); +    trustStoreConfig.setFriendlyName(FRIENDLYNAME_METADATA_TRUSTSTORE); +    trustStoreConfig.setKeyStoreType(basicConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_TRUSTSTORE_TYPE, +        KeyStoreType.JKS.getKeyStoreType())); +    trustStoreConfig.setKeyStoreName(basicConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_TRUSTSTORE_NAME)); +    trustStoreConfig.setSoftKeyStoreFilePath(basicConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_TRUSTSTORE_PATH)); +    trustStoreConfig.setSoftKeyStorePassword(basicConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_TRUSTSTORE_PASSWORD)); + +    // validate configuration +    trustStoreConfig.validate(); + +    // open new TrustStore +    metadataSigningTrustStore = keyStoreFactory.buildNewKeyStore(trustStoreConfig); + +  } + +} + diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java index 230d6052..b5493edb 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/CcSpecificEidProcessingService.java @@ -32,6 +32,7 @@ import java.util.Map.Entry;  import javax.annotation.PostConstruct; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData;  import org.apache.commons.lang3.StringUtils;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory; @@ -40,7 +41,6 @@ import org.springframework.context.ApplicationContext;  import org.springframework.stereotype.Service;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.handler.INationalEidProcessor; @@ -104,7 +104,7 @@ public class CcSpecificEidProcessingService implements ICcSpecificEidProcessingS    }    @Override -  public ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +  public SimpleEidasData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException,        EidasAttributeException {      // extract citizen country from eIDAS unique identifier      final Object eIdentifierObj = eidasAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java index ebbc15e4..fb9ba318 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/service/ICcSpecificEidProcessingService.java @@ -25,7 +25,7 @@ package at.asitplus.eidas.specific.modules.auth.eidas.v2.service;  import java.util.Map; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException;  import at.gv.egiz.eaaf.core.api.IRequest; @@ -36,20 +36,20 @@ public interface ICcSpecificEidProcessingService {    /**     * Post-process eIDAS eID attributes into national format. -   *  +   *     * @param eidasAttrMap Map of eIDAS attributes in format friendlyName and     *                     attribute -   *  +   *     * @return eID attributes for SZR request     * @throws EidPostProcessingException In case of a post-processing error     * @throws EidasAttributeException In case of an invalid eIDAS attribute value     */ -  ErnbEidData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException, +  SimpleEidasData postProcess(Map<String, Object> eidasAttrMap) throws EidPostProcessingException,        EidasAttributeException;    /**     * Pre Process eIDAS request into national requirements. -   *  +   *     * @param selectedCC          Citizen Country from selection     * @param pendingReq          current pending request     * @param authnRequestBuilder eIDAS {@link ILightRequest} builder 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()); +    } + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java deleted file mode 100644 index 11b1e589..00000000 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/szr/SzrClient.java +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Copyright 2018 A-SIT Plus GmbH - * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, - * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. - * - * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by - * the European Commission - subsequent versions of the EUPL (the "License"); - * You may not use this work except in compliance with the License. - * You may obtain a copy of the License at: - * https://joinup.ec.europa.eu/news/understanding-eupl-v12 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * This product combines work with different licenses. See the "NOTICE" text - * file for details on the various modules and licenses. - * The "NOTICE" text file is part of the distribution. Any derivative works - * that you distribute must include a readable copy of the "NOTICE" text file. - */ - -package at.asitplus.eidas.specific.modules.auth.eidas.v2.szr; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.URL; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.UnrecoverableKeyException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.PostConstruct; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; -import javax.xml.namespace.QName; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import javax.xml.ws.BindingProvider; -import javax.xml.ws.Dispatch; -import javax.xml.ws.handler.Handler; - -import org.apache.commons.lang3.StringUtils; -import org.apache.cxf.configuration.jsse.TLSClientParameters; -import org.apache.cxf.endpoint.Client; -import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.jaxws.DispatchImpl; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; -import org.apache.xpath.XPathAPI; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.LoggingHandler; -import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; -import at.gv.egiz.eaaf.core.api.data.XmlNamespaceConstants; -import at.gv.egiz.eaaf.core.api.idp.IConfiguration; -import at.gv.egiz.eaaf.core.impl.utils.DomUtils; -import at.gv.egiz.eaaf.core.impl.utils.FileUtils; -import at.gv.egiz.eaaf.core.impl.utils.KeyStoreUtils; -import lombok.extern.slf4j.Slf4j; -import szrservices.GetBPK; -import szrservices.GetBPKResponse; -import szrservices.GetIdentityLinkEidas; -import szrservices.GetIdentityLinkEidasResponse; -import szrservices.IdentityLinkType; -import szrservices.JwsHeaderParam; -import szrservices.ObjectFactory; -import szrservices.PersonInfoType; -import szrservices.SZR; -import szrservices.SZRException_Exception; -import szrservices.SignContent; -import szrservices.SignContentEntry; -import szrservices.SignContentResponseType; - - -@Slf4j -@Service("SZRClientForeIDAS") -public class SzrClient { - -  private static final String CLIENT_DEFAULT = "DefaultClient"; -  private static final String CLIENT_RAW = "RawClient"; - -  private static final String ATTR_NAME_VSZ = "urn:eidgvat:attributes.vsz.value"; -  private static final String ATTR_NAME_PUBKEYS = "urn:eidgvat:attributes.user.pubkeys"; -  private static final String ATTR_NAME_STATUS = "urn:eidgvat:attributes.eid.status"; -  private static final String KEY_BC_BIND = "bcBindReq"; -  private static final String JOSE_HEADER_USERCERTPINNING_TYPE = "urn:at.gv.eid:bindtype"; -  private static final String JOSE_HEADER_USERCERTPINNING_EIDASBIND = "urn:at.gv.eid:eidasBind"; -  public static final String ATTR_NAME_MDS = "urn:eidgvat:mds"; -   -  @Autowired -  private IConfiguration basicConfig; - -  // client for anything, without identitylink -  private SZR szr = null; - -  // RAW client is needed for identitylink -  private Dispatch<Source> dispatch = null; - -  private SzrService szrService = null; -  private String szrUrl = null; -  private QName qname = null; - -  final ObjectMapper mapper = new ObjectMapper(); - -  /** -   * Get IdentityLink of a person. -   * -   * @param personInfo Person identification information -   * @return IdentityLink -   * @throws SzrCommunicationException In case of a SZR error -   */ -  public IdentityLinkType getIdentityLinkInRawMode(PersonInfoType personInfo) -      throws SzrCommunicationException { -    try { -      final GetIdentityLinkEidas getIdl = new GetIdentityLinkEidas(); -      getIdl.setPersonInfo(personInfo); - -      final JAXBContext jaxbContext = JAXBContext.newInstance(ObjectFactory.class); -      final Marshaller jaxbMarshaller = jaxbContext.createMarshaller(); - -      final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -      jaxbMarshaller.marshal(getIdl, outputStream); -      outputStream.flush(); - -      final Source source = new StreamSource(new ByteArrayInputStream(outputStream.toByteArray())); -      outputStream.close(); - -      log.trace("Requesting SZR ... "); -      final Source response = dispatch.invoke(source); -      log.trace("Receive RAW response from SZR"); - -      final byte[] szrResponse = sourceToByteArray(response); -      final GetIdentityLinkEidasResponse jaxbElement = (GetIdentityLinkEidasResponse) jaxbContext -          .createUnmarshaller().unmarshal(new ByteArrayInputStream(szrResponse)); - -      // build response -      log.trace(new String(szrResponse, "UTF-8")); - -      // ok, we have success -      final Document doc = DomUtils.parseDocument( -          new ByteArrayInputStream(szrResponse), -          true, -          XmlNamespaceConstants.ALL_SCHEMA_LOCATIONS + " " + Constants.SZR_SCHEMA_LOCATIONS, -          null, null); -      final String xpathExpression = "//saml:Assertion"; -      final Element nsNode = doc.createElementNS("urn:oasis:names:tc:SAML:1.0:assertion", "saml:NSNode"); - -      log.trace("Selecting signed doc " + xpathExpression); -      final Element documentNode = (Element) XPathAPI.selectSingleNode(doc, -          xpathExpression, nsNode); -      log.trace("Signed document: " + DomUtils.serializeNode(documentNode)); - -      final IdentityLinkType idl = new IdentityLinkType(); -      idl.setAssertion(documentNode); -      idl.setPersonInfo(jaxbElement.getGetIdentityLinkReturn().getPersonInfo()); - -      return idl; - -    } catch (final Exception e) { -      log.warn("SZR communication FAILED for operation: {} Reason: {}",  -          "GetIdentityLinkEidas", e.getMessage(), e); -      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); - -    } - -  } - -  /** -   * Get bPK of person. -   * -   * @param personInfo Person identification information -   * @param target     requested bPK target -   * @param vkz        Verfahrenskennzeichen -   * @return bPK for this person -   * @throws SzrCommunicationException In case of a SZR error -   */ -  public List<String> getBpk(PersonInfoType personInfo, String target, String vkz) -      throws SzrCommunicationException { -    try { -      final GetBPK parameters = new GetBPK(); -      parameters.setPersonInfo(personInfo); -      parameters.getBereichsKennung().add(target); -      parameters.setVKZ(vkz); -      final GetBPKResponse result = this.szr.getBPK(parameters); - -      return result.getGetBPKReturn(); - -    } catch (final SZRException_Exception e) { -      log.warn("SZR communication FAILED for operation: {} Reason: {}",  -          "GetBPK", e.getMessage(), e); -      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); - -    } - -  } - -  /** -   * Request a encryped baseId from SRZ. -   * -   * @param personInfo Minimum dataset of person -   * @param insertErnp insertErnp flag on SZR request -   * @return encrypted baseId -   * @throws SzrCommunicationException    In case of a SZR error -   */ -  public String getEncryptedStammzahl(final PersonInfoType personInfo, boolean insertErnp)  -      throws SzrCommunicationException { -    final String resp; -    try { -      resp = this.szr.getStammzahlEncrypted(personInfo, insertErnp); -     -    } catch (SZRException_Exception e) { -      log.warn("SZR communication FAILED for operation: {} Reason: {}",  -          "getStammzahlEncrypted", e.getMessage(), e); -      throw new SzrCommunicationException("ernb.02", new Object[]{e.getMessage()}, e); -    } - -    if (StringUtils.isEmpty(resp)) {       -      throw new SzrCommunicationException("ernb.01", new Object[]{"Stammzahl response empty"}); // TODO error handling -       -    } - -    return resp;   -  } -     -  /** -   * Request a encrypted baseId from SRZ without insertErnp. -   * -   * @param personInfo Minimum dataset of person -   * @return encrypted baseId -   * @throws SzrCommunicationException    In case of a SZR error -   */ -  public String getEncryptedStammzahl(final PersonInfoType personInfo) -      throws SzrCommunicationException { -    return getEncryptedStammzahl(personInfo, false); - -  } - -  /** -   * Sign an eidasBind data-structure that combines vsz with user's pubKey and E-ID status. -   * -   * @param vsz encryped baseId -   * @param bindingPubKey  binding PublikKey as PKCS1# (ASN.1) container -   * @param eidStatus Status of the E-ID -   * @param eidData eID information that was used for ERnP registration -   * @return bPK for this person -   * @throws SzrCommunicationException In case of a SZR error -   */ -  public String getEidsaBind(final String vsz, final String bindingPubKey, final String eidStatus, -      ErnbEidData eidData)throws SzrCommunicationException { - -    final Map<String, Object> eidsaBindMap = new HashMap<>(); -    eidsaBindMap.put(ATTR_NAME_VSZ, vsz); -    eidsaBindMap.put(ATTR_NAME_STATUS, eidStatus); -    eidsaBindMap.put(ATTR_NAME_PUBKEYS, Arrays.asList(bindingPubKey)); -    eidsaBindMap.put(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, eidData.getCitizenCountryCode()); -    injectMdsIfAvailableAndActive(eidsaBindMap, eidData);         -     -    try { -      final String serializedEidasBind = mapper.writeValueAsString(eidsaBindMap); -      final SignContent req = new SignContent(); -      final SignContentEntry eidasBindInfo = new SignContentEntry(); -      eidasBindInfo.setKey(KEY_BC_BIND); -      eidasBindInfo.setValue(serializedEidasBind); -      req.getIn().add(eidasBindInfo); -      req.setAppendCert(false); -      final JwsHeaderParam eidasBindJoseHeader = new JwsHeaderParam(); -      eidasBindJoseHeader.setKey(JOSE_HEADER_USERCERTPINNING_TYPE); -      eidasBindJoseHeader.setValue(JOSE_HEADER_USERCERTPINNING_EIDASBIND); -      req.getJWSHeaderParam().add(eidasBindJoseHeader); - -      log.trace("Requesting SZR to sign bcBind datastructure ... "); -      final SignContentResponseType resp = szr.signContent(req.isAppendCert(), req.getJWSHeaderParam(), req.getIn()); -      log.trace("Receive SZR response on bcBind siging operation "); - -      if (resp == null || resp.getOut() == null -          || resp.getOut().isEmpty() -          || StringUtils.isEmpty(resp.getOut().get(0).getValue())) { -        throw new SzrCommunicationException("ernb.01", new Object[]{"BcBind response empty"}); -      } - -      return resp.getOut().get(0).getValue(); - -    } catch (final JsonProcessingException | SZRException_Exception e) { -      log.warn("SZR communication FAILED for operation: {} Reason: {}",  -          "SignContent", e.getMessage(), e); -      throw new SzrCommunicationException("ernb.02", -          new Object[]{e.getMessage()}, e); -    } -  } - -  @PostConstruct -  private void initialize() { -    log.info("Starting SZR-Client initialization .... "); -    final URL url = SzrClient.class.getResource("/szr_client/SZR_v4.0.wsdl"); - -    final boolean useTestSzr = basicConfig.getBasicConfigurationBoolean( -        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_USETESTSERVICE, -        true); - -    if (useTestSzr) { -      log.debug("Initializing SZR test environment configuration."); -      qname = SzrService.SZRTestumgebung; -      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); -      szr = szrService.getSzrTestumgebung(); -      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_TEST); - -    } else { -      log.debug("Initializing SZR productive configuration."); -      qname = SzrService.SZRProduktionsumgebung; -      szrService = new SzrService(url, new QName("urn:SZRServices", "SZRService")); -      szr = szrService.getSzrProduktionsumgebung(); -      szrUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_ENDPOINT_PROD); - -    } - -    // create raw client; -    dispatch = szrService.createDispatch(qname, Source.class, javax.xml.ws.Service.Mode.PAYLOAD); - -    if (StringUtils.isEmpty(szrUrl)) { -      log.error("No SZR service-URL found. SZR-Client initalisiation failed."); -      throw new RuntimeException("No SZR service URL found. SZR-Client initalisiation failed."); - -    } - -    // check if Clients can be initialized -    if (szr == null) { -      log.error("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); -      throw new RuntimeException("SZR " + CLIENT_DEFAULT + " is 'NULL'. Something goes wrong"); - -    } -    if (dispatch == null) { -      log.error("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); -      throw new RuntimeException("SZR " + CLIENT_RAW + " is 'NULL'. Something goes wrong"); - -    } - -    // inject handler -    log.info("Use SZR service-URL: " + szrUrl); -    injectBindingProvider((BindingProvider) szr, CLIENT_DEFAULT); -    injectBindingProvider(dispatch, CLIENT_RAW); - -    // inject http parameters and SSL context -    log.debug("Inject HTTP client settings ... "); -    injectHttpClient(szr, CLIENT_DEFAULT); -    injectHttpClient(dispatch, CLIENT_RAW); - -    log.info("SZR-Client initialization successfull"); -  } - -  private void injectHttpClient(Object raw, String clientType) { -    // extract client from implementation -    Client client = null; -    if (raw instanceof DispatchImpl<?>) { -      client = ((DispatchImpl<?>) raw).getClient(); -    } else if (raw instanceof Client) { -      client = ClientProxy.getClient(raw); -    } else { -      throw new RuntimeException("SOAP Client for SZR connection is of UNSUPPORTED type: " + raw.getClass() -          .getName()); -    } - -    // set basic connection policies -    final HTTPConduit http = (HTTPConduit) client.getConduit(); - -    // set timeout policy -    final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy(); -    httpClientPolicy.setConnectionTimeout( -        Integer.parseInt(basicConfig.getBasicConfiguration( -            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_CONNECTION, -            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_CONNECTION)) * 1000); -    httpClientPolicy.setReceiveTimeout( -        Integer.parseInt(basicConfig.getBasicConfiguration( -            Constants.CONIG_PROPS_EIDAS_SZRCLIENT_TIMEOUT_RESPONSE, -            Constants.HTTP_CLIENT_DEFAULT_TIMEOUT_RESPONSE)) * 1000); -    http.setClient(httpClientPolicy); - -    // inject SSL context in case of https -    if (szrUrl.toLowerCase().startsWith("https")) { -      log.debug("Adding SSLContext to client: " + clientType + " ... "); -      final TLSClientParameters tlsParams = new TLSClientParameters(); -      tlsParams.setSSLSocketFactory(createSslContext(clientType).getSocketFactory()); -      http.setTlsClientParameters(tlsParams); -      log.info("SSLContext initialized for client: " + clientType); - -    } - -  } - -  private void injectBindingProvider(BindingProvider bindingProvider, String clientType) { -    final Map<String, Object> requestContext = bindingProvider.getRequestContext(); -    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, szrUrl); - -    log.trace("Adding JAX-WS request/response trace handler to client: " + clientType); -    List<Handler> handlerList = bindingProvider.getBinding().getHandlerChain(); -    if (handlerList == null) { -      handlerList = new ArrayList<>(); -      bindingProvider.getBinding().setHandlerChain(handlerList); - -    } - -    // add logging handler to trace messages if required -    if (basicConfig.getBasicConfigurationBoolean( -        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_TRACEMESSAGES, -        false)) { -      final LoggingHandler loggingHandler = new LoggingHandler(); -      handlerList.add(loggingHandler); - -    } -    bindingProvider.getBinding().setHandlerChain(handlerList); -  } - -  private SSLContext createSslContext(String clientType) { -    try { -      final SSLContext context = SSLContext.getInstance("TLS"); - -      // initialize key-mangager for SSL client-authentication -      KeyManager[] keyManager = null; -      final String keyStorePath = basicConfig.getBasicConfiguration( -          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PATH); -      final String keyStorePassword = basicConfig.getBasicConfiguration( -          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_KEYSTORE_PASSWORD); -      if (StringUtils.isNotEmpty(keyStorePath)) { -        log.trace("Find keyStore path: " + keyStorePath + " Injecting SSL client certificate ... "); -        try { -          final KeyStore keyStore = KeyStoreUtils.loadKeyStore( -              FileUtils.makeAbsoluteUrl(keyStorePath, basicConfig.getConfigurationRootDirectory()), -              keyStorePassword); - -          final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); -          kmf.init(keyStore, keyStorePassword.toCharArray()); -          keyManager = kmf.getKeyManagers(); -          log.debug("SSL client certificate injected to client: " + clientType); - -        } catch (KeyStoreException | IOException | UnrecoverableKeyException e) { -          log.error("Can NOT load SSL client certificate from path: " + keyStorePath); -          throw new RuntimeException("Can NOT load SSL client certificate from path: " + keyStorePath, e); - -        } -      } else { -        log.debug( -            "No KeyStore for SSL Client Auth. found. Initializing SSLContext without authentication ... "); - -      } - -      // initialize SSL TrustStore -      TrustManager[] trustManager = null; -      final String trustStorePath = basicConfig.getBasicConfiguration( -          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PATH); -      final String trustStorePassword = basicConfig.getBasicConfiguration( -          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SSL_TRUSTSTORE_PASSWORD); -      if (StringUtils.isNotEmpty(trustStorePath)) { -        log.trace("Find trustStore path: " + trustStorePath + " Injecting SSL TrustStore ... "); -        try { -          final KeyStore trustStore = KeyStoreUtils.loadKeyStore( -              FileUtils.makeAbsoluteUrl(trustStorePath, basicConfig.getConfigurationRootDirectory()), -              trustStorePassword); - -          final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509"); -          tmf.init(trustStore); -          trustManager = tmf.getTrustManagers(); -          log.debug("SSL TrustStore injected to client: " + clientType); - -        } catch (KeyStoreException | IOException e) { -          log.error("Can NOT open SSL TrustStore from path: " + trustStorePath); -          throw new RuntimeException("Can NOT open SSL TrustStore from path: " + trustStorePath, e); - -        } - -      } else { -        log.debug("No custom SSL TrustStore found. Initializing SSLContext with JVM default truststore ... "); - -      } - -      context.init(keyManager, trustManager, new SecureRandom()); -      return context; - -    } catch (NoSuchAlgorithmException | KeyManagementException e) { -      log.error("SSLContext initialization FAILED.", e); -      throw new RuntimeException("SSLContext initialization FAILED.", e); - -    } - -  } - -  private void injectMdsIfAvailableAndActive(Map<String, Object> eidsaBindMap, ErnbEidData eidData) { -    if (basicConfig.getBasicConfigurationBoolean( -        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_SET_MDS_TO_EIDASBIND, false)) { -      log.info("Injecting MDS into eidasBind ... "); -      final Map<String, Object> mds = new HashMap<>();       -      mds.put(PvpAttributeDefinitions.PRINCIPAL_NAME_NAME, eidData.getFamilyName()); -      mds.put(PvpAttributeDefinitions.GIVEN_NAME_NAME, eidData.getGivenName()); -      mds.put(PvpAttributeDefinitions.BIRTHDATE_NAME, eidData.getFormatedDateOfBirth());      -      eidsaBindMap.put(ATTR_NAME_MDS, mds); -       -    } -  } -   -  private byte[] sourceToByteArray(Source result) throws TransformerException { -    final TransformerFactory factory = TransformerFactory.newInstance(); -    factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); -    final Transformer transformer = factory.newTransformer(); -    transformer.setOutputProperty("omit-xml-declaration", "yes"); -    transformer.setOutputProperty("method", "xml"); -    final ByteArrayOutputStream out = new ByteArrayOutputStream(); -    final StreamResult streamResult = new StreamResult(); -    streamResult.setOutputStream(out); -    transformer.transform(result, streamResult); -    return out.toByteArray(); -  } - -} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java new file mode 100644 index 00000000..96aa9c51 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/AlternativeSearchTask.java @@ -0,0 +1,246 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +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.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; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +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.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 eu.eidas.auth.commons.light.ILightResponse; +import lombok.extern.slf4j.Slf4j; + +/** + * Searches registers (ERnP and ZMR) after alternative eIDAS authn, before adding person to SZR. + * Input: + * <ul> + *     <li>{@link Constants#DATA_FULL_EIDAS_RESPONSE_ALTERNATIVE} data from the alternative eIDAS authn</li> + *     <li>{@link Constants#DATA_SIMPLE_EIDAS} data from the initial eIDAS authn</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#DATA_PERSON_MATCH_RESULT} results after second search in registers with MDS</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link GenerateOtherLoginMethodGuiTask} if no results in registers were found for this user</li> + *     <li>{@link CreateIdentityLinkTask} if search in register returned one match, user is uniquely identified</li> + * </ul> + * + * @author amarsalek + * @author ckollmann + * @author tlenz + */ +@Slf4j +@Component("AlternativeSearchTask") +@SuppressWarnings("PMD.TooManyStaticImports") +public class AlternativeSearchTask extends AbstractAuthServletTask { + +  private static final String MSG_PROP_25 = "module.eidasauth.matching.25"; +   +  private final RegisterSearchService registerSearchService; +  private final ICcSpecificEidProcessingService eidPostProcessor; + +  /** +   * Constructor. +   * +   * @param registerSearchService Service for register search access +   * @param eidPostProcessor      Country-Specific post processing of attributes +   */ +  public AlternativeSearchTask(RegisterSearchService registerSearchService, +                               ICcSpecificEidProcessingService eidPostProcessor) { +    this.registerSearchService = registerSearchService; +    this.eidPostProcessor = eidPostProcessor; +  } + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { +    try { +      final SimpleEidasData altEidasData = convertEidasAttrToSimpleData(); +      final SimpleEidasData initialEidasData = MatchingTaskUtils.getInitialEidasData(pendingReq); +      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); + +    } +  } + +  /** +   * Pre-validation of eIDAS information. +   * +   * <p>Check if country-code and MDS (givenName, familyName, dateOfBirth) matches.</p> +   * +   * @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", 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 altEidasData, +      RegisterStatusResults intermediateMatchingState, SimpleEidasData initialEidasData) +      throws WorkflowException, EaafStorageException { +    try { +      log.trace("Starting step11RegisterSearchWithPersonIdentifier"); +      RegisterStatusResults altSearchResult = registerSearchService.searchWithPersonIdentifier( +          intermediateMatchingState.getOperationStatus(), altEidasData); + +      int resultCount = altSearchResult.getResultCount(); +      if (resultCount == 0) { +        step12CountrySpecificSearch(executionContext, intermediateMatchingState, initialEidasData, +            altSearchResult.getOperationStatus(), altEidasData); + +      } else if (resultCount == 1) { +        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) { +      log.warn("Workflow error during matching step: {}. Reason: {}", e.getProcessStepName(), e.getErrorReason()); +      throw e; + +    } +  } + +  private void step12CountrySpecificSearch(ExecutionContext executionContext, +                                           RegisterStatusResults intermediateMatchingState, +                                           SimpleEidasData initialEidasData, +                                           RegisterOperationStatus registerOperationStatus, +                                           SimpleEidasData altEidasData) +      throws EaafStorageException, WorkflowException { +    log.trace("Starting 'step12CountrySpecificSearch' ... "); +    RegisterStatusResults ccAltSearchResult = registerSearchService.searchWithCountrySpecifics( +        registerOperationStatus, altEidasData); + +    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); +      executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_25); +      executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true);  + +    } 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) +      throws WorkflowException, EaafStorageException { +    MatchedPersonResult result = MatchedPersonResult.generateFormMatchingResult( +        searchResult.getResult(), eidasData.getCitizenCountryCode()); +    MatchingTaskUtils.storeFinalMatchingResult(pendingReq, result); + +    //remove intermediate matching-state +    MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, null); + +  } + +  @NotNull +  private SimpleEidasData convertEidasAttrToSimpleData() +      throws EidasAttributeException, EidPostProcessingException { +    final ILightResponse eidasResponse = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq) +        .getGenericDataFromSession(Constants.DATA_FULL_EIDAS_RESPONSE_ALTERNATIVE, ILightResponse.class); +    Map<String, Object> simpleMap = MatchingTaskUtils.convertEidasAttrToSimpleMap( +        eidasResponse.getAttributes().getAttributeMap(), log); +    return eidPostProcessor.postProcess(simpleMap); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java index 58ab0c6a..6d315b0a 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateIdentityLinkTask.java @@ -23,87 +23,78 @@  package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap;  import java.util.List; -import java.util.Map;  import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse; -import javax.xml.parsers.ParserConfigurationException; -import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; +import org.jetbrains.annotations.Nullable; +import org.jose4j.lang.JoseException;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.stereotype.Component;  import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.fasterxml.jackson.core.JsonProcessingException;  import at.asitplus.eidas.specific.core.MsConnectorEventCodes;  import at.asitplus.eidas.specific.core.MsEidasNodeConstants;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ErnbEidData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.szr.SzrClient; +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.EidasAttributeException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.SzrCommunicationException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.AuthBlockSigningService; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.szr.SzrClient; -import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.EidasResponseUtils; -import at.gv.e_government.reference.namespace.persondata._20020228.AlternativeNameType; -import at.gv.e_government.reference.namespace.persondata._20020228.IdentificationType; -import at.gv.e_government.reference.namespace.persondata._20020228.PersonNameType; -import at.gv.e_government.reference.namespace.persondata._20020228.PhysicalPersonType; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils;  import at.gv.egiz.eaaf.core.api.data.EaafConstants;  import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions;  import at.gv.egiz.eaaf.core.api.idp.IConfiguration;  import at.gv.egiz.eaaf.core.api.idp.auth.data.IIdentityLink;  import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; -import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException;  import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.EaafStorageException;  import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;  import at.gv.egiz.eaaf.core.impl.builder.BpkBuilder;  import at.gv.egiz.eaaf.core.impl.data.Pair;  import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper;  import at.gv.egiz.eaaf.core.impl.idp.auth.data.SimpleIdentityLinkAssertionParser;  import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; -import at.gv.egiz.eaaf.core.impl.utils.DomUtils; -import at.gv.egiz.eaaf.core.impl.utils.XPathUtils; -import eu.eidas.auth.commons.attribute.AttributeDefinition; -import eu.eidas.auth.commons.attribute.AttributeValue; -import eu.eidas.auth.commons.light.ILightResponse; -import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress;  import lombok.Data;  import lombok.extern.slf4j.Slf4j;  import szrservices.IdentityLinkType; -import szrservices.PersonInfoType; -import szrservices.TravelDocumentType;  /**   * Task that creates the IdentityLink for an eIDAS authenticated person. - * + * Input: + * <ul> + *     <li>{@link Constants#DATA_SIMPLE_EIDAS} initial login data from user</li> + *     <li>{@link Constants#DATA_PERSON_MATCH_RESULT} the data of the matched entry in a register</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#EIDAS_BIND} the binding block</li> + *     <li>{@link Constants#SZR_AUTHBLOCK} the auth block</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link at.gv.egiz.eaaf.core.impl.idp.controller.tasks.FinalizeAuthenticationTask}</li> + * </ul>   * @author tlenz   */  @Slf4j  @Component("CreateIdentityLinkTask")  public class CreateIdentityLinkTask extends AbstractAuthServletTask { +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")    @Autowired    private IConfiguration basicConfig;    @Autowired    private SzrClient szrClient;    @Autowired -  private ICcSpecificEidProcessingService eidPostProcessor; -   -  @Autowired    private AuthBlockSigningService authBlockSigner;    private static final String EID_STATUS = "urn:eidgvat:eid.status.eidas"; -   +    /*     * (non-Javadoc)     * @@ -116,98 +107,32 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask {    public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response)        throws TaskExecutionException {      try { -      final AuthProcessDataWrapper authProcessData = pendingReq.getSessionData(AuthProcessDataWrapper.class); -      final ILightResponse eidasResponse = authProcessData -          .getGenericDataFromSession(Constants.DATA_FULL_EIDAS_RESPONSE, ILightResponse.class); -      final Map<String, Object> simpleAttrMap = convertEidasAttrToSimpleMap( -          eidasResponse.getAttributes().getAttributeMap()); +      /*TODO: needs more re-factoring if we finalize CreateNewErnpEntryTask and we know how add entries into ERnP +       *      Maybe, we can fully replace eidData by matchedPersonData, +       *      because matchedPersonData holds the result after a successful matching process. +       * +       *      Currently, we only add a work-around to operate without new ERnP implementation. +       */ +      final SimpleEidasData eidData = MatchingTaskUtils.getInitialEidasData(pendingReq); +      MatchedPersonResult matchedPersonData = MatchingTaskUtils.getFinalMatchingResult(pendingReq); -      // post-process eIDAS attributes -      final ErnbEidData eidData = eidPostProcessor.postProcess(simpleAttrMap); - -      // write MDS into technical log and revision log +      // write log information based on current configuration        writeMdsLogInformation(eidData); -      //build IdentityLink or VSZ and eidasBind -      if (basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USEDUMMY, false)) { -        SzrResultHolder idlResult = createDummyIdentityLinkForTestDeployment(eidData); -        //inject personal-data into session -        authProcessData.setIdentityLink(idlResult.getIdentityLink());   -         -        // set bPK and bPKType into auth session -        authProcessData.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix( -            idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); -        authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, -                                                pendingReq.getServiceProviderConfiguration() -                                                          .getAreaSpecificTargetIdentifier()); -                  +      //request SZR based on IDL or E-ID mode +      if (pendingReq.getServiceProviderConfiguration() +          .isConfigurationValue(MsEidasNodeConstants.PROP_CONFIG_SP_NEW_EID_MODE, false)) { +        executeEidMode(eidData, matchedPersonData); +        } else { -        //build SZR request from eIDAS data         -        final PersonInfoType personInfo = generateSzrRequest(eidData); -         -        //request SZR based on IDL or E-ID mode -        if (pendingReq.getServiceProviderConfiguration() -            .isConfigurationValue(MsEidasNodeConstants.PROP_CONFIG_SP_NEW_EID_MODE, false)) { -           -          // get VSZ -          String vsz = getVszForPerson(personInfo); -                                                          -          //write revision-Log entry and extended infos personal-identifier mapping -          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_VSZ_RECEIVED); -          writeExtendedRevisionLogEntry(simpleAttrMap, eidData); -           -           -          // get eIDAS bind -          String signedEidasBind = szrClient.getEidsaBind(vsz,  -              authBlockSigner.getBase64EncodedPublicKey(),  -              EID_STATUS, eidData); -          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_EIDASBIND_RECEIVED); -          authProcessData.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_EIDAS_BIND, signedEidasBind); -           -          //get signed AuthBlock -          String jwsSignature = authBlockSigner.buildSignedAuthBlock(pendingReq); -          revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.TECH_AUCHBLOCK_CREATED); -          authProcessData.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_SZR_AUTHBLOCK, jwsSignature); -           -          //inject personal-data into session -          authProcessData.setEidProcess(true); -                -        } else { -          //request SZR -          SzrResultHolder idlResult = requestSzrForIdentityLink(personInfo); -           -          //write revision-Log entry for personal-identifier mapping -          writeExtendedRevisionLogEntry(simpleAttrMap, eidData); -           -          //check result-data and write revision-log based on current state -          checkStateAndWriteRevisionLog(idlResult); -           -          //inject personal-data into session -          authProcessData.setIdentityLink(idlResult.getIdentityLink());   -          authProcessData.setEidProcess(false); -           -          // set bPK and bPKType into auth session -          authProcessData.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix( -              idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); -          authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, -                                                  pendingReq.getServiceProviderConfiguration() -                                                            .getAreaSpecificTargetIdentifier()); -           -        } +        executeIdlMode(eidData, matchedPersonData); +        } -       -      //add generic info's into session -      authProcessData.setForeigner(true); -      authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, EidasResponseUtils -          .parseEidasPersonalIdentifier((String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)) -          .getFirst()); -      authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); -            -      // store pending-request + +      storeGenericInfoToSession(eidData);        requestStoreage.storePendingRequest(pendingReq); -       -       +      } catch (final EidasAttributeException e) {        throw new TaskExecutionException(pendingReq, "Minimum required eIDAS attributeset not found.", e); @@ -221,338 +146,176 @@ public class CreateIdentityLinkTask extends AbstractAuthServletTask {      }    } +  private void storeGenericInfoToSession(SimpleEidasData eidData) throws EaafStorageException { +    AuthProcessDataWrapper authProcessData = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq); +    authProcessData.setForeigner(true); +    authProcessData.setGenericDataToSession(PvpAttributeDefinitions.EID_ISSUING_NATION_NAME, +        eidData.getCitizenCountryCode()); +  } -  private String getVszForPerson(PersonInfoType personInfo) throws SzrCommunicationException, EaafException { -    if (basicConfig.getBasicConfigurationBoolean( -        Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_IDA_VSZ_IDL, true)) { -      log.debug("IDA workaround is active. Requesting IDL to insert person into ERnP .... "); -       -      // work-around, because getEncryptedStammzahl does not support insertERnP for eIDAS entities -      SzrResultHolder idlResult = requestSzrForIdentityLink(personInfo); -                                    -      // get encrypted baseId -      return szrClient.getEncryptedStammzahl(buildGetEncryptedBaseIdReq(idlResult.identityLink)); -       -                  -    } else { -      return szrClient.getEncryptedStammzahl(personInfo, true);   -       -    } +  private void executeIdlMode(SimpleEidasData eidData, MatchedPersonResult matchedPersonData) throws EaafException { +    //request SZR +    SzrResultHolder idlResult = requestSzrForIdentityLink(eidData, matchedPersonData); + +    //write revision-Log entry for personal-identifier mapping +    writeExtendedRevisionLogEntry(eidData, eidData.getPersonalIdentifier()); +    //check result-data and write revision-log based on current state +    checkStateAndWriteRevisionLog(idlResult); + +    //inject personal-data into session +    AuthProcessDataWrapper authProcessDataWrapper = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq); +    authProcessDataWrapper.setIdentityLink(idlResult.getIdentityLink()); +    authProcessDataWrapper.setEidProcess(false); + +    // set bPK and bPKType into auth session +    authProcessDataWrapper.setGenericDataToSession(PvpAttributeDefinitions.BPK_NAME, extendBpkByPrefix( +        idlResult.getBpK(), pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier())); +    authProcessDataWrapper.setGenericDataToSession(PvpAttributeDefinitions.EID_SECTOR_FOR_IDENTIFIER_NAME, +        pendingReq.getServiceProviderConfiguration() +            .getAreaSpecificTargetIdentifier());    } -  private PersonInfoType buildGetEncryptedBaseIdReq(IIdentityLink identityLink) throws EaafBuilderException { -    log.debug("Generating getVsz request from identityLink information ... "); -    final PersonInfoType personInfo = new PersonInfoType(); -    final PersonNameType personName = new PersonNameType(); -    final PhysicalPersonType naturalPerson = new PhysicalPersonType(); -    IdentificationType bpk = new IdentificationType(); -     -    naturalPerson.setName(personName); -    personInfo.setPerson(naturalPerson); -    naturalPerson.setIdentification(bpk); -     -    // person information -    personName.setFamilyName(identityLink.getFamilyName()); -    personName.setGivenName(identityLink.getGivenName()); -    naturalPerson.setDateOfBirth(identityLink.getDateOfBirth()); -     -    final Pair<String, String> bpkCalc =  -        BpkBuilder.generateAreaSpecificPersonIdentifier( -            identityLink.getIdentificationValue(), -            identityLink.getIdentificationType(), -            EaafConstants.URN_PREFIX_CDID + "ZP");     -    bpk.setValue(bpkCalc.getFirst()); -    bpk.setType(bpkCalc.getSecond());         -    return personInfo; +  private void executeEidMode(SimpleEidasData eidData, MatchedPersonResult matchedPersonData) +      throws JsonProcessingException, EaafException, JoseException { +    // get encrypted baseId +    log.debug("Requesting encrypted baseId by already matched person information ... "); +    String vsz = szrClient.getEncryptedStammzahl(matchedPersonData); + +    //write revision-Log entry and extended infos personal-identifier mapping +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_VSZ_RECEIVED); +    writeExtendedRevisionLogEntry(eidData, eidData.getPersonalIdentifier()); + +    // get eIDAS bind +    String signedEidasBind = szrClient +        .getEidasBind(vsz, authBlockSigner.getBase64EncodedPublicKey(), EID_STATUS, eidData); +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_EIDASBIND_RECEIVED); +    AuthProcessDataWrapper authProcessDataWrapper = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq); +    authProcessDataWrapper.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_EIDAS_BIND, signedEidasBind); + +    //get signed AuthBlock +    String jwsSignature = authBlockSigner.buildSignedAuthBlock(pendingReq); +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.TECH_AUCHBLOCK_CREATED); +    authProcessDataWrapper.setGenericDataToSession(MsEidasNodeConstants.AUTH_DATA_SZR_AUTHBLOCK, jwsSignature); + +    //inject personal-data into session +    authProcessDataWrapper.setEidProcess(true);    } -  private void writeExtendedRevisionLogEntry(Map<String, Object> simpleAttrMap, ErnbEidData eidData) { -    // write ERnB input-data into revision-log +  private void writeExtendedRevisionLogEntry(SimpleEidasData eidData, String personalIdentifier) { +    // write ERnP input-data into revision-log      if (basicConfig.getBasicConfigurationBoolean(          Constants.CONIG_PROPS_EIDAS_SZRCLIENT_WORKAROUND_REVISIONLOGDATASTORE_ACTIVE, false)) { -      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, -                               (String) simpleAttrMap.get(Constants.eIDAS_ATTR_PERSONALIDENTIFIER)); +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_RAW_ID, personalIdentifier);        revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_ERNB_EIDAS_ERNB_ID, eidData.getPseudonym());      }    } -   -  private PersonInfoType generateSzrRequest(ErnbEidData eidData) { -    log.debug("Starting connecting SZR Gateway"); -    final PersonInfoType personInfo = new PersonInfoType(); -    final PersonNameType personName = new PersonNameType(); -    final PhysicalPersonType naturalPerson = new PhysicalPersonType(); -    final TravelDocumentType eDocument = new TravelDocumentType(); - -    naturalPerson.setName(personName); -    personInfo.setPerson(naturalPerson); -    personInfo.setTravelDocument(eDocument); - -    // person information -    personName.setFamilyName(eidData.getFamilyName()); -    personName.setGivenName(eidData.getGivenName()); -    naturalPerson.setDateOfBirth(eidData.getFormatedDateOfBirth()); -    eDocument.setIssuingCountry(eidData.getCitizenCountryCode()); -    eDocument.setDocumentNumber(eidData.getPseudonym()); - -    // eID document information -    eDocument.setDocumentType(basicConfig -                                  .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_EDOCUMENTTYPE, -                                                         Constants.SZR_CONSTANTS_DEFAULT_DOCUMENT_TYPE)); - -    // set PlaceOfBirth if available -    if (eidData.getPlaceOfBirth() != null) { -      log.trace("Find 'PlaceOfBirth' attribute: " + eidData.getPlaceOfBirth()); -      if (basicConfig -          .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETPLACEOFBIRTHIFAVAILABLE, -                                        true)) { -        naturalPerson.setPlaceOfBirth(eidData.getPlaceOfBirth()); -        log.trace("Adding 'PlaceOfBirth' to ERnB request ... "); - -      } -    } - -    // set BirthName if available -    if (eidData.getBirthName() != null) { -      log.trace("Find 'BirthName' attribute: " + eidData.getBirthName()); -      if (basicConfig -          .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_SETBIRTHNAMEIFAVAILABLE, -                                        true)) { -        final AlternativeNameType alternativeName = new AlternativeNameType(); -        naturalPerson.setAlternativeName(alternativeName); -        alternativeName.setFamilyName(eidData.getBirthName()); -        log.trace("Adding 'BirthName' to ERnB request ... "); - -      } -    } -     -    return personInfo; -     -  } -  private SzrResultHolder requestSzrForIdentityLink(PersonInfoType personInfo)  -      throws SzrCommunicationException, EaafException { +  private SzrResultHolder requestSzrForIdentityLink(SimpleEidasData eidData, +      MatchedPersonResult matchedPersonData) throws EaafException {      //request IdentityLink from SZR -    final IdentityLinkType result = szrClient.getIdentityLinkInRawMode(personInfo); -     +    log.debug("Requesting encrypted baseId by already matched person information ... "); +    IdentityLinkType result = szrClient.getIdentityLinkInRawMode(matchedPersonData); +      final Element idlFromSzr = (Element) result.getAssertion(); -    IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink(); +    final IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlFromSzr).parseIdentityLink();      // get bPK from SZR      String bpk = null; -    if (basicConfig -        .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true)) { -      List<String> bpkList = szrClient -          .getBpk(personInfo, pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(), -                  basicConfig -                      .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, "no VKZ defined")); +    String targetId = pendingReq.getServiceProviderConfiguration().getAreaSpecificTargetIdentifier(); +    boolean debugUseSzrForBpk = basicConfig +        .getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_DEBUG_USESRZFORBPKGENERATION, true); +    if (debugUseSzrForBpk) { +      String vkz = basicConfig +          .getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_SZRCLIENT_PARAMS_VKZ, "no VKZ defined"); +      List<String> bpkList = szrClient.getBpk(eidData, targetId, vkz);        if (!bpkList.isEmpty()) {          bpk = bpkList.get(0); -                } -         -      } else {        log.debug("Calculating bPK from baseId ... "); -      final Pair<String, String> bpkCalc = BpkBuilder -          .generateAreaSpecificPersonIdentifier(identityLink.getIdentificationValue(), -                                                identityLink.getIdentificationType(), -                                                pendingReq.getServiceProviderConfiguration() -                                                          .getAreaSpecificTargetIdentifier()); +      String idValue = identityLink.getIdentificationValue(); +      String idType = identityLink.getIdentificationType(); +      final Pair<String, String> bpkCalc = BpkBuilder.generateAreaSpecificPersonIdentifier(idValue, idType, targetId);        bpk = bpkCalc.getFirst(); - +            } -     +      return new SzrResultHolder(identityLink, bpk); -        } -   +    private void checkStateAndWriteRevisionLog(SzrResultHolder idlResult) throws SzrCommunicationException {      // write some infos into revision log      if (idlResult.getIdentityLink() == null) {        log.error("ERnB did not return an identity link.");        throw new SzrCommunicationException("ernb.00", null); -      } -    revisionsLogger.logEvent(pendingReq, -                             MsConnectorEventCodes.SZR_IDL_RECEIVED, -                             idlResult.getIdentityLink().getSamlAssertion() -                                         .getAttribute(SimpleIdentityLinkAssertionParser.ASSERTIONID)); + +    String assertionId = idlResult.getIdentityLink().getSamlAssertion() +        .getAttribute(SimpleIdentityLinkAssertionParser.ASSERTIONID); +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_IDL_RECEIVED, assertionId);      if (idlResult.getBpK() == null) {        log.error("ERnB did not return a bPK for target: " + pendingReq.getServiceProviderConfiguration() -                                                                     .getAreaSpecificTargetIdentifier()); +          .getAreaSpecificTargetIdentifier());        throw new SzrCommunicationException("ernb.01", null); -      } +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.SZR_BPK_RECEIVED);      log.debug("ERnB communication was successfull"); -            } -   -  private String extendBpkByPrefix(String bpk, String type) { -    String bpkType = null; - -    if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { -      bpkType = type.substring(EaafConstants.URN_PREFIX_WBPK.length()); -    } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { -      bpkType = type.substring(EaafConstants.URN_PREFIX_CDID.length()); -    } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { -      bpkType = type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); -    } +  private String extendBpkByPrefix(String bpk, String type) { +    String bpkType = getBpkType(type);      if (bpkType != null) {        log.trace("Authenticate user with bPK/wbPK " + bpk + " and Type=" + bpkType);        return bpkType + ":" + bpk; -      } else {        log.warn("Service Provider Target with: " + type + " is NOT supported. Set bPK as it is ...");        return bpk; -      } -    } -  private Map<String, Object> convertEidasAttrToSimpleMap( -      ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap) { -    final Map<String, Object> result = new HashMap<>(); - -    for (final AttributeDefinition<?> el : attributeMap.keySet()) { - -      final Class<?> parameterizedType = el.getParameterizedType(); -      if (DateTime.class.equals(parameterizedType)) { -        final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el).asList()); -        if (attribute != null) { -          result.put(el.getFriendlyName(), attribute); -          log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + attribute.toString()); - -        } else { -          log.info("Ignore empty 'DateTime' attribute"); -        } - -      } else if (PostalAddress.class.equals(parameterizedType)) { -        final PostalAddress addressAttribute = EidasResponseUtils -            .translateAddressAttribute(el, attributeMap.get(el).asList()); -        if (addressAttribute != null) { -          result.put(el.getFriendlyName(), addressAttribute); -          log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + addressAttribute.toString()); - -        } else { -          log.info("Ignore empty 'PostalAddress' attribute"); -        } - -      } else { -        final List<String> natPersonIdObj = EidasResponseUtils -            .translateStringListAttribute(el, attributeMap.get(el)); -        if (natPersonIdObj.isEmpty()) { -          log.info("Ignore attribute: {}, because no attributeValue was found", -              el.getNameUri());           -           -        } else { -          final String stringAttr = natPersonIdObj.get(0); -          if (StringUtils.isNotEmpty(stringAttr)) { -            result.put(el.getFriendlyName(), stringAttr); -            log.trace("Find attr '" + el.getFriendlyName() + "' with value: " + stringAttr); - -          } else { -            log.info("Ignore empty 'String' attributeValue for: {}", -                el.getNameUri()); -             -          } - -        } -      } +  @Nullable +  private String getBpkType(String type) { +    if (type.startsWith(EaafConstants.URN_PREFIX_WBPK)) { +      return type.substring(EaafConstants.URN_PREFIX_WBPK.length()); +    } else if (type.startsWith(EaafConstants.URN_PREFIX_CDID)) { +      return type.substring(EaafConstants.URN_PREFIX_CDID.length()); +    } else if (type.startsWith(EaafConstants.URN_PREFIX_EIDAS)) { +      return type.substring(EaafConstants.URN_PREFIX_EIDAS.length()); +    } else { +      return null;      } - -    log.debug("Receive #" + result.size() + " attributes with names: " + result.keySet().toString()); - -    return result;    } -  private void writeMdsLogInformation(ErnbEidData eidData) { -    // log MDS and country code into technical log -    if (basicConfig -        .getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_TECHNICALLOG_WRITE_MDS_INTO_TECH_LOG, false)) { + +  /** +   * write MDS into technical log and revision log. +   */ +  private void writeMdsLogInformation(SimpleEidasData eidData) { +    boolean writeMdsInTechLog = basicConfig +        .getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_TECHNICALLOG_WRITE_MDS_INTO_TECH_LOG, false); +    if (writeMdsInTechLog) {        log.info("eIDAS Auth. for user: " + eidData.getGivenName() + " " + eidData.getFamilyName() + " " + eidData -          .getFormatedDateOfBirth() + " " + "from " + eidData.getCitizenCountryCode()); +          .getDateOfBirth() + " " + "from " + eidData.getCitizenCountryCode());      } -    // log MDS and country code into revision log -    if (basicConfig +    boolean writeMdsInRevLog = basicConfig          .getBasicConfigurationBoolean(MsEidasNodeConstants.PROP_CONFIG_REVISIONLOG_WRITE_MDS_INTO_REVISION_LOG, -                                      false)) { +            false); +    if (writeMdsInRevLog) {        revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_MDSDATA, -                               "{" + eidData.getGivenName() + "," + eidData.getFamilyName() + "," + eidData -                                   .getFormatedDateOfBirth() + "," + eidData.getCitizenCountryCode() + "}"); +          "{" + eidData.getGivenName() + "," + eidData.getFamilyName() + "," + eidData +              .getDateOfBirth() + "," + eidData.getCitizenCountryCode() + "}");      } -    } -   +    @Data -  private static class SzrResultHolder {     +  private static class SzrResultHolder {      final IIdentityLink identityLink;      final String bpK; -     -  } -     -  /** -   * Build a dummy IdentityLink and a dummy bPK based on eIDAS information. -   *  -   * <br><br> -   * <b>FOR LOCAL TESTING ONLY!!!</b> -   *  -   * @param eidData Information from eIDAS response -   * @return IdentityLink and bPK -   * @throws ParserConfigurationException In case of an IDL processing error -   * @throws SAXException In case of an IDL processing error -   * @throws IOException In case of an IDL processing error -   * @throws EaafException In case of a bPK generation error -   */ -  private SzrResultHolder createDummyIdentityLinkForTestDeployment(ErnbEidData eidData)  -      throws ParserConfigurationException, SAXException, IOException, EaafException { -    log.warn("SZR-Dummy IS ACTIVE! IdentityLink is NOT VALID!!!!"); -    // create fake IdL -    // - fetch IdL template from resources -    final InputStream s = CreateIdentityLinkTask.class -        .getResourceAsStream("/resources/xmldata/fakeIdL_IdL_template.xml"); -    final Element idlTemplate = DomUtils.parseXmlValidating(s); - -    IIdentityLink identityLink = new SimpleIdentityLinkAssertionParser(idlTemplate).parseIdentityLink(); - -    // replace data -    final Element idlassertion = identityLink.getSamlAssertion(); - -    // - set fake baseID; -    final Node prIdentification = XPathUtils -        .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_IDENT_VALUE_XPATH); -    prIdentification.getFirstChild().setNodeValue(eidData.getPseudonym()); - -    // - set last name -    final Node prFamilyName = XPathUtils -        .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_FAMILY_NAME_XPATH); -    prFamilyName.getFirstChild().setNodeValue(eidData.getFamilyName()); - -    // - set first name -    final Node prGivenName = XPathUtils -        .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_GIVEN_NAME_XPATH); -    prGivenName.getFirstChild().setNodeValue(eidData.getGivenName()); - -    // - set date of birth -    final Node prDateOfBirth = XPathUtils -        .selectSingleNode(idlassertion, SimpleIdentityLinkAssertionParser.PERSON_DATE_OF_BIRTH_XPATH); - -    prDateOfBirth.getFirstChild().setNodeValue(eidData.getFormatedDateOfBirth()); - -    identityLink = new SimpleIdentityLinkAssertionParser(idlassertion).parseIdentityLink(); - -    final Pair<String, String> bpkCalc = BpkBuilder -        .generateAreaSpecificPersonIdentifier(identityLink.getIdentificationValue(), -                                              identityLink.getIdentificationType(), -                                              pendingReq.getServiceProviderConfiguration() -                                                        .getAreaSpecificTargetIdentifier());    -    return new SzrResultHolder(identityLink, bpkCalc.getFirst()); -     -  } +  }  } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateNewErnpEntryTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateNewErnpEntryTask.java new file mode 100644 index 00000000..c7843be5 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/CreateNewErnpEntryTask.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.ErnpRestClient; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.clients.ernp.ErnpRestClient.ErnpRegisterResult; +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.WorkflowException; +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.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import lombok.extern.slf4j.Slf4j; + +/** + * Task that searches ERnP and ZMR before adding person to SZR. + * This corresponds to Step 9 in the eIDAS Matching Concept. + * Input: + * <ul> + *   <li>{@link Constants#DATA_SIMPLE_EIDAS}</li> + * </ul> + * Output: + * <ul> + *   <li>TODO MDS, BPK of new entry</li> + * </ul> + * + * @author tlenz + * @author amarsalek + * @author ckollmann + */ +@Slf4j +@Component("CreateNewErnbEntryTask") +public class CreateNewErnpEntryTask extends AbstractAuthServletTask { + +  private final ErnpRestClient ernpClient; + +  /** +   * Constructor. +   * @param client SZR client for creating a new ERnP entry +   */ +  public CreateNewErnpEntryTask(@Autowired ErnpRestClient client) { +    this.ernpClient = client; +  } + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { +    try { +      SimpleEidasData simpleEidasData = MatchingTaskUtils.getInitialEidasData(pendingReq);             +      if (simpleEidasData == null) { +        throw new WorkflowException("step09", "No initial eIDAS authn data", true); +         +      } +       +      //add person into ERnP +      ErnpRegisterResult resp = ernpClient.add(simpleEidasData); +      if (resp.getPersonResult().size() != 1) { +        log.error("Receive {} from ERnP during 'add person' step",  +            resp.getPersonResult().isEmpty() ? "no result" : "more-than-one result"); +        throw new WorkflowException("step09", "Add person into ERnP failed", true); +         +      } +            +      // finish matching process, because new user-entry uniquly matches +      log.info("User successfully registerred into ERnP and matching tasks are finished "); +      MatchingTaskUtils.storeFinalMatchingResult(pendingReq,  +          MatchedPersonResult.generateFormMatchingResult( +              resp.getPersonResult().get(0), simpleEidasData.getCitizenCountryCode())); + +    } catch (final Exception e) { +      log.error("Initial search FAILED.", e); +      throw new TaskExecutionException(pendingReq, "Initial search FAILED.", e); +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAustrianResidenceGuiTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAustrianResidenceGuiTask.java new file mode 100644 index 00000000..dc57dd78 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAustrianResidenceGuiTask.java @@ -0,0 +1,105 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.gui.AbstractGuiFormBuilderConfiguration; +import at.gv.egiz.eaaf.core.impl.idp.controller.tasks.AbstractLocaleAuthServletTask; +import lombok.extern.slf4j.Slf4j; + +/** + * Show GUI where user can provide an Austrian residence address, to provide further input to match the identity. + * + * @author ckollmann + */ +@Slf4j +@Component("GenerateAustrianResidenceGuiTask") +public class GenerateAustrianResidenceGuiTask extends AbstractLocaleAuthServletTask { + +  public static final String PARAM_FORMWIZARDPOINT = "wizardEndpoint"; +   +  @Autowired +  private ISpringMvcGuiFormBuilder guiBuilder; +  @Autowired +  private IConfiguration basicConfig; + +  @Override +  public void executeWithLocale(ExecutionContext executionContext,  +      HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { +    try { +      final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( +          basicConfig, +          pendingReq, +          basicConfig.getBasicConfiguration( +              MsEidasNodeConstants.PROP_CONFIG_WEBCONTENT_TEMPLATES_RESIDENCY, +              MsEidasNodeConstants.TEMPLATE_HTML_RESIDENCY), +          MsEidasNodeConstants.ENDPOINT_RESIDENCY_INPUT, +          resourceLoader); + +      // inject REST end-point for wizard +      config.putCustomParameterWithOutEscaption(null,  +          PARAM_FORMWIZARDPOINT,  +          pendingReq.getAuthUrl() + MsEidasNodeConstants.ENDPOINT_RESIDENCY_SEARCH); +       +       +      // inject flag to indicate advanced matching error +      if (MatchingTaskUtils.getExecutionContextFlag( +          executionContext, Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)) { +        config.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_UIOPTIONS, +            Constants.HTML_FORM_ADVANCED_MATCHING_FAILED, String.valueOf(true)); +         +        //set detailed error-code +        if (executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON) != null) { +          config.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_UIOPTIONS, +              Constants.HTML_FORM_ADVANCED_MATCHING_FAILED_REASON,  +              executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON).toString()); +        }        +      } +             +      // reset executionContext parameters +      ReceiveOtherLoginMethodGuiResponseTask.ALL_EXECUTIONCONTEXT_PARAMETERS.forEach( +          el -> executionContext.remove(el)); +       +      guiBuilder.build(request, response, config, "Query Austrian residency"); + +    } catch (final Exception e) { +      log.error("Initial search FAILED.", e); +      throw new TaskExecutionException(pendingReq, "Gui creation FAILED.", e); +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java index 0f1b5432..849f8136 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateAuthnRequestTask.java @@ -19,10 +19,11 @@   * file for details on the various modules and licenses.   * The "NOTICE" text file is part of the distribution. Any derivative works   * that you distribute must include a readable copy of the "NOTICE" text file. -*/ + */  package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; +import java.io.IOException;  import java.util.UUID;  import javax.servlet.ServletException; @@ -30,6 +31,7 @@ import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;  import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.context.ApplicationContext;  import org.springframework.stereotype.Component; @@ -39,6 +41,7 @@ import at.asitplus.eidas.specific.core.MsConnectorEventCodes;  import at.asitplus.eidas.specific.core.MsEidasNodeConstants;  import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService;  import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; @@ -47,6 +50,7 @@ import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext;  import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage;  import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException;  import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.GuiBuildException;  import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException;  import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask;  import eu.eidas.auth.commons.EidasParameterKeys; @@ -59,149 +63,168 @@ import eu.eidas.specificcommunication.exception.SpecificCommunicationException;  import eu.eidas.specificcommunication.protocol.SpecificCommunicationService;  import lombok.extern.slf4j.Slf4j; +  /** - * Authentication-process task that generates the Authn. Request to eIDAS Node. - *  - * @author tlenz + * Generates the authn request to the eIDAS Node. This is the first task in the process. + * Input: + * <ul> + *     <li>none</li> + * </ul> + * Output: + * <ul> + *     <li>none</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks.ReceiveAuthnResponseTask} + *     to read the response from the eIDAS Node</li> + * </ul>   * + * @author tlenz + * @author ckollmann   */  @Slf4j -@Component("ConnecteIDASNodeTask") +@Component("GenerateAuthnRequestTask")  public class GenerateAuthnRequestTask extends AbstractAuthServletTask { +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")    @Autowired    IConfiguration basicConfig; +    @Autowired    ApplicationContext context; + +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")    @Autowired    ITransactionStorage transactionStore; + +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")    @Autowired    ISpringMvcGuiFormBuilder guiBuilder; +    @Autowired    ICcSpecificEidProcessingService ccSpecificProcessing;    @Override -  public void execute(ExecutionContext executionContext, -      HttpServletRequest request, HttpServletResponse response) +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response)        throws TaskExecutionException { -      try { -      // get target, environment and validate citizen countryCode -      final String citizenCountryCode = (String) executionContext.get( -          MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); -      final String environment = (String) executionContext.get( -          MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT); - -      if (StringUtils.isEmpty(citizenCountryCode)) { -        // illegal state; task should not have been executed without a selected country -        throw new EidasSAuthenticationException("eidas.03", new Object[] { "" }); - -      } - -      // TODO: maybe add countryCode validation before request ref. impl. eIDAS node -      log.info("Request eIDAS auth. for citizen of country: " + citizenCountryCode); -      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.COUNTRY_SELECTED, citizenCountryCode); - -      // build eIDAS AuthnRequest -      final LightRequest.Builder authnRequestBuilder = LightRequest.builder(); -      authnRequestBuilder.id(UUID.randomUUID().toString()); - -      // set nameIDFormat -      authnRequestBuilder.nameIdFormat( -          authConfig.getBasicConfiguration(Constants.CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT)); - -      // set citizen country code for foreign uses -      authnRequestBuilder.citizenCountryCode(citizenCountryCode); -       -      //set Issuer -      final String issur = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID); -      if (StringUtils.isEmpty(issur)) { -        log.error("Found NO 'eIDAS node issuer' in configuration. Authentication NOT possible!"); -        throw new EaafConfigurationException("config.27", -            new Object[] { "Application config containts NO " + Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID }); - -      } -      authnRequestBuilder.issuer(issur); - +      final String citizenCountryCode = extractCitizenCountryCode(executionContext); +      final String environment = (String) executionContext.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT); +      final String issuer = loadIssuerFromConfig(); +      +      // inject MS-Connector staging parameters +      injectStagingWorkaroundForMsConnector(); -      // Add country-specific informations into eIDAS request -      ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, authnRequestBuilder); -                  -      // build request -      final LightRequest lightAuthnReq = authnRequestBuilder.build(); - -      // put request into shared cache +      final LightRequest lightAuthnReq = buildEidasAuthnRequest(citizenCountryCode, issuer); +             final BinaryLightToken token = putRequestInCommunicationCache(lightAuthnReq);        final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); +      workaroundRelayState(lightAuthnReq); +      final String forwardUrl = selectForwardUrl(environment); -      // Workaround for ms-connector staging -      injectStagingWorkaroundForMsConnector(); -       -      // Workaround, because eIDAS node ref. impl. does not return relayState -      if (basicConfig.getBasicConfigurationBoolean( -          Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, -          false)) { -        log.trace("Put lightRequestId into transactionstore as session-handling backup"); -        transactionStore.put(lightAuthnReq.getId(), pendingReq.getPendingRequestId(), -1); - -      } - -      // select forward URL regarding the selected environment -      String forwardUrl = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); -      if (StringUtils.isNotEmpty(environment)) { -        forwardUrl = selectedForwardUrlForEnvironment(environment); -      } - -      if (StringUtils.isEmpty(forwardUrl)) { -        log.warn("NO ForwardURL defined in configuration. Can NOT forward to eIDAS node! Process stops"); -        throw new EaafConfigurationException("config.08", new Object[] { -            environment == null ? Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL -                : Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + environment -        }); - -      } -      log.debug("ForwardURL: " + forwardUrl + " selected to forward eIDAS request"); - -      if (basicConfig.getBasicConfiguration( -          Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, -          Constants.FORWARD_METHOD_GET).equals(Constants.FORWARD_METHOD_GET)) { - -        log.debug("Use http-redirect for eIDAS node forwarding ...  "); -        // send redirect -        final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(forwardUrl); -        redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); -        response.sendRedirect(redirectUrl.build().encode().toString()); +      String configValue = basicConfig.getBasicConfiguration( +          Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, Constants.FORWARD_METHOD_GET); +      boolean useHttpRedirect = configValue.equals(Constants.FORWARD_METHOD_GET); +      if (useHttpRedirect) { +        sendRedirect(response, tokenBase64, forwardUrl);        } else { -        log.debug("Use http-post for eIDAS node forwarding ...  "); -        final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( -            basicConfig, -            pendingReq, -            Constants.TEMPLATE_POST_FORWARD_NAME, -            null, -            resourceLoader); - -        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardUrl); -        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME, -            EidasParameterKeys.TOKEN.toString()); -        config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, -            tokenBase64); - -        guiBuilder.build(request, response, config, "Forward to eIDASNode form"); - +        sendPost(request, response, tokenBase64, forwardUrl);        }        revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.EIDAS_NODE_CONNECTED, lightAuthnReq.getId()); -      } catch (final EidasSAuthenticationException e) {        throw new TaskExecutionException(pendingReq, "eIDAS AuthnRequest generation FAILED.", e); -      } catch (final Exception e) {        log.warn("eIDAS AuthnRequest generation FAILED.", e);        throw new TaskExecutionException(pendingReq, e.getMessage(), e); +    } +  } +  @NotNull +  private String extractCitizenCountryCode(ExecutionContext executionContext) throws EidasSAuthenticationException { +    final String result = (String) executionContext.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +    // illegal state; task should not have been executed without a selected country +    if (StringUtils.isEmpty(result)) { +      throw new EidasSAuthenticationException("eidas.03", new Object[]{""});      } +    // TODO: maybe add countryCode validation before request ref. impl. eIDAS node +    log.info("Request eIDAS auth. for citizen of country: {}", result); +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.COUNTRY_SELECTED, result); +    return result; +  } + +  @NotNull +  private String loadIssuerFromConfig() throws EaafConfigurationException { +    final String result = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID); +    if (StringUtils.isEmpty(result)) { +      log.error("Found NO 'eIDAS node issuer' in configuration. Authentication NOT possible!"); +      throw new EaafConfigurationException("config.27", +          new Object[]{"Application config containts NO " + Constants.CONIG_PROPS_EIDAS_NODE_ENTITYID}); +    } +    return result; +  } + +  @NotNull +  private LightRequest buildEidasAuthnRequest(String citizenCountryCode, String issuer) +      throws EidPostProcessingException { +    final LightRequest.Builder builder = LightRequest.builder(); +    builder.id(UUID.randomUUID().toString()); +     +    // set nameIDFormat +    builder.nameIdFormat( +        authConfig.getBasicConfiguration(Constants.CONFIG_PROP_EIDAS_NODE_NAMEIDFORMAT)); + +    builder.citizenCountryCode(citizenCountryCode); +    builder.issuer(issuer); +    // Add country-specific information into eIDAS request +    ccSpecificProcessing.preProcess(citizenCountryCode, pendingReq, builder); +    return builder.build(); +  } + +  private BinaryLightToken putRequestInCommunicationCache(ILightRequest lightRequest) +      throws ServletException { +    final BinaryLightToken binaryLightToken; +    try { +      String beanName = SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString(); +      final SpecificCommunicationService service = (SpecificCommunicationService) context.getBean(beanName); +      binaryLightToken = service.putRequest(lightRequest); +    } catch (final SpecificCommunicationException e) { +      log.error("Unable to process specific request"); +      throw new ServletException(e); +    } + +    return binaryLightToken; +  } + +  /** +   * Workaround, because eIDAS node ref. impl. does not return relayState +   */ +  private void workaroundRelayState(LightRequest lightAuthnReq) throws EaafException { +    if (basicConfig.getBasicConfigurationBoolean( +        Constants.CONIG_PROPS_EIDAS_NODE_WORKAROUND_USEREQUESTIDASTRANSACTIONIDENTIFIER, +        false)) { +      log.trace("Put lightRequestId into transactionstore as session-handling backup"); +      transactionStore.put(lightAuthnReq.getId(), pendingReq.getPendingRequestId(), -1); +    } +  } +  @NotNull +  private String selectForwardUrl(String environment) throws EaafConfigurationException { +    String result = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL); +    if (StringUtils.isNotEmpty(environment)) { +      result = selectedForwardUrlForEnvironment(environment); +    } +    if (StringUtils.isEmpty(result)) { +      log.warn("NO ForwardURL defined in configuration. Can NOT forward to eIDAS node! Process stops"); +      throw new EaafConfigurationException("config.08", new Object[]{ +          environment == null ? Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL +              : Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL + "." + environment +      }); +    } +    log.debug("ForwardURL: {} selected to forward eIDAS request", result); +    return result;    } @@ -224,51 +247,48 @@ public class GenerateAuthnRequestTask extends AbstractAuthServletTask {     * <br>     * <b>Info: </b> This method is needed, because eIDAS Ref. Impl only supports     * one countrycode on each instance. In consequence, more than one eIDAS Ref. -   * Impl nodes are required to support producation, testing, or QS stages for one +   * Impl nodes are required to support production, testing, or QS stages for one     * country by using one ms-specific eIDAS connector -   *  +   *     * @param environment Environment selector from CountrySlection page -   * @return +   * @return the URL from the configuration     */    private String selectedForwardUrlForEnvironment(String environment) { -    log.trace("Starting endpoint selection process for environment: " + environment + " ... "); +    log.trace("Starting endpoint selection process for environment: {} ... ", environment);      if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_PRODUCTION)) {        return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL);      } else if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS)) {        return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL            + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_QS); -    } else if (environment.equalsIgnoreCase( -        MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING)) { +    } else if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING)) {        return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL            + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_TESTING); -    } else if (environment.equalsIgnoreCase( -        MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT)) { +    } else if (environment.equalsIgnoreCase(MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT)) {        return basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_FORWARD_URL            + "." + MsEidasNodeConstants.REQ_PARAM_SELECTED_ENVIRONMENT_VALUE_DEVELOPMENT);      } - -    log.info("Environment selector: " + environment + " is not supported"); +    log.info("Environment selector: {} is not supported", environment);      return null; -    } -  private BinaryLightToken putRequestInCommunicationCache(ILightRequest lightRequest) -      throws ServletException { -    final BinaryLightToken binaryLightToken; -    try { -      final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = -          (SpecificCommunicationService) context.getBean( -              SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); - -      binaryLightToken = springManagedSpecificConnectorCommunicationService.putRequest(lightRequest); - -    } catch (final SpecificCommunicationException e) { -      log.error("Unable to process specific request"); -      throw new ServletException(e); - -    } +  private void sendRedirect(HttpServletResponse response, String tokenBase64, String forwardUrl) throws IOException { +    log.debug("Use http-redirect for eIDAS node forwarding ...  "); +    final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(forwardUrl); +    redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); +    response.sendRedirect(redirectUrl.build().encode().toString()); +     +  } -    return binaryLightToken; +  private void sendPost(HttpServletRequest request, HttpServletResponse response, String tokenBase64, String forwardUrl) +      throws GuiBuildException { +    log.debug("Use http-post for eIDAS node forwarding ...  "); +    final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( +        basicConfig, pendingReq, Constants.TEMPLATE_POST_FORWARD_NAME, null, resourceLoader); +    config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardUrl); +    String token = EidasParameterKeys.TOKEN.toString(); +    config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_NAME, token); +    config.putCustomParameter(null, Constants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, tokenBase64); +    guiBuilder.build(request, response, config, "Forward to eIDASNode form");    }  } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateMobilePhoneSignatureRequestTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateMobilePhoneSignatureRequestTask.java new file mode 100644 index 00000000..26282d5c --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateMobilePhoneSignatureRequestTask.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import java.text.MessageFormat; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthRequestBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthCredentialProvider; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthMetadataProvider; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.storage.ITransactionStorage; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.core.impl.utils.Random; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.sp.impl.PvpAuthnRequestBuilder; +import lombok.extern.slf4j.Slf4j; +import net.shibboleth.utilities.java.support.resolver.ResolverException; +import net.shibboleth.utilities.java.support.security.impl.SecureRandomIdentifierGenerationStrategy; + +/** + * Generate a SAML2 AuthnRequest to authenticate the user at ID Austria system. + * This corresponds to Step 15A in the eIDAS Matching Concept. + * + * @author tlenz + */ +@Slf4j +@Component("GenerateMobilePhoneSignatureRequestTask") +public class GenerateMobilePhoneSignatureRequestTask extends AbstractAuthServletTask { + +  private static final String ERROR_MSG_1 = +      "Requested 'ms-specific eIDAS node' {0} has no valid metadata or metadata is not found"; + +  @Autowired +  PvpAuthnRequestBuilder authnReqBuilder; +  @Autowired +  IdAustriaClientAuthCredentialProvider credential; +  @Autowired +  IdAustriaClientAuthMetadataProvider metadataService; +  @Autowired +  private IConfiguration basicConfig; +  @Autowired +  protected ITransactionStorage transactionStorage; + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { +    try { +      log.trace("Starting GenerateMobilePhoneSignatureRequestTask"); +      final String entityId = loadEntityId(); +      final EntityDescriptor entityDesc = loadEntityDescriptor(entityId); +      final IdAustriaClientAuthRequestBuilderConfiguration authnReqConfig = buildAuthnRequestConfig(entityDesc); +      final String relayState = buildRelayState(); +      authnReqBuilder.buildAuthnRequest(pendingReq, authnReqConfig, relayState, response); // also transmits! +    } catch (final Exception e) { +      throw new TaskExecutionException(pendingReq, "Generation of SAML2 AuthnRequest to ID Austria System FAILED", e); +    } +  } + +  @NotNull +  private String loadEntityId() throws EaafConfigurationException { +    final String msNodeEntityID = basicConfig.getBasicConfiguration( +        IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID); +    if (StringUtils.isEmpty(msNodeEntityID)) { +      log.warn("ID Austria authentication not possible -> NO EntityID for ID Austria System FOUND!"); +      throw new EaafConfigurationException(Constants.ERRORCODE_00, +          new Object[]{IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID}); +    } +    return msNodeEntityID; +  } + +  /** +   * Build relayState for session synchronization, because SAML2 only allows RelayState with 80 characters +   * but encrypted PendingRequestId is much longer. +   */ +  @NotNull +  private String buildRelayState() throws EaafException { +    String relayState = Random.nextProcessReferenceValue(); +    transactionStorage.put(relayState, pendingReq.getPendingRequestId(), -1); +    return relayState; +  } + +  @NotNull +  private EntityDescriptor loadEntityDescriptor(String msNodeEntityID) +      throws ResolverException, EaafConfigurationException { +    final EntityDescriptor entityDesc = metadataService.getEntityDescriptor(msNodeEntityID); +    if (entityDesc == null) { +      throw new EaafConfigurationException(IdAustriaClientAuthConstants.ERRORCODE_02, +          new Object[]{MessageFormat.format(ERROR_MSG_1, msNodeEntityID)}); + +    } +    return entityDesc; +  } + +  @NotNull +  private IdAustriaClientAuthRequestBuilderConfiguration buildAuthnRequestConfig(EntityDescriptor entityDesc) +      throws CredentialsNotAvailableException { +    final IdAustriaClientAuthRequestBuilderConfiguration authnReqConfig = +        new IdAustriaClientAuthRequestBuilderConfiguration(); +    final SecureRandomIdentifierGenerationStrategy gen = new SecureRandomIdentifierGenerationStrategy(); +    authnReqConfig.setRequestId(gen.generateIdentifier()); +    authnReqConfig.setIdpEntity(entityDesc); +    authnReqConfig.setPassive(false); +    authnReqConfig.setSignCred(credential.getMessageSigningCredential()); +    authnReqConfig.setSpEntityID( +        pendingReq.getAuthUrlWithOutSlash() + IdAustriaClientAuthConstants.ENDPOINT_METADATA); +    return authnReqConfig; +     +  } +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateOtherLoginMethodGuiTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateOtherLoginMethodGuiTask.java new file mode 100644 index 00000000..a90c5929 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/GenerateOtherLoginMethodGuiTask.java @@ -0,0 +1,112 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.gui.AbstractGuiFormBuilderConfiguration; +import at.gv.egiz.eaaf.core.impl.idp.controller.tasks.AbstractLocaleAuthServletTask; +import lombok.extern.slf4j.Slf4j; + +/** + * Task that provides GUI for user to select an alternative login method. + * This page is shown when the matching of the eIDAS data to ZMR/ERnP data is ambiguous. + * This corresponds to Steps 10, 14, 16 in the eIDAS Matching Concept. + * The response is handled in {@link ReceiveOtherLoginMethodGuiResponseTask} + * + * @author amarsalek + * @author ckollmann + */ +@Slf4j +@Component("GenerateOtherLoginMethodGuiTask") +public class GenerateOtherLoginMethodGuiTask extends AbstractLocaleAuthServletTask { + +  @Autowired +  private ISpringMvcGuiFormBuilder guiBuilder; + +  @Autowired +  private IConfiguration basicConfig; + +  @Override +  public void executeWithLocale(ExecutionContext executionContext,  +      HttpServletRequest request, HttpServletResponse response) throws TaskExecutionException { +    try { +      final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( +          basicConfig, +          pendingReq, +          basicConfig.getBasicConfiguration( +              MsEidasNodeConstants.PROP_CONFIG_WEBCONTENT_TEMPLATES_OTHER_LOGIN_METHOD_SELECTION, +              MsEidasNodeConstants.TEMPLATE_HTML_OTHERLOGINMETHODS), +          MsEidasNodeConstants.ENDPOINT_OTHER_LOGIN_METHOD_SELECTION, +          resourceLoader); + +      // inject flag to indicate advanced matching error +      if (MatchingTaskUtils.getExecutionContextFlag( +          executionContext, Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED)) { +        config.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_UIOPTIONS, +            Constants.HTML_FORM_ADVANCED_MATCHING_FAILED, String.valueOf(true)); +         +        //set detailed error-code +        if (executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON) != null) { +          config.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_UIOPTIONS, +              Constants.HTML_FORM_ADVANCED_MATCHING_FAILED_REASON,  +              executionContext.get(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON).toString()); +        }        +      } +       +      // inject request to create a new ERnP entry +      config.putCustomParameter(AbstractGuiFormBuilderConfiguration.PARAM_GROUP_UIOPTIONS, +          Constants.HTML_FORM_CREATE_NEW_ERNP_ENTRY,  +          String.valueOf( +              MatchingTaskUtils.getExecutionContextFlag( +                  executionContext,Constants.TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK))); +                         +      // reset executionContext parameters +      ReceiveOtherLoginMethodGuiResponseTask.ALL_EXECUTIONCONTEXT_PARAMETERS.forEach( +          el -> executionContext.remove(el)); +                   +      // store pending request before next step +      requestStoreage.storePendingRequest(pendingReq); +       +      guiBuilder.build(request, response, config, "Other login methods selection form"); + +    } catch (final Exception e) { +      log.error("Initial search FAILED.", e); +      throw new TaskExecutionException(pendingReq, "Gui creation FAILED.", e); +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/InitialSearchTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/InitialSearchTask.java new file mode 100644 index 00000000..3a775837 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/InitialSearchTask.java @@ -0,0 +1,211 @@ +/* + * Copyright 2020 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK; + +import java.util.Map; + +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.RegisterResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidPostProcessingException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasAttributeException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +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.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 eu.eidas.auth.commons.light.ILightResponse; +import lombok.extern.slf4j.Slf4j; + +/** + * Searches registers (ERnP and ZMR) after initial user auth, before adding person to SZR. + * Input: + * <ul> + *     <li>{@link Constants#DATA_FULL_EIDAS_RESPONSE}</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#DATA_SIMPLE_EIDAS} converted from Full eIDAS Response</li> + *     <li>{@link Constants#DATA_INTERMEDIATE_RESULT} results from first search in registers with + *     PersonIdentifier</li> + *     <li>{@link Constants#DATA_PERSON_MATCH_RESULT} results after second search in registers with MDS</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link CreateNewErnpEntryTask} if no results in registers where found for this user</li> + *     <li>{@link GenerateOtherLoginMethodGuiTask} if search with MDS returns more than one match, user may provide + *     alternative login methods to get an unique match</li> + *     <li>{@link CreateIdentityLinkTask} if search in register returned one match, user is uniquely identified</li> + * </ul> + * + * @author amarsalek + * @author ckollmann + * @author tlenz + */ +@Slf4j +@Component("InitialSearchTask") +@SuppressWarnings("PMD.TooManyStaticImports") +public class InitialSearchTask extends AbstractAuthServletTask { + +  private final RegisterSearchService registerSearchService; +  private final ICcSpecificEidProcessingService eidPostProcessor; + +  /** +   * Constructor. +   * +   * @param registerSearchService Service for register search access +   * @param eidPostProcessor      Country-Specific post processing of attributes +   */ +  public InitialSearchTask(RegisterSearchService registerSearchService, +                           ICcSpecificEidProcessingService eidPostProcessor) { +    this.registerSearchService = registerSearchService; +    this.eidPostProcessor = eidPostProcessor; +  } + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { +    try { +      final SimpleEidasData eidasData = convertEidasAttrToSimpleData(); +      MatchingTaskUtils.storeInitialEidasData(pendingReq, eidasData); +      step2RegisterSearchWithPersonIdentifier(executionContext, eidasData); +    } 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 step2RegisterSearchWithPersonIdentifier( +      ExecutionContext executionContext, SimpleEidasData eidasData) throws WorkflowException, EaafStorageException { +    try { +      log.trace("Starting step2RegisterSearchWithPersonIdentifier"); +      RegisterStatusResults searchResult = registerSearchService.searchWithPersonIdentifier(eidasData); +      int resultCount = searchResult.getResultCount(); +      if (resultCount == 0) { +        step6CountrySpecificSearch(executionContext, searchResult.getOperationStatus(), eidasData); + +      } else if (resultCount == 1) {         +        RegisterResult updatedResult = step3CheckRegisterUpdateNecessary(searchResult, eidasData);         +        foundMatchFinalizeTask(updatedResult, eidasData); + +      } else { +        throw new WorkflowException("step2RegisterSearchWithPersonIdentifier", +            "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 step6CountrySpecificSearch( +      ExecutionContext executionContext, RegisterOperationStatus registerOperationStatus, SimpleEidasData eidasData) +      throws EaafStorageException, WorkflowException { +    log.trace("Starting 'step6CountrySpecificSearch' ... "); +    RegisterStatusResults searchResult = registerSearchService.searchWithCountrySpecifics( +        registerOperationStatus, eidasData); +    if (searchResult.getResultCount() == 0) { +      log.trace("'step6CountrySpecificSearch' ends with no result. Forward to next matching step ... "); +      step8RegisterSearchWithMds(executionContext, searchResult.getOperationStatus(), eidasData); +       +    } else if (searchResult.getResultCount() == 1) { +      log.trace("'step6CountrySpecificSearch' finds a person. Forward to 'step7aKittProcess' step ... "); +      RegisterStatusResults updatedResult = registerSearchService.step7aKittProcess(searchResult, eidasData); +      foundMatchFinalizeTask(updatedResult.getResult(), eidasData); +       +    } else { +      throw new WorkflowException("step6CountrySpecificSearch", +          "More than one entry with unique country-specific information", true); +    } +  } + +  private void step8RegisterSearchWithMds(ExecutionContext executionContext, +                                          RegisterOperationStatus registerOperationStatus, SimpleEidasData eidasData) +      throws EaafStorageException, WorkflowException { +    log.trace("Starting step8RegisterSearchWithMds"); +    RegisterStatusResults registerData = registerSearchService.searchWithMds(registerOperationStatus, eidasData); +    if (registerData.getResultCount() == 0) { +      log.debug("Matching step: 'step8RegisterSearchWithMds' has no result. Forward to create new ERnP entry ...  "); +      executionContext.put(TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK, true); +       +    } else { +      log.debug("Matching step: 'step8RegisterSearchWithMds' has #{} results. " +          + "Forward to GUI based matching steps ... ", registerData.getResultCount()); +      MatchingTaskUtils.storeIntermediateMatchingResult(pendingReq, registerData); +      executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); +       +    } +  } + +  private RegisterResult step3CheckRegisterUpdateNecessary( +      RegisterStatusResults searchResult, SimpleEidasData eidasData) throws WorkflowException { +    log.trace("Starting step3CheckRegisterUpdateNecessary"); +    if (!eidasData.equalsRegisterData(searchResult.getResult())) { +      log.debug("PersonalIdentifier match but MDS or other information changed. Starting update process ... ");         +      return registerSearchService.step7aKittProcess(searchResult, eidasData).getResult(); +       +    } else { +      log.debug("Register information match to eIDAS information. No update required"); +      return searchResult.getResult(); +    } +  } +   +  private void foundMatchFinalizeTask(RegisterResult updatedResult, SimpleEidasData eidasData) +      throws WorkflowException, EaafStorageException { +    MatchedPersonResult result =  +        MatchedPersonResult.generateFormMatchingResult(updatedResult, eidasData.getCitizenCountryCode()); +    MatchingTaskUtils.storeFinalMatchingResult(pendingReq, result); +     +  } + +  @NotNull +  private SimpleEidasData convertEidasAttrToSimpleData() +      throws EidasAttributeException, EidPostProcessingException { +    final ILightResponse eidasResponse = MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq) +        .getGenericDataFromSession(Constants.DATA_FULL_EIDAS_RESPONSE, ILightResponse.class); +    Map<String, Object> simpleMap = MatchingTaskUtils.convertEidasAttrToSimpleMap( +        eidasResponse.getAttributes().getAttributeMap(), log); +    return eidPostProcessor.postProcess(simpleMap); +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java new file mode 100644 index 00000000..f335bc2a --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAustrianResidenceGuiResponseTask.java @@ -0,0 +1,236 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +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_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK; +import static at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants.TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK; + +import java.util.Enumeration; +import java.util.Set; + +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 com.google.common.collect.Sets; + +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; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterStatusResults; +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.controller.tasks.AbstractLocaleAuthServletTask; +import lombok.extern.slf4j.Slf4j; + + +/** + * Task receives the response of {@link GenerateAustrianResidenceGuiTask} and handles it. + * This corresponds to Steps 17B, 18, 19 in the eIDAS Matching Concept. + * Input: + * <ul> + *   <li>{@link Constants#DATA_SIMPLE_EIDAS} initial login data from user</li> + *   <li>{@link Constants#DATA_INTERMEDIATE_RESULT} results from search in registers with personIdentifier</li> + * </ul> + * Output: + * <ul> + *   <li>{@link Constants#DATA_PERSON_MATCH_RESULT} if one register result found</li> + * </ul> + * Transitions: + * <ul> + *   <li>{@link GenerateOtherLoginMethodGuiTask} if no results from search with residency data in registers</li> + *   <li>{@link CreateIdentityLinkTask} if one exact match between initial register search (with MDS) and results + *   from search with residency data in registers exists</li> + *   <li>{@link GenerateOtherLoginMethodGuiTask} if a user input error has happened</li> + * </ul> + * + * @author amarsalek + * @author ckollmann + * @author tlenz + */ +@Slf4j +@Component("ReceiveAustrianResidenceGuiResponseTask") +public class ReceiveAustrianResidenceGuiResponseTask extends AbstractLocaleAuthServletTask { + +  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";   +  public static final Set<String> ALL_EXECUTIONCONTEXT_PARAMETERS = Sets.newHashSet( +      CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, +      CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, +      TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK, +      TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK); +   +  private final RegisterSearchService registerSearchService; + +   +  public ReceiveAustrianResidenceGuiResponseTask(RegisterSearchService registerSearchService) { +    this.registerSearchService = registerSearchService; +     +  } +   +  @Override +  protected void executeWithLocale(ExecutionContext executionContext, HttpServletRequest request, +      HttpServletResponse response) throws TaskExecutionException { +    log.trace("Starting ReceiveAustrianResidenceGuiResponseTask"); + +    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 'insert-into-ERnP' selection ... ");        +        executionContext.put(TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK, true); +        executionContext.put(TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK, false); +        return; +       +      } else { +        executionContext.put(TRANSITION_TO_REQUESTING_NEW_ERNP_ENTRY_TASK, false); +         +      } +             +      //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 'input residence inputs' ... "); +        executionContext.put(TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK, true); +     +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_21); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); +        return; + +      } +     +      // 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); +       +      // validate matching response from registers +      if (residencyResult.getResultCount() != 1) { +        log.info("Find {} match by using residence information. Forward user to 'input residence infos' ... ", +            residencyResult.getResultCount() == 0 ? "no" : "more-than-one");        +        executionContext.put(TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK, true); +         +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_22); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true);     + +      } else { +        log.debug("Find single match by using residence information. Starting data validation ... "); +        compareSearchResultWithInitialData(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); + +    } +  } + +  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(RegisterStatusResults residencyResult, SimpleEidasData eidasData) +      throws TaskExecutionException, EaafStorageException { +    try { +      if (!eidasData.equalsRegisterData(residencyResult.getResult())) { +        // update register information +        RegisterStatusResults updateResult = registerSearchService.step7aKittProcess(residencyResult, eidasData); + +        // store updated result to re-used in CreateIdentityLink step, because there we need bPK and MDS +        MatchingTaskUtils.storeFinalMatchingResult(pendingReq, +            MatchedPersonResult.generateFormMatchingResult( +                updateResult.getResult(), eidasData.getCitizenCountryCode())); + +      } else { +        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) { +      log.warn("Kitt operation after successful residence matching FAILED.", e); +      throw new TaskExecutionException(pendingReq, "Search failed", new ManualFixNecessaryException(eidasData)); + +    } +  } + +  private @NotNull AdresssucheOutput parseHtmlInput(HttpServletRequest request) { +    Enumeration<String> reqParamNames = request.getParameterNames(); +    AdresssucheOutputBuilder resultBuilder = AdresssucheOutput.builder(); +    while (reqParamNames.hasMoreElements()) { +      final String paramName = reqParamNames.nextElement(); +      String escaped = StringEscapeUtils.escapeHtml(request.getParameter(paramName)); +      if (AdresssucheController.PARAM_MUNIPICALITY.equalsIgnoreCase(paramName)) { +        resultBuilder.municipality(escaped); + +      } else if (AdresssucheController.PARAM_NUMBER.equalsIgnoreCase(paramName)) { +        resultBuilder.number(escaped); + +      } else if (AdresssucheController.PARAM_POSTLEITZAHL.equalsIgnoreCase(paramName)) { +        resultBuilder.postleitzahl(escaped); + +      } else if (AdresssucheController.PARAM_STREET.equalsIgnoreCase(paramName)) { +        resultBuilder.street(escaped); + +      } else if (AdresssucheController.PARAM_VILLAGE.equalsIgnoreCase(paramName)) { +        resultBuilder.village(escaped); + +      } +    } +     +    return resultBuilder.build();     +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseAlternativeTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseAlternativeTask.java new file mode 100644 index 00000000..d2bd0128 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseAlternativeTask.java @@ -0,0 +1,195 @@ +/* + * Copyright 2018 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.web.util.UriComponentsBuilder; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasValidationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.validator.EidasResponseValidator; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.EidAuthProcessDataWrapper; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.light.ILightResponse; +import eu.eidas.auth.commons.light.impl.LightResponse; +import eu.eidas.auth.commons.tx.BinaryLightToken; +import eu.eidas.specificcommunication.BinaryLightTokenHelper; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; +import lombok.extern.slf4j.Slf4j; + + +/** + * Receives the authn response from the eIDAS Node, containing the (alternative) eIDAS authentication. + * Input: + * <ul> + *     <li>none</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#DATA_FULL_EIDAS_RESPONSE_ALTERNATIVE} the full response details</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link InitialSearchTask} to perform search in registers</li> + * </ul> + * + * @author tlenz + * @author ckollmann + */ +@Slf4j +@Component("ReceiveAuthnResponseTask") +public class ReceiveAuthnResponseAlternativeTask extends AbstractAuthServletTask { + +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +   +  @Autowired +  ApplicationContext context; +   +  @Autowired +  private IConfiguration basicConfig; + +  @Autowired +  private EidasAttributeRegistry attrRegistry; + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, +                      HttpServletResponse response) throws TaskExecutionException { +    try { +      final ILightResponse eidasResponse = extractEidasResponse(request); +       +      String stagingEndpoint = pendingReq.getRawData( +          MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class);       +      if (StringUtils.isNotEmpty(stagingEndpoint)) { +        log.info("Find ms-connector staging to: {}. Forwarding to that endpoint ... ", stagingEndpoint); +        forwardToOtherStage(response, executionContext, eidasResponse, stagingEndpoint); +                 +      } else {       +        checkStatusCode(eidasResponse); +        validateMsSpecificResponse(executionContext, eidasResponse); +        storeInSession(eidasResponse); +         +      } +       +    } catch (final EidasSAuthenticationException e) { +      log.warn("eIDAS Response processing FAILED.", e); +      throw new TaskExecutionException(pendingReq, e.getMessage(), e); +       +    } catch (final Exception e) { +      log.warn("eIDAS Response processing FAILED.", e); +      throw new TaskExecutionException(pendingReq, e.getMessage(), +          new EidasSAuthenticationException("eidas.05", new Object[]{e.getMessage()}, e)); +    } +  } + +  @NotNull +  private ILightResponse extractEidasResponse(HttpServletRequest request) throws EidasSAuthenticationException { +    final ILightResponse eidasResponse = (ILightResponse) request.getAttribute(Constants.DATA_FULL_EIDAS_RESPONSE); +    if (eidasResponse == null) { +      log.warn("NO eIDAS response-message found."); +      throw new EidasSAuthenticationException("eidas.01", null); +    } +    log.debug("Receive eIDAS response with RespId: {} for ReqId: {}", +        eidasResponse.getId(), eidasResponse.getInResponseToId()); +    log.trace("Full eIDAS-Resp: {}", eidasResponse); +    return eidasResponse; +  } + +  private void checkStatusCode(ILightResponse eidasResponse) throws EidasSAuthenticationException { +    if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { +      log.info("Receive eIDAS Response with StatusCode: {} Subcode: {} Msg: {}", +          eidasResponse.getStatus().getStatusCode(), +          eidasResponse.getStatus().getSubStatusCode(), +          eidasResponse.getStatus().getStatusMessage()); +      throw new EidasSAuthenticationException("eidas.02", new Object[]{eidasResponse.getStatus() +          .getStatusCode(), eidasResponse.getStatus().getStatusMessage()}); +    } +  } + +  private void validateMsSpecificResponse(ExecutionContext executionContext, ILightResponse eidasResponse) +      throws EidasValidationException { +    final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, "AT"); +    final String citizenCountryCode = (String) executionContext.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +    EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, attrRegistry); +  } + +  private void storeInSession(ILightResponse eidasResponse) throws EaafException { +    log.debug("Store eIDAS response information into pending-request."); +    final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class); +    authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); +    authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE_ALTERNATIVE, eidasResponse); +     +    //inject set flag to inject  +    authProcessData.setTestIdentity( +        basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); +     +    requestStoreage.storePendingRequest(pendingReq); +     +  } +   +  private void forwardToOtherStage(HttpServletResponse response, ExecutionContext executionContext,  +      ILightResponse eidasResponse, String stagingEndpoint)  +          throws SpecificCommunicationException, IOException, EaafException { +    executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, true); +                 +    //remove staging information because it's still in use +    pendingReq.setRawDataToTransaction(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, null); +     +    final SpecificCommunicationService specificConnectorCommunicationService = +        (SpecificCommunicationService) context.getBean( +            SpecificCommunicationDefinitionBeanNames.SPECIFIC_CONNECTOR_COMMUNICATION_SERVICE.toString()); +    BinaryLightToken token = specificConnectorCommunicationService.putResponse( +        LightResponse.builder(eidasResponse).relayState(pendingReq.getPendingRequestId()).build()); +    final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token);     +     +    final UriComponentsBuilder redirectUrl = UriComponentsBuilder.fromHttpUrl(stagingEndpoint); +    redirectUrl.queryParam(EidasParameterKeys.TOKEN.toString(), tokenBase64); + +    // store pendingRequest +    requestStoreage.storePendingRequest(pendingReq); +     +    log.debug("Forward to other stage .... "); +    response.sendRedirect(redirectUrl.build().encode().toString()); +        +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java index 35cb6015..5e4075de 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveAuthnResponseTask.java @@ -19,7 +19,7 @@   * file for details on the various modules and licenses.   * The "NOTICE" text file is part of the distribution. Any derivative works   * that you distribute must include a readable copy of the "NOTICE" text file. -*/ + */  package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; @@ -29,6 +29,7 @@ import javax.servlet.http.HttpServletRequest;  import javax.servlet.http.HttpServletResponse;  import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.context.ApplicationContext;  import org.springframework.stereotype.Component; @@ -38,6 +39,7 @@ import at.asitplus.eidas.specific.core.MsConnectorEventCodes;  import at.asitplus.eidas.specific.core.MsEidasNodeConstants;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasSAuthenticationException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.EidasValidationException;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.EidasAttributeRegistry;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.validator.EidasResponseValidator;  import at.gv.egiz.eaaf.core.api.idp.IConfiguration; @@ -56,30 +58,45 @@ import eu.eidas.specificcommunication.exception.SpecificCommunicationException;  import eu.eidas.specificcommunication.protocol.SpecificCommunicationService;  import lombok.extern.slf4j.Slf4j; + +/** + * Receives the authn response from the eIDAS Node, containing the (initial) eIDAS authentication. + * Input: + * <ul> + *     <li>none</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#DATA_FULL_EIDAS_RESPONSE} the full response details</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link InitialSearchTask} to perform search in registers</li> + * </ul> + * + * @author tlenz + * @author ckollmann + */  @Slf4j -@Component("ReceiveResponseFromeIDASNodeTask") +@Component("ReceiveAuthnResponseTask")  public class ReceiveAuthnResponseTask extends AbstractAuthServletTask { +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +      @Autowired    ApplicationContext context;    @Autowired    private IConfiguration basicConfig; +    @Autowired    private EidasAttributeRegistry attrRegistry;    @Override    public void execute(ExecutionContext executionContext, HttpServletRequest request, -      HttpServletResponse response) throws TaskExecutionException { +                      HttpServletResponse response) throws TaskExecutionException {      try { -      final ILightResponse eidasResponse = (ILightResponse) request.getAttribute( -          Constants.DATA_FULL_EIDAS_RESPONSE); -      if (eidasResponse == null) { -        log.warn("NO eIDAS response-message found."); -        throw new EidasSAuthenticationException("eidas.01", null); - -      } - +      final ILightResponse eidasResponse = extractEidasResponse(request);        String stagingEndpoint = pendingReq.getRawData(            MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, String.class);              if (StringUtils.isNotEmpty(stagingEndpoint)) { @@ -88,20 +105,20 @@ public class ReceiveAuthnResponseTask extends AbstractAuthServletTask {        } else {          executionContext.put(MsEidasNodeConstants.EXECCONTEXT_PARAM_MSCONNECTOR_STAGING, false); -        processResponseOnThatStage(executionContext, eidasResponse); -         +        checkStatusCode(eidasResponse); +        validateMsSpecificResponse(executionContext, eidasResponse); +        storeInSession(eidasResponse);                } +      revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID);      } catch (final EaafException e) {        revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID);        throw new TaskExecutionException(pendingReq, "eIDAS Response processing FAILED.", e); -      } catch (final Exception e) {        log.warn("eIDAS Response processing FAILED.", e);        revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_NOT_VALID);        throw new TaskExecutionException(pendingReq, e.getMessage(), -          new EidasSAuthenticationException("eidas.05", new Object[] { e.getMessage() }, e)); - +          new EidasSAuthenticationException("eidas.05", new Object[]{e.getMessage()}, e));      }    } @@ -130,56 +147,51 @@ public class ReceiveAuthnResponseTask extends AbstractAuthServletTask {      response.sendRedirect(redirectUrl.build().encode().toString());    } +   +  @NotNull +  private ILightResponse extractEidasResponse(HttpServletRequest request) throws EidasSAuthenticationException { +    final ILightResponse eidasResponse = (ILightResponse) request.getAttribute(Constants.DATA_FULL_EIDAS_RESPONSE); +    if (eidasResponse == null) { +      log.warn("NO eIDAS response-message found."); +      throw new EidasSAuthenticationException("eidas.01", null); +    } +    log.debug("Receive eIDAS response with RespId: {} for ReqId: {}", +        eidasResponse.getId(), eidasResponse.getInResponseToId()); +    log.trace("Full eIDAS-Resp: {}", eidasResponse); +    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse.getId()); +    return eidasResponse; +  } -  private void processResponseOnThatStage(ExecutionContext executionContext, ILightResponse eidasResponse)  -      throws EaafException { -    log.debug("Receive eIDAS response with RespId:" + eidasResponse.getId() + " for ReqId:" + eidasResponse -        .getInResponseToId()); -    log.trace("Full eIDAS-Resp: " + eidasResponse.toString()); -    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE, eidasResponse -        .getId()); - -    // check response StatusCode +  private void checkStatusCode(ILightResponse eidasResponse) throws EidasSAuthenticationException {      if (!eidasResponse.getStatus().getStatusCode().equals(Constants.SUCCESS_URI)) { -      log.info("Receice eIDAS Response with StatusCode:" + eidasResponse.getStatus().getStatusCode() -          + " Subcode:" + eidasResponse.getStatus().getSubStatusCode() + " Msg:" + eidasResponse.getStatus() -              .getStatusMessage()); -      throw new EidasSAuthenticationException("eidas.02", new Object[] { eidasResponse.getStatus() -          .getStatusCode(), eidasResponse.getStatus().getStatusMessage() }); - +      log.info("Receive eIDAS Response with StatusCode: {} Subcode: {} Msg: {}", +          eidasResponse.getStatus().getStatusCode(), +          eidasResponse.getStatus().getSubStatusCode(), +          eidasResponse.getStatus().getStatusMessage()); +      throw new EidasSAuthenticationException("eidas.02", new Object[]{eidasResponse.getStatus() +          .getStatusCode(), eidasResponse.getStatus().getStatusMessage()});      } +  } -    // extract all Attributes from response - -    // ********************************************************** -    // ******* MS-specificresponse validation ********** -    // ********************************************************** -    final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, -        "AT"); -    final String citizenCountryCode = (String) executionContext.get( -        MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); -    EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, -        attrRegistry); - -    // ********************************************************** -    // ******* Store resonse infos into session object ********** -    // ********************************************************** +  private void validateMsSpecificResponse(ExecutionContext executionContext, ILightResponse eidasResponse) +      throws EidasValidationException { +    final String spCountry = basicConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, "AT"); +    final String citizenCountryCode = (String) executionContext.get(MsEidasNodeConstants.REQ_PARAM_SELECTED_COUNTRY); +    EidasResponseValidator.validateResponse(pendingReq, eidasResponse, spCountry, citizenCountryCode, attrRegistry); +  } -    // update MOA-Session data with received information +  private void storeInSession(ILightResponse eidasResponse) throws EaafException {      log.debug("Store eIDAS response information into pending-request.");      final EidAuthProcessDataWrapper authProcessData = pendingReq.getSessionData(EidAuthProcessDataWrapper.class);      authProcessData.setQaaLevel(eidasResponse.getLevelOfAssurance()); -    authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); -      //inject set flag to inject       authProcessData.setTestIdentity(          basicConfig.getBasicConfigurationBoolean(Constants.CONIG_PROPS_EIDAS_IS_TEST_IDENTITY, false)); -           -    // store MOA-session to database -    requestStoreage.storePendingRequest(pendingReq); -    revisionsLogger.logEvent(pendingReq, MsConnectorEventCodes.RESPONSE_FROM_EIDAS_NODE_VALID); +     +    authProcessData.setGenericDataToSession(Constants.DATA_FULL_EIDAS_RESPONSE, eidasResponse); +    requestStoreage.storePendingRequest(pendingReq);    } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseTask.java new file mode 100644 index 00000000..b212d133 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveMobilePhoneSignatureResponseTask.java @@ -0,0 +1,403 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +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 static at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants.MODULE_NAME_FOR_LOGGING; + +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.TransformerException; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.opensaml.core.xml.io.MarshallingException; +import org.opensaml.messaging.decoder.MessageDecodingException; +import org.opensaml.saml.saml2.core.Response; +import org.opensaml.saml.saml2.core.StatusCode; +import org.opensaml.saml.saml2.metadata.IDPSSODescriptor; +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.RegisterResult; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleEidasData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SimpleMobileSignatureData; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.exception.WorkflowException; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.IdAustriaClientAuthEventConstants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthCredentialProvider; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.idaustriaclient.provider.IdAustriaClientAuthMetadataProvider; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterStatusResults; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.utils.MatchingTaskUtils; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafBuilderException; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; +import at.gv.egiz.eaaf.modules.pvp2.api.binding.IDecoder; +import at.gv.egiz.eaaf.modules.pvp2.exception.CredentialsNotAvailableException; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlAssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.exception.SamlSigningException; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.PostBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.binding.RedirectBinding; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.InboundMessage; +import at.gv.egiz.eaaf.modules.pvp2.impl.message.PvpSProfileResponse; +import at.gv.egiz.eaaf.modules.pvp2.impl.utils.Saml2Utils; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.EaafUriCompare; +import at.gv.egiz.eaaf.modules.pvp2.impl.validation.TrustEngineFactory; +import at.gv.egiz.eaaf.modules.pvp2.impl.verification.SamlVerificationEngine; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AssertionValidationExeption; +import at.gv.egiz.eaaf.modules.pvp2.sp.exception.AuthnResponseValidationException; +import at.gv.egiz.eaaf.modules.pvp2.sp.impl.utils.AssertionAttributeExtractor; +import lombok.extern.slf4j.Slf4j; + +/** + * Task that receives the SAML2 response from ID Austria system. + * This corresponds to Step 15 in the eIDAS Matching Concept. + * Input: + * <ul> + *     <li>{@link Constants#DATA_SIMPLE_EIDAS} initial login data from user</li> + *     <li>{@link Constants#DATA_INTERMEDIATE_RESULT} results from search in registers with personIdentifier</li> + * </ul> + * Output: + * <ul> + *     <li>{@link Constants#DATA_PERSON_MATCH_RESULT} if one register result found</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link GenerateAustrianResidenceGuiTask} if no results in registers were found</li> + *     <li>{@link CreateIdentityLinkTask} if one exact match between initial register search (with MDS) data and + *     register search with MPS data exists</li> + *     <li>{@link GenerateOtherLoginMethodGuiTask} if a user input error has happened</li> + * </ul> + * + * @author tlenz + * @author ckollmann + */ +@Slf4j +@Component("ReceiveMobilePhoneSignatureResponseTask") +public class ReceiveMobilePhoneSignatureResponseTask extends AbstractAuthServletTask { + +  private final SamlVerificationEngine samlVerificationEngine; +  private final RegisterSearchService registerSearchService; +  private final IdAustriaClientAuthCredentialProvider credentialProvider; +  private final IdAustriaClientAuthMetadataProvider metadataProvider; + +  private static final String ERROR_PVP_03 = "sp.pvp2.03"; +  private static final String ERROR_PVP_05 = "sp.pvp2.05"; +  private static final String ERROR_PVP_06 = "sp.pvp2.06"; +  private static final String ERROR_PVP_08 = "sp.pvp2.08"; +  private static final String ERROR_PVP_10 = "sp.pvp2.10"; +  private static final String ERROR_PVP_11 = "sp.pvp2.11"; +  private static final String ERROR_PVP_12 = "sp.pvp2.12"; + +  private static final String ERROR_MSG_00 = "Receive INVALID PVP Response from ID Austria system"; +  private static final String ERROR_MSG_01 = "Processing PVP response from 'ID Austria system' FAILED."; +  private static final String ERROR_MSG_02 = "PVP response decryption FAILED. No credential found."; +  private static final String ERROR_MSG_03 = "PVP response validation FAILED."; +  private static final String ERROR_GENERIC = "Matching failed, because response from ID Austria was " +      + "invalid or contains an error. Detail: {}"; +   +  private static final String MSG_PROP_23 = "module.eidasauth.matching.23"; +  private static final String MSG_PROP_24 = "module.eidasauth.matching.24"; +   +  /** +   * Creates the new task, with autowired dependencies from Spring. +   */ +  @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") +  public ReceiveMobilePhoneSignatureResponseTask(SamlVerificationEngine samlVerificationEngine, +                                                 RegisterSearchService registerSearchService, +                                                 IdAustriaClientAuthCredentialProvider credentialProvider, +                                                 IdAustriaClientAuthMetadataProvider metadataProvider) { +    this.samlVerificationEngine = samlVerificationEngine; +    this.registerSearchService = registerSearchService; +    this.credentialProvider = credentialProvider; +    this.metadataProvider = metadataProvider; +  } + +  @Override +  public void execute(ExecutionContext executionContext, HttpServletRequest request, HttpServletResponse response) +      throws TaskExecutionException { +    try { +      log.trace("Starting ReceiveMobilePhoneSignatureResponseTask"); +      IDecoder decoder = loadDecoder(request); +      EaafUriCompare comparator = loadComparator(request); +      InboundMessage inboundMessage = decodeAndVerifyMessage(request, response, decoder, comparator); +      Pair<PvpSProfileResponse, Boolean> processedMsg = validateAssertion((PvpSProfileResponse) inboundMessage); +      if (processedMsg.getSecond()) { +        log.info("Matching failed, because ID Austria login was stopped by user."); +        // forward to next matching step in case of ID Autria authentication was stopped by user +        executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_23); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true);               +        return; + +      } + +      // validate SAML2 response +      validateEntityId(inboundMessage); +      log.info("Receive a valid assertion from IDP " + inboundMessage.getEntityID()); + +      // load already existing information from session +      SimpleEidasData eidasData = MatchingTaskUtils.getInitialEidasData(pendingReq); +      RegisterStatusResults initialSearchResult = MatchingTaskUtils.getIntermediateMatchingResult(pendingReq); + +      // extract user information from ID Austria authentication +      AssertionAttributeExtractor extractor = new AssertionAttributeExtractor(processedMsg.getFirst().getResponse()); +      SimpleMobileSignatureData simpleMobileSignatureData = getAuthDataFromInterfederation(extractor); + +      // check if MDS from ID Austria authentication matchs to eIDAS authentication +      if (!simpleMobileSignatureData.equalsSimpleEidasData(eidasData)) { +        log.info("Matching failed, because MDS from ID-Austria login does not match to MDS from initial eIDAS"); +        executionContext.put(TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON, MSG_PROP_24); +        executionContext.put(CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true);   +        return; + +      } + +      // search entry in initial search result from steps before and build new RegisterSearchResult +      RegisterStatusResults registerResult = new RegisterStatusResults(initialSearchResult.getOperationStatus(), +          extractEntriesByBpk(initialSearchResult.getResultsZmr().stream(), simpleMobileSignatureData.getBpk()), +          extractEntriesByBpk(initialSearchResult.getResultsErnp().stream(), simpleMobileSignatureData.getBpk())); + +      if (registerResult.getResultCount() != 1) { +        throw new WorkflowException("matchWithIDAustriaAuthentication", +            "Suspect state detected. MDS matches to eIDAS authentication " +                + "but register search-result with MDS contains #" + registerResult.getResultCount() +                + " entry with bPK from ID Austria authentication", false); + +      } else { +        // perform kit operation +        registerSearchService.step7aKittProcess(registerResult, eidasData); + +        // store search result to re-used in CreateIdentityLink step, because there we need bPK and MDS +        MatchingTaskUtils.storeFinalMatchingResult(pendingReq, +            MatchedPersonResult.generateFormMatchingResult(registerResult.getResult(), +                eidasData.getCitizenCountryCode())); + +      } + +    } catch (final AuthnResponseValidationException e) { +      log.info(ERROR_GENERIC, e.getMessage()); +      throw new TaskExecutionException(pendingReq, ERROR_MSG_03, e); + +    } catch (MessageDecodingException | SecurityException | SamlSigningException e) { +      log.info(ERROR_GENERIC, e.getMessage()); +      throw new TaskExecutionException(pendingReq, ERROR_MSG_00, +          new AuthnResponseValidationException(ERROR_PVP_11, new Object[]{MODULE_NAME_FOR_LOGGING}, e)); + +    } catch (IOException | MarshallingException | TransformerException e) { +      log.info("Processing PVP response from 'ms-specific eIDAS node' FAILED.", e); +      throw new TaskExecutionException(pendingReq, ERROR_MSG_01, +          new AuthnResponseValidationException(ERROR_PVP_12, new Object[]{MODULE_NAME_FOR_LOGGING, e.getMessage()}, e)); + +    } catch (final CredentialsNotAvailableException e) { +      log.info("PVP response decryption FAILED. No credential found.", e); +      throw new TaskExecutionException(pendingReq, ERROR_MSG_02, +          new AuthnResponseValidationException(ERROR_PVP_10, new Object[]{MODULE_NAME_FOR_LOGGING}, e)); + +    } catch (final Exception e) { +      // todo catch ManualFixNecessaryException in any other way? +      log.info("PVP response validation FAILED. Msg: {}",e.getMessage(), e); +      throw new TaskExecutionException(pendingReq, ERROR_MSG_03, +          new AuthnResponseValidationException(ERROR_PVP_12, new Object[]{MODULE_NAME_FOR_LOGGING, e.getMessage()}, e)); + +    } +  } + +  private List<RegisterResult> extractEntriesByBpk(Stream<RegisterResult> stream, String bpk) { +    return stream.filter(el -> bpk.equals(el.getBpk())).collect(Collectors.toList()); + +  } + +  @NotNull +  private InboundMessage decodeAndVerifyMessage(HttpServletRequest request, HttpServletResponse response, +                                                IDecoder decoder, EaafUriCompare comparator) throws Exception { +    InboundMessage inboundMessage = (InboundMessage) decoder.decode(request, response, metadataProvider, +        IDPSSODescriptor.DEFAULT_ELEMENT_NAME, comparator); +    if (!inboundMessage.isVerified()) { +      samlVerificationEngine.verify(inboundMessage, TrustEngineFactory.getSignatureKnownKeysTrustEngine( +          metadataProvider)); +      inboundMessage.setVerified(true); +    } +    return inboundMessage; +  } + +  private void validateEntityId(InboundMessage inboundMessage) throws AuthnResponseValidationException { +    final String msNodeEntityID = authConfig +        .getBasicConfiguration(IdAustriaClientAuthConstants.CONFIG_PROPS_ID_AUSTRIA_ENTITYID); +    final String respEntityId = inboundMessage.getEntityID(); +    if (!msNodeEntityID.equals(respEntityId)) { +      log.warn("Response Issuer is not from valid 'ID Austria IDP'. Stopping ID Austria authentication ..."); +      throw new AuthnResponseValidationException(ERROR_PVP_08, +          new Object[]{MODULE_NAME_FOR_LOGGING, +              inboundMessage.getEntityID()}); +    } +  } + +  @NotNull +  private EaafUriCompare loadComparator(HttpServletRequest request) throws AuthnResponseValidationException { +    if (request.getMethod().equalsIgnoreCase("POST")) { +      log.trace("Receive PVP Response from 'ID Austria system', by using POST-Binding."); +      return new EaafUriCompare(pendingReq.getAuthUrl() + IdAustriaClientAuthConstants.ENDPOINT_POST); +    } else if (request.getMethod().equalsIgnoreCase("GET")) { +      log.trace("Receive PVP Response from 'ID Austria system', by using Redirect-Binding."); +      return new EaafUriCompare(pendingReq.getAuthUrl() + IdAustriaClientAuthConstants.ENDPOINT_REDIRECT); +    } else { +      log.warn("Receive PVP Response from 'ID Austria system', but Binding {} is not supported.", request.getMethod()); +      throw new AuthnResponseValidationException(ERROR_PVP_03, new Object[]{MODULE_NAME_FOR_LOGGING}); +    } +  } + +  @NotNull +  private IDecoder loadDecoder(HttpServletRequest request) throws AuthnResponseValidationException { +    if (request.getMethod().equalsIgnoreCase("POST")) { +      log.trace("Receive PVP Response from 'ID Austria system', by using POST-Binding."); +      return new PostBinding(); +    } else if (request.getMethod().equalsIgnoreCase("GET")) { +      log.trace("Receive PVP Response from 'ID Austria system', by using Redirect-Binding."); +      return new RedirectBinding(); +    } else { +      log.warn("Receive PVP Response from 'ID Austria system', but Binding {} is not supported.", request.getMethod()); +      throw new AuthnResponseValidationException(ERROR_PVP_03, new Object[]{MODULE_NAME_FOR_LOGGING}); +    } +  } + +  private Pair<PvpSProfileResponse, Boolean> validateAssertion(PvpSProfileResponse msg) +      throws IOException, MarshallingException, TransformerException, +      CredentialsNotAvailableException, AuthnResponseValidationException, SamlAssertionValidationExeption { +    log.debug("Start PVP21 assertion processing... "); +    final Response response = (Response) msg.getResponse(); +    if (response.getStatus().getStatusCode().getValue().equals(StatusCode.SUCCESS)) { +      samlVerificationEngine.validateAssertion(response, +          credentialProvider.getMessageEncryptionCredential(), +          pendingReq.getAuthUrl() + IdAustriaClientAuthConstants.ENDPOINT_METADATA, +          MODULE_NAME_FOR_LOGGING); +      msg.setSamlMessage(Saml2Utils.asDomDocument(response).getDocumentElement()); +      revisionsLogger.logEvent(pendingReq, +          IdAustriaClientAuthEventConstants.AUTHPROCESS_ID_AUSTRIA_RESPONSE_RECEIVED, +          response.getID()); +      return Pair.newInstance(msg, false); +       +    } else { +      log.info("Receive StatusCode {} from 'ms-specific eIDAS node'.", response.getStatus().getStatusCode().getValue()); +      StatusCode subStatusCode = getSubStatusCode(response); +      if (subStatusCode != null +          && IdAustriaClientAuthConstants.SAML2_STATUSCODE_USERSTOP.equals(subStatusCode.getValue())) { +        log.info("Find 'User-Stop operation' in SAML2 response. Stopping authentication process ... "); +        return Pair.newInstance(msg, true); +         +      } + +      revisionsLogger.logEvent(pendingReq, +          IdAustriaClientAuthEventConstants.AUTHPROCESS_ID_AUSTRIA_RESPONSE_RECEIVED_ERROR); +      throw new AuthnResponseValidationException(ERROR_PVP_05, +          new Object[]{MODULE_NAME_FOR_LOGGING, +              response.getIssuer().getValue(), +              response.getStatus().getStatusCode().getValue(), +              response.getStatus().getStatusMessage().getValue()}); +    } +  } + +  /** +   * Get SAML2 Sub-StatusCode if not <code>null</code>. +   * +   * @param samlResp SAML2 response +   * @return Sub-StatusCode or <code>null</code> if it's not set +   */ +  private StatusCode getSubStatusCode(Response samlResp) { +    if (samlResp.getStatus().getStatusCode().getStatusCode() != null +        && StringUtils.isNotEmpty(samlResp.getStatus().getStatusCode().getStatusCode().getValue())) { +      return samlResp.getStatus().getStatusCode().getStatusCode(); +    } +    return null; +  } + +  private SimpleMobileSignatureData getAuthDataFromInterfederation(AssertionAttributeExtractor extractor) +      throws EaafBuilderException { +    List<String> requiredAttributes = IdAustriaClientAuthConstants.DEFAULT_REQUIRED_PVP_ATTRIBUTE_NAMES; +    SimpleMobileSignatureData.SimpleMobileSignatureDataBuilder builder = SimpleMobileSignatureData.builder(); +    if (!extractor.containsAllRequiredAttributes(requiredAttributes)) { +      log.warn("PVP Response from 'ID Austria node' contains not all requested attributes."); +      AssertionValidationExeption e = new AssertionValidationExeption(ERROR_PVP_06, +          new Object[]{MODULE_NAME_FOR_LOGGING}); +      throw new EaafBuilderException(ERROR_PVP_06, null, e.getMessage(), e); +    } +    final Set<String> includedAttrNames = extractor.getAllIncludeAttributeNames(); +    for (final String attrName : includedAttrNames) { +      if (PvpAttributeDefinitions.BPK_NAME.equals(attrName)) { +        builder.bpk(removeTargetPrefixFromBpk(extractor.getSingleAttributeValue(attrName))); +      } +      if (PvpAttributeDefinitions.GIVEN_NAME_NAME.equals(attrName)) { +        builder.givenName(extractor.getSingleAttributeValue(attrName)); +      } +      if (PvpAttributeDefinitions.PRINCIPAL_NAME_NAME.equals(attrName)) { +        builder.familyName(extractor.getSingleAttributeValue(attrName)); +      } +      if (PvpAttributeDefinitions.BIRTHDATE_NAME.equals(attrName)) { +        builder.dateOfBirth(extractor.getSingleAttributeValue(attrName)); +      } +      if (PvpAttributeDefinitions.EID_CITIZEN_EIDAS_QAA_LEVEL_NAME.equals(attrName)) { +        MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq).setQaaLevel( +            extractor.getSingleAttributeValue(attrName)); +      } +    } +    MatchingTaskUtils.getAuthProcessDataWrapper(pendingReq).setIssueInstant(extractor.getAssertionIssuingDate()); +    return builder.build(); + +  } + +  private String removeTargetPrefixFromBpk(String bpkWithPrefix) { +    if (StringUtils.isNotEmpty(bpkWithPrefix)) {     +      final String[] spitted = bpkWithPrefix.split(":"); +      if (spitted.length == 2) { +        log.debug("Find PVP-Attr: {}", PvpAttributeDefinitions.BPK_FRIENDLY_NAME); +        return spitted[1]; +             +      } else {       +        log.info("Find PVP-Attr: {} without prefix. Use it as it is", PvpAttributeDefinitions.BPK_FRIENDLY_NAME); +        return spitted[0]; +       +      } +    } else { +      log.warn("Receive no bPK in response from ID Austria System. There is something wrong on IDA side!!!"); +      return null;   +       +    }         +  } + + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveOtherLoginMethodGuiResponseTask.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveOtherLoginMethodGuiResponseTask.java new file mode 100644 index 00000000..184ad499 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/tasks/ReceiveOtherLoginMethodGuiResponseTask.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021 A-SIT Plus GmbH + * AT-specific eIDAS Connector has been developed in a cooperation between EGIZ, + * A-SIT Plus GmbH, A-SIT, and Graz University of Technology. + * + * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "License"); + * You may not use this work except in compliance with the License. + * You may obtain a copy of the License at: + * https://joinup.ec.europa.eu/news/understanding-eupl-v12 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * This product combines work with different licenses. See the "NOTICE" text + * file for details on the various modules and licenses. + * The "NOTICE" text file is part of the distribution. Any derivative works + * that you distribute must include a readable copy of the "NOTICE" text file. + */ + +package at.asitplus.eidas.specific.modules.auth.eidas.v2.tasks; + +import java.util.Enumeration; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringEscapeUtils; +import org.springframework.stereotype.Component; + +import com.google.common.collect.Sets; + +import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.SelectedLoginMethod; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.impl.idp.controller.tasks.AbstractLocaleAuthServletTask; +import lombok.extern.slf4j.Slf4j; + +/** + * Handles user's selection from {@link GenerateOtherLoginMethodGuiTask}. + * This corresponds to Steps 10, 14, 16 in the eIDAS Matching Concept. + * Input: + * <ul> + *     <li>{@link Constants#DATA_SIMPLE_EIDAS} initial login data from user</li> + *     <li>{@link Constants#DATA_INTERMEDIATE_RESULT} results from search in registers with personIdentifier</li> + * </ul> + * Transitions: + * <ul> + *     <li>{@link GenerateMobilePhoneSignatureRequestTask} if selected by user</li> + *     <li>{@link GenerateAustrianResidenceGuiTask} if selected by user</li> + *     <li>{@link GenerateAuthnRequestTask} if selected by user</li> + *     <li>{@link GenerateOtherLoginMethodGuiTask} if a user input error has happened</li> + * </ul> + * + * @author amarsalek + * @author ckollmann + */ +@Slf4j +@Component("ReceiveOtherLoginMethodGuiResponseTask") +public class ReceiveOtherLoginMethodGuiResponseTask extends AbstractLocaleAuthServletTask { + +  public static final Set<String> ALL_EXECUTIONCONTEXT_PARAMETERS = Sets.newHashSet( +      Constants.REQ_SELECTED_LOGIN_METHOD_PARAMETER, +      Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, +      Constants.TRANSITION_TO_GENERATE_EIDAS_LOGIN, +      Constants.TRANSITION_TO_GENERATE_MOBILE_PHONE_SIGNATURE_REQUEST_TASK, +      Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK, +      Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK); +   +  @Override +  public void executeWithLocale(ExecutionContext executionContext, HttpServletRequest request, +                                HttpServletResponse response) { +    try { +      SelectedLoginMethod selection = SelectedLoginMethod.valueOf(extractUserSelection(request)); +      executionContext.put(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, false); +      executionContext.put(Constants.REQ_SELECTED_LOGIN_METHOD_PARAMETER, selection.name()); +      executionContext.remove(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED); +      executionContext.remove(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED_REASON);       +      transitionToNextTask(executionContext, selection); + +    } catch (final Exception e) { +      log.error("Parsing selected login method FAILED.", e); +      executionContext.put(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); +      executionContext.put(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true);       +    } +  } + +  private String extractUserSelection(HttpServletRequest request) { +    Enumeration<String> paramNames = request.getParameterNames(); +    while (paramNames.hasMoreElements()) { +      String paramName = paramNames.nextElement(); +      if (Constants.REQ_SELECTED_LOGIN_METHOD_PARAMETER.equalsIgnoreCase(paramName)) { +        return StringEscapeUtils.escapeHtml(request.getParameter(paramName)); +      } +    } +    return null; +  } + +  private void transitionToNextTask(ExecutionContext executionContext, SelectedLoginMethod selection) { +    switch (selection) { +      case EIDAS_LOGIN: +        executionContext.put(Constants.TRANSITION_TO_GENERATE_EIDAS_LOGIN, true); +        return; + +      case MOBILE_PHONE_SIGNATURE_LOGIN: +        executionContext.put(Constants.TRANSITION_TO_GENERATE_MOBILE_PHONE_SIGNATURE_REQUEST_TASK, true); +        return; + +      case NO_OTHER_LOGIN: +        executionContext.put(Constants.TRANSITION_TO_GENERATE_GUI_QUERY_AUSTRIAN_RESIDENCE_TASK, true); +        return; + +      case ADD_ME_AS_NEW: +        executionContext.put(Constants.TRANSITION_TO_CREATE_NEW_ERNP_ENTRY_TASK, true); +        return; + +      default: +        executionContext.put(Constants.CONTEXT_FLAG_ADVANCED_MATCHING_FAILED, true); +        executionContext.put(Constants.TRANSITION_TO_GENERATE_OTHER_LOGIN_METHOD_GUI_TASK, true); +        return; +    } +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java index ced6ffe6..2853d8ab 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/EidasResponseUtils.java @@ -19,10 +19,12 @@   * file for details on the various modules and licenses.   * The "NOTICE" text file is part of the distribution. Any derivative works   * that you distribute must include a readable copy of the "NOTICE" text file. -*/ + */  package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; +import java.text.ParseException; +import java.text.SimpleDateFormat;  import java.util.ArrayList;  import java.util.List;  import java.util.regex.Matcher; @@ -37,6 +39,8 @@ import com.google.common.collect.ImmutableList;  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.exception.EidasAttributeException; +import at.gv.e_government.reference.namespace.persondata._20020228.PostalAddressType;  import at.gv.egiz.eaaf.core.impl.data.Triple;  import eu.eidas.auth.commons.attribute.AttributeDefinition;  import eu.eidas.auth.commons.attribute.AttributeValue; @@ -44,6 +48,7 @@ import eu.eidas.auth.commons.attribute.AttributeValueMarshaller;  import eu.eidas.auth.commons.attribute.AttributeValueMarshallingException;  import eu.eidas.auth.commons.attribute.AttributeValueTransliterator;  import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; +import lombok.NonNull;  import lombok.extern.slf4j.Slf4j;  @Slf4j @@ -53,7 +58,7 @@ public class EidasResponseUtils {    /**     * Validate a eIDAS PersonalIdentifier attribute value This validation is done     * according to eIDAS SAML Attribute Profile - Section 2.2.3 Unique Identifier -   *  +   *     * @param uniqueID eIDAS attribute value of a unique identifier     * @return true if the uniqueID matches to eIDAS to Unique Identifier     *         specification, otherwise false @@ -69,9 +74,9 @@ public class EidasResponseUtils {     * Parse an eIDAS PersonalIdentifier attribute value into it components. This     * processing is done according to eIDAS SAML Attribute Profile - Section 2.2.3     * Unique Identifier -   *  +   *     * @param uniqueID eIDAS attribute value of a unique identifier -   * @return {@link Trible} that contains: <br> +   * @return {@link Triple} that contains: <br>     *         First : citizen country <br>     *         Second: destination country <br>     *         Third : unique identifier <br> @@ -79,25 +84,26 @@ public class EidasResponseUtils {     */    public static Triple<String, String, String> parseEidasPersonalIdentifier(String uniqueID) {      if (!validateEidasPersonalIdentifier(uniqueID)) { -      log.error("eIDAS attribute value for {} looks wrong formated. Value: {}",  +      log.error("eIDAS attribute value for {} looks wrong formated. Value: {}",            Constants.eIDAS_ATTR_PERSONALIDENTIFIER, uniqueID);        return null;      }      return Triple.newInstance(uniqueID.substring(0, 2), uniqueID.substring(3, 5), uniqueID.substring(6)); -    }    /** -   * Get eIDAS attribute-values from eIDAS Node attributes.  -   *  +   * Get eIDAS attribute-values from eIDAS Node attributes. +   *     * @param attributeDefinition eIDAS attribute definition -   * @param attributeValues Attributes from eIDAS response -   * @return Set of attribute values. If more then one value than the first value contains the 'Latin' value.  +   * @param attributeValues     Attributes from eIDAS response +   * @return Set of attribute values. If more then one value than the first value +   *         contains the 'Latin' value.     */    // TODO: check possible problem with nonLatinCharacters  +  @NonNull    public static List<String> translateStringListAttribute(AttributeDefinition<?> attributeDefinition, -      ImmutableSet<? extends AttributeValue<?>> attributeValues) { +      @Nullable ImmutableSet<? extends AttributeValue<?>> attributeValues) {      final List<String> stringListAttribute = new ArrayList<>();      if (attributeValues != null && !attributeValues.isEmpty()) {        final AttributeValueMarshaller<?> attributeValueMarshaller = attributeDefinition @@ -109,7 +115,7 @@ public class EidasResponseUtils {            log.trace("Find attr: {} with value: {} nonLatinFlag: {} needTransliteration: {}",                attributeDefinition.getFriendlyName(), attributeValue.toString(), -              attributeValue.isNonLatinScriptAlternateVersion(),  +              attributeValue.isNonLatinScriptAlternateVersion(),                AttributeValueTransliterator.needsTransliteration(valueString));            // if (attributeValue.isNonLatinScriptAlternateVersion()) { @@ -120,12 +126,26 @@ public class EidasResponseUtils {              log.trace("Find 'needsTransliteration' flag. Setting this value at last list element ... ");              stringListAttribute.add(valueString); -          } +            log.trace("Find attr: {} with value: {} nonLatinFlag: {} needTransliteration: {}", +                attributeDefinition.getFriendlyName(), attributeValue.toString(), +                attributeValue.isNonLatinScriptAlternateVersion(), +                AttributeValueTransliterator.needsTransliteration(valueString)); + +            // if (attributeValue.isNonLatinScriptAlternateVersion()) { +            if (!AttributeValueTransliterator.needsTransliteration(valueString)) { +              stringListAttribute.add(0, valueString); +            } else { +              log.trace("Find 'needsTransliteration' flag. Setting this value at last list element ... "); +              stringListAttribute.add(valueString); + +            } +          }          } catch (final AttributeValueMarshallingException e) {            throw new IllegalStateException(e);          } +        }        log.trace("Extract values: {} for attr: {}",             StringUtils.join(stringListAttribute, ","), attributeDefinition.getFriendlyName()); @@ -140,16 +160,17 @@ public class EidasResponseUtils {    } +    /** -   * Convert eIDAS DateTime attribute to Java Object.  -   *  +   * Convert eIDAS DateTime attribute to Java Object. +   *     * @param attributeDefinition eIDAS attribute definition. -   * @param attributeValues eIDAS attribute value +   * @param attributeValues     eIDAS attribute value     * @return     */    @Nullable    public static DateTime translateDateAttribute(AttributeDefinition<?> attributeDefinition, -      ImmutableList<? extends AttributeValue<?>> attributeValues) { +                                                ImmutableList<? extends AttributeValue<?>> attributeValues) {      if (attributeValues.size() != 0) {        final AttributeValue<?> firstAttributeValue = attributeValues.get(0);        return (DateTime) firstAttributeValue.getValue(); @@ -161,17 +182,216 @@ public class EidasResponseUtils {    /**     * Concert eIDAS Address attribute to Java object. -   *  +   *     * @param attributeDefinition eIDAS attribute definition -   * @param attributeValues eIDAS attribute value +   * @param attributeValues     eIDAS attribute value     * @return     */    @Nullable    public static PostalAddress translateAddressAttribute(AttributeDefinition<?> attributeDefinition, -      ImmutableList<? extends AttributeValue<?>> attributeValues) { +                                                        ImmutableList<? extends AttributeValue<?>> attributeValues) {      final AttributeValue<?> firstAttributeValue = attributeValues.get(0);      return (PostalAddress) firstAttributeValue.getValue(); +  } + +  /** +   * Post-Process the eIDAS CurrentAddress attribute. +   * +   * @param currentAddressObj eIDAS current address information +   * @return current address or null if no attribute is available +   * @throws EidasAttributeException if eIDAS attribute is of a wrong type +   */ +  public static PostalAddressType processAddress(Object currentAddressObj) throws EidasAttributeException { +    if (currentAddressObj != null) { +      if (currentAddressObj instanceof PostalAddress) { +        final PostalAddressType result = new PostalAddressType(); +        result.setPostalCode(((PostalAddress) currentAddressObj).getPostCode()); +        result.setMunicipality(((PostalAddress) currentAddressObj).getPostName()); +        // TODO: add more mappings +        return result; +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_CURRENTADDRESS + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTADDRESS); +      } +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_CURRENTADDRESS + "' attribute. Post-Processing skipped ... "); +    } +    return null; +  } + +  /** +   * Post-Process the eIDAS BirthName attribute. +   * +   * @param birthNameObj eIDAS birthname information +   * @return birthName or null if no attribute is available +   * @throws EidasAttributeException if eIDAS attribute is of a wrong type +   */ +  public static String processBirthName(Object birthNameObj) throws EidasAttributeException { +    if (birthNameObj != null) { +      if (birthNameObj instanceof String) { +        return (String) birthNameObj; +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_BIRTHNAME + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_BIRTHNAME); +      } +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_BIRTHNAME + "' attribute. Post-Processing skipped ... "); +    } +    return null; +  } +  /** +   * Post-Process the eIDAS PlaceOfBirth attribute. +   * +   * @param placeOfBirthObj eIDAS Place-of-Birth information +   * @return place of Birth or null if no attribute is available +   * @throws EidasAttributeException if eIDAS attribute is of a wrong type +   */ +  public static String processPlaceOfBirth(Object placeOfBirthObj) throws EidasAttributeException { +    if (placeOfBirthObj != null) { +      if (placeOfBirthObj instanceof String) { +        return (String) placeOfBirthObj; + +      } else { +        log.warn("eIDAS attr: " + Constants.eIDAS_ATTR_PLACEOFBIRTH + " is of WRONG type"); +        throw new EidasAttributeException(Constants.eIDAS_ATTR_PLACEOFBIRTH); + +      } + +    } else { +      log.debug("NO '" + Constants.eIDAS_ATTR_PLACEOFBIRTH + "' attribute. Post-Processing skipped ... "); +    } +    return null; +  } + +  /** +   * Post-Process the eIDAS DateOfBirth attribute. +   * +   * @param dateOfBirthObj eIDAS date-of-birth attribute information +   * @return formated user's date-of-birth +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static DateTime processDateOfBirth(Object dateOfBirthObj) throws EidasAttributeException { +    if (!(dateOfBirthObj instanceof DateTime)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); +    } +    return (DateTime) dateOfBirthObj;    } -} +  /** +   * Post-Process the eIDAS DateOfBirth attribute to a string. +   * +   * @param dateOfBirthObj eIDAS date-of-birth attribute information +   * @return formated user's date-of-birth as string +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processDateOfBirthToString(Object dateOfBirthObj) throws EidasAttributeException { +    if (dateOfBirthObj instanceof String) { +      try { +        new SimpleDateFormat("yyyy-MM-dd").parse((String) dateOfBirthObj); +        return (String) dateOfBirthObj; +      } catch (ParseException e) { +        throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); +      } +    } +    if (!(dateOfBirthObj instanceof DateTime)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_DATEOFBIRTH); +    } +    return new SimpleDateFormat("yyyy-MM-dd").format(((DateTime) dateOfBirthObj).toDate()); +  } + +  /** +   * Post-Process the eIDAS GivenName attribute. +   * +   * @param givenNameObj eIDAS givenName attribute information +   * @return formated user's givenname +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processGivenName(Object givenNameObj) throws EidasAttributeException { +    if (!(givenNameObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTGIVENNAME); +    } +    return (String) givenNameObj; +  } + +  /** +   * Post-Process the eIDAS FamilyName attribute. +   * +   * @param familyNameObj eIDAS familyName attribute information +   * @return formated user's familyname +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processFamilyName(Object familyNameObj) throws EidasAttributeException { +    if (!(familyNameObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_CURRENTFAMILYNAME); +    } +    return (String) familyNameObj; +  } + +  /** +   * Post-Process the eIDAS personal identifier attribute. +   * +   * @param personalIdentifierObj eIDAS personal identifier attribute-information +   * @return formated user's full personal identifier +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processPersonalIdentifier(Object personalIdentifierObj) throws EidasAttributeException { +    if (!(personalIdentifierObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } +    return (String) personalIdentifierObj; +  } +   +   +  /** +   * Post-Process the eIDAS pseudonym to ERnB unique identifier. +   * +   * @param personalIdObj eIDAS PersonalIdentifierAttribute +   * @return Unique personal identifier without country-code information +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processPseudonym(Object personalIdObj) throws EidasAttributeException { +    if (!(personalIdObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); +    if (eIdentifier == null || eIdentifier.getThird() == null) { +      throw new EidasAttributeException("Error processing eIdentifier"); +    } +    return eIdentifier.getThird(); +  } + +  /** +   * Post-Process the eIDAS pseudonym to citizen country code. +   * +   * @param personalIdObj eIDAS PersonalIdentifierAttribute +   * @return Citizen Country Code +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processCountryCode(Object personalIdObj) throws EidasAttributeException { +    if (!(personalIdObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_PERSONALIDENTIFIER); +    } +    final Triple<String, String, String> eIdentifier = +        EidasResponseUtils.parseEidasPersonalIdentifier((String) personalIdObj); +    if (eIdentifier == null || eIdentifier.getFirst() == null) { +      throw new EidasAttributeException("Error processing eIdentifier"); +    } +    return eIdentifier.getFirst(); +  } + +  /** +   * Post-Process the eIDAS TaxReference attribute. +   * +   * @param taxReferenceObj eIDAS TaxReference attribute information +   * @return formated user's TaxReference +   * @throws EidasAttributeException if NO attribute is available +   */ +  public static String processTaxReference(Object taxReferenceObj) throws EidasAttributeException { +    if (!(taxReferenceObj instanceof String)) { +      throw new EidasAttributeException(Constants.eIDAS_ATTR_TAXREFERENCE); +    } +    return (String) taxReferenceObj; +  } +}
\ No newline at end of file diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java index 70290cd3..10acf3ad 100644 --- a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/LoggingHandler.java @@ -41,17 +41,23 @@ public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {    @Override    public boolean handleMessage(SOAPMessageContext context) { -    final SOAPMessage msg = context.getMessage(); -    final ByteArrayOutputStream bos = new ByteArrayOutputStream(); +    // only perform operations if logging is on trace level     +    if (log.isTraceEnabled()) { +      final SOAPMessage msg = context.getMessage(); +      final ByteArrayOutputStream bos = new ByteArrayOutputStream(); +      final boolean request = ((Boolean) context +          .get(SOAPMessageContext.MESSAGE_OUTBOUND_PROPERTY)).booleanValue(); +      try { +        msg.writeTo(bos); +        log.trace("{} Web-Service with content: {}",  +            request ? "Requesting to" : "Response from",  bos.toString("UTF-8")); -    try { -      msg.writeTo(bos); -      log.trace(bos.toString("UTF-8")); -      log.trace(new String(bos.toByteArray(), "UTF-8")); - -    } catch (final Exception e) { -      log.trace(e.getMessage(), e); +      } catch (final Exception e) { +        log.trace(e.getMessage(), e); +             +      }      } +          return true;    } diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java new file mode 100644 index 00000000..c8a1f190 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/MatchingTaskUtils.java @@ -0,0 +1,192 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; + +import java.io.Serializable; +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; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.ICcSpecificEidProcessingService; +import at.asitplus.eidas.specific.modules.auth.eidas.v2.service.RegisterSearchService.RegisterStatusResults; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; +import at.gv.egiz.eaaf.core.impl.idp.auth.data.AuthProcessDataWrapper; +import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.AttributeValue; +import eu.eidas.auth.commons.protocol.eidas.impl.PostalAddress; + +public class MatchingTaskUtils { + +  /** +   * Get eIDAS log-in information from session. +   * +   * @param pendingReq Current pendingRequest +   * @return eIDAS infos or <code>null</code> if not exist +   */ +  @Nullable +  public static SimpleEidasData getInitialEidasData(IRequest pendingReq) { +    return getAuthProcessDataWrapper(pendingReq).getGenericDataFromSession( +        Constants.DATA_SIMPLE_EIDAS, SimpleEidasData.class); + +  } + +  /** +   * Set eIDAS log-in information to session. +   * +   * @param pendingReq Current pendingRequest +   * @param eidasData  infos from eIDAS Proxy-Service +   * @throws EaafStorageException In case of data can not be add into session +   */ +  @Nullable +  public static void storeInitialEidasData(IRequest pendingReq, SimpleEidasData eidasData) +      throws EaafStorageException { +    getAuthProcessDataWrapper(pendingReq).setGenericDataToSession(Constants.DATA_SIMPLE_EIDAS, eidasData); + +  } + +  /** +   * Get intermediate matching result from session. +   * +   * @param pendingReq Current pendingRequest +   * @return Intermediate matching result or <code>null</code> if not exist +   */ +  @Nullable +  public static RegisterStatusResults getIntermediateMatchingResult(IRequest pendingReq) { +    return getAuthProcessDataWrapper(pendingReq).getGenericDataFromSession(Constants.DATA_INTERMEDIATE_RESULT, +        RegisterStatusResults.class); + +  } +       +  /** +   * Store intermediate matching result into session. +   * +   * @param pendingReq   Current pendingRequest +   * @param registerData Intermediate matching result information +   * @throws EaafStorageException In case of data can not be add into session +   */ +  @Nullable +  public static void storeIntermediateMatchingResult(IRequest pendingReq, RegisterStatusResults registerData) +      throws EaafStorageException { +    getAuthProcessDataWrapper(pendingReq).setGenericDataToSession( +        Constants.DATA_INTERMEDIATE_RESULT, registerData); + +  } + +  /** +   * Get intermediate matching result from session. +   * +   * @param pendingReq Current pendingRequest +   * @return Intermediate matching result or <code>null</code> if not exist +   */ +  @Nullable +  public static MatchedPersonResult getFinalMatchingResult(IRequest pendingReq) { +    return getAuthProcessDataWrapper(pendingReq).getGenericDataFromSession(Constants.DATA_PERSON_MATCH_RESULT, +        MatchedPersonResult.class); + +  } + +  /** +   * Store intermediate matching result into session. +   * +   * @param pendingReq  Current pendingRequest +   * @param personInfos Person information after a successful match +   * @throws EaafStorageException In case of data can not be add into session +   */ +  @Nullable +  public static void storeFinalMatchingResult(IRequest pendingReq, MatchedPersonResult personInfos) +      throws EaafStorageException { +    getAuthProcessDataWrapper(pendingReq).setGenericDataToSession( +        Constants.DATA_PERSON_MATCH_RESULT, personInfos); + +  } + +  /** +   * Get holder for authentication information for the current process. +   * +   * @param pendingReq Current pendingRequest +   * @return {@link AuthProcessDataWrapper} +   */ +  @NonNull +  public static AuthProcessDataWrapper getAuthProcessDataWrapper(IRequest pendingReq) { +    return pendingReq.getSessionData(AuthProcessDataWrapper.class); + +  } + +   +  /** +   * Evaluate a flag on Execution context. +   *  +   * @param executionContext Current execution context. +   * @param key Parameter name +   * @return <code>true</code> if the parameter exists and evaluates to <code>true</code>, otherwise <code>false</code> +   */ +  public static boolean getExecutionContextFlag(ExecutionContext executionContext, String key) { +    Serializable value = executionContext.get(key);         +    return  value instanceof Boolean && (boolean)value  +        || value instanceof String && Boolean.parseBoolean((String) value); +     +  } +   +  /** +   * Convert attributes from eIDAS Authn Response into a simple map, to be used from +   * {@link ICcSpecificEidProcessingService#postProcess(Map)}. +   */ +  public static Map<String, Object> convertEidasAttrToSimpleMap( +      ImmutableMap<AttributeDefinition<?>, ImmutableSet<? extends AttributeValue<?>>> attributeMap, Logger log) { +    final Map<String, Object> result = new HashMap<>(); +    for (final AttributeDefinition<?> el : attributeMap.keySet()) { +      final Class<?> parameterizedType = el.getParameterizedType(); +      if (DateTime.class.equals(parameterizedType)) { +        final DateTime attribute = EidasResponseUtils.translateDateAttribute(el, attributeMap.get(el).asList()); +        if (attribute != null) { +          result.put(el.getFriendlyName(), attribute); +          log.trace("Find attr '{}' with value: {}", el.getFriendlyName(), attribute); +        } else { +          log.info("Ignore empty 'DateTime' attribute: {}", el.getNameUri()); +        } +      } else if (PostalAddress.class.equals(parameterizedType)) { +        final PostalAddress addressAttribute = EidasResponseUtils +            .translateAddressAttribute(el, attributeMap.get(el).asList()); +        if (addressAttribute != null) { +          result.put(el.getFriendlyName(), addressAttribute); +          log.trace("Find attr '{}' with value: {}", el.getFriendlyName(), addressAttribute); +        } else { +          log.info("Ignore empty 'PostalAddress' attribute: {}", el.getNameUri()); +        } +      } else { +        final List<String> natPersonIdObj = EidasResponseUtils.translateStringListAttribute(el, attributeMap.get(el)); +        if (natPersonIdObj.isEmpty() || StringUtils.isEmpty(natPersonIdObj.get(0))) { +          log.info("Ignore empty 'String' attribute: {}", el.getNameUri()); +           +        } else { +          result.put(el.getFriendlyName(), natPersonIdObj.get(0)); +          log.trace("Find attr '{}' with value: {}", el.getFriendlyName(), natPersonIdObj.get(0)); + +        } +      }       +    }   +     +    log.debug("Receive #{} attributes with names: {}", result.size(), result.keySet()); +    return result; +     +  } + +  private MatchingTaskUtils() { +    //hide constructor in case of class contains only static methods + +  } + +} diff --git a/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/VersionHolder.java b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/VersionHolder.java new file mode 100644 index 00000000..dbe88d33 --- /dev/null +++ b/modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/utils/VersionHolder.java @@ -0,0 +1,40 @@ +package at.asitplus.eidas.specific.modules.auth.eidas.v2.utils; + +import java.util.Optional; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.ApplicationContext; + +/** + * SpringBoot based implementation of an application-version holder. + *  + * @author tlenz + * + */ +public class VersionHolder { + +  private final String version; + +  /** +   * Build up a holder that contains the current version of this application. +   *  +   * @param context SprintBoot context +   */ +  public VersionHolder(ApplicationContext context) { +    version = context.getBeansWithAnnotation(SpringBootApplication.class).entrySet().stream() +        .findFirst()                 +        .flatMap(es -> Optional.ofNullable(es.getValue().getClass().getPackage().getImplementationVersion())) +        .orElse("unknown"); +     +  } + +  /** +   * Get version of this application. +   *  +   * @return version +   */ +  public String getVersion() { +    return version; +     +  } +} | 
