Fixed an IdP configuration schema bug.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / IdPResponder.java
index 5951965..b34edbe 100644 (file)
@@ -26,8 +26,9 @@
 package edu.internet2.middleware.shibboleth.idp;
 
 import java.io.IOException;
+import java.net.MalformedURLException;
 import java.net.URI;
-import java.net.URISyntaxException;
+import java.net.URL;
 import java.util.HashMap;
 import java.util.Random;
 
@@ -49,11 +50,13 @@ import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 import org.w3c.dom.NodeList;
 
-import sun.misc.BASE64Decoder;
 import edu.internet2.middleware.shibboleth.aa.arp.ArpEngine;
 import edu.internet2.middleware.shibboleth.aa.arp.ArpException;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolver;
 import edu.internet2.middleware.shibboleth.aa.attrresolv.AttributeResolverException;
+import edu.internet2.middleware.shibboleth.artifact.ArtifactMapper;
+import edu.internet2.middleware.shibboleth.artifact.ArtifactMapperFactory;
+import edu.internet2.middleware.shibboleth.artifact.provider.MemoryArtifactMapper;
 import edu.internet2.middleware.shibboleth.common.Credentials;
 import edu.internet2.middleware.shibboleth.common.NameIdentifierMapping;
 import edu.internet2.middleware.shibboleth.common.NameIdentifierMappingException;
@@ -62,7 +65,6 @@ import edu.internet2.middleware.shibboleth.common.OriginConfig;
 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapper;
 import edu.internet2.middleware.shibboleth.common.ServiceProviderMapperException;
 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
-import edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler;
 import edu.internet2.middleware.shibboleth.metadata.Metadata;
 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
 
