da31a31dd63184a33402db0b659fc0e3b81f1819
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPResponder.java
1 /*
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.
24  */
25
26 package edu.internet2.middleware.shibboleth.idp;
27
28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.Random;
32
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;
41
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;
52
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;
70
71 /**
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.
74  * 
75  * @author Walter Hoehn
76  */
77
78 public class IdPResponder extends HttpServlet {
79
80         private static Logger transactionLog;
81         private static Logger log;
82         private static Random idgen = new Random();
83         private SAMLBinding binding;
84
85         private IdPConfig configuration;
86         private HashMap protocolHandlers = new HashMap();
87         private IdPProtocolSupport protocolSupport;
88
89         /*
90          * @see javax.servlet.GenericServlet#init()
91          */
92         public void init() throws ServletException {
93
94                 super.init();
95
96                 try {
97                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
98
99                         Document idPConfig = IdPConfigLoader.getIdPConfig(this.getServletContext());
100
101                         // Initialize logging
102                         NodeList itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
103                                         "Logging");
104                         if (itemElements.getLength() > 0) {
105                                 if (itemElements.getLength() > 1) {
106                                         System.err
107                                                         .println("WARNING: More than one Logging element in IdP configuration, using the first one.");
108                                 } else {
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.");
115                                 }
116                         }
117
118                         // Load global configuration properties
119                         configuration = new IdPConfig(idPConfig.getDocumentElement());
120
121                         // Load name mappings
122                         NameMapper nameMapper = new NameMapper();
123                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(
124                                         NameIdentifierMapping.mappingNamespace, "NameMapping");
125
126                         for (int i = 0; i < itemElements.getLength(); i++) {
127                                 try {
128                                         nameMapper.addNameMapping((Element) itemElements.item(i));
129                                 } catch (NameIdentifierMappingException e) {
130                                         log.error("Name Identifier mapping could not be loaded: " + e);
131                                 }
132                         }
133
134                         // Load signing credentials
135                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
136                                         "Credentials");
137                         if (itemElements.getLength() < 1) {
138                                 log.error("No credentials specified.");
139                         }
140                         if (itemElements.getLength() > 1) {
141                                 log.error("Multiple Credentials specifications found, using first.");
142                         }
143                         Credentials credentials = new Credentials((Element) itemElements.item(0));
144
145                         // Load relying party config
146                         ServiceProviderMapper spMapper;
147                         try {
148                                 spMapper = new ServiceProviderMapper(idPConfig.getDocumentElement(), configuration, credentials,
149                                                 nameMapper);
150                         } catch (ServiceProviderMapperException e) {
151                                 log.error("Could not load Identity Provider configuration: " + e);
152                                 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
153                         }
154
155                         // Startup Attribute Resolver & ARP engine
156                         AttributeResolver resolver = null;
157                         ArpEngine arpEngine = null;
158                         try {
159                                 resolver = new AttributeResolver(configuration);
160
161                                 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
162                                                 "ReleasePolicyEngine");
163
164                                 if (itemElements.getLength() > 1) {
165                                         log.warn("Encountered multiple <ReleasePolicyEngine/> configuration elements.  Using first...");
166                                 }
167                                 if (itemElements.getLength() < 1) {
168                                         arpEngine = new ArpEngine();
169                                 } else {
170                                         arpEngine = new ArpEngine((Element) itemElements.item(0));
171                                 }
172
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.");
181                         }
182
183                         // Load artifact mapping implementation
184                         ArtifactMapper artifactMapper = null;
185                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
186                                         "ArtifactMapper");
187                         if (itemElements.getLength() > 1) {
188                                 log.warn("Encountered multiple <ArtifactMapper/> configuration elements.  Using first...");
189                         }
190                         if (itemElements.getLength() > 0) {
191                                 artifactMapper = ArtifactMapperFactory.getInstance((Element) itemElements.item(0));
192                         } else {
193                                 log.debug("No Artifact Mapper configuration found.  Defaulting to Memory-based implementation.");
194                                 artifactMapper = new MemoryArtifactMapper();
195                         }
196
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,
201                                         "ProtocolHandler");
202
203                         // Default if no handlers are specified
204                         if (itemElements.getLength() < 1) {
205                                 itemElements = getDefaultHandlers();
206
207                                 // If handlers were specified, load them and register them against their locations
208                         }
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;
218                                         }
219                                         log.info("Registering handler (" + handler.getHandlerName() + ") to listen at (" + locations[j]
220                                                         + ").");
221                                         protocolHandlers.put(locations[j].toString(), handler);
222                                 }
223                         }
224
225                         // Load metadata
226                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
227                                         "MetadataProvider");
228                         for (int i = 0; i < itemElements.getLength(); i++) {
229                                 protocolSupport.addMetadataProvider((Element) itemElements.item(i));
230                         }
231                         if (protocolSupport.providerCount() < 1) {
232                                 log.error("No Metadata Provider metadata loaded.");
233                                 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
234                         }
235
236                         log.info("Identity Provider initialization complete.");
237
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.");
244                 }
245         }
246
247         /*
248          * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
249          *      javax.servlet.http.HttpServletResponse)
250          */
251         public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
252
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() + ").");
256
257                 try {
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.");
265                         }
266
267                 } catch (SAMLException ex) {
268                         log.error(ex);
269                         displayBrowserError(request, response, ex);
270                         return;
271                 }
272         }
273
274         /*
275          * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
276          *      javax.servlet.http.HttpServletResponse)
277          */
278         public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
279
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() + ").");
283
284                 // Parse SOAP request and marshall SAML request object
285                 SAMLRequest samlRequest = null;
286                 try {
287                         try {
288                                 samlRequest = binding.receive(request);
289                         } catch (SAMLException e) {
290                                 log.fatal("Unable to parse request: " + e);
291                                 throw new SAMLException("Invalid request data.");
292                         }
293
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());
299                         }
300
301                         IdPProtocolHandler activeHandler = lookupProtocolHandler(request);
302                         // Pass request to the appropriate handler and respond
303                         log.info("Processing " + activeHandler.getHandlerName() + " request.");
304
305                         SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
306                         binding.respond(response, samlResponse, null);
307
308                 } catch (SAMLException e) {
309                         sendFailureToSAMLBinding(response, samlRequest, e);
310                 }
311         }
312
313         private IdPProtocolHandler lookupProtocolHandler(HttpServletRequest request) throws SAMLException {
314
315                 // Determine which protocol handler is active for this endpoint
316                 String requestURL = request.getRequestURL().toString();
317                 IdPProtocolHandler activeHandler = null;
318
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);
325                                 break;
326                         }
327                 }
328
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.");
332                 }
333                 return activeHandler;
334         }
335
336         private NodeList getDefaultHandlers() throws ShibbolethConfigurationException {
337
338                 log.debug("Loading default protocol handler configuration.");
339                 try {
340                         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
341                         docFactory.setNamespaceAware(true);
342                         Document placeHolder = docFactory.newDocumentBuilder().newDocument();
343                         Element baseNode = placeHolder.createElementNS(IdPConfig.configNameSpace, "IdPConfig");
344
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);
352
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);
360
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);
368
369                         return baseNode.getElementsByTagNameNS(IdPConfig.configNameSpace, "ProtocolHandler");
370
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.");
374                 }
375         }
376
377         private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
378                         SAMLException exception) throws ServletException {
379
380                 log.error("Error while processing request: " + exception);
381                 try {
382                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
383                                         null, exception);
384                         if (log.isDebugEnabled()) {
385                                 log.debug("Dumping generated SAML Error Response:" + System.getProperty("line.separator")
386                                                 + samlResponse.toString());
387                         }
388                         binding.respond(httpResponse, samlResponse, null);
389                         log.debug("Returning SAML Error Response.");
390                 } catch (SAMLException se) {
391                         try {
392                                 binding.respond(httpResponse, null, exception);
393                         } catch (SAMLException e) {
394                                 log.error("Caught exception while responding to requester: " + e.getMessage());
395                                 try {
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.");
400                                 }
401                         }
402                         log.error("Identity Provider failed to make an error message: " + se);
403                 }
404         }
405
406         private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
407                         throws ServletException, IOException {
408
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);
413         }
414
415 }
416
417 class MetadataProviderFactory {
418
419         private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
420
421         public static Metadata loadProvider(Element e) throws MetadataException {
422
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.");
427                 } else {
428                         try {
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();
437                                 }
438                                 throw new MetadataException("Failed to initialize Metadata Provider.");
439                         }
440                 }
441         }
442 }