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