/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.login.local.view; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.lang.Validate; /** Ips that are temporarily blacklisted. * * <p>IP addresses can be added to the black list, and they will remain in the * list for a configured period of time. After that period, the IP will be * removed from the blacklist. * * @author mario.roman */ public class IpBlacklist { /** The set of blacklisted IP address mapped to the task that will evict the * entry. * * It is never null. */ private Map<String, TimerTask> ips = Collections.synchronizedMap(new HashMap<String, TimerTask>()); /** The timer that will run every configured period of time removing the * blacklisted IP addresses, never null. */ private Timer timer = new Timer(true); /** The period of time in milliseconds that an IP address will remain * blacklisted. * * It will be always greater than 0. */ private long period; /** Marks if this ip blacklisting is enabled or not. * * The effect of disabling blacklisting is that captchas will be effectively * disabled. isEnabled must be true for alwaysBlacklist to be true. */ private boolean isEnabled; /** Marks if all ips will be considered blacklisted. * * This forces captchas to be shown always. This cannot be true if isEnabled * is false. */ private boolean alwaysBlacklist; /** Flags that destroy was called. * * Used for debugging purposes only. See finalize(); */ private boolean destroyed = false; /** Creates a new IpBlacklist with a given period of time that an IP will * remain blacklisted. * * @param blacklistDuration the period of that that an IP will remain * blacklisted, in milliseconds. It must be greater than 0. * * @param enable true enables blacklisting. * * @param forceBlacklist true makes all ip blacklisted, forcing the captchas * to be inconditionally shown. forceBlacklist can only be true if enable is * true. */ public IpBlacklist(final long blacklistDuration, final boolean enable, final boolean forceBlacklist) { Validate.isTrue(blacklistDuration > 0, "The time an ip is blacklisted must be greater than 0."); Validate.isTrue(enable || !forceBlacklist, "You cannot force blacklist if captcha is disabled."); period = blacklistDuration; isEnabled = enable; alwaysBlacklist = forceBlacklist; } /** Blacklists an ip. * * The ip will be blacklisted for the configured amount of time, defined in * the constructor. * * @param ipAddress IP address to blacklist, cannot be null nor empty. */ public void blacklistIp(final String ipAddress) { Validate.notNull(ipAddress, "The IP address cannot be null"); Validate.notEmpty(ipAddress, "The IP address cannot be empty"); if (isEnabled) { scheduleRemoveTask(ipAddress); } } /** Checks if the ip has been blacklisted. * * @param ipAddress IP address to check, it cannot be null. * * @return {@code true} if the Blacklist contains the given IP, {@code false} * otherwise. */ public boolean isBlacklisted(final String ipAddress) { Validate.notNull(ipAddress, "The ip address cannot be null."); if (alwaysBlacklist) { return true; } else { return ips.containsKey(ipAddress); } } /** Destroys all the resources allocated by this IpBlacklist instance. * * Once this operation is called, this class is no longer usable. Continue * using this object will give unexpected results. * * This object is normally instantiated by spring, so add * destroy-method='destroy' to the bean declaration. */ public void destroy() { destroyed = true; timer.cancel(); } /** Checks that the object was destroyed and logs an error to the console * otherwise. * * {@inheritDoc} * * Warning: we are not calling super.finalize() here, because we just inherit * from Object. * * The original signature was protected void finalize() throws Throwable, but * it makes findbugs complain. */ @Override protected void finalize() { if (!destroyed) { System.out.println( "ERROR: IpBlacklist instance not correctly destroyed."); } } /** Schedules a new task to remove the given IP address from the list of * blacklisted ips. * * This will add a new task every time that the IP fails, but it's still a * good enough solution for now. * * @param ipAddress IP address to remove, it cannot be null. */ private synchronized void scheduleRemoveTask(final String ipAddress) { Validate.notNull(ipAddress, "The ip address cannot be null."); if (ips.containsKey(ipAddress)) { ips.get(ipAddress).cancel(); } TimerTask task = new TimerTask() { @Override public void run() { ips.remove(ipAddress); } }; timer.schedule(task, period); ips.put(ipAddress, task); } }