/**
* $Revision$
* $Date$
*
* Copyright (C) 2005-2008 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.openfire.lockout;
import java.util.Date;
import java.util.Map;
import org.jivesoftware.util.ClassUtils;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The LockOutManager manages the LockOutProvider configured for this server, caches knowledge of
* whether accounts are disabled or enabled, and provides a single point of entry for handling
* locked/disabled accounts.
*
* The provider can be specified in system properties by adding:
*
* <ul>
* <li><tt>provider.lockout.className = my.lock.out.provider</tt></li>
* </ul>
*
* @author Daniel Henninger
*/
public class LockOutManager {
private static final Logger Log = LoggerFactory.getLogger(LockOutManager.class);
// Wrap this guy up so we can mock out the LockOutManager class.
private static class LockOutManagerContainer {
private static LockOutManager instance = new LockOutManager();
}
/**
* Returns the currently-installed LockOutProvider. <b>Warning:</b> in virtually all
* cases the lockout provider should not be used directly. Instead, the appropriate
* methods in LockOutManager should be called. Direct access to the lockout provider is
* only provided for special-case logic.
*
* @return the current LockOutProvider.
*/
public static LockOutProvider getLockOutProvider() {
return LockOutManagerContainer.instance.provider;
}
/**
* Returns a singleton instance of LockOutManager.
*
* @return a LockOutManager instance.
*/
public static LockOutManager getInstance() {
return LockOutManagerContainer.instance;
}
/* Cache of locked out accounts */
private Cache<String,LockOutFlag> lockOutCache;
private LockOutProvider provider;
/**
* Constructs a LockOutManager, setting up it's cache, propery listener, and setting up the provider.
*/
private LockOutManager() {
// Initialize the lockout cache.
lockOutCache = CacheFactory.createCache("Locked Out Accounts");
// Load an lockout provider.
initProvider();
// Detect when a new lockout provider class is set
PropertyEventListener propListener = new PropertyEventListener() {
public void propertySet(String property, Map params) {
if ("provider.lockout.className".equals(property)) {
initProvider();
}
}
public void propertyDeleted(String property, Map params) {
//Ignore
}
public void xmlPropertySet(String property, Map params) {
//Ignore
}
public void xmlPropertyDeleted(String property, Map params) {
//Ignore
}
};
PropertyEventDispatcher.addListener(propListener);
}
/**
* Initializes the server's lock out provider, based on configuration and defaults to
* DefaultLockOutProvider if the specified provider is not valid or not specified.
*/
private void initProvider() {
// Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("provider.lockout.className");
String className = JiveGlobals.getProperty("provider.lockout.className",
"org.jivesoftware.openfire.lockout.DefaultLockOutProvider");
// Check if we need to reset the provider class
if (provider == null || !className.equals(provider.getClass().getName())) {
try {
Class c = ClassUtils.forName(className);
provider = (LockOutProvider) c.newInstance();
}
catch (Exception e) {
Log.error("Error loading lockout provider: " + className, e);
provider = new DefaultLockOutProvider();
}
}
}
/**
* Returns a LockOutFlag for a given username, which contains information about the time
* period that the specified account is going to be disabled.
*
* @param username Username of account to request status of.
* @return The LockOutFlag instance describing the accounts disabled status or null if user
* account specified is not currently locked out (disabled).
*/
public LockOutFlag getDisabledStatus(String username) {
if (username == null) {
throw new UnsupportedOperationException("Null username not allowed!");
}
if (provider.shouldNotBeCached()) {
return provider.getDisabledStatus(username);
}
LockOutFlag flag = lockOutCache.get(username);
// If ID wan't found in cache, load it up and put it there.
if (flag == null) {
synchronized (username.intern()) {
flag = lockOutCache.get(username);
// If group wan't found in cache, load it up and put it there.
if (flag == null) {
flag = provider.getDisabledStatus(username);
lockOutCache.put(username, flag);
}
}
}
return flag;
}
/**
* Returns true or false if an account is currently locked out.
*
* @param username Username of account to check on.
* @return True or false if the account is currently locked out.
*/
public boolean isAccountDisabled(String username) {
LockOutFlag flag = getDisabledStatus(username);
if (flag == null) {
return false;
}
Date curDate = new Date();
if (flag.getStartTime() != null && curDate.before(flag.getStartTime())) {
return false;
}
if (flag.getEndTime() != null && curDate.after(flag.getEndTime())) {
return false;
}
return true;
}
/**
* Sets an account to disabled, starting at an optional time and ending at an optional time.
* If either times are set to null, the lockout is considered "forever" in that direction.
* For example, if you had a start time of 2 hours from now, and a null end time, then the account
* would be locked out in two hours, and never unlocked until someone manually unlcoked the account.
*
* @param username User whose account will be disabled.
* @param startTime When to start the lockout, or null if immediately.
* @param endTime When to end the lockout, or null if forever.
* @throws UnsupportedOperationException if the provider is readonly.
*/
public void disableAccount(String username, Date startTime, Date endTime) throws UnsupportedOperationException {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException();
}
LockOutFlag flag = new LockOutFlag(username, startTime, endTime);
provider.setDisabledStatus(flag);
if (!provider.shouldNotBeCached()) {
// Add lockout data to cache.
lockOutCache.put(username, flag);
}
// Fire event.
LockOutEventDispatcher.accountLocked(flag);
}
/**
* Enables an account that may or may not have previously been disabled. This erases any
* knowledge of a lockout, including one that wasn't necessarily in effect at the time the
* method was called.
*
* @param username User to enable.
* @throws UnsupportedOperationException if the provider is readonly.
*/
public void enableAccount(String username) throws UnsupportedOperationException {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException();
}
provider.unsetDisabledStatus(username);
if (!provider.shouldNotBeCached()) {
// Remove lockout data from cache.
lockOutCache.remove(username);
}
// Fire event.
LockOutEventDispatcher.accountUnlocked(username);
}
/**
* "Records" (notifies all listeners) that a failed login occurred.
*
* @param username Locked out user that attempted to login.
*/
public void recordFailedLogin(String username) {
// Fire event.
LockOutEventDispatcher.lockedAccountDenied(username);
}
}