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 {
60 public static int SERVER_PORT = 443;
61 public static String SERVER_NAME = "idp.example.org";
62 public static String SCHEME = "https";
63 public static String PROTOCOL = "HTTP/1.1";
64 public static String CONTEXT_PATH = "/shibboleth-idp";
65 public static String REMOTE_ADDR = "127.0.0.1";
66 public static String RESOURCE_CONTEXT_PATH = "/secure";
67 public static int RESOURCE_SERVER_PORT = 9443;
68 public static String CONTEXT_URL = SCHEME+"://"+SERVER_NAME+CONTEXT_PATH+"/";
69 public static String RESOURCE_CONTEXT_URL = SCHEME+"://"+SERVER_NAME+":"+RESOURCE_SERVER_PORT+RESOURCE_CONTEXT_PATH+"/";
72 private static SAMLConfig samlConfig;
77 * Initialization logic goes here.
78 * <p>Reqires that Log4J already be configured.</p>
80 public ShibbolethRunner() {
82 // Configure SAML to use the MockRunner interface to callback
83 // from the SP to the IdP instead of trying to use real HTTP.
84 samlConfig = SAMLConfig.instance();
85 samlConfig.setDefaultBindingProvider(SAMLBinding.SOAP,"edu.internet2.middleware.shibboleth.runner.MockHTTPBindingProvider" );
93 * For automated test cases that normally just work, you
94 * probably want to leave the logging level to ERROR. However,
95 * if you are running a custom test case to discover the source
96 * of a problem, or when building a new test case, then you
97 * may want to set the logging level to DEBUG.
99 * You can change the loglevel variable from the test case
100 * code before calling setupLogging().
102 public static Level loglevel = Level.INFO;
104 private static Logger clientLogger = Logger.getLogger("edu.internet2.middleware.shibboleth");
105 private static Logger initLogger = Logger.getLogger("shibboleth.init");
106 private static Logger samlLogger = Logger.getLogger("org.opensaml");
107 private static boolean manageLogs = false;
110 * You will almost always call setupLogging first, but it
111 * it not automatic in case you have exotic logging
114 * <p>Restriction: avoid any static initialization that generates
115 * log messages because this method can only be called after
116 * static initialation.</p>
118 public static void setupLogging() {
120 Logger root = Logger.getRootLogger();
121 Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
122 ConsoleAppender consoleAppender= new ConsoleAppender(initLayout,ConsoleAppender.SYSTEM_OUT);
123 root.removeAllAppenders();
124 root.addAppender(consoleAppender);
125 root.setLevel(Level.ERROR);
126 clientLogger.removeAllAppenders();
127 clientLogger.setLevel(loglevel);
128 initLogger.removeAllAppenders();
129 initLogger.setLevel(loglevel);
130 samlLogger.removeAllAppenders();
131 samlLogger.setLevel(loglevel);
135 * Sometimes (as in IdP initialization) the logging levels
136 * get reset to some unintended level. This resets them
137 * to whatever we want for testing.
139 public static void resetLoggingLevels() {
140 if (!manageLogs) return; // If setupLogging was never called.
141 clientLogger.removeAllAppenders();
142 clientLogger.setLevel(loglevel);
143 initLogger.removeAllAppenders();
144 initLogger.setLevel(loglevel);
145 samlLogger.removeAllAppenders();
146 samlLogger.setLevel(loglevel);
153 * The SP is represented by an SPContext object and the objects
154 * SessionManager, SPConfig, etc. chained off it. The context
155 * is initialized and then the main configuration file is read
156 * in to create the Config object.
158 * The testing environment doesn't bother with MockRunner objects.
159 * The Servlet interface to the SP is a thin layer that only
160 * translates between HTTP/HTML (the Request object) and method
161 * calls. So once initialized, it is just as easy to call the
162 * SessionManager and FilterSupportImpl directly.
165 private String spConfigFileName = "/basicSpHome/spconfig.xml";
167 * If you are goint to change the SP Config File
168 * do it before calling initServiceProvider.
170 * @param spConfigFileName
172 public void setSpConfigFileName(String spConfigFileName) {
173 this.spConfigFileName = spConfigFileName;
176 private static boolean SPinitialized = false; // don't do it twice
179 * Load an SP configuration file.
180 * @throws ShibbolethConfigurationException if bad config file
182 public void initializeSP()
183 throws ShibbolethConfigurationException{
184 if (SPinitialized) return;
187 ServiceProviderContext context = ServiceProviderContext.getInstance();
188 context.initialize();
190 ServiceProviderConfig config = new ServiceProviderConfig();
191 context.setServiceProviderConfig(config);
192 config.loadConfigObjects(spConfigFileName);
198 * Setup the IdP interface object
200 * The IdP keeps its "context" of cached data and configured
201 * objects internal rather than exposing it as a public object.
202 * The IdpTestContext object does the initialization and creates
203 * a set of MockRunner object through which the SSO, AA, and
204 * Artifact requests can be generated.
206 * The real IdP objects configure themselves when the Servlet
207 * init() method is called. The Configuration file name coded
208 * here is passed to the Servlet as a simulated context parameter.
210 * To direct the AA and Artifact queries back to the Idp object,
211 * a call to SAML sets up the MockHTTPBindingProvider to replace
212 * the normal HTTPBindingProvider. Thus instead of creating URL
213 * and sockets to talk to the IdP, a simulated Request object is
214 * configured and the IdP is called through MockRunner.
216 public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
217 public void setIdpConfigFileName(String idpConfigFileName) {
218 this.idpConfigFileName = idpConfigFileName;
221 public static IdpTestContext idp = null;
224 * Initializes the IdP if necessary, then returns a
225 * pointer to the MockRunner interface object
226 * @return IdpTestContext with Mockrunner objects
228 public IdpTestContext getIdp() {
230 idp = new IdpTestContext();
237 * Establish initialized IdP and a set of MockRunner objects to
238 * process SSO, AA, and Artifact requests.
240 * <p>The IdP is initialized when the IdpResponder servlet init() is
241 * called. This establishes the static context of tables that
242 * allow the IdP to issue a Subject and then respond when that
243 * Subject is returned in an Attribute Query.</p>
245 * <p>This class creates the Mockrunner control blocks needed to
246 * call the IdP and, by creating the IdP Servlet object, also
247 * initializes an instance of the IdP. It depends on a configuration
248 * file located as a resource in the classpath, typically in the
249 * /testresources directory of the project.</p>
251 public class IdpTestContext {
255 // The Factory creates the Request, Response, Session, etc.
256 public WebMockObjectFactory factory = new WebMockObjectFactory();
258 // The TestModule runs the Servlet and Filter methods in the simulated container
259 public ServletTestModule testModule = new ServletTestModule(factory);
261 // Now simulated Servlet API objects
262 public MockServletContext servletContext= factory.getMockServletContext();
263 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
264 public MockHttpServletResponse response = factory.getMockResponse();
265 public MockHttpServletRequest request = factory.getMockRequest();
268 // The IdP Servlet that processes SSO, AA, and Artifact requests
269 // The object is created by Mockrunner
270 public IdPResponder idpServlet;
273 * Construct with the default configuration file
275 public IdpTestContext() {
276 this(idpConfigFileName);
280 * Construct using a specified IdP configuration file.
282 public IdpTestContext(String configFileName) {
285 servletContext.setServletContextName("dummy IdP Context");
286 servletContext.setInitParameter("IdPConfigFile", configFileName);
289 // Create instance of Filter class, add to chain, call its init()
290 // NOTE: The IdP reads its configuration file and initializes
291 // itself within this call.
292 idpServlet = (IdPResponder) testModule.createServlet(IdPResponder.class);
293 resetLoggingLevels();
295 // Unchanging properties of the HttpServletRequest
296 request.setRemoteAddr(REMOTE_ADDR);
297 request.setContextPath(CONTEXT_PATH);
298 request.setProtocol(PROTOCOL);
299 request.setScheme(SCHEME);
300 request.setServerName(SERVER_NAME);
301 request.setServerPort(SERVER_PORT);
306 * Set the fields of the request that depend on a suffix,
307 * normally SSO, AA, or Artifact
309 public void setRequestUrls(String suffix) {
310 request.setRequestURI(CONTEXT_URL+suffix);
311 request.setRequestURL(CONTEXT_URL+suffix);
312 request.setServletPath(CONTEXT_PATH+"/"+suffix);
322 * Here we keep a static reference to a Collection of Attributes.
324 * The Test can clear the collection and add attributes. When
325 * the IdP needs attributes, it treats this collection as the
326 * starting point and processes them through ARP. When then get
327 * to the SP they go through AAP. So you can test the Attribute
328 * processing logic in both components by creating Attributes
329 * with names and values that are accepted or rejected.
332 public static BasicAttributes attributes = new BasicAttributes();
335 * The Test should obtain a reference to the Attribute collection and add
336 * such attributes as it wants the IdP to return for a Principal.
337 * @return Attributes collection
339 public Attributes getAttributesCollection() {
346 * The Filter depends on a Servlet environment simulated by MockRunner.
347 * We give it its own set of MockRunner blocks because in real life
348 * it runs in a separate context from the SP or IdP.
350 * The Filter depends on the SP and, once initialized, has a reference
351 * to FilterSupportImpl and through it the SP configuration and Sessions.
353 private AuthenticationFilterContext filter;
354 public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
356 filter=new AuthenticationFilterContext();
361 * Create the MockRunning interface for running the ServletFilter.
363 * <p>The SP must be initialized to provide parameters.</p>
366 public class AuthenticationFilterContext {
369 // The Factory creates the Request, Response, Session, etc.
370 public WebMockObjectFactory factory = new WebMockObjectFactory();
372 // The TestModule runs the Servlet and Filter methods in the simulated container
373 public ServletTestModule testModule = new ServletTestModule(factory);
375 // Now simulated Servlet API objects
376 public MockServletContext servletContext= new MockServletContext();
377 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
378 public MockHttpServletResponse response = factory.getMockResponse();
379 public MockHttpServletRequest request = factory.getMockRequest();
382 * The Missing Manual: There are three types of init-params in
383 * the web.xml. One applies to the Context as a whole. The other
384 * two are nested inside a <servlet> or <filter> and provide
385 * parameters specific to that particular object. If you do
386 * a factory.getMockServletContext() you get an object that corresponds
387 * to the web.xml configuration itself. However, rather than adding
388 * init-param collections to the MockServletConfig and MockFilterConfig,
389 * Mockrunner seems to chain a user-created MockServletContext object
390 * to them and use its init-params as the parameters fed back to the
391 * Filter or Servlet object. So when you see "new MockServletContext()"
392 * there is a pretty good reason to expect this will not be used as a
393 * real ServletContext but rather as a secondary control block to a
394 * MockFilterConfig or MockServletConfig.
398 private AuthenticationFilter filter;
400 // SP configuration objects
401 private FilterSupport service;
402 private RMAppInfo rmAppInfo;
404 public AuthenticationFilterContext() {
406 // ServletContext (argument to Filters and Servlets)
407 servletContext.setServletContextName("dummy Servlet Context");
408 servletContext.setInitParameter("requireId", ".+/test.+");
410 // The FilterConfig (argument to Filter init)
411 filterConfig.setupServletContext(servletContext);
412 filterConfig.setFilterName("Test Filter under JUnit");
416 * Call after any changes to Context init-param values to
417 * initialize the filter object and connect to the SP.
419 * @throws ShibbolethConfigurationException from SP init.
421 public void setUp() throws ShibbolethConfigurationException {
423 // Create instance of Filter class, add to chain, call its init()
424 filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
426 // Note: if the SP is already initialized, this noops.
429 // Plug an instance of FilterSupportImpl into the Filter
430 service = new FilterSupportImpl();
431 AuthenticationFilter.setFilterSupport(service);
433 // Get our own copy of SP Config info for Assert statements
434 rmAppInfo = service.getRMAppInfo("default");
436 request.setRemoteAddr(REMOTE_ADDR);
437 request.setContextPath(RESOURCE_CONTEXT_PATH);
438 request.setProtocol(PROTOCOL);
439 request.setScheme(SCHEME);
440 request.setServerName(SERVER_NAME);
441 request.setServerPort(RESOURCE_SERVER_PORT);
444 public void setRequestUrls(String suffix) {
445 request.setMethod("GET");
446 request.setRequestURI(RESOURCE_CONTEXT_URL+suffix);
447 request.setRequestURL(RESOURCE_CONTEXT_URL+suffix);
448 request.setServletPath(RESOURCE_CONTEXT_PATH+"/"+suffix);