diff --git a/webapp/config/tlds/VitroUtils.tld b/webapp/config/tlds/VitroUtils.tld index 562c9c0de..401185a7f 100644 --- a/webapp/config/tlds/VitroUtils.tld +++ b/webapp/config/tlds/VitroUtils.tld @@ -49,4 +49,33 @@ + + requiresAuthorizationFor + Confirm that the user is authorized for the actions that this page requires. + + Confirm that the user is authorized to perform all of the RequestedActions that + this page requires. A check is done for each such action, to see whether the + current policy will authorize that action for the current user. If any of the + actions is not authorized, the user will be redirected to the appropriate page. + + If the user is not authorized because he is not logged in, he will be directed + to the login page, with the current request stored as a post-login destination. + + If the user is logged in but without sufficient authorization, he will be + directed to the home page, which will display an "insufficient authorization" + message. + + The requested actions are specified as a comma delimited list of names (with + optional spaces). These names must match against the map of classes in + JspPolicyHelper, or an error will be logged and the authorization will fail. + + edu.cornell.mannlib.vitro.webapp.web.jsptags.RequiresAuthorizationFor + empty + + actions + true + true + + + \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java new file mode 100644 index 000000000..5cc7a94b1 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java @@ -0,0 +1,211 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.RequestIdentifiers; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper.RequiresAuthorizationFor.NoAction; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; + +/** + * A collection of static methods to help determine whether requested actions + * are authorized by current policy. + */ +public class PolicyHelper { + private static final Log log = LogFactory.getLog(PolicyHelper.class); + + /** + * A subclass of VitroHttpServlet may be annotated to say what actions + * should be checked for authorization before permitting the user to view + * the page that the servlet would create. + * + * Any RequestedAction can be specified, but the most common use will be to + * specify implementations of UsePagesRequestedAction. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public static @interface RequiresAuthorizationFor { + static class NoAction extends RequestedAction { + /* no fields */ + } + + Class[] value() default NoAction.class; + } + + /** + * Does this servlet require authorization? + */ + public static boolean isRestrictedPage(VitroHttpServlet servlet) { + Class servletClass = servlet.getClass(); + return !getRequiredAuthorizationsForServlet(servletClass).isEmpty(); + } + + /** + * What RequestedActions does this servlet require authorization for? + */ + public static Set getRequiredAuthorizationsForServlet( + Class clazz) { + Set result = new HashSet(); + + RequiresAuthorizationFor annotation = clazz + .getAnnotation(RequiresAuthorizationFor.class); + + if (annotation != null) { + for (Class actionClass : annotation + .value()) { + if (NoAction.class != actionClass) { + RequestedAction action = instantiateAction(actionClass); + if (action != null) { + result.add(action); + } + } + } + } + return result; + } + + /** + * Are the actions that this servlet requires authorized for the current + * user by the current policies? + */ + public static boolean areRequiredAuthorizationsSatisfied( + HttpServletRequest req, VitroHttpServlet servlet) { + Class servletClass = servlet.getClass(); + return areRequiredAuthorizationsSatisfied(req, + getRequiredAuthorizationsForServlet(servletClass)); + } + + /** + * Are these action classes authorized for the current user by the current + * policies? + */ + public static boolean areRequiredAuthorizationsSatisfied( + HttpServletRequest req, + Class... actionClasses) { + List> classList = Arrays + .asList(actionClasses); + + Set actions = instantiateActions(classList); + if (actions == null) { + log.debug("not authorized: failed to instantiate actions"); + return false; + } + + return areRequiredAuthorizationsSatisfied(req, actions); + } + + /** + * Are these actions authorized for the current user by the current + * policies? + */ + public static boolean areRequiredAuthorizationsSatisfied( + HttpServletRequest req, + Collection actions) { + PolicyIface policy = ServletPolicyList.getPolicies(req); + IdentifierBundle ids = RequestIdentifiers.getIdBundleForRequest(req); + + for (RequestedAction action : actions) { + if (isAuthorized(policy, ids, action)) { + log.debug("not authorized"); + return false; + } + } + + log.debug("authorized"); + return true; + } + + /** + * Is this action class authorized for the current user by the current + * policies? + */ + @SuppressWarnings("unchecked") + public static boolean isAuthorized(HttpServletRequest req, + Class actionClass) { + return areRequiredAuthorizationsSatisfied(req, actionClass); + } + + /** + * Is this action authorized for these IDs by this policy? + */ + private static boolean isAuthorized(PolicyIface policy, + IdentifierBundle ids, RequestedAction action) { + PolicyDecision decision = policy.isAuthorized(ids, action); + log.debug("decision for '" + action.getClass().getName() + "' was: " + + decision); + return (decision == null) + || (decision.getAuthorized() != Authorization.AUTHORIZED); + } + + /** + * Instantiate actions from their classes. If any one of the classes cannot + * be instantiated, return null. + */ + private static Set instantiateActions( + Collection> actionClasses) { + Set actions = new HashSet(); + for (Class actionClass : actionClasses) { + RequestedAction action = instantiateAction(actionClass); + if (action == null) { + return null; + } else { + actions.add(action); + } + } + return actions; + } + + /** + * Get an instance of the RequestedAction, from its class. If the class + * cannot be instantiated, return null. + */ + private static RequestedAction instantiateAction( + Class actionClass) { + try { + Constructor constructor = actionClass + .getConstructor(); + RequestedAction instance = constructor.newInstance(); + return instance; + } catch (NoSuchMethodException e) { + log.error("'" + actionClass.getName() + + "' does not have a no-argument constructor."); + return null; + } catch (IllegalAccessException e) { + log.error("The no-argument constructor for '" + + actionClass.getName() + "' is not public."); + return null; + } catch (Exception e) { + log.error("Failed to instantiate '" + actionClass.getName() + "'", + e); + return null; + } + } + + /** + * No need to instantiate this helper class - all methods are static. + */ + private PolicyHelper() { + // nothing to do. + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/UseRestrictedPagesByRoleLevelPolicy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/UseRestrictedPagesByRoleLevelPolicy.java new file mode 100644 index 000000000..fea9d3788 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/UseRestrictedPagesByRoleLevelPolicy.java @@ -0,0 +1,92 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.policy; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.auth.identifier.HasRoleLevel; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.Identifier; +import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision; +import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.UseAdvancedDataToolsPages; +import edu.cornell.mannlib.vitro.webapp.beans.BaseResourceBean.RoleLevel; + +/** + * Check the users role level to determine whether they are allowed to use + * restricted pages. + */ +public class UseRestrictedPagesByRoleLevelPolicy implements PolicyIface { + private static final Log log = LogFactory + .getLog(UseRestrictedPagesByRoleLevelPolicy.class); + + @Override + public PolicyDecision isAuthorized(IdentifierBundle whoToAuth, + RequestedAction whatToAuth) { + if (whoToAuth == null) { + return defaultDecision("whomToAuth was null"); + } + if (whatToAuth == null) { + return defaultDecision("whatToAuth was null"); + } + + RoleLevel userRole = getUsersRoleLevel(whoToAuth); + + PolicyDecision result; + if (whatToAuth instanceof UseAdvancedDataToolsPages) { + result = isAuthorized(whatToAuth, RoleLevel.DB_ADMIN, userRole); + } else { + result = defaultDecision("Unrecognized action"); + } + + log.debug("decision for '" + whatToAuth + "' is " + result); + return result; + } + + private PolicyDecision isAuthorized(RequestedAction whatToAuth, + RoleLevel requiredRole, RoleLevel currentRole) { + if (isRoleAtLeast(requiredRole, currentRole)) { + return authorized("User may view page: " + whatToAuth + + ", requiredRole=" + requiredRole + ", currentRole=" + + currentRole); + } else { + return defaultDecision("User may not view page: " + whatToAuth + + ", requiredRole=" + requiredRole + ", currentRole=" + + currentRole); + } + } + + private boolean isRoleAtLeast(RoleLevel required, RoleLevel current) { + return (current.compareTo(required) >= 0); + } + + /** If the user is explicitly authorized, return this. */ + private PolicyDecision authorized(String message) { + String className = this.getClass().getSimpleName(); + return new BasicPolicyDecision(Authorization.AUTHORIZED, className + + ": " + message); + } + + /** If the user isn't explicitly authorized, return this. */ + private PolicyDecision defaultDecision(String message) { + return new BasicPolicyDecision(Authorization.INCONCLUSIVE, message); + } + + /** + * The user is nobody unless they have a HasRoleLevel identifier. + */ + private RoleLevel getUsersRoleLevel(IdentifierBundle whoToAuth) { + RoleLevel userRole = RoleLevel.PUBLIC; + for (Identifier id : whoToAuth) { + if (id instanceof HasRoleLevel) { + userRole = ((HasRoleLevel) id).getRoleLevel(); + } + } + return userRole; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/CommonPolicyFamilySetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/CommonPolicyFamilySetup.java index ff6eee6e4..94f0623f3 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/CommonPolicyFamilySetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/setup/CommonPolicyFamilySetup.java @@ -14,6 +14,7 @@ import edu.cornell.mannlib.vitro.webapp.auth.identifier.CommonIdentifierBundleFa import edu.cornell.mannlib.vitro.webapp.auth.policy.DisplayRestrictedDataByRoleLevelPolicy; import edu.cornell.mannlib.vitro.webapp.auth.policy.DisplayRestrictedDataToSelfPolicy; import edu.cornell.mannlib.vitro.webapp.auth.policy.ServletPolicyList; +import edu.cornell.mannlib.vitro.webapp.auth.policy.UseRestrictedPagesByRoleLevelPolicy; import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup; /** @@ -36,6 +37,8 @@ public class CommonPolicyFamilySetup implements ServletContextListener { new DisplayRestrictedDataByRoleLevelPolicy(ctx)); ServletPolicyList.addPolicy(ctx, new DisplayRestrictedDataToSelfPolicy(ctx)); + ServletPolicyList.addPolicy(ctx, + new UseRestrictedPagesByRoleLevelPolicy()); // This factory creates Identifiers for all of the above policies. CommonIdentifierBundleFactory factory = new CommonIdentifierBundleFactory(); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequestedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequestedAction.java index f2616104c..b22a3e5c6 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequestedAction.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/ifaces/RequestedAction.java @@ -12,4 +12,8 @@ public abstract class RequestedAction { return RequestActionConstants.actionNamespace + this.getClass().getName(); } + @Override + public String toString() { + return this.getClass().getSimpleName(); + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UseAdvancedDataToolsPages.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UseAdvancedDataToolsPages.java new file mode 100644 index 000000000..221585671 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UseAdvancedDataToolsPages.java @@ -0,0 +1,11 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; + +/** Should we allow the user to use the pages for Advanced Data Tools? */ +public class UseAdvancedDataToolsPages extends RequestedAction implements + UsePagesRequestedAction { + // no fields +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UsePagesRequestedAction.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UsePagesRequestedAction.java new file mode 100644 index 000000000..313cdbc60 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/requestedAction/usepages/UsePagesRequestedAction.java @@ -0,0 +1,8 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages; + +/** Denotes a request to use a particular page or group of pages. */ +public interface UsePagesRequestedAction { + /** marker interface */ +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java index e5fc45116..a94b19d5c 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import edu.cornell.mannlib.vedit.beans.LoginStatusBean; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; import edu.cornell.mannlib.vitro.webapp.beans.DisplayMessage; import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector; @@ -44,6 +45,38 @@ public class VitroHttpServlet extends HttpServlet { public final static String TTL_MIMETYPE = "text/turtle"; // unofficial and // unregistered + /** + * Check that any required authorizations are satisfied before processing + * the request. + */ + @Override + public final void service(ServletRequest req, ServletResponse resp) + throws ServletException, IOException { + if ((req instanceof HttpServletRequest) + && (resp instanceof HttpServletResponse)) { + HttpServletRequest hreq = (HttpServletRequest) req; + HttpServletResponse hresp = (HttpServletResponse) resp; + + if (log.isTraceEnabled()) { + dumpRequestHeaders(hreq); + } + + if (PolicyHelper.isRestrictedPage(this)) { + LogoutRedirector.recordRestrictedPageUri(hreq); + } + + if (!PolicyHelper.areRequiredAuthorizationsSatisfied(hreq, this)) { + if (LoginStatusBean.getBean(hreq).isLoggedIn()) { + redirectToInsufficientAuthorizationPage(hreq, hresp); + } else { + redirectToLoginPage(hreq, hresp); + } + } + } + + super.service(req, resp); + } + /** * Show this to the user if they are logged in, but still not authorized to * view the page. @@ -87,6 +120,8 @@ public class VitroHttpServlet extends HttpServlet { /** * If not logged in, redirect them to the login page. + * + * TODO this goes away as it is replace by annotations. */ public static boolean checkLoginStatus(HttpServletRequest request, HttpServletResponse response) { @@ -104,6 +139,8 @@ public class VitroHttpServlet extends HttpServlet { /** * If not logged in at the required level, redirect them to the appropriate * page. + * + * TODO this goes away as it is replace by annotations. */ public static boolean checkLoginStatus(HttpServletRequest request, HttpServletResponse response, int minimumLevel) { @@ -183,24 +220,17 @@ public class VitroHttpServlet extends HttpServlet { * If logging is set to the TRACE level, dump the HTTP headers on the * request. */ - @SuppressWarnings("unchecked") - @Override - public void service(ServletRequest req, ServletResponse resp) - throws ServletException, IOException { - if (log.isTraceEnabled()) { - HttpServletRequest request = (HttpServletRequest) req; - Enumeration names = request.getHeaderNames(); - log.trace("----------------------request:" - + request.getRequestURL()); - while (names.hasMoreElements()) { - String name = names.nextElement(); - if (!BORING_HEADERS.contains(name)) { - log.trace(name + "=" + request.getHeader(name)); - } + private void dumpRequestHeaders(HttpServletRequest req) { + @SuppressWarnings("unchecked") + Enumeration names = req.getHeaderNames(); + + log.trace("----------------------request:" + req.getRequestURL()); + while (names.hasMoreElements()) { + String name = names.nextElement(); + if (!BORING_HEADERS.contains(name)) { + log.trace(name + "=" + req.getHeader(name)); } } - - super.service(req, resp); } /** Don't dump the contents of these headers, even if log.trace is enabled. */ diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/ConfirmLoginStatus.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/ConfirmLoginStatus.java index 699ff3fc0..1a83cff66 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/ConfirmLoginStatus.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/ConfirmLoginStatus.java @@ -16,13 +16,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.authenticate.LogoutRedirector import edu.cornell.mannlib.vitro.webapp.filters.VitroRequestPrep; /** - * JSP tag to generate the HTML of links for edit, delete or add of a Property. - * - * Maybe we should have a mode where it just sets a var to a map with "href" = - * "edit/editDatapropDispatch.jsp?subjectUri=..." and "type" = "delete" - * - * @author bdc34 - * + * TODO This should go away as it is replaced by vitro:requiresAuthorizationFor */ public class ConfirmLoginStatus extends BodyTagSupport { private static final Log log = LogFactory.getLog(ConfirmLoginStatus.class); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java new file mode 100644 index 000000000..9fc8d7a84 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/jsptags/RequiresAuthorizationFor.java @@ -0,0 +1,125 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.web.jsptags; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.jsp.JspException; +import javax.servlet.jsp.tagext.BodyTagSupport; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vedit.beans.LoginStatusBean; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.UseAdvancedDataToolsPages; +import edu.cornell.mannlib.vitro.webapp.controller.VitroHttpServlet; + +/** + * Confirm that the user is authorized to perform each of the RequestedActions. + * + * The user specifies the actions as a comma delimited list of names (with + * optional spaces). These names are matched against the map of recognized + * names. If no match is found, an error is logged and the authorization fails. + */ +public class RequiresAuthorizationFor extends BodyTagSupport { + private static final Log log = LogFactory + .getLog(RequiresAuthorizationFor.class); + + /** + * These are the only action names that we recognize. + */ + private static final Map actionMap = new HashMap(); + static { + actionMap.put("UseAdvancedDataToolsPages", + new UseAdvancedDataToolsPages()); + } + + String actionNames = ""; + + public void setActions(String actionNames) { + this.actionNames = actionNames; + } + + /** + * This is all of it. If they are authorized, continue. Otherwise, redirect. + */ + @Override + public int doEndTag() throws JspException { + if (isAuthorized()) { + return EVAL_PAGE; + } else if (isLoggedIn()) { + return showInsufficientAuthorizationMessage(); + } else { + return redirectToLoginPage(); + } + } + + /** + * They are authorized if we recognize the actions they ask for, and they + * are authorized for those actions. + */ + private boolean isAuthorized() { + Collection actions = parseActionNames(); + if (actions == null) { + return false; + } + return PolicyHelper.areRequiredAuthorizationsSatisfied(getRequest(), + actions); + } + + /** + * Parse the string and pull the corresponding actions from the map. If we + * can't do that, complain and return null. + */ + private Collection parseActionNames() { + Set actions = new HashSet(); + + for (String part : actionNames.split("[\\s],[\\s]")) { + String key = part.trim(); + if (key.isEmpty()) { + continue; + } + + if (actionMap.containsKey(key)) { + log.debug("checking authorization for '" + key + "'"); + actions.add(actionMap.get(key)); + } else { + log.error("JSP requested authorization for unknown action: '" + + key + "'"); + return null; + } + } + return actions; + } + + private boolean isLoggedIn() { + return LoginStatusBean.getBean(getRequest()).isLoggedIn(); + } + + private int showInsufficientAuthorizationMessage() { + VitroHttpServlet.redirectToInsufficientAuthorizationPage(getRequest(), + getResponse()); + return SKIP_PAGE; + } + + private int redirectToLoginPage() throws JspException { + VitroHttpServlet.redirectToLoginPage(getRequest(), getResponse()); + return SKIP_PAGE; + } + + private HttpServletRequest getRequest() { + return ((HttpServletRequest) pageContext.getRequest()); + } + + private HttpServletResponse getResponse() { + return (HttpServletResponse) pageContext.getResponse(); + } +}