2 * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3 * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4 * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5 * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above
6 * copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials
7 * provided with the distribution, if any, must include the following acknowledgment: "This product includes software
8 * developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu> Internet2 Project.
9 * Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10 * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2, nor
11 * the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12 * products derived from this software without specific prior written permission. For written permission, please contact
13 * shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2, UCAID, or the
14 * University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name, without prior
15 * written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS PROVIDED BY THE
16 * COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE
18 * DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. IN NO
19 * EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC.
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
21 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package edu.internet2.middleware.shibboleth.idp;
28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Random;
33 import javax.servlet.RequestDispatcher;
34 import javax.servlet.ServletException;
35 import javax.servlet.UnavailableException;
36 import javax.servlet.http.HttpServlet;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
39 import javax.xml.parsers.DocumentBuilderFactory;
40 import javax.xml.parsers.ParserConfigurationException;
42 import org.apache.log4j.Logger;
43 import org.apache.log4j.MDC;
44 import org.opensaml.SAMLBinding;
45 import org.opensaml.SAMLBindingFactory;
46 import org.opensaml.SAMLException;
47 import org.opensaml.SAMLRequest;
48 import org.opensaml.SAMLResponse;
49 import org.w3c.dom.Document;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.NodeList;
53 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
54 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
55 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
56 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
57 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
58 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapperFactory;
59 import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
60 import edu.internet2.middleware.shibboleth.common.Credentials;
61 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
62 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
63 import edu.internet2.middleware.shibboleth.common.NameMapper;
64 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
65 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
66 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
67 import edu.internet2.middleware.shibboleth.log.LoggingInitializer;
68 import edu.internet2.middleware.shibboleth.metadata.Metadata;
69 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
72 * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
73 * IdP processing components, and delivers proper protocol responses.
75 * @author Walter Hoehn
78 public class IdPResponder extends HttpServlet {
80 private static Logger transactionLog;
81 private static Logger log;
82 private static Random idgen = new Random();
83 private SAMLBinding binding;
85 private IdPConfig configuration;
86 private HashMap protocolHandlers = new HashMap();
87 private IdPProtocolSupport protocolSupport;
90 * @see javax.servlet.GenericServlet#init()
92 public void init() throws ServletException {
97 binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
99 Document idPConfig = IdPConfigLoader.getIdPConfig(this.getServletContext());
101 // Initialize logging
102 NodeList itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
104 if (itemElements.getLength() > 0) {
105 if (itemElements.getLength() > 1) {
107 .println("WARNING: More than one Logging element in IdP configuration, using the first one.");
109 Element loggingConfig = (Element) itemElements.item(0);
110 LoggingInitializer.initializeLogging(loggingConfig);
111 transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
112 log = Logger.getLogger(IdPResponder.class);
113 MDC.put("serviceId", "[IdP] Core");
114 log.info("Initializing Identity Provider.");
118 // Load global configuration properties
119 configuration = new IdPConfig(idPConfig.getDocumentElement());
121 // Load name mappings
122 NameMapper nameMapper = new NameMapper();
123 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(
124 NameIdentifierMapping.mappingNamespace, "NameMapping");
126 for (int i = 0; i < itemElements.getLength(); i++) {
128 nameMapper.addNameMapping((Element) itemElements.item(i));
129 } catch (NameIdentifierMappingException e) {
130 log.error("Name Identifier mapping could not be loaded: " + e);
134 // Load signing credentials
135 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
137 if (itemElements.getLength() < 1) {
138 log.error("No credentials specified.");
140 if (itemElements.getLength() > 1) {
141 log.error("Multiple Credentials specifications found, using first.");
143 Credentials credentials = new Credentials((Element) itemElements.item(0));
145 // Load relying party config
146 ServiceProviderMapper spMapper;
148 spMapper = new ServiceProviderMapper(idPConfig.getDocumentElement(), configuration, credentials,
150 } catch (ServiceProviderMapperException e) {
151 log.error("Could not load Identity Provider configuration: " + e);
152 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
155 // Startup Attribute Resolver & ARP engine
156 AttributeResolver resolver = null;
157 ArpEngine arpEngine = null;
159 resolver = new AttributeResolver(configuration);
161 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
162 "ReleasePolicyEngine");
164 if (itemElements.getLength() > 1) {
165 log.warn("Encountered multiple <ReleasePolicyEngine/> configuration elements. Using first...");
167 if (itemElements.getLength() < 1) {
168 arpEngine = new ArpEngine();
170 arpEngine = new ArpEngine((Element) itemElements.item(0));
173 } catch (ArpException ae) {
174 log.fatal("The Identity Provider could not be initialized "
175 + "due to a problem with the ARP Engine configuration: " + ae);
176 throw new ShibbolethConfigurationException("Could not load ARP Engine.");
177 } catch (AttributeResolverException ne) {
178 log.fatal("The Identity Provider could not be initialized due "
179 + "to a problem with the Attribute Resolver configuration: " + ne);
180 throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
183 // Load artifact mapping implementation
184 ArtifactMapper artifactMapper = null;
185 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
187 if (itemElements.getLength() > 1) {
188 log.warn("Encountered multiple <ArtifactMapper/> configuration elements. Using first...");
190 if (itemElements.getLength() > 0) {
191 artifactMapper = ArtifactMapperFactory.getInstance((Element) itemElements.item(0));
193 log.debug("No Artifact Mapper configuration found. Defaulting to Memory-based implementation.");
194 artifactMapper = new MemoryArtifactMapper();
197 // Load protocol handlers and support library
198 protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
199 resolver, artifactMapper);
200 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
203 // Default if no handlers are specified
204 if (itemElements.getLength() < 1) {
205 itemElements = getDefaultHandlers();
207 // If handlers were specified, load them and register them against their locations
209 EACHHANDLER : for (int i = 0; i < itemElements.getLength(); i++) {
210 IdPProtocolHandler handler = ProtocolHandlerFactory.getInstance((Element) itemElements.item(i));
211 String[] locations = handler.getLocations();
212 EACHLOCATION : for (int j = 0; j < locations.length; j++) {
213 if (protocolHandlers.containsKey(locations[j])) {
214 log.error("Multiple protocol handlers are registered to listen at (" + locations[j]
215 + "). Ignoring all except ("
216 + ((IdPProtocolHandler) protocolHandlers.get(locations[j])).getHandlerName() + ").");
217 continue EACHLOCATION;
219 log.info("Registering handler (" + handler.getHandlerName() + ") to listen at (" + locations[j]
221 protocolHandlers.put(locations[j].toString(), handler);
226 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
228 for (int i = 0; i < itemElements.getLength(); i++) {
229 protocolSupport.addMetadataProvider((Element) itemElements.item(i));
231 if (protocolSupport.providerCount() < 1) {
232 log.error("No Metadata Provider metadata loaded.");
233 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
236 log.info("Identity Provider initialization complete.");
238 } catch (ShibbolethConfigurationException ae) {
239 log.fatal("The Identity Provider could not be initialized: " + ae);
240 throw new UnavailableException("Identity Provider failed to initialize.");
241 } catch (SAMLException se) {
242 log.fatal("SAML SOAP binding could not be loaded: " + se);
243 throw new UnavailableException("Identity Provider failed to initialize.");
248 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
249 * javax.servlet.http.HttpServletResponse)
251 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
253 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
254 MDC.put("remoteAddr", request.getRemoteAddr());
255 log.debug("Recieved a request via GET for location (" + request.getRequestURL() + ").");
258 IdPProtocolHandler activeHandler = lookupProtocolHandler(request);
259 // Pass request to the appropriate handler
260 log.info("Processing " + activeHandler.getHandlerName() + " request.");
261 if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
262 // This shouldn't happen unless somebody configures a protocol handler incorrectly
263 log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
264 throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
267 } catch (SAMLException ex) {
269 displayBrowserError(request, response, ex);
275 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
276 * javax.servlet.http.HttpServletResponse)
278 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
280 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
281 MDC.put("remoteAddr", request.getRemoteAddr());
282 log.debug("Recieved a request via POST for location (" + request.getRequestURL() + ").");
284 // Parse SOAP request and marshall SAML request object
285 SAMLRequest samlRequest = null;
288 samlRequest = binding.receive(request,1);
289 } catch (SAMLException e) {
290 log.fatal("Unable to parse request: " + e);
291 throw new SAMLException("Invalid request data.");
294 // If we have DEBUG logging turned on, dump out the request to the log
295 // This takes some processing, so only do it if we need to
296 if (log.isDebugEnabled()) {
297 log.debug("Dumping generated SAML Request:" + System.getProperty("line.separator")
298 + samlRequest.toString());
301 IdPProtocolHandler activeHandler = lookupProtocolHandler(request);
302 // Pass request to the appropriate handler and respond
303 log.info("Processing " + activeHandler.getHandlerName() + " request.");
305 SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
306 binding.respond(response, samlResponse, null);
308 } catch (SAMLException e) {
309 sendFailureToSAMLBinding(response, samlRequest, e);
313 private IdPProtocolHandler lookupProtocolHandler(HttpServletRequest request) throws SAMLException {
315 // Determine which protocol handler is active for this endpoint
316 String requestURL = request.getRequestURL().toString();
317 IdPProtocolHandler activeHandler = null;
319 Iterator registeredLocations = protocolHandlers.keySet().iterator();
320 while (registeredLocations.hasNext()) {
321 String handlerLocation = (String) registeredLocations.next();
322 if (requestURL.matches(handlerLocation)) {
323 log.debug("Matched handler location: (" + handlerLocation + ").");
324 activeHandler = (IdPProtocolHandler) protocolHandlers.get(handlerLocation);
329 if (activeHandler == null) {
330 log.error("No protocol handler registered for location (" + request.getRequestURL() + ").");
331 throw new SAMLException("Request submitted to an invalid location.");
333 return activeHandler;
336 private NodeList getDefaultHandlers() throws ShibbolethConfigurationException {
338 log.debug("Loading default protocol handler configuration.");
340 DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
341 docFactory.setNamespaceAware(true);
342 Document placeHolder = docFactory.newDocumentBuilder().newDocument();
343 Element baseNode = placeHolder.createElementNS(IdPConfig.configNameSpace, "IdPConfig");
345 Element ssoHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
346 ssoHandler.setAttribute("implementation",
347 "edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler");
348 Element ssoLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
349 ssoLocation.appendChild(placeHolder.createTextNode("https?://[^/]+(:443)?/shibboleth/SSO"));
350 ssoHandler.appendChild(ssoLocation);
351 baseNode.appendChild(ssoHandler);
353 Element attributeHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
354 attributeHandler.setAttribute("implementation",
355 "edu.internet2.middleware.shibboleth.idp.provider.SAMLv1_AttributeQueryHandler");
356 Element attributeLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
357 attributeLocation.appendChild(placeHolder.createTextNode("https?://[^/]+:8443/shibboleth/AA"));
358 attributeHandler.appendChild(attributeLocation);
359 baseNode.appendChild(attributeHandler);
361 Element artifactHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
362 artifactHandler.setAttribute("implementation",
363 "edu.internet2.middleware.shibboleth.idp.provider.SAMLv1_1ArtifactQueryHandler");
364 Element artifactLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
365 artifactLocation.appendChild(placeHolder.createTextNode("https?://[^/]+:8443/shibboleth/Artifact"));
366 artifactHandler.appendChild(artifactLocation);
367 baseNode.appendChild(artifactHandler);
369 return baseNode.getElementsByTagNameNS(IdPConfig.configNameSpace, "ProtocolHandler");
371 } catch (ParserConfigurationException e) {
372 log.fatal("Encoutered an error while loading default protocol handlers: " + e);
373 throw new ShibbolethConfigurationException("Could not load protocol handlers.");
377 private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
378 SAMLException exception) throws ServletException {
380 log.error("Error while processing request: " + exception);
382 SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
384 if (log.isDebugEnabled()) {
385 log.debug("Dumping generated SAML Error Response:" + System.getProperty("line.separator")
386 + samlResponse.toString());
388 binding.respond(httpResponse, samlResponse, null);
389 log.debug("Returning SAML Error Response.");
390 } catch (SAMLException se) {
392 binding.respond(httpResponse, null, exception);
393 } catch (SAMLException e) {
394 log.error("Caught exception while responding to requester: " + e.getMessage());
396 httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
397 } catch (IOException ee) {
398 log.fatal("Could not construct a SAML error response: " + ee);
399 throw new ServletException("Identity Provider response failure.");
402 log.error("Identity Provider failed to make an error message: " + se);
406 private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
407 throws ServletException, IOException {
409 req.setAttribute("errorText", e.toString());
410 req.setAttribute("requestURL", req.getRequestURI().toString());
411 RequestDispatcher rd = req.getRequestDispatcher("/IdPError.jsp");
412 rd.forward(req, res);
417 class MetadataProviderFactory {
419 private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
421 public static Metadata loadProvider(Element e) throws MetadataException {
423 String className = e.getAttribute("type");
424 if (className == null || className.equals("")) {
425 log.error("Metadata Provider requires specification of the attribute \"type\".");
426 throw new MetadataException("Failed to initialize Metadata Provider.");
429 Class[] params = {Class.forName("org.w3c.dom.Element"),};
430 return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
431 } catch (Exception loaderException) {
432 log.error("Failed to load Metadata Provider implementation class: " + loaderException);
433 Throwable cause = loaderException.getCause();
434 while (cause != null) {
435 log.error("caused by: " + cause);
436 cause = cause.getCause();
438 throw new MetadataException("Failed to initialize Metadata Provider.");