@@ -79,7 +81,7 @@ public class IdPResponder extends HttpServlet {
        private static Logger log = Logger.getLogger(IdPResponder.class.getName());
        private static Random idgen = new Random();
        private SAMLBinding binding;
-       private Semaphore throttle;
+
        private IdPConfig configuration;
        private HashMap protocolHandlers = new HashMap();
        private IdPProtocolSupport protocolSupport;
@@ -101,9 +103,6 @@ public class IdPResponder extends HttpServlet {
                        // Load global configuration properties
                        configuration = new IdPConfig(originConfig.getDocumentElement());
 
-                       // Load a semaphore that throttles how many requests the IdP will handle at once
-                       throttle = new Semaphore(configuration.getMaxThreads());
-
                        // Load name mappings
                        NameMapper nameMapper = new NameMapper();
                        NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
@@ -144,11 +143,11 @@ public class IdPResponder extends HttpServlet {
                        try {
                                resolver = new AttributeResolver(configuration);
 
-                               itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
-                                               IdPConfig.originConfigNamespace, "ReleasePolicyEngine");
+                               itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
+                                               "ReleasePolicyEngine");
 
                                if (itemElements.getLength() > 1) {
-                                       log.warn("Encountered multiple <ReleasePolicyEngine> configuration elements.  Using first...");
+                                       log.warn("Encountered multiple <ReleasePolicyEngine/> configuration elements.  Using first...");
                                }
                                if (itemElements.getLength() < 1) {
                                        arpEngine = new ArpEngine();
@@ -166,25 +165,60 @@ public class IdPResponder extends HttpServlet {
                                throw new ShibbolethConfigurationException("Could not load Attribute Resolver.");
                        }
 
+                       // Load artifact mapping implementation
+                       ArtifactMapper artifactMapper = null;
+                       itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
+                                       "ArtifactMapper");
+                       if (itemElements.getLength() > 1) {
+                               log.warn("Encountered multiple <ArtifactMapper/> configuration elements.  Using first...");
+                       }
+                       if (itemElements.getLength() > 0) {
+                               artifactMapper = ArtifactMapperFactory.getInstance((Element) itemElements.item(0));
+                       } else {
+                               log.debug("No Artifact Mapper configuration found.  Defaulting to Memory-based implementation.");
+                               artifactMapper = new MemoryArtifactMapper();
+                       }
+
                        // Load protocol handlers and support library
                        protocolSupport = new IdPProtocolSupport(configuration, transactionLog, nameMapper, spMapper, arpEngine,
-                                       resolver);
-                       log.debug("Starting with Shibboleth v1 protocol handling enabled.");
-                       try {
-                               protocolHandlers.put(new URI("https://wraith.memphis.edu/shibboleth/HS"), new ShibbolethV1SSOHandler());
-                       } catch (URISyntaxException e1) {
-                               // TODO get rid of this
+                                       resolver, artifactMapper);
+                       itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
+                                       "ProtocolHandler");
+
+                       // Default if no handlers are specified
+                       if (itemElements.getLength() < 1) {
+                               // TODO work out defaulting
+
+                               // If handlers were specified, load them and register them against their locations
+                       } else {
+                               EACHHANDLER : for (int i = 0; i < itemElements.getLength(); i++) {
+                                       IdPProtocolHandler handler = ProtocolHandlerFactory.getInstance((Element) itemElements.item(i));
+                                       URI[] locations = handler.getLocations();
+                                       EACHLOCATION : for (int j = 0; j < locations.length; j++) {
+                                               if (protocolHandlers.containsKey(locations[j].toString())) {
+                                                       log.error("Multiple protocol handlers are registered to listen at ("
+                                                                       + locations[j]
+                                                                       + ").  Ignoring all except ("
+                                                                       + ((IdPProtocolHandler) protocolHandlers.get(locations[j].toString()))
+                                                                                       .getHandlerName() + ").");
+                                                       continue EACHLOCATION;
+                                               }
+                                               log.info("Registering handler (" + handler.getHandlerName() + ") to listen at (" + locations[j]
+                                                               + ").");
+                                               protocolHandlers.put(locations[j].toString(), handler);
+                                       }
+                               }
                        }
 
                        // Load metadata
-                       itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.originConfigNamespace,
-                                       "FederationProvider");
+                       itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(IdPConfig.configNameSpace,
+                                       "MetadataProvider");
                        for (int i = 0; i < itemElements.getLength(); i++) {
-                               protocolSupport.addFederationProvider((Element) itemElements.item(i));
+                               protocolSupport.addMetadataProvider((Element) itemElements.item(i));
                        }
                        if (protocolSupport.providerCount() < 1) {
-                               log.error("No Federation Provider metadata loaded.");
-                               throw new ShibbolethConfigurationException("Could not load federation metadata.");
+                               log.error("No Metadata Provider metadata loaded.");
+                               throw new ShibbolethConfigurationException("Could not load SAML metadata.");
                        }
 
                        log.info("Identity Provider initialization complete.");
@@ -206,34 +240,39 @@ public class IdPResponder extends HttpServlet {
 
                MDC.put("serviceId", "[IdP] " + idgen.nextInt());
                MDC.put("remoteAddr", request.getRemoteAddr());
-               log.debug("Recieved a request via GET.");
+               log.debug("Recieved a request via GET for location (" + request.getRequestURL() + ").");
 
                try {
-                       // TODO this throttle should probably just wrap signing operations...
-                       throttle.enter();
-
-                       // Determine which protocol we are responding to (at this point, Shibv1 vs. EAuth)
-                       IdPProtocolHandler activeHandler = null;
-                       activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURI());
+                       // Determine which protocol we are responding to (at this point normally Shibv1 vs. EAuth)
+                       String requestURL = request.getRequestURL().toString();
+                       IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(requestURL);
+                       if (activeHandler == null) {
+                               log.debug("No protocol handler registered for location (" + request.getRequestURL()
+                                               + ").  Attempting to match against relative path.");
+                               try {
+                                       activeHandler = (IdPProtocolHandler) protocolHandlers.get(new URL(requestURL).getPath());
+                               } catch (MalformedURLException e) {
+                                       // squelch, we will just fail to find a handler
+                               }
+                       }
 
-                       if (activeHandler == null) { throw new InvalidClientDataException(
-                                       "The request did not contain sufficient parameter data to determine the protocol."); }
+                       if (activeHandler == null) {
+                               log.error("No protocol handler registered for location (" + request.getRequestURL() + ").");
+                               throw new SAMLException("Request submitted to an invalid location.");
+                       }
 
                        // Pass request to the appropriate handler
                        log.info("Processing " + activeHandler.getHandlerName() + " request.");
-                       activeHandler.processRequest(request, response, null, protocolSupport);
+                       if (activeHandler.processRequest(request, response, null, protocolSupport) != null) {
+                               // This shouldn't happen unless somebody configures a protocol handler incorrectly
+                               log.error("Protocol Handler returned a SAML Response, but there is no binding to handle it.");
+                               throw new SAMLException(SAMLException.RESPONDER, "General error processing request.");
+                       }
 
-                       // TODO hmmm... there is kind of an assupmtion here that we don't get a response... how to handle?
-               } catch (InvalidClientDataException ex) {
-                       log.error(ex);
-                       displayBrowserError(request, response, ex);
-                       return;
                } catch (SAMLException ex) {
                        log.error(ex);
                        displayBrowserError(request, response, ex);
                        return;
-               } finally {
-                       throttle.exit();
                }
        }
 
