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