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