package org.apereo.cas.util;
import com.google.common.base.Throwables;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.configuration.model.support.saml.sps.AbstractSamlSPProperties;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.services.ChainingAttributeReleasePolicy;
import org.apereo.cas.services.PrincipalAttributeRegisteredServiceUsernameProvider;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ReturnMappedAttributeReleasePolicy;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.support.saml.services.SamlRegisteredService;
import org.apereo.cas.support.saml.services.idp.metadata.cache.SamlRegisteredServiceCachingMetadataResolver;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver;
import org.opensaml.saml.metadata.resolver.filter.impl.PredicateFilter;
import org.opensaml.saml.metadata.resolver.impl.AbstractBatchMetadataResolver;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
/**
* This is {@link SamlSPUtils}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public final class SamlSPUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(SamlSPUtils.class);
private SamlSPUtils() {
}
/**
* New saml service provider registration.
*
* @param sp the properties
* @param resolver the resolver
* @return the saml registered service
*/
public static SamlRegisteredService newSamlServiceProviderService(final AbstractSamlSPProperties sp,
final SamlRegisteredServiceCachingMetadataResolver resolver) {
if (StringUtils.isBlank(sp.getMetadata())) {
LOGGER.debug("Skipped registration of [{}] since no metadata location is found", sp.getName());
return null;
}
try {
final SamlRegisteredService service = new SamlRegisteredService();
service.setName(sp.getName());
service.setDescription(sp.getDescription());
service.setEvaluationOrder(Integer.MIN_VALUE);
service.setMetadataLocation(sp.getMetadata());
final List<String> attributesToRelease = new ArrayList<>(sp.getAttributes());
if (StringUtils.isNotBlank(sp.getNameIdAttribute())) {
attributesToRelease.add(sp.getNameIdAttribute());
service.setUsernameAttributeProvider(new PrincipalAttributeRegisteredServiceUsernameProvider(sp.getNameIdAttribute()));
}
if (StringUtils.isNotBlank(sp.getNameIdFormat())) {
service.setRequiredNameIdFormat(sp.getNameIdFormat());
}
final Map<String, String> attributes = Beans.transformPrincipalAttributesListIntoMap(attributesToRelease);
final ChainingAttributeReleasePolicy policy = new ChainingAttributeReleasePolicy();
policy.addPolicy(new ReturnMappedAttributeReleasePolicy(attributes));
service.setAttributeReleasePolicy(policy);
service.setMetadataCriteriaRoles(SPSSODescriptor.DEFAULT_ELEMENT_NAME.getLocalPart());
service.setMetadataCriteriaRemoveEmptyEntitiesDescriptors(true);
service.setMetadataCriteriaRemoveRolelessEntityDescriptors(true);
if (StringUtils.isNotBlank(sp.getSignatureLocation())) {
service.setMetadataSignatureLocation(sp.getSignatureLocation());
}
final List<String> entityIDList = sp.getEntityIds();
if (entityIDList.isEmpty()) {
final ChainingMetadataResolver chainingResolver = resolver.resolve(service);
if (chainingResolver.getResolvers().isEmpty()) {
LOGGER.warn("Skipped registration of [{}] since no metadata resolver could be constructed", sp.getName());
return null;
}
chainingResolver.getResolvers().forEach(r -> {
if (r instanceof AbstractBatchMetadataResolver) {
final Iterator<EntityDescriptor> it = ((AbstractBatchMetadataResolver) r).iterator();
final Optional<EntityDescriptor> descriptor =
StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, Spliterator.ORDERED), false)
.filter(e -> e.getSPSSODescriptor(SAMLConstants.SAML20P_NS) != null)
.findFirst();
if (descriptor.isPresent()) {
entityIDList.add(descriptor.get().getEntityID());
} else {
LOGGER.warn("Skipped registration of [{}] since no entity id could be found", sp.getName());
}
}
});
}
if (entityIDList.isEmpty()) {
LOGGER.warn("Skipped registration of [{}] since no metadata entity ids could be found", sp.getName());
return null;
}
final String entityIds = org.springframework.util.StringUtils.collectionToDelimitedString(entityIDList, "|");
service.setMetadataCriteriaDirection(PredicateFilter.Direction.INCLUDE.name());
service.setMetadataCriteriaPattern(entityIds);
LOGGER.debug("Registering saml service [{}] by entity id [{}]", sp.getName(), entityIds);
service.setServiceId(entityIds);
service.setSignAssertions(sp.isSignAssertions());
service.setSignResponses(sp.isSignResponses());
return service;
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Save service only if it's not already found in the registry.
*
* @param service the service
* @param servicesManager the services manager
*/
public static void saveService(final RegisteredService service, final ServicesManager servicesManager) {
servicesManager.load();
if (servicesManager.findServiceBy(registeredService -> registeredService instanceof SamlRegisteredService
&& registeredService.getServiceId().equals(service.getServiceId())) != null) {
LOGGER.info("Service [{}] does not exist in the registry and will be added.", service.getServiceId());
servicesManager.save(service);
servicesManager.load();
} else {
LOGGER.info("Service [{}] exists in the registry and will not be added again.", service.getServiceId());
}
}
}