diff options
author | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2022-08-17 10:51:54 +0000 |
---|---|---|
committer | Thomas Lenz <thomas.lenz@egiz.gv.at> | 2022-08-17 10:51:54 +0000 |
commit | d83cf36c1454a10f5a46d677b5f0f30e0cbe7e95 (patch) | |
tree | 1cf2bf2853d604d8579cc3f5bf411d10f80c0207 /modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific | |
parent | 48f19dc45cec670fe62399d09fb34732fb4eeedc (diff) | |
parent | 920d33465e5ab1a71d81cc280e41de10cd8b5247 (diff) | |
download | National_eIDAS_Gateway-d83cf36c1454a10f5a46d677b5f0f30e0cbe7e95.tar.gz National_eIDAS_Gateway-d83cf36c1454a10f5a46d677b5f0f30e0cbe7e95.tar.bz2 National_eIDAS_Gateway-d83cf36c1454a10f5a46d677b5f0f30e0cbe7e95.zip |
Merge branch 'feature/ms_proxy_service' into 'nightlybuild'
add basic implementation of eIDAS-Node Proxy-Service request-controller
See merge request egiz/eidas_at_proxy!20
Diffstat (limited to 'modules/eidas_proxy-sevice/src/main/java/at/asitplus/eidas/specific')
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 + } +} |