Added override for SHIRE location.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / shire / ShireServlet.java
1 /* 
2  * The Shibboleth License, Version 1. 
3  * Copyright (c) 2002 
4  * University Corporation for Advanced Internet Development, Inc. 
5  * All rights reserved
6  * 
7  * 
8  * Redistribution and use in source and binary forms, with or without 
9  * modification, are permitted provided that the following conditions are met:
10  * 
11  * Redistributions of source code must retain the above copyright notice, this 
12  * list of conditions and the following disclaimer.
13  * 
14  * Redistributions in binary form must reproduce the above copyright notice, 
15  * this list of conditions and the following disclaimer in the documentation 
16  * and/or other materials provided with the distribution, if any, must include 
17  * the following acknowledgment: "This product includes software developed by 
18  * the University Corporation for Advanced Internet Development 
19  * <http://www.ucaid.edu>Internet2 Project. Alternately, this acknowledegement 
20  * may appear in the software itself, if and wherever such third-party 
21  * acknowledgments normally appear.
22  * 
23  * Neither the name of Shibboleth nor the names of its contributors, nor 
24  * Internet2, nor the University Corporation for Advanced Internet Development, 
25  * Inc., nor UCAID may be used to endorse or promote products derived from this 
26  * software without specific prior written permission. For written permission, 
27  * please contact shibboleth@shibboleth.org
28  * 
29  * Products derived from this software may not be called Shibboleth, Internet2, 
30  * UCAID, or the University Corporation for Advanced Internet Development, nor 
31  * may Shibboleth appear in their name, without prior written permission of the 
32  * University Corporation for Advanced Internet Development.
33  * 
34  * 
35  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
36  * AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
37  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
38  * PARTICULAR PURPOSE, AND NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK 
39  * OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS WITH LICENSEE. 
40  * IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY 
41  * CORPORATION FOR ADVANCED INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, 
42  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
43  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
44  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
47  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  */
49
50 package edu.internet2.middleware.shibboleth.shire;
51
52 import edu.internet2.middleware.shibboleth.common.*;
53
54 import java.io.*;
55 import java.security.Key;
56 import java.security.KeyStore;
57 import java.security.cert.Certificate;
58 import java.text.*;
59 import javax.servlet.*;
60 import javax.servlet.http.*;
61 import org.doomdark.uuid.*;
62 import org.opensaml.*;
63
64 /**
65  *  Implements a SAML POST profile consumer
66  *
67  * @author     Scott Cantor
68  * @created    June 10, 2002
69  */
70 public class ShireServlet extends HttpServlet
71 {
72     private String shireLocation = null;
73     private String cookieName = null;
74     private String sessionDir = null;
75     private boolean sslOnly = true;
76     private boolean checkAddress = true;
77     private boolean verbose = false;
78     private XMLOriginSiteMapper mapper = null;
79
80     private static void HTMLFormat(PrintWriter pw, String buf)
81     {
82         for (int i = 0; i < buf.length(); i++)
83         {
84             if (buf.charAt(i) == '<')
85                 pw.write("&lt;");
86             else if (buf.charAt(i) == '>')
87                 pw.write("&gt;");
88             else if (buf.charAt(i) == '&')
89                 pw.write("&amp;");
90             else
91                 pw.write(buf.charAt(i));
92         }
93     }
94
95     /**
96      *  Use the following servlet init parameters:<P>
97      *
98      *
99      *  <DL>
100      *    <DT> shire-location <I>(optional)</I> </DT>
101      *    <DD> The URL of the SHIRE if not derivable from requests</DD>
102      *    <DT> keystore-path <I>(required)</I> </DT>
103      *    <DD> A pathname to the trusted CA roots to accept</DD>
104      *    <DT> keystore-password <I>(required)</I> </DT>
105      *    <DD> The root keystore password</DD>
106      *    <DT> registry-alias <I>(optional)</I> </DT>
107      *    <DD> An alias in the provided keystore for the cert that can verify
108      *    the origin site registry signature</DD>
109      *    <DT> registry-uri <I>(required)</I> </DT>
110      *    <DD> The origin site registry URI to install</DD>
111      *    <DT> cookie-name <I>(required)</I> </DT>
112      *    <DD> Name of session cookie to set in browser</DD>
113      *    <DT> ssl-only <I>(defaults to true)</I> </DT>
114      *    <DD> If true, allow only SSL-protected POSTs and issue a secure cookie
115      *    </DD>
116      *    <DT> check-address <I>(defaults to true)</I> </DT>
117      *    <DD> If true, check client's IP address against assertion</DD>
118      *    <DT> session-dir <I>(defaults to /tmp)</I> </DT>
119      *    <DD> Directory in which to place session files</DD>
120      *    <DT> verbose <I>(defaults to false)</I> </DT>
121      *    <DD> Verbosity of redirection response</DD>
122      *  </DL>
123      *
124      *
125      * @exception  ServletException  Raised if the servlet cannot be initialized
126      */
127     public void init()
128         throws ServletException
129     {
130         edu.internet2.middleware.shibboleth.common.Init.init();
131
132         ServletConfig conf = getServletConfig();
133
134         shireLocation = conf.getInitParameter("shire-location");
135
136         cookieName = conf.getInitParameter("cookie-name");
137         if (cookieName == null)
138             throw new ServletException("ShireServlet.init() missing init parameter: cookie-name");
139
140         sessionDir = conf.getInitParameter("session-dir");
141         if (sessionDir == null)
142             sessionDir = "/tmp";
143
144         String temp = conf.getInitParameter("ssl-only");
145         if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
146             sslOnly = false;
147
148         temp = conf.getInitParameter("check-address");
149         if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
150             checkAddress = false;
151
152         temp = conf.getInitParameter("verbose");
153         if (temp != null && (temp.equalsIgnoreCase("true") || temp.equals("1")))
154             verbose = true;
155
156         try
157         {
158             Key k = null;
159             KeyStore ks = KeyStore.getInstance("JKS");
160             ks.load(conf.getServletContext().getResourceAsStream(conf.getInitParameter("keystore-path")),
161                     conf.getInitParameter("keystore-password").toCharArray());
162             if (conf.getInitParameter("keystore-alias") != null)
163             {
164                 Certificate cert = ks.getCertificate(conf.getInitParameter("keystore-alias"));
165                 if (cert == null || (k = cert.getPublicKey()) == null)
166                     throw new ServletException("ShireServlet.init() unable to find registry verification certificate/key");
167             }
168             mapper = new XMLOriginSiteMapper(conf.getInitParameter("registry-uri"), k, ks);
169         }
170         catch (java.security.KeyStoreException e)
171         {
172             throw new ServletException("ShireServlet.init() unable to load Java keystore");
173         }
174         catch (java.security.NoSuchAlgorithmException e)
175         {
176             throw new ServletException("ShireServlet.init() unable to load Java keystore");
177         }
178         catch (java.security.cert.CertificateException e)
179         {
180             throw new ServletException("ShireServlet.init() unable to load Java keystore");
181         }
182         catch (FileNotFoundException e)
183         {
184             throw new ServletException("ShireServlet.init() unable to locate Java keystore");
185         }
186         catch (IOException e)
187         {
188             throw new ServletException("ShireServlet.init() unable to load Java keystore");
189         }
190         catch (Exception e)
191         {
192             throw new ServletException("ShireServlet.init() unable to load origin site registry: " + e.getMessage());
193         }
194     }
195
196     /**
197      *  Processes a sign-on submission<P>
198      *
199      *
200      *
201      * @param  request               HTTP request context
202      * @param  response              HTTP response context
203      * @exception  IOException       Thrown if an I/O error occurs
204      * @exception  ServletException  Thrown if a servlet engine error occurs
205      */
206     public void doPost(HttpServletRequest request, HttpServletResponse response)
207         throws IOException, ServletException
208     {
209         // Output page opener.
210         response.setContentType("text/html");
211         PrintWriter out = response.getWriter();
212         out.println("<HTML>");
213         out.println("<HEAD>");
214         out.println("<TITLE>Shibboleth Session Establisher</TITLE>");
215         out.println("</HEAD>");
216         out.println("<BODY>");
217         out.println("<H3>Shibboleth Session Establisher</H3>");
218
219         if (sslOnly && !request.isSecure())
220         {
221             out.println("<H4>There was a problem with this submission.</H4>");
222             out.println("<P>Access to this site requires the use of SSL. To correct the problem, please re-enter the desired target URL into your browser and make sure it begins with 'https'.</P>");
223             out.println("</BODY></HTML>");
224             return;
225         }
226
227         String target = request.getParameter("TARGET");
228         if (target == null || target.length() == 0)
229         {
230             out.println("<H4>There was a problem with this submission.</H4>");
231             out.println("<P>The target location was unspecified. To correct the problem, please re-enter the desired target URL into your browser.</P>");
232             out.println("</BODY></HTML>");
233             return;
234         }
235         else if (verbose)
236             out.println("<P><B>Target URL:</B>" + target + "</P>");
237
238         String responseData = request.getParameter("SAMLResponse");
239         if (responseData == null || responseData.length() == 0)
240         {
241             out.println("<H4>There was a problem with this submission.</H4>");
242             out.println("<P>The assertion of your Shibboleth identity was missing. To correct the problem, please re-enter the desired target URL into your browser.</P>");
243             out.println("</BODY></HTML>");
244             return;
245         }
246
247         // Process the SAML response inside an exception handler.
248         try
249         {
250             // Get a profile object using our specifics.
251             String[] policies = {Constants.POLICY_CLUBSHIB};
252             ShibPOSTProfile profile =
253                 ShibPOSTProfileFactory.getInstance(policies, mapper,
254                     (shireLocation!=null) ? shireLocation : HttpUtils.getRequestURL(request).toString(),
255                     300);
256
257             // Try and accept the response...
258             SAMLResponse r = profile.accept(responseData.getBytes());
259
260             // We've got a valid signed response we can trust (or the whole response was empty...)
261             if (verbose)
262             {
263                 ByteArrayOutputStream bytestr = new ByteArrayOutputStream();
264                 r.toStream(bytestr);
265                 out.println("<P><B>Parsed SAML Response:</B></P>");
266                 out.println("<P>");
267                 HTMLFormat(out, bytestr.toString(response.getCharacterEncoding()));
268                 out.println("</P>");
269             }
270
271             // Get the statement we need.
272             SAMLAuthenticationStatement s = profile.getSSOStatement(r);
273             if (s == null)
274             {
275                 out.println("<H4>There was a problem with this submission.</H4>");
276                 out.println("<P>The assertion of your Shibboleth identity was missing or incompatible with the policies of this site. To correct the problem, please re-enter the desired target URL into your browser. If the problem persists, please contact the technical staff at your site.</P>");
277                 out.println("</BODY></HTML>");
278                 return;
279             }
280
281             if (checkAddress)
282             {
283                 if (verbose)
284                     out.println("<P><B>Client Address:</B>" + request.getRemoteAddr() + "</P>");
285                 if (s.getSubjectIP() == null || !s.getSubjectIP().equals(request.getRemoteAddr()))
286                 {
287                     if (verbose && s.getSubjectIP() != null)
288                         out.println("<P><B>Supplied Client Address:</B>" + s.getSubjectIP() + "</P>");
289                     out.println("<H4>There was a problem with this submission.</H4>");
290                     out.println("<P>The IP address provided by your origin site was either missing or did not match your current address. To correct this problem, you may need to bypass a local proxy server and/or contact your origin site technical staff.</P>");
291                     out.println("</BODY></HTML>");
292                 }
293             }
294
295             // All we really need is here...
296             String handle = s.getSubject().getName();
297             String domain = s.getSubject().getNameQualifier();
298             SAMLAuthorityBinding[] bindings = s.getBindings();
299
300             if (verbose)
301             {
302                 out.println("<P><B>Shibboleth Origin Site:</B>" + domain + "</P>");
303                 out.println("<P><B>Shibboleth Handle:</B>" + handle + "</P>");
304                 if (bindings != null)
305                     out.println("<P><B>Shibboleth AA URL:</B>" + bindings[0].getLocation() + "</P>");
306             }
307
308             // Generate a random session file.
309             String filename = UUIDGenerator.getInstance().generateRandomBasedUUID().toString();
310             String pathname = null;
311             if (sessionDir.endsWith(File.separator))
312                 pathname = sessionDir + filename;
313             else
314                 pathname = sessionDir + File.separatorChar + filename;
315             PrintWriter fout = new PrintWriter(new FileWriter(pathname));
316
317             if (verbose)
318                 out.println("<P><B>Session Pathname:</B>" + pathname + "</P>");
319
320             fout.println("Handle=" + handle);
321             fout.println("Domain=" + domain);
322             fout.println("PBinding0=" + bindings[0].getBinding());
323             fout.println("LBinding0=" + bindings[0].getLocation());
324             fout.println("Time=" + System.currentTimeMillis()/1000);
325             fout.println("ClientAddress=" + request.getRemoteAddr());
326             fout.println("EOF");
327             fout.close();
328
329             out.println("<P>Redirecting you to your target...</P>");
330             out.println("<P>Allow 10-15 seconds, then click <A HREF='" + target + "'>here</A> if you do not get redirected.</P>");
331             out.println("</BODY></HTML>");
332
333             // Set the session cookie.
334             Cookie cookie = new Cookie(cookieName, filename);
335             cookie.setPath("/");
336             response.addCookie(cookie);
337
338             // Redirect back to the requested resource.
339             response.sendRedirect(target);
340         }
341         catch (SAMLException e)
342         {
343             out.println("<H4>There was a problem with this submission.</H4>");
344             out.println("<P>The system detected the following error while processing your submission:</P>");
345             out.println("<BLOCKQUOTE>" + e.getMessage() + "</BLOCKQUOTE>");
346             out.println("<P>Please contact this site's administrator to resolve the problem.</P>");
347             out.println("</BODY></HTML>");
348         }
349     }
350 }
351