36dc08d8f1f92ee48ba0db96125a6eead31cf639
[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.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;
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.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);
117     }
118     
119     /**
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.
123      */
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);
132         
133     }
134     
135     
136     
137     /*
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.
142      * 
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.
148      */
149     
150     private String spConfigFileName = "/basicSpHome/spconfig.xml";
151     /**
152      * If you are goint to change the SP Config File
153      * do it before calling initServiceProvider.
154      * 
155      * @param spConfigFileName
156      */
157     public void setSpConfigFileName(String spConfigFileName) {
158         this.spConfigFileName = spConfigFileName;
159     }
160     
161     private static boolean SPinitialized = false; // don't do it twice
162     
163     /**
164      * Load an SP configuration file.
165      * @throws ShibbolethConfigurationException  if bad config file
166      */
167     public void initializeSP() 
168         throws ShibbolethConfigurationException{
169         if (SPinitialized) return;
170         SPinitialized=true;
171         
172         ServiceProviderContext context = ServiceProviderContext.getInstance();
173         context.initialize();
174         
175         ServiceProviderConfig config = new ServiceProviderConfig();
176         context.setServiceProviderConfig(config);
177         config.loadConfigObjects(spConfigFileName);
178     }
179     
180
181     
182     /*
183      * Setup the IdP interface object
184      * 
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.
190      * 
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.
194      * 
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.
200      */
201     public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
202     public void setIdpConfigFileName(String idpConfigFileName) {
203         this.idpConfigFileName = idpConfigFileName;
204     } 
205     
206     public static IdpTestContext idp = null;
207     
208     /**
209      * Initializes the IdP if necessary, then returns a 
210      * pointer to the MockRunner interface object
211      * @return IdpTestContext with Mockrunner objects
212      */
213     public IdpTestContext getIdp() {
214         if (idp==null) {
215             idp = new IdpTestContext();
216         }
217         return idp;
218     }
219     
220     
221     /**
222      * Establish initialized IdP and a set of MockRunner objects to
223      * process SSO, AA, and Artifact requests.
224      * 
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>
229      * 
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>
235      */
236     public class IdpTestContext {
237         
238         
239         // The Factory creates the Request, Response, Session, etc.
240         public WebMockObjectFactory factory = new WebMockObjectFactory();
241         
242         // The TestModule runs the Servlet and Filter methods in the simulated container
243         public ServletTestModule testModule = new ServletTestModule(factory);
244         
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();
250         
251         
252         // The IdP Servlet that processes SSO, AA, and Artifact requests
253         // The object is created by Mockrunner
254         public IdPResponder idpServlet;
255         
256         /**
257          * Construct with the default configuration file
258          */
259         public IdpTestContext() {
260             this(idpConfigFileName);
261         }
262         
263         /**
264          * Construct using a specified IdP configuration file.
265          */
266         public IdpTestContext(String configFileName) {
267             
268             // ServletContext
269             servletContext.setServletContextName("dummy IdP Context");
270             servletContext.setInitParameter("IdPConfigFile", configFileName);
271             
272             
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();
278             
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);
286             
287         }
288         
289         /**
290          * Set the fields of the request that depend on a suffix,
291          * normally SSO, AA, or Artifact
292          */
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);
297             
298         }
299         
300     }
301
302     
303     
304     
305     
306     /*
307      * Here we keep a static reference to a Collection of Attributes. 
308      * 
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.
315      */
316     
317     public static BasicAttributes attributes = new BasicAttributes();
318     
319     /**
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
323      */
324     public Attributes getAttributesCollection() {
325         return attributes;
326     }
327     
328     
329     
330     /*
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.
334      * 
335      * The Filter depends on the SP and, once initialized, has a reference
336      * to FilterSupportImpl and through it the SP configuration and Sessions.
337      */
338     private AuthenticationFilterContext filter;
339     public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
340         if (filter==null)
341             filter=new AuthenticationFilterContext();
342         return filter;
343     }
344     
345     /**
346      * Create the MockRunning interface for running the ServletFilter.
347      * 
348      * <p>The SP must be initialized to provide parameters.</p>
349      *
350      */
351     public class AuthenticationFilterContext {
352         
353         // The Factory creates the Request, Response, Session, etc.
354         public WebMockObjectFactory factory = new WebMockObjectFactory();
355         
356         // The TestModule runs the Servlet and Filter methods in the simulated container
357         public ServletTestModule testModule = new ServletTestModule(factory);
358         
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();
364         
365         /*
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.
379          */
380         
381         // Filter objects
382         private AuthenticationFilter filter;
383         
384         // SP configuration objects
385         private FilterSupport service;
386         private RMAppInfo rmAppInfo;
387
388        public AuthenticationFilterContext() {
389             
390             // ServletContext (argument to Filters and Servlets)
391             servletContext.setServletContextName("dummy Servlet Context");
392             servletContext.setInitParameter("requireId", ".+/test.+");
393             
394             // The FilterConfig (argument to Filter init)
395             filterConfig.setupServletContext(servletContext);
396             filterConfig.setFilterName("Test Filter under JUnit");
397        }
398        
399        /**
400         * Call after any changes to Context init-param values to
401         * initialize the filter object and connect to the SP.
402         * 
403         * @throws ShibbolethConfigurationException from SP init.
404         */
405        public void setUp() throws ShibbolethConfigurationException {
406             
407             // Create instance of Filter class, add to chain, call its init()
408             filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
409             
410             // Note: if the SP is already initialized, this noops.
411             initializeSP();
412             
413             // Plug an instance of FilterSupportImpl into the Filter
414             service = new FilterSupportImpl();
415             AuthenticationFilter.setFilterSupport(service);
416
417             // Get our own copy of SP Config info for Assert statements
418             rmAppInfo = service.getRMAppInfo("default");
419
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);
426         }
427         
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);
433             
434         }
435     }
436     
437 }