/******************************************************************************* * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. *******************************************************************************/ package org.cloudfoundry.identity.uaa.provider.saml.idp; import org.opensaml.saml2.core.NameIDType; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.util.SimpleURLCanonicalizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.saml.metadata.ExtendedMetadata; import org.springframework.security.saml.metadata.ExtendedMetadataDelegate; import org.springframework.security.saml.metadata.MetadataDisplayFilter; import org.springframework.security.saml.metadata.MetadataMemoryProvider; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.Arrays; import java.util.Collection; /** * The filter expects calls on configured URL and presents user with SAML2 metadata representing this application * deployment. In case the application is configured to automatically generate metadata, the generation occurs upon * first invocation of this filter (first request made to the server). * * This class is based on org.springframework.security.saml.metadata.MetadataGeneratorFilter. */ public class IdpMetadataGeneratorFilter extends GenericFilterBean { /** * Class logger. */ protected final static Logger log = LoggerFactory.getLogger(IdpMetadataGeneratorFilter.class); /** * Class storing all SAML metadata documents */ protected IdpMetadataManager manager; /** * Class capable of generating new metadata. */ protected IdpMetadataGenerator generator; /** * Metadata display filter. */ protected MetadataDisplayFilter displayFilter; /** * Flag indicates that in case generated base url is used (when value is not provided in the MetadataGenerator) it * should be normalized. Normalization includes lower-casing of scheme and server name and removing standar ports * of 80 for http and 443 for https schemes. */ protected boolean normalizeBaseUrl; /** * Default constructor. * * @param generator * generator */ public IdpMetadataGeneratorFilter(IdpMetadataGenerator generator) { this.generator = generator; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { processMetadataInitialization((HttpServletRequest) request); chain.doFilter(request, response); } /** * Verifies whether generation is needed and if so the metadata document is created and stored in metadata * manager. * * @param request * request * @throws javax.servlet.ServletException * error */ protected void processMetadataInitialization(HttpServletRequest request) throws ServletException { // In case the hosted IdP metadata weren't initialized, let's do it now if (manager.getHostedIdpName() == null) { synchronized (IdpMetadataManager.class) { if (manager.getHostedIdpName() == null) { try { log.info( "No default metadata configured, generating with default values, please pre-configure metadata for production use"); // Defaults String alias = generator.getEntityAlias(); String baseURL = getDefaultBaseURL(request); // Use default baseURL if not set if (generator.getEntityBaseURL() == null) { log.warn( "Generated default entity base URL {} based on values in the first server request. Please set property entityBaseURL on MetadataGenerator bean to fixate the value.", baseURL); generator.setEntityBaseURL(baseURL); } else { baseURL = generator.getEntityBaseURL(); } // Use default entityID if not set if (generator.getEntityId() == null) { generator.setEntityId(getDefaultEntityID(baseURL, alias)); } // Ensure supported nameID formats in uaa are listed in the metadata Collection<String> supportedNameID = Arrays.asList(NameIDType.EMAIL, NameIDType.PERSISTENT, NameIDType.UNSPECIFIED); generator.setNameID(supportedNameID); EntityDescriptor descriptor = generator.generateMetadata(); ExtendedMetadata extendedMetadata = generator.generateExtendedMetadata(); log.info("Created default metadata for system with entityID: " + descriptor.getEntityID()); MetadataMemoryProvider memoryProvider = new MetadataMemoryProvider(descriptor); memoryProvider.initialize(); MetadataProvider metadataProvider = new ExtendedMetadataDelegate(memoryProvider, extendedMetadata); manager.addMetadataProvider(metadataProvider); manager.setHostedIdpName(descriptor.getEntityID()); manager.refreshMetadata(); } catch (MetadataProviderException e) { log.error("Error generating system metadata", e); throw new ServletException("Error generating system metadata", e); } } } } } protected String getDefaultEntityID(String entityBaseUrl, String alias) { String displayFilterUrl = MetadataDisplayFilter.FILTER_URL; if (displayFilter != null) { displayFilterUrl = displayFilter.getFilterProcessesUrl(); } StringBuilder sb = new StringBuilder(); sb.append(entityBaseUrl); sb.append(displayFilterUrl); if (StringUtils.hasLength(alias)) { sb.append("/alias/"); sb.append(alias); } return sb.toString(); } protected String getDefaultBaseURL(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); sb.append(request.getScheme()).append("://").append(request.getServerName()).append(":") .append(request.getServerPort()); sb.append(request.getContextPath()); String url = sb.toString(); if (isNormalizeBaseUrl()) { return SimpleURLCanonicalizer.canonicalize(url); } else { return url; } } @Autowired(required = false) public void setDisplayFilter(MetadataDisplayFilter displayFilter) { this.displayFilter = displayFilter; } @Autowired public void setManager(IdpMetadataManager manager) { this.manager = manager; } public boolean isNormalizeBaseUrl() { return normalizeBaseUrl; } /** * When true flag indicates that in case generated base url is used (when value is not provided in the * MetadataGenerator) it should be normalized. Normalization includes lower-casing of scheme and server name and * removing standar ports of 80 for http and 443 for https schemes. * * @param normalizeBaseUrl * flag */ public void setNormalizeBaseUrl(boolean normalizeBaseUrl) { this.normalizeBaseUrl = normalizeBaseUrl; } /** * Verifies that required entities were autowired or set. */ @Override public void afterPropertiesSet() throws ServletException { super.afterPropertiesSet(); Assert.notNull(generator, "Metadata generator"); Assert.notNull(manager, "MetadataManager must be set"); } }