Refine the structure for IdP+SP integration testing, adding Attribute Query test...
authorgilbert <gilbert@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 12 Sep 2005 18:42:25 +0000 (18:42 +0000)
committergilbert <gilbert@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Mon, 12 Sep 2005 18:42:25 +0000 (18:42 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@1853 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

tests/edu/internet2/middleware/shibboleth/serviceprovider/IdpTestContext.java [new file with mode: 0644]
tests/edu/internet2/middleware/shibboleth/serviceprovider/MockHTTPBindingProvider.java [new file with mode: 0644]
tests/edu/internet2/middleware/shibboleth/serviceprovider/SPTestCase.java
tests/edu/internet2/middleware/shibboleth/serviceprovider/SSOTest.java

diff --git a/tests/edu/internet2/middleware/shibboleth/serviceprovider/IdpTestContext.java b/tests/edu/internet2/middleware/shibboleth/serviceprovider/IdpTestContext.java
new file mode 100644 (file)
index 0000000..6f35f9c
--- /dev/null
@@ -0,0 +1,90 @@
+package edu.internet2.middleware.shibboleth.serviceprovider;
+
+import com.mockrunner.mock.web.MockFilterConfig;
+import com.mockrunner.mock.web.MockHttpServletRequest;
+import com.mockrunner.mock.web.MockHttpServletResponse;
+import com.mockrunner.mock.web.MockServletContext;
+import com.mockrunner.mock.web.WebMockObjectFactory;
+import com.mockrunner.servlet.ServletTestModule;
+
+import edu.internet2.middleware.shibboleth.idp.IdPResponder;
+
+/**
+ * Establish initialized IdP to respond to requests.
+ * 
+ * <p>The IdP is initialized when the IdpResponder servlet init() is 
+ * called. This establishes the static context of tables that 
+ * allow the IdP to issue a Subject and then respond when that
+ * Subject is returned in an Attribute Query.</p>
+ * 
+ * <p>This class creates the Mockrunner control blocks needed to 
+ * call the IdP and, by creating the IdP Servlet object, also 
+ * initializes an instance of the IdP. It depends on a configuration
+ * file located as a resource in the classpath, typically in the 
+ * /testresources directory of the project.</p>
+ */
+public class IdpTestContext {
+    
+    // Default to a configuration in /testresources
+    public static String defaultConfigFileName = "/basicIdpHome/idpconfig.xml";
+    
+    // The Factory creates the Request, Response, Session, etc.
+    public WebMockObjectFactory factory = new WebMockObjectFactory();
+    
+    // The TestModule runs the Servlet and Filter methods in the simulated container
+    public ServletTestModule testModule = new ServletTestModule(factory);
+    
+    // Now simulated Servlet API objects
+    MockServletContext servletContext= factory.getMockServletContext();
+    MockFilterConfig filterConfig= factory.getMockFilterConfig();
+    MockHttpServletResponse response = factory.getMockResponse();
+    MockHttpServletRequest request = factory.getMockRequest();
+    
+    
+    // The IdP Servlet that processes SSO, AA, and Artifact requests
+    // The object is created by Mockrunner
+    public IdPResponder idpServlet;
+    
+    /**
+     * Construct with the default configuration file
+     */
+    public IdpTestContext() {
+        this(defaultConfigFileName);
+    }
+    
+    /**
+     * Construct using a specified IdP configuration file.
+     */
+    public IdpTestContext(String configFileName) {
+        
+        // ServletContext
+        servletContext.setServletContextName("dummy IdP Context");
+        servletContext.setInitParameter("IdPConfigFile", configFileName);
+        
+        
+        // Create instance of Filter class, add to chain, call its init()
+        idpServlet = (IdPResponder) testModule.createServlet(IdPResponder.class);
+        
+        // Initialize the unchanging properties of the HttpServletRequest
+        request.setRemoteAddr("127.0.0.1");
+        request.setContextPath("/shibboleth-idp");
+        request.setProtocol("HTTP/1.1");
+        request.setScheme("https");
+        request.setServerName("idp.example.org");
+        request.setServerPort(443);
+        
+    }
+    
+    /**
+     * Set all fields of the HttpServletRequest that relate to a particular
+     * Servlet, extra path, and query.
+     * @param suffix Everything after the context (no leading "/")
+     */
+    void setRequestUrls(String suffix) {
+        request.setRequestURI("https://idp.example.org/shibboleth-idp/"+suffix);
+        request.setRequestURL("https://idp.example.org/shibboleth-idp/"+suffix);
+        request.setServletPath("/shibboleth.idp/"+suffix);
+        
+    }
+    
+}
diff --git a/tests/edu/internet2/middleware/shibboleth/serviceprovider/MockHTTPBindingProvider.java b/tests/edu/internet2/middleware/shibboleth/serviceprovider/MockHTTPBindingProvider.java
new file mode 100644 (file)
index 0000000..b1241ec
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ *  Copyright 2001-2005 Internet2
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package edu.internet2.middleware.shibboleth.serviceprovider;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.net.MalformedURLException;
+
+import org.apache.xml.security.c14n.CanonicalizationException;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.apache.xml.security.c14n.InvalidCanonicalizerException;
+import org.opensaml.BindingException;
+import org.opensaml.SAMLBinding;
+import org.opensaml.SAMLConfig;
+import org.opensaml.SAMLException;
+import org.opensaml.SAMLRequest;
+import org.opensaml.SAMLResponse;
+import org.opensaml.XML;
+import org.opensaml.provider.SOAPHTTPBindingProvider;
+import org.w3c.dom.Element;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+
+/**
+ *  This is a replacement for SOAPHTTPBindingProvider in OpenSAML. While that
+ *  module builds a URL and URLConnection to send a request to a Web Server
+ *  hosting the IdP, this code generates a direct call to the AA or Artifact
+ *  Resolver through the IdP Servlet.
+ *  
+ *  <p>Call setDefaultBindingProvider() to change the SAML configuration
+ *  to use this class to access the IdP.</p>
+ *  
+ *  <p>Sanity Check: In order for the AA or Artifact query to work, the IdP
+ *  has to have received an SSO, and we have to go back to the same Servlet
+ *  object with the same configuration and caches that vended the SSO. So
+ *  this code must depend on a prior initialization of both the IdP and the
+ *  testing environment generated by either a previous test or at least a 
+ *  setup phase.<p>
+ *  
+ */
+public class MockHTTPBindingProvider 
+    extends SOAPHTTPBindingProvider {
+    
+    
+       private static SAMLConfig config = SAMLConfig.instance();
+    
+    /**
+     * Static initialization routine that must be called so OpenSAML uses
+     * this class.
+     */
+    public static void setDefaultBindingProvider() {
+        config.setDefaultBindingProvider(SAMLBinding.SOAP,"edu.internet2.middleware.shibboleth.serviceprovider.MockHTTPBindingProvider" );
+    }
+    
+    public static IdpTestContext idp = null;
+    
+    /** OpenSAML will construct this object. */
+    public MockHTTPBindingProvider(String binding, Element e) throws SAMLException {
+        super(binding, e);
+    }
+
+    /**
+     * Based on the Http version of this code, this method replaces the URL and
+     * URLConnection with operations on the Mock HttpRequest.
+     */
+    public SAMLResponse send(String endpoint, SAMLRequest request, Object callCtx)
+        throws SAMLException
+    {
+        try {
+            Element envelope = sendRequest(request, callCtx);
+            
+            /*
+             * Prepare the Mockrunner blocks for the Query
+             */
+            idp.request.setLocalPort(8443);
+            idp.request.setRequestURI(endpoint);
+            idp.request.setRequestURL(endpoint);
+            if (endpoint.endsWith("/AA")) {
+                idp.request.setServletPath("/shibboleth.idp/AA");
+            } else {
+                idp.request.setServletPath("/shibboleth.idp/Artifact");
+            }
+
+            idp.request.setContentType("text/xml; charset=UTF-8");
+            idp.request.setHeader("SOAPAction","http://www.oasis-open.org/committees/security");
+//            Code in the overridden method is left as commentary            
+//            ((HttpURLConnection)conn).setRequestMethod("POST");
+//            ((HttpURLConnection)conn).setRequestProperty("Content-Type","text/xml; charset=UTF-8");
+//            ((HttpURLConnection)conn).setRequestProperty("SOAPAction","http://www.oasis-open.org/committees/security");
+        
+             
+            
+            Canonicalizer c = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
+//            conn.getOutputStream().write(c.canonicalizeSubtree(envelope));
+            byte[] bs = c.canonicalizeSubtree(envelope);
+            idp.request.setBodyContent(bs);
+
+            idp.testModule.doPost();
+            
+            String content_type=idp.response.getContentType();
+            
+            if (content_type == null || !content_type.startsWith("text/xml")) {
+                String outputStreamContent = idp.response.getOutputStreamContent();
+                StringReader outputreader = new StringReader(outputStreamContent);
+                BufferedReader reader=new BufferedReader(outputreader);
+//                BufferedReader reader=new BufferedReader(new InputStreamReader(conn.getInputStream()));
+                throw new BindingException(
+                       "MockHTTPBindingProvider.send() detected an invalid content type ("
+                               + (content_type!=null ? content_type : "none")
+                               + ") in the response.");
+            }
+            
+            envelope=XML.parserPool.parse(
+//                    new InputSource(conn.getInputStream()),
+                    new InputSource(new StringReader(idp.response.getOutputStreamContent())),
+                    (request.getMinorVersion()>0) ? XML.parserPool.getSchemaSAML11() : XML.parserPool.getSchemaSAML10()
+                    ).getDocumentElement();
+            
+            SAMLResponse ret = recvResponse(envelope, callCtx);
+           
+            if (!ret.getInResponseTo().equals(request.getId())) {
+               throw new BindingException("MockHTTPBindingProvider.send() unable to match SAML InResponseTo value to request");
+            }
+            return ret;
+        }
+        catch (MalformedURLException ex) {
+            throw new SAMLException("SAMLSOAPBinding.send() detected a malformed URL in the binding provided", ex);
+        }
+        catch (SAXException ex) {
+            throw new SAMLException("SAMLSOAPBinding.send() caught an XML exception while parsing the response", ex);
+        }
+        catch (InvalidCanonicalizerException ex) {
+            throw new SAMLException("SAMLSOAPBinding.send() caught a C14N exception while serializing the request", ex);
+        }
+        catch (CanonicalizationException ex) {
+            throw new SAMLException("SAMLSOAPBinding.send() caught a C14N exception while serializing the request", ex);
+        }
+        catch (java.io.IOException ex) {
+            throw new SAMLException("SAMLSOAPBinding.send() caught an I/O exception", ex);
+        }
+        finally {
+        }
+    }
+
+
+}
index 45ac68f..3923243 100644 (file)
@@ -15,6 +15,8 @@ import junit.framework.TestCase;
  */
 public class SPTestCase extends TestCase {
     
+    public static String defaultConfigFileName = "/basicSpHome/spconfig.xml";
+    
     public SPTestCase() {
         Logger root = Logger.getRootLogger();
         Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
@@ -32,10 +34,16 @@ public class SPTestCase extends TestCase {
      */
     public void initServiceProvider(String configFileName) 
         throws ShibbolethConfigurationException{
-            context.initialize();
-            ServiceProviderConfig config = new ServiceProviderConfig();
-            context.setServiceProviderConfig(config);
-            config.loadConfigObjects(configFileName);
+            
+        context.initialize();
+        ServiceProviderConfig config = new ServiceProviderConfig();
+        context.setServiceProviderConfig(config);
+        config.loadConfigObjects(configFileName);
+    }
+    
+    public void initServiceProvider() 
+        throws ShibbolethConfigurationException {
+        initServiceProvider(defaultConfigFileName);
     }
 
 }
index f48a226..ac82c75 100644 (file)
@@ -1,37 +1,21 @@
 package edu.internet2.middleware.shibboleth.serviceprovider;
 
-import java.io.File;
-
 import org.apache.commons.codec.binary.Base64;
 import org.opensaml.SAMLException;
 
-import com.mockrunner.mock.web.MockFilterConfig;
-import com.mockrunner.mock.web.MockHttpServletRequest;
-import com.mockrunner.mock.web.MockHttpServletResponse;
-import com.mockrunner.mock.web.MockServletContext;
-import com.mockrunner.mock.web.WebMockObjectFactory;
-import com.mockrunner.servlet.ServletTestModule;
-
-import edu.internet2.middleware.shibboleth.idp.IdPResponder;
 import edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler;
 import edu.internet2.middleware.shibboleth.resource.FilterSupport.NewSessionData;
 
+/**
+ * Test the IdP SSO function and the attribute fetch
+ * @author gilbert
+ *
+ */
 public class SSOTest extends SPTestCase {
-
-    // The Factory creates the Request, Response, Session, etc.
-    WebMockObjectFactory factory = new WebMockObjectFactory();
-    
-    // The TestModule runs the Servlet and Filter methods in the simulated container
-    ServletTestModule testModule = new ServletTestModule(factory);
-    
-    // Now simulated Servlet API objects
-    MockServletContext servletContext= factory.getMockServletContext();
-    MockFilterConfig filterConfig= factory.getMockFilterConfig();
-    MockHttpServletResponse response = factory.getMockResponse();
-    MockHttpServletRequest request = factory.getMockRequest();
     
-    // Servlet objects
-    private IdPResponder sso;
+    // The Mockrunner control blocks and the initialized IdP Servlet
+    IdpTestContext idp;
+
 
     // data returned from SSO
     private String bin64assertion;
@@ -42,66 +26,118 @@ public class SSOTest extends SPTestCase {
     protected void setUp() throws Exception {
         super.setUp();
         
-        // ServletContext (argument to Filters and Servlets)
-        servletContext.setServletContextName("dummy SSO Context");
-        servletContext.setInitParameter("IdPConfigFile", "file:/C:/usr/local/shibboleth-idp/etc/idp.xml");
-        
+        // Initialize OpenSAML
+        MockHTTPBindingProvider.setDefaultBindingProvider();
         
-        // Create instance of Filter class, add to chain, call its init()
-        sso = (IdPResponder) testModule.createServlet(IdPResponder.class);
+        // Initialize the IdP with the default configuration file.
+        idp=new IdpTestContext();
+        MockHTTPBindingProvider.idp=idp;
         
         // Initialize an SP Context and Confg
-        String configFileName = new File("data/spconfig.xml").toURI().toString();
-        initServiceProvider(configFileName); 
-        
-
-        request.setRemoteAddr("127.0.0.1");
-        request.setContextPath("/shibboleth-idp");
-        request.setProtocol("HTTP/1.1");
-        request.setScheme("https");
-        request.setServerName("idp.example.org");
-        request.setServerPort(443);
-    }
+        initServiceProvider(); 
+     }
     
-    void setRequestUrls(String suffix) {
-        request.setMethod("GET");
-        request.setRequestURI("https://idp.example.org/shibboleth-idp/"+suffix);
-        request.setRequestURL("https://idp.example.org/shibboleth-idp/"+suffix);
-        request.setServletPath("/shibboleth.idp/"+suffix);
+   
+    public void testAttributePush() throws SAMLException {
         
-    }
-    
-    public void testInitialGET() throws SAMLException {
+        // Set the URL suffix that triggers SSO processing
+        idp.setRequestUrls("SSO");
+        
+        // Add the WAYF/RM parameters
+        idp.testModule.addRequestParameter("target", "https://nonsense");
+        idp.testModule.addRequestParameter("shire","https://sp.example.org/Shibboleth.sso/SAML/POST");
+        idp.testModule.addRequestParameter("providerId", "https://sp.example.org/shibboleth");
         
-        setRequestUrls("SSO");
-        testModule.addRequestParameter("target", "https://nonsense");
-        testModule.addRequestParameter("shire","https://sp.example.org/Shibboleth.sso/SAML/POST");
-        testModule.addRequestParameter("providerId", "https://sp.example.org/shibboleth");
-        request.setRemoteUser("BozoTClown");
+        // Add a userid, as if provided by Basic Authentication or a Filter
+        idp.request.setRemoteUser("BozoTClown");
         
+        // Force Attribute Push
         ShibbolethV1SSOHandler.pushAttributeDefault=true;
         
-        testModule.doGet();
+        // Call the IdP 
+        idp.testModule.doGet();
         
-        bin64assertion = (String) request.getAttribute("assertion");
+        /*
+         * Sanity check: The IdP normally ends by transferring control to a
+         * JSP page that generates the FORM. However, we have not set up
+         * Mockrunner to perform the transfer, because the form would just
+         * create parsing work. Rather, the following code extracts the
+         * information from the request attributes that the JSP would have
+         * used as its source.
+         */
+        
+        bin64assertion = (String) idp.request.getAttribute("assertion");
         assertion = new String(Base64.decodeBase64(bin64assertion.getBytes()));
-        handlerURL = (String) request.getAttribute("shire");
-        targetURL = (String) request.getAttribute("target");
+        handlerURL = (String) idp.request.getAttribute("shire");
+        targetURL = (String) idp.request.getAttribute("target");
+        
+        
+        /*
+         * We could create Mockrunner control blocks to present this data
+         * to the AuthenticationConsumer Servlet, but this level of 
+         * intergration testing is supposed to check the processing of the
+         * SAML objects. All the real work is done in SessionManager, so 
+         * we might just as well go to it directly.
+         */
         
-        // There is no need to use the Servlet interface to consume it
         NewSessionData data = new NewSessionData();
         data.applicationId="default";
         data.handlerURL=handlerURL;
-        data.ipaddr=request.getRemoteAddr();
+        data.ipaddr=idp.request.getRemoteAddr();
         data.providerId="https://sp.example.org/shibboleth";
         data.SAMLResponse = bin64assertion;
         data.target=targetURL;
         String sessionId = AssertionConsumerServlet.createSessionFromData(data);
         
+        /*
+         * Within the prevous call, the SAML assertion was presented to OpenSAML
+         * for processing and the Attributes were stored.
+         */
+        
         // Now get what was created in case you want to test it.
         Session session = context.getSessionManager().findSession(sessionId, "default");
         
     }
 
+    public void testAttributeQuery() throws SAMLException {
+        
+        // Set the URL suffix that triggers SSO processing
+        idp.setRequestUrls("SSO");
+        
+        // Add the WAYF/RM parameters
+        idp.testModule.addRequestParameter("target", "https://nonsense");
+        idp.testModule.addRequestParameter("shire","https://sp.example.org/Shibboleth.sso/SAML/POST");
+        idp.testModule.addRequestParameter("providerId", "https://sp.example.org/shibboleth");
+        
+        // Add a userid, as if provided by Basic Authentication or a Filter
+        idp.request.setRemoteUser("BozoTClown");
+        
+        // Force Attribute Push
+        //ShibbolethV1SSOHandler.pushAttributeDefault=true;
+        
+        // Call the IdP 
+        idp.testModule.doGet();
+        
+        bin64assertion = (String) idp.request.getAttribute("assertion");
+        assertion = new String(Base64.decodeBase64(bin64assertion.getBytes()));
+        handlerURL = (String) idp.request.getAttribute("shire");
+        targetURL = (String) idp.request.getAttribute("target");
+        
+        
+        
+        NewSessionData data = new NewSessionData();
+        data.applicationId="default";
+        data.handlerURL=handlerURL;
+        data.ipaddr=idp.request.getRemoteAddr();
+        data.providerId="https://sp.example.org/shibboleth";
+        data.SAMLResponse = bin64assertion;
+        data.target=targetURL;
+        String sessionId = AssertionConsumerServlet.createSessionFromData(data);
+        
+        
+        // Now get what was created in case you want to test it.
+        Session session = context.getSessionManager().findSession(sessionId, "default");
+        
+    }
 
 }