1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
227 try {
228
229
230 final Properties config = getConfig();
231
232
233 if (request.getParameter("logout") != null) {
234
235
236 response.sendRedirect(config.getProperty(CONFIG_LOGOUT_URL));
237 }
238
239
240 Moria service = new Moria(config.getProperty(CONFIG_SERVICE_ENDPOINT), config.getProperty(CONFIG_MASTER_USERNAME), config.getProperty(CONFIG_MASTER_PASSWORD));
241
242
243 final String ticket = request.getParameter(PARAM_TICKET);
244 if (ticket == null) {
245
246
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
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;
261
262
263
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
269 String name = attributes[i].getName();
270 if (name.equals("tgt")) {
271
272
273 ticketGrantingTicket = attributes[i].getValues()[0];
274
275 } else {
276
277
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
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
301
302 final String proxyTicket = service.getProxyTicket(ticketGrantingTicket, config.getProperty(CONFIG_SLAVE_USERNAME));
303
304
305
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
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
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
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
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
392 if ((commaSeparatedList == null) || (commaSeparatedList.length() == 0))
393 return new String[] {};
394
395
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 }