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 javax.servlet.http.*;
009import java.security.*;
010import java.sql.*;
011import java.io.*;
012
013import fc.jdbc.*;
014import fc.io.*;
015import fc.web.*;
016import fc.util.*;
017 
018/**  
019Modified version of CommandToken class from the book: <blockquote><tt>
020Web dev. with JSP by fields, kolb and bayern </tt></blockquote> Uses
021{@link JDBCSession} to store data.
022<p>
023Essentially, a token prevents repeating a page action upon back-button, reload,
024etc of a page. (for example, reprocessing an order if an order page was
025reloaded). This is done by setting a transaction token as a hidden field in the
026page we want to protect and also setting the same token in the user session.
027(This setting is done by other pages/servlets that send the use to the
028protected page, for example, from an earlier html page). When the protected
029order page is submitted, the order processing code checks to see if the
030session-token and the submitted form-token match. If so, the order is run
031<b>and</b> the session-token deleted. 
032<p>
033The next time the protected page is reloaded and submitted, the session-token
034is missing on the server, hence the submitted form (which still has the earlier
035token) and session token will not match and hence the order is not rerun.
036<p>
037Thread safety: Methods in this class are not thread safe and should be
038called via higher level synchronization (typically on the session object
039for a given user);
040
041@author hursh jain
042**/ 
043public class TransactionToken
044{
045/** The token is stored in the session with this key **/
046public static final String  TransactionToken_Key = "transact_token";
047
048static Log      log   = Log.get("fc.web.servlet");
049static JDBCSession session = JDBCSession.getInstance(log);
050
051/** 
052Creates a new transactional token. Tokens are unique per
053session. Calling this method more than once will replace a
054prior token in (if any) in the session. 
055
056@param  con     connection to database used by {@link JDBCSession}
057@param  sessionID the sessionID of the client
058
059@throws IllegalStateException if the specified sessionID is expired/not valid
060**/
061public static void create(Connection con, String sessionID) 
062throws SQLException
063  {
064  if (! session.exists(con, sessionID))
065    throw new IllegalStateException("the specifed sessionID [" + sessionID + "] does not exist");
066
067  long currentTime = System.currentTimeMillis();
068    byte[] time  = String.valueOf(currentTime).getBytes();
069    byte[] id = sessionID.getBytes();
070  MessageDigest md5 = null;     
071  String token = null;
072  try { 
073    md5 = MessageDigest.getInstance("MD5");
074    md5.update(id);
075      md5.update(time);
076    token = toHex(md5.digest()).toString();
077      }
078    catch (Exception e) {
079      throw new IllegalStateException(IOUtil.throwableToString(e)); 
080      }
081  
082  log.bug("Adding new transaction token=", token, " for sessionID=", sessionID); 
083  session.add(con, sessionID, TransactionToken_Key, token);
084    }
085
086/**
087Revokes the transactionID (if any) from the session
088data
089
090@param  session   the JDBC session to save the token to
091@param  sessionID the sessionID of the client
092**/
093public static void revoke(Connection con, String sessionID) 
094throws SQLException
095  {
096  session.delete(con, sessionID, TransactionToken_Key);
097  }
098
099/** 
100Searches for a token in the request (under the parameter name
101{@link #TransactionToken_Key} and tries to match it with a
102corresponding token in the session. Returns <tt>true</tt> if the
103tokens match (and hence the token is valid), <tt>false</tt>
104otherwise.
105**/
106public static boolean isValid(
107  Connection con, String sessionID, HttpServletRequest req) 
108throws SQLException
109  {
110    String requestToken = req.getParameter(TransactionToken_Key);
111    if (requestToken == null) {
112    return false;
113        }
114    else{       
115      String sessionToken = session.get(
116                  con, sessionID, TransactionToken_Key);
117        //sessionToken can be null (if not set or session expired)
118        //or some bad value
119        return requestToken.equals(sessionToken);
120      }
121  }
122
123private static StringBuffer toHex(byte[] digest) 
124  {
125    StringBuffer buf = new StringBuffer();
126    for (int i=0; i < digest.length; i++)
127        buf.append(Integer.toHexString((int)digest[i] & 0x00ff));
128    return buf;
129    }
130
131public static void main(String args[]) throws Exception
132  {
133  Args myargs = new Args(args);
134  myargs.setUsage("java fc.web.servlet.TransactionToken -conf conf-file -sessionID sessionID");
135  
136  String propfile = myargs.getRequired("conf");
137  String sessionID = myargs.getRequired("sessionID");
138  
139  ConnectionMgr mgr = new SimpleConnectionMgr(  
140              new FilePropertyMgr(
141                new File(propfile)));
142  Connection con = mgr.getConnection();
143  
144  log.setLevel(Log.DEBUG);
145
146  JDBCSession session = JDBCSession.getInstance(log);
147  session.create(con, sessionID);
148  
149  System.out.println("creating new transaction id...");
150  TransactionToken.create(con, sessionID);
151  System.out.println("Getting new transaction id from database");
152  String tok = session.get(con, sessionID, TransactionToken_Key);
153  System.out.println("transaction id = " + tok);
154  System.out.println("revoking token..");
155  TransactionToken.revoke(con, sessionID);
156  System.out.println("....revoked");
157  session.get(con, sessionID, TransactionToken_Key);
158  System.out.println("transaction id = " + tok);
159
160  con.close();
161    }
162}