diff options
| author | Thomas <> | 2022-02-03 20:28:50 +0100 | 
|---|---|---|
| committer | Thomas <> | 2022-02-08 09:35:52 +0100 | 
| commit | 84bb45ecb417f6a92b7d8884048bd7901c6b1b42 (patch) | |
| tree | fe4d921881009d747400f2ba88c6f8b8ed578424 /eidas_modules/authmodule-eIDAS-v2/src | |
| parent | 837ce1a4fbece82fd84ab77fdd1eb0284ad2106a (diff) | |
| download | National_eIDAS_Gateway-84bb45ecb417f6a92b7d8884048bd7901c6b1b42.tar.gz National_eIDAS_Gateway-84bb45ecb417f6a92b7d8884048bd7901c6b1b42.tar.bz2 National_eIDAS_Gateway-84bb45ecb417f6a92b7d8884048bd7901c6b1b42.zip | |
fix(ernp): fix some bugs in ERnP client implementation
Diffstat (limited to 'eidas_modules/authmodule-eIDAS-v2/src')
3 files changed, 180 insertions, 111 deletions
| diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java index d48d69bf..6da30299 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/Constants.java @@ -166,8 +166,6 @@ public class Constants {    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_DEBUG_TRACEMESSAGES = CONIG_PROPS_EIDAS_ERNPCLIENT -      + ".debug.logfullmessages";    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 diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java index a651385f..605f1d0f 100644 --- a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/clients/ernp/ErnpRestClient.java @@ -2,6 +2,7 @@ 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.Collections; @@ -12,9 +13,11 @@ import java.util.stream.Collectors;  import javax.annotation.Nonnull;  import javax.annotation.PostConstruct; +import org.apache.commons.lang3.StringUtils;  import org.apache.http.client.HttpClient;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType;  import org.springframework.http.client.ClientHttpRequestFactory;  import org.springframework.http.client.ClientHttpResponse;  import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; @@ -23,10 +26,10 @@ import org.springframework.http.converter.json.MappingJackson2HttpMessageConvert  import org.springframework.lang.NonNull;  import org.springframework.lang.Nullable;  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.ObjectMapper;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.Constants;  import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.RegisterResult; @@ -42,6 +45,7 @@ import at.asitplus.eidas.specific.modules.auth.eidas.v2.dao.ernp.model.SuchenRes  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.zmr._20040201.PersonSuchenRequest; @@ -60,17 +64,18 @@ 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_01 = "module.eidasauth.matching.01"; -  private static final String ERROR_MATCHING_02 = "module.eidasauth.matching.02"; +  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 = @@ -78,69 +83,73 @@ public class ErnpRestClient implements IErnpClient {    private static final String LOGMSG_ERNP_SOAP_ERROR =        "ERnP anwser for transaction: {0} with code: {1} and message: {2}"; -   -  private static final String PROCESS_SEARCH_PERSONAL_IDENTIFIER =  + +  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 FRIENDLYNAME_HTTP_CLIENT = "ERnP Client"; -     -  private static final String PATTERN_BIRTHDAY_STRING = "{0}-{1}-{2}"; -   -   -  @Autowired IConfiguration basicConfig; -  @Autowired EaafKeyStoreFactory keyStoreFactory; -  @Autowired IHttpClientFactory httpClientFactory; -  @Autowired VersionHolder versionHolder; -   + +  @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 -      GenericRequestParams generic = buildGenericRequestParameters("step1"); +      final GenericRequestParams generic = buildGenericRequestParameters("step1"); -      // build search request     -      SuchEidas eidasInfos = new SuchEidas(); +      // build search request +      final SuchEidas eidasInfos = new SuchEidas();        eidasInfos.setArt(Constants.eIDAS_ATTRURN_PERSONALIDENTIFIER);        eidasInfos.setWert(personIdentifier);        eidasInfos.setStaatscode2(citizenCountryCode); -         -      PersonSuchen personSuchen = new PersonSuchen(); + +      final PersonSuchen personSuchen = new PersonSuchen();        personSuchen.setSuchoptionen(generateSearchParameters()); -      personSuchen.setBegruendung(PROCESS_SEARCH_PERSONAL_IDENTIFIER);         -      Suchdaten searchInfos = new Suchdaten(); -      searchInfos.setEidas(eidasInfos);         +      personSuchen.setBegruendung(PROCESS_SEARCH_PERSONAL_IDENTIFIER); +      final Suchdaten searchInfos = new Suchdaten(); +      searchInfos.setEidas(eidasInfos);        personSuchen.setSuchdaten(searchInfos); -         +        // request ERnP        log.trace("Requesting ERnP for '{}' operation", PROCESS_SEARCH_PERSONAL_IDENTIFIER); -      SuchenResponse resp = ernpClient.suchen(generic.getClientBehkz(), generic.clientName,  +      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 (EidasSAuthenticationException e) { +    } catch (final EidasSAuthenticationException e) {        throw e; -       -    } catch (Exception 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 { @@ -164,54 +173,66 @@ public class ErnpRestClient implements IErnpClient {        String zipcode, String city, String street) {      return new ErnpRegisterResult(Collections.emptyList());    } -   +    @PostConstruct    private void initialize() throws EaafException { -    // set-up the Ernp client -    ernpClient = new DefaultApi(new ApiClient(buildRestClient())); -          // 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() { -    // TODO Auto-generated method stub -     +    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() { -    Suchoptionen options = new Suchoptionen(); +    final Suchoptionen options = new Suchoptionen();      options.setZmr(false);      options.setHistorisch(HistorischEnum.AKTUELLUNDHISTORISCH);      options.setSucheMitNamensteilen(false); -    options.setSuchwizard(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  +      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",  + +    } 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 @@ -224,22 +245,22 @@ public class ErnpRestClient implements IErnpClient {          .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,  +      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) { @@ -254,15 +275,28 @@ public class ErnpRestClient implements IErnpClient {          .placeOfBirth(selectSingleEidasDocument(person, citizenCountryCode,              Constants.eIDAS_ATTRURN_PLACEOFBIRTH))          .birthName(selectSingleEidasDocument(person, citizenCountryCode, -            Constants.eIDAS_ATTRURN_BIRTHNAME))         +            Constants.eIDAS_ATTRURN_BIRTHNAME))          .build();    } -    + +  /** +   * 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 buildTextualBirthday(PartialDate geburtsdatum) { -    return MessageFormat.format(PATTERN_BIRTHDAY_STRING,  -        geburtsdatum.getJahr(), geburtsdatum.getMonat(), geburtsdatum.getTag()); -     +    return MessageFormat.format("{0}-{1}-{2}", +        String.valueOf(geburtsdatum.getJahr()), +        String.format("%02d", geburtsdatum.getMonat()), +        String.format("%02d", geburtsdatum.getTag())); +    }    /** @@ -276,12 +310,18 @@ public class ErnpRestClient implements IErnpClient {     */    @NonNull    private List<String> selectAllEidasDocument(Person person, String citizenCountryCode, -      String eidasAttrurnPersonalidentifier) {     -    return person.getEidas().stream() -        .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getArt()) -            && el.getStaatscode2().equals(citizenCountryCode)) -        .map(el -> el.getWert()) -        .collect(Collectors.toList()); +      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(); + +    }    } @@ -297,15 +337,20 @@ public class ErnpRestClient implements IErnpClient {    @Nullable    private String selectSingleEidasDocument(Person person, String citizenCountryCode,        String eidasAttrurnPersonalidentifier) { -    return person.getEidas().stream() -        .filter(el -> eidasAttrurnPersonalidentifier.equals(el.getArt()) -            && el.getStaatscode2().equals(citizenCountryCode)) -        .findFirst() -        .map(el -> el.getWert()) -        .orElse(null); +    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()); @@ -316,42 +361,44 @@ public class ErnpRestClient implements IErnpClient {      return springClient;    } -   +    private HttpMessageConverter<?> buildCustomJacksonObjectMapper() { -    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); -    ObjectMapper objectMapper = new ObjectMapper(); -    objectMapper.setSerializationInclusion(Include.NON_NULL); -    converter.setObjectMapper(objectMapper);     +    final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); +    converter.setSupportedMediaTypes(Collections.singletonList(MediaType.ALL)); +    converter.getObjectMapper() +        .setSerializationInclusion(Include.NON_NULL);      return converter; -     +    } -   +    @Nonnull    private ResponseErrorHandler buildErrorHandler() {      return new ResponseErrorHandler() {        @Override        public boolean hasError(ClientHttpResponse response) throws IOException { -         return response.getStatusCode().is4xxClientError() -             || response.getStatusCode().is5xxServerError(); -          +        return response.getStatusCode().is4xxClientError() +            || response.getStatusCode().is5xxServerError(); +        }        @Override -      public void handleError(ClientHttpResponse response) throws IOException {         -        //TODO: implement errorHandling based on response infos -         +      public void handleError(ClientHttpResponse response) throws IOException { +        // TODO: implement errorHandling based on response infos +          if (response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR) {            log.warn("Receive http-server-error: {} from ERnP", response.getRawStatusCode()); - +                    } else if (response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR) {            log.warn("Receive http-client-error: {} from ERnP", response.getRawStatusCode()); - +                    } +         +        throw new ErnpRestCommunicationException(response.getRawStatusCode());        }      };    } -   +    @Nonnull    private HttpClientConfiguration buildHttpClientConfiguration() throws EaafException {      final HttpClientConfiguration config = new HttpClientConfiguration(FRIENDLYNAME_HTTP_CLIENT); @@ -369,39 +416,39 @@ public class ErnpRestClient implements IErnpClient {          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  -     +    // 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)) +        .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();     -     +        .build(); +    } -   +    @Builder    @Getter -  private static class GenericRequestParams {     +  private static class GenericRequestParams {      String clientBehkz; -    String clientName;     -    OffsetDateTime clientRequestTime;  +    String clientName; +    OffsetDateTime clientRequestTime;      String clientRequestId; -     +    }  } diff --git a/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java new file mode 100644 index 00000000..72a5ece6 --- /dev/null +++ b/eidas_modules/authmodule-eIDAS-v2/src/main/java/at/asitplus/eidas/specific/modules/auth/eidas/v2/exception/ErnpRestCommunicationException.java @@ -0,0 +1,24 @@ +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; +   +  public ErnpRestCommunicationException(int rawStatusCode) { +    super("ERnP service answers with an error and HTTP status-code: " + rawStatusCode); +    this.httpStatusCode = rawStatusCode;     +  } +   +} | 
