aboutsummaryrefslogtreecommitdiff
path: root/modules/eidas_proxy-sevice/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'modules/eidas_proxy-sevice/src/main/java')
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/EidasProxyMessageSource.java22
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceConstants.java59
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/MsProxyServiceSpringResourceProvider.java55
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/AttrMappingElement.java49
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/IdaAttribute.java29
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/dto/attributes/Type.java91
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/exception/EidasProxyServiceException.java19
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/EJusticePersonRoleHandler.java138
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/handler/IEidasAttributeHandler.java36
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/EidasProxyServiceController.java524
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServiceAuthenticationAction.java373
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/protocol/ProxyServicePendingRequest.java28
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/service/ProxyEidasAttributeRegistry.java249
-rw-r--r--modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific/modules/msproxyservice/utils/EidasProxyServiceUtils.java45
14 files changed, 1717 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
+ }
+}