aff83e1d6cdb124c2b4dfec6854673708f86f4df
[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.serviceprovider.AssertionConsumerServlet;
41 import edu.internet2.middleware.shibboleth.serviceprovider.FilterSupportImpl;
42 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig;
43 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderContext;
44
45 /**
46  * A JUnit Test support class for Shibboleth.
47  * 
48  * <p>This class can create the Mockrunner control blocks to 
49  * interface to an instance of the SP and one or more instances
50  * of the IdP and Resource Filter. Each instance is initialized
51  * with its own set of configuration files. The test only needs
52  * to create the objects it needs.</p>
53  * 
54  * <p>Look at *.integration.IntegrationTest for an example of use.</p>
55  * 
56  * @author Howard Gilbert.
57  */
58 public class ShibbolethRunner {
59     
60     // Default values used to define each context
61     public static String REMOTE_ADDR = "192.168.0.99";
62     public static String SCHEME = "https";
63     public static String PROTOCOL = "HTTP/1.1";
64     public static int    SERVER_PORT = 443;
65     
66     // IdP
67     public static String IDP_SERVER_NAME = "idp.example.org";
68     public static String IDP_CONTEXT_PATH = "/shibboleth-idp";
69     public static String IDP_CONTEXT_URL = SCHEME+"://"+IDP_SERVER_NAME+IDP_CONTEXT_PATH+"/";
70     
71     // SP
72     public static String SP_SERVER_NAME = "sp.example.org";
73     public static String SP_CONTEXT_PATH = "/shibboleth-sp";
74     public static String SP_CONTEXT_URL = SCHEME+"://"+SP_SERVER_NAME+SP_CONTEXT_PATH+"/";
75     
76     // Resource
77     public static int    RESOURCE_SERVER_PORT = 9443;
78     public static String RESOURCE_CONTEXT_PATH = "/secure";
79     public static String RESOURCE_CONTEXT_URL = SCHEME+"://"+SP_SERVER_NAME+":"+RESOURCE_SERVER_PORT+RESOURCE_CONTEXT_PATH+"/";
80     
81     
82     private static SAMLConfig samlConfig; // See constructor for use
83
84     
85     
86  
87     /********************* Static Methods **********************/
88     
89     /*
90      * Logging
91      * 
92      * For automated test cases that normally just work, you 
93      * probably want to leave the logging level to ERROR. However,
94      * if you are running a custom test case to discover the source
95      * of a problem, or when building a new test case, then you 
96      * may want to set the logging level to DEBUG.
97      * 
98      * You can change the loglevel variable from the test case
99      * code before calling setupLogging(). 
100      */
101     public static Level loglevel = Level.INFO;
102     
103     private static Logger clientLogger = Logger.getLogger("edu.internet2.middleware.shibboleth");
104     private static Logger initLogger = Logger.getLogger("shibboleth.init");
105     private static Logger samlLogger = Logger.getLogger("org.opensaml");
106     private static boolean manageLogs = false;
107     
108     /**
109      * You will almost always call setupLogging first, but it
110      * it not automatic in case you have exotic logging 
111      * requirements.
112      * 
113      * <p>Restriction: avoid any static initialization that generates
114      * log messages because this method can only be called after 
115      * static initialation.</p>
116      */
117     public static void setupLogging() {
118         manageLogs = true;
119         Logger root = Logger.getRootLogger();
120         Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
121         ConsoleAppender consoleAppender= new ConsoleAppender(initLayout,ConsoleAppender.SYSTEM_OUT);
122         root.removeAllAppenders();
123         root.addAppender(consoleAppender);
124         root.setLevel(Level.ERROR);
125         clientLogger.removeAllAppenders();
126         clientLogger.setLevel(loglevel);
127         initLogger.removeAllAppenders();
128         initLogger.setLevel(loglevel);
129         samlLogger.removeAllAppenders();
130         samlLogger.setLevel(loglevel);
131     }
132     
133     /**
134      * Sometimes (as in IdP initialization) the logging levels
135      * get reset to some unintended level. This resets them
136      * to whatever we want for testing.
137      */
138     public static void resetLoggingLevels() {
139         if (!manageLogs) return;  // If setupLogging was never called.
140         clientLogger.removeAllAppenders();
141         clientLogger.setLevel(loglevel);
142         initLogger.removeAllAppenders();
143         initLogger.setLevel(loglevel);
144         samlLogger.removeAllAppenders();
145         samlLogger.setLevel(loglevel);
146         
147     }
148     
149     
150     
151     
152     
153     /********************* Constructors ************************
154      * Initialization logic goes here.
155      * <p>Reqires that Log4J already be configured.</p>
156      */
157     public ShibbolethRunner() {
158         configureTestSAMLQueries();
159     }
160
161     /**
162      * SAML has a list of BindingProviders that access the IdP.
163      * Normally the SOAP HTTP BindingProvider is the default and
164      * it accesses the IdP by creating a URL socket. This code 
165      * replaces that default with a Mockrunner BindingProvider.
166      * So when the SP does an AA or Artifact Query, the IdP is
167      * called using its Mockrunner simulated Servlet context.
168      * 
169      * <p>Note: This method depends on a real IdP context created
170      * using the IdPTestContext. Use another method if you want
171      * to feed back pre-created test responses.</p>
172      */
173     private void configureTestSAMLQueries() {
174         samlConfig = SAMLConfig.instance();
175         samlConfig.setDefaultBindingProvider(SAMLBinding.SOAP,
176                 "edu.internet2.middleware.shibboleth.runner.MockHTTPBindingProvider" );
177     }
178     
179     
180     
181     
182     
183     
184     
185     
186     /************************* Service Provider ********************
187      * The SP is represented by an SPContext object and the objects
188      * SessionManager, SPConfig, etc. chained off it. The context
189      * is initialized and then the main configuration file is read
190      * in to create the Config object.
191      */
192     
193     private String spConfigFileName = "/basicSpHome/spconfig.xml";
194     /**
195      * If you are goint to change the SP Config File
196      * do it before calling initServiceProvider or constructing
197      * an SPTestContext.
198      * 
199      * @param spConfigFileName
200      */
201     public void setSpConfigFileName(String spConfigFileName) {
202         this.spConfigFileName = spConfigFileName;
203     }
204     
205     private static boolean SPinitialized = false; // don't do it twice
206     public static SPTestContext consumer = null;
207     
208     /**
209      * Initialize an instance of the SP context and configuration.
210      * 
211      * @throws ShibbolethConfigurationException  if bad config file
212      */
213     public void initializeSP() 
214         throws ShibbolethConfigurationException{
215         if (SPinitialized) return;
216         SPinitialized=true;
217         
218         ServiceProviderContext context = ServiceProviderContext.getInstance();
219         context.initialize();
220         
221         ServiceProviderConfig config = new ServiceProviderConfig();
222         context.setServiceProviderConfig(config);
223         config.loadConfigObjects(spConfigFileName);
224         
225         // Plug an instance of FilterSupportImpl into the Filter
226         FilterSupportImpl service = new FilterSupportImpl();
227         AuthenticationFilter.setFilterSupport(service);
228         
229     }
230     
231     
232     /**
233      * A MockRunner interface object for the AssertionConsumerServlet.
234      * 
235      * <p>The SP itself is a static set of objects initialized 
236      * under the ServiceProviderContext. There can be only one
237      * SP per ClassLoader, so there is no way to test multiple 
238      * SPs at the same time. However, SPs don't interact, so it
239      * doesn't matter.</p>
240      * 
241      * <p>If more than one SPTestContext object is created, they
242      * share the same SPContext objects.
243      */
244     public class SPTestContext {
245
246         // The Factory creates the Request, Response, Session, etc.
247         public WebMockObjectFactory factory = new WebMockObjectFactory();
248         
249         // The TestModule runs the Servlet and Filter methods in the simulated container
250         public ServletTestModule testModule = new ServletTestModule(factory);
251         
252         // Now simulated Servlet API objects
253         public MockServletContext servletContext= factory.getMockServletContext();
254         public MockFilterConfig filterConfig= factory.getMockFilterConfig();
255         public MockHttpServletResponse response = factory.getMockResponse();
256         public MockHttpServletRequest request = factory.getMockRequest();
257         
258         public AssertionConsumerServlet spServlet;
259         
260         
261         /**
262          * Construct the related objects
263          * @throws ShibbolethConfigurationException 
264          */
265         public SPTestContext() throws ShibbolethConfigurationException {
266             
267             // ServletContext
268             servletContext.setServletContextName("dummy SP Context");
269             servletContext.setInitParameter("ServiceProviderConfigFile", spConfigFileName);
270             
271             // Create the Servlet object, but do not run its init()
272             // instead use the initializeSP() routine which does 
273             // the same initialize in the test environment
274             spServlet = new AssertionConsumerServlet();
275             testModule.setServlet(spServlet, false);
276             initializeSP();
277             
278         }
279         
280         /**
281          * Set the fields of the request that depend on a suffix,
282          */
283         public void resetRequest(String suffix) {
284             request.resetAll();
285             response.resetAll();
286             
287             // Unchanging properties of the HttpServletRequest
288             request.setRemoteAddr(REMOTE_ADDR);
289             request.setContextPath(SP_CONTEXT_PATH);
290             request.setProtocol(PROTOCOL);
291             request.setScheme(SCHEME);
292             request.setServerName(SP_SERVER_NAME);
293             request.setServerPort(SERVER_PORT);
294             
295             request.setRequestURI(SP_CONTEXT_URL+suffix);
296             request.setRequestURL(SP_CONTEXT_URL+suffix);
297             request.setServletPath(SP_CONTEXT_PATH+"/"+suffix);
298         }
299         
300     }
301
302     
303     
304     
305     
306     
307     /************************ IdP ******************************
308      * Setup the IdP interface object
309      * 
310      * The IdP associates its "context" of cached data and configured 
311      * objects with the IdPResponder Servlet object. They are 
312      * initialized when the Servlet init() is called. It is 
313      * possible to create more than one IdpTestContext object
314      * representing different configuration files, or a new
315      * IdpTestContext can be created with fresh Mockrunner object
316      * on top of an existing initialized IdP.
317      * 
318      * To direct the AA and Artifact queries back to the Idp object,
319      * a call to SAML sets up the MockHTTPBindingProvider to replace
320      * the normal HTTPBindingProvider. Thus instead of creating URL
321      * and sockets to talk to the IdP, a simulated Request object is
322      * configured and the IdP is called through MockRunner.
323      */
324     public String idpConfigFileName = "/basicIdpHome/idpconfig.xml";
325     public void setIdpConfigFileName(String idpConfigFileName) {
326         this.idpConfigFileName = idpConfigFileName;
327     } 
328     
329     /**
330      * Although it is possible in theory to have more than one IdP 
331      * running in a TestCase, this one static IdpTestContext
332      * pointer tells the MockHTTPBindingProvider which IdP
333      * to use for AA and Artifact queries. If you have more
334      * that one IdP, the TestCase has to figure out how to swap this
335      * pointer between them.  
336      */
337     public static IdpTestContext idp = null;
338     
339     /**
340      * Initializes the IdP if necessary, then returns a 
341      * pointer to the MockRunner interface object
342      * 
343      * @return IdpTestContext with Mockrunner objects
344      */
345     public IdpTestContext getIdp() {
346         if (idp==null) {
347             idp = new IdpTestContext();
348         }
349         return idp;
350     }
351     
352     
353     /**
354      * A set of Mockrunner control blocks to call a newly initialized
355      * or previously created IdP. 
356      * 
357      * <p>By default, an IdpTestContext creates a new instance of the
358      * IdP using the current configuration file. However, if an 
359      * already intialized IdPResponder servlet is passed to the
360      * constructor, then new Mockrunner blocks are created but
361      * the existing IdP is reused.</p>
362      * 
363      * <p>The IdP is initialized when the IdpResponder servlet init() is 
364      * called. This establishes the static context of tables that 
365      * allow the IdP to issue a Subject and then respond when that
366      * Subject is returned in an Attribute Query.</p>
367      * 
368      * <p>This class creates the Mockrunner control blocks needed to 
369      * call the IdP and, by creating the IdP Servlet object, also 
370      * initializes an instance of the IdP. It depends on a configuration
371      * file located as a resource in the classpath, typically in the 
372      * /testresources directory of the project.</p>
373      */
374     public class IdpTestContext {
375         
376         
377
378         // The Factory creates the Request, Response, Session, etc.
379         public WebMockObjectFactory factory = new WebMockObjectFactory();
380         
381         // The TestModule runs the Servlet and Filter methods in the simulated container
382         public ServletTestModule testModule = new ServletTestModule(factory);
383         
384         // Now simulated Servlet API objects
385         public MockServletContext servletContext= factory.getMockServletContext();
386         public MockFilterConfig filterConfig= factory.getMockFilterConfig();
387         public MockHttpServletResponse response = factory.getMockResponse();
388         public MockHttpServletRequest request = factory.getMockRequest();
389         
390         
391         // The IdP Servlet that processes SSO, AA, and Artifact requests
392         public IdPResponder idpServlet;
393         
394         /**
395          * Construct new context and new IdP from the configuration file.
396          */
397         public IdpTestContext() {
398             this(null);
399         }
400         
401         /**
402          * Create a new Mockrunner context. If an previous
403          * IdP was initialized in a prior context, reuse it 
404          * and therefore only refresh the Mockrunner objects.
405          * Otherwise, initialize a new instance of the IdP.
406          */
407         public IdpTestContext(IdPResponder oldidp) {
408             
409             // ServletContext
410             servletContext.setServletContextName("dummy IdP Context");
411             servletContext.setInitParameter("IdPConfigFile", idpConfigFileName);
412             
413             if (oldidp==null) {
414                 idpServlet = new IdPResponder();
415                 // NOTE: The IdP reads its configuration file and initializes
416                 // itself within this call.
417                 testModule.setServlet(idpServlet,true);
418             resetLoggingLevels();
419             } else {
420                 // reuse an existing initialized servlet
421                 idpServlet=oldidp;
422             }
423         }
424         
425         
426         /**
427          * Set the fields of the request that depend on a suffix,
428          * normally SSO, AA, or Artifact
429          */
430         public void resetRequest(String suffix) {
431             
432             request.resetAll();
433             response.resetAll();
434             
435             // Unchanging properties of the HttpServletRequest
436             request.setRemoteAddr(REMOTE_ADDR);
437             request.setContextPath(IDP_CONTEXT_PATH);
438             request.setProtocol(PROTOCOL);
439             request.setScheme(SCHEME);
440             request.setServerName(IDP_SERVER_NAME);
441             request.setServerPort(SERVER_PORT);
442             
443             request.setRequestURI(IDP_CONTEXT_URL+suffix);
444             request.setRequestURL(IDP_CONTEXT_URL+suffix);
445             request.setServletPath(IDP_CONTEXT_PATH+"/"+suffix);
446         }
447         
448     }
449
450     
451     
452     
453     
454     /********************** Attribute Source ***********************
455      * Here we keep a static reference to a Collection of Attributes. 
456      * 
457      * The Test can clear the collection and add attributes. When
458      * the IdP needs attributes, it treats this collection as the 
459      * starting point and processes them through ARP. When then get
460      * to the SP they go through AAP. So you can test the Attribute
461      * processing logic in both components by creating Attributes 
462      * with names and values that are accepted or rejected.
463      */
464     
465     public static BasicAttributes attributes = new BasicAttributes();
466     
467     /**
468      * The Test should obtain a reference to the Attribute collection and add
469      * such attributes as it wants the IdP to return for a Principal.
470      * @return Attributes collection
471      */
472     public Attributes getAttributesCollection() {
473         return attributes;
474     }
475     
476     
477     
478     
479     
480     /*************************** Resource Manage Filter *****************
481      * The Filter depends on a Servlet environment simulated by MockRunner.
482      * We give it its own set of MockRunner blocks because in real life
483      * it runs in a separate context from the SP or IdP.
484      * 
485      * The Filter depends on the SP and, once initialized, has a reference
486      * to FilterSupportImpl and through it the SP configuration and Sessions.
487      */
488     private AuthenticationFilterContext filter;
489     public AuthenticationFilterContext getFilter() throws ShibbolethConfigurationException {
490         if (filter==null)
491             filter=new AuthenticationFilterContext();
492         return filter;
493     }
494     
495     /**
496      * Create the MockRunning interface for running the ServletFilter.
497      * 
498      * <p>The AuthenticationFilter object itself contains no 
499      * meaningful state, so you can create multiple instances
500      * of this interface object to represent more than one
501      * Resource context being managed by the same SP.</p>
502      *
503      */
504     public class AuthenticationFilterContext {
505         
506
507         // The Factory creates the Request, Response, Session, etc.
508         public WebMockObjectFactory factory = new WebMockObjectFactory();
509         
510         // The TestModule runs the Servlet and Filter methods in the simulated container
511         public ServletTestModule testModule = new ServletTestModule(factory);
512         
513         // Now simulated Servlet API objects
514         public MockServletContext servletContext= factory.getMockServletContext();
515         public MockFilterConfig filterConfig= factory.getMockFilterConfig();
516         public MockHttpServletResponse response = factory.getMockResponse();
517         public MockHttpServletRequest request = factory.getMockRequest();
518         
519         // Filter objects
520         private AuthenticationFilter filter;
521         
522        public AuthenticationFilterContext() {
523             
524             // Dummy web.xml for Resouce context
525             servletContext.setServletContextName("dummy Servlet Context");
526             
527             // Dummy <Filter> in dummy web.xml
528             MockServletContext filterParameters = new MockServletContext();
529             filterParameters.setInitParameter("requireId", ".+/test.+");
530             filterConfig.setupServletContext(filterParameters);
531             filterConfig.setFilterName("Test Filter under JUnit");
532        }
533        
534        /**
535         * Call after any changes to Context init-param values to
536         * initialize the filter object and connect to the SP.
537         * 
538         * @throws ShibbolethConfigurationException from SP init.
539         */
540        public void setUp() throws ShibbolethConfigurationException {
541             
542             // Create instance of Filter class, add to chain, call its init()
543             filter = new AuthenticationFilter();
544             testModule.addFilter(filter,true);
545             
546             // Note: if the SP is already initialized, this noops.
547             initializeSP();
548             
549
550         }
551         
552         public void resetRequest(String suffix) {
553             
554             request.setRemoteAddr(REMOTE_ADDR);
555             request.setContextPath(RESOURCE_CONTEXT_PATH);
556             request.setProtocol(PROTOCOL);
557             request.setScheme(SCHEME);
558             request.setServerName(SP_SERVER_NAME);
559             request.setServerPort(RESOURCE_SERVER_PORT);
560
561             request.setMethod("GET");
562             request.setRequestURI(RESOURCE_CONTEXT_URL+suffix);
563             request.setRequestURL(RESOURCE_CONTEXT_URL+suffix);
564             request.setServletPath(RESOURCE_CONTEXT_PATH+"/"+suffix);
565             
566         }
567     }
568     
569 }