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: StatusServlet.java,v 1.17 2006/02/10 11:24:39 indal Exp $
19   */
20  
21  package no.feide.moria.servlet;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  import java.util.Iterator;
27  import java.util.Map;
28  import java.util.HashMap;
29  import java.util.TreeMap;
30  import java.util.Properties;
31  import java.util.ResourceBundle;
32  import java.util.Vector;
33  import javax.net.ssl.HttpsURLConnection;
34  import java.net.URL;
35  import java.net.Authenticator;
36  import java.net.PasswordAuthentication;
37  import java.net.MalformedURLException;
38  
39  import javax.servlet.ServletException;
40  import javax.servlet.http.HttpServletRequest;
41  import javax.servlet.http.HttpServletResponse;
42  import javax.xml.parsers.SAXParser;
43  import javax.xml.parsers.SAXParserFactory;
44  
45  import no.feide.moria.controller.AuthenticationException;
46  import no.feide.moria.controller.AuthorizationException;
47  import no.feide.moria.controller.DirectoryUnavailableException;
48  import no.feide.moria.controller.IllegalInputException;
49  import no.feide.moria.controller.InoperableStateException;
50  import no.feide.moria.controller.MoriaController;
51  import no.feide.moria.log.MessageLogger;
52  import no.feide.moria.store.MoriaStore;
53  import no.feide.moria.store.MoriaStoreException;
54  import no.feide.moria.store.MoriaStoreFactory;
55  
56  /***
57   * The StatusServlet shows the status of Moria.
58   * @version $Revision: 1.17 $
59   */
60  public class StatusServlet
61  extends MoriaServlet {
62  
63      /*** Used for logging. */
64      private final MessageLogger log = new MessageLogger(StatusServlet.class);
65  
66      /***
67       * List of parameters required by <code>StatusServlet</code>.
68       * <br>
69       * <br>
70       * Current required parameters are:
71       * <ul>
72       * <li><code>RequestUtil.PROP_BACKENDSTATUS_STATUS_XML</code>
73       * </ul>
74       * @see RequestUtil#PROP_BACKENDSTATUS_STATUS_XML
75       */
76      private static final String[] REQUIRED_PARAMETERS = {
77              RequestUtil.PROP_BACKENDSTATUS_STATUS_XML,
78              RequestUtil.PROP_COOKIE_LANG };
79      
80      /***
81       * 
82       * @return the required parameters for this servlet.
83       */
84      public static String[] getRequiredParameters() {
85          return REQUIRED_PARAMETERS;
86      }
87  
88      /***
89       * A hash map containing the attributes for a test-user.
90       * Each item in the hashmap maps from a user name to an
91       * backendStatusUser class instance
92       */
93      private HashMap backendDataUsers = null;
94      
95      /***
96       * Monitor for the status xml file.
97       */
98      private FileMonitor statusFileMonitor = null;
99      
100     /***
101      * The organization the test user comes from.
102      */
103     private static final String STATUS_ATTRIBUTE = "eduPersonAffiliation";    
104     
105     /***
106      * The name of the service.
107      */
108     private static final String STATUS_PRINCIPAL = "status";
109     
110     /***
111      * Implements a simple xml parser that parses the status.xml file
112      * into a HashMap with BackendStatusUser instances.
113      *
114      * @see BackendStatusHandler
115      * @see BackendStatusUser
116      */
117     public final synchronized HashMap getBackendStatusData() {
118         if (statusFileMonitor == null || statusFileMonitor.hasChanged()) {
119           Properties config = getConfig();
120           if (config != null) {
121             BackendStatusHandler handler = new BackendStatusHandler();
122             SAXParserFactory factory = SAXParserFactory.newInstance();
123             try {
124                String filename = (String) config.get(RequestUtil.PROP_BACKENDSTATUS_STATUS_XML);
125                SAXParser saxParser = factory.newSAXParser();
126                saxParser.parse(new File(filename), handler);
127                statusFileMonitor = new FileMonitor(filename);
128             } catch (Throwable t) {
129               log.logCritical("Error parsing status.xml");
130             } finally {
131               backendDataUsers = handler.getAttribs();
132             }
133           }
134         }
135         return backendDataUsers;
136     }
137     
138     private void printTable() {
139         HashMap backendStatusData = getBackendStatusData();
140         
141     }
142 
143     /***
144      * Checks the config
145      * 
146      * @param bundle
147      * @return
148      */
149     private Vector checkConfigStatus(ResourceBundle bundle) {
150         Vector statusConfig = new Vector();
151         // InformationServlet
152         try {
153             this.getServletConfig(InformationServlet.getRequiredParameters(), log);
154         } catch (IllegalStateException e) {
155             String errorMsg = bundle.getString("config_info");
156             statusConfig.add(errorMsg);
157         }
158 
159         // LoginServlet
160         try {
161             this.getServletConfig(LoginServlet.getRequiredParameters(), log);
162         } catch (IllegalStateException e) {
163             String errorMsg = bundle.getString("config_login");
164             statusConfig.add(errorMsg);
165         }
166 
167         // StatisticsServlet
168         try {
169             this.getServletConfig(StatisticsServlet.getRequiredParameters(), log);
170         } catch (IllegalStateException e) {
171             String errorMsg = bundle.getString("config_statistics");
172             statusConfig.add(errorMsg);
173         }
174 
175         return statusConfig; 
176     }
177     
178     /***
179      * Checks the modules
180      * 
181      * @param bundle
182      * @return
183      */
184     private Vector checkModules(ResourceBundle bundle) {
185         Map statusMap = MoriaController.getStatus();
186         Vector statusMsg = new Vector();
187         
188         if (statusMap != null) {
189 
190             String[] states = {"moria", "init", "am", "dm", "sm", "web"};
191             Map moduleNames = new HashMap();
192             moduleNames.put("moria", "Moria");
193             moduleNames.put("init", "Controller");
194             moduleNames.put("am", "Authorization manager");
195             moduleNames.put("dm", "Directory manager");
196             moduleNames.put("sm", "Store manager");
197             moduleNames.put("web", "Web application");
198 
199             for (int i = 0; i < states.length; i++) {
200 
201                 Object stateObject = statusMap.get(states[i]);
202                 Boolean isReady = new Boolean(false);
203 
204                 if (stateObject instanceof Boolean) {
205                     isReady = (Boolean) stateObject;
206                 }
207 
208                 if (states[i].equals("moria") && isReady.booleanValue()) {
209                     statusMsg = new Vector(); // return an empty vector
210                     break;
211                 } else if (!isReady.booleanValue()) {                
212                     String errormsg = bundle.getString("module_1") + moduleNames.get(states[i]) + bundle.getString("module_2") + "<br>";
213                     statusMsg.add(errormsg);
214                 }
215             }
216         }
217         return statusMsg;
218     }
219     
220     /***
221      * Checks the SOAP page.
222      * 
223      * @param bundle
224      * @return
225      */
226     private Vector checkSoap(ResourceBundle bundle) {
227         
228         Vector soapMsg = new Vector();
229         
230         try {
231             URL url = new URL("https://login.feide.no/moria2/v2_1/Authentication?wsdl");
232             java.net.Authenticator.setDefault(new Authenticator() {
233                 protected java.net.PasswordAuthentication getPasswordAuthentication() 
234                 {
235                     char[] passwd = {'d', 'e', 'm', 'o', '_', 's', 'e', 'r', 'v', 'i', 'c', 'e'};
236                     return new PasswordAuthentication(new String("demo_service"), passwd);
237                 }
238             });
239                      
240             
241             HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
242             connection.setRequestMethod("GET");
243             connection.connect();
244             int code = connection.getResponseCode();
245             if (code != HttpsURLConnection.HTTP_OK) {
246               String err = new String(bundle.getString("soap_error") + new Integer(code).toString());
247               soapMsg.add(err);
248             }
249         //should not happen    
250         } catch(MalformedURLException e) {
251             String err = bundle.getString("soap_exception1");
252             soapMsg.add(err);
253         } catch (IOException e) {
254             String err = bundle.getString("soap_exception2");
255             soapMsg.add(err);
256         }
257         
258         return soapMsg;
259     }
260     
261     public Vector checkBackend(ResourceBundle bundle) {
262         
263         Vector backendMsg = new Vector();
264         
265         for (Iterator iterator = backendDataUsers.keySet().iterator(); iterator.hasNext();) {
266             String key = (String) iterator.next();
267             BackendStatusUser userData = (BackendStatusUser) backendDataUsers.get(key);
268             String org = userData.getOrganization();
269             try {
270                 final Map attributes = MoriaController.directNonInteractiveAuthentication(new String[] {STATUS_ATTRIBUTE},
271                         userData.getName(), userData.getPassword(), STATUS_PRINCIPAL);
272             } catch (Exception e) {
273                 String err = bundle.getString("ldap_error") + org;
274                 backendMsg.add(err);
275             }
276         }
277     return backendMsg;
278     }
279 
280     /***
281      * Handles the GET requests.
282      * @param request
283      *            The HTTP request object.
284      * @param response
285      *            The HTTP response object.
286      * @throws java.io.IOException
287      *             If an input or output error is detected when the servlet
288      *             handles the GET request.
289      * @throws javax.servlet.ServletException
290      *             If the request for the GET could not be handled.
291      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
292      *      javax.servlet.http.HttpServletResponse)
293      */
294     public final void doGet(final HttpServletRequest request, final HttpServletResponse response)
295     throws IOException, ServletException {
296         getBackendStatusData();
297         
298         Properties config = getConfig();
299         response.setContentType("text/html");
300         PrintWriter out = response.getWriter();
301         String docType = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n";
302                 
303         /* Resource bundle. */
304         String language = null;
305         String langFromCookie = null;
306         if (config != null && request.getCookies() != null) {
307            langFromCookie = RequestUtil.getCookieValue((String) config.get(RequestUtil.PROP_COOKIE_LANG), request.getCookies());
308         }
309         // Update cookie if language has changed
310         if (request.getParameter(RequestUtil.PARAM_LANG) != null) {
311             language = request.getParameter(RequestUtil.PARAM_LANG);
312             response.addCookie(RequestUtil.createCookie(
313                     (String) config.get(RequestUtil.PROP_COOKIE_LANG),
314                     language,
315                     new Integer((String) config.get(RequestUtil.PROP_COOKIE_LANG_TTL)).intValue()));
316         }
317 
318         /* Get bundle, using either cookie or language parameter */
319         final ResourceBundle bundle = RequestUtil.getBundle(
320                 RequestUtil.BUNDLE_STATUSSERVLET,
321                 language, langFromCookie, null,
322                 request.getHeader("Accept-Language"), 
323                 (String) config.get(RequestUtil.PROP_LOGIN_DEFAULT_LANGUAGE));
324         
325         // Header
326         out.println("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>");
327         out.println(docType + "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=" + bundle.getLocale() + ">"); 
328         out.println("<head><link rel=\"icon\" href=\"/favicon.ico\" type=\"image/png\">"); 
329         out.println("<style type=\"text/css\">\n@import url(\"../resource/stil.css\");\n</style>");
330         out.println("<link rel=\"author\" href=\"mailto:" + config.get(RequestUtil.RESOURCE_MAIL)+ "\">");
331         out.println("<title>" + bundle.getString("header_title") + "</title></head><body>");
332         
333         //Layout table
334         out.println("<table summary=\"Layout-tabell\" class=\"invers\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">");
335         out.println("<tbody><tr valign=\"middle\">");
336         out.println("<td class=\"logo\" width=\"76\"><a href=" + config.get(RequestUtil.RESOURCE_LINK) + ">");
337         out.println("<img src=\"../resource/logo.gif\" alt=" + config.get(RequestUtil.PROP_FAQ_OWNER) + " border=\"0\" height=\"41\" width=\"76\"></a></td>");
338         out.println("<td width=\"0%\"><a class=\"noline\" href=" + config.get(RequestUtil.RESOURCE_LINK) + ">");
339         out.println(bundle.getString("header_feide") + "</a></td>");
340         out.println("<td width=\"35%\">&nbsp;</td>");
341         
342         	// Language selection
343             TreeMap languages = (TreeMap)RequestUtil.parseConfig(config, RequestUtil.PROP_LANGUAGE, RequestUtil.PROP_COMMON);
344             Iterator it = languages.keySet().iterator();
345             while(it.hasNext()) {
346                 String longName = (String) it.next();
347                 String shortName  = (String) languages.get(longName);
348                 if (RequestUtil.ATTR_SELECTED_LANG.equals(shortName)) {
349                     out.println("[" + longName + "]");
350                 } else
351                     out.println("<td align=\"centre\"><small><a class=\"invers\" href ="
352                             + config.get(RequestUtil.PROP_FAQ_STATUS) + "?" + RequestUtil.PARAM_LANG + "=" + shortName + ">" + longName
353                             + "</a></small></td>");
354                 }
355          
356         //More Layout
357         out.println("<td class=\"dekor1\" width=\"100%\">&nbsp;</td></tr></tbody></table>");
358         out.println("<div class=\"midt\">");
359         out.println("<table cellspacing=\"0\">");
360         out.println("<tbody><tr valign=\"top\">");
361         out.println("<td class=\"kropp\">");
362                 
363         // Check status
364         Vector allerrors = new Vector();
365         allerrors.addAll(checkModules(bundle));
366         allerrors.addAll(checkConfigStatus(bundle));
367         allerrors.addAll(checkSoap(bundle));
368         allerrors.addAll(checkBackend(bundle));
369         if (allerrors.size()>0) {
370             for (int i = 0; i < allerrors.size(); i++) {
371             out.println("<font color=#FFFFFF>" + (String)(allerrors.get(i)) + "</font>" + "<br>");
372             }
373         } else if (allerrors.size() == 0) {
374             out.println("<font color=#FFFFFF>" + bundle.getString("ready_msg") + "</font>");
375         }
376         
377         // Prepare to check test users.
378         out.println("<p><table border=1><tr><th>" + bundle.getString("table_organization") + "</th><th>" + bundle.getString("table_status") + "</th></tr>");
379 
380         // Start checking a new user.
381         for (Iterator iterator = backendDataUsers.keySet().iterator(); iterator.hasNext();) {
382             String key = (String) iterator.next();
383             BackendStatusUser userData = (BackendStatusUser) backendDataUsers.get(key);
384             out.println("<tr><td>" + userData.getOrganization() + "</td>");
385             try {
386                 final Map attributes = MoriaController.directNonInteractiveAuthentication(new String[] {STATUS_ATTRIBUTE},
387                         userData.getName(), userData.getPassword(), STATUS_PRINCIPAL);
388             
389                 // This test user worked.
390                 out.println("<td>OK</td>");
391                 
392             } catch (AuthenticationException e) {
393                 log.logWarn("Authentication failed for: " + userData.getName() + ", contact: " + userData.getContact());
394                 String message = "<a href=mailto:" + userData.getContact() + "?subject=" + userData.getOrganization() 
395                 + ":%20" + bundle.getString("subject_authentication") + userData.getName() + ">" + bundle.getString("error_help");
396                 out.println("<td>" + bundle.getString("error_authentication") + message + "</a></td></tr>");
397             } catch (DirectoryUnavailableException e) {
398                 log.logWarn("The directory is unavailable for: " + userData.getName() + ", contact: " + userData.getContact());
399                 String message = "<a href=mailto:" + userData.getContact() + "?subject=" + userData.getOrganization() 
400                 + ":%20" + bundle.getString("subject_directory") + userData.getName() + ">" + bundle.getString("error_help");
401                 out.println("<td>" + bundle.getString("error_directory") + message + "</a></td></tr>");            
402             } catch (AuthorizationException e) {
403                 log.logWarn("Authorization failed for: " + userData.getName() + ", contact: " + userData.getContact());
404                 String message = "<a href=mailto:" + userData.getContact() + "?subject=" + userData.getOrganization() 
405                 + ":%20" + bundle.getString("subject_authorization") + userData.getName() + ">" + bundle.getString("error_help");
406                 out.println("<td>" + bundle.getString("error_authorization") + message + "</a></td></tr>");            
407             } catch (IllegalInputException e) {
408                 log.logWarn("Illegal input for: " + userData.getName() + ", contact: " + userData.getContact());
409                 String message = "<a href=mailto:" + userData.getContact() + "?subject=" + userData.getOrganization() 
410                 + ":%20" + bundle.getString("subject_illegal") + userData.getName() + ">" + bundle.getString("error_help");
411                 out.println("<td>" + bundle.getString("error_illegal") + message + "</a></td></tr>");            
412             } catch (InoperableStateException e) {
413                 log.logWarn("Inoperable state for: " + userData.getName() + ", contact: " + userData.getContact());
414                 //Only print moria-support adress if moria is inoperable
415                 if (userData.getOrganization().equals("Uninett")){
416                     String message = "<a href=mailto:" + userData.getContact() + "?subject=" + userData.getOrganization() 
417                     + ":%20" + bundle.getString("subject_inoperable") + userData.getName() + ">" + bundle.getString("error_help");
418                     out.println("<td>" + bundle.getString("error_inoperable") + message + "</a></td></tr>");
419                 } else {
420                     out.println("<td>" + bundle.getString("error_inoperable"));                
421                 }
422             } finally {
423             
424                 // Finish the table row.
425                 out.println("</tr>");
426             
427             }
428         }
429         
430         // Done with all test users.
431         out.println("</table></p>");
432 
433         //Layout
434         out.println("</tr>");
435         out.println("</table>");
436         out.println("</tbody>");
437         out.println("</div>");
438 
439         out.println("<p>");
440         out.println("<table summary=\"Layout-tabell\" class=\"invers\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">");
441         out.println("<tbody><tr class=\"bunn\" valign=\"middle\">");
442         out.println("<td class=\"invers\" align=\"left\"><small><a class=\"invers\" href=\"mailto:"
443                 + config.get(RequestUtil.RESOURCE_MAIL) + "\">" + config.get(RequestUtil.RESOURCE_MAIL) + "</a></small></td>");
444         out.println("<td class=\"invers\" align=\"right\"><small>" + config.get(RequestUtil.RESOURCE_DATE) + "</small></td>");
445         out.println("</tr></tbody></table></p>");
446         
447         // Finish up.
448         out.println("</body></html>");
449         
450     }
451     
452     /***
453      * Get this servlet's configuration from the web module, given by
454      * <code>RequestUtil.PROP_CONFIG</code>.
455      * @return The last valid configuration.
456      * @throws IllegalStateException
457      *             If unable to read the current configuration from the servlet
458      *             context, and there is no previous configuration. Also thrown
459      *             if any of the required parameters (given by
460      *             <code>REQUIRED_PARAMETERS</code>) are not set.
461      * @see #REQUIRED_PARAMETERS
462      * @see RequestUtil#PROP_CONFIG
463      */
464     private Properties getConfig() {
465         try {
466             return getServletConfig(getRequiredParameters(), log);
467         }
468         catch (IllegalStateException e) {
469          return null;   
470         }
471     }
472     
473 }
474