Add SAML to thead log, fix error page url
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / serviceprovider / AuthenticationAssertionConsumerServlet.java
1 /*
2  * AuthenticatonAssertionConsumerServlet
3  * 
4  * The Shibboleth function previous known as the SHIRE.
5  * 
6  * Authentication Assertion Consumer is the SAML 2.0 term for what the
7  * SHIRE does. A SAML Assertion containing an Authentication statement
8  * with the "principal" identifier value equal to the handle vended by
9  * the Handle Server is received from the Browser. The Handle Server
10  * generated a form, prefilled it with a Bin64 encoding of the SAML
11  * statement, and included Javascript to automatically submit the form
12  * to this URL.
13  * 
14  * All HTTP, HTML, and servlet logic is localized to this layer of
15  * modules. Any information must be extracted from the Servlet API
16  * to be passed directly to Shibboleth.
17  * 
18  * The work is done by a ShibPOSTProfile object. It takes the Bin64
19  * encoded string, converts it to a SAMLObject, verifies structure,
20  * and validates signatures.
21  * 
22  * The resulting Authentication Assertion SAML statement is passed
23  * to Session Manager to create a new session. This process feeds
24  * back a session identifier that becomes the value of a Cookie sent
25  * back to the Browser to track the session.
26  * 
27  * If the decision is made to fetch attributes immediately, the 
28  * Session object can be passed to the static AttributeRequestor
29  * service. It generates a ShibBinding, sends a request to the AA,
30  * validates the response, applies AAP, and stores the resulting 
31  * SAML Attribute Assertion in the Session object.
32  * 
33  * --------------------
34  * Copyright 2002, 2004 
35  * University Corporation for Advanced Internet Development, Inc. 
36  * All rights reserved
37  * [Thats all we have to say to protect ourselves]
38  * Your permission to use this code is governed by "The Shibboleth License".
39  * A copy may be found at http://shibboleth.internet2.edu/license.html
40  * [Nothing in copyright law requires license text in every file.]
41  */
42 package edu.internet2.middleware.shibboleth.serviceprovider;
43
44 import java.io.IOException;
45 import java.util.Collections;
46
47 import javax.servlet.ServletContext;
48 import javax.servlet.ServletException;
49 import javax.servlet.http.Cookie;
50 import javax.servlet.http.HttpServlet;
51 import javax.servlet.http.HttpServletRequest;
52 import javax.servlet.http.HttpServletResponse;
53 import org.opensaml.SAMLAssertion;
54 import org.opensaml.SAMLAuthenticationStatement;
55 import org.opensaml.SAMLException;
56 import org.opensaml.SAMLPOSTProfile;
57 import org.opensaml.SAMLResponse;
58 import org.w3c.dom.Element;
59 import org.apache.log4j.FileAppender;
60 import org.apache.log4j.Layout;
61 import org.apache.log4j.Level;
62 import org.apache.log4j.Logger;
63 import org.apache.log4j.PatternLayout;
64 import org.apache.xml.security.Init;
65
66 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
67
68 import edu.internet2.middleware.commons.log4j.ThreadLocalAppender;
69 import edu.internet2.middleware.shibboleth.common.Credentials;
70 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
71 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
72 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
73
74 /**
75  * AuthenticatonAssertionConsumerServlet
76  * 
77  * @author Howard Gilbert
78  */
79 public class AuthenticationAssertionConsumerServlet extends HttpServlet {
80
81         private static Logger log = null;
82         
83         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
84         
85         private Element                 configuration;
86         private Credentials                             credentials;
87         
88         public static final String SESSIONPARM =
89             "ShibbolethSessionId";
90         
91         
92         public void init() throws ServletException {
93                 super.init();
94                 ServletContext servletContext = this.getServletContext();
95                 
96                 Init.init();
97
98                 // Initialize logging specially
99                 Logger targetLogger = Logger.getLogger("edu.internet2.middleware");
100                 Logger samlLogger = Logger.getLogger("org.opensaml");
101                 String logname = servletContext.getRealPath("/diagnose/initialize.log");
102                 Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
103                 
104                 try {
105             FileAppender initLogAppender = new FileAppender(initLayout,logname);
106             ThreadLocalAppender threadAppender = new ThreadLocalAppender();
107             threadAppender.setLayout(initLayout);
108             targetLogger.setAdditivity(false);
109             targetLogger.addAppender(initLogAppender);
110             targetLogger.addAppender(threadAppender);
111             targetLogger.setLevel(Level.DEBUG);
112             samlLogger.addAppender(threadAppender);
113             samlLogger.setLevel(Level.DEBUG);
114         } catch (IOException e) {
115             e.printStackTrace();
116         }
117                 
118 /*              ConsoleAppender rootAppender = new ConsoleAppender();
119                 rootAppender.setWriter(new PrintWriter(System.out));
120                 rootAppender.setName("stdout");
121                 targetLogger.addAppender(rootAppender);
122
123                 // rootAppender.setLayout(new PatternLayout("%-5p %-41X{serviceId} %d{ISO8601} (%c:%L) - %m%n"));
124                 // Logger.getRootLogger().setLevel((Level) Level.DEBUG);
125                 Logger.getRootLogger().setLevel((Level) Level.INFO);
126                 rootAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p %-41X{serviceId} - %m%n"));
127 */
128                 log = Logger.getLogger(AuthenticationAssertionConsumerServlet.class.getName());
129                 
130                 ServletContextInitializer.initServiceProvider(servletContext);
131                 AuthenticationFilter.setFilterSupport(new FilterSupportImpl());
132                 
133         }
134
135
136
137         /**
138          * Accept the SAML Assertion post from the HS.
139          * 
140          * @param request the request send by the client to the server
141          * @param response the response send by the server to the client
142          * @throws ServletException if an error occurred
143          * @throws IOException if an error occurred
144          */
145         public void doPost(
146                 HttpServletRequest request,
147                 HttpServletResponse response)
148                 // throws ServletException, IOException 
149                 {
150             ServletContextInitializer.beginService(request,response);
151                 try {
152             ServiceProviderConfig config = context.getServiceProviderConfig();
153             
154             String ipaddr = request.getRemoteAddr();
155             
156             // URL of Resource that triggered authorization
157             String target = request.getParameter("TARGET");
158             
159             // Bin64 encoded SAML Authentication Assertion from HS
160             String assertparm = request.getParameter("SAMLResponse");
161             byte [] bin64Assertion = assertparm.getBytes();
162             
163             // Map the Resource URL into an <Application>
164             String applicationId = config.mapRequest(target);
165             ApplicationInfo appinfo = config.getApplication(applicationId);
166             Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
167             
168             // Sanity check:
169             // I am the SHIRE. So the SHIRE URL should be the one in the 
170             // HttpRequest. However, it might have been stepped on by a filter
171             // or frontend. This is the configured cannonical URL that was 
172             // passed to the filter, sent to the HS, and used by the browser
173             // in the redirect. If I need (for whatever reason) to pass a 
174             // Shire URL to the POST processing, lets use the configured one
175             String shireURL = appSessionValues.getShireURL();
176             
177             // Provider ID of me, the Service Provider, for this application
178             String providerId = appinfo.getApplicationConfig().getProviderId();
179             String[] audiences = new String[1];
180             audiences[0]=providerId;
181             
182             if (appSessionValues.getShireSSL()&& // Requires SSL
183                         !request.isSecure()) {       // isn't SSL
184                 log.error("Authentication Assersion not posted over SSL.");
185                 response.sendRedirect("/shibboleth/shireError.html");
186             }
187             
188             log.debug("Authentication received from "+ipaddr+" for "+target+
189                         "(application:"+applicationId+") (Provider:"+providerId+")");
190             
191             // Unfortunately, the previous mix of Java and C had about 100 things
192             // called "providers". In this particular case, we are passing to 
193             // the POST processing layer an empty StringBuffer into which will be
194             // placed a second return value (tricky!). This will be the ID of the
195             // Origin. 
196             StringBuffer pproviderId = new StringBuffer();
197             
198             SAMLResponse samldata = null;       
199             SAMLAssertion assertion = null;
200             SAMLAuthenticationStatement authstmt = null;
201             try { 
202                 ShibPOSTProfile profile = new ShibPOSTProfile(applicationId);
203                 samldata = profile.accept(
204                         bin64Assertion, // Assertion from POST of Form field
205                         shireURL,   // My URL (Why??)
206                         60, 
207                         audiences,  // My "Provider" (Entity) ID
208                         pproviderId // HS "Provider" (Entity) ID returned
209                         );
210                 
211                 assertion = SAMLPOSTProfile.getSSOAssertion(samldata,
212                         Collections.singleton(providerId));
213                 authstmt = SAMLPOSTProfile.getSSOStatement(assertion);
214                 
215             } catch (SAMLException e) {
216                 log.error("Authentication Assertion had invalid format.");
217                 response.sendRedirect("/shibboleth/shireError.html");
218                 return;
219             }
220             catch (MetadataException e) {
221                 log.error("Authentication Assertion source not found in Metadata.");
222                 response.sendRedirect("/shibboleth/shireError.html");
223                 return;
224             }
225
226             
227             // The Authentication Assertion gets placed in a newly created
228             // Session object. Later, someone will get an Attribute Assertion
229             // and add it to the Session. The SessionID key is returned to
230             // the Browser as a Cookie.
231             SessionManager sessionManager = context.getSessionManager();
232             String sessionid = sessionManager.newSession(
233                     applicationId, ipaddr, pproviderId.toString(), assertion, authstmt);
234             Cookie cookie = new Cookie("ShibbolethSPSession",sessionid);
235             response.addCookie(cookie);
236             
237             // Very agressive attribute fetch rule 
238             // Get the Attributes immediately! [good for debugging]
239             Session session = sessionManager.findSession(sessionid, applicationId);
240             boolean gotattributes = AttributeRequestor.fetchAttributes(session);
241             if (!gotattributes)
242                 response.sendRedirect("/shibboleth/shireError.html");
243             
244             log.debug(SessionManager.dumpAttributes(session));
245             
246             response.sendRedirect(target+"?"+SESSIONPARM+"="+sessionid);
247         } catch (IOException e) {
248             // A sendRedirect() failed. 
249             // This can only happen if the user closed the Browser.
250             // Nothing to do
251         } finally {
252             ServletContextInitializer.finishService(request,response);
253         }
254
255         }
256         
257     protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1)
258         throws ServletException, IOException {
259         // TODO Auto-generated method stub
260         super.doGet(arg0, arg1);
261     }
262         
263
264 }