cea0e072b1976f7f0300594eb187bfcf3cac22d2
[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     
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+"/";
70     
71     
72     private static SAMLConfig samlConfig; 
73
74     
75     
76     /**
77      * Initialization logic goes here.
78      * <p>Reqires that Log4J already be configured.</p>
79      */
80     public ShibbolethRunner() {
81         
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" );
86     }
87  
88     
89     
90     /*
91      * Logging
92      * 
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.
98      * 
99      * You can change the loglevel variable from the test case
100      * code before calling setupLogging(). 
101      */
102     public static Level loglevel = Level.INFO;
103     
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;
108     
109     /**
110      * You will almost always call setupLogging first, but it
111      * it not automatic in case you have exotic logging 
112      * requirements.
113      * 
114      * <p>Restriction: avoid any static initialization that generates
115      * log messages because this method can only be called after 
116      * static initialation.</p>
117      */
118     public static void setupLogging() {
119         manageLogs = true;
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);
132     }
133     
134     /**
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.
138      */
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);
147         
148     }
149     
150     
151     
152     /*
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.
157      * 
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.
163      */
164     
165     private String spConfigFileName = "/basicSpHome/spconfig.xml";
166     /**
167      * If you are goint to change the SP Config File
168      * do it before calling initServiceProvider.
169      * 
170      * @param spConfigFileName
171      */
172     public void setSpConfigFileName(String spConfigFileName) {
173         this.spConfigFileName = spConfigFileName;
174     }
175     
176     private static boolean SPinitialized = false; // don't do it twice
177     
178     /**
179      * Load an SP configuration file.
180      * @throws ShibbolethConfigurationException  if bad config file
181      */
182     public void initializeSP() 
183         throws ShibbolethConfigurationException{
184         if (SPinitialized) return;
185         SPinitialized=true;
186         
187         ServiceProviderContext context = ServiceProviderContext.getInstance();
188         context.initialize();
189         
190         ServiceProviderConfig config = new ServiceProviderConfig();
191         context.setServiceProviderConfig(config);
192         config.loadConfigObjects(spConfigFileName);
193     }
194     
195
196     
197     /*
198      * Setup the IdP interface object
199      * 
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.
205      * 
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.
209      * 
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.
215      */
216     public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
217     public void setIdpConfigFileName(String idpConfigFileName) {
218         this.idpConfigFileName = idpConfigFileName;
219     } 
220     
221     public static IdpTestContext idp = null;
222     
223     /**
224      * Initializes the IdP if necessary, then returns a 
225      * pointer to the MockRunner interface object
226      * @return IdpTestContext with Mockrunner objects
227      */
228     public IdpTestContext getIdp() {
229         if (idp==null) {
230             idp = new IdpTestContext();
231         }
232         return idp;
233     }
234     
235     
236     /**
237      * Establish initialized IdP and a set of MockRunner objects to
238      * process SSO, AA, and Artifact requests.
239      * 
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>
244      * 
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>
250      */
251     public class IdpTestContext {
252         
253         
254
255         // The Factory creates the Request, Response, Session, etc.
256         public WebMockObjectFactory factory = new WebMockObjectFactory();
257         
258         // The TestModule runs the Servlet and Filter methods in the simulated container
259         public ServletTestModule testModule = new ServletTestModule(factory);
260         
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();
266         
267         
268         // The IdP Servlet that processes SSO, AA, and Artifact requests
269         // The object is created by Mockrunner
270         public IdPResponder idpServlet;
271         
272         /**
273          * Construct with the default configuration file
274          */
275         public IdpTestContext() {
276             this(idpConfigFileName);
277         }
278         
279         /**
280          * Construct using a specified IdP configuration file.
281          */
282         public IdpTestContext(String configFileName) {
283             
284             // ServletContext
285             servletContext.setServletContextName("dummy IdP Context");
286             servletContext.setInitParameter("IdPConfigFile", configFileName);
287             
288             
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();
294             
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);
302             
303         }
304         
305         /**
306          * Set the fields of the request that depend on a suffix,
307          * normally SSO, AA, or Artifact
308          */
309         public void setRequestUrls(String suffix) {
310             request.setRequestURI(CONTEXT_URL+suffix);
311             request.setRequestURL(CONTEXT_URL+suffix);
312             request.setServletPath(CONTEXT_PATH+"/"+suffix);
313         }
314         
315     }
316
317     
318     
319     
320     
321     /*
322      * Here we keep a static reference to a Collection of Attributes. 
323      * 
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.
330      */
331     
332     public static BasicAttributes attributes = new BasicAttributes();
333     
334     /**
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
338      */
339     public Attributes getAttributesCollection() {
340         return attributes;
341     }
342     
343     
344     
345     /*
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.
349      * 
350      * The Filter depends on the SP and, once initialized, has a reference
351      * to FilterSupportImpl and through it the SP configuration and Sessions.
352      */
353     private AuthenticationFilterContext filter;
354     public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
355         if (filter==null)
356             filter=new AuthenticationFilterContext();
357         return filter;
358     }
359     
360     /**
361      * Create the MockRunning interface for running the ServletFilter.
362      * 
363      * <p>The SP must be initialized to provide parameters.</p>
364      *
365      */
366     public class AuthenticationFilterContext {
367         
368
369         // The Factory creates the Request, Response, Session, etc.
370         public WebMockObjectFactory factory = new WebMockObjectFactory();
371         
372         // The TestModule runs the Servlet and Filter methods in the simulated container
373         public ServletTestModule testModule = new ServletTestModule(factory);
374         
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();
380         
381         /*
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.
395          */
396         
397         // Filter objects
398         private AuthenticationFilter filter;
399         
400         // SP configuration objects
401         private FilterSupport service;
402         private RMAppInfo rmAppInfo;
403
404        public AuthenticationFilterContext() {
405             
406             // ServletContext (argument to Filters and Servlets)
407             servletContext.setServletContextName("dummy Servlet Context");
408             servletContext.setInitParameter("requireId", ".+/test.+");
409             
410             // The FilterConfig (argument to Filter init)
411             filterConfig.setupServletContext(servletContext);
412             filterConfig.setFilterName("Test Filter under JUnit");
413        }
414        
415        /**
416         * Call after any changes to Context init-param values to
417         * initialize the filter object and connect to the SP.
418         * 
419         * @throws ShibbolethConfigurationException from SP init.
420         */
421        public void setUp() throws ShibbolethConfigurationException {
422             
423             // Create instance of Filter class, add to chain, call its init()
424             filter = (AuthenticationFilter) testModule.createFilter(AuthenticationFilter.class);
425             
426             // Note: if the SP is already initialized, this noops.
427             initializeSP();
428             
429             // Plug an instance of FilterSupportImpl into the Filter
430             service = new FilterSupportImpl();
431             AuthenticationFilter.setFilterSupport(service);
432
433             // Get our own copy of SP Config info for Assert statements
434             rmAppInfo = service.getRMAppInfo("default");
435
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);
442         }
443         
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);
449             
450         }
451     }
452     
453 }