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;
30 import java.net.URISyntaxException;
31 import java.util.HashMap;
32 import java.util.Random;
34 import javax.servlet.RequestDispatcher;
35 import javax.servlet.ServletException;
36 import javax.servlet.UnavailableException;
37 import javax.servlet.http.HttpServlet;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
41 import org.apache.log4j.Logger;
42 import org.apache.log4j.MDC;
43 import org.opensaml.SAMLBinding;
44 import org.opensaml.SAMLBindingFactory;
45 import org.opensaml.SAMLException;
46 import org.opensaml.SAMLRequest;
47 import org.opensaml.SAMLResponse;
48 import org.w3c.dom.Document;
49 import org.w3c.dom.Element;
50 import org.w3c.dom.NodeList;
52 import sun.misc.BASE64Decoder;
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.common.Credentials;
58 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
59 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
60 import edu.internet2.middleware.shibboleth.common.NameMapper;
61 import edu.internet2.middleware.shibboleth.common.OriginConfig;
62 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
63 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
64 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
65 import edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler;
66 import edu.internet2.middleware.shibboleth.metadata.Metadata;
67 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
70 * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
71 * IdP processing components, and delivers proper protocol responses.
73 * @author Walter Hoehn
76 public class IdPResponder extends HttpServlet {
78 private static Logger transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
79 private static Logger log = Logger.getLogger(IdPResponder.class.getName());
80 private static Random idgen = new Random();
81 private SAMLBinding binding;
82 private Semaphore throttle;
83 private IdPConfig configuration;
84 private HashMap protocolHandlers = new HashMap();
85 private IdPProtocolSupport protocolSupport;
88 * @see javax.servlet.GenericServlet#init()
90 public void init() throws ServletException {
93 MDC.put("serviceId", "[IdP] Core");
94 log.info("Initializing Identity Provider.");
97 binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
99 Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
101 // Load global configuration properties
102 configuration = new IdPConfig(originConfig.getDocumentElement());
104 // Load a semaphore that throttles how many requests the IdP will handle at once
105 throttle = new Semaphore(configuration.getMaxThreads());
107 // Load name mappings
108 NameMapper nameMapper = new NameMapper();
109 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
110 NameIdentifierMapping.mappingNamespace, "NameMapping");
112 for (int i = 0; i < itemElements.getLength(); i++) {
114 nameMapper.addNameMapping((Element) itemElements.item(i));
115 } catch (NameIdentifierMappingException e) {
116 log.error("Name Identifier mapping could not be loaded: " + e);
120 // Load signing credentials
121 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
123 if (itemElements.getLength() < 1) {
124 log.error("No credentials specified.");
126 if (itemElements.getLength() > 1) {
127 log.error("Multiple Credentials specifications found, using first.");
129 Credentials credentials = new Credentials((Element) itemElements.item(0));
131 // Load relying party config
132 ServiceProviderMapper spMapper;
134 spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
136 } catch (ServiceProviderMapperException e) {
137 log.error("Could not load Identity Provider configuration: " + e);
138 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
141 // Startup Attribute Resolver & ARP engine
142 AttributeResolver resolver = null;
143 ArpEngine arpEngine = null;
145 resolver = new AttributeResolver(configuration);
147 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
148 IdPConfig.originConfigNamespace, "ReleasePolicyEngine");
150 if (itemElements.getLength() > 1) {
151 log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements. Using first...");
153 if (itemElements.getLength() < 1) {
154 arpEngine = new ArpEngine();
156 arpEngine = new ArpEngine((Element) itemElements.item(0));
159 } catch (ArpException ae) {
160 log.fatal("The Identity Provider could not be initialized "
161 + "due to a problem with the ARP Engine configuration: " + ae);
162 throw new ShibbolethConfigurationException("Could not load ARP Engine.");
163 } catch (AttributeResolverException ne) {
164 log.fatal("The Identity Provider could not be initialized due "
165 + "to a problem with the Attribute Resolver configuration: " + ne);
166 throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
169 // Load protocol handlers and support library
170 protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
172 log.debug("Starting with Shibboleth v1 protocol handling enabled.");
174 protocolHandlers.put("https://wraith.memphis.edu/shibboleth/SSO", new ShibbolethV1SSOHandler());
177 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
178 "FederationProvider");
179 for (int i = 0; i < itemElements.getLength(); i++) {
180 protocolSupport.addFederationProvider((Element) itemElements.item(i));
182 if (protocolSupport.providerCount() < 1) {
183 log.error("No Federation Provider metadata loaded.");
184 throw new ShibbolethConfigurationException("Could not load federation metadata.");
187 log.info("Identity Provider initialization complete.");
189 } catch (ShibbolethConfigurationException ae) {
190 log.fatal("The Identity Provider could not be initialized: " + ae);
191 throw new UnavailableException("Identity Provider failed to initialize.");
192 } catch (SAMLException se) {
193 log.fatal("SAML SOAP binding could not be loaded: " + se);
194 throw new UnavailableException("Identity Provider failed to initialize.");
199 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
200 * javax.servlet.http.HttpServletResponse)
202 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
204 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
205 MDC.put("remoteAddr", request.getRemoteAddr());
206 log.debug("Recieved a request via GET for endpoint (" + request.getRequestURL() + ").");
209 // TODO this throttle should probably just wrap signing operations...
212 // Determine which protocol we are responding to (at this point normally Shibv1 vs. EAuth)
213 IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURL()
215 if (activeHandler == null) {
216 log.error("No protocol handler registered for endpoint (" + request.getRequestURL() + ").");
217 throw new SAMLException("Request submitted to an invalid endpoint.");
220 // Pass request to the appropriate handler
221 log.info("Processing " + activeHandler.getHandlerName() + " request.");
222 if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
223 // This shouldn't happen unless somebody configures a protocol handler incorrectly
224 log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
225 throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
228 } catch (SAMLException ex) {
230 displayBrowserError(request, response, ex);
238 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
239 * javax.servlet.http.HttpServletResponse)
241 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
243 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
244 MDC.put("remoteAddr", request.getRemoteAddr());
245 log.debug("Recieved a request via POST for endpoint (" + request.getRequestURI() + ").");
247 // Parse SOAP request and marshall SAML request object
248 SAMLRequest samlRequest = null;
251 samlRequest = binding.receive(request);
252 } catch (SAMLException e) {
253 log.fatal("Unable to parse request: " + e);
254 throw new SAMLException("Invalid request data.");
257 // If we have DEBUG logging turned on, dump out the request to the log
258 // This takes some processing, so only do it if we need to
259 if (log.isDebugEnabled()) {
261 log.debug("Dumping generated SAML Request:"
262 + System.getProperty("line.separator")
263 + new String(new BASE64Decoder().decodeBuffer(new String(samlRequest.toBase64(), "ASCII")),
265 } catch (SAMLException e) {
266 log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
267 } catch (IOException e) {
268 log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
272 // Determine which protocol handler is active for this endpoint
273 IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURI());
274 if (activeHandler == null) {
275 log.error("No protocol handler registered for endpoint (" + request.getRequestURI() + ").");
276 throw new SAMLException("Request submitted to an invalid endpoint.");
279 // Pass request to the appropriate handler and respond
280 log.info("Processing " + activeHandler.getHandlerName() + " request.");
282 SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
283 binding.respond(response, samlResponse, null);
285 } catch (SAMLException e) {
286 sendFailureToSAMLBinding(response, samlRequest, e);
290 private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
291 SAMLException exception) throws ServletException {
293 log.error("Error while processing request: " + exception);
295 SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
297 if (log.isDebugEnabled()) {
299 log.debug("Dumping generated SAML Error Response:"
300 + System.getProperty("line.separator")
302 new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
304 } catch (IOException e) {
305 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
308 binding.respond(httpResponse, samlResponse, null);
309 log.debug("Returning SAML Error Response.");
310 } catch (SAMLException se) {
312 binding.respond(httpResponse, null, exception);
313 } catch (SAMLException e) {
314 log.error("Caught exception while responding to requester: " + e.getMessage());
316 httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
317 } catch (IOException ee) {
318 log.fatal("Could not construct a SAML error response: " + ee);
319 throw new ServletException("Identity Provider response failure.");
322 log.error("Identity Provider failed to make an error message: " + se);
326 private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
327 throws ServletException, IOException {
329 req.setAttribute("errorText", e.toString());
330 req.setAttribute("requestURL", req.getRequestURI().toString());
331 RequestDispatcher rd = req.getRequestDispatcher("/IdPError.jsp");
332 rd.forward(req, res);
335 private class Semaphore {
339 public Semaphore(int value) {
344 public synchronized void enter() {
350 } catch (InterruptedException e) {
351 // squelch and continue
356 public synchronized void exit() {
365 class FederationProviderFactory {
367 private static Logger log = Logger.getLogger(FederationProviderFactory.class.getName());
369 public static Metadata loadProvider(Element e) throws MetadataException {
371 String className = e.getAttribute("type");
372 if (className == null || className.equals("")) {
373 log.error("Federation Provider requires specification of the attribute \"type\".");
374 throw new MetadataException("Failed to initialize Federation Provider.");
377 Class[] params = {Class.forName("org.w3c.dom.Element"),};
378 return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
379 } catch (Exception loaderException) {
380 log.error("Failed to load Federation Provider implementation class: " + loaderException);
381 Throwable cause = loaderException.getCause();
382 while (cause != null) {
383 log.error("caused by: " + cause);
384 cause = cause.getCause();
386 throw new MetadataException("Failed to initialize Federation Provider.");