/*
* ====================================================================
* JAFFA - Java Application Framework For All
*
* Copyright (C) 2002 JAFFA Development Group
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Redistribution and use of this software and associated documentation ("Software"),
* with or without modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain copyright statements and notices.
* Redistributions must also contain a copy of this document.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name "JAFFA" must not be used to endorse or promote products derived from
* this Software without prior written permission. For written permission,
* please contact mail to: jaffagroup@yahoo.com.
* 4. Products derived from this Software may not be called "JAFFA" nor may "JAFFA"
* appear in their names without prior written permission.
* 5. Due credit should be given to the JAFFA Project (http://jaffa.sourceforge.net).
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*/
package org.jaffa.presentation.portlet;
import org.apache.log4j.Logger;
import java.util.Enumeration;
import java.lang.reflect.Method;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.RequestDispatcher;
import org.jaffa.security.SecurityManager;
import org.jaffa.security.VariationContext;
import org.jaffa.presentation.portlet.session.UserSession;
import org.jaffa.presentation.portlet.session.LocaleContext;
import org.jaffa.presentation.portlet.widgets.taglib.FormTag;
import java.io.IOException;
import javax.servlet.ServletException;
import org.jaffa.exceptions.ApplicationExceptions;
import org.jaffa.exceptions.ComponentExpiredException;
import org.jaffa.presentation.portlet.session.UserSessionSetupException;
/** This servlet filter is a replacement for the JAFFA PortletServlet and the portlet.security package.
* It will invoke the SecurityManager to execute the rest of the request-processing under a SecurityContext. The SecurityContext is stored as a thread variable, which enables a servlet un-aware program (eg. the Persistence Engine) to get a user's authentication information.
* <p>
* The following will be executed under the SecurityContext
* <ol>
* <li> - Ensure that the HttpSession object, if new, is clean.
* <li> - Validate the componentId
* <li> - ReAuthenticate/AutoAuthenticate the user and create a valid UserSession object, if required
* <li> - Set the LocaleContext
* <li> - Set the VariationContext
* </ol>
* An application may use this filter as is, or it may extend it and provide an implementation for the initUserInfo() method, to initialize the UserSession object.
*
* @author GautamJ
* @version 1.0
* @since 1.3
*/
public class PortletFilter implements Filter {
private static final Logger log = Logger.getLogger(PortletFilter.class);
/** Cache the main method used to execute this filter under security */
private static Method c_method = null;
/** Called by the web container to indicate to a filter that it is being placed into service.
* This does nothing.
* @param filterConfig Config object from web.xml
* @throws ServletException Should never be thrown by this filter
*/
public void init(FilterConfig filterConfig) throws ServletException {
}
/** Called by the web container to indicate to a filter that it is being taken out of service.
* This does nothing.
*/
public void destroy() {
}
/** The doFilter method of the Filter is called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the end of the chain.
* It will invoke the SecurityManager to execute the doFilterUnderSecurityContext() method under a SecurityContext.
* @param request Request to process
* @param response Response to return from filter
* @param chain Chain of other filters to call
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Get the main method to run
try {
if(c_method == null)
determineMainMethod();
} catch (Exception e) {
String str = "Cannot find main method for Filter";
log.fatal(str, e);
throw new ServletException(str, e);
}
// Set the SecurityContext and execute the rest of the process under that context
try {
SecurityManager.runWithContext((HttpServletRequest) request, this, c_method, new Object[] {request, response, chain});
} catch (IOException e) {
throw e;
} catch (ServletException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
Throwable cause = e.getCause();
if (cause != null) {
if (cause instanceof IOException)
throw (IOException) cause;
else if (cause instanceof ServletException)
throw (ServletException) cause;
else if (cause instanceof RuntimeException)
throw (RuntimeException) cause;
}
String str = "Unknown Exception Processing Filter";
log.error(str, e);
throw new ServletException(str, e);
}
}
/** This method cotains the main process code and should be invoked only under a SecurityContext.
* The following will be executed under the SecurityContext
* 1- Ensure that the HttpSession object, if new, is clean.
* 2- Validate the componentId
* 3- ReAuthenticate/AutoAuthenticate the user and create a valid UserSession object, if required
* 4- Set the LocaleContext
* 5- Set the VariationContext
* Finally, it will pass control to the next Filter in the chain.
* @param request Request to process
* @param response Response to return from filter
* @param chain Chain of other filters to call
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
public void doFilterUnderSecurityContext(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (log.isDebugEnabled())
log.debug("Executing the Filter under a Security Context having the UserPrincipal: " + request.getUserPrincipal());
// This acquires a Session object. Its possible that the session object could be a re-cycled/invalidated session object. Currently tomcat doesn't seem to unbind objects from an invalidated session. Hence this method will cleanup newly acquired session objects.
cleanupNewSession(request);
// Validate the componentId
validateComponentId(request);
// ReAuthenticate/AutoAuthenticate the user and create a valid UserSession object, if required
performSecurityCheck(request);
// Set the Locale and Variation contexts
setContexts(request);
// Continue normal processing of the request
chain.doFilter(request, response);
}
/** This acquires a HttpSession object from the request stream. Its possible that the session object could be a re-cycled/invalidated session object. Currently tomcat doesn't seem to unbind objects from an invalidated session. Hence this method will cleanup newly acquired session objects.
* @param request Request to process
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
protected void cleanupNewSession(HttpServletRequest request)
throws IOException, ServletException {
HttpSession session = request.getSession(false);
if (session != null && session.isNew()) {
if (log.isDebugEnabled())
log.debug("Acquired a NEW Session object: " + session);
Enumeration enum = session.getAttributeNames();
if (enum != null) {
while (enum.hasMoreElements()) {
String attributeName = (String) enum.nextElement();
session.removeAttribute(attributeName);
if (log.isDebugEnabled())
log.debug("Removed an existing attribute from the newly acquired Session object: " + attributeName);
}
}
}
}
/** Validate the componentId passed in the request stream. It is possible that the user can hit the back button and perform actions on an inactive component. This method will prevent such cases.
* @param request Request to process
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
protected void validateComponentId(HttpServletRequest request)
throws IOException, ServletException {
String componentId = request.getParameter(FormTag.PARAMETER_COMPONENT_ID);
if (componentId != null && componentId.length() > 0) {
if (!UserSession.isUserSession(request)
|| UserSession.getUserSession(request).getComponent(componentId) == null) {
String str = "Component has expired";
if ( log.isDebugEnabled() )
log.debug(str);
// This exception should be picked up in web.xml and directed to the appropriate JSP, eg. 'pageExpired.jsp'
throw new ServletException(str, new ComponentExpiredException());
} else {
UserSession.getUserSession(request).getComponent(componentId).updateLastActivityDate();
}
}
}
/** For an authenticated principal, a UserSession, if one doesn't exist, will be created automatically.
* If the UserSession already exists, then it will be re-authenticated against the principal. If the existing UserSession is invalid for the principal, then it will be destroyed and a new UserSession will be created for the Principal.
* The UserSession, if it already exists, will be destroyed, in case the principal is un-authenticated.
* This will destroy the UserSession and HttpSession, in case any error occurs.
* @param request Request to process
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
protected void performSecurityCheck(HttpServletRequest request)
throws IOException, ServletException {
try {
if(UserSession.isUserSession(request)) {
if (log.isDebugEnabled())
log.debug("Security: Re-Authenticate");
reAuthenticate(request);
} else {
if (log.isDebugEnabled())
log.debug("Security: Auto-Authenticate");
autoAuthenticate(request);
}
} catch (IOException e) {
// Kill the user session if one exists.
if(UserSession.isUserSession(request))
UserSession.getUserSession(request).kill();
// Kill the real session if one exists.
if(request.getSession(false) != null)
request.getSession().invalidate();
throw e;
} catch (ServletException e) {
// Kill the user session if one exists.
if(UserSession.isUserSession(request))
UserSession.getUserSession(request).kill();
// Kill the real session if one exists.
if(request.getSession(false) != null)
request.getSession().invalidate();
throw e;
}
}
/** Sets the LocaleContext and the VariationContext.
* @param request Request to process
* @throws IOException Standard Exception For Filter
* @throws ServletException Standard Exception For Filter
*/
protected void setContexts(HttpServletRequest request)
throws IOException, ServletException {
// Set the LocaleContext
if (log.isDebugEnabled())
log.debug("Setting the Locale to: " + request.getLocale());
LocaleContext.setLocale(request.getLocale());
// Set the VariationContext
if (UserSession.isUserSession(request)) {
if (log.isDebugEnabled())
log.debug("Setting the VariationContext to: " + UserSession.getUserSession(request).getVariation());
VariationContext.setVariation(UserSession.getUserSession(request).getVariation());
}
}
/** This is invoked by the performSecurityCheck() method, when it automatically creates a new UserSession object for an authenticated user.
* By default, this method does nothing. However, a subclass can override it and use it to load any additional user context into the UserSession object.
* The developer can assume that at the point this is called, the UserSession.getUserId() will return the name of the authenticated user.
* The developer is responsible for calling the UserSession.setUserData() in this method to store any additional user context.
*
* @param us The new UserSession object to be intialized
* @throws UserSessionSetupException if any error occurs in initializing the UserSession.
*/
protected void initUserInfo(UserSession us) throws UserSessionSetupException {
}
/** On entry it is assumed that a UserSession object exists. The purpose of this
* function is to implement any required logic to re-validate that this UserSession is
* still ok.
*
* On exit, if the UserSession object still exists in the HttpSession it is assumed that
* it has been re-validated (regardless of whether it has been updated, or re-used for
* another user). If it has been removed from the session, the assumtion is that
* the reAuthentication failed.
*
* @param request HttpRequest that holds any log in context information
*/
private void reAuthenticate(HttpServletRequest request)
throws IOException, ServletException {
// Get the Current Session
UserSession us = UserSession.getUserSession(request);
// If we have an authenticated user ...
if(request.getUserPrincipal() != null) {
// ...and it is the same user that the valid session is for, we are ok
if(us.isValid() && us.getUserId().equals(request.getUserPrincipal().getName())) {
// no nothing, life is peachy!
return;
} else {
// this is a differnt user, or an invalid session, so kill this UserSession and try an auto-authenticate
us.kill();
autoAuthenticate(request);
return;
}
} else {
// We have reached the security manager, with out and authenticatic user,
// but we have a user session. We must therefore just kill it and continue.
us.kill();
}
}
/** On entry it is assumed that there is no UserSession record. If there are some
* special reasons for a UserSession to be automatically created, this is the place
* to do it.
*
* On exit from this method, if a UserSession object has been created, it assumes that
* this is an authenticated Session.
*
*
* @param request HttpRequest that holds any log in context information
*/
private void autoAuthenticate(HttpServletRequest request)
throws IOException, ServletException {
// Make sure there is an authenticated user
if(request.getUserPrincipal() != null) {
// This will create a new session if one doesn't exist
UserSession us = UserSession.getUserSession(request);
us.setUserId(request.getUserPrincipal().getName());
try {
initUserInfo(us);
} catch (UserSessionSetupException e) {
ApplicationExceptions appExps = new ApplicationExceptions();
appExps.add(e);
// This exception can be picked by some generic error-handling JSP, which will loop thru the ApplicationException objects inside the ApplicationExceptions, printing the corresponding error message.
throw new ServletException("Error in initializing the UserSession. " + e, appExps);
}
}
}
/** Uses reflection to create a Method object for the doFilterUnderSecurityContext() method.
*/
private synchronized void determineMainMethod() throws Exception {
if (c_method == null)
c_method = getClass().getMethod("doFilterUnderSecurityContext", new Class[] {HttpServletRequest.class, HttpServletResponse.class, FilterChain.class});
}
}
|