@@ -245,7 +284,7 @@ public class IdPResponder extends HttpServlet {
 
                MDC.put("serviceId", "[IdP] " + idgen.nextInt());
                MDC.put("remoteAddr", request.getRemoteAddr());
-               log.debug("Recieved a request via POST.");
+               log.debug("Recieved a request via POST for location (" + request.getRequestURL() + ").");
 
                // Parse SOAP request and marshall SAML request object
                SAMLRequest samlRequest = null;
@@ -260,69 +299,44 @@ public class IdPResponder extends HttpServlet {
                        // If we have DEBUG logging turned on, dump out the request to the log
                        // This takes some processing, so only do it if we need to
                        if (log.isDebugEnabled()) {
-                               try {
-                                       log.debug("Dumping generated SAML Request:"
-                                                       + System.getProperty("line.separator")
-                                                       + new String(new BASE64Decoder().decodeBuffer(new String(samlRequest.toBase64(), "ASCII")),
-                                                                       "UTF8"));
-                               } catch (SAMLException e) {
-                                       log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
-                               } catch (IOException e) {
-                                       log.error("Encountered an error while decoding SAMLRequest for logging purposes.");
-                               }
+                               log.debug("Dumping generated SAML Request:" + System.getProperty("line.separator")
+                                               + samlRequest.toString());
                        }
 
                        // Determine which protocol handler is active for this endpoint
-                       IdPProtocolHandler activeHandler = null;
-                       activeHandler = (IdPProtocolHandler) protocolHandlers.get(request.getRequestURI());
+                       String requestURL = request.getRequestURL().toString();
+                       IdPProtocolHandler activeHandler = (IdPProtocolHandler) protocolHandlers.get(requestURL);
                        if (activeHandler == null) {
-                               log.fatal("No protocol handler registered for endpoint (" + request.getRequestURI() + ").");
-                               throw new SAMLException("Invalid request endpoint.");
+                               log.debug("No protocol handler registered for location (" + request.getRequestURL()
+                                               + ").  Attempting to match against relative path.");
+                               try {
+                                       activeHandler = (IdPProtocolHandler) protocolHandlers.get(new URL(requestURL).getPath());
+                               } catch (MalformedURLException e) {
+                                       // squelch, we will just fail to find a handler
+                               }
                        }
 
                        // Pass request to the appropriate handler and respond
                        log.info("Processing " + activeHandler.getHandlerName() + " request.");
-                       try {
-                               SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest,
-                                               protocolSupport);
-                               binding.respond(response, samlResponse, null);
 
-                       } catch (InvalidClientDataException e1) {
-                               // TODO throw SAML Exception here
-                               e1.printStackTrace();
-                       }
+                       SAMLResponse samlResponse = activeHandler.processRequest(request, response, samlRequest, protocolSupport);
+                       binding.respond(response, samlResponse, null);
 
                } catch (SAMLException e) {
-                       log.error("Error while processing request: " + e);
-                       try {
-                               // TODO pass thru SAML Exception
-                               sendSAMLFailureResponse(response, samlRequest, new SAMLException(SAMLException.RESPONDER,
-                                               "General error processing request."));
-                               return;
-                       } catch (Exception ee) {
-                               log.fatal("Could not construct a SAML error response: " + ee);
-                               throw new ServletException("Identity Provider response failure.");
-                       }
+                       sendFailureToSAMLBinding(response, samlRequest, e);
                }
-
        }
 
-       private void sendSAMLFailureResponse(HttpServletResponse httpResponse, SAMLRequest samlRequest,
-                       SAMLException exception) throws IOException {
+       private void sendFailureToSAMLBinding(HttpServletResponse httpResponse, SAMLRequest samlRequest,
+                       SAMLException exception) throws ServletException {
 
+               log.error("Error while processing request: " + exception);
                try {
                        SAMLResponse samlResponse = new SAMLResponse((samlRequest != null) ? samlRequest.getId() : null, null,
                                        null, exception);
                        if (log.isDebugEnabled()) {
-                               try {
-                                       log.debug("Dumping generated SAML Error Response:"
-                                                       + System.getProperty("line.separator")
-                                                       + new String(
-                                                                       new BASE64Decoder().decodeBuffer(new String(samlResponse.toBase64(), "ASCII")),
-                                                                       "UTF8"));
-                               } catch (IOException e) {
-                                       log.error("Encountered an error while decoding SAMLReponse for logging purposes.");
-                               }
+                               log.debug("Dumping generated SAML Error Response:" + System.getProperty("line.separator")
+                                               + samlResponse.toString());
                        }
                        binding.respond(httpResponse, samlResponse, null);
                        log.debug("Returning SAML Error Response.");
@@ -331,7 +345,12 @@ public class IdPResponder extends HttpServlet {
                                binding.respond(httpResponse, null, exception);
                        } catch (SAMLException e) {
                                log.error("Caught exception while responding to requester: " + e.getMessage());
-                               httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
+                               try {
+                                       httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while responding.");
+                               } catch (IOException ee) {
+                                       log.fatal("Could not construct a SAML error response: " + ee);
+                                       throw new ServletException("Identity Provider response failure.");
+                               }
                        }
                        log.error("Identity Provider failed to make an error message: " + se);
                }
@@ -346,58 +365,30 @@ public class IdPResponder extends HttpServlet {
                rd.forward(req, res);
        }
 
-       private class Semaphore {
-
-               private int value;
-
-               public Semaphore(int value) {
-
-                       this.value = value;
-               }
-
-               public synchronized void enter() {
-
-                       --value;
-                       if (value < 0) {
-                               try {
-                                       wait();
-                               } catch (InterruptedException e) {
-                                       // squelch and continue
-                               }
-                       }
-               }
-
-               public synchronized void exit() {
-
-                       ++value;
-                       notify();
-               }
-       }
-
 }
 
-class FederationProviderFactory {
+class MetadataProviderFactory {
 
-       private static Logger log = Logger.getLogger(FederationProviderFactory.class.getName());
+       private static Logger log = Logger.getLogger(MetadataProviderFactory.class.getName());
 
        public static Metadata loadProvider(Element e) throws MetadataException {
 
                String className = e.getAttribute("type");
                if (className == null || className.equals("")) {
-                       log.error("Federation Provider requires specification of the attribute \"type\".");
-                       throw new MetadataException("Failed to initialize Federation Provider.");
+                       log.error("Metadata Provider requires specification of the attribute \"type\".");
+                       throw new MetadataException("Failed to initialize Metadata Provider.");
                } else {
                        try {
                                Class[] params = {Class.forName("org.w3c.dom.Element"),};
                                return (Metadata) Class.forName(className).getConstructor(params).newInstance(new Object[]{e});
                        } catch (Exception loaderException) {
-                               log.error("Failed to load Federation Provider implementation class: " + loaderException);
+                               log.error("Failed to load Metadata Provider implementation class: " + loaderException);
                                Throwable cause = loaderException.getCause();
                                while (cause != null) {
                                        log.error("caused by: " + cause);
                                        cause = cause.getCause();
                                }
-                               throw new MetadataException("Failed to initialize Federation Provider.");
+                               throw new MetadataException("Failed to initialize Metadata Provider.");
                        }
                }
        }