Fixed an IdP configuration schema bug.
[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.net.MalformedURLException;
30 import java.net.URI;
31 import java.net.URL;
32 import java.util.HashMap;
33 import java.util.Random;
34
35 import javax.servlet.RequestDispatcher;
36 import javax.servlet.ServletException;
37 import javax.servlet.UnavailableException;
38 import javax.servlet.http.HttpServlet;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
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.OriginConfig;
65 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
66 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
67 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
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 = Logger.getLogger("Shibboleth-TRANSACTION");
81         private static Logger log = Logger.getLogger(IdPResponder.class.getName());
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                 MDC.put("serviceId", "[IdP] Core");
96                 log.info("Initializing Identity Provider.");
97
98                 try {
99                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
100
101                         Document originConfig = OriginConfig.getOriginConfig(this.getServletContext());
102
103                         // Load global configuration properties
104                         configuration = new IdPConfig(originConfig.getDocumentElement());
105
106                         // Load name mappings
107                         NameMapper nameMapper = new NameMapper();
108                         NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
109                                         NameIdentifierMapping.mappingNamespace, "NameMapping");
110
111                         for (int i = 0; i < itemElements.getLength(); i++) {
112                                 try {
113                                         nameMapper.addNameMapping((Element) itemElements.item(i));
114                                 } catch (NameIdentifierMappingException e) {
115                                         log.error("Name Identifier mapping could not be loaded: " + e);
116                                 }
117                         }
118
119                         // Load signing credentials
120                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
121                                         "Credentials");
122                         if (itemElements.getLength() < 1) {
123                                 log.error("No credentials specified.");
124                         }
125                         if (itemElements.getLength() > 1) {
126                                 log.error("Multiple Credentials specifications found, using first.");
127                         }
128                         Credentials credentials = new Credentials((Element) itemElements.item(0));
129
130                         // Load relying party config
131                         ServiceProviderMapper spMapper;
132                         try {
133                                 spMapper = new ServiceProviderMapper(originConfig.getDocumentElement(), configuration, credentials,
134                                                 nameMapper);
135                         } catch (ServiceProviderMapperException e) {
136                                 log.error("Could not load Identity Provider configuration: " + e);
137                                 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
138                         }
139
140                         // Startup Attribute Resolver & ARP engine
141                         AttributeResolver resolver = null;
142                         ArpEngine arpEngine = null;
143                         try {
144                                 resolver = new AttributeResolver(configuration);
145
146                                 itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
147                                                 "ReleasePolicyEngine");
148
149                                 if (itemElements.getLength() > 1) {
150                                         log.warn("Encountered multiple <ReleasePolicyEngine/> configuration elements.  Using first...");
151                                 }
152                                 if (itemElements.getLength() < 1) {
153                                         arpEngine = new ArpEngine();
154                                 } else {
155                                         arpEngine = new ArpEngine((Element) itemElements.item(0));
156                                 }
157
158                         } catch (ArpException ae) {
159                                 log.fatal("The Identity Provider could not be initialized "
160                                                 + "due to a problem with the ARP Engine configuration: " + ae);
161                                 throw new ShibbolethConfigurationException("Could not load ARP Engine.");
162                         } catch (AttributeResolverException ne) {
163                                 log.fatal("The Identity Provider could not be initialized due "
164                                                 + "to a problem with the Attribute Resolver configuration: " + ne);
165                                 throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
166                         }
167
168                         // Load artifact mapping implementation
169                         ArtifactMapper artifactMapper = null;
170                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
171                                         "ArtifactMapper");
172                         if (itemElements.getLength() > 1) {
173                                 log.warn("Encountered multiple <ArtifactMapper/> configuration elements.  Using first...");
174                         }
175                         if (itemElements.getLength() > 0) {
176                                 artifactMapper = ArtifactMapperFactory.getInstance((Element) itemElements.item(0));
177                         } else {
178                                 log.debug("No Artifact Mapper configuration found.  Defaulting to Memory-based implementation.");
179                                 artifactMapper = new MemoryArtifactMapper();
180                         }
181
182                         // Load protocol handlers and support library
183                         protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
184                                         resolver, artifactMapper);
185                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
186                                         "ProtocolHandler");
187
188                         // Default if no handlers are specified
189                         if (itemElements.getLength() < 1) {
190                                 // TODO work out defaulting
191
192                                 // If handlers were specified, load them and register them against their locations
193                         } else {
194                                 EACHHANDLER : for (int i = 0; i < itemElements.getLength(); i++) {
195                                         IdPProtocolHandler handler = ProtocolHandlerFactory.getInstance((Element) itemElements.item(i));
196                                         URI[] locations = handler.getLocations();
197                                         EACHLOCATION : for (int j = 0; j < locations.length; j++) {
198                                                 if (protocolHandlers.containsKey(locations[j].toString())) {
199                                                         log.error("Multiple protocol handlers are registered to listen at ("
200                                                                         + locations[j]
201                                                                         + ").  Ignoring all except ("
202                                                                         + ((IdPProtocolHandler) protocolHandlers.get(locations[j].toString()))
203                                                                                         .getHandlerName() + ").");
204                                                         continue EACHLOCATION;
205                                                 }
206                                                 log.info("Registering handler (" + handler.getHandlerName() + ") to listen at (" + locations[j]
207                                                                 + ").");
208                                                 protocolHandlers.put(locations[j].toString(), handler);
209                                         }
210                                 }
211                         }
212
213                         // Load metadata
214                         itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
215                                         "MetadataProvider");
216                         for (int i = 0; i < itemElements.getLength(); i++) {
217                                 protocolSupport.addMetadataProvider((Element) itemElements.item(i));
218                         }
219                         if (protocolSupport.providerCount() < 1) {
220                                 log.error("No Metadata Provider metadata loaded.");
221                                 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
222                         }
223
224                         log.info("Identity Provider initialization complete.");
225
226                 } catch (ShibbolethConfigurationException ae) {
227                         log.fatal("The Identity Provider could not be initialized: " + ae);
228                         throw new UnavailableException("Identity Provider failed to initialize.");
229                 } catch (SAMLException se) {
230                         log.fatal("SAML SOAP binding could not be loaded: " + se);
231                         throw new UnavailableException("Identity Provider failed to initialize.");
232                 }
233         }
234
235         /*
236          * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
237          *      javax.servlet.http.HttpServletResponse)
238          */
239         public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
240
241                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
242                 MDC.put("remoteAddr", request.getRemoteAddr());
243                 log.debug("Recieved a request via GET for location (" + request.getRequestURL() + ").");
244
245                 try {
246                         // Determine which protocol we are responding to (at this point normally Shibv1 vs. EAuth)
247                         String requestURL = request.getRequestURL().toString();
248                         IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(requestURL);
249                         if (activeHandler == null) {
250                                 log.debug("No protocol handler registered for location (" + request.getRequestURL()
251                                                 + ").  Attempting to match against relative path.");
252                                 try {
253                                         activeHandler = (IdPProtocolHandler) protocolHandlers.get(new URL(requestURL).getPath());
254                                 } catch (MalformedURLException e) {
255                                         // squelch, we will just fail to find a handler
256                                 }
257                         }
258
259                         if (activeHandler == null) {
260                                 log.error("No protocol handler registered for location (" + request.getRequestURL() + ").");
261                                 throw new SAMLException("Request submitted to an invalid location.");
262                         }
263
264                         // Pass request to the appropriate handler
265                         log.info("Processing " + activeHandler.getHandlerName() + " request.");
266                         if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
267                                 // This shouldn't happen unless somebody configures a protocol handler incorrectly
268                                 log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
269                                 throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
270                         }
271
272                 } catch (SAMLException ex) {
273                         log.error(ex);
274                         displayBrowserError(request, response, ex);
275                         return;
276                 }
277         }
278
279         /*
280          * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
281          *      javax.servlet.http.HttpServletResponse)
282          */
283         public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
284
285                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
286                 MDC.put("remoteAddr", request.getRemoteAddr());
287                 log.debug("Recieved a request via POST for location (" + request.getRequestURL() + ").");
288
289                 // Parse SOAP request and marshall SAML request object
290                 SAMLRequest samlRequest = null;
291                 try {
292                         try {
293                                 samlRequest = binding.receive(request);
294                         } catch (SAMLException e) {
295                                 log.fatal("Unable to parse request: " + e);
296                                 throw new SAMLException("Invalid request data.");
297                         }
298
299                         // If we have DEBUG logging turned on, dump out the request to the log
300                         // This takes some processing, so only do it if we need to
301                         if (log.isDebugEnabled()) {
302                                 log.debug("Dumping generated SAML Request:" + System.getProperty("line.separator")
303                                                 + samlRequest.toString());
304                         }
305
306                         // Determine which protocol handler is active for this endpoint
307                         String requestURL = request.getRequestURL().toString();
308                         IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(requestURL);
309                         if (activeHandler == null) {
310                                 log.debug("No protocol handler registered for location (" + request.getRequestURL()
311                                                 + ").  Attempting to match against relative path.");
312                                 try {
313                                         activeHandler = (IdPProtocolHandler) protocolHandlers.get(new URL(requestURL).getPath());
314                                 } catch (MalformedURLException e) {
315                                         // squelch, we will just fail to find a handler
316                                 }
317                         }
318
319                         // Pass request to the appropriate handler and respond
320                         log.info("Processing " + activeHandler.getHandlerName() + " request.");
321
322                         SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
323                         binding.respond(response, samlResponse, null);
324
325                 } catch (SAMLException e) {
326                         sendFailureToSAMLBinding(response, samlRequest, e);
327                 }
328         }
329
330         private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
331                         SAMLException exception) throws ServletException {
332
333                 log.error("Error while processing request: " + exception);
334                 try {
335                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
336                                         null, exception);
337                         if (log.isDebugEnabled()) {
338                                 log.debug("Dumping generated SAML Error Response:" + System.getProperty("line.separator")
339                                                 + samlResponse.toString());
340                         }
341                         binding.respond(httpResponse, samlResponse, null);
342                         log.debug("Returning SAML Error Response.");
343                 } catch (SAMLException se) {
344                         try {
345                                 binding.respond(httpResponse, null, exception);
346                         } catch (SAMLException e) {
347                                 log.error("Caught exception while responding to requester: " + e.getMessage());
348                                 try {
349                                         httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
350                                 } catch (IOException ee) {
351                                         log.fatal("Could not construct a SAML error response: " + ee);
352                                         throw new ServletException("Identity Provider response failure.");
353                                 }
354                         }
355                         log.error("Identity Provider failed to make an error message: " + se);
356                 }
357         }
358
359         private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
360                         throws ServletException, IOException {
361
362                 req.setAttribute("errorText", e.toString());
363                 req.setAttribute("requestURL", req.getRequestURI().toString());
364                 RequestDispatcher rd = req.getRequestDispatcher("/IdPError.jsp");
365                 rd.forward(req, res);
366         }
367
368 }
369
370 class MetadataProviderFactory {
371
372         private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
373
374         public static Metadata loadProvider(Element e) throws MetadataException {
375
376                 String className = e.getAttribute("type");
377                 if (className == null || className.equals("")) {
378                         log.error("Metadata Provider requires specification of the attribute \"type\".");
379                         throw new MetadataException("Failed to initialize Metadata Provider.");
380                 } else {
381                         try {
382                                 Class[] params = {Class.forName("org.w3c.dom.Element"),};
383                                 return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
384                         } catch (Exception loaderException) {
385                                 log.error("Failed to load Metadata Provider implementation class: " + loaderException);
386                                 Throwable cause = loaderException.getCause();
387                                 while (cause != null) {
388                                         log.error("caused by: " + cause);
389                                         cause = cause.getCause();
390                                 }
391                                 throw new MetadataException("Failed to initialize Metadata Provider.");
392                         }
393                 }
394         }
395 }