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: Moria1DemoServlet.java,v 1.4 2006/02/14 14:26:53 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.mellon.Moria;
29  import no.feide.mellon.MoriaException;
30  
31  import java.io.File;
32  import java.io.FileInputStream;
33  import java.io.IOException;
34  import java.io.PrintWriter;
35  import java.net.MalformedURLException;
36  import java.util.ArrayList;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Properties;
40  import java.util.StringTokenizer;
41  
42  /***
43   * This is a simple demonstration servlet, primarily intended as a code example
44   * of how to access the Moria SOAP interface. <br>
45   * <br>
46   * This implementation uses the Mellon2 API. <br>
47   * <br>
48   * The servlet works as follows: <br>
49   * <ol>
50   * <li>If the HTTP request does not contain a service ticket (found by checking
51   * URL parameter <code>PARAM_TICKET</code>) the user is redirected to a Moria
52   * instance (given by <code>SERVICE_ENDPOINT</code>) for authentication. <br>
53   * This is done using the <code>initiateAuthentication(...)</code> method to
54   * receive a correct redirect URL to the Moria instance.
55   * <li>Once the user has gone through a successful authentication, he or she is
56   * redirected back to this servlet, now with the previously missing service
57   * ticket.
58   * <li>The servlet then attempts to read the attributes requested by the
59   * earlier use of <code>initiateAuthentication(...)</code>, by using
60   * <code>getUserAttributes(...)</code> with the given service ticket.
61   * <li>If successful, the attributes and their values are then displayed.
62   * </ol>
63   * A few other points to note:
64   * <ul>
65   * <li>The attributes requested are given by <code>ATTRIBUTE_REQUEST</code>.
66   * <li>This servlet, as a Moria client service, authenticates itself to Moria
67   * using the username/password combination given by <code>CLIENT_USERNAME</code>
68   * and <code>CLIENT_PASSWORD</code>.
69   * <li>The Moria service instance used is given by
70   * <code>SERVICE_ENDPOINT</code>.
71   * </ul>
72   * @see no.feide.moria.webservices.v2_1.AuthenticationSoapBindingStub#initiateAuthentication(String[],
73   *      String, String, boolean)
74   * @see no.feide.moria.webservices.v2_1.AuthenticationSoapBindingStub#getUserAttributes(String)
75   */
76  public class Moria1DemoServlet
77  extends HttpServlet {
78  
79      /***
80       * Serial version UID.
81       */
82      private static final long serialVersionUID = 855155044401873910L;
83  
84      /***
85       * The system property giving the configuration file name for the Demo
86       * servlet. <br>
87       * <br>
88       * Current value is <code>"no.feide.mellon.demo.config"</code>.
89       */
90      private static final String CONFIG_FILENAME = "no.feide.mellon.demo.config";
91  
92      /***
93       * The service endpoint. <br>
94       * <br>
95       * Current value is <code>"no.feide.mellon.demo.serviceEndpoint"</code>.
96       */
97      private static final String CONFIG_SERVICE_ENDPOINT = "no.feide.mellon.demo.serviceEndpoint";
98  
99      /***
100      * A comma-separated list of attributes requested by the main service. <br>
101      * <br>
102      * Current value is
103      * <code>"no.feide.mellon.demo.master.attributeRequest"</code>.
104      */
105     private static final String CONFIG_MASTER_ATTRIBUTE_REQUEST = "no.feide.mellon.demo.master.attributeRequest";
106 
107     /***
108      * The username used by DemoServlet to access Moria2 as a main service. <br>
109      * <br>
110      * Current value is <code>"no.feide.mellon.demo.master.username"</code>.
111      */
112     private static final String CONFIG_MASTER_USERNAME = "no.feide.mellon.demo.master.username";
113 
114     /***
115      * The password used by DemoServlet to access Moria2 as a main service. <br>
116      * <br>
117      * Current value is <code>"no.feide.mellon.demo.master.password"</code>.
118      */
119     private static final String CONFIG_MASTER_PASSWORD = "no.feide.mellon.demo.master.password";
120 
121     /***
122      * The URL that the user should be redirected to in order to complete
123      * logout. <br>
124      * <br>
125      * Current value is <code>"no.feide.mellon.demo.logout.url"</code>.
126      */
127     private static final String CONFIG_LOGOUT_URL = "no.feide.mellon.demo.logout.url";
128 
129     /***
130      * The truststore filename used when accepting Moria's certificate. If not
131      * set, no custom truststore will be used. <br>
132      * <br>
133      * Current value is <code>"no.feide.mellon.demo.trustStore"</code>.
134      */
135     private static final String CONFIG_TRUSTSTORE = "no.feide.mellon.demo.trustStore";
136 
137     /***
138      * The truststore password used in conjunction with
139      * <code>CONFIG_TRUSTSTORE</code>.<br>
140      * <br>
141      * Current value is <code>"no.feide.mellon.demo.trustStorePassword"</code>.
142      */
143     private static final String CONFIG_TRUSTSTORE_PASSWORD = "no.feide.mellon.demo.trustStorePassword";
144 
145     /***
146      * Required parameters.
147      */
148     private static final String[] REQUIRED_PARAMETERS = {
149                                                          CONFIG_SERVICE_ENDPOINT,
150                                                          CONFIG_MASTER_USERNAME,
151                                                          CONFIG_MASTER_PASSWORD,
152                                                          CONFIG_LOGOUT_URL};
153 
154     /***
155      * Name of the URL parameter used to retrieve the Moria service ticket. <br>
156      * <br>
157      * Current value is <code>"ticket"</code>.
158      */
159     private static final String PARAM_TICKET = "ticket";
160 
161 
162     /***
163      * Initialization. Will set the truststore used by the servlet when trusting
164      * the Moria instance's certificate, if it is not covered by the default
165      * truststore.
166      * @throws ServletException
167      *             Never.
168      */
169     public final void init() throws ServletException {
170 
171         // Set the truststore.
172         final Properties config = getConfig();
173         System.setProperty("javax.net.ssl.trustStore", config.getProperty(CONFIG_TRUSTSTORE));
174         System.setProperty("javax.net.ssl.trustStorePassword", config.getProperty(CONFIG_TRUSTSTORE_PASSWORD));
175 
176     }
177 
178 
179     /***
180      * Handles the GET requests.
181      * @param request
182      *            The HTTP request object. If it contains a request parameter
183      *            <i>moriaID </i>, the request's attribute <i>attributes </i>
184      *            will be filled with the attributes contained in the session
185      *            given by <i>moriaID </i>.
186      * @param response
187      *            The HTTP response object.
188      * @throws java.io.IOException
189      *             If an input or output error is detected when the servlet
190      *             handles the GET request.
191      * @throws javax.servlet.ServletException
192      *             If the request for the GET could not be handled.
193      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
194      *      javax.servlet.http.HttpServletResponse)
195      */
196     public final void doGet(final HttpServletRequest request,
197                             final HttpServletResponse response) throws IOException,
198                                                                ServletException {
199 
200         // Be sure to dump all exceptions.
201         try {
202 
203             // Get configuration.
204             final Properties config = getConfig();
205 
206             // Handle logout request.
207             if (request.getParameter("logout") != null) {
208 
209                 // Redirect to the configured logout URL.
210                 response.sendRedirect(config.getProperty(CONFIG_LOGOUT_URL));
211             }
212 
213             // Prepare API.
214             System.setProperty(Moria.SERVICE_ENDPOINT_PROPERTY, config.getProperty(CONFIG_SERVICE_ENDPOINT));
215             System.setProperty(Moria.CLIENT_USERNAME, config.getProperty(CONFIG_MASTER_USERNAME));
216             System.setProperty(Moria.CLIENT_PASSWORD, config.getProperty(CONFIG_MASTER_PASSWORD));
217             Moria service = Moria.getInstance();
218 
219             // Do we have a ticket?
220             final String ticket = request.getParameter(PARAM_TICKET);
221             if (ticket == null) {
222 
223                 // No ticket; redirect for authentication.
224                 String redirectURL = service.requestSession(convert(config.getProperty(CONFIG_MASTER_ATTRIBUTE_REQUEST)), request.getRequestURL().toString() + "?" + PARAM_TICKET + "=", "", false);
225                 response.sendRedirect(redirectURL);
226 
227             } else {
228 
229                 // We have a ticket.
230                 response.setContentType("text/html");
231                 PrintWriter out = response.getWriter();
232                 out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01" + "Transitional//EN\">\n");
233                 out.println("<html><head><title>Moria Demo Service</title></head><body>");
234                 out.println("<h1 align=\"center\">Authentication successful</h1>");
235                 out.println("<p align=\"center\"><a href=\"" + config.getProperty(CONFIG_LOGOUT_URL) + "\">Logout</a></p>");
236                 out.println("<i>System '" + config.getProperty(CONFIG_MASTER_USERNAME) + "':</i>");
237 
238                 // Get and display attributes.
239                 // TODO: Catch exceptions here.
240                 HashMap attributes = service.getAttributes(ticket);
241                 out.println("<table align=\"center\"><tr><td><b>Attribute Name</b></td><td><b>Attribute Value(s)</b></td></tr>");
242                 Iterator i = attributes.keySet().iterator();
243                 String name = null;
244                 while (i.hasNext()) {
245 
246                     // Show the actual attribute and its values.
247                     name = (String) i.next();
248                     out.println("<tr><td>" + name + "</td>");
249                     String[] values = (String[]) attributes.get(name);
250                     for (int j = 0; j < values.length; j++) {
251                         if (j > 0)
252                             out.println("<tr><td></td>");
253                         out.println("<td>" + values[j] + "</td></tr>");
254                     }
255 
256                 }
257                 out.println("</table>");
258 
259                 // We're done!
260                 out.println("</html></body>");
261 
262             }
263 
264         } catch (MoriaException e) {
265             System.err.println("MoriaException caugth: " + e);
266             throw new ServletException(e);
267         } catch (MalformedURLException e) {
268             System.err.println("MalformedURLException caught: " + e);
269             throw new ServletException(e);
270         }
271 
272     }
273 
274 
275     /***
276      * Read configuration from the file given by <code>CONFIG_FILENAME</code>
277      * and check that all required properties are given.
278      * @throws IllegalStateException
279      *             If the required property <code>CONFIG_FILENAME</code> is
280      *             not set, or if the file given by this property could not be
281      *             read.
282      * @return Configuration properties from file.
283      * @see #REQUIRED_PARAMETERS
284      */
285     private Properties getConfig() throws IllegalStateException {
286 
287         // Read properties from file.
288         final String configFile = System.getProperty(CONFIG_FILENAME);
289         if (configFile == null) { throw new IllegalStateException("Required base property '" + CONFIG_FILENAME + "' not set"); }
290         Properties config = new Properties();
291         try {
292             config.load(new FileInputStream(new File(configFile)));
293         } catch (IOException e) {
294             throw new IllegalStateException("Unable to read configuration from " + configFile);
295         }
296 
297         // Are we missing some required properties?
298         for (int i = 0; i < REQUIRED_PARAMETERS.length; i++) {
299             String parvalue = config.getProperty(REQUIRED_PARAMETERS[i]);
300             if ((parvalue == null) || (parvalue.equals(""))) { throw new IllegalStateException("Required parameter '" + REQUIRED_PARAMETERS[i] + "' not set in '" + configFile + "'"); }
301         }
302         return config;
303 
304     }
305 
306 
307     /***
308      * Convert a comma-separated list into an array.
309      * @param commaSeparatedList
310      *            A comma-separated list of elements. May be <code>null</code>
311      *            or an empty string.
312      * @return <code>commaSeparatedList</code> as a string array. Will always
313      *         return an empty array if <code>commaSeparatedList</code> is
314      *         <code>null</code> or an empty string.
315      */
316     private String[] convert(final String commaSeparatedList) {
317 
318         // Sanity checks.
319         if ((commaSeparatedList == null) || (commaSeparatedList.length() == 0))
320             return new String[] {};
321 
322         // Convert and return.
323         ArrayList buffer = new ArrayList();
324         StringTokenizer tokenizer = new StringTokenizer(commaSeparatedList, ",");
325         while (tokenizer.hasMoreTokens())
326             buffer.add(tokenizer.nextToken());
327         return (String[]) buffer.toArray(new String[] {});
328 
329     }
330 
331 }