--- /dev/null
+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);
+
+ }
+
+}
--- /dev/null
+/*
+ * 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 {
+ }
+ }
+
+
+}
*/
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");
*/
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);
}
}
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;
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");
+
+ }
}