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;
}
}