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.Random;
32 import javax.servlet.RequestDispatcher;
33 import javax.servlet.ServletException;
34 import javax.servlet.UnavailableException;
35 import javax.servlet.http.HttpServlet;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
39 import org.apache.log4j.Logger;
40 import org.apache.log4j.MDC;
41 import org.opensaml.SAMLBinding;
42 import org.opensaml.SAMLBindingFactory;
43 import org.opensaml.SAMLException;
44 import org.opensaml.SAMLRequest;
45 import org.opensaml.SAMLResponse;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.Element;
48 import org.w3c.dom.NodeList;
50 import sun.misc.BASE64Decoder;
51 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
52 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
53 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
54 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
55 import edu.internet2.middleware.shibboleth.common.Credentials;
56 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
57 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
58 import edu.internet2.middleware.shibboleth.common.NameMapper;
59 import edu.internet2.middleware.shibboleth.common.OriginConfig;
60 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
61 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
62 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
63 import edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler;
64 import edu.internet2.middleware.shibboleth.metadata.Metadata;
65 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
68 * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
69 * IdP processing components, and delivers proper protocol responses.
71 * @author Walter Hoehn
74 public class IdPResponder extends HttpServlet {
76 private static Logger transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
77 private static Logger log = Logger.getLogger(IdPResponder.class.getName());
78 private static Random idgen = new Random();
79 private SAMLBinding binding;
80 private Semaphore throttle;
81 private IdPConfig configuration;
82 private HashMap protocolHandlers = new HashMap();
83 private IdPProtocolSupport protocolSupport;
86 * @see javax.servlet.GenericServlet#init()
88 public void init() throws ServletException {
91 MDC.put("serviceId", "[IdP] Core");
92 log.info("Initializing Identity Provider.");
95 binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
97 Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
99 // Load global configuration properties
100 configuration = new IdPConfig(originConfig.getDocumentElement());
102 // Load a semaphore that throttles how many requests the IdP will handle at once
103 throttle = new Semaphore(configuration.getMaxThreads());
105 // Load name mappings
106 NameMapper nameMapper = new NameMapper();
107 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
108 NameIdentifierMapping.mappingNamespace, "NameMapping");
110 for (int i = 0; i < itemElements.getLength(); i++) {
112 nameMapper.addNameMapping((Element) itemElements.item(i));
113 } catch (NameIdentifierMappingException e) {
114 log.error("Name Identifier mapping could not be loaded: " + e);
118 // Load signing credentials
119 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
121 if (itemElements.getLength() < 1) {
122 log.error("No credentials specified.");
124 if (itemElements.getLength() > 1) {
125 log.error("Multiple Credentials specifications found, using first.");
127 Credentials credentials = new Credentials((Element) itemElements.item(0));
129 // Load relying party config
130 ServiceProviderMapper spMapper;
132 spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
134 } catch (ServiceProviderMapperException e) {
135 log.error("Could not load Identity Provider configuration: " + e);
136 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
139 // Startup Attribute Resolver & ARP engine
140 AttributeResolver resolver = null;
141 ArpEngine arpEngine = null;
143 resolver = new AttributeResolver(configuration);
145 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
146 "ReleasePolicyEngine");
148 if (itemElements.getLength() > 1) {
149 log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements. Using first...");
151 if (itemElements.getLength() < 1) {
152 arpEngine = new ArpEngine();
154 arpEngine = new ArpEngine((Element) itemElements.item(0));
157 } catch (ArpException ae) {
158 log.fatal("The Identity Provider could not be initialized "
159 + "due to a problem with the ARP Engine configuration: " + ae);
160 throw new ShibbolethConfigurationException("Could not load ARP Engine.");
161 } catch (AttributeResolverException ne) {
162 log.fatal("The Identity Provider could not be initialized due "
163 + "to a problem with the Attribute Resolver configuration: " + ne);
164 throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
167 // Load protocol handlers and support library
168 protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
170 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
173 //TODO Default if no handlers are specified
175 for (int i = 0; i < itemElements.getLength(); i++) {
176 IdPProtocolHandler handler = ProtocolHandlerFactory.getInstance((Element) itemElements.item(i));
178 //TODO finish fleshing this out
179 log.debug("Starting with Shibboleth v1 protocol handling enabled.");
180 protocolHandlers.put("https://wraith.memphis.edu/shibboleth/SSO", new ShibbolethV1SSOHandler());
183 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
184 "FederationProvider");
185 for (int i = 0; i < itemElements.getLength(); i++) {
186 protocolSupport.addFederationProvider((Element) itemElements.item(i));
188 if (protocolSupport.providerCount() < 1) {
189 log.error("No Federation Provider metadata loaded.");
190 throw new ShibbolethConfigurationException("Could not load federation metadata.");
193 log.info("Identity Provider initialization complete.");
195 } catch (ShibbolethConfigurationException ae) {
196 log.fatal("The Identity Provider could not be initialized: " + ae);
197 throw new UnavailableException("Identity Provider failed to initialize.");
198 } catch (SAMLException se) {
199 log.fatal("SAML SOAP binding could not be loaded: " + se);
200 throw new UnavailableException("Identity Provider failed to initialize.");
205 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
206 * javax.servlet.http.HttpServletResponse)
208 public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
210 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
211 MDC.put("remoteAddr", request.getRemoteAddr());
212 log.debug("Recieved a request via GET for endpoint (" + request.getRequestURL() + ").");
215 // TODO this throttle should probably just wrap signing operations...
218 // Determine which protocol we are responding to (at this point normally Shibv1 vs. EAuth)
219 IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURL()
221 if (activeHandler == null) {
222 log.error("No protocol handler registered for endpoint (" + request.getRequestURL() + ").");
223 throw new SAMLException("Request submitted to an invalid endpoint.");
226 // Pass request to the appropriate handler
227 log.info("Processing " + activeHandler.getHandlerName() + " request.");
228 if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
229 // This shouldn't happen unless somebody configures a protocol handler incorrectly
230 log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
231 throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
234 } catch (SAMLException ex) {
236 displayBrowserError(request, response, ex);
244 * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
245 * javax.servlet.http.HttpServletResponse)
247 public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
249 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
250 MDC.put("remoteAddr", request.getRemoteAddr());
251 log.debug("Recieved a request via POST for endpoint (" + request.getRequestURL() + ").");
253 // Parse SOAP request and marshall SAML request object
254 SAMLRequest samlRequest = null;
257 samlRequest = binding.receive(request);
258 } catch (SAMLException e) {
259 log.fatal("Unable to parse request: " + e);
260 throw new SAMLException("Invalid request data.");
263 // If we have DEBUG logging turned on, dump out the request to the log
264 // This takes some processing, so only do it if we need to
265 if (log.isDebugEnabled()) {
267 log.debug("Dumping generated SAML Request:"
268 + System.getProperty("line.separator")
269 + new String(new BASE64Decoder().decodeBuffer(new String(samlRequest.toBase64(), "ASCII")),
271 } catch (SAMLException e) {
272 log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
273 } catch (IOException e) {
274 log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
278 // Determine which protocol handler is active for this endpoint
279 IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURL()
281 if (activeHandler == null) {
282 log.error("No protocol handler registered for endpoint (" + request.getRequestURL() + ").");
283 throw new SAMLException("Request submitted to an invalid endpoint.");
286 // Pass request to the appropriate handler and respond
287 log.info("Processing " + activeHandler.getHandlerName() + " request.");
289 SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
290 binding.respond(response, samlResponse, null);
292 } catch (SAMLException e) {
293 sendFailureToSAMLBinding(response, samlRequest, e);
297 private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
298 SAMLException exception) throws ServletException {
300 log.error("Error while processing request: " + exception);
302 SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
304 if (log.isDebugEnabled()) {
306 log.debug("Dumping generated SAML Error Response:"
307 + System.getProperty("line.separator")
309 new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
311 } catch (IOException e) {
312 log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
315 binding.respond(httpResponse, samlResponse, null);
316 log.debug("Returning SAML Error Response.");
317 } catch (SAMLException se) {
319 binding.respond(httpResponse, null, exception);
320 } catch (SAMLException e) {
321 log.error("Caught exception while responding to requester: " + e.getMessage());
323 httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
324 } catch (IOException ee) {
325 log.fatal("Could not construct a SAML error response: " + ee);
326 throw new ServletException("Identity Provider response failure.");
329 log.error("Identity Provider failed to make an error message: " + se);
333 private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
334 throws ServletException, IOException {
336 req.setAttribute("errorText", e.toString());
337 req.setAttribute("requestURL", req.getRequestURI().toString());
338 RequestDispatcher rd = req.getRequestDispatcher("/IdPError.jsp");
339 rd.forward(req, res);
342 private class Semaphore {
346 public Semaphore(int value) {
351 public synchronized void enter() {
357 } catch (InterruptedException e) {
358 // squelch and continue
363 public synchronized void exit() {
372 class FederationProviderFactory {
374 private static Logger log = Logger.getLogger(FederationProviderFactory.class.getName());
376 public static Metadata loadProvider(Element e) throws MetadataException {
378 String className = e.getAttribute("type");
379 if (className == null || className.equals("")) {
380 log.error("Federation Provider requires specification of the attribute \"type\".");
381 throw new MetadataException("Failed to initialize Federation Provider.");
384 Class[] params = {Class.forName("org.w3c.dom.Element"),};
385 return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
386 } catch (Exception loaderException) {
387 log.error("Failed to load Federation Provider implementation class: " + loaderException);
388 Throwable cause = loaderException.getCause();
389 while (cause != null) {
390 log.error("caused by: " + cause);
391 cause = cause.getCause();
393 throw new MetadataException("Failed to initialize Federation Provider.");