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 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 true if mandates are supported, otherwise false * @return {@link Stream} of IDA specific attribute names */ @NonNull public Stream 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 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 true if mandates are supported, otherwise false * @return {@link Set} of IDA specific attribute names */ @NonNull public Set 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 true if mandates are supported, otherwise false * @return Name of the related IDA attribute if available */ public Optional 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 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 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 getReleadedIdaAttribute(AttrMappingElement el, boolean withMandates) { if (el.getIdaAttribute() != null) { Set 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; } }