New structure to JUnit integration test to simplify Test files
[java-idp.git] / tests / edu / internet2 / middleware / shibboleth / runner / ShibbolethRunner.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package edu.internet2.middleware.shibboleth.runner;
18
19 import javax.naming.directory.Attributes;
20 import javax.naming.directory.BasicAttributes;
21
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;
29
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;
36
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;
45
46 /**
47  * Initialize on request the IdP, SP, and Filter on behalf of a JUnit test.
48  * 
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>
52  * 
53  * <p>Look at *.integration.IntegrationTest for an example of use.</p>
54  * 
55  * @author Howard Gilbert.
56  */
57 public class ShibbolethRunner {
58     
59     private static SAMLConfig samlConfig; 
60     
61     /**
62      * Initialization logic goes here.
63      * <p>Reqires that Log4J already be configured.</p>
64      */
65     public ShibbolethRunner() {
66         
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" );
71     }
72  
73     
74     
75     /*
76      * Logging
77      * 
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.
83      * 
84      * You can change the loglevel variable from the test case
85      * code before calling setupLogging(). 
86      */
87     public static Level loglevel = Level.INFO;
88     
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;
93     
94     /**
95      * You will almost always call setupLogging first, but it
96      * it not automatic in case you have exotic logging 
97      * requirements.
98      * 
99      * <p>Restriction: avoid any static initialization that generates
100      * log messages because this method can only be called after 
101      * static initialation.</p>
102      */
103     public static void setupLogging() {
104         manageLogs = true;
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);
113     }
114     
115     /**
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.
119      */
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);
125         
126     }
127     
128     
129     
130     /*
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.
135      * 
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.
141      */
142     
143     private String spConfigFileName = "/basicSpHome/spconfig.xml";
144     /**
145      * If you are goint to change the SP Config File
146      * do it before calling initServiceProvider.
147      * 
148      * @param spConfigFileName
149      */
150     public void setSpConfigFileName(String spConfigFileName) {
151         this.spConfigFileName = spConfigFileName;
152     }
153     
154     private static boolean SPinitialized = false; // don't do it twice
155     
156     /**
157      * Load an SP configuration file.
158      * @throws ShibbolethConfigurationException  if bad config file
159      */
160     public void initializeSP() 
161         throws ShibbolethConfigurationException{
162         if (SPinitialized) return;
163         SPinitialized=true;
164         
165         ServiceProviderContext context = ServiceProviderContext.getInstance();
166         context.initialize();
167         
168         ServiceProviderConfig config = new ServiceProviderConfig();
169         context.setServiceProviderConfig(config);
170         config.loadConfigObjects(spConfigFileName);
171     }
172     
173
174     
175     /*
176      * Setup the IdP interface object
177      * 
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.
183      * 
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.
187      * 
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.
193      */
194     public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
195     public void setIdpConfigFileName(String idpConfigFileName) {
196         this.idpConfigFileName = idpConfigFileName;
197     } 
198     
199     public static IdpTestContext idp = null;
200     
201     /**
202      * Initializes the IdP if necessary, then returns a 
203      * pointer to the MockRunner interface object
204      * @return IdpTestContext with Mockrunner objects
205      */
206     public IdpTestContext getIdp() {
207         if (idp==null) {
208             idp = new IdpTestContext();
209         }
210         return idp;
211     }
212     
213     
214     /**
215      * Establish initialized IdP and a set of MockRunner objects to
216      * process SSO, AA, and Artifact requests.
217      * 
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>
222      * 
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>
228      */
229     public class IdpTestContext {
230         
231         
232         // The Factory creates the Request, Response, Session, etc.
233         public WebMockObjectFactory factory = new WebMockObjectFactory();
234         
235         // The TestModule runs the Servlet and Filter methods in the simulated container
236         public ServletTestModule testModule = new ServletTestModule(factory);
237         
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();
243         
244         
245         // The IdP Servlet that processes SSO, AA, and Artifact requests
246         // The object is created by Mockrunner
247         public IdPResponder idpServlet;
248         
249         /**
250          * Construct with the default configuration file
251          */
252         public IdpTestContext() {
253             this(idpConfigFileName);
254         }
255         
256         /**
257          * Construct using a specified IdP configuration file.
258          */
259         public IdpTestContext(String configFileName) {
260             
261             // ServletContext
262             servletContext.setServletContextName("dummy IdP Context");
263             servletContext.setInitParameter("IdPConfigFile", configFileName);
264             
265             
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);
270             
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);
278             
279         }
280         
281         /**
282          * Set the fields of the request that depend on a suffix,
283          * normally SSO, AA, or Artifact
284          */
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);
289             
290         }
291         
292     }
293
294     
295     
296     
297     
298     /*
299      * Here we keep a static reference to a Collection of Attributes. 
300      * 
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.
307      */
308     
309     public static BasicAttributes attributes = new BasicAttributes();
310     
311     /**
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
315      */
316     public Attributes getAttributesCollection() {
317         return attributes;
318     }
319     
320     
321     
322     /*
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.
326      * 
327      * The Filter depends on the SP and, once initialized, has a reference
328      * to FilterSupportImpl and through it the SP configuration and Sessions.
329      */
330     private AuthenticationFilterContext filter;
331     public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
332         if (filter==null)
333             filter=new AuthenticationFilterContext();
334         return filter;
335     }
336     
337     /**
338      * Create the MockRunning interface for running the ServletFilter.
339      * 
340      * <p>The SP must be initialized to provide parameters.</p>
341      *
342      */
343     public class AuthenticationFilterContext {
344         
345         // The Factory creates the Request, Response, Session, etc.
346         public WebMockObjectFactory factory = new WebMockObjectFactory();
347         
348         // The TestModule runs the Servlet and Filter methods in the simulated container
349         public ServletTestModule testModule = new ServletTestModule(factory);
350         
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();
356         
357         /*
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.
371          */
372         
373         // Filter objects
374         private AuthenticationFilter filter;
375         
376         // SP configuration objects
377         private FilterSupport service;
378         private RMAppInfo rmAppInfo;
379
380        public AuthenticationFilterContext() {
381             
382             // ServletContext (argument to Filters and Servlets)
383             servletContext.setServletContextName("dummy Servlet Context");
384             servletContext.setInitParameter("requireId", ".+/test.+");
385             
386             // The FilterConfig (argument to Filter init)
387             filterConfig.setupServletContext(servletContext);
388             filterConfig.setFilterName("Test Filter under JUnit");
389        }
390        
391        /**
392         * Call after any changes to Context init-param values to
393         * initialize the filter object and connect to the SP.
394         * 
395         * @throws ShibbolethConfigurationException from SP init.
396         */
397        public void setUp() throws ShibbolethConfigurationException {
398             
399             // Create instance of Filter class, add to chain, call its init()
400             filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
401             
402             // Note: if the SP is already initialized, this noops.
403             initializeSP();
404             
405             // Plug an instance of FilterSupportImpl into the Filter
406             service = new FilterSupportImpl();
407             AuthenticationFilter.setFilterSupport(service);
408
409             // Get our own copy of SP Config info for Assert statements
410             rmAppInfo = service.getRMAppInfo("default");
411
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);
418         }
419         
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);
425             
426         }
427     }
428     
429 }