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.shibboleth");
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.removeAllAppenders();
109 root.addAppender(consoleAppender);
110 root.setLevel(Level.ERROR);
111 clientLogger.removeAllAppenders();
112 clientLogger.setLevel(loglevel);
113 initLogger.removeAllAppenders();
114 initLogger.setLevel(loglevel);
115 samlLogger.removeAllAppenders();
116 samlLogger.setLevel(loglevel);
120 * Sometimes (as in IdP initialization) the logging levels
121 * get reset to some unintended level. This resets them
122 * to whatever we want for testing.
124 public static void resetLoggingLevels() {
125 if (!manageLogs) return; // If setupLogging was never called.
126 clientLogger.removeAllAppenders();
127 clientLogger.setLevel(loglevel);
128 initLogger.removeAllAppenders();
129 initLogger.setLevel(loglevel);
130 samlLogger.removeAllAppenders();
131 samlLogger.setLevel(loglevel);
138 * The SP is represented by an SPContext object and the objects
139 * SessionManager, SPConfig, etc. chained off it. The context
140 * is initialized and then the main configuration file is read
141 * in to create the Config object.
143 * The testing environment doesn't bother with MockRunner objects.
144 * The Servlet interface to the SP is a thin layer that only
145 * translates between HTTP/HTML (the Request object) and method
146 * calls. So once initialized, it is just as easy to call the
147 * SessionManager and FilterSupportImpl directly.
150 private String spConfigFileName = "/basicSpHome/spconfig.xml";
152 * If you are goint to change the SP Config File
153 * do it before calling initServiceProvider.
155 * @param spConfigFileName
157 public void setSpConfigFileName(String spConfigFileName) {
158 this.spConfigFileName = spConfigFileName;
161 private static boolean SPinitialized = false; // don't do it twice
164 * Load an SP configuration file.
165 * @throws ShibbolethConfigurationException if bad config file
167 public void initializeSP()
168 throws ShibbolethConfigurationException{
169 if (SPinitialized) return;
172 ServiceProviderContext context = ServiceProviderContext.getInstance();
173 context.initialize();
175 ServiceProviderConfig config = new ServiceProviderConfig();
176 context.setServiceProviderConfig(config);
177 config.loadConfigObjects(spConfigFileName);
183 * Setup the IdP interface object
185 * The IdP keeps its "context" of cached data and configured
186 * objects internal rather than exposing it as a public object.
187 * The IdpTestContext object does the initialization and creates
188 * a set of MockRunner object through which the SSO, AA, and
189 * Artifact requests can be generated.
191 * The real IdP objects configure themselves when the Servlet
192 * init() method is called. The Configuration file name coded
193 * here is passed to the Servlet as a simulated context parameter.
195 * To direct the AA and Artifact queries back to the Idp object,
196 * a call to SAML sets up the MockHTTPBindingProvider to replace
197 * the normal HTTPBindingProvider. Thus instead of creating URL
198 * and sockets to talk to the IdP, a simulated Request object is
199 * configured and the IdP is called through MockRunner.
201 public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
202 public void setIdpConfigFileName(String idpConfigFileName) {
203 this.idpConfigFileName = idpConfigFileName;
206 public static IdpTestContext idp = null;
209 * Initializes the IdP if necessary, then returns a
210 * pointer to the MockRunner interface object
211 * @return IdpTestContext with Mockrunner objects
213 public IdpTestContext getIdp() {
215 idp = new IdpTestContext();
222 * Establish initialized IdP and a set of MockRunner objects to
223 * process SSO, AA, and Artifact requests.
225 * <p>The IdP is initialized when the IdpResponder servlet init() is
226 * called. This establishes the static context of tables that
227 * allow the IdP to issue a Subject and then respond when that
228 * Subject is returned in an Attribute Query.</p>
230 * <p>This class creates the Mockrunner control blocks needed to
231 * call the IdP and, by creating the IdP Servlet object, also
232 * initializes an instance of the IdP. It depends on a configuration
233 * file located as a resource in the classpath, typically in the
234 * /testresources directory of the project.</p>
236 public class IdpTestContext {
239 // The Factory creates the Request, Response, Session, etc.
240 public WebMockObjectFactory factory = new WebMockObjectFactory();
242 // The TestModule runs the Servlet and Filter methods in the simulated container
243 public ServletTestModule testModule = new ServletTestModule(factory);
245 // Now simulated Servlet API objects
246 public MockServletContext servletContext= factory.getMockServletContext();
247 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
248 public MockHttpServletResponse response = factory.getMockResponse();
249 public MockHttpServletRequest request = factory.getMockRequest();
252 // The IdP Servlet that processes SSO, AA, and Artifact requests
253 // The object is created by Mockrunner
254 public IdPResponder idpServlet;
257 * Construct with the default configuration file
259 public IdpTestContext() {
260 this(idpConfigFileName);
264 * Construct using a specified IdP configuration file.
266 public IdpTestContext(String configFileName) {
269 servletContext.setServletContextName("dummy IdP Context");
270 servletContext.setInitParameter("IdPConfigFile", configFileName);
273 // Create instance of Filter class, add to chain, call its init()
274 // NOTE: The IdP reads its configuration file and initializes
275 // itself within this call.
276 idpServlet = (IdPResponder) testModule.createServlet(IdPResponder.class);
277 resetLoggingLevels();
279 // Unchanging properties of the HttpServletRequest
280 request.setRemoteAddr("127.0.0.1");
281 request.setContextPath("/shibboleth-idp");
282 request.setProtocol("HTTP/1.1");
283 request.setScheme("https");
284 request.setServerName("idp.example.org");
285 request.setServerPort(443);
290 * Set the fields of the request that depend on a suffix,
291 * normally SSO, AA, or Artifact
293 public void setRequestUrls(String suffix) {
294 request.setRequestURI("https://idp.example.org/shibboleth-idp/"+suffix);
295 request.setRequestURL("https://idp.example.org/shibboleth-idp/"+suffix);
296 request.setServletPath("/shibboleth.idp/"+suffix);
307 * Here we keep a static reference to a Collection of Attributes.
309 * The Test can clear the collection and add attributes. When
310 * the IdP needs attributes, it treats this collection as the
311 * starting point and processes them through ARP. When then get
312 * to the SP they go through AAP. So you can test the Attribute
313 * processing logic in both components by creating Attributes
314 * with names and values that are accepted or rejected.
317 public static BasicAttributes attributes = new BasicAttributes();
320 * The Test should obtain a reference to the Attribute collection and add
321 * such attributes as it wants the IdP to return for a Principal.
322 * @return Attributes collection
324 public Attributes getAttributesCollection() {
331 * The Filter depends on a Servlet environment simulated by MockRunner.
332 * We give it its own set of MockRunner blocks because in real life
333 * it runs in a separate context from the SP or IdP.
335 * The Filter depends on the SP and, once initialized, has a reference
336 * to FilterSupportImpl and through it the SP configuration and Sessions.
338 private AuthenticationFilterContext filter;
339 public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
341 filter=new AuthenticationFilterContext();
346 * Create the MockRunning interface for running the ServletFilter.
348 * <p>The SP must be initialized to provide parameters.</p>
351 public class AuthenticationFilterContext {
353 // The Factory creates the Request, Response, Session, etc.
354 public WebMockObjectFactory factory = new WebMockObjectFactory();
356 // The TestModule runs the Servlet and Filter methods in the simulated container
357 public ServletTestModule testModule = new ServletTestModule(factory);
359 // Now simulated Servlet API objects
360 public MockServletContext servletContext= new MockServletContext();
361 public MockFilterConfig filterConfig= factory.getMockFilterConfig();
362 public MockHttpServletResponse response = factory.getMockResponse();
363 public MockHttpServletRequest request = factory.getMockRequest();
366 * The Missing Manual: There are three types of init-params in
367 * the web.xml. One applies to the Context as a whole. The other
368 * two are nested inside a <servlet> or <filter> and provide
369 * parameters specific to that particular object. If you do
370 * a factory.getMockServletContext() you get an object that corresponds
371 * to the web.xml configuration itself. However, rather than adding
372 * init-param collections to the MockServletConfig and MockFilterConfig,
373 * Mockrunner seems to chain a user-created MockServletContext object
374 * to them and use its init-params as the parameters fed back to the
375 * Filter or Servlet object. So when you see "new MockServletContext()"
376 * there is a pretty good reason to expect this will not be used as a
377 * real ServletContext but rather as a secondary control block to a
378 * MockFilterConfig or MockServletConfig.
382 private AuthenticationFilter filter;
384 // SP configuration objects
385 private FilterSupport service;
386 private RMAppInfo rmAppInfo;
388 public AuthenticationFilterContext() {
390 // ServletContext (argument to Filters and Servlets)
391 servletContext.setServletContextName("dummy Servlet Context");
392 servletContext.setInitParameter("requireId", ".+/test.+");
394 // The FilterConfig (argument to Filter init)
395 filterConfig.setupServletContext(servletContext);
396 filterConfig.setFilterName("Test Filter under JUnit");
400 * Call after any changes to Context init-param values to
401 * initialize the filter object and connect to the SP.
403 * @throws ShibbolethConfigurationException from SP init.
405 public void setUp() throws ShibbolethConfigurationException {
407 // Create instance of Filter class, add to chain, call its init()
408 filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
410 // Note: if the SP is already initialized, this noops.
413 // Plug an instance of FilterSupportImpl into the Filter
414 service = new FilterSupportImpl();
415 AuthenticationFilter.setFilterSupport(service);
417 // Get our own copy of SP Config info for Assert statements
418 rmAppInfo = service.getRMAppInfo("default");
420 request.setRemoteAddr("127.0.0.1");
421 request.setContextPath("/secure");
422 request.setProtocol("HTTP/1.1");
423 request.setScheme("https");
424 request.setServerName("sp.example.org");
425 request.setServerPort(9443);
428 public void setRequestUrls(String suffix) {
429 request.setMethod("GET");
430 request.setRequestURI("http://sp.example.org:9443/secure/"+suffix);
431 request.setRequestURL("http://sp.example.org:9443/secure/"+suffix);
432 request.setServletPath("/secure/"+suffix);