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    
006    package fc.web.servlet;
007    
008    import javax.servlet.http.*;
009    import java.security.*;
010    import java.sql.*;
011    import java.io.*;
012    
013    import fc.jdbc.*;
014    import fc.io.*;
015    import fc.web.*;
016    import fc.util.*;
017     
018    /**  
019    Modified version of CommandToken class from the book: <blockquote><tt>
020    Web dev. with JSP by fields, kolb and bayern </tt></blockquote> Uses
021    {@link JDBCSession} to store data.
022    <p>
023    Essentially, a token prevents repeating a page action upon back-button, reload,
024    etc of a page. (for example, reprocessing an order if an order page was
025    reloaded). This is done by setting a transaction token as a hidden field in the
026    page 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
028    protected page, for example, from an earlier html page). When the protected
029    order page is submitted, the order processing code checks to see if the
030    session-token and the submitted form-token match. If so, the order is run
031    <b>and</b> the session-token deleted. 
032    <p>
033    The next time the protected page is reloaded and submitted, the session-token
034    is missing on the server, hence the submitted form (which still has the earlier
035    token) and session token will not match and hence the order is not rerun.
036    <p>
037    Thread safety: Methods in this class are not thread safe and should be
038    called via higher level synchronization (typically on the session object
039    for a given user);
040    
041    @author hursh jain
042    **/ 
043    public class TransactionToken
044    {
045    /** The token is stored in the session with this key **/
046    public static final String  TransactionToken_Key = "transact_token";
047    
048    static Log      log   = Log.get("fc.web.servlet");
049    static JDBCSession session = JDBCSession.getInstance(log);
050    
051    /** 
052    Creates a new transactional token. Tokens are unique per
053    session. Calling this method more than once will replace a
054    prior 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    **/
061    public static void create(Connection con, String sessionID) 
062    throws 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    /**
087    Revokes the transactionID (if any) from the session
088    data
089    
090    @param  session   the JDBC session to save the token to
091    @param  sessionID the sessionID of the client
092    **/
093    public static void revoke(Connection con, String sessionID) 
094    throws SQLException
095      {
096      session.delete(con, sessionID, TransactionToken_Key);
097      }
098    
099    /** 
100    Searches for a token in the request (under the parameter name
101    {@link #TransactionToken_Key} and tries to match it with a
102    corresponding token in the session. Returns <tt>true</tt> if the
103    tokens match (and hence the token is valid), <tt>false</tt>
104    otherwise.
105    **/
106    public static boolean isValid(
107      Connection con, String sessionID, HttpServletRequest req) 
108    throws 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    
123    private 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    
131    public 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    }