Validate Signatures
[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.File;
45 import java.io.IOException;
46
47 import javax.servlet.ServletContext;
48 import javax.servlet.ServletException;
49 import javax.servlet.ServletOutputStream;
50 import javax.servlet.http.Cookie;
51 import javax.servlet.http.HttpServlet;
52 import javax.servlet.http.HttpServletRequest;
53 import javax.servlet.http.HttpServletResponse;
54
55 import org.opensaml.SAMLException;
56 import org.opensaml.SAMLBrowserProfile;
57 import org.opensaml.SAMLResponse;
58 import org.opensaml.XML;
59 import org.opensaml.SAMLBrowserProfile.BrowserProfileResponse;
60 import org.w3c.dom.Element;
61 import org.apache.log4j.FileAppender;
62 import org.apache.log4j.Layout;
63 import org.apache.log4j.Level;
64 import org.apache.log4j.Logger;
65 import org.apache.log4j.PatternLayout;
66 import org.apache.xml.security.Init;
67
68 import x0.maceShibbolethTargetConfig1.SessionsDocument.Sessions;
69
70 import edu.internet2.middleware.commons.log4j.ThreadLocalAppender;
71 import edu.internet2.middleware.shibboleth.common.Credentials;
72 import edu.internet2.middleware.shibboleth.common.ShibBrowserProfile;
73 import edu.internet2.middleware.shibboleth.metadata.MetadataException;
74 import edu.internet2.middleware.shibboleth.resource.AuthenticationFilter;
75 import edu.internet2.middleware.shibboleth.serviceprovider.ServiceProviderConfig.ApplicationInfo;
76
77 /**
78  * AuthenticatonAssertionConsumerServlet
79  * 
80  * @author Howard Gilbert
81  */
82 public class AuthenticationAssertionConsumerServlet extends HttpServlet {
83
84         private static Logger log = null;
85         
86         private static ServiceProviderContext context = ServiceProviderContext.getInstance();
87         
88         private Element                 configuration = null;
89         private Credentials             credentials = null;
90         
91         public static final String SESSIONPARM =
92             "ShibbolethSessionId";
93         
94         
95         public void init() throws ServletException {
96                 super.init();
97                 ServletContext servletContext = this.getServletContext();
98                 
99                 Init.init();
100
101                 // Initialize logging specially
102                 Logger targetLogger = Logger.getLogger("edu.internet2.middleware");
103                 Logger samlLogger = Logger.getLogger("org.opensaml");
104                 File diagdir = new File(servletContext.getRealPath("/diagnose"));
105                 diagdir.mkdirs();
106                 String logname = servletContext.getRealPath("/diagnose/initialize.log");
107                 Layout initLayout = new PatternLayout("%d{HH:mm} %-5p %m%n");
108                 
109                 try {
110             FileAppender initLogAppender = new FileAppender(initLayout,logname);
111             ThreadLocalAppender threadAppender = new ThreadLocalAppender();
112             threadAppender.setLayout(initLayout);
113             targetLogger.setAdditivity(false);
114             targetLogger.addAppender(initLogAppender);
115             targetLogger.addAppender(threadAppender);
116             targetLogger.setLevel(Level.DEBUG);
117             samlLogger.addAppender(threadAppender);
118             samlLogger.setLevel(Level.DEBUG);
119         } catch (IOException e) {
120             e.printStackTrace();
121         }
122                 
123 /*              ConsoleAppender rootAppender = new ConsoleAppender();
124                 rootAppender.setWriter(new PrintWriter(System.out));
125                 rootAppender.setName("stdout");
126                 targetLogger.addAppender(rootAppender);
127
128                 // rootAppender.setLayout(new PatternLayout("%-5p %-41X{serviceId} %d{ISO8601} (%c:%L) - %m%n"));
129                 // Logger.getRootLogger().setLevel((Level) Level.DEBUG);
130                 Logger.getRootLogger().setLevel((Level) Level.INFO);
131                 rootAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p %-41X{serviceId} - %m%n"));
132 */
133                 log = Logger.getLogger(AuthenticationAssertionConsumerServlet.class.getName());
134                 
135                 ServletContextInitializer.initServiceProvider(servletContext);
136                 AuthenticationFilter.setFilterSupport(new FilterSupportImpl());
137         }
138
139
140
141         /**
142          * Accept the SAML Assertion post from the HS.
143          * 
144          * @param request the request send by the client to the server
145          * @param response the response send by the server to the client
146          * @throws ServletException if an error occurred
147          * @throws IOException if an error occurred
148          */
149         public void doPost(
150                 HttpServletRequest request,
151                 HttpServletResponse response)
152                 // throws ServletException, IOException 
153                 {
154             ServletContextInitializer.beginService(request,response);
155                 try {
156             ServiceProviderConfig config = context.getServiceProviderConfig();
157             
158             String ipaddr = request.getRemoteAddr();
159             
160             // URL of Resource that triggered authorization
161             // XXX: I added support to the profile for extracting TARGET, but
162             // it's not too critical in Java since you can grab it easily anyway.
163             // Might be better in the 2.0 future though, since the bindings get trickier.
164             String target = request.getParameter("TARGET");
165             
166             // Map the Resource URL into an <Application>
167             String applicationId = config.mapRequest(target);
168             ApplicationInfo appinfo = config.getApplication(applicationId);
169             Sessions appSessionValues = appinfo.getApplicationConfig().getSessions();
170             String shireURL = appSessionValues.getShireURL();
171             String providerId = appinfo.getApplicationConfig().getProviderId();
172             
173            
174             if (appSessionValues.getShireSSL()&& // Requires SSL
175                         !request.isSecure()) {       // isn't SSL
176                 log.error("Authentication Assersion not posted over SSL.");
177                 try {
178                     response.sendRedirect("/shibboleth/shireError.html");
179                 } catch (IOException e1) {
180                 }
181                 return;
182             }
183             
184             log.debug("Authentication received from "+ipaddr+" for "+target+
185                         "(application:"+applicationId+") (Provider:"+providerId+")");
186
187             String sessionId = createSessionFromPost(ipaddr, request, applicationId, shireURL, providerId, null);
188             
189             Cookie cookie = new Cookie("ShibbolethSPSession",sessionId);
190             response.addCookie(cookie);
191             
192             try {
193                                 if (target.equals("SendAttributesBackToMe")) {
194                                         ServletOutputStream outputStream = response.getOutputStream();
195                                         response.setContentType("text/xml");
196                                         Session session = context.getSessionManager().findSession(sessionId,applicationId);
197                                         SAMLResponse attributeResponse = session.getAttributeResponse();
198                                         outputStream.print(attributeResponse.toString());
199                                 } else {
200                                         response.sendRedirect(target+"?"+SESSIONPARM+"="+sessionId);
201                                 }
202             }
203             catch (IOException e) {
204             }
205         }
206         catch (MetadataException e) {
207             log.error("Authentication Assertion source not found in Metadata.");
208             try {
209                 response.sendRedirect("/shibboleth/shireError.html");
210             }
211             catch (IOException e1) {
212             }
213         }
214         catch (SAMLException e) {
215             log.error("Authentication Assertion had invalid format.");
216             try {
217                 response.sendRedirect("/shibboleth/shireError.html");
218             }
219             catch (IOException e1) {
220             }
221         }
222         finally {
223             ServletContextInitializer.finishService(request,response);
224         }
225         }
226         
227     /**
228      * Create a Session object from SHIRE POST data
229      * 
230      * @param ipaddr IP Address of Browser
231      * @param bin64Assertion Authentication assertion from POST
232      * @param applicationId from RequestMap
233      * @param shireURL 
234      * @param providerId Our Entity name
235      * @return random key of Session
236      * @throws SAMLException
237      */
238     public static 
239     String createSessionFromPost(
240             String ipaddr, 
241             HttpServletRequest req, 
242             String applicationId, 
243             String shireURL, 
244             String providerId,
245             String emptySessionId
246             ) 
247     throws SAMLException {
248         String sessionid=null;
249         StringBuffer pproviderId = // Get back Origin Entity name from SAML
250             new StringBuffer();
251         String[] audiences = new String[1];
252         audiences[0]=providerId;
253         
254         ShibBrowserProfile profile = new ShibBrowserProfile(applicationId);
255         BrowserProfileResponse samldata = profile.receive(
256                 pproviderId,
257                 req,
258                 shireURL,   // My URL (Why??) To prevent attackers from redirecting messages. 
259                 SAMLBrowserProfile.PROFILE_POST,    // TODO: support both profiles 
260                 context.getReplayCache(),
261                 null
262         );
263         
264         // TODO: Audience/condition checking is now the profile caller's job.
265         
266         // The Authentication Assertion gets placed in a newly created
267         // Session object. Later, someone will get an Attribute Assertion
268         // and add it to the Session. The SessionID key is returned to
269         // the Browser as a Cookie.
270         SessionManager sessionManager = context.getSessionManager();
271         sessionid = sessionManager.newSession(
272                 applicationId, 
273                 ipaddr, 
274                 pproviderId.toString(), 
275                 samldata.assertion, 
276                 samldata.authnStatement,
277                 emptySessionId);
278         
279         // Very agressive attribute fetch rule 
280         // Get the Attributes immediately! [good for debugging]
281         Session session = sessionManager.findSession(sessionid, applicationId);
282         AttributeRequestor.fetchAttributes(session);
283
284         return sessionid;
285     }
286
287
288
289     protected void doGet(HttpServletRequest arg0, HttpServletResponse arg1)
290         throws ServletException, IOException {
291         // TODO Auto-generated method stub
292         super.doGet(arg0, arg1);
293     }
294         
295
296 }