View Javadoc

1   /*
2    * Copyright (c) 2004 UNINETT FAS
3    *
4    * This program is free software; you can redistribute it and/or modify it
5    * under the terms of the GNU General Public License as published by the Free
6    * Software Foundation; either version 2 of the License, or (at your option)
7    * any later version.
8    *
9    * This program is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11   * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
12   * more details.
13   *
14   * You should have received a copy of the GNU General Public License along with
15   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
16   * Place - Suite 330, Boston, MA 02111-1307, USA.
17   *
18   * $Id: Moria2DemoServlet.java,v 1.6 2006/02/14 14:26:33 catoolsen Exp $
19   */
20  
21  package no.feide.mellon.demo;
22  
23  import javax.servlet.ServletException;
24  import javax.servlet.http.HttpServlet;
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import no.feide.moria.webservices.v2_1.Attribute;
29  import no.feide.moria.webservices.v2_1.AuthenticationSoapBindingStub;
30  import no.feide.mellon.MoriaException;
31  import no.feide.mellon.v2_1.Moria;
32  
33  import java.io.File;
34  import java.io.FileInputStream;
35  import java.io.IOException;
36  import java.io.PrintWriter;
37  import java.net.MalformedURLException;
38  import java.net.URL;
39  import java.rmi.RemoteException;
40  import java.util.ArrayList;
41  import java.util.Properties;
42  import java.util.StringTokenizer;
43  
44  /***
45   * This is a simple demonstration servlet, primarily intended as a code example
46   * of how to access the Moria SOAP interface. <br>
47   * <br>
48   * This implementation uses the Mellon2 API. <br>
49   * <br>
50   * The servlet works as follows: <br>
51   * <ol>
52   * <li>If the HTTP request does not contain a service ticket (found by checking
53   * URL parameter <code>PARAM_TICKET</code>) the user is redirected to a Moria
54   * instance (given by <code>SERVICE_ENDPOINT</code>) for authentication. <br>
55   * This is done using the <code>initiateAuthentication(...)</code> method to
56   * receive a correct redirect URL to the Moria instance.
57   * <li>Once the user has gone through a successful authentication, he or she is
58   * redirected back to this servlet, now with the previously missing service
59   * ticket.
60   * <li>The servlet then attempts to read the attributes requested by the
61   * earlier use of <code>initiateAuthentication(...)</code>, by using
62   * <code>getUserAttributes(...)</code> with the given service ticket.
63   * <li>If successful, the attributes and their values are then displayed.
64   * </ol>
65   * A few other points to note:
66   * <ul>
67   * <li>The attributes requested are given by <code>ATTRIBUTE_REQUEST</code>.
68   * <li>This servlet, as a Moria client service, authenticates itself to Moria
69   * using the username/password combination given by <code>CLIENT_USERNAME</code>
70   * and <code>CLIENT_PASSWORD</code>.
71   * <li>The Moria service instance used is given by
72   * <code>SERVICE_ENDPOINT</code>.
73   * </ul>
74   * @see no.feide.moria.webservices.v2_1.AuthenticationSoapBindingStub#initiateAuthentication(String[],
75   *      String, String, boolean)
76   * @see no.feide.moria.webservices.v2_1.AuthenticationSoapBindingStub#getUserAttributes(String)
77   */
78  public class Moria2DemoServlet
79  extends HttpServlet {
80  
81      /***
82       * Serial version UID.
83       */
84      private static final long serialVersionUID = 1399457211704776584L;
85  
86      /***
87       * The system property giving the configuration file name for the Demo
88       * servlet. <br>
89       * <br>
90       * Current value is <code>"no.feide.mellon.demo.config"</code>.
91       */
92      private static final String CONFIG_FILENAME = "no.feide.mellon.demo.config";
93  
94      /***
95       * The service endpoint. <br>
96       * <br>
97       * Current value is <code>"no.feide.mellon.demo.serviceEndpoint"</code>.
98       */
99      private static final String CONFIG_SERVICE_ENDPOINT = "no.feide.mellon.demo.serviceEndpoint";
100 
101     /***
102      * A comma-separated list of attributes requested by the main service. <br>
103      * <br>
104      * Current value is
105      * <code>"no.feide.mellon.demo.master.attributeRequest"</code>.
106      */
107     private static final String CONFIG_MASTER_ATTRIBUTE_REQUEST = "no.feide.mellon.demo.master.attributeRequest";
108 
109     /***
110      * The username used by DemoServlet to access Moria2 as a main service. <br>
111      * <br>
112      * Current value is <code>"no.feide.mellon.demo.master.username"</code>.
113      */
114     private static final String CONFIG_MASTER_USERNAME = "no.feide.mellon.demo.master.username";
115 
116     /***
117      * The password used by DemoServlet to access Moria2 as a main service. <br>
118      * <br>
119      * Current value is <code>"no.feide.mellon.demo.master.password"</code>.
120      */
121     private static final String CONFIG_MASTER_PASSWORD = "no.feide.mellon.demo.master.password";
122 
123     /***
124      * A comma-separated list of attributes requested by the subservice. <br>
125      * <br>
126      * Current value is
127      * <code>"no.feide.mellon.demo.slave.attributeRequest"</code>.
128      */
129     private static final String CONFIG_SLAVE_ATTRIBUTE_REQUEST = "no.feide.mellon.demo.slave.attributeRequest";
130 
131     /***
132      * The username used to access Moria2 as a subservice. <br>
133      * <br>
134      * Current value is <code>"no.feide.mellon.demo.slave.username"</code>.
135      */
136     private static final String CONFIG_SLAVE_USERNAME = "no.feide.mellon.demo.slave.username";
137 
138     /***
139      * The password used to access Moria2 as a subservice. <br>
140      * <br>
141      * Current value is <code>"no.feide.mellon.demo.slave.password"</code>.
142      */
143     private static final String CONFIG_SLAVE_PASSWORD = "no.feide.mellon.demo.slave.password";
144 
145     /***
146      * The URL that the user should be redirected to in order to complete
147      * logout. <br>
148      * <br>
149      * Current value is <code>"no.feide.mellon.demo.logout.url"</code>.
150      */
151     private static final String CONFIG_LOGOUT_URL = "no.feide.mellon.demo.logout.url";
152 
153     /***
154      * The truststore filename used when accepting Moria's certificate. If not
155      * set, no custom truststore will be used. <br>
156      * <br>
157      * Current value is <code>"no.feide.mellon.demo.trustStore"</code>.
158      */
159     private static final String CONFIG_TRUSTSTORE = "no.feide.mellon.demo.trustStore";
160 
161     /***
162      * The truststore password used in conjunction with
163      * <code>CONFIG_TRUSTSTORE</code>.<br>
164      * <br>
165      * Current value is <code>"no.feide.mellon.demo.trustStorePassword"</code>.
166      */
167     private static final String CONFIG_TRUSTSTORE_PASSWORD = "no.feide.mellon.demo.trustStorePassword";
168 
169     /***
170      * Required parameters.
171      */
172     private static final String[] REQUIRED_PARAMETERS = {
173                                                          CONFIG_SERVICE_ENDPOINT,
174                                                          CONFIG_MASTER_USERNAME,
175                                                          CONFIG_MASTER_PASSWORD,
176                                                          CONFIG_SLAVE_USERNAME,
177                                                          CONFIG_SLAVE_PASSWORD,
178                                                          CONFIG_LOGOUT_URL};
179 
180     /***
181      * Name of the URL parameter used to retrieve the Moria service ticket. <br>
182      * <br>
183      * Current value is <code>"ticket"</code>.
184      */
185     private static final String PARAM_TICKET = "ticket";
186 
187 
188     /***
189      * Initialization. Will set the truststore used by the servlet when trusting
190      * the Moria instance's certificate, if it is not covered by the default
191      * truststore.
192      * @throws ServletException
193      *             Never.
194      */
195     public final void init() throws ServletException {
196 
197         // Set the truststore.
198         final Properties config = getConfig();
199         System.setProperty("javax.net.ssl.trustStore", config.getProperty(CONFIG_TRUSTSTORE));
200         System.setProperty("javax.net.ssl.trustStorePassword", config.getProperty(CONFIG_TRUSTSTORE_PASSWORD));
201 
202     }
203 
204 
205     /***
206      * Handles the GET requests.
207      * @param request
208      *            The HTTP request object. If it contains a request parameter
209      *            <i>moriaID </i>, the request's attribute <i>attributes </i>
210      *            will be filled with the attributes contained in the session
211      *            given by <i>moriaID </i>.
212      * @param response
213      *            The HTTP response object.
214      * @throws java.io.IOException
215      *             If an input or output error is detected when the servlet
216      *             handles the GET request.
217      * @throws javax.servlet.ServletException
218      *             If the request for the GET could not be handled.
219      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
220      *      javax.servlet.http.HttpServletResponse)
221      */
222     public final void doGet(final HttpServletRequest request,
223                             final HttpServletResponse response) throws IOException,
224                                                                ServletException {
225 
226         // Be sure to dump all exceptions.
227         try {
228 
229             // Get configuration.
230             final Properties config = getConfig();
231 
232             // Handle logout request.
233             if (request.getParameter("logout") != null) {
234 
235                 // Redirect to the configured logout URL.
236                 response.sendRedirect(config.getProperty(CONFIG_LOGOUT_URL));
237             }
238 
239             // Prepare API.
240             Moria service = new Moria(config.getProperty(CONFIG_SERVICE_ENDPOINT), config.getProperty(CONFIG_MASTER_USERNAME), config.getProperty(CONFIG_MASTER_PASSWORD));
241 
242             // Do we have a ticket?
243             final String ticket = request.getParameter(PARAM_TICKET);
244             if (ticket == null) {
245 
246                 // No ticket; redirect for authentication.
247                 String redirectURL = service.initiateAuthentication(convert(config.getProperty(CONFIG_MASTER_ATTRIBUTE_REQUEST)), request.getRequestURL().toString() + "?" + PARAM_TICKET + "=", "", false);
248                 response.sendRedirect(redirectURL);
249 
250             } else {
251 
252                 // We have a ticket.
253                 response.setContentType("text/html");
254                 PrintWriter out = response.getWriter();
255                 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01" + "Transitional//EN\">\n");
256                 out.println("<html><head><title>Moria Demo Service</title></head><body>");
257                 out.println("<h1 align=\"center\">Authentication successful</h1>");
258                 out.println("<p align=\"center\"><a href=\"" + config.getProperty(CONFIG_LOGOUT_URL) + "\">Logout</a></p>");
259                 out.println("<i>System '" + config.getProperty(CONFIG_MASTER_USERNAME) + "':</i>");
260                 String ticketGrantingTicket = null; // For later use.
261 
262                 // Get and display attributes.
263                 // TODO: Catch exceptions here.
264                 Attribute[] attributes = service.getUserAttributes(ticket);
265                 out.println("<table align=\"center\"><tr><td><b>Attribute Name</b></td><td><b>Attribute Value(s)</b></td></tr>");
266                 for (int i = 0; i < attributes.length; i++) {
267 
268                     // Is this the ticket granting ticket?
269                     String name = attributes[i].getName();
270                     if (name.equals("tgt")) {
271 
272                         // Just remember the ticket for later use.
273                         ticketGrantingTicket = attributes[i].getValues()[0];
274 
275                     } else {
276 
277                         // Show the actual attribute and its values.
278                         out.println("<tr><td>" + name + "</td>");
279                         String[] values = attributes[i].getValues();
280                         for (int j = 0; j < values.length; j++) {
281                             if (j > 0)
282                                 out.println("<tr><td></td>");
283                             out.println("<td>" + values[j] + "</td></tr>");
284                         }
285 
286                     }
287 
288                 }
289                 out.println("</table>");
290                 out.println("<p><b>Ticket granting ticket:</b> <tt>" + ticketGrantingTicket + "</tt></p>");
291 
292                 // Should we try to fake a subsystem using SSO?
293                 out.println("<hr><i>Subsystem '" + config.getProperty(CONFIG_SLAVE_USERNAME) + "':</i>");
294                 if (ticketGrantingTicket == null) {
295 
296                     out.println("<p align=\"center\">No ticket granting ticket was retrieved.<br>SSO denied.</p>");
297 
298                 } else {
299 
300                     // Now get a proxy ticket for your subsystem (we're actually
301                     // our own subsystem, to keep the code simple).
302                     final String proxyTicket = service.getProxyTicket(ticketGrantingTicket, config.getProperty(CONFIG_SLAVE_USERNAME));
303 
304                     // We now have a proxy ticket; now let's fake our own
305                     // subsystem. Retrieve and display some attributes.
306                     AuthenticationSoapBindingStub subservice = new AuthenticationSoapBindingStub(new URL(config.getProperty(CONFIG_SERVICE_ENDPOINT)), null);
307                     subservice.setUsername(config.getProperty(CONFIG_SLAVE_USERNAME));
308                     subservice.setPassword(config.getProperty(CONFIG_SLAVE_PASSWORD));
309                     attributes = subservice.proxyAuthentication(convert(config.getProperty(CONFIG_SLAVE_ATTRIBUTE_REQUEST)), proxyTicket);
310                     out.println("<table align=\"center\"><tr><td><b>Attribute Name</b></td><td><b>Attribute Value(s)</b></td></tr>");
311                     for (int i = 0; i < attributes.length; i++) {
312 
313                         // Show the actual attribute and its values.
314                         String name = attributes[i].getName();
315                         out.println("<tr><td>" + name + "</td>");
316                         String[] values = attributes[i].getValues();
317                         for (int j = 0; j < values.length; j++) {
318                             if (j > 0)
319                                 out.println("<tr><td></td>");
320                             out.println("<td>" + values[j] + "</td></tr>");
321                         }
322 
323                     }
324                     out.println("</table>");
325                     out.println("<p><b>Proxy ticket:</b> <tt>" + proxyTicket + "</tt></p>");
326 
327                 }
328 
329                 // We're done!
330                 out.println("</html></body>");
331 
332             }
333 
334         } catch (MoriaException e) {
335             System.err.println("MoriaException caught: " + e);
336             throw new ServletException(e);
337         } catch (RemoteException e) {
338             System.err.println("RemoteException caught: " + e);
339             throw new ServletException(e);
340         } catch (MalformedURLException e) {
341             System.err.println("MalformedURLException caught: " + e);
342             throw new ServletException(e);
343         }
344 
345     }
346 
347 
348     /***
349      * Read configuration from the file given by <code>CONFIG_FILENAME</code>
350      * and check that all required properties are given.
351      * @throws IllegalStateException
352      *             If the required property <code>CONFIG_FILENAME</code> is
353      *             not set, or if the file given by this property could not be
354      *             read.
355      * @return Configuration properties from file.
356      * @see #REQUIRED_PARAMETERS
357      */
358     private Properties getConfig() throws IllegalStateException {
359 
360         // Read properties from file.
361         final String configFile = System.getProperty(CONFIG_FILENAME);
362         if (configFile == null) { throw new IllegalStateException("Required base property '" + CONFIG_FILENAME + "' not set"); }
363         Properties config = new Properties();
364         try {
365             config.load(new FileInputStream(new File(configFile)));
366         } catch (IOException e) {
367             throw new IllegalStateException("Unable to read configuration from " + configFile);
368         }
369 
370         // Are we missing some required properties?
371         for (int i = 0; i < REQUIRED_PARAMETERS.length; i++) {
372             String parvalue = config.getProperty(REQUIRED_PARAMETERS[i]);
373             if ((parvalue == null) || (parvalue.equals(""))) { throw new IllegalStateException("Required parameter '" + REQUIRED_PARAMETERS[i] + "' not set in '" + configFile + "'"); }
374         }
375         return config;
376 
377     }
378 
379 
380     /***
381      * Convert a comma-separated list into an array.
382      * @param commaSeparatedList
383      *            A comma-separated list of elements. May be <code>null</code>
384      *            or an empty string.
385      * @return <code>commaSeparatedList</code> as a string array. Will always
386      *         return an empty array if <code>commaSeparatedList</code> is
387      *         <code>null</code> or an empty string.
388      */
389     private String[] convert(final String commaSeparatedList) {
390 
391         // Sanity checks.
392         if ((commaSeparatedList == null) || (commaSeparatedList.length() == 0))
393             return new String[] {};
394 
395         // Convert and return.
396         ArrayList buffer = new ArrayList();
397         StringTokenizer tokenizer = new StringTokenizer(commaSeparatedList, ",");
398         while (tokenizer.hasMoreTokens())
399             buffer.add(tokenizer.nextToken());
400         return (String[]) buffer.toArray(new String[] {});
401 
402     }
403 
404 }