2 * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.runner;
19 import javax.naming.directory.Attributes;
20 import javax.naming.directory.BasicAttributes;
22 import org.apache.log4j.ConsoleAppender;
23 import org.apache.log4j.Layout;
24 import org.apache.log4j.Level;
25 import org.apache.log4j.Logger;
26 import org.apache.log4j.PatternLayout;
27 import org.opensaml.SAMLBinding;
28 import org.opensaml.SAMLConfig;
30 import com.mockrunner.mock.web.MockFilterConfig;
31 import com.mockrunner.mock.web.MockHttpServletRequest;
32 import com.mockrunner.mock.web.MockHttpServletResponse;
33 import com.mockrunner.mock.web.MockServletContext;
34 import com.mockrunner.mock.web.WebMockObjectFactory;
35 import com.mockrunner.servlet.ServletTestModule;
37 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
38 import edu.internet2.middleware.shibboleth.idp.IdPResponder;
39 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
40 import edu.internet2.middleware.shibboleth.resource.FilterSupport;
41 import edu.internet2.middleware.shibboleth.resource.FilterSupport.RMAppInfo;
42 import edu.internet2.middleware.shibboleth.serviceprovider.FilterSupportImpl;
43 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig;
44 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderContext;
47 * Initialize on request the IdP, SP, and Filter on behalf of a JUnit test.
49 * <p>An instance of this class is created by a JUnit test class, which
50 * uses it to initialize and create MockRunner objects for testing the
51 * Shibboleth components. This keeps the tests themselves simple.</p>
53 * <p>Look at *.integration.IntegrationTest for an example of use.</p>
55 * @author Howard Gilbert.
57 public class ShibbolethRunner {
59 private static SAMLConfig samlConfig;
62 * Initialization logic goes here.
63 * <p>Reqires that Log4J already be configured.</p>
65 public ShibbolethRunner() {
67 // Configure SAML to use the MockRunner interface to callback
68 // from the SP to the IdP instead of trying to use real HTTP.
69 samlConfig = SAMLConfig.instance();
70 samlConfig.setDefaultBindingProvider(SAMLBinding.SOAP,"edu.internet2.middleware.shibboleth.runner.MockHTTPBindingProvider" );
78 * For automated test cases that normally just work, you
79 * probably want to leave the logging level to ERROR. However,
80 * if you are running a custom test case to discover the source
81 * of a problem, or when building a new test case, then you
82 * may want to set the logging level to DEBUG.
84 * You can change the loglevel variable from the test case
85 * code before calling setupLogging().
87 public static Level loglevel = Level.INFO;
89 private static Logger clientLogger = Logger.getLogger("edu.internet2.middleware");
90 private static Logger initLogger = Logger.getLogger("shibboleth.init");
91 private static Logger samlLogger = Logger.getLogger("org.opensaml");
92 private static boolean manageLogs = false;
95 * You will almost always call setupLogging first, but it
96 * it not automatic in case you have exotic logging
99 * <p>Restriction: avoid any static initialization that generates
100 * log messages because this method can only be called after
101 * static initialation.</p>
103 public static void setupLogging() {
105 Logger root = Logger.getRootLogger();
106 Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
107 ConsoleAppender consoleAppender= new ConsoleAppender(initLayout,ConsoleAppender.SYSTEM_OUT);
108 root.addAppender(consoleAppender);
109 root.setLevel(Level.ERROR);
110 clientLogger.setLevel(loglevel);
111 initLogger.setLevel(loglevel);
112 samlLogger.setLevel(loglevel);
116 * Sometimes (as in IdP initialization) the logging levels
117 * get reset to some unintended level. This resets them
118 * to whatever we want for testing.
120 public static void resetLoggingLevels() {
121 if (!manageLogs) return; // If setupLogging was never called.
122 clientLogger.setLevel(loglevel);
123 initLogger.setLevel(loglevel);
124 samlLogger.setLevel(loglevel);
131 * The SP is represented by an SPContext object and the objects
132 * SessionManager, SPConfig, etc. chained off it. The context
133 * is initialized and then the main configuration file is read
134 * in to create the Config object.
136 * The testing environment doesn't bother with MockRunner objects.
137 * The Servlet interface to the SP is a thin layer that only
138 * translates between HTTP/HTML (the Request object) and method
139 * calls. So once initialized, it is just as easy to call the
140 * SessionManager and FilterSupportImpl directly.
143 private String spConfigFileName = "/basicSpHome/spconfig.xml";
145 * If you are goint to change the SP Config File
146 * do it before calling initServiceProvider.
148 * @param spConfigFileName
150 public void setSpConfigFileName(String spConfigFileName) {
151 this.spConfigFileName = spConfigFileName;
154 private static boolean SPinitialized = false; // don't do it twice
157 * Load an SP configuration file.
158 * @throws ShibbolethConfigurationException if bad config file
160 public void initializeSP()
161 throws ShibbolethConfigurationException{
162 if (SPinitialized) return;
165 ServiceProviderContext context = ServiceProviderContext.getInstance();
166 context.initialize();
168 ServiceProviderConfig config = new ServiceProviderConfig();
169 context.setServiceProviderConfig(config);
170 config.loadConfigObjects(spConfigFileName);
176 * Setup the IdP interface object
178 * The IdP keeps its "context" of cached data and configured
179 * objects internal rather than exposing it as a public object.
180 * The IdpTestContext object does the initialization and creates
181 * a set of MockRunner object through which the SSO, AA, and
182 * Artifact requests can be generated.
184 * The real IdP objects configure themselves when the Servlet
185 * init() method is called. The Configuration file name coded
186 * here is passed to the Servlet as a simulated context parameter.
188 * To direct the AA and Artifact queries back to the Idp object,
189 * a call to SAML sets up the MockHTTPBindingProvider to replace
190 * the normal HTTPBindingProvider. Thus instead of creating URL
191 * and sockets to talk to the IdP, a simulated Request object is
192 * configured and the IdP is called through MockRunner.
194 public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
195 public void setIdpConfigFileName(String idpConfigFileName) {
196 this.idpConfigFileName = idpConfigFileName;
199 public static IdpTestContext idp = null;
202 * Initializes the IdP if necessary, then returns a
203 * pointer to the MockRunner interface object
204 * @return IdpTestContext with Mockrunner objects
206 public IdpTestContext getIdp() {
208 idp = new IdpTestContext();
215 * Establish initialized IdP and a set of MockRunner objects to
216 * process SSO, AA, and Artifact requests.
218 * <p>The IdP is initialized when the IdpResponder servlet init() is
219 * called. This establishes the static context of tables that
220 * allow the IdP to issue a Subject and then respond when that
221 * Subject is returned in an Attribute Query.</p>
223 * <p>This class creates the Mockrunner control blocks needed to
224 * call the IdP and, by creating the IdP Servlet object, also
225 * initializes an instance of the IdP. It depends on a configuration
226 * file located as a resource in the classpath, typically in the
227 * /testresources directory of the project.</p>
229 public class IdpTestContext {
232 // The Factory creates the Request, Response, Session, etc.
233 public WebMockObjectFactory factory = new WebMockObjectFactory();
235 // The TestModule runs the Servlet and Filter methods in the simulated container
236 public ServletTestModule testModule = new ServletTestModule(factory);
238 // Now simulated Servlet API objects
239 public MockServletContext servletContext= factory.getMockServletContext();
240 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
241 public MockHttpServletResponse response = factory.getMockResponse();
242 public MockHttpServletRequest request = factory.getMockRequest();
245 // The IdP Servlet that processes SSO, AA, and Artifact requests
246 // The object is created by Mockrunner
247 public IdPResponder idpServlet;
250 * Construct with the default configuration file
252 public IdpTestContext() {
253 this(idpConfigFileName);
257 * Construct using a specified IdP configuration file.
259 public IdpTestContext(String configFileName) {
262 servletContext.setServletContextName("dummy IdP Context");
263 servletContext.setInitParameter("IdPConfigFile", configFileName);
266 // Create instance of Filter class, add to chain, call its init()
267 // NOTE: The IdP reads its configuration file and initializes
268 // itself within this call.
269 idpServlet = (IdPResponder) testModule.createServlet(IdPResponder.class);
271 // Unchanging properties of the HttpServletRequest
272 request.setRemoteAddr("127.0.0.1");
273 request.setContextPath("/shibboleth-idp");
274 request.setProtocol("HTTP/1.1");
275 request.setScheme("https");
276 request.setServerName("idp.example.org");
277 request.setServerPort(443);
282 * Set the fields of the request that depend on a suffix,
283 * normally SSO, AA, or Artifact
285 public void setRequestUrls(String suffix) {
286 request.setRequestURI("https://idp.example.org/shibboleth-idp/"+suffix);
287 request.setRequestURL("https://idp.example.org/shibboleth-idp/"+suffix);
288 request.setServletPath("/shibboleth.idp/"+suffix);
299 * Here we keep a static reference to a Collection of Attributes.
301 * The Test can clear the collection and add attributes. When
302 * the IdP needs attributes, it treats this collection as the
303 * starting point and processes them through ARP. When then get
304 * to the SP they go through AAP. So you can test the Attribute
305 * processing logic in both components by creating Attributes
306 * with names and values that are accepted or rejected.
309 public static BasicAttributes attributes = new BasicAttributes();
312 * The Test should obtain a reference to the Attribute collection and add
313 * such attributes as it wants the IdP to return for a Principal.
314 * @return Attributes collection
316 public Attributes getAttributesCollection() {
323 * The Filter depends on a Servlet environment simulated by MockRunner.
324 * We give it its own set of MockRunner blocks because in real life
325 * it runs in a separate context from the SP or IdP.
327 * The Filter depends on the SP and, once initialized, has a reference
328 * to FilterSupportImpl and through it the SP configuration and Sessions.
330 private AuthenticationFilterContext filter;
331 public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
333 filter=new AuthenticationFilterContext();
338 * Create the MockRunning interface for running the ServletFilter.
340 * <p>The SP must be initialized to provide parameters.</p>
343 public class AuthenticationFilterContext {
345 // The Factory creates the Request, Response, Session, etc.
346 public WebMockObjectFactory factory = new WebMockObjectFactory();
348 // The TestModule runs the Servlet and Filter methods in the simulated container
349 public ServletTestModule testModule = new ServletTestModule(factory);
351 // Now simulated Servlet API objects
352 public MockServletContext servletContext= new MockServletContext();
353 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
354 public MockHttpServletResponse response = factory.getMockResponse();
355 public MockHttpServletRequest request = factory.getMockRequest();
358 * The Missing Manual: There are three types of init-params in
359 * the web.xml. One applies to the Context as a whole. The other
360 * two are nested inside a <servlet> or <filter> and provide
361 * parameters specific to that particular object. If you do
362 * a factory.getMockServletContext() you get an object that corresponds
363 * to the web.xml configuration itself. However, rather than adding
364 * init-param collections to the MockServletConfig and MockFilterConfig,
365 * Mockrunner seems to chain a user-created MockServletContext object
366 * to them and use its init-params as the parameters fed back to the
367 * Filter or Servlet object. So when you see "new MockServletContext()"
368 * there is a pretty good reason to expect this will not be used as a
369 * real ServletContext but rather as a secondary control block to a
370 * MockFilterConfig or MockServletConfig.
374 private AuthenticationFilter filter;
376 // SP configuration objects
377 private FilterSupport service;
378 private RMAppInfo rmAppInfo;
380 public AuthenticationFilterContext() {
382 // ServletContext (argument to Filters and Servlets)
383 servletContext.setServletContextName("dummy Servlet Context");
384 servletContext.setInitParameter("requireId", ".+/test.+");
386 // The FilterConfig (argument to Filter init)
387 filterConfig.setupServletContext(servletContext);
388 filterConfig.setFilterName("Test Filter under JUnit");
392 * Call after any changes to Context init-param values to
393 * initialize the filter object and connect to the SP.
395 * @throws ShibbolethConfigurationException from SP init.
397 public void setUp() throws ShibbolethConfigurationException {
399 // Create instance of Filter class, add to chain, call its init()
400 filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
402 // Note: if the SP is already initialized, this noops.
405 // Plug an instance of FilterSupportImpl into the Filter
406 service = new FilterSupportImpl();
407 AuthenticationFilter.setFilterSupport(service);
409 // Get our own copy of SP Config info for Assert statements
410 rmAppInfo = service.getRMAppInfo("default");
412 request.setRemoteAddr("127.0.0.1");
413 request.setContextPath("/secure");
414 request.setProtocol("HTTP/1.1");
415 request.setScheme("https");
416 request.setServerName("sp.example.org");
417 request.setServerPort(9443);
420 public void setRequestUrls(String suffix) {
421 request.setMethod("GET");
422 request.setRequestURI("http://sp.example.org:9443/secure/"+suffix);
423 request.setRequestURL("http://sp.example.org:9443/secure/"+suffix);
424 request.setServletPath("/secure/"+suffix);