ebbd17fd2cab1bf93fee244cef8d04722e8fa483
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / shire / ShireServlet.java
1 package edu.internet2.middleware.shibboleth.shire;
2
3 import edu.internet2.middleware.shibboleth.common.*;
4
5 import java.io.*;
6 import java.security.Key;
7 import java.security.KeyStore;
8 import java.security.cert.Certificate;
9 import java.text.*;
10 import javax.servlet.*;
11 import javax.servlet.http.*;
12 import org.doomdark.uuid.*;
13 import org.opensaml.*;
14
15 /**
16  *  Implements a SAML POST profile consumer
17  *
18  * @author     Scott Cantor, Sridhar Muppidi
19  * @created    June 10, 2002
20  */
21 public class ShireServlet extends HttpServlet
22 {
23     private String cookieName = null;
24     private String sessionDir = null;
25     private boolean sslOnly = true;
26     private boolean checkAddress = true;
27     private boolean verbose = false;
28     private XMLOriginSiteMapper mapper = null;
29
30     private static void HTMLFormat(PrintWriter pw, String buf)
31     {
32         for (int i = 0; i < buf.length(); i++)
33         {
34             if (buf.charAt(i) == '<')
35                 pw.write("&lt;");
36             else if (buf.charAt(i) == '>')
37                 pw.write("&gt;");
38             else if (buf.charAt(i) == '&')
39                 pw.write("&amp;");
40             else
41                 pw.write(buf.charAt(i));
42         }
43     }
44
45     /**
46      *  Use the following servlet init parameters:<P>
47      *
48      *
49      *  <DL>
50      *    <DT> keystore-path <I>(required)</I> </DT>
51      *    <DD> A pathname to the trusted CA roots to accept</DD>
52      *    <DT> keystore-password <I>(required)</I> </DT>
53      *    <DD> The root keystore password</DD>
54      *    <DT> registry-alias <I>(optional)</I> </DT>
55      *    <DD> An alias in the provided keystore for the cert that can verify
56      *    the origin site registry signature</DD>
57      *    <DT> registry-uri <I>(required)</I> </DT>
58      *    <DD> The origin site registry URI to install</DD>
59      *    <DT> cookie-name <I>(required)</I> </DT>
60      *    <DD> Name of session cookie to set in browser</DD>
61      *    <DT> ssl-only <I>(defaults to true)</I> </DT>
62      *    <DD> If true, allow only SSL-protected POSTs and issue a secure cookie
63      *    </DD>
64      *    <DT> check-address <I>(defaults to true)</I> </DT>
65      *    <DD> If true, check client's IP address against assertion</DD>
66      *    <DT> session-dir <I>(defaults to /tmp)</I> </DT>
67      *    <DD> Directory in which to place session files</DD>
68      *    <DT> verbose <I>(defaults to false)</I> </DT>
69      *    <DD> Verbosity of redirection response</DD>
70      *  </DL>
71      *
72      *
73      * @exception  ServletException  Raised if the servlet cannot be initialized
74      */
75     public void init()
76         throws ServletException
77     {
78         edu.internet2.middleware.shibboleth.common.Init.init();
79
80         ServletConfig conf = getServletConfig();
81
82         cookieName = conf.getInitParameter("cookie-name");
83         if (cookieName == null)
84             throw new ServletException("ShireServlet.init() missing init parameter: cookie-name");
85
86         sessionDir = conf.getInitParameter("session-dir");
87         if (sessionDir == null)
88             sessionDir = "/tmp";
89
90         String temp = conf.getInitParameter("ssl-only");
91         if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
92             sslOnly = false;
93
94         temp = conf.getInitParameter("check-address");
95         if (temp != null && (temp.equalsIgnoreCase("false") || temp.equals("0")))
96             checkAddress = false;
97
98         temp = conf.getInitParameter("verbose");
99         if (temp != null && (temp.equalsIgnoreCase("true") || temp.equals("1")))
100             verbose = true;
101
102         try
103         {
104             Key k = null;
105             KeyStore ks = KeyStore.getInstance("JKS");
106             ks.load(conf.getServletContext().getResourceAsStream(conf.getInitParameter("keystore-path")),
107                     conf.getInitParameter("keystore-password").toCharArray());
108             if (conf.getInitParameter("keystore-alias") != null)
109             {
110                 Certificate cert = ks.getCertificate(conf.getInitParameter("keystore-alias"));
111                 if (cert == null || (k = cert.getPublicKey()) == null)
112                     throw new ServletException("ShireServlet.init() unable to find registry verification certificate/key");
113             }
114             mapper = new XMLOriginSiteMapper(conf.getInitParameter("registry-uri"), k, ks);
115         }
116         catch (java.security.KeyStoreException e)
117         {
118             throw new ServletException("ShireServlet.init() unable to load Java keystore");
119         }
120         catch (java.security.NoSuchAlgorithmException e)
121         {
122             throw new ServletException("ShireServlet.init() unable to load Java keystore");
123         }
124         catch (java.security.cert.CertificateException e)
125         {
126             throw new ServletException("ShireServlet.init() unable to load Java keystore");
127         }
128         catch (FileNotFoundException e)
129         {
130             throw new ServletException("ShireServlet.init() unable to locate Java keystore");
131         }
132         catch (IOException e)
133         {
134             throw new ServletException("ShireServlet.init() unable to load Java keystore");
135         }
136         catch (Exception e)
137         {
138             throw new ServletException("ShireServlet.init() unable to load origin site registry: " + e.getMessage());
139         }
140     }
141
142     /**
143      *  Processes a sign-on submission<P>
144      *
145      *
146      *
147      * @param  request               HTTP request context
148      * @param  response              HTTP response context
149      * @exception  IOException       Thrown if an I/O error occurs
150      * @exception  ServletException  Thrown if a servlet engine error occurs
151      */
152     public void doPost(HttpServletRequest request, HttpServletResponse response)
153         throws IOException, ServletException
154     {
155         // Output page opener.
156         response.setContentType("text/html");
157         PrintWriter out = response.getWriter();
158         out.println("<HTML>");
159         out.println("<HEAD>");
160         out.println("<TITLE>Shibboleth Session Establisher</TITLE>");
161         out.println("</HEAD>");
162         out.println("<BODY>");
163         out.println("<H3>Shibboleth Session Establisher</H3>");
164
165         if (sslOnly && !request.isSecure())
166         {
167             out.println("<H4>There was a problem with this submission.</H4>");
168             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>");
169             out.println("</BODY></HTML>");
170             return;
171         }
172
173         String target = request.getParameter("TARGET");
174         if (target == null || target.length() == 0)
175         {
176             out.println("<H4>There was a problem with this submission.</H4>");
177             out.println("<P>The target location was unspecified. To correct the problem, please re-enter the desired target URL into your browser.</P>");
178             out.println("</BODY></HTML>");
179             return;
180         }
181         else if (verbose)
182             out.println("<P><B>Target URL:</B>" + target + "</P>");
183
184         String responseData = request.getParameter("SAMLResponse");
185         if (responseData == null || responseData.length() == 0)
186         {
187             out.println("<H4>There was a problem with this submission.</H4>");
188             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>");
189             out.println("</BODY></HTML>");
190             return;
191         }
192
193         // Process the SAML response inside an exception handler.
194         try
195         {
196             // Get a profile object using our specifics.
197             String[] policies = {Constants.POLICY_CLUBSHIB};
198             ShibPOSTProfile profile =
199                 ShibPOSTProfileFactory.getInstance(policies, mapper, HttpUtils.getRequestURL(request).toString(), 300);
200
201             // Try and accept the response...
202             SAMLResponse r = profile.accept(responseData.getBytes());
203
204             // We've got a valid signed response we can trust (or the whole response was empty...)
205             if (verbose)
206             {
207                 ByteArrayOutputStream bytestr = new ByteArrayOutputStream();
208                 r.toStream(bytestr);
209                 out.println("<P><B>Parsed SAML Response:</B></P>");
210                 out.println("<P>");
211                 HTMLFormat(out, bytestr.toString(response.getCharacterEncoding()));
212                 out.println("</P>");
213             }
214
215             // Get the statement we need.
216             SAMLAuthenticationStatement s = profile.getSSOStatement(r);
217             if (s == null)
218             {
219                 out.println("<H4>There was a problem with this submission.</H4>");
220                 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>");
221                 out.println("</BODY></HTML>");
222                 return;
223             }
224
225             if (checkAddress)
226             {
227                 if (verbose)
228                     out.println("<P><B>Client Address:</B>" + request.getRemoteAddr() + "</P>");
229                 if (s.getSubjectIP() == null || !s.getSubjectIP().equals(request.getRemoteAddr()))
230                 {
231                     if (verbose && s.getSubjectIP() != null)
232                         out.println("<P><B>Supplied Client Address:</B>" + s.getSubjectIP() + "</P>");
233                     out.println("<H4>There was a problem with this submission.</H4>");
234                     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>");
235                     out.println("</BODY></HTML>");
236                 }
237             }
238
239             // All we really need is here...
240             String handle = s.getSubject().getName();
241             String domain = s.getSubject().getNameQualifier();
242             SAMLAuthorityBinding[] bindings = s.getBindings();
243
244             if (verbose)
245             {
246                 out.println("<P><B>Shibboleth Origin Site:</B>" + domain + "</P>");
247                 out.println("<P><B>Shibboleth Handle:</B>" + handle + "</P>");
248                 if (bindings != null)
249                     out.println("<P><B>Shibboleth AA URL:</B>" + bindings[0].getLocation() + "</P>");
250             }
251
252             // Generate a random session file.
253             String filename = UUIDGenerator.getInstance().generateRandomBasedUUID().toString().replace('-', '_');
254             String pathname = null;
255             if (sessionDir.endsWith(File.separator))
256                 pathname = sessionDir + filename;
257             else
258                 pathname = sessionDir + File.separatorChar + filename;
259             PrintWriter fout = new PrintWriter(new FileWriter(pathname));
260
261             if (verbose)
262                 out.println("<P><B>Session Pathname:</B>" + pathname + "</P>");
263
264             fout.println("Handle=" + handle);
265             fout.println("Domain=" + domain);
266             fout.println("PBinding0=" + bindings[0].getBinding());
267             fout.println("LBinding0=" + bindings[0].getLocation());
268             fout.println("EOF");
269             fout.close();
270
271             out.println("<P>Redirecting you to your target...</P>");
272             out.println("<P>Allow 10-15 seconds, then click <A HREF='" + target + "'>here</A> if you do not get redirected.</P>");
273             out.println("</BODY></HTML>");
274
275             // Set the session cookie.
276             Cookie cookie = new Cookie(cookieName, filename);
277             cookie.setPath("/");
278             response.addCookie(cookie);
279
280             // Redirect back to the requested resource.
281             response.sendRedirect(target);
282         }
283         catch (SAMLException e)
284         {
285             out.println("<H4>There was a problem with this submission.</H4>");
286             out.println("<P>The system detected the following error while processing your submission:</P>");
287             out.println("<BLOCKQUOTE>" + e.getMessage() + "</BLOCKQUOTE>");
288             out.println("<P>Please contact this site's administrator to resolve the problem.</P>");
289             out.println("</BODY></HTML>");
290         }
291     }
292 }
293