001: /*
002: * $Header: /cvsroot/mvnforum/myvietnam/src/net/myvietnam/mvncore/security/FloodControl.java,v 1.13 2007/01/15 10:31:17 dungbtm Exp $
003: * $Author: dungbtm $
004: * $Revision: 1.13 $
005: * $Date: 2007/01/15 10:31:17 $
006: *
007: * ====================================================================
008: *
009: * Copyright (C) 2002-2007 by MyVietnam.net
010: *
011: * All copyright notices regarding MyVietnam and MyVietnam CoreLib
012: * MUST remain intact in the scripts and source code.
013: *
014: * This library is free software; you can redistribute it and/or
015: * modify it under the terms of the GNU Lesser General Public
016: * License as published by the Free Software Foundation; either
017: * version 2.1 of the License, or (at your option) any later version.
018: *
019: * This library is distributed in the hope that it will be useful,
020: * but WITHOUT ANY WARRANTY; without even the implied warranty of
021: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
022: * Lesser General Public License for more details.
023: *
024: * You should have received a copy of the GNU Lesser General Public
025: * License along with this library; if not, write to the Free Software
026: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
027: *
028: * Correspondence and Marketing Questions can be sent to:
029: * info at MyVietnam net
030: *
031: * @author: Minh Nguyen
032: * @author: Mai Nguyen
033: */
034: package net.myvietnam.mvncore.security;
035:
036: import java.util.*;
037:
038: import org.apache.commons.logging.Log;
039: import org.apache.commons.logging.LogFactory;
040: import net.myvietnam.mvncore.exception.FloodException;
041: import net.myvietnam.mvncore.util.DateUtil;
042:
043: /**
044: * This class is used to control the number of actions per hour. This is usually
045: * used to prevent the flood of any action. You should call FloodControl.setOption()
046: * when your application is inited.
047: * <p>
048: * Note that the action id from 0 to 999 is belong to mvnCore, application used it
049: * should not use the value in this range
050: */
051: public class FloodControl {
052:
053: private static Log log = LogFactory.getLog(FloodControl.class);
054:
055: /** The value from 0 to 999 should belong to mvnCore */
056: public static int MAX_MVNCORE_ACTION_ID = 999;
057:
058: private static Map actionControlMap = new TreeMap();
059:
060: static final long REMOVE_INTERVAL = DateUtil.MINUTE * 2;//2 minutes
061:
062: private FloodControl() {
063: }
064:
065: /**
066: * To set the mamximum number of actions per hour for an action.
067: * If the caller does not call this method, the the action has no limit
068: * @param action Integer the action that want to set the option
069: * @param actionsPerHour int the maximum number of actions per hour
070: */
071: public static void setOption(Integer action, int actionsPerHour) {
072: getControlledAction(action).setActionsPerHour(actionsPerHour);
073: }
074:
075: public static int getActionsPerHour(Integer action) {
076: return getControlledAction(action).getActionsPerHour();
077: }
078:
079: /**
080: * Check that an action of an IP has reach the maximum number of allowed times
081: * @param action Integer the action to check
082: * @param strIP String the IP to check
083: * @return boolean true if it has reached the maximum
084: */
085: public static boolean reachMaximum(Integer action, String strIP) {
086: return getControlledAction(action).reachMaximum(strIP);
087: }
088:
089: /**
090: * This is a utility method to ensure that the action has not reached the mamximum.
091: * It calls the method reachMaximum and throw an exception if it reached the maximum.
092: * A program could use this method to use the default error message, otherwise
093: * it has to use reachMaximum
094: * @param action Integer the action to ensure
095: * @param strIP String the IP to ensure
096: * @throws FloodException if it reached the maximum
097: * @see FloodControl#reachMaximum(Integer, String)
098: */
099: public static void ensureNotReachMaximum(Integer action,
100: String strIP) throws FloodException {
101: if (reachMaximum(action, strIP)) {
102: log
103: .info("Attempt to exceed the maximum number of actions: ActionID = "
104: + action + " and IP = " + strIP);
105: //@todo : localize me
106: throw new FloodException(
107: "You have reached the maximum number of actions for this page (actionID = "
108: + action
109: + "). Please try this page later. This is to prevent forum from being flooded.");
110: }
111: }
112:
113: /**
114: * Increase the number of action. This method should be called the the program
115: * has done this action. Forget to call this method will void the reachMaximum method.
116: * @param action Integer the action to increase the number of times
117: * @param strIP String the IP to increase the number of times
118: */
119: public static void increaseCount(Integer action, String strIP) {
120: getControlledAction(action).increaseCount(strIP);
121: }
122:
123: /**
124: * Reset the action history. This method is useful in such a case in the login
125: * process that after login successfully, the value should be reset. Please note
126: * that this is just an example and usually no need to use this method.
127: * @param action Integer
128: * @param strIP String
129: */
130: public static void resetActionHistory(Integer action, String strIP) {
131: getControlledAction(action).resetActionHistory(strIP);
132: }
133:
134: /**
135: * Return the instance of ControlledAction for the action. It will create
136: * new instance if no previous instance for this action exist.
137: * @param action Integer
138: * @return ControlledAction
139: */
140: private static synchronized ControlledAction getControlledAction(
141: Integer action) {
142: ControlledAction controlledAction = (ControlledAction) actionControlMap
143: .get(action);
144: if (controlledAction == null) {
145: controlledAction = new ControlledAction();
146: actionControlMap.put(action, controlledAction);
147: }
148: return controlledAction;
149: }
150: }
151:
152: /**
153: * For one action that handles a list of all IP
154: */
155: class ControlledAction {
156: private int actionsPerHour = 0;
157: private Map ipMap = new TreeMap();
158: private long lastRemoveTime = 0;
159:
160: void setActionsPerHour(int actionsPerHour) {
161: if (actionsPerHour >= 0) {
162: this .actionsPerHour = actionsPerHour;
163: }
164: }
165:
166: int getActionsPerHour() {
167: return actionsPerHour;
168: }
169:
170: boolean reachMaximum(String strIP) {
171: removeTimeoutControlledIP();
172: return getControlledIP(strIP).reachMaximum();
173: }
174:
175: void increaseCount(String strIP) {
176: removeTimeoutControlledIP();
177: getControlledIP(strIP).increaseCount();
178: }
179:
180: void resetActionHistory(String strIP) {
181: removeTimeoutControlledIP();
182: getControlledIP(strIP).resetActionHistory();
183: }
184:
185: private synchronized ControlledIP getControlledIP(String strIP) {
186: ControlledIP controlledIP = (ControlledIP) ipMap.get(strIP);
187: if (controlledIP == null) {
188: controlledIP = new ControlledIP(actionsPerHour);
189: ipMap.put(strIP, controlledIP);
190: } else {
191: // there is a ControlledIP, update the actionsPerHour
192: controlledIP.setActionsPerHour(actionsPerHour);
193: }
194: return controlledIP;
195: }
196:
197: private synchronized void removeTimeoutControlledIP() {
198: long now = System.currentTimeMillis();
199: if ((now - lastRemoveTime) > FloodControl.REMOVE_INTERVAL) {
200: lastRemoveTime = now;
201: Collection ipList = ipMap.values();
202: for (Iterator iter = ipList.iterator(); iter.hasNext();) {
203: ControlledIP currentControlledIP = (ControlledIP) iter
204: .next();
205: if (now - currentControlledIP.getLastIncrementTime() > DateUtil.HOUR) {
206: iter.remove();
207: }
208: }
209: }
210: }
211: }
212:
213: /**
214: * For one action per one IP
215: */
216: class ControlledIP {
217: private int actionsPerHour = 0;
218: private long lastRemoveTime = 0;
219: private long lastIncrementTime = 0;
220: private ArrayList actionHistoryList = new ArrayList();
221:
222: ControlledIP(int actionsPerHour) {
223: if (actionsPerHour >= 0) {
224: this .actionsPerHour = actionsPerHour;
225: }
226: }
227:
228: void setActionsPerHour(int actionsPerHour) {
229: if (actionsPerHour >= 0) {
230: this .actionsPerHour = actionsPerHour;
231: }
232: }
233:
234: long getLastIncrementTime() {
235: return lastIncrementTime;
236: }
237:
238: void increaseCount() {
239: long now = System.currentTimeMillis();
240: lastIncrementTime = now;
241: actionHistoryList.add(new Long(now));
242: }
243:
244: void resetActionHistory() {
245: lastRemoveTime = 0;
246: lastIncrementTime = 0;
247: actionHistoryList.clear();
248: }
249:
250: boolean reachMaximum() {
251: if (actionsPerHour == 0) {//unlimited
252: return false;
253: }
254:
255: if (actionHistoryList.size() < actionsPerHour) {
256: return false;
257: }
258:
259: // now try to remove timeout actions
260: removeTimeoutActions();
261:
262: return (actionHistoryList.size() >= actionsPerHour);
263: }
264:
265: private synchronized void removeTimeoutActions() {
266: long now = System.currentTimeMillis();
267: if (now - lastRemoveTime > FloodControl.REMOVE_INTERVAL) {
268: lastRemoveTime = now;
269: for (Iterator iter = actionHistoryList.iterator(); iter
270: .hasNext();) {
271: Long currentAction = (Long) iter.next();
272: if ((now - currentAction.longValue()) > DateUtil.HOUR) {
273: iter.remove();
274: }
275: } //for
276: }
277: }
278: }
|