274d2599ea47c06becfb7f1d2721a3a0a28f06d0
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPResponder.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp;
18
19 import java.io.IOException;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.Random;
23
24 import javax.servlet.RequestDispatcher;
25 import javax.servlet.ServletConfig;
26 import javax.servlet.ServletException;
27 import javax.servlet.UnavailableException;
28 import javax.servlet.http.HttpServlet;
29 import javax.servlet.http.HttpServletRequest;
30 import javax.servlet.http.HttpServletResponse;
31 import javax.xml.parsers.DocumentBuilderFactory;
32 import javax.xml.parsers.ParserConfigurationException;
33
34 import org.apache.log4j.Logger;
35 import org.apache.log4j.MDC;
36 import org.opensaml.SAMLBinding;
37 import org.opensaml.SAMLBindingFactory;
38 import org.opensaml.SAMLException;
39 import org.opensaml.SAMLRequest;
40 import org.opensaml.SAMLResponse;
41 import org.w3c.dom.Document;
42 import org.w3c.dom.Element;
43 import org.w3c.dom.NodeList;
44
45 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
46 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
47 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
48 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
49 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
50 import edu.internet2.middleware.shibboleth.artifact.ArtifactMapperFactory;
51 import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
52 import edu.internet2.middleware.shibboleth.common.Credentials;
53 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
54 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
55 import edu.internet2.middleware.shibboleth.common.NameMapper;
56 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
57 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
58 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
59 import edu.internet2.middleware.shibboleth.log.LoggingInitializer;
60 import edu.internet2.middleware.shibboleth.metadata.Metadata;
61 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
62
63 /**
64  * Primary entry point for requests to the SAML IdP. Listens on multiple endpoints, routes requests to the appropriate
65  * IdP processing components, and delivers proper protocol responses.
66  * 
67  * @author Walter Hoehn
68  */
69
70 public class IdPResponder extends HttpServlet {
71
72         private static Logger transactionLog;
73         private static Logger log;
74         private static Random idgen = new Random();
75         private SAMLBinding binding;
76
77         private IdPConfig configuration;
78         private HashMap protocolHandlers = new HashMap();
79         private IdPProtocolSupport protocolSupport;
80
81         /*
82          * @see javax.servlet.GenericServlet#init()
83          */
84         public void init(ServletConfig servletConfig) throws ServletException {
85
86                 super.init(servletConfig);
87
88                 try {
89                         binding = SAMLBindingFactory.getInstance(SAMLBinding.SOAP);
90
91                         Document idPConfig = IdPConfigLoader.getIdPConfig(this.getServletContext());
92
93                         // Initialize logging
94                         NodeList itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
95                                         "Logging");
96                         if (itemElements.getLength() > 0) {
97                                 if (itemElements.getLength() > 1) {
98                                         System.err.println("WARNING: More than one Logging element in IdP configuration, "
99                                                         + "using the first one.");
100                                 } else {
101                                         Element loggingConfig = (Element) itemElements.item(0);
102                                         LoggingInitializer.initializeLogging(loggingConfig);
103                                 }
104                         } else {
105                                 LoggingInitializer.initializeLogging();
106                         }
107
108                         transactionLog = Logger.getLogger("Shibboleth-TRANSACTION");
109                         log = Logger.getLogger(IdPResponder.class);
110                         MDC.put("serviceId", "[IdP] Core");
111                         log.info("Initializing Identity Provider.");
112
113                         // Load global configuration properties
114                         configuration = new IdPConfig(idPConfig.getDocumentElement());
115
116                         // Load name mappings
117                         NameMapper nameMapper = new NameMapper();
118                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(
119                                         NameIdentifierMapping.mappingNamespace, "NameMapping");
120
121                         for (int i = 0; i < itemElements.getLength(); i++) {
122                                 try {
123                                         nameMapper.addNameMapping((Element) itemElements.item(i));
124                                 } catch (NameIdentifierMappingException e) {
125                                         log.error("Name Identifier mapping could not be loaded: " + e);
126                                 }
127                         }
128
129                         // Load signing credentials
130                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(Credentials.credentialsNamespace,
131                                         "Credentials");
132                         if (itemElements.getLength() < 1) {
133                                 log.error("No credentials specified.");
134                         }
135                         if (itemElements.getLength() > 1) {
136                                 log.error("Multiple Credentials specifications found, using first.");
137                         }
138                         Credentials credentials = new Credentials((Element) itemElements.item(0));
139
140                         // Load relying party config
141                         ServiceProviderMapper spMapper;
142                         try {
143                                 spMapper = new ServiceProviderMapper(idPConfig.getDocumentElement(), configuration, credentials,
144                                                 nameMapper);
145                         } catch (ServiceProviderMapperException e) {
146                                 log.error("Could not load Identity Provider configuration: " + e);
147                                 throw new ShibbolethConfigurationException("Could not load Identity Provider configuration.");
148                         }
149
150                         // Startup Attribute Resolver & ARP engine
151                         AttributeResolver resolver = null;
152                         ArpEngine arpEngine = null;
153                         try {
154                                 resolver = new AttributeResolver(configuration);
155
156                                 itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
157                                                 "ReleasePolicyEngine");
158
159                                 if (itemElements.getLength() > 1) {
160                                         log.warn("Encountered multiple <ReleasePolicyEngine/> configuration elements.  Using first...");
161                                 }
162                                 if (itemElements.getLength() < 1) {
163                                         arpEngine = new ArpEngine();
164                                 } else {
165                                         arpEngine = new ArpEngine((Element) itemElements.item(0));
166                                 }
167
168                         } catch (ArpException ae) {
169                                 log.fatal("The Identity Provider could not be initialized "
170                                                 + "due to a problem with the ARP Engine configuration: " + ae);
171                                 throw new ShibbolethConfigurationException("Could not load ARP Engine.");
172                         } catch (AttributeResolverException ne) {
173                                 log.fatal("The Identity Provider could not be initialized due "
174                                                 + "to a problem with the Attribute Resolver configuration: " + ne);
175                                 throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
176                         }
177
178                         // Load artifact mapping implementation
179                         ArtifactMapper artifactMapper = null;
180                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
181                                         "ArtifactMapper");
182                         if (itemElements.getLength() > 1) {
183                                 log.warn("Encountered multiple <ArtifactMapper/> configuration elements.  Using first...");
184                         }
185                         if (itemElements.getLength() > 0) {
186                                 artifactMapper = ArtifactMapperFactory.getInstance((Element) itemElements.item(0));
187                         } else {
188                                 log.debug("No Artifact Mapper configuration found.  Defaulting to Memory-based implementation.");
189                                 artifactMapper = new MemoryArtifactMapper();
190                         }
191
192                         // Load protocol handlers and support library
193                         protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
194                                         resolver, artifactMapper);
195                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
196                                         "ProtocolHandler");
197
198                         // Default if no handlers are specified
199                         if (itemElements.getLength() < 1) {
200                                 itemElements = getDefaultHandlers();
201
202                                 // If handlers were specified, load them and register them against their locations
203                         }
204                         EACHHANDLER : for (int i = 0; i < itemElements.getLength(); i++) {
205                                 IdPProtocolHandler handler = ProtocolHandlerFactory.getInstance((Element) itemElements.item(i));
206                                 String[] locations = handler.getLocations();
207                                 EACHLOCATION : for (int j = 0; j < locations.length; j++) {
208                                         if (protocolHandlers.containsKey(locations[j])) {
209                                                 log.error("Multiple protocol handlers are registered to listen at (" + locations[j]
210                                                                 + ").  Ignoring all except ("
211                                                                 + ((IdPProtocolHandler) protocolHandlers.get(locations[j])).getHandlerName() + ").");
212                                                 continue EACHLOCATION;
213                                         }
214                                         log.info("Registering handler (" + handler.getHandlerName() + ") to listen at (" + locations[j]
215                                                         + ").");
216                                         protocolHandlers.put(locations[j].toString(), handler);
217                                 }
218                         }
219
220                         // Load metadata
221                         itemElements = idPConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
222                                         "MetadataProvider");
223                         for (int i = 0; i < itemElements.getLength(); i++) {
224                                 protocolSupport.addMetadataProvider((Element) itemElements.item(i));
225                         }
226                         if (protocolSupport.providerCount() < 1) {
227                                 log.error("No Metadata Provider metadata loaded.");
228                                 throw new ShibbolethConfigurationException("Could not load SAML metadata.");
229                         }
230
231                         log.info("Identity Provider initialization complete.");
232
233                 } catch (ShibbolethConfigurationException ae) {
234                         servletConfig.getServletContext().log("The Identity Provider could not be initialized: " + ae);
235                         if (log != null) {
236                                 log.fatal("The Identity Provider could not be initialized: " + ae);
237                         }
238                         throw new UnavailableException("Identity Provider failed to initialize.");
239                 } catch (SAMLException se) {
240                         log.fatal("SAML SOAP binding could not be loaded: " + se);
241                         throw new UnavailableException("Identity Provider failed to initialize.");
242                 }
243         }
244
245         /*
246          * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
247          *      javax.servlet.http.HttpServletResponse)
248          */
249         public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
250
251                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
252                 MDC.put("remoteAddr", request.getRemoteAddr());
253                 log.debug("Received a request via GET for location (" + request.getRequestURL() + ").");
254
255                 try {
256                         IdPProtocolHandler activeHandler = lookupProtocolHandler(request);
257                         // Pass request to the appropriate handler
258                         log.info("Processing " + activeHandler.getHandlerName() + " request.");
259                         if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
260                                 // This shouldn't happen unless somebody configures a protocol handler incorrectly
261                                 log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
262                                 throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
263                         }
264
265                 } catch (SAMLException ex) {
266                         log.error(ex);
267                         displayBrowserError(request, response, ex);
268                         return;
269                 }
270         }
271
272         /*
273          * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
274          *      javax.servlet.http.HttpServletResponse)
275          */
276         public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
277
278                 MDC.put("serviceId", "[IdP] " + idgen.nextInt());
279                 MDC.put("remoteAddr", request.getRemoteAddr());
280                 log.debug("Received a request via POST for location (" + request.getRequestURL() + ").");
281
282                 // Parse SOAP request and marshall SAML request object
283                 SAMLRequest samlRequest = null;
284                 try {
285                         try {
286                                 samlRequest = binding.receive(request, 1);
287                         } catch (SAMLException e) {
288                                 log.fatal("Unable to parse request: " + e);
289                                 throw new SAMLException("Invalid request data.");
290                         }
291
292                         // If we have DEBUG logging turned on, dump out the request to the log
293                         // This takes some processing, so only do it if we need to
294                         if (log.isDebugEnabled()) {
295                                 log.debug("Dumping generated SAML Request:" + System.getProperty("line.separator")
296                                                 + samlRequest.toString());
297                         }
298
299                         IdPProtocolHandler activeHandler = lookupProtocolHandler(request);
300                         // Pass request to the appropriate handler and respond
301                         log.info("Processing " + activeHandler.getHandlerName() + " request.");
302
303                         SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
304                         binding.respond(response, samlResponse, null);
305
306                 } catch (SAMLException e) {
307                         sendFailureToSAMLBinding(response, samlRequest, e);
308                 }
309         }
310
311         private IdPProtocolHandler lookupProtocolHandler(HttpServletRequest request) throws SAMLException {
312
313                 // Determine which protocol handler is active for this endpoint
314                 String requestURL = request.getRequestURL().toString();
315                 IdPProtocolHandler activeHandler = null;
316
317                 Iterator registeredLocations = protocolHandlers.keySet().iterator();
318                 while (registeredLocations.hasNext()) {
319                         String handlerLocation = (String) registeredLocations.next();
320                         if (requestURL.matches(handlerLocation)) {
321                                 log.debug("Matched handler location: (" + handlerLocation + ").");
322                                 activeHandler = (IdPProtocolHandler) protocolHandlers.get(handlerLocation);
323                                 break;
324                         }
325                 }
326
327                 if (activeHandler == null) {
328                         log.error("No protocol handler registered for location (" + request.getRequestURL() + ").");
329                         throw new SAMLException("Request submitted to an invalid location.");
330                 }
331                 return activeHandler;
332         }
333
334         private NodeList getDefaultHandlers() throws ShibbolethConfigurationException {
335
336                 log.debug("Loading default protocol handler configuration.");
337                 try {
338                         DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
339                         docFactory.setNamespaceAware(true);
340                         Document placeHolder = docFactory.newDocumentBuilder().newDocument();
341                         Element baseNode = placeHolder.createElementNS(IdPConfig.configNameSpace, "IdPConfig");
342
343                         Element ssoHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
344                         ssoHandler.setAttribute("implementation",
345                                         "edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler");
346                         Element ssoLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
347                         ssoLocation.appendChild(placeHolder.createTextNode("https?://[^/]+(:443)?/shibboleth/SSO"));
348                         ssoHandler.appendChild(ssoLocation);
349                         baseNode.appendChild(ssoHandler);
350
351                         Element attributeHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
352                         attributeHandler.setAttribute("implementation",
353                                         "edu.internet2.middleware.shibboleth.idp.provider.SAMLv1_AttributeQueryHandler");
354                         Element attributeLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
355                         attributeLocation.appendChild(placeHolder.createTextNode("https?://[^/]+:8443/shibboleth/AA"));
356                         attributeHandler.appendChild(attributeLocation);
357                         baseNode.appendChild(attributeHandler);
358
359                         Element artifactHandler = placeHolder.createElementNS(IdPConfig.configNameSpace, "ProtocolHandler");
360                         artifactHandler.setAttribute("implementation",
361                                         "edu.internet2.middleware.shibboleth.idp.provider.SAMLv1_1ArtifactQueryHandler");
362                         Element artifactLocation = placeHolder.createElementNS(IdPConfig.configNameSpace, "Location");
363                         artifactLocation.appendChild(placeHolder.createTextNode("https?://[^/]+:8443/shibboleth/Artifact"));
364                         artifactHandler.appendChild(artifactLocation);
365                         baseNode.appendChild(artifactHandler);
366
367                         return baseNode.getElementsByTagNameNS(IdPConfig.configNameSpace, "ProtocolHandler");
368
369                 } catch (ParserConfigurationException e) {
370                         log.fatal("Encoutered an error while loading default protocol handlers: " + e);
371                         throw new ShibbolethConfigurationException("Could not load protocol handlers.");
372                 }
373         }
374
375         private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
376                         SAMLException exception) throws ServletException {
377
378                 log.error("Error while processing request: " + exception);
379                 try {
380                         SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
381                                         null, exception);
382                         if (log.isDebugEnabled()) {
383                                 log.debug("Dumping generated SAML Error Response:" + System.getProperty("line.separator")
384                                                 + samlResponse.toString());
385                         }
386                         binding.respond(httpResponse, samlResponse, null);
387                         log.debug("Returning SAML Error Response.");
388                 } catch (SAMLException se) {
389                         try {
390                                 binding.respond(httpResponse, null, exception);
391                         } catch (SAMLException e) {
392                                 log.error("Caught exception while responding to requester: " + e.getMessage());
393                                 try {
394                                         httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
395                                 } catch (IOException ee) {
396                                         log.fatal("Could not construct a SAML error response: " + ee);
397                                         throw new ServletException("Identity Provider response failure.");
398                                 }
399                         }
400                         log.error("Identity Provider failed to make an error message: " + se);
401                 }
402         }
403
404         private static void displayBrowserError(HttpServletRequest req, HttpServletResponse res, Exception e)
405                         throws ServletException, IOException {
406
407                 req.setAttribute("errorText", e.toString());
408                 req.setAttribute("requestURL", req.getRequestURI().toString());
409                 RequestDispatcher rd = req.getRequestDispatcher("/IdPError.jsp");
410                 rd.forward(req, res);
411         }
412
413 }
414
415 class MetadataProviderFactory {
416
417         private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
418
419         public static Metadata loadProvider(Element e) throws MetadataException {
420
421                 String className = e.getAttribute("type");
422                 if (className == null || className.equals("")) {
423                         log.error("Metadata Provider requires specification of the attribute \"type\".");
424                         throw new MetadataException("Failed to initialize Metadata Provider.");
425                 } else {
426                         try {
427                                 Class[] params = {Class.forName("org.w3c.dom.Element"),};
428                                 return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
429                         } catch (Exception loaderException) {
430                                 log.error("Failed to load Metadata Provider implementation class: " + loaderException);
431                                 Throwable cause = loaderException.getCause();
432                                 while (cause != null) {
433                                         log.error("caused by: " + cause);
434                                         cause = cause.getCause();
435                                 }
436                                 throw new MetadataException("Failed to initialize Metadata Provider.");
437                         }
438                 }
439         }
440 }