001// Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 
002// The Molly framework is freely distributable under the terms of an
003// MIT-style license. For details, see the molly pages web site at:
004// http://www.mollypages.org/. Use, modify, have fun !
005
006package fc.web.servlet;
007
008import java.io.*;
009import java.net.*;
010import javax.servlet.*;
011import javax.servlet.http.*;
012
013import java.util.*;
014
015import fc.io.*;
016import fc.web.*;
017import fc.util.*;
018
019/** 
020An abstract servlet filter that only allows authenticated access to a
021resource. This filter will redirect the user to the <tt>login_page</tt> if
022the user is not logged in. The originally requested target URL (if any)
023will be saved via the method. The saved URL will be complete, i.e., will
024start from the protocol string <tt>http..</tt> upto and including any query
025string. Note: if the original URL was submitted via POST, the POST data is
026<u>not</u> saved, only the target URL is saved.
027<p>
028There are 2 ways that the original URL can be saved. One is to save it as a
029temporary cookie. This implies that the client must have cookies enabled.
030The other way is to tack it on to the URL as a url parameter. <u>In either
031case, the original target is saved as a parameter with
032name=<tt>login.target</tt> and value=<tt>URLEncoder.encode(target_page)</tt></u>
033<p>
034The following keys must be specified as the init parameters.
035<ul>
036  <li>key name: <tt>appName</tt>, the name of the webapp (the name is an
037  arbitrary string but must be the same as is specified in the init parameter
038  of {@link WebApp}).
039  <li>key name: <tt>login_page</tt>, the value should be an absolute
040  path (from the website's document root) to the login page or URL.
041The following key is optional:
042  <li>key name: <tt>use_cookie</tt>, the value should be <tt>"true"</tt>
043  or <tt>"false"</tt>. <tt>true</tt> means save the original target page
044  as a cookie, <tt>false</tt> means save that page as part of the URL.
045  Defaults to <tt>false</tt>
046</ul>
047
048<b>Note:</b> this class cannot be directly instantiated (since it's
049abstract) and should not be specified as the name of some filter in the
050servlet container's web.xml file.
051
052@author hursh jain
053**/
054public abstract class AuthFilter implements Filter 
055{
056private static  final boolean dbg = false;
057
058boolean     use_cookie = true;
059FilterConfig  config;
060ServletContext  context;
061String      login_page;
062String      appName;
063  
064public void init(FilterConfig config) throws ServletException 
065  {
066  this.config = config;
067  this.context = config.getServletContext();
068  login_page = config.getInitParameter("login_page");
069
070  if (login_page == null)
071    throw new ServletException("AuthFilter: the login_page parameter was missing");
072  
073  appName = config.getInitParameter("appName");
074  if (appName == null)
075    throw new ServletException("AuthFilter: the appName parameter was missing");
076    
077  String tmp = config.getInitParameter("use_cookie");
078  if (tmp != null)
079    this.use_cookie = Boolean.valueOf(tmp).booleanValue();
080  }
081
082public void destroy() 
083  {
084  config = null;
085  context = null;
086  }
087
088public void doFilter (
089  final ServletRequest req, final ServletResponse res, final FilterChain chain) 
090throws ServletException, IOException
091  {
092  if (dbg) System.out.println(">>>AuthFilter: doFilter() START");
093  
094    if ( (! (req instanceof HttpServletRequest)) ||
095     (! (res instanceof HttpServletResponse)) ) 
096    {
097        throw new ServletException("Request type was not HTTP/HTTPS");
098      }
099    
100    final HttpServletRequest  request = (HttpServletRequest) req;
101  final HttpServletResponse response = (HttpServletResponse) res;
102
103  boolean loggedin = false;
104  try {
105    loggedin = isUserLoggedIn(request, response);
106    }
107  catch (Exception e) {
108    throw new ServletException(e);
109    }
110    
111  if (! loggedin ) 
112    {
113    if (dbg) System.out.println(">>>AuthFilter: user not logged in");
114    showLoginPage(request, response);
115    }
116  else {
117    if (dbg) System.out.println(">>>AuthFilter: user logged in"); 
118    //only if user is logged in
119    chain.doFilter(req, res);
120    }
121
122    if (dbg) System.out.println(">>>AuthFilter: doFilter() END");
123  }
124  
125/**
126Redirects the user to a login page. The login page must set login status in
127some way understandable by invokations of this method.
128*/
129protected void showLoginPage(
130  final HttpServletRequest req, final HttpServletResponse res)
131throws ServletException, IOException
132  {
133  /*
134   requestURL gives us the protocol, port, the entire URL
135   (including path info) whereas requestURI would only give
136   us the entire path but not the protocol, port etc. We
137   save the full URL and the query string
138  */
139  final StringBuffer buf = req.getRequestURL();
140
141  final String query = req.getQueryString();
142  if (query != null) {
143    buf.append('?');
144    buf.append(query);
145    }
146
147  //we encode because the login target may itself contain
148  //chars like ? and = etc
149  String login_target = URLEncoder.encode(buf.toString());
150
151  //set target to original request page
152  if (use_cookie) {
153    final Cookie cookie = new Cookie("target", login_target);
154    cookie.setPath("/");
155    cookie.setMaxAge(-1); //only need it for browser session
156    res.addCookie(cookie);
157    if (dbg) System.out.println("setting cookie: " + cookie.getValue());
158    }
159  else {
160    login_page =  login_page + "?target=" +  login_target;
161    }
162  
163  res.sendRedirect(login_page);  //login_page NOT target  
164  }
165
166/**
167This method should somehow check to see if the user is logged in or not.
168Typically, this will be done via getting a session_id (either from a cookie
169or a URL) and using that session_id to search for a memory or database
170session data to see if that session still exists and has not expired.
171*/
172public abstract boolean isUserLoggedIn(
173  HttpServletRequest req, HttpServletResponse res)
174throws Exception;
175      
176} //~class AuthFilter
177