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: InformationServlet.java,v 1.33 2006/01/16 16:06:43 indal Exp $
19   */
20  
21  package no.feide.moria.servlet;
22  
23  import java.io.IOException;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Properties;
28  import java.util.ResourceBundle;
29  import java.util.Arrays;
30  import javax.servlet.RequestDispatcher;
31  import javax.servlet.ServletException;
32  import javax.servlet.http.HttpServlet;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletResponse;
35  import java.io.File;
36  import javax.xml.parsers.SAXParserFactory;
37  import javax.xml.parsers.SAXParser;
38  import no.feide.moria.controller.AuthorizationException;
39  import no.feide.moria.controller.IllegalInputException;
40  import no.feide.moria.controller.InoperableStateException;
41  import no.feide.moria.controller.MoriaController;
42  import no.feide.moria.controller.UnknownTicketException;
43  import no.feide.moria.log.MessageLogger;
44  import java.util.Vector;
45  
46  
47  /***
48   * This servlet is responsible for retrieving information about a user, and
49   * sending it to information.jsp for display.
50   *
51   * @author Eva Indal
52   * @version $Revision: 1.33 $
53   */
54  public class InformationServlet extends MoriaServlet {
55  
56      /***
57       * A hash map containing all possible attributes for a user.
58       * Each item in the hashmap maps from an attribute name to an
59       * AttribsData class instance
60       */
61      private HashMap feideattribsStored = null;
62      
63      /***
64       * Monitor for the attribute description file.
65       */
66      private FileMonitor feideattribsMonitor = null;
67  
68      /*** Principal name of the service.
69       *  Current value is "info"
70       */
71      private static final String PRINCIPAL = "info";
72  
73      /*** Used for logging. */
74      private final MessageLogger log = new MessageLogger(InformationServlet.class);
75  
76      /***
77       * List of parameters required by <code>InformationServlet</code>.
78       * <br>
79       * <br>
80       * Current required parameters are:
81       * <ul>
82       * <li><code>RequestUtil.PROP_COOKIE_LANG</code>
83       * <li><code>RequestUtil.PROP_COOKIE_LANG_TTL</code>
84       * <li><code>RequestUtil.PROP_COOKIE_DENYSSO</code>
85       * <li><code>RequestUtil.PROP_LOGIN_TICKET_PARAM</code>
86       * <li><code>RequestUtil.PROP_INFORMATION_URL_PREFIX</code>
87       * <li><code>RequestUtil.PROP_INFORMATION_DESCRIPTIONS</code>
88       * <li><code>RequestUtil.PIC_LINK</code>
89       * </ul>
90       * @see RequestUtil#PROP_COOKIE_LANG
91       * @see RequestUtil#PROP_COOKIE_LANG_TTL
92       * @see RequestUtil#PROP_COOKIE_DENYSSO
93       * @see RequestUtil#PROP_LOGIN_TICKET_PARAM
94       * @see RequestUtil#PROP_INFORMATION_URL_PREFIX
95       * @see RequestUtil#PROP_INFORMATION_DESCRIPTIONS
96       * @see RequestUtil#PIC_LINK
97       */
98      private static final String[] REQUIRED_PARAMETERS = {
99          RequestUtil.PROP_COOKIE_LANG,
100         RequestUtil.PROP_COOKIE_LANG_TTL,
101         RequestUtil.PROP_COOKIE_DENYSSO,
102         RequestUtil.PROP_LOGIN_TICKET_PARAM,
103         RequestUtil.PROP_INFORMATION_URL_PREFIX,
104         RequestUtil.PROP_INFORMATION_DESCRIPTIONS,
105         RequestUtil.PIC_LINK
106     };
107     
108     /***
109      * 
110      * @return the required parameters for this servlet.
111      */
112     public static String[] getRequiredParameters() {
113         return REQUIRED_PARAMETERS;
114     }
115     /***
116      * Constructor.
117      */
118     public InformationServlet() {
119     }
120 
121     /***
122      * Implements a simple xml parser that parses the attribute description file
123      * into a HashMap with AttribsData instances.
124      *
125      * @see AttribsHandler
126      *      AttribsData
127      */
128     public final synchronized HashMap getAttribs() {
129        if (feideattribsMonitor == null || feideattribsMonitor.hasChanged()) {
130           Properties config; 
131           try {
132                config = getServletConfig(getRequiredParameters(), log);
133           } catch (IllegalStateException e) {
134               config = null;
135           }              
136           if (config != null) {
137             AttribsHandler handler = new AttribsHandler();
138             SAXParserFactory factory = SAXParserFactory.newInstance();
139             String filename = (String) config.get(RequestUtil.PROP_INFORMATION_DESCRIPTIONS);
140             try {
141                if ((filename == null) || (filename.equals(""))) {
142                    log.logCritical("Required configuration property PROP_INFORMATION_DESCRIPTIONS is not set");
143                    throw new IllegalStateException();
144                }
145                SAXParser saxParser = factory.newSAXParser();
146                saxParser.parse(new File(filename), handler);
147                feideattribsMonitor = new FileMonitor(filename);
148             } catch (Throwable t) {
149                 log.logCritical("Error parsing attribute description file '" + filename + "'");
150               throw new IllegalStateException();
151             } finally {
152               feideattribsStored = handler.getAttribs();
153             }
154           }
155         }
156         return feideattribsStored;
157     }
158 
159     /***
160      * Generates table for a user. The vector consists of rows of data. Each row
161      * has four columns. The first column is the URL link for the user attribute. The
162      * second column is the attribute description as presented to the user. The
163      * third column is the actual data stored for the attribute, and the fourth
164      * columns is either fd_mandatory or fd_optional, as a key for a mandatory
165      * or optional attribute.
166      *
167      * @param userData The user data.
168      * @param bundle Resource bundle for language.
169      * @return Vector with table data.
170      */
171     private Vector printTableToVector(final Map userData, final ResourceBundle bundle) {
172 
173         /* Stores the data in a temporary vector */
174         Vector temp = new Vector();
175         HashMap feideattribs = getAttribs();
176         for (Iterator iterator = feideattribs.keySet().iterator(); iterator.hasNext();) {
177             String key = (String) iterator.next();
178             AttribsData attribsData = (AttribsData) feideattribs.get(key);
179             temp.add(attribsData);
180         }
181         /* Sorts the description fields according to the xml file */
182         Object[] attribsArray = temp.toArray();
183         Arrays.sort(attribsArray, new AttribsData(0));
184 
185         Vector out = new Vector();
186 
187         /* Fetch the data to print */
188         int n = temp.size();
189         for (int i = 0; i < n; i++) {
190             AttribsData adata = (AttribsData) attribsArray[i];
191             String key2 = (String) adata.getData("key");  
192             boolean hasuserdata = userData.containsKey(key2);
193             String[] userdata = (String[]) userData.get(key2);
194 
195             String description = adata.getData("description");
196             String bundleid = adata.getData("resourcebundle");
197             String bundlename = (String) bundle.getString(bundleid);
198             /* see if resourcebundle has a name */
199             if (bundlename != null)
200                 description = bundlename;
201             String link = adata.getData("link");
202             String relevance = adata.getData("relevance");
203             String o_relevance =adata.getData("o-relevance");
204 
205             String userstring = "";
206            // String relevanceString = null;
207            // String o_relevanceString = null;
208 
209             if (userdata != null) {
210                 for (int j = 0; j < userdata.length; j++) {
211                     userstring += userdata[j];
212                     userstring += "<BR>";
213                 }
214             }
215                 
216             //Check if a user has picture
217             if (key2.equals("jpegPhoto")) {
218                 if (userdata != null) {
219                     userstring = "p_yes";                    
220                 }
221             }
222             out.add(link);
223             out.add(description);
224             out.add(userstring);
225             out.add(relevance);
226             out.add(o_relevance);
227             out.add(key2);
228         }
229         return out;
230     }
231 
232     /***
233      * Implements the HttpServlet.doGet method.
234      *
235      * @param request   The HTTP request.
236      * @param response  The HTTP response.
237      * @throws IOException
238      *           If getAttribs returns null.
239      * @throws ServletException
240      *           If the Moria controller throws an exception.
241      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
242      */
243     public final void doGet(final HttpServletRequest request, final HttpServletResponse response)
244             throws ServletException, IOException {
245         if (getAttribs() == null) throw new IOException("Attribute description file not parsed");
246 
247         String ticketId = request.getParameter("moriaID");
248 
249         if (request.getParameter("logout") != null) {
250             log.logDebug("Logout received");
251             final RequestDispatcher rd = getServletContext().getNamedDispatcher("Logout");
252             rd.forward(request, response);
253             return;
254         }
255 
256         /*
257          * Makes a Properties object named config, which gets the config
258          * from the getServletConfig() method
259          */
260         Properties config;
261 
262         try {
263             config = getServletConfig(getRequiredParameters(), log);
264         } catch (IllegalStateException e) {
265             config = null;
266         }
267 
268         /* Resource bundle. */
269         String langFromCookie = null;
270         if (config != null && request.getCookies() != null) {
271             langFromCookie = RequestUtil.getCookieValue((String) config.get(RequestUtil.PROP_COOKIE_LANG), request.getCookies());
272         }
273 
274         /*Use default login language as default for the information servlet */
275         final ResourceBundle bundle = RequestUtil.getBundle(
276                 RequestUtil.BUNDLE_INFORMATIONSERVLET,
277                 request.getParameter(RequestUtil.PARAM_LANG), langFromCookie, null,
278                 request.getHeader("Accept-Language"), (String) config.get(RequestUtil.PROP_LOGIN_DEFAULT_LANGUAGE));
279 
280         request.setAttribute("bundle", bundle);
281         String urlPrefix = (String)config.get(RequestUtil.PROP_INFORMATION_URL_PREFIX);
282         if ((urlPrefix == null) || (urlPrefix.equals(""))) {
283             log.logCritical("Required configuration property PROP_INFORMATION_DESCRIPTIONS is not set");
284             throw new IllegalStateException();
285         } else {
286             request.setAttribute("urlPrefix", urlPrefix);
287         }
288        
289         // update cookie if language has changed
290         boolean changelanguage = false;
291         if (request.getParameter(RequestUtil.PARAM_LANG) != null) {
292             response.addCookie(RequestUtil.createCookie(
293                     (String) config.get(RequestUtil.PROP_COOKIE_LANG),
294                     request.getParameter(RequestUtil.PARAM_LANG),
295                     new Integer((String) config.get(RequestUtil.PROP_COOKIE_LANG_TTL)).intValue()));
296             changelanguage = true;
297         }
298         Map userData = null;
299         // to change language, we have to get a new ticket from the LoginServlet
300         if (ticketId != null && !changelanguage) {
301             try {
302                 userData = MoriaController.getUserAttributes(ticketId, PRINCIPAL);
303             } catch (AuthorizationException e) {
304                 log.logCritical("AuthorizationException");
305                 throw new ServletException(e);
306             } catch (IllegalInputException e) {
307                 log.logCritical("IllegalInputException");
308                 throw new ServletException(e);
309             } catch (UnknownTicketException e) {
310                 log.logCritical("UnknownTicketException");
311                 throw new ServletException(e);
312             } catch (InoperableStateException e) {
313                 log.logCritical("InoperableStateException");
314                 throw new ServletException(e);
315             }
316         }
317         if (userData != null) {
318             // need userorg as an attribute in the JSP to be able to print
319             // instructions on where to update the optional or mandatory info
320             String [] userorgarray = (String[]) userData.get("o");
321             String userorg = bundle.getString("unknown_userorg");
322             if (userorgarray != null && userorgarray.length > 0) {
323                 userorg = userorgarray[0];
324             }
325             request.setAttribute("userorg", userorg);
326             // Username for display after login
327             String[] usernamearray = (String[]) userData.get("eduPersonPrincipalName");
328             String username = null;
329             if (usernamearray != null && usernamearray.length > 0) {
330                 username = usernamearray[0];
331             }
332             request.setAttribute("username", username);
333             // Pictures are stored for later use in the JSP
334             String[] picarr = (String[]) userData.get(new String("jpegPhoto"));
335             if (picarr != null && picarr.length > 0) {
336                 request.setAttribute(PictureServlet.PICTURE_ATTRIBUTE, picarr);
337             }
338             
339             Vector tabledata = printTableToVector(userData, bundle);
340             request.setAttribute("tabledata", tabledata);
341 
342             /* Configured values */
343             request.setAttribute(RequestUtil.ATTR_ORGANIZATIONS,
344                                  RequestUtil.parseConfig(config, RequestUtil.PROP_ORG, bundle.getLocale().getLanguage()));
345             request.setAttribute(RequestUtil.ATTR_LANGUAGES,
346                                  RequestUtil.parseConfig(config, RequestUtil.PROP_LANGUAGE, RequestUtil.PROP_COMMON));
347             request.setAttribute(RequestUtil.ATTR_BASE_URL,
348                                  config.getProperty(RequestUtil.PROP_INFORMATION_URL_PREFIX) + "?"
349                                  + config.getProperty(RequestUtil.PROP_LOGIN_TICKET_PARAM) + "=" + ticketId);
350             request.setAttribute(RequestUtil.ATTR_SELECTED_LANG, bundle.getLocale());
351             request.setAttribute(RequestUtil.PIC_LINK, config.getProperty(RequestUtil.PIC_LINK));
352 
353             // only print language menu if SSO is enabled
354             final String denySSOChoice = RequestUtil.getCookieValue(config.getProperty(RequestUtil.PROP_COOKIE_DENYSSO), request.getCookies());
355             final boolean denySSO;
356             if (denySSOChoice == null || denySSOChoice.equals("false") || denySSOChoice.equals("")) {
357                 // Allow SSO.
358                 request.setAttribute(RequestUtil.ATTR_SELECTED_DENYSSO, new Boolean(false));
359                 denySSO = false;
360 
361             } else {
362                 // Deny SSO.
363                 request.setAttribute(RequestUtil.ATTR_SELECTED_DENYSSO, new Boolean(true));
364                 denySSO = true;
365             }
366             // print the table in the JSP
367             final RequestDispatcher rd = getServletContext().getNamedDispatcher("Information.JSP");
368             rd.forward(request, response);
369         } else {
370             // call doPost() to get the user attributes from the Controller
371             doPost(request, response);
372         }
373     }
374 
375     /***
376      * Implements the HttpServlet.doPost method.
377      *
378      * @param request   The HTTP request.
379      * @param response  The HTTP response.
380      * @throws IOException      Required by interface.
381      * @throws ServletException
382      *           If the Moria controller throws an exception.
383      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
384      */
385     public final void doPost(final HttpServletRequest request, final HttpServletResponse response)
386     throws ServletException, IOException {
387 
388         String jspLocation = getServletContext().getInitParameter("jsp.location");
389         log.logDebug("jsp.location is '" + jspLocation + "'");
390         String moriaID = null;
391         boolean error = false;
392 
393         String attributes = getAllAttributes();
394         String urlPrefix = (String) request.getAttribute("urlPrefix") + "?moriaID=";
395         String urlPostfix = "";
396         String principal = PRINCIPAL;
397 
398         try {
399             MoriaController.initController(getServletContext());
400             log.logDebug("Requested attributes: " + attributes);
401             log.logDebug("URL prefix: " + urlPrefix);
402             log.logDebug("URL postfix: " + urlPostfix);
403             log.logDebug("Principal: " + principal);
404             moriaID = MoriaController.initiateAuthentication(attributes.split(","),
405                                                              urlPrefix, urlPostfix, true, principal);
406             log.logDebug("Moria ID is now " + moriaID);
407 
408         } catch (IllegalInputException e) {
409             log.logCritical("IllegalInputException");
410             throw new ServletException(e);
411         } catch (AuthorizationException e) {
412             log.logCritical("AuthorizationException");
413             throw new ServletException(e);
414         } catch (InoperableStateException e) {
415             log.logCritical("InoperableStateException");
416             throw new ServletException(e);
417         }
418 
419         if (!error) {
420             Properties config = (Properties) getServletContext().getAttribute(RequestUtil.PROP_CONFIG);
421             log.logDebug("Configuration: " + config.toString());
422             String redirectURL = config.getProperty(RequestUtil.PROP_LOGIN_URL_PREFIX)
423                  + "?" + config.getProperty(RequestUtil.PROP_LOGIN_TICKET_PARAM) + "=" + moriaID;
424             log.logDebug("Redirect URL: " + redirectURL);
425             response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
426             response.setHeader("Location", redirectURL);
427         } else {
428             // should never happen
429             throw new ServletException("Unknown error in InformationServlet");
430         }
431     }
432 
433     /***
434      * Builds a list of all possible user attributes.
435      *
436      * @return Comma separated list of attributes.
437      */
438     private String getAllAttributes() {
439         String acc = "";
440         HashMap feideattribs = getAttribs();
441         for (Iterator iterator = feideattribs.keySet().iterator(); iterator.hasNext();) {
442             String key = (String) iterator.next();
443             acc += key;
444             if (iterator.hasNext()) acc += ",";
445         }
446         return acc;
447     }
448 }
449