package org.apereo.cas.support.saml.mdui.config; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.authentication.principal.ServiceFactory; import org.apereo.cas.authentication.principal.WebApplicationService; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.support.saml.OpenSamlConfigBean; import org.apereo.cas.support.saml.SamlProtocolConstants; import org.apereo.cas.support.saml.SamlUtils; import org.apereo.cas.support.saml.mdui.AbstractMetadataResolverAdapter; import org.apereo.cas.support.saml.mdui.ChainingMetadataResolverAdapter; import org.apereo.cas.support.saml.mdui.DynamicMetadataResolverAdapter; import org.apereo.cas.support.saml.mdui.MetadataResolverAdapter; import org.apereo.cas.support.saml.mdui.StaticMetadataResolverAdapter; import org.apereo.cas.support.saml.mdui.web.flow.SamlMetadataUIParserAction; import org.apereo.cas.support.saml.mdui.web.flow.SamlMetadataUIWebflowConfigurer; import org.apereo.cas.util.ResourceUtils; import org.apereo.cas.web.flow.CasWebflowConfigurer; import org.jooq.lambda.Unchecked; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; import org.opensaml.saml.metadata.resolver.filter.impl.RequiredValidUntilFilter; import org.opensaml.saml.metadata.resolver.filter.impl.SignatureValidationFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.execution.Action; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This is {@link SamlMetadataUIConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("samlMetadataUIConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) public class SamlMetadataUIConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(SamlMetadataUIConfiguration.class); private static final String DEFAULT_SEPARATOR = "::"; @Autowired @Qualifier("shibboleth.OpenSAMLConfig") private OpenSamlConfigBean openSamlConfigBean; @Autowired private CasConfigurationProperties casProperties; @Autowired private ResourceLoader resourceLoader; @Autowired(required = false) @Qualifier("loginFlowRegistry") private FlowDefinitionRegistry loginFlowDefinitionRegistry; @Autowired(required = false) private FlowBuilderServices flowBuilderServices; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Autowired @Qualifier("webApplicationServiceFactory") private ServiceFactory<WebApplicationService> serviceFactory; @ConditionalOnMissingBean(name = "samlMetadataUIWebConfigurer") @Bean public CasWebflowConfigurer samlMetadataUIWebConfigurer() { return new SamlMetadataUIWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry, samlMetadataUIParserAction()); } @ConditionalOnMissingBean(name = "samlMetadataUIParserAction") @Bean public Action samlMetadataUIParserAction() { final String parameter = StringUtils.defaultIfEmpty(casProperties.getSamlMetadataUi().getParameter(), SamlProtocolConstants.PARAMETER_ENTITY_ID); return new SamlMetadataUIParserAction(parameter, chainingSamlMetadataUIMetadataResolverAdapter(), serviceFactory, servicesManager); } @ConditionalOnMissingBean(name = "chainingSamlMetadataUIMetadataResolverAdapter") @Bean public MetadataResolverAdapter chainingSamlMetadataUIMetadataResolverAdapter() { return new ChainingMetadataResolverAdapter(Arrays.asList(getStaticMetadataResolverAdapter(), getDynamicMetadataResolverAdapter())); } private MetadataResolverAdapter configureAdapter(final AbstractMetadataResolverAdapter adapter) { final Map<Resource, MetadataFilterChain> resources = new HashMap<>(); final MetadataFilterChain chain = new MetadataFilterChain(); casProperties.getSamlMetadataUi().getResources().forEach(Unchecked.consumer(r -> configureResource(resources, chain, r))); adapter.setRequireValidMetadata(casProperties.getSamlMetadataUi().isRequireValidMetadata()); adapter.setMetadataResources(resources); adapter.setConfigBean(openSamlConfigBean); return adapter; } private void configureResource(final Map<Resource, MetadataFilterChain> resources, final MetadataFilterChain chain, final String r) throws Exception { final String[] splitArray = org.springframework.util.StringUtils.commaDelimitedListToStringArray(r); Arrays.stream(splitArray).forEach(Unchecked.consumer(entry -> { final String[] arr = entry.split(DEFAULT_SEPARATOR); final String metadataFile = arr[0]; final String signingKey = arr.length > 1 ? arr[1] : null; final List<MetadataFilter> filters = new ArrayList<>(); if (casProperties.getSamlMetadataUi().getMaxValidity() > 0) { filters.add(new RequiredValidUntilFilter(casProperties.getSamlMetadataUi().getMaxValidity())); } boolean addResource = true; if (StringUtils.isNotBlank(signingKey)) { final SignatureValidationFilter sigFilter = SamlUtils.buildSignatureValidationFilter(this.resourceLoader, signingKey); if (sigFilter != null) { sigFilter.setRequireSignedRoot(casProperties.getSamlMetadataUi().isRequireSignedRoot()); filters.add(sigFilter); } else { LOGGER.warn("Failed to locate the signing key [{}] for [{}]", signingKey, metadataFile); addResource = false; } } chain.setFilters(filters); final Resource resource = this.resourceLoader.getResource(metadataFile); if (addResource && ResourceUtils.doesResourceExist(resource)) { resources.put(resource, chain); } else { LOGGER.warn("Skipping metadata [{}]; Either the resource cannot be retrieved or its signing key is missing", metadataFile); } })); } private MetadataResolverAdapter getDynamicMetadataResolverAdapter() { final DynamicMetadataResolverAdapter adapter = new DynamicMetadataResolverAdapter(); configureAdapter(adapter); return adapter; } private MetadataResolverAdapter getStaticMetadataResolverAdapter() { final StaticMetadataResolverAdapter adapter = new StaticMetadataResolverAdapter(); configureAdapter(adapter); adapter.buildMetadataResolverAggregate(); return adapter; } }