/* * Copyright (C) 2004-2009 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.admin; import java.util.Map; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.TaskEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Handles recording admin console login attempts and handling temporary lockouts where necessary. * * @author Daniel Henninger */ public class LoginLimitManager { private static final Logger Log = LoggerFactory.getLogger(LoginLimitManager.class); // Wrap this guy up so we can mock out the LoginLimitManager class. private static class LoginLimitManagerContainer { private static LoginLimitManager instance = new LoginLimitManager(); } /** * Returns a singleton instance of LoginLimitManager. * * @return a LoginLimitManager instance. */ public static LoginLimitManager getInstance() { return LoginLimitManagerContainer.instance; } // Max number of attempts per ip address that can be performed in given time frame private long maxAttemptsPerIP; // Time frame before attempts per ip addresses are reset private long millisecondsBetweenPerIP; // Max number of attempts per username that can be performed in a given time frame private long maxAttemptsPerUsername; // Time frame before attempts per username are reset private long millisecondsBetweenPerUsername; // Record of attempts per IP address private Map<String,Long> attemptsPerIP; // Record of attempts per username private Map<String,Long> attemptsPerUsername; /** * Constructs a new login limit manager. */ private LoginLimitManager() { // Set up initial maps attemptsPerIP = new ConcurrentHashMap<>(); attemptsPerUsername = new ConcurrentHashMap<>(); // Max number of attempts per ip address that can be performed in given time frame (10 attempts default) maxAttemptsPerIP = JiveGlobals.getLongProperty("adminConsole.maxAttemptsPerIP", 10); // Time frame before attempts per ip addresses are reset (15 minutes default) millisecondsBetweenPerIP = JiveGlobals.getLongProperty("adminConsole.perIPAttemptResetInterval", 900000); // Max number of attempts per username that can be performed in a given time frame (10 attempts default) maxAttemptsPerUsername = JiveGlobals.getLongProperty("adminConsole.maxAttemptsPerUsername", 10); // Time frame before attempts per ip addresses are reset (15 minutes default) millisecondsBetweenPerUsername = JiveGlobals.getLongProperty("adminConsole.perUsernameAttemptResetInterval", 900000); // Set up per username attempt reset task TaskEngine.getInstance().scheduleAtFixedRate(new PerUsernameTask(), 0, millisecondsBetweenPerUsername); // Set up per IP attempt reset task TaskEngine.getInstance().scheduleAtFixedRate(new PerIPAddressTask(), 0, millisecondsBetweenPerIP); } /** * Returns true of the entered username or connecting IP address has hit it's attempt limit. * * @param username Username being checked. * @param address IP address that is connecting. * @return True if the login attempt limit has been hit. */ public boolean hasHitConnectionLimit(String username, String address) { if (attemptsPerIP.get(address) != null && attemptsPerIP.get(address) > maxAttemptsPerIP) { return true; } if (attemptsPerUsername.get(username) != null && attemptsPerUsername.get(username) > maxAttemptsPerUsername) { return true; } // No problem then, no limit hit. return false; } /** * Records a failed connection attempt. * * @param username Username being attempted. * @param address IP address that is attempting. */ public void recordFailedAttempt(String username, String address) { Log.warn("Failed admin console login attempt by "+username+" from "+address); Long cnt = (long)0; if (attemptsPerIP.get(address) != null) { cnt = attemptsPerIP.get(address); } cnt++; attemptsPerIP.put(address, cnt); if (cnt > maxAttemptsPerIP) { Log.warn("Login attempt limit breeched for address "+address); } cnt = (long)0; if (attemptsPerUsername.get(username) != null) { cnt = attemptsPerUsername.get(username); } cnt++; attemptsPerUsername.put(username, cnt); if (cnt > maxAttemptsPerUsername) { Log.warn("Login attempt limit breeched for username "+username); } } /** * Clears failed login attempts if a success occurs. * * @param username Username being attempted. * @param address IP address that is attempting. */ public void recordSuccessfulAttempt(String username, String address) { attemptsPerIP.remove(address); attemptsPerUsername.remove(username); } /** * Runs at configured interval to clear out attempts per username, thereby wiping lockouts. */ private class PerUsernameTask extends TimerTask { /** * Wipes failed attempt list for usernames. */ @Override public void run() { attemptsPerUsername.clear(); } } /** * Runs at configured interval to clear out attempts per ip address, thereby wiping lockouts. */ private class PerIPAddressTask extends TimerTask { /** * Wipes failed attempt list for ip addresses. */ @Override public void run() { attemptsPerIP.clear(); } } }