Add SP MockRunner interface class, code an example of its use, improve comments
[java-idp.git] / tests / edu / internet2 / middleware / shibboleth / integration / IntegrationTest.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.integration;
18
19 import java.io.UnsupportedEncodingException;
20 import java.net.URLDecoder;
21 import java.util.Enumeration;
22 import java.util.Map;
23
24 import javax.naming.directory.Attributes;
25 import javax.naming.directory.BasicAttribute;
26 import javax.servlet.http.HttpServletRequest;
27
28 import junit.framework.TestCase;
29
30 import org.apache.commons.codec.binary.Base64;
31 import org.apache.log4j.Level;
32 import org.opensaml.SAMLException;
33
34 import com.mockrunner.mock.web.MockHttpServletResponse;
35
36 import edu.internet2.middleware.shibboleth.idp.provider.ShibbolethV1SSOHandler;
37 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
38 import edu.internet2.middleware.shibboleth.resource.FilterUtil;
39 import edu.internet2.middleware.shibboleth.resource.FilterSupport.NewSessionData;
40 import edu.internet2.middleware.shibboleth.runner.ShibbolethRunner;
41 import edu.internet2.middleware.shibboleth.serviceprovider.AssertionConsumerServlet;
42 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig;
43 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderContext;
44 import edu.internet2.middleware.shibboleth.serviceprovider.Session;
45
46 /**
47  * A JUnit test case that exercises the IdP, SP, and Filter
48  * @author Howard Gilbert
49  */
50 public class IntegrationTest extends TestCase {
51     
52     // Create some constants, both as parameters and to test responses
53     private static final String GIVENNAME = "Bozo";
54     public static final String SURNAME = "Clown";
55     private static final String TITLE = "clown";
56     public static final String AFFILIATION = "member";
57     public static final String SP_ENTITY = "https://sp.example.org/shibboleth";
58     public static final String POST_SHIRE = "https://sp.example.org/shibboleth-sp/Shibboleth.sso/SAML/POST";
59     public static final String ARTIFACT_SHIRE = "https://sp.example.org/shibboleth-sp/Shibboleth.sso/SAML/Artifact";
60     public static final String TARGET = "https://nonsense";
61     public static final String NETID = "BozoTClown";
62     public static final String APPLICATIONID = "default";
63     
64     ShibbolethRunner runner;
65     ShibbolethRunner.IdpTestContext idp;
66     ShibbolethRunner.SPTestContext consumer;
67     ShibbolethRunner.AuthenticationFilterContext filter;
68     private NewSessionData newSessionData = new NewSessionData();
69     ServiceProviderContext context;
70     ServiceProviderConfig config;
71     
72     
73     /**
74      * TestCase setUp
75      */
76     protected void setUp() throws Exception {
77         super.setUp();
78
79         // Static call to set Log4J appenders and levels
80         ShibbolethRunner.loglevel = Level.DEBUG;
81         ShibbolethRunner.setupLogging();
82         
83         // Create the overall testing framework
84         runner = new ShibbolethRunner();
85         
86         // Initialize the Idp, create the Mockrunner
87         // objects to do SSO, AA, and Artifact calls, and
88         // configure SAML to use the MockHTTPBindingProvider
89         runner.setIdpConfigFileName("/basicIdpHome/idpconfig.xml"); // default value
90         idp = runner.getIdp();
91         
92         // Initialize the SP with the default config file.
93         runner.setSpConfigFileName("/basicSpHome/spconfig.xml"); // default value
94         
95         // Use one of two forms to initialize the SP
96         // If only calling AssertionConsumerServlet.createSessionFromData directly
97             //runner.initializeSP(); 
98         // If calling AssertionConsumerServlet through MockRunner
99             consumer = ShibbolethRunner.consumer = runner.new SPTestContext();
100         
101         context=ServiceProviderContext.getInstance();
102         config = context.getServiceProviderConfig();
103         
104         // Initialize the Filter and create its separate
105         // Mockrunner simulated context. 
106         filter= runner.getFilter();
107             // Note: If you are going to change the Filter init-param
108             // values, do it here before calling setUp()
109         filter.setUp();
110   
111         newSessionData.applicationId=APPLICATIONID;
112         newSessionData.providerId=SP_ENTITY;
113           
114         
115         // Create the static collection of Attributes that are 
116         // returned by the IdP for every principal.
117         // This could be done in each test, just as long as it
118         // is done before the SSO.
119         Attributes attributes = runner.getAttributesCollection();
120         attributes.put(new BasicAttribute("eduPersonAffiliation", AFFILIATION));
121         // scoped
122         attributes.put(new BasicAttribute("eduPersonScopedAffiliation", AFFILIATION));
123         attributes.put(new BasicAttribute("title", TITLE));
124         attributes.put(new BasicAttribute("givenName", GIVENNAME));
125         attributes.put(new BasicAttribute("surname", SURNAME));
126         // not in AAP
127         attributes.put(new BasicAttribute("unacceptable","nonsense"));
128         // not in ARP
129         attributes.put(new BasicAttribute("unreleasable","foolishness"));
130     }
131     
132     /**
133      * Test the Post Profile, Attribute Push
134      * <p>Run SSO, call AssertionConsumerServlet directly, then Run Filter</p>
135      */
136     public void testAttributePush() throws SAMLException {
137         
138         // Set the URL suffix that triggers SSO processing
139         idp.setRequestUrls("SSO");
140         
141         // Add the WAYF/RM parameters
142         idp.testModule.addRequestParameter("target", TARGET);
143         idp.testModule.addRequestParameter("shire",POST_SHIRE);
144         idp.testModule.addRequestParameter("providerId", SP_ENTITY);
145         
146         // Add a userid, as if provided by Basic Authentication or a Filter
147         idp.request.setRemoteUser(NETID);
148         
149         // Force Attribute Push
150         ShibbolethV1SSOHandler.pushAttributeDefault=true;
151         
152         // Call the IdP 
153         idp.testModule.doGet();
154         
155             /*
156              * Sanity check: The IdP normally ends by transferring control to a
157              * JSP page that generates the FORM. However, we have not set up
158              * Mockrunner to perform the transfer, because the form would just
159              * create parsing work. Rather, the following code extracts the
160              * information from the request attributes that the JSP would have
161              * used as its source.
162              */
163         String bin64assertion = (String) idp.request.getAttribute("assertion");
164         String assertion = new String(Base64.decodeBase64(bin64assertion.getBytes()));
165         String handlerURL = (String) idp.request.getAttribute("shire");
166         String targetURL = (String) idp.request.getAttribute("target");
167         
168         // Create the session directly without MockRunner
169         FilterUtil.sessionDataFromRequest(newSessionData,idp.request);
170             // there was no real redirect, so the next two fields are not
171             // in the places that sessionDataFromRequest expects.
172             newSessionData.SAMLResponse = bin64assertion;  
173             newSessionData.target=targetURL;
174         newSessionData.handlerURL=handlerURL;
175         
176         // Create the session, extract pushed Attributes 
177         String sessionId = AssertionConsumerServlet.createSessionFromData(newSessionData);
178         
179         // Now get what was created in case you want to test it.
180         Session session = context.getSessionManager().findSession(sessionId, APPLICATIONID);
181         checkSession(session);
182         
183         // Pass the SessionId to the Filter, let it fetch the attributes
184         filter.testModule.addRequestParameter(AuthenticationFilter.SESSIONPARM, sessionId);
185         filter.setRequestUrls("test.txt");
186         filter.testModule.doFilter();
187         
188             /*
189              * Sanity Check: doFilter runs just the Filter itself. On 
190              * input there was a Request and Response. When done, there
191              * will be a replacement Request object created by the Filter
192              * wrapping the original request and adding features.
193              */
194
195         checkFilter();
196     }
197     
198     /**
199      * Verify correct operation of Filter and wrapped Request object,
200      * including attributes and headers.
201      */
202     private void checkFilter() {
203         // Get the Request Wrapper object created by the Filter
204         HttpServletRequest filteredRequest = 
205             (HttpServletRequest) filter.testModule.getFilteredRequest();
206         
207         assertEquals(NETID,filteredRequest.getRemoteUser());
208         assertEquals(NETID,filteredRequest.getHeader("REMOTE_USER"));
209         assertEquals(SURNAME,filteredRequest.getHeader("Shib-Person-surname"));
210         assertEquals(GIVENNAME,filteredRequest.getHeader("Shib-InetOrgPerson-givenName"));
211         assertEquals(TITLE,filteredRequest.getHeader("Shib-OrgPerson-title"));
212         
213         Map attributes = (Map) filteredRequest.getAttribute(AuthenticationFilter.SHIB_ATTRIBUTES_PREFIX);
214         
215         
216         Enumeration headerNames = filteredRequest.getHeaderNames();
217         while (headerNames.hasMoreElements()) {
218             String name = (String) headerNames.nextElement();
219             String value = (String) filteredRequest.getHeader(name);
220             System.out.println(name+ "-"+value );
221         }
222     }
223     
224     /**
225      * Add Session object checking here.
226      */
227     private void checkSession(Session session) {
228         assertNotNull(session);
229         assertEquals(APPLICATIONID,session.getApplicationId());
230         
231         
232         
233     }
234     
235     /**
236      * Test the Post Profile with Attribute Query
237      * <p>Run SSO, Run AssertionConsumerServlet, then Run Filter</p>
238      */
239     public void testAttributeQuery() throws SAMLException {
240         
241         // Set the URL suffix that triggers SSO processing
242         idp.setRequestUrls("SSO");
243         
244         // Add the WAYF/RM parameters
245         idp.testModule.addRequestParameter("target", TARGET);
246         idp.testModule.addRequestParameter("shire",POST_SHIRE);
247         idp.testModule.addRequestParameter("providerId", SP_ENTITY);
248         
249         // Add a userid, as if provided by Basic Authentication or a Filter
250         idp.request.setRemoteUser(NETID);
251         
252         // Block Attribute Push
253         ShibbolethV1SSOHandler.pushAttributeDefault=false;
254         
255         // Call the IdP 
256         idp.testModule.doGet();
257         
258         String bin64assertion = (String) idp.request.getAttribute("assertion");
259         String assertion = new String(Base64.decodeBase64(bin64assertion.getBytes()));
260         String handlerURL = (String) idp.request.getAttribute("shire");
261         String targetURL = (String) idp.request.getAttribute("target");
262
263         // Simulate the POST to the SP Context using MockRunner
264         consumer.testModule.addRequestParameter("SAMLResponse",bin64assertion);
265         consumer.testModule.addRequestParameter("TARGET",targetURL);
266         consumer.setRequestUrls("Shibboleth.sso/SAML/POST");
267         consumer.testModule.doPost();
268         
269         // Now check up on what the AssertionConsumerServlet did with the POST
270         MockHttpServletResponse response = consumer.response;
271         assertTrue(response.wasRedirectSent());
272         String redirectURL = response.getHeader("Location");
273         
274         // The SessionId is on the end of the redirected URL
275         int pos = redirectURL.indexOf(AssertionConsumerServlet.SESSIONPARM);
276         assertTrue(pos>0);
277         String sessionId = redirectURL.substring(
278                 pos+AssertionConsumerServlet.SESSIONPARM.length()+1);
279         
280         // Now get what was created in case you want to test it.
281         Session session = context.getSessionManager().findSession(sessionId, APPLICATIONID);
282         checkSession(session);
283         
284         // Pass the SessionId to the Filter, let it fetch the attributes
285         filter.testModule.addRequestParameter(AuthenticationFilter.SESSIONPARM, sessionId);
286         filter.setRequestUrls("test.txt"); // need any URL
287         filter.testModule.doFilter();
288         
289         checkFilter();
290         
291     }
292     
293     /**
294      * Test Artifact
295      * <p>Run SSO, call AssertionConsumerServlet directly, then Run Filter</p>
296      */
297     public void testArtifact() throws SAMLException, UnsupportedEncodingException {
298         
299         // Set the URL suffix that triggers SSO processing
300         idp.setRequestUrls("SSO");
301         
302         // Add the WAYF/RM parameters
303         idp.testModule.addRequestParameter("target", TARGET);
304         idp.testModule.addRequestParameter("shire",ARTIFACT_SHIRE);
305         idp.testModule.addRequestParameter("providerId", SP_ENTITY);
306         
307         // Add a userid, as if provided by Basic Authentication or a Filter
308         idp.request.setRemoteUser(NETID);
309         
310         // Attribute Push is implied by Artifact
311         ShibbolethV1SSOHandler.pushAttributeDefault=false;
312         
313         // Call the IdP 
314         idp.testModule.doGet();
315         
316         // Now check the response from the IdP
317         MockHttpServletResponse response = idp.response;
318         assertTrue(response.wasRedirectSent());
319         String redirectURL = response.getHeader("Location");
320         
321         // The artifacts were appended to the end of the Redirect URL
322         String[] splits = redirectURL.split("\\&SAMLart=");
323         assertTrue(splits.length>1);
324         String[] artifactArray = new String[splits.length-1];
325         for (int i=0;i<artifactArray.length;i++) {
326             artifactArray[i]=URLDecoder.decode(splits[i+1],"UTF-8");
327         }
328         
329         // Build the parameter for Session creation
330         FilterUtil.sessionDataFromRequest(newSessionData,idp.request);
331         newSessionData.SAMLArt=artifactArray;
332         newSessionData.target=TARGET;
333         newSessionData.handlerURL=ARTIFACT_SHIRE;
334         
335         // Create the Session
336         // Under the covers, SAML will see the Artifact and fetch the Assertion
337         String sessionId = AssertionConsumerServlet.createSessionFromData(newSessionData);
338         
339         
340         // Now get what was created in case you want to test it.
341         Session session = context.getSessionManager().findSession(sessionId, APPLICATIONID);
342         checkSession(session);
343         
344         
345         // Pass the SessionId to the Filter, let it fetch the attributes
346         filter.testModule.addRequestParameter(AuthenticationFilter.SESSIONPARM, sessionId);
347         filter.setRequestUrls("test.txt"); // need any URL
348         filter.testModule.doFilter();
349
350         checkFilter();
351      }
352     
353 }