diff options
Diffstat (limited to 'modules/eidas_proxy-sevice/src/main')
17 files changed, 1773 insertions, 0 deletions
| diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/EidasProxyMessageSource.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/EidasProxyMessageSource.java new file mode 100644 index 00000000..23390da8 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/EidasProxyMessageSource.java @@ -0,0 +1,22 @@ +package at.asitplus.eidas.specific.modules.msproxyservice; + +import java.util.Arrays; +import java.util.List; + +import at.gv.egiz.eaaf.core.api.logging.IMessageSourceLocation; + +/** + * i18n Message-Source for eIDAS Proxy-Service messages. + *  + * @author tlenz + * + */ +public class EidasProxyMessageSource implements IMessageSourceLocation { + +  @Override +  public List<String> getMessageSourceLocation() { +    return Arrays.asList("classpath:messages/eidasproxy_messages"); +     +  } + +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceConstants.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceConstants.java new file mode 100644 index 00000000..a2a2e78f --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceConstants.java @@ -0,0 +1,59 @@ +package at.asitplus.eidas.specific.modules.msproxyservice; + +import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants; +import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; + +/** + * Constants for MS-specific eIDAS Proxy-Service. + *  + * @author tlenz + * + */ +public class MsProxyServiceConstants { + +  // general constants +  public static final String TEMPLATE_SP_UNIQUE_ID = "eidasProxyAuth_from_{0}_type_{1}"; +   +  // configuration constants +  public static final String CONIG_PROPS_EIDAS_PROXY_NODE_ENTITYID = EidasConstants.CONIG_PROPS_EIDAS_NODE +      + ".proxy.entityId"; +  public static final String CONIG_PROPS_EIDAS_PROXY_NODE_FORWARD_URL = EidasConstants.CONIG_PROPS_EIDAS_NODE +      + ".proxy.forward.endpoint"; +   +   +  public static final String CONIG_PROPS_EIDAS_PROXY_ATTIBUTE_CONFIGURATION =  +      EidasConstants.CONIG_PROPS_EIDAS_PREFIX + ".proxy.attribute.mapping.config"; +   +   +  // mandate configuration +  public static final String CONIG_PROPS_EIDAS_PROXY_MANDATES_ENABLED =  +      EidasConstants.CONIG_PROPS_EIDAS_PREFIX + ".proxy.mandates.enabled";   +  public static final String CONIG_PROPS_EIDAS_PROXY_MANDATES_PROFILE_DEFAULT_NATURAL =  +      EidasConstants.CONIG_PROPS_EIDAS_PREFIX + ".proxy.mandates.profiles.natural.default"; +  public static final String CONIG_PROPS_EIDAS_PROXY_MANDATES_PROFILE_DEFAULT_LEGAL =  +      EidasConstants.CONIG_PROPS_EIDAS_PREFIX + ".proxy.mandates.profiles.legal.default"; +   +   +  public static final String CONIG_PROPS_EIDAS_PROXY_WORKAROUND_MANDATES_LEGAL_PERSON =  +      EidasConstants.CONIG_PROPS_EIDAS_PREFIX + ".proxy.workaround.mandates.legalperson"; +   +  // specific eIDAS-Connector configuration +  public static final String CONIG_PROPS_CONNECTOR_PREFIX = "connector"; +  public static final String CONIG_PROPS_CONNECTOR_UNIQUEID = EaafConfigConstants.SERVICE_UNIQUEIDENTIFIER; +  public static final String CONIG_PROPS_CONNECTOR_COUNTRYCODE = "countryCode";   +  public static final String CONIG_PROPS_CONNECTOR_MANDATES_ENABLED = "mandates.enabled"; +  public static final String CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_NATURAL = "mandates.natural";  +  public static final String CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_LEGAL = "mandates.legal"; +  public static final String CONIG_PROPS_CONNECTOR_VALIDATION_ATTR_MDS = "validation.attributes.mds"; +   +   +  //http end-points +  public static final String EIDAS_HTTP_ENDPOINT_IDP_POST = "/eidas/light/idp/post"; +  public static final String EIDAS_HTTP_ENDPOINT_IDP_REDIRECT = "/eidas/light/idp/redirect"; +   +  private MsProxyServiceConstants() { +   //private constructor for class with only constant values +     +  } +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceSpringResourceProvider.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceSpringResourceProvider.java new file mode 100644 index 00000000..571ad8ab --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceSpringResourceProvider.java @@ -0,0 +1,55 @@ +/* + * 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.msproxyservice; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import at.gv.egiz.components.spring.api.SpringResourceProvider; + +public class MsProxyServiceSpringResourceProvider implements SpringResourceProvider { + +  @Override +  public String getName() { +    return "MS-specific eIDAS Proxy-Service module"; +  } + +  @Override +  public String[] getPackagesToScan() { +    return null; +     +  } + +  @Override +  public Resource[] getResourcesToLoad() { +    final ClassPathResource eidasProxyServiceConfig =  +        new ClassPathResource("/spring/eidas_proxy-service.beans.xml", MsProxyServiceSpringResourceProvider.class); +    final ClassPathResource eidasRefImplConfig = new ClassPathResource("/eidas_v2_auth_ref_impl_config.beans.xml", +        MsProxyServiceSpringResourceProvider.class); + +         +    return new Resource[] { eidasProxyServiceConfig, eidasRefImplConfig }; +  } + +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/AttrMappingElement.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/AttrMappingElement.java new file mode 100644 index 00000000..2dffbc2d --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/AttrMappingElement.java @@ -0,0 +1,49 @@ + +package at.asitplus.eidas.specific.modules.msproxyservice.dto.attributes; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import lombok.Data; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ +    "eidasAttribute", +    "idaAttribute", +    "addionalRequiredAttributes", +    "specificAttributeHandlerClass", +    "type" +}) +@Data +public class AttrMappingElement { + +  /** +   * eIDAS specific attribute name. +   */ +  @JsonProperty("eidasAttribute") +  private String eidasAttributeName; + +  /** +   * IDA specific attribute name. +   */ +  @JsonProperty("idaAttribute") +  private IdaAttribute idaAttribute; +   +   +  @JsonProperty("addionalRequiredAttributes") +  private List<String> addionalRequiredAttributes; +   + +  @JsonProperty("specificAttributeHandlerClass") +  private String specificAttributeHandlerClass; +    +  /** +   * attribute characteristics. +   */ +  @JsonProperty("type") +  private Type type; +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/IdaAttribute.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/IdaAttribute.java new file mode 100644 index 00000000..ee5fc810 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/IdaAttribute.java @@ -0,0 +1,29 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.dto.attributes; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import lombok.Data; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ +    "basic", +    "withMandates" +}) +@Data +public class IdaAttribute { + +  /** +   * IDA attribute name, in case of simple process without mandates. +   */ +  @JsonProperty("basic") +  private String basic; + +  /** +   * IDA attribute name, in case of mandate process. +   */ +  @JsonProperty("withMandates") +  private String withMandates; +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java new file mode 100644 index 00000000..6a06a5b5 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java @@ -0,0 +1,91 @@ + +package at.asitplus.eidas.specific.modules.msproxyservice.dto.attributes; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonValue; + +import lombok.Data; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ +    "mds", +    "autoIncludeWithMandates", +    "mandator" +}) +@Data +public class Type { + +  /** +   * <code>true</code> if this attribute is part of MDS, otherwise <code>false</code>. +   */ +  @JsonProperty("mds") +  private Boolean mds; + +  /** +   * <code>true</code> if that attribute has to be included into eIDAS response in case of mandates.   +   */ +  @JsonProperty("autoIncludeWithMandates") +  private Boolean autoIncludeWithMandates; +   +  /** +   * Classifie that attribute to specific mandate modes. +   */ +  @JsonProperty("mandator") +  private Type.Mandator mandator; + +  /** +   * Mandate type in case of a mandate attriute. +   */ +  public enum Mandator { +    BOTH("both"), +    LEGAL("legal"), +    NATURAL("natural"), +    NONE("none"); + +    private final String value; +    private static final Map<String, Type.Mandator> CONSTANTS = new HashMap<>(); + +    static { +      for (final Type.Mandator c : values()) { +        CONSTANTS.put(c.value, c); +      } +    } + +    Mandator(String value) { +      this.value = value; +    } + +    @Override +    public String toString() { +      return this.value; +    } + +    @JsonValue +    public String value() { +      return this.value; +    } + +    /** +     * Build {@link Mandator} from textual representation. +     *  +     * @param value textual representation +     * @return Type of the mandator +     */ +    @JsonCreator +    public static Type.Mandator fromValue(String value) { +      final Type.Mandator constant = CONSTANTS.get(value); +      if (constant == null) { +        throw new IllegalArgumentException(value); +      } else { +        return constant; +      } +    } + +  } +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/exception/EidasProxyServiceException.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/exception/EidasProxyServiceException.java new file mode 100644 index 00000000..43592a28 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/exception/EidasProxyServiceException.java @@ -0,0 +1,19 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.exception; + +import at.gv.egiz.eaaf.core.exceptions.EaafException; + +public class EidasProxyServiceException extends EaafException { + +  private static final long serialVersionUID = 1L; + +  public EidasProxyServiceException(String errorId, Object[] params) { +    super(errorId, params); + +  } + +  public EidasProxyServiceException(String errorId, Object[] params, Throwable e) { +    super(errorId, params, e); +     +  } + +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/EJusticePersonRoleHandler.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/EJusticePersonRoleHandler.java new file mode 100644 index 00000000..6a5e4967 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/EJusticePersonRoleHandler.java @@ -0,0 +1,138 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.handler; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; + +import at.asitplus.eidas.specific.core.config.ServiceProviderConfiguration; +import at.gv.egiz.eaaf.core.api.data.ExtendedPvpAttributeDefinitions.SpMandateModes; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.idp.IEidAuthData; +import at.gv.egiz.eaaf.core.api.idp.IExtendedConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +/** + * Attribute handling to integrate BORIS attributes without full IDA support for sector-specific attributes. + *  + * <p>This attribute-handler maps a specific mandate-profile to an eIDAS attribute.</p> + *  + * @author tlenz + * + */ +@Slf4j +public class EJusticePersonRoleHandler implements IEidasAttributeHandler { + +  public static final String CONFIG_PROP_IDA_MANDATE_PROFILE = "advanced.attributes.ejusticerole.mandate.profiles"; +  public static final String CONFIG_PROP_IDA_MANDATE_MODE = "advanced.attributes.ejusticerole.mandate.mode"; +  public static final String CONFIG_PROP_IDA_ADDITIONAL_ATTRIBUTES =  +      "advanced.attributes.ejusticerole.additional.ida.attributes";     +   +  public static final String CONFIG_PROP_RESULT_PREFIX = "advanced.attributes.ejusticerole.value"; +  public static final String CONFIG_PROP_RESULT_VALUE_DELIMITER = "="; +   +   +  @Autowired IExtendedConfiguration config; +   +  private SpMandateModes mandateMode; +  private List<String> mandateProfiles; +  private List<String> additionalReqAttributes; +  private Map<String, String> resultMapper; +   +  @Override +  public void performSpConfigPostprocessing(ServiceProviderConfiguration spConfig) {     +    spConfig.setMandateMode(mandateMode); +    spConfig.setMandateProfiles(mandateProfiles); +    log.info("Enforcing mandate-mode: {} with profile: {}", mandateMode, mandateProfiles); +     +    if (!additionalReqAttributes.isEmpty()) { +      spConfig.getRequestedAttributes().addAll(additionalReqAttributes); +      log.info("Add additional requested attributes: {}", additionalReqAttributes); +       +    } +     +  } +   +  @Override +  public String buildAttributeValue(@NonNull IEidAuthData eidAuthData) { +    final String mandateType = eidAuthData.getGenericData( +        PvpAttributeDefinitions.MANDATE_TYPE_NAME, String.class);     +    if (StringUtils.isNotEmpty(mandateType)) {       +      String attrValue = resultMapper.get(mandateType); +      if (StringUtils.isNotEmpty(attrValue)) { +        log.debug("Mapping mandate-type: {} to EJusticePersonRole: {}", mandateType, attrValue); +        return attrValue; +         +      } else { +        log.info("Ignore mandate-type: {}, because it is not mapped to a EJusticePersonRole", mandateType); +         +      } +             +    } else { +      log.warn("Can not build: EJusticePersonRole, because IDA response contains no attribute: ", +          PvpAttributeDefinitions.MANDATE_TYPE_NAME); +       +    } +     +     +    return null; +     +  } +   +   +  @PostConstruct +  private void initialize() throws EaafConfigurationException {     +    mandateMode = SpMandateModes.fromString(loadConfigValue(CONFIG_PROP_IDA_MANDATE_MODE, true)); +    mandateProfiles = KeyValueUtils.getListOfCsvValues(loadConfigValue(CONFIG_PROP_IDA_MANDATE_PROFILE, true));   +    additionalReqAttributes = KeyValueUtils.getListOfCsvValues( +        loadConfigValue(CONFIG_PROP_IDA_ADDITIONAL_ATTRIBUTES, false)); +    resultMapper = config.getBasicConfigurationWithPrefix(CONFIG_PROP_RESULT_PREFIX).values().stream() +        .filter(el -> el.contains(CONFIG_PROP_RESULT_VALUE_DELIMITER))         +        .collect(Collectors.toMap(x -> split(x, 0), x -> split(x, 1)));                             +       +    // validate requested profiles to result map +    Optional<String> missingConfig = mandateProfiles.stream() +        .filter(el -> !resultMapper.containsKey(el)) +        .findFirst();        +    if (missingConfig.isPresent()) { +      log.error("Missing mandate-profile: {} in result mapping", missingConfig.get()); +      throw new EaafConfigurationException("internal.configuration.00",  +          new Object[]{CONFIG_PROP_RESULT_PREFIX});  +       +    } +                                     +    log.info("Initialize: {} with mandate-profile: {} mandate-mode: {} and result-map:",  +        EJusticePersonRoleHandler.class.getSimpleName(), mandateProfiles, mandateMode); +    resultMapper.entrySet().stream().forEach(el ->  +        log.info("Profile: {} --> Attribute-Value: {}", el.getKey(), el.getValue())); + +     +  } + +  private String split(String value, int i) { +    return value.split(CONFIG_PROP_RESULT_VALUE_DELIMITER, 2)[i]; +     +  } + + +  private String loadConfigValue(String configProp, boolean isRequired) throws EaafConfigurationException { +    String value = config.getBasicConfiguration(configProp); +    if (StringUtils.isEmpty(value) && isRequired) { +      throw new EaafConfigurationException("internal.configuration.00",  +          new Object[]{configProp});   +       +    } +     +    return value; + +  } +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/IEidasAttributeHandler.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/IEidasAttributeHandler.java new file mode 100644 index 00000000..5a9c8d8c --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/IEidasAttributeHandler.java @@ -0,0 +1,36 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.handler; + +import javax.annotation.Nullable; + +import at.asitplus.eidas.specific.core.config.ServiceProviderConfiguration; +import at.gv.egiz.eaaf.core.api.idp.IEidAuthData; +import lombok.NonNull; + +/** + * Handlers for attribute-processing that requires more features than a simple mapping. + *  + * @author tlenz + * + */ +public interface IEidasAttributeHandler { + +  /** +   * Perform attribute-releated post-processing of internal Service-Provider configuration. +   *  +   * @param spConfig SP configuration that was build from incoming eIDAS Authn. request. +   */ +  void performSpConfigPostprocessing(@NonNull ServiceProviderConfiguration spConfig); + +   +  /** +   * Build eIDAS attribute-value from authentication data. +   *  +   * @param eidAuthData Authentication data for current process +   * @return attribute-value if attribute is available, otherwise <code>null</code> +   */ +  @Nullable +  String buildAttributeValue(@NonNull IEidAuthData eidAuthData); + +   +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/EidasProxyServiceController.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/EidasProxyServiceController.java new file mode 100644 index 00000000..d0e3d1ba --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/EidasProxyServiceController.java @@ -0,0 +1,524 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.protocol; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml.saml2.core.NameIDType; +import org.opensaml.saml.saml2.core.StatusCode; +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.collect.ImmutableSortedSet; +import com.google.common.collect.Streams; + +import at.asitplus.eidas.specific.core.MsEidasNodeConstants; +import at.asitplus.eidas.specific.core.config.ServiceProviderConfiguration; +import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants; +import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants; +import at.asitplus.eidas.specific.modules.msproxyservice.exception.EidasProxyServiceException; +import at.asitplus.eidas.specific.modules.msproxyservice.handler.IEidasAttributeHandler; +import at.asitplus.eidas.specific.modules.msproxyservice.service.ProxyEidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.msproxyservice.utils.EidasProxyServiceUtils; +import at.gv.egiz.components.eventlog.api.EventConstants; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.EaafConfigConstants; +import at.gv.egiz.eaaf.core.api.data.EaafConstants; +import at.gv.egiz.eaaf.core.api.data.ExtendedPvpAttributeDefinitions.SpMandateModes; +import at.gv.egiz.eaaf.core.api.idp.IModulInfo; +import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.exceptions.GuiBuildException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractController; +import at.gv.egiz.eaaf.core.impl.utils.KeyValueUtils; +import eu.eidas.auth.commons.EIDASSubStatusCode; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.impl.LightResponse; +import eu.eidas.auth.commons.light.impl.LightResponse.Builder; +import eu.eidas.auth.commons.light.impl.ResponseStatus; +import eu.eidas.specificcommunication.SpecificCommunicationDefinitionBeanNames; +import eu.eidas.specificcommunication.exception.SpecificCommunicationException; +import eu.eidas.specificcommunication.protocol.SpecificCommunicationService; +import lombok.extern.slf4j.Slf4j; + +/** + * End-point implementation for authentication requests from eIDAS Proxy-Service + * to MS-specific eIDAS Proxy-Service. + * + * @author tlenz + * + */ +@Slf4j +@Controller +public class EidasProxyServiceController extends AbstractController implements IModulInfo { + +  private static final String ERROR_01 = "eidas.proxyservice.01"; +  private static final String ERROR_02 = "eidas.proxyservice.02"; +  private static final String ERROR_03 = "eidas.proxyservice.03"; +  private static final String ERROR_04 = "eidas.proxyservice.04"; +  private static final String ERROR_05 = "eidas.proxyservice.05"; +  private static final String ERROR_07 = "eidas.proxyservice.07"; +  private static final String ERROR_08 = "eidas.proxyservice.08"; +  private static final String ERROR_09 = "eidas.proxyservice.09"; +  private static final String ERROR_10 = "eidas.proxyservice.10"; +  private static final String ERROR_11 = "eidas.proxyservice.11"; + +  public static final String PROTOCOL_ID = "eidasProxy"; + +  @Autowired +  ProxyEidasAttributeRegistry attrRegistry; +  @Autowired +  ProxyServiceAuthenticationAction responseAction; + +  /** +   * End-point that receives authentication requests from eIDAS Node. +   * +   * @param httpReq  Http request +   * @param httpResp Http response +   * @throws IOException   In case of general error +   * @throws EaafException In case of a validation or processing error +   */ +  @RequestMapping(value = { +      MsProxyServiceConstants.EIDAS_HTTP_ENDPOINT_IDP_POST, +      MsProxyServiceConstants.EIDAS_HTTP_ENDPOINT_IDP_REDIRECT +      }, +      method = { RequestMethod.POST, RequestMethod.GET }) +  public void receiveEidasAuthnRequest(HttpServletRequest httpReq, HttpServletResponse httpResp) +      throws IOException, +      EaafException { +    log.trace("Receive request on eidas proxy-service end-points"); +    ProxyServicePendingRequest pendingReq = null; +    try { +      // get token from Request +      final String tokenBase64 = httpReq.getParameter(EidasParameterKeys.TOKEN.toString()); +      if (StringUtils.isEmpty(tokenBase64)) { +        log.warn("NO eIDAS message token found."); +        throw new EidasProxyServiceException(ERROR_02, null); + +      } +      log.trace("Receive eIDAS-node token: {}. Searching authentication request from eIDAS Proxy-Service ...", +          tokenBase64); + +      // read authentication request from shared cache +      final SpecificCommunicationService specificProxyCommunicationService = +          (SpecificCommunicationService) applicationContext.getBean( +              SpecificCommunicationDefinitionBeanNames.SPECIFIC_PROXYSERVICE_COMMUNICATION_SERVICE +                  .toString()); +      final ILightRequest eidasRequest = specificProxyCommunicationService.getAndRemoveRequest( +          tokenBase64, +          ImmutableSortedSet.copyOf(attrRegistry.getCoreRegistry().getCoreAttributeRegistry() +              .getAttributes())); +      if (eidasRequest == null) { +        log.info("Find no eIDAS Authn. Request with stated token."); +        throw new EidasProxyServiceException(ERROR_11, null); + +      } + +      log.debug("Received eIDAS auth. request from: {}, Initializing authentication environment ... ", +          eidasRequest.getSpCountryCode() != null ? eidasRequest.getSpCountryCode() : "'missing SP-country'"); +      log.trace("Received eIDAS requst: {}", eidasRequest); + +      // create pendingRequest object +      pendingReq = applicationContext.getBean(ProxyServicePendingRequest.class); +      pendingReq.initialize(httpReq, authConfig); +      pendingReq.setModule(getName()); + +      // log 'transaction created' event +      revisionsLogger.logEvent(EventConstants.TRANSACTION_CREATED, +          pendingReq.getUniqueTransactionIdentifier()); +      revisionsLogger.logEvent(pendingReq.getUniqueSessionIdentifier(), +          pendingReq.getUniqueTransactionIdentifier(), EventConstants.TRANSACTION_IP, +          httpReq.getRemoteAddr()); + +      // validate eIDAS Authn. request and set into pending-request +      validateEidasAuthnRequest(eidasRequest); +      pendingReq.setEidasRequest(eidasRequest); + +      // generate Service-Provider configuration from eIDAS request +      final ISpConfiguration spConfig = generateSpConfigurationFromEidasRequest(eidasRequest); + +      // validate eIDAS Authn. request by using eIDAS Connector specifc parameters +      validateEidasAuthnRequest(spConfig, eidasRequest); + +      // populate pendingRequest with parameters +      pendingReq.setOnlineApplicationConfiguration(spConfig); +      pendingReq.setSpEntityId(spConfig.getUniqueIdentifier()); +      pendingReq.setPassiv(false); +      pendingReq.setForce(true); + +      // AuthnRequest needs authentication +      pendingReq.setNeedAuthentication(true); + +      // set protocol action, which should be executed after authentication +      pendingReq.setAction(ProxyServiceAuthenticationAction.class.getName()); + +      // switch to session authentication +      protAuthService.performAuthentication(httpReq, httpResp, pendingReq); + +    } catch (final EidasProxyServiceException e) { +      throw e; + +    } catch (final SpecificCommunicationException e) { +      log.error("Can not read eIDAS Authn request from shared cache. Reason: {}", e.getMessage()); +      throw new EidasProxyServiceException(ERROR_03, new Object[] { e.getMessage() }, e); + +    } catch (final Throwable e) { +      // write revision log entries +      if (pendingReq != null) { +        revisionsLogger.logEvent(pendingReq, EventConstants.TRANSACTION_ERROR, +            pendingReq.getUniqueTransactionIdentifier()); +      } + +      throw new EidasProxyServiceException(ERROR_01, new Object[] { e.getMessage() }, e); +    } + +  } + +  @Override +  public boolean generateErrorMessage(Throwable e, HttpServletRequest httpReq, HttpServletResponse httpResp, +      IRequest pendingReq) throws Throwable { +    if (pendingReq instanceof ProxyServicePendingRequest) { +      try { +        final ILightRequest eidasReq = ((ProxyServicePendingRequest) pendingReq).getEidasRequest(); + +        // build eIDAS response +        final Builder lightRespBuilder = LightResponse.builder(); +        lightRespBuilder.id(UUID.randomUUID().toString()); +        lightRespBuilder.inResponseToId(eidasReq.getId()); +        lightRespBuilder.relayState(eidasReq.getRelayState()); +        lightRespBuilder.issuer(authConfig.getBasicConfiguration( +            MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_ENTITYID)); +        lightRespBuilder.subject(UUID.randomUUID().toString()); +        lightRespBuilder.subjectNameIdFormat(NameIDType.TRANSIENT); +        lightRespBuilder.status(ResponseStatus.builder() +            .statusCode(StatusCode.RESPONDER) +            .subStatusCode(EIDASSubStatusCode.AUTHN_FAILED_URI.getValue()) +            .statusMessage(StringEscapeUtils.escapeXml(e.getLocalizedMessage())) +            .build()); + +        // forward to eIDAS Proxy-Service +        responseAction.forwardToEidasProxy(pendingReq, httpReq, httpResp, lightRespBuilder.build()); + +        return true; + +      } catch (ServletException | IOException | GuiBuildException e1) { +        log.warn("Forward error to eIDAS Proxy-Service FAILED. Handle error localy ... ", e1); + +      } + +    } else { +      log.error("eIDAS Proxy-Service authentication requires PendingRequest of Type: {}", +          ProxyServicePendingRequest.class.getName()); + +    } + +    return false; + +  } + +  @Override +  public String getName() { +    return EidasProxyServiceController.class.getName(); + +  } + +  @Override +  public String getAuthProtocolIdentifier() { +    return PROTOCOL_ID; + +  } + +  @Override +  public boolean validate(HttpServletRequest request, HttpServletResponse response, IRequest pending) { +    return true; + +  } + +  /** +   * Generic validation of incoming eIDAS request. +   * +   * @param eidasRequest Incoming eIDAS authentication request +   * @throws EidasProxyServiceException In case of a validation error +   */ +  private void validateEidasAuthnRequest(ILightRequest eidasRequest) throws EidasProxyServiceException { +    if (StringUtils.isEmpty(eidasRequest.getIssuer())) { +      throw new EidasProxyServiceException(ERROR_05, null); + +    } + +    // TODO: validate some other stuff + +  } + +  /** +   * eIDAS Connector specific validation of incoming eIDAS request. +   * +   * @param eidasRequest Incoming eIDAS authentication request +   * @param spConfig     eIDAS Connector configuration +   * @throws EidasProxyServiceException In case of a validation error +   */ +  private void validateEidasAuthnRequest(ISpConfiguration spConfig, ILightRequest eidasRequest) +      throws EidasProxyServiceException { +    // check if natural-person and legal-person attributes requested in parallel +    if (spConfig.isConfigurationValue(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_VALIDATION_ATTR_MDS, true) +        && EidasProxyServiceUtils.isLegalPersonRequested(eidasRequest) +        && EidasProxyServiceUtils.isNaturalPersonRequested(eidasRequest)) { +      throw new EidasProxyServiceException(ERROR_08, null); + +    } + +    // TODO: validate some other stuff + +  } + +  /** +   * Generate a dummy Service-Provider configuration for processing. +   * +   * @param eidasRequest Incoming eIDAS authentication request +   * @return Service-Provider configuration that can be used for authentication +   * @throws EidasProxyServiceException In case of a configuration error +   */ +  private ISpConfiguration generateSpConfigurationFromEidasRequest(ILightRequest eidasRequest) +      throws EidasProxyServiceException { +    try { + +      final Map<String, String> connectorConfigMap = extractRawConnectorConfiguration(eidasRequest); + +      // check if country-code is available +      final String spCountry = connectorConfigMap.get( +          MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_COUNTRYCODE); +      if (StringUtils.isEmpty(spCountry)) { +        throw new EidasProxyServiceException(ERROR_07, null); + +      } + +      // build FriendyName from CountryCode and SPType +      connectorConfigMap.put(MsEidasNodeConstants.PROP_CONFIG_SP_FRIENDLYNAME, +          MessageFormat.format(MsProxyServiceConstants.TEMPLATE_SP_UNIQUE_ID, +              spCountry, eidasRequest.getSpType())); + +      // build Service-Provider configuration object +      final ServiceProviderConfiguration spConfig = new ServiceProviderConfiguration(connectorConfigMap, +          authConfig); + +      // build bPK target from Country-Code +      final String ccCountry = authConfig.getBasicConfiguration( +          EidasConstants.CONIG_PROPS_EIDAS_NODE_COUNTRYCODE, +          EidasConstants.DEFAULT_MS_NODE_COUNTRY_CODE); +      spConfig.setBpkTargetIdentifier( +          EaafConstants.URN_PREFIX_EIDAS + ccCountry + "+" + spCountry); + +      // set required LoA from eIDAS request +      spConfig.setRequiredLoA( +          eidasRequest.getLevelsOfAssurance().stream().map(el -> el.getValue()).collect(Collectors.toList())); + +      // build mandate profiles for this specific request +      buildMandateProfileConfiguration(spConfig, eidasRequest); + +      // map eIDAS attributes to national attributes +      buildNationalRequestedAttributes(spConfig, eidasRequest); +       +      // execute custom attribute-handler +      advancedAttributeHandler(spConfig, eidasRequest); +       +      return spConfig; + +    } catch (final EidasProxyServiceException e) { +      throw e; + +    } catch (final EaafException e) { +      throw new EidasProxyServiceException(ERROR_04, new Object[] { e.getMessage() }, e); + +    } +  } +     +  private void advancedAttributeHandler(ServiceProviderConfiguration spConfig, ILightRequest eidasRequest) { +    Set<String> requiredHandlers = eidasRequest.getRequestedAttributes().getAttributeMap().keySet().stream() +        .map(el -> attrRegistry.mapEidasAttributeToAttributeHandler(el.getNameUri().toString()).orElse(null)) +        .filter(Objects::nonNull) +        .distinct() +        .collect(Collectors.toSet()); + +    if (!requiredHandlers.isEmpty()) {     +      log.info("eIDAS requested attributes requires #{} specific attribute-hander. " +          + "Starting advanced attribute-handling ... ", requiredHandlers.size());      +      requiredHandlers.forEach(el -> executeAttributeHandler(el, spConfig));       +       +    } else { +      log.debug("No advanced eIDAS attribute-handling required."); +       +    }         +  } + +  private void executeAttributeHandler(String handlerClass, ServiceProviderConfiguration spConfig) { +    try { +      IEidasAttributeHandler handler = applicationContext.getBean(handlerClass, IEidasAttributeHandler.class); +     +      log.trace("Perfom SP config post-processing by using: {}", handler.getClass().getName()); +      handler.performSpConfigPostprocessing(spConfig); +       +    } catch (Exception e) { +      log.error("No custom attribute-handler implementation for: {}. Operation can NOT be performed", handlerClass, e); +       +    }      +  } + +  private void buildNationalRequestedAttributes( +      ServiceProviderConfiguration spConfig, ILightRequest eidasRequest) { +    final boolean mandatesEnabled = !SpMandateModes.NONE.equals(spConfig.getMandateMode()); +    spConfig.setRequestedAttributes( +        Streams.concat( +            eidasRequest.getRequestedAttributes().getAttributeMap().keySet().stream() +                .map(el -> attrRegistry.getIdaAttributesForEidasAttribute( +                    el.getNameUri().toString(), mandatesEnabled)) +                .flatMap(Collection::stream) +                .filter(Objects::nonNull), +            attrRegistry.getAlwaysRequestedAttributes(mandatesEnabled)) +            .collect(Collectors.toSet())); +    log.debug("Inject #{} attributes to request from IDA system", spConfig.getRequestedAttributes().size()); + +  } + +  private Map<String, String> extractRawConnectorConfiguration(ILightRequest eidasRequest) { +    final Map<String, String> allConnectorConfigs = authConfig.getBasicConfigurationWithPrefix( +        MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_PREFIX); +    if (log.isTraceEnabled()) { +      log.trace("Full-connector configuration:"); +      allConnectorConfigs.entrySet().stream().forEach( +          el -> log.trace("Key: {} -> Value: {}", el.getKey(), el.getValue())); + +    } + +    final Map<String, String> connectorConfig = allConnectorConfigs.entrySet().stream() +        .filter(el -> el.getKey().endsWith(MsEidasNodeConstants.PROP_CONFIG_SP_UNIQUEIDENTIFIER) +            && el.getValue().equals(eidasRequest.getIssuer())) +        .findFirst() +        .map(el -> KeyValueUtils.getSubSetWithPrefix(allConnectorConfigs, +            KeyValueUtils.getParentKey(el.getKey()) + KeyValueUtils.KEY_DELIMITER)) +        .orElse(new HashMap<>()); + +    if (connectorConfig.isEmpty()) { +      log.debug("No specific configuration for eIDAS Connector: {} Using default configuration ... ", +          eidasRequest.getIssuer()); + +      // set EntityId of the requesting eIDAS Connector +      connectorConfig.put(EaafConfigConstants.SERVICE_UNIQUEIDENTIFIER, eidasRequest.getIssuer()); + +      // set country-code from eIDAS request +      connectorConfig.put(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_COUNTRYCODE, +          eidasRequest.getSpCountryCode()); + +      // set default mandate configuration +      connectorConfig.put(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_ENABLED, +          String.valueOf(authConfig.getBasicConfigurationBoolean( +              MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_MANDATES_ENABLED, false))); +      connectorConfig.put(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_NATURAL, +          authConfig.getBasicConfiguration( +              MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_MANDATES_PROFILE_DEFAULT_NATURAL)); +      connectorConfig.put(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_LEGAL, +          authConfig.getBasicConfiguration( +              MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_MANDATES_PROFILE_DEFAULT_LEGAL)); + +    } else { +      log.debug("Find specific configuration for eIDAS Connector: {}", eidasRequest.getIssuer()); + +    } + +    return connectorConfig; + +  } + +  private void buildMandateProfileConfiguration(ServiceProviderConfiguration spConfig, +      ILightRequest eidasRequest) +      throws EidasProxyServiceException { +    // check if mandates are enabled +    if (spConfig.isConfigurationValue(MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_ENABLED, +        false)) { +      injectMandateInfosIntoSpConfig(spConfig, eidasRequest); + +    } else { +      if (EidasProxyServiceUtils.isLegalPersonRequested(eidasRequest)) { +        throw new EidasProxyServiceException(ERROR_09, null); + +      } + +      spConfig.setMandateProfiles(Collections.emptyList()); +      spConfig.setMandateMode(SpMandateModes.NONE); + +    } + +  } + +  private void injectMandateInfosIntoSpConfig(ServiceProviderConfiguration spConfig, +      ILightRequest eidasRequest) throws EidasProxyServiceException { +    log.trace("eIDAS Proxy-Service allows mandates for Connector: {}. Selecting profiles ... ", +        spConfig.getUniqueIdentifier()); + +    final List<String> legalPersonProfiles = KeyValueUtils.getListOfCsvValues(spConfig.getConfigurationValue( +        MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_LEGAL)); +    final List<String> natPersonProfiles = KeyValueUtils.getListOfCsvValues(spConfig.getConfigurationValue( +        MsProxyServiceConstants.CONIG_PROPS_CONNECTOR_MANDATES_PROFILE_NATURAL)); + +    if (EidasProxyServiceUtils.isLegalPersonRequested(eidasRequest) +        && EidasProxyServiceUtils.isNaturalPersonRequested(eidasRequest)) { +      log.debug( +          "Find requested attributes for legal and natural persons. Injecting mandate-profiles for both ... "); +      spConfig.setMandateProfiles(ListUtils.union(natPersonProfiles, legalPersonProfiles)); + +      // set Mandate-Mode based on SP configuration +      final boolean isLegalPersonProfile = !legalPersonProfiles.isEmpty(); +      final boolean isNaturalPersonProfile = !natPersonProfiles.isEmpty(); +      spConfig.setMandateMode( +          isLegalPersonProfile && isNaturalPersonProfile +              ? SpMandateModes.BOTH +              : isLegalPersonProfile +                  ? SpMandateModes.LEGAL_FORCE +                  : SpMandateModes.NATURAL); + +    } else if (EidasProxyServiceUtils.isLegalPersonRequested(eidasRequest)) { +      // check if legal person is requested +      spConfig.setMandateProfiles(legalPersonProfiles); +      spConfig.setMandateMode(SpMandateModes.LEGAL_FORCE); + +      if (spConfig.getMandateProfiles().isEmpty()) { +        throw new EidasProxyServiceException(ERROR_10, null); + +      } + +    } else if (EidasProxyServiceUtils.isNaturalPersonRequested(eidasRequest)) { +      spConfig.setMandateProfiles(natPersonProfiles); +      spConfig.setMandateMode(SpMandateModes.NATURAL); + +    } + +    if (spConfig.getMandateProfiles().isEmpty()) { +      log.debug("No mandate-profiles for issure: {}. Set mandate-mode to 'none'", +          spConfig.getUniqueIdentifier()); +      spConfig.setMandateMode(SpMandateModes.NONE); + +    } else { +      log.debug("Set mandate-profiles: {} to request from issuer: {}", +          spConfig.getMandateProfiles(), spConfig.getUniqueIdentifier()); + +    } + +  } +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java new file mode 100644 index 00000000..f1cb8f0b --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java @@ -0,0 +1,373 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.protocol; + +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang3.StringUtils; +import org.opensaml.saml.saml2.core.NameIDType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.ResourceLoader; +import org.springframework.web.util.UriComponentsBuilder; + +import at.asitplus.eidas.specific.core.gui.StaticGuiBuilderConfiguration; +import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants; +import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants; +import at.asitplus.eidas.specific.modules.msproxyservice.exception.EidasProxyServiceException; +import at.asitplus.eidas.specific.modules.msproxyservice.handler.IEidasAttributeHandler; +import at.asitplus.eidas.specific.modules.msproxyservice.service.ProxyEidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.msproxyservice.utils.EidasProxyServiceUtils; +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.data.PvpAttributeDefinitions; +import at.gv.egiz.eaaf.core.api.gui.ISpringMvcGuiFormBuilder; +import at.gv.egiz.eaaf.core.api.idp.IAction; +import at.gv.egiz.eaaf.core.api.idp.IAuthData; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.IEidAuthData; +import at.gv.egiz.eaaf.core.api.idp.slo.SloInformationInterface; +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.impl.data.SloInformationImpl; +import eu.eidas.auth.commons.EidasParameterKeys; +import eu.eidas.auth.commons.attribute.AttributeDefinition; +import eu.eidas.auth.commons.attribute.ImmutableAttributeMap; +import eu.eidas.auth.commons.light.ILightRequest; +import eu.eidas.auth.commons.light.ILightResponse; +import eu.eidas.auth.commons.light.impl.LightResponse; +import eu.eidas.auth.commons.light.impl.LightResponse.Builder; +import eu.eidas.auth.commons.light.impl.ResponseStatus; +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; + +/** + * Result action of a successfully performed eIDAS Proxy-Service authentication. + * + * @author tlenz + * + */ +@Slf4j +public class ProxyServiceAuthenticationAction implements IAction { + +  private static final String PROXYSERVICE_AUTH_ACTION_NAME = "MS-specific eIDAS-Proxy action"; + +  @Autowired +  ApplicationContext context; +  @Autowired +  IConfiguration basicConfig; +  @Autowired +  ResourceLoader resourceLoader; +  @Autowired +  ISpringMvcGuiFormBuilder guiBuilder; +  @Autowired +  ProxyEidasAttributeRegistry attrRegistry; + +  @Override +  public SloInformationInterface processRequest(IRequest pendingReq, HttpServletRequest httpReq, +      HttpServletResponse httpResp, IAuthData authData) throws EaafException { +    if (pendingReq instanceof ProxyServicePendingRequest) { +      try { +        final ILightRequest eidasReq = ((ProxyServicePendingRequest) pendingReq).getEidasRequest(); + +        // build eIDAS response +        final Builder lightRespBuilder = LightResponse.builder(); +        lightRespBuilder.id(UUID.randomUUID().toString()); +        lightRespBuilder.inResponseToId(eidasReq.getId()); +        lightRespBuilder.relayState(eidasReq.getRelayState()); + +        lightRespBuilder.status(ResponseStatus.builder() +            .statusCode(EidasConstants.SUCCESS_URI) +            .build()); + +        // TODO: check if we can use transient subjectNameIds +        lightRespBuilder.subject(UUID.randomUUID().toString()); +        lightRespBuilder.subjectNameIdFormat(NameIDType.TRANSIENT); + +        // TODO: +        lightRespBuilder.issuer(basicConfig.getBasicConfiguration( +            MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_ENTITYID)); +        lightRespBuilder.levelOfAssurance(authData.getEidasQaaLevel()); +        lightRespBuilder.attributes(buildAttributesFromAuthData(authData, eidasReq)); + +        // set SLO response object of EAAF framework +        final SloInformationImpl sloInformation = new SloInformationImpl(); +        sloInformation.setProtocolType(pendingReq.requestedModule()); +        sloInformation +            .setSpEntityID(pendingReq.getServiceProviderConfiguration().getUniqueIdentifier()); + +        // forward to eIDAS Proxy-Service +        forwardToEidasProxy(pendingReq, httpReq, httpResp, lightRespBuilder.build()); + +        return sloInformation; + +      } catch (ServletException | IOException | GuiBuildException e) { +        throw new EidasProxyServiceException("eidas.proxyservice.06", null, e); + +      } + +    } else { +      log.error("eIDAS Proxy-Service authentication requires PendingRequest of Type: {}", +          ProxyServicePendingRequest.class.getName()); +      throw new EaafException("eidas.proxyservice.99"); + +    } +  } + +  @Override +  public boolean needAuthentication(IRequest req, HttpServletRequest httpReq, HttpServletResponse httpResp) { +    return true; + +  } + +  @Override +  public String getDefaultActionName() { +    return PROXYSERVICE_AUTH_ACTION_NAME; + +  } + +  /** +   * Forward eIDAS Light response to eIDAS node. +   * +   * @param pendingReq    Current pending request. +   * @param httpReq       Current HTTP request +   * @param httpResp      Current HTTP response +   * @param lightResponse eIDAS LightResponse +   * @throws EaafConfigurationException In case of a configuration error +   * @throws IOException                In case of a general error +   * @throws GuiBuildException          In case of a GUI rendering error, if http +   *                                    POST binding is used +   * @throws ServletException           In case of a general error +   */ +  public void forwardToEidasProxy(IRequest pendingReq, HttpServletRequest httpReq, +      HttpServletResponse httpResp, LightResponse lightResponse) throws EaafConfigurationException, +      IOException, +      GuiBuildException, ServletException { + +    // put request into shared cache +    final BinaryLightToken token = putResponseInCommunicationCache(lightResponse); +    final String tokenBase64 = BinaryLightTokenHelper.encodeBinaryLightTokenBase64(token); + +    // select forward URL regarding the selected environment +    final String forwardUrl = basicConfig.getBasicConfiguration( +        MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_FORWARD_URL); + +    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[] { MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_NODE_FORWARD_URL }); + +    } +    log.debug("ForwardURL: " + forwardUrl + " selected to forward eIDAS request"); + +    if (basicConfig.getBasicConfiguration( +        EidasConstants.CONIG_PROPS_EIDAS_NODE_FORWARD_METHOD, +        EidasConstants.FORWARD_METHOD_GET).equals(EidasConstants.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); +      httpResp.sendRedirect(redirectUrl.build().encode().toString()); + +    } else { +      log.debug("Use http-post for eIDAS node forwarding ...  "); +      final StaticGuiBuilderConfiguration config = new StaticGuiBuilderConfiguration( +          basicConfig, +          pendingReq, +          EidasConstants.TEMPLATE_POST_FORWARD_NAME, +          null, +          resourceLoader); + +      config.putCustomParameter(null, EidasConstants.TEMPLATE_POST_FORWARD_ENDPOINT, forwardUrl); +      config.putCustomParameter(null, EidasConstants.TEMPLATE_POST_FORWARD_TOKEN_NAME, +          EidasParameterKeys.TOKEN.toString()); +      config.putCustomParameter(null, EidasConstants.TEMPLATE_POST_FORWARD_TOKEN_VALUE, +          tokenBase64); + +      guiBuilder.build(httpReq, httpResp, config, "Forward to eIDASNode form"); + +    } +  } + +  @PostConstruct +  private void checkConfiguration() { +    // TODO: validate configuration on start-up + +  } + +  private ImmutableAttributeMap buildAttributesFromAuthData(IAuthData authData, +      ILightRequest eidasReq) { +    final IEidAuthData eidAuthData = (IEidAuthData) authData; +    final ImmutableAttributeMap.Builder attributeMap = ImmutableAttributeMap.builder(); + +    // inject all requested attributres +    injectRequestedAttributes(attributeMap, eidasReq, eidAuthData); + +    if (eidAuthData.isUseMandate()) { +      log.debug("Building eIDAS Proxy-Service response with mandate ... "); +      injectMdsRepesentativeInformation(attributeMap, eidAuthData, eidasReq.getRequestedAttributes()); + +      // work-around that injects nat. person subject to bypass validation on eIDAS +      // Node +      injectJurPersonWorkaroundIfRequired(attributeMap, eidasReq, authData); + +    } + +    return attributeMap.build(); + +  } + +  private void injectRequestedAttributes(ImmutableAttributeMap.Builder attributeMap, ILightRequest eidasReq, +      IEidAuthData eidAuthData) { +    eidasReq.getRequestedAttributes().getAttributeMap().keySet().stream() +        .forEach(el -> injectEidasAttribute(attributeMap, eidAuthData, +            el.getNameUri().toString(), eidAuthData.isUseMandate())); + +  } + +  private void injectMdsRepesentativeInformation( +      ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData, +      ImmutableAttributeMap requestedAttributes) { +    attrRegistry.getRepresentativeAttributesToAddByDefault() +        .filter(el -> requestedAttributes.getAttributeValuesByNameUri(el) == null) +        .forEach(el -> injectEidasAttribute(attributeMap, eidAuthData, el, true)); + +  } + +  private void injectEidasAttribute(ImmutableAttributeMap.Builder attributeMap, IEidAuthData eidAuthData, +      String eidasAttrName, boolean mandatesUsed) { +    final Optional<String> releatedIdaAttribute = +        attrRegistry.mapEidasAttributeToSpecificIdaAttribute(eidasAttrName, mandatesUsed); +    if (releatedIdaAttribute.isPresent()) { +      log.trace("Mapping IDA attribute: {} to eIDAS attribute: {}", releatedIdaAttribute.get(), +          eidasAttrName); +      final String idaAttrValue = eidAuthData.getGenericData(releatedIdaAttribute.get(), String.class); +      if (StringUtils.isNotEmpty(idaAttrValue)) { +        log.debug("Build eIDAS attribute: {} from IDA attribute: {}", eidasAttrName, releatedIdaAttribute +            .get()); +        attributeMap.put( +            attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByName(eidasAttrName), +            idaAttrValue); + +      } else { +        log.info("No IDA attribute: {}, eIDAS attribute: {} will be ignored", releatedIdaAttribute.get(), +            eidasAttrName); + +      } + +    } else {       +      Optional<String> advancedAttributeHandler = attrRegistry.mapEidasAttributeToAttributeHandler(eidasAttrName); +      if (advancedAttributeHandler.isPresent()) {                 +        final String idaAttrValue = context.getBean(advancedAttributeHandler.get(), IEidasAttributeHandler.class) +            .buildAttributeValue(eidAuthData);                    +        if (StringUtils.isNotEmpty(idaAttrValue)) { +          log.debug("Build eIDAS attribute: {} by advanced attribute-handler: {}",  +              eidasAttrName, advancedAttributeHandler.get()); +          attributeMap.put( +              attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByName(eidasAttrName), +              idaAttrValue); + +        } else { +          log.info("Empty attribte-value returned by advanced attribute-handler, eIDAS attribute: {} will be ignored",  +              eidasAttrName); + +        } +         +      } else { +        log.warn("Can not build eIDAS attribute: {}, because there is not corresponding IDA attribute defined", +            eidasAttrName);   +         +      } +    } +  } + +  private BinaryLightToken putResponseInCommunicationCache(ILightResponse lightResponse) +      throws ServletException { +    final BinaryLightToken binaryLightToken; +    try { +      final SpecificCommunicationService springManagedSpecificConnectorCommunicationService = +          (SpecificCommunicationService) context.getBean( +              SpecificCommunicationDefinitionBeanNames.SPECIFIC_PROXYSERVICE_COMMUNICATION_SERVICE +                  .toString()); + +      binaryLightToken = springManagedSpecificConnectorCommunicationService.putResponse(lightResponse); + +    } catch (final SpecificCommunicationException e) { +      log.error("Unable to process specific request"); +      throw new ServletException(e); + +    } + +    return binaryLightToken; +  } + +  /** +   * Work-around to inject representative information as nat. person subject to +   * bypass eIDAS Node validation. +   * +   * <p> +   * <b>Injection will only be done if this work-around is enabled by +   * configuration, the mandator is a legal person, and both legal and natural +   * person subject's is requested.</b> +   * </p> +   * +   * @param attributeMap Attribute set for eIDAS response +   * @param eidasReq     Incoming eIDAS request +   * @param authData     Authentication data +   */ +  private void injectJurPersonWorkaroundIfRequired( +      ImmutableAttributeMap.Builder attributeMap, ILightRequest eidasReq, IAuthData authData) { +     +    //TODO: maybe we have update that check, because we need an activation on Connector level +    if (isLegalPersonWorkaroundActive() && isLegalPersonMandateAvailable(authData) +        && EidasProxyServiceUtils.isNaturalPersonRequested(eidasReq) +        && EidasProxyServiceUtils.isLegalPersonRequested(eidasReq)) { +      log.debug( +          "Injecting representative information as nat. person subject to bypass eIDAS Node validation"); + +      final AttributeDefinition<?> attrDefPersonalId = +          attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( +              EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER).first(); +      final AttributeDefinition<?> attrDefFamilyName = +          attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( +              EidasConstants.eIDAS_ATTR_CURRENTFAMILYNAME).first(); +      final AttributeDefinition<?> attrDefGivenName = +          attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( +              EidasConstants.eIDAS_ATTR_CURRENTGIVENNAME).first(); +      final AttributeDefinition<?> attrDefDateOfBirth = +          attrRegistry.getCoreRegistry().getCoreAttributeRegistry().getByFriendlyName( +              EidasConstants.eIDAS_ATTR_DATEOFBIRTH).first(); + +      attributeMap.put(attrDefPersonalId, authData.getGenericData(PvpAttributeDefinitions.BPK_NAME, +          String.class)); +      attributeMap.put(attrDefFamilyName, authData.getFamilyName()); +      attributeMap.put(attrDefGivenName, authData.getGivenName()); +      attributeMap.put(attrDefDateOfBirth, authData.getDateOfBirth()); + +    } +  } + +  private boolean isLegalPersonWorkaroundActive() { +    return basicConfig.getBasicConfigurationBoolean( +        MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_WORKAROUND_MANDATES_LEGAL_PERSON, +        false); + +  } + +  private boolean isLegalPersonMandateAvailable(IAuthData authData) { +    return StringUtils.isNoneEmpty(authData.getGenericData( +        PvpAttributeDefinitions.MANDATE_LEG_PER_SOURCE_PIN_NAME, String.class)); + +  } + +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServicePendingRequest.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServicePendingRequest.java new file mode 100644 index 00000000..a3b5007a --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServicePendingRequest.java @@ -0,0 +1,28 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.protocol; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import at.gv.egiz.eaaf.core.impl.idp.controller.protocols.RequestImpl; +import eu.eidas.auth.commons.light.ILightRequest; +import lombok.Getter; +import lombok.Setter; + +/** + * Pending-request of an authentication process from eIDAS Proxy-Service.  + *  + * @author tlenz + * + */ +@Component("ProxyServicePendingRequest") +@Scope(value = BeanDefinition.SCOPE_PROTOTYPE) +public class ProxyServicePendingRequest extends RequestImpl { + +  private static final long serialVersionUID = 4227378344716277935L; + +  @Getter +  @Setter +  ILightRequest eidasRequest; +     +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java new file mode 100644 index 00000000..edb21722 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java @@ -0,0 +1,249 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.service; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.PostConstruct; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.CollectionType; +import com.google.common.collect.Sets; + +import at.asitplus.eidas.specific.modules.core.eidas.service.EidasAttributeRegistry; +import at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceConstants; +import at.asitplus.eidas.specific.modules.msproxyservice.dto.attributes.AttrMappingElement; +import at.asitplus.eidas.specific.modules.msproxyservice.handler.IEidasAttributeHandler; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.impl.utils.FileUtils; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ProxyEidasAttributeRegistry { +   +  private static final String ATTR_CONFIG_ALL = "*"; +   +  private static ObjectMapper mapper = new ObjectMapper(); +   +  @Autowired ApplicationContext context; +  @Autowired IConfiguration basicConfig; +  @Autowired ResourceLoader resourceLoader; + +   +  @Getter +  private EidasAttributeRegistry coreRegistry; + +  private Set<AttrMappingElement> attributeConfiguration; +   +   +  /** +   * Attribute Registry for eIDAS Proxy-Service implementation. +   * @param registry Core attribute registry +   */ +  public ProxyEidasAttributeRegistry(@Autowired EidasAttributeRegistry registry) { +    this.coreRegistry = registry; +         +  } +   +   +  /** +   * Get all attributes that requested from IDA by default. +   *  +   * @param withMandates <code>true</code> if mandates are supported, otherwise <code>false</code> +   * @return {@link Stream} of IDA specific attribute names +   */ +  @NonNull +  public Stream<String> getAlwaysRequestedAttributes(boolean withMandates) { +    return attributeConfiguration.stream() +        .filter(el -> ATTR_CONFIG_ALL.equals(el.getEidasAttributeName())) +        .map(el -> getReleadedIdaAttribute(el, withMandates)) +        .flatMap(Collection::stream) +        .filter(Objects::nonNull); +             +  } + +  /** +   * Get all eIDAS attributes that are added by default in case of mandates. +   *  +   * @return {@link Stream} of eIDAS attributes +   */ +  @NonNull +  public Stream<String> getRepresentativeAttributesToAddByDefault() { +    return attributeConfiguration.stream() +        .filter(el -> el.getType() != null && el.getType().getAutoIncludeWithMandates()) +        .map(el -> el.getEidasAttributeName()); +     +  } +   +  /** +   * Get IDA attributes for a specific eIDAS attribute. +   *    +   * @param eidasAttributeName Name of the eIDAS attribute. +   * @param withMandates <code>true</code> if mandates are supported, otherwise <code>false</code> +   * @return {@link Set} of IDA specific attribute names +   */ +  @NonNull +  public Set<String> getIdaAttributesForEidasAttribute(String eidasAttributeName, boolean withMandates) { +    return attributeConfiguration.stream() +        .filter(el -> el.getEidasAttributeName().equals(eidasAttributeName)) +        .findFirst() +        .map(el -> getReleadedIdaAttribute(el, withMandates)) +        .orElse(Collections.emptySet()) +        .stream() +        .filter(Objects::nonNull) +        .collect(Collectors.toSet()); +                 +  } + +  /** +   * Get eIDAS related IDA attribute for a specific mode-operation. +   *   +   * @param eidasAttributeName Name of the eIDAS attribute. +   * @param withMandates <code>true</code> if mandates are supported, otherwise <code>false</code> +   * @return Name of the related IDA attribute if available  +   */ +  public Optional<String> mapEidasAttributeToSpecificIdaAttribute( +      String eidasAttributeName, boolean withMandates) { +    return attributeConfiguration.stream() +        .filter(el -> el.getEidasAttributeName().equals(eidasAttributeName)) +        .filter(el -> el.getIdaAttribute() != null) +        .findFirst() +        .map(el -> withMandates ? el.getIdaAttribute().getWithMandates() : el.getIdaAttribute().getBasic()) +        .filter(el -> StringUtils.isNotEmpty(el)); +             +  } +     +  /** +   * Get eIDAS related custom attribute-handler. +   *   +   * @param eidasAttributeName Name of the eIDAS attribute. +   * @return full classname of the handler implementation if available  +   */ +  public Optional<String> mapEidasAttributeToAttributeHandler(String eidasAttributeName) { +    return attributeConfiguration.stream() +        .filter(el -> el.getEidasAttributeName().equals(eidasAttributeName)) +        .filter(el -> StringUtils.isNotEmpty(el.getSpecificAttributeHandlerClass())) +        .findFirst() +        .map(el -> el.getSpecificAttributeHandlerClass()); +     +  } +   +   +  @PostConstruct +  private void initialize() throws EaafConfigurationException { +    final String attrConfPath = basicConfig.getBasicConfiguration( +        MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_ATTIBUTE_CONFIGURATION); +     +    log.debug("Initializing eIDAS <--> IDA attribute mapping from: {} ... ", attrConfPath); +     +    if (StringUtils.isEmpty(attrConfPath)) { +      log.error("Error: Path to attribute-mapping config is unknown"); +      throw new EaafConfigurationException("internal.configuration.00",  +          new Object[]{MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_ATTIBUTE_CONFIGURATION}); +       +    } +     +    try { +      // reading attribute-configuration file +      final CollectionType javaType =  +          mapper.getTypeFactory().constructCollectionType(List.class, AttrMappingElement.class); +      List<AttrMappingElement> internalAttributeConfiguration =  +          mapper.readValue(readFromFile(attrConfPath), javaType); +      log.debug("Found #{} eIDAS <--> IDA attribute-mappings . Starting import process ...  ",  +          internalAttributeConfiguration.size()); +       +      // post-validation of attribute configuration +      attributeConfiguration = internalAttributeConfiguration.stream() +          .filter(el -> checkEidasAttributeName(el)) +          .collect(Collectors.toSet());         +      log.info("Load {} eIDAS <--> IDA attribute-mappings into attribute-registry", attributeConfiguration.size()); +                   +    } catch (Exception e) { +      log.error("Error reading eIDAS <--> IDA attribute-mapping configuration file", e); +      throw new EaafConfigurationException("internal.configuration.01", +          new Object[]{MsProxyServiceConstants.CONIG_PROPS_EIDAS_PROXY_ATTIBUTE_CONFIGURATION,  +              "Error reading Configurations file"}, e); +       +    }     +  } +   +  private Set<String> getReleadedIdaAttribute(AttrMappingElement el, boolean withMandates) { +    if (el.getIdaAttribute() != null) { +      Set<String> directMapping = withMandates  +          ? Sets.newHashSet(el.getIdaAttribute().getBasic(), el.getIdaAttribute().getWithMandates()) +          : Sets.newHashSet(el.getIdaAttribute().getBasic()); +                 +      if (el.getAddionalRequiredAttributes() != null) { +        el.getAddionalRequiredAttributes().forEach( +            attr -> directMapping.add(attr)); +         +      }       +      return directMapping; +       +    } else { +      return Collections.emptySet();       +       +    }            +  } +   +  private boolean checkEidasAttributeName(AttrMappingElement el) { +    if (StringUtils.isNotEmpty(el.getEidasAttributeName())) { +      if (ATTR_CONFIG_ALL.equals(el.getEidasAttributeName())  +          || coreRegistry.getCoreAttributeRegistry().getByName(el.getEidasAttributeName()) != null) { +         +        // check if custom attribute-handler implementation is available +        if (StringUtils.isNotEmpty(el.getSpecificAttributeHandlerClass())) { +          try { +            context.getBean(el.getSpecificAttributeHandlerClass(), IEidasAttributeHandler.class); +           +          } catch (Exception e) { +            log.error("No custom attribute-handler implementation for: {}", el.getSpecificAttributeHandlerClass(), e); +            return false; +          }           +        } +         +        return true; +             +      } else { +        log.warn("eIDAS attribute: {} is UNKNOWN by eIDAS node. Ignore it!", el.getEidasAttributeName()); +         +      } +       +    } else { +      log.warn("Find attribute-mapping element WITHOUT eIDAS attribute-name. Ignore it!"); +       +    } +     +    return false; +  } +    +  private byte[] readFromFile(final String filePath) throws URISyntaxException, IOException { +    final String fullFilePath = FileUtils.makeAbsoluteUrl(filePath, basicConfig.getConfigurationRootDirectory()); +    final Resource ressource = resourceLoader.getResource(fullFilePath); +    final InputStream is = ressource.getInputStream(); +    final byte[] result = IOUtils.toByteArray(is); +    is.close(); +    return result; +     +  } +   +   +} diff --git a/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/utils/EidasProxyServiceUtils.java b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/utils/EidasProxyServiceUtils.java new file mode 100644 index 00000000..b8a4c598 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/utils/EidasProxyServiceUtils.java @@ -0,0 +1,45 @@ +package at.asitplus.eidas.specific.modules.msproxyservice.utils; + +import at.asitplus.eidas.specific.modules.core.eidas.EidasConstants; +import eu.eidas.auth.commons.light.ILightRequest; + +/** + * Common utils for eIDAS Proxy-Service implementation. + *  + * @author tlenz + * + */ +public class EidasProxyServiceUtils { + +  /** +   * Check if legal person subject is requested by eIDAS Connector. +   *  +   * @param eidasRequest Authentication request from eIDAS Connector. +   * @return <code>true</code> if <i>LegalPersonIdentifier</i> is requested, otherwise <code>false</code>lse +   */ +  public static boolean isLegalPersonRequested(ILightRequest eidasRequest) { +    return eidasRequest.getRequestedAttributes().entrySet().stream() +        .filter(el -> el.getKey().getFriendlyName().equals(EidasConstants.eIDAS_ATTR_LEGALPERSONIDENTIFIER)) +        .findFirst() +        .isPresent(); +     +  } +   +  /** +   * Check if natural person subject is requested by eIDAS Connector. +   *  +   * @param eidasRequest Authentication request from eIDAS Connector. +   * @return <code>true</code> if <i>PersonIdentifier</i> is requested, otherwise <code>false</code>lse +   */ +  public static boolean isNaturalPersonRequested(ILightRequest eidasRequest) { +    return eidasRequest.getRequestedAttributes().entrySet().stream() +        .filter(el -> el.getKey().getFriendlyName().equals(EidasConstants.eIDAS_ATTR_PERSONALIDENTIFIER)) +        .findFirst() +        .isPresent(); +     +  } +   +  private EidasProxyServiceUtils() { +    //hide constructor for class with static methods only +  } +} diff --git a/modules/eidas_proxy-sevice/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider b/modules/eidas_proxy-sevice/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider new file mode 100644 index 00000000..9158d2e6 --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/resources/META-INF/services/at.gv.egiz.components.spring.api.SpringResourceProvider @@ -0,0 +1 @@ +at.asitplus.eidas.specific.modules.msproxyservice.MsProxyServiceSpringResourceProvider
\ No newline at end of file diff --git a/modules/eidas_proxy-sevice/src/main/resources/messages/eidasproxy_messages.properties b/modules/eidas_proxy-sevice/src/main/resources/messages/eidasproxy_messages.properties new file mode 100644 index 00000000..3f92d58a --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/resources/messages/eidasproxy_messages.properties @@ -0,0 +1,14 @@ +eidas.proxyservice.01=General error on request-validation from national eIDAS Proxy-Service +eidas.proxyservice.02=Authentication request contains not communication token. +eidas.proxyservice.03=General error during eIDAS-Node communication. Reason: {} +eidas.proxyservice.04=Validation of eIDAS Authn request failed. Reason: {} +eidas.proxyservice.05=No eIDAS-Connector Issuer in Authn. request. Authentication not possible +eidas.proxyservice.06=Can not build eIDAS Proxy-Service response. Authentication FAILED. +eidas.proxyservice.07=Can not determine eIDAS-Connector CountryCode. Authentication not possible +eidas.proxyservice.08=Validation of eIDAS Authn request failed. Reason: Legal person and natural person can not be requested at once. +eidas.proxyservice.09=eIDAS authentication not possible, because legal person is requested but mandates are disabled in general +eidas.proxyservice.10=eIDAS authentication not possible, because legal person is requested but not mandate profiles are defined +eidas.proxyservice.11=No Authentication request with stated communication token. + + +eidas.proxyservice.99=Internal error during eIDAS Proxy-Service authentication
\ No newline at end of file diff --git a/modules/eidas_proxy-sevice/src/main/resources/spring/eidas_proxy-service.beans.xml b/modules/eidas_proxy-sevice/src/main/resources/spring/eidas_proxy-service.beans.xml new file mode 100644 index 00000000..38bd44da --- /dev/null +++ b/modules/eidas_proxy-sevice/src/main/resources/spring/eidas_proxy-service.beans.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<beans xmlns="http://www.springframework.org/schema/beans" +  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +  xmlns:context="http://www.springframework.org/schema/context" +  xmlns:tx="http://www.springframework.org/schema/tx" +  xmlns:aop="http://www.springframework.org/schema/aop" +  xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd +    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd +    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd +    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd"> + +  <context:annotation-config /> + +  <bean id="ProxyServicePendingRequest"  +        class="at.asitplus.eidas.specific.modules.msproxyservice.protocol.ProxyServicePendingRequest" +        scope="prototype"/> + +  <bean id="ProxyServiceAuthenticationAction"  +        class="at.asitplus.eidas.specific.modules.msproxyservice.protocol.ProxyServiceAuthenticationAction"/> + +  <bean id="msSpecificProxyController" +        class="at.asitplus.eidas.specific.modules.msproxyservice.protocol.EidasProxyServiceController"/> +   +  <bean id="eidasProxyMessageSource" +        class="at.asitplus.eidas.specific.modules.msproxyservice.EidasProxyMessageSource"/> +   +  <bean id="attributeRegistry" +        class="at.asitplus.eidas.specific.modules.core.eidas.service.EidasAttributeRegistry"> +    <property name="eidasAttributesFile" +              ref="specificConnectorAttributesFileWithPath" /> +    <property name="additionalAttributesFile" +              ref="specificConnectorAdditionalAttributesFileWithPath" /> +  </bean> +   +  <bean id="proxyAttributeRegistry" +        class="at.asitplus.eidas.specific.modules.msproxyservice.service.ProxyEidasAttributeRegistry" /> +   +  <bean id="at.asitplus.eidas.specific.modules.msproxyservice.handler.EJusticePersonRoleHandler" +        class="at.asitplus.eidas.specific.modules.msproxyservice.handler.EJusticePersonRoleHandler" /> +   +</beans>
\ No newline at end of file | 
