Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/RuntimeCollective/webapps/tag/CheckLogonTag.java


1   /* $Header: /home/CVS/rjp/src/com/RuntimeCollective/webapps/tag/CheckLogonTag.java,v 1.55 2003/10/13 15:42:44 fabrice Exp $
2    * $Revision: 1.55 $
3    * $Date: 2003/10/13 15:42:44 $
4    *
5    * ====================================================================
6    *
7    * Josephine : http://www.runtime-collective.com/josephine/index.html
8    *
9    * Copyright (C) 2003 Runtime Collective
10   * 
11   * This product includes software developed by the
12   * Apache Software Foundation (http://www.apache.org/).
13   *
14   * This library is free software; you can redistribute it and/or
15   * modify it under the terms of the GNU Lesser General Public
16   * License as published by the Free Software Foundation; either
17   * version 2.1 of the License, or (at your option) any later version.
18   *
19   * This library is distributed in the hope that it will be useful,
20   * but WITHOUT ANY WARRANTY; without even the implied warranty of
21   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22   * Lesser General Public License for more details.
23   *
24   * You should have received a copy of the GNU Lesser General Public
25   * License along with this library; if not, write to the Free Software
26   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
27   *
28   */
29  
30  package com.RuntimeCollective.webapps.tag;
31  
32  import com.RuntimeCollective.webapps.bean.User;
33  import com.RuntimeCollective.webapps.bean.TrackedUser;
34  import com.RuntimeCollective.webapps.bean.UserGroup;
35  import com.RuntimeCollective.webapps.bean.UserGroupType;
36  import com.RuntimeCollective.webapps.RuntimeParameters;
37  import com.RuntimeCollective.webapps.bean.LoginCookie;
38  import com.RuntimeCollective.webapps.bean.SearchResult;
39  import com.RuntimeCollective.webapps.IndexedEntityBeanStore;
40  import com.RuntimeCollective.webapps.SaveRequestURLFilter;
41  import com.RuntimeCollective.permission.SecurityConstants;
42  
43  import java.io.IOException;
44  import java.lang.reflect.Method;
45  import java.util.ArrayList;
46  import java.util.Date;
47  import java.util.Enumeration;
48  import java.util.Iterator;
49  import java.util.Set;
50  import java.util.StringTokenizer;
51  import javax.servlet.http.HttpSession;
52  import javax.servlet.http.HttpServletRequest;
53  import javax.servlet.jsp.JspException;
54  import javax.servlet.jsp.JspWriter;
55  import javax.servlet.jsp.PageContext;
56  import javax.servlet.jsp.tagext.TagSupport;
57  import javax.servlet.ServletContext;
58  
59  import org.apache.struts.action.Action;
60  import org.apache.struts.action.ActionError;
61  import org.apache.struts.action.ActionErrors;
62  import org.apache.struts.util.MessageResources;
63  
64  
65  /**
66   * Check for a valid User logged on in the current session under
67   * the key <code>RuntimeParameters.getParam("logonUserKey")</code>.
68   * If there is no such user, forward control to the logon page.
69   * <p>
70   * <b>Important:</b> This tag must be placed on a jsp page before anything is written to the response.  It's safest to always put it at the top.
71   * <p>This tag takes the following optional parameters:
72   * <ul>
73   * <li><code>page</code> - the page to go to if the user is not logged in (defaults to /logon.jsp)
74   * <li><code>group</code> - if set then the tag will only admit users if they are logged in, and are members of the UserGroup with this name.
75   * <li><code>groups</code> - if set then the tag will only admit users if they are logged in, and are members of any UserGroups with these names.
76   * <li><code>groupType</code> - if set then the tag will only admit users if they are logged in, and are members of any group which has this UserGroupType.
77   * <li><code>role</code> - if set then the tag will only allow users in if they are logged in, and have this role.You may seperate roles with commas e.g. "0,1,2" - this will allow users in if they have ANY of the specified roles.For example, inserting 
78   * <pre><code>&lt;%@ taglib uri="/WEB-INF/runtime-struts.tld" prefix="rs" %>
79   * &lt;rs:checkLogon role="0" page="home.jsp"/></code></pre>
80   * into a jsp page will check that the user is logged in, with role 0, and if not will forward them to the home page.
81   * <p>An example of how to use the <code>groups</code> parameter to allow only members of the Administrators or Editors group:</p>
82   * <p><code>&lt;rs:belongsTo groups='&lt;%= new String[]{&quot;Administrators&quot;, &quot;Editors&quot;} %&gt;'&gt;</code></p>
83   * </ul>
84   *
85   * @see com.RuntimeCollective.webapps.bean.User
86   * @see com.RuntimeCollective.webapps.bean.UserGroup
87   * @see com.RuntimeCollective.webapps.bean.UserGroupType
88   *
89   * @version $Id: CheckLogonTag.java,v 1.55 2003/10/13 15:42:44 fabrice Exp $
90   */
91  public class CheckLogonTag extends TagSupport {
92  
93      /** Where to put the list of group names on the request when the user is not in the groups specified. */
94      public static final String LIST_GROUP_NAMES_KEY = "ugpermrule_uncleared_group_name_list";
95  
96      /** The key of the session-scope bean we look for. */
97      protected static String name = RuntimeParameters.getParam("logonUserKey");
98  
99      /** The page to which we should forward for the user to log on. */
100     protected String page = "/logon.jsp";
101 
102     /** The role(s) that the user must have in order to access this page. */
103     protected String role = null;
104 
105     /** The name of the group (or names, seperated by commas) that the user must be a member of [Optional] */
106     private String iGroup; 
107 
108     /** The names of the groups, at least one of which the user must be a member of [Optional] */
109     private String[] iGroups; 
110 
111     /** The user must be a member of at least one group that has this group type [Optional] */
112     private String iGroupType; 
113 
114     /** The cookie name to look for. */
115     protected String cookie = LoginCookie.COOKIE_NAME;
116 
117     /** Return the forward page. */
118     public String getPage() {
119   return (this.page);
120     }
121 
122     /** Set the forward page.
123      * @param page The new forward page
124      */
125     public void setPage(String page) {
126   this.page = page;
127     }
128 
129     /** Return the required role. */
130     public String getRole() { return this.role; }
131 
132     /** Set the required role. */
133     public void setRole(String role) { this.role = role; }
134 
135 
136     /** Get the name (or names, seperated by commas) of the group that the user must be a member of [Optional] */
137     public String getGroup() { return iGroup; }
138 
139     /** Set the name (or names, seperated by commas) of the group that the user must be a member of [Optional] */
140     public void setGroup(String group) { iGroup = group; }
141 
142 
143     /** Get the names of the groups, at least one of which the user must be a member of [Optional] */
144     public String[] getGroups() { return iGroups; }
145 
146     /** Set the names of the groups, at least one of which the user must be a member of [Optional] */
147     public void setGroups(String[] groups) { iGroups = groups; }
148 
149 
150     /** Get the name of the group type containing a group that the user must be a member of [Optional] */
151     public String getGroupType() { return iGroupType; }
152 
153     /** Set the name of the group type containing a group that the user must be a member of [Optional] */
154     public void setGroupType(String groupType) { iGroupType = groupType; }
155 
156 
157     /** Constructor **/
158     public CheckLogonTag() {
159         // initialised once and for all - actually this didn't seem to work, either
160         //name = RuntimeParameters.getParam("logonUserKey");
161     }
162 
163 
164     /** Defer our checking until the end of this tag is encountered.
165      * @exception JspException if a JSP exception has occurred
166      */
167     public int doStartTag() throws JspException {
168         return (SKIP_BODY);
169     }
170 
171     /**
172      * Perform our logged-in user check by looking for the existence of
173      * a session scope bean under the specified name, using <code>getUser</code>.
174      * Control is forwarded using <code>forwardControl</code>, based on whether
175      * <code>getUser</code> returns a User or null.
176      * @exception JspException if a JSP exception has occurred
177      */
178     public int doEndTag() throws JspException {
179 
180   // Get our user; will be null if there isn't one
181   User user = getLoggedOnUser((HttpServletRequest) pageContext.getRequest(), pageContext.getSession());
182 
183   if (user!=null) {
184       RuntimeParameters.logDebug(this,"User found, id="+user.getId());
185   } else {
186       RuntimeParameters.logDebug(this,"No user found");
187   }
188 
189   // Forward control based on whether this user is valid
190   return forwardControl( checkValid(user) );
191     }
192 
193 
194     /**
195      * CheckLogonTag (or a subclass)'s "getUser(HttpServletRequest request, HttpSession session)" method
196      */
197     private static Method getUserMethod = null;
198 
199     /**
200      * Get the currently logged-on user from the given session.
201      * This uses the "getUser" method of CheckLogonTag, or a subclass defined in web.xml as "checkLogonTag".
202      */
203     public static User getLoggedOnUser(HttpServletRequest request, HttpSession session) throws JspException {
204 
205       RuntimeParameters.logDebug("CheckLogonTag", "Getting logged on user");
206 
207   // If "getUserMethod" is null, we need to find it
208   if (getUserMethod == null) {
209 
210       // Find if we're using a subclass of CheckLogonTag, instead of this actual one
211       String logonTag = "";
212       // Get the subclass of CheckLogonTag we're using
213       if (RuntimeParameters.getParam("checkLogonTag") != null) {
214     logonTag = RuntimeParameters.getParam("checkLogonTag");
215       }
216         
217       // param.checkLogonTag has not been set; use CheckLogonTag itself
218       if (logonTag.equals("")) {
219             return getUser(request, session);
220       }  
221       
222       // Call its "getUser" method, and store it in the static variable "getUserMethod"
223       try {
224             Class tagClass = Class.forName(logonTag);
225             getUserMethod = tagClass.getMethod("getUser", new Class[]{
226                 Class.forName("javax.servlet.http.HttpServletRequest"),
227                 Class.forName("javax.servlet.http.HttpSession")});
228             
229       } catch (Exception e) {
230             throw new JspException("Problem finding the getUser method for "+logonTag+": "+e);
231       }
232   }
233   
234   // Use the stored "getUserMethod" method
235   try {
236       User loggedUser = null;
237       Object obj = getUserMethod.invoke(null, new Object[]{ request, session });
238       if (obj != null) {
239             loggedUser = (User) obj;
240             loggedUser = (User) RuntimeParameters.getStore().get(User.class.getName(), loggedUser.getId());
241       }
242       return loggedUser;
243   } catch (Exception e) {
244       throw new JspException("Problem executing the getUser method: "+e);
245   }
246 
247     }
248 
249 
250     /**
251      * Perform our logged-in user check by looking for the existence of
252      * a session scope bean under the specified name.  If this bean is not
253      * present, check for a cookie called "LoginCookie.COOKIE_NAME".
254      * If no such cookie exists, the current page is stored on the Session with <code>PutReturnURLOnSession</code>
255      * and control is forwarded to the specified logon page.
256      * If we find a user from a cookie, set it on the session with <code>foundUserFromCookie</code>.
257      *
258      * @return Will return the logged-in User, if there is one, or <code>null</code> if not.
259      */
260     public static User getUser(HttpServletRequest request, HttpSession session) throws JspException {
261 
262         // Is there a user logged on?
263         User user = null;
264         
265         if (session!=null) {
266             // Check on the session
267             user = (User) session.getAttribute(name);
268             RuntimeParameters.logDebug("CheckLogonTag", "getUser: Session attribute '"+name+"' checked: "+user);
269         }
270         
271         if (user == null) {
272             if (request != null) {
273                 // Check the cookie
274                 user = LoginCookie.getUser(request.getCookies());
275                 RuntimeParameters.logDebug("CheckLogonTag", "getUser: LoginCookie checked: "+user);
276                 // If we've found a user, put them on the session
277                 if (user != null) {
278                     foundUserFromCookie(user, request);
279                 }
280             }
281         }
282 
283         if (user == null) {
284             // last resort, try to get the AuthToken
285             // the session may have the AuthToken, but not the User object,
286             // for example after serialisation
287             Integer authTokenId = (Integer) session.getAttribute(SecurityConstants.AUTH_TOKEN);
288             RuntimeParameters.logDebug("CheckLogonTag", "getUser: AuthToken checked : "+authTokenId);
289             if (authTokenId != null) {
290                 // get the user from the id
291                 user = (User) RuntimeParameters.getStore().get(User.class.getName(), authTokenId.intValue());
292                 if (user != null) {
293                     foundUserFromAuthToken(user, request);
294                 }
295             }
296         }
297         
298         // Don't do that anymore, as this method is called from outside the Tag,
299         // and it ended up setting unwanted return urls
300         //   // If still no user, we don't have one; 
301         //   // put the return url on the session
302         //   if (user == null) {
303         //       putReturnURLOnSession(request, session);
304         //   }
305         
306         // This may return null!
307         return user;
308     }
309     
310     /**
311      * If the group attribute is set, check the user is in the specified group.
312      * <p>Otherwise, if the groupType attribute is set, check the user is in at least one
313      * group of the given type.
314      * <p>Otherwise, if the role attribute is set, then check the user has the required role(s)
315      * by calling <code>checkRole()</code>.
316      */
317      public boolean checkValid(User user) throws JspException {
318          // Is there a valid user logged on?
319          boolean valid = false;
320 
321          RuntimeParameters.logDebug(this, "checkValid: user="+user);
322          
323          // Do we have a user?
324          if (user != null) {
325              
326              RuntimeParameters.logDebug(this, "checkValid: getGroup()="+getGroup());
327              
328              // Is the user in the specified group(s)?
329              if (getGroup()!=null) {
330                  UserGroup group = RuntimeParameters.getUserGroups().getForName( getGroup() );
331                  valid = group.contains( user );
332                  
333                  // if not valid, we put the group name (as a list) on the request,
334                  // so the resulting JSP knows what to display
335                  ArrayList groupNames = new ArrayList();
336                  groupNames.add(getGroup());
337                  if (!valid) {
338                      pageContext.getRequest().setAttribute(LIST_GROUP_NAMES_KEY, groupNames);
339                  }
340 
341              } else if (getGroups() != null) {
342                  for (int i=0; i<getGroups().length; i++) {
343                      UserGroup group = RuntimeParameters.getUserGroups().getForName( getGroups()[i] );
344                      valid = valid || group.contains( user );
345                  }
346 
347                  // if not valid, we put the list of group names on the request,
348                  // so the resulting JSP knows what to display
349                  if (!valid) {
350                      // put together the list of group names
351                      ArrayList groupNames = new ArrayList();
352                      for (int i=0; i<getGroups().length; i++) {
353                          groupNames.add(getGroups()[i]);
354                      }
355                      pageContext.getRequest().setAttribute(LIST_GROUP_NAMES_KEY, groupNames);
356                  }
357 
358              } else if (getGroupType()!=null) {
359                  UserGroupType type = UserGroupType.getForName( getGroupType() );
360                  if (type != null) {
361                      Iterator allGroups = RuntimeParameters.getUserGroups().getAllGroups( type ).iterator();
362                      UserGroup group;
363                      SearchResult result;
364                      while (valid == false && allGroups.hasNext()) {
365                          result = (SearchResult) allGroups.next();
366                          group = (UserGroup) RuntimeParameters.getStore().get(UserGroup.class.getName(), result.getId());
367                          valid = group.contains( user );
368                      }
369                      
370                      // if the user is being bumped out of the "Admin and edit"
371                      // user group type, raise a detailed error.
372                      // we could do the same for the common groups (admins, editors)
373                      // and other group types?
374                      if (!valid && ("Administrators and Editors".equals(getGroupType()))) {
375                          ActionErrors errors = new ActionErrors();
376                          errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.logon.editor"));
377                          pageContext.getRequest().setAttribute(Action.ERROR_KEY, errors);
378                      }
379                  }
380                  
381              } else {
382                  valid = checkRole(role, user);
383              }
384          }
385 
386          // If NOT valid, then set the return URL on the session
387          if (!valid) {
388              putReturnURLOnSession((HttpServletRequest) pageContext.getRequest(), pageContext.getSession());
389          }
390          
391          return valid;
392      }
393 
394 
395     /**
396      * Put the return URL on the session
397      * under "LoginCookie.RETURN_URL_NAME",
398      */
399     public static void putReturnURLOnSession(HttpServletRequest request, HttpSession session) {
400 
401         String returnUrl;
402 
403         // Try getting the current URI from the SaveRequestURLFilter
404         String sruf = SaveRequestURLFilter.getRequestURI(request);
405 
406         if ((sruf != null) && (!sruf.equals(""))) {
407             returnUrl = sruf;
408         } else {
409             returnUrl = request.getRequestURI();
410             if (request.getQueryString() != null) {
411                 returnUrl = returnUrl.concat("?").concat(request.getQueryString());
412             }
413         }
414         
415         // Strip the context from this URL path
416         String contextPath = request.getContextPath();
417         if ((!"".equals(contextPath)) && (returnUrl.startsWith(contextPath))) {
418             returnUrl = returnUrl.substring(contextPath.length());
419         }
420         
421         RuntimeParameters.logDebug("CheckLogonTag", "ReturnUrl : "+returnUrl);
422         session.setAttribute(LoginCookie.RETURN_URL_NAME, returnUrl);
423     }
424     
425     /**
426      * This method is called when a user is found from a cookie.
427      * <p>It adds the user to the session.
428      */
429     public static void foundUserFromCookie(User user, HttpServletRequest request) {
430         RuntimeParameters.logDebug("", "Setting attribute "+RuntimeParameters.getParam("logonUserKey")+" to : "+user);
431         request.getSession().setAttribute(RuntimeParameters.getParam("logonUserKey"), user);
432         request.getSession().setAttribute(SecurityConstants.AUTH_TOKEN, new Integer(user.getId()));
433 
434         // also update the last login date and no logins (ticket #5770)
435         // FIXME : we could want to do this after the user has been validated
436         if (user instanceof TrackedUser) {
437             ((TrackedUser) user).increaseNoOfLogins();
438             ((TrackedUser) user).setLastLoginDate(new Date());
439             RuntimeParameters.getStore().save(user);
440         }
441     }
442 
443     /**
444      * This method is called when a user is found from the auth token.
445      * <p>
446      * It adds the user to the session.
447      */
448     public static void foundUserFromAuthToken(User user, HttpServletRequest request) {
449         RuntimeParameters.logDebug("CheckLogonTag", "Found user from AuthToken, user object itself was absent.");
450 
451         // FIXME: no need to reset the auth token
452         foundUserFromCookie(user, request);
453     }
454 
455 
456     /**
457      * Remove the user from the session.
458      */
459     public static void removeUserFromSession(User user, HttpServletRequest request) {
460         request.getSession().removeAttribute(RuntimeParameters.getParam("logonUserKey"));
461         request.getSession().removeAttribute(SecurityConstants.AUTH_TOKEN);
462     }
463 
464 
465     /**
466      * Continue to evaluate the page, or redirect to the login page
467      * @param valid If true, will continue evaluating the page; if false, will redirect
468      * @return EVAL_PAGE if evaluating the page, SKIP_PAGE if redirecting
469      */
470     public int forwardControl(boolean valid) throws JspException {
471         if (valid)
472             return (EVAL_PAGE);
473         else {
474             try {
475                 // only set the generic "error.logon" error if there is nothing there already
476                 // (the checkValid(user) method may have already put a more sensible error)
477                 if ((pageContext.getRequest().getAttribute(LIST_GROUP_NAMES_KEY) == null) && ((pageContext.getRequest().getAttribute(Action.ERROR_KEY) == null) || ((ActionErrors) pageContext.getRequest().getAttribute(Action.ERROR_KEY)).empty())) {
478                     ActionErrors errors = new ActionErrors();
479                     errors.add(ActionErrors.GLOBAL_ERROR, new ActionError("error.logon"));
480                     pageContext.getRequest().setAttribute(Action.ERROR_KEY, errors);
481                 }
482                 
483                 // Note. If the response is already committed then we cannot forward
484                 // This may happen if, eg, the tag is in an included page
485                 pageContext.include(getPage());
486                 
487             } catch (Exception e) {
488                 // FIXME: the log never shows the real stack trace unless we do this...
489                 // FIXME  java 1.4 has getStackTrace()
490                 e.printStackTrace();
491                 throw new JspException(e.toString());
492             }
493 
494             return (SKIP_PAGE);
495         }
496     }
497 
498 
499     /** Check roles. 
500      * @param role Comma-delimited list of roles (e.g. 0,1), or <code>null</code>
501      * @return If role is not <code>null</code>, returns whether the user has the specified roles. Otherwise, returns <code>true</code>.
502      */
503     protected boolean checkRole(String role, User user) {
504   boolean valid = false;
505   if (role!=null) {
506       // If so, does the user have the required role(s)?
507       // (role may be a comma-seperated list (e.g. 1,2,3) - 
508       //  if so, accept if the user has ANY role)
509       boolean hasRole = false;
510       StringTokenizer st = new StringTokenizer(role, ",");
511       while (st.hasMoreTokens()) {
512     String nextRole = st.nextToken();
513     if ( user.hasRole( Integer.parseInt( nextRole ) ) ) valid = true;
514       }
515   } else {
516       valid = true;
517   }
518 
519   return valid;
520     }
521 
522 
523 
524     /** Release any acquired resources. */
525     public void release() {
526         super.release();
527         this.page = "/logon.jsp";
528   this.role = null;
529   setGroup(null);
530   setGroups(null);
531   setGroupType(null);
532     }
533 }