/*******************************************************************************
* Copyright 2009, 2010 Innovation Gate GmbH. All Rights Reserved.
*
* This file is part of the OpenWGA server platform.
*
* OpenWGA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* In addition, a special exception is granted by the copyright holders
* of OpenWGA called "OpenWGA plugin exception". You should have received
* a copy of this exception along with OpenWGA in file COPYING.
* If not, see <http://www.openwga.com/gpl-plugin-exception>.
*
* OpenWGA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenWGA in file COPYING.
* If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package de.innovationgate.wgpublisher.auth;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.log4j.Logger;
import de.innovationgate.webgate.api.WGAPIException;
import de.innovationgate.webgate.api.WGDatabase;
import de.innovationgate.webgate.api.auth.AuthenticationException;
import de.innovationgate.webgate.api.auth.AuthenticationSession;
import de.innovationgate.wga.config.Administrator;
import de.innovationgate.wgpublisher.WGACore;
import de.innovationgate.wgpublisher.WGAMailNotification;
import de.innovationgate.wgpublisher.WGACore.DomainConfiguration;
public class BruteForceLoginBlocker {
public static final Logger LOG = Logger.getLogger("wga.api.auth");
private WGACore _core;
public BruteForceLoginBlocker(WGACore core) {
_core = core;
}
public static final String DOMAIN_ADMINLOGINS = WGACore.DOMAIN_ADMINLOGINS;
private Map _failedLoginAttempts = new HashMap();
public class LoginAttemptsComparator implements Comparator {
public int compare(Object arg0, Object arg1) {
LoginAttemptInformation inf1 = (LoginAttemptInformation) arg0;
LoginAttemptInformation inf2 = (LoginAttemptInformation) arg1;
return new CompareToBuilder().append(inf1.getDomain(), inf2.getDomain()).append(inf1.getName(), inf2.getName()).toComparison();
}
}
public synchronized void clearFailedLoginAttempts(String domain, String user) {
Map newAttemptMap = new HashMap();
newAttemptMap.putAll(_failedLoginAttempts);
newAttemptMap.remove(LoginAttemptInformation.createLoginAttemptKey(domain, user));
_failedLoginAttempts = newAttemptMap;
}
/**
* @return Returns the failedLoginAttempts.
*/
public List getFailedLoginAttempts() {
List list = new ArrayList(_failedLoginAttempts.values());
Collections.sort(list, new LoginAttemptsComparator());
return Collections.unmodifiableList(list);
}
/**
* @return currently blocked logins
*/
public List getBlockedLogins() {
List failedLogins = getFailedLoginAttempts();
Iterator loginsIt = failedLogins.iterator();
LoginAttemptInformation inf;
List blockedLogins = new ArrayList();
while (loginsIt.hasNext()) {
inf = (LoginAttemptInformation) loginsIt.next();
// add only blocked logins to list
if (!inf.isBlocked()) {
continue;
} else {
blockedLogins.add(inf);
}
}
Collections.sort(blockedLogins, new LoginAttemptsComparator());
return Collections.unmodifiableList(blockedLogins);
}
public LoginAttemptInformation getLoginAttemptInformation(String domain, String user) {
return (LoginAttemptInformation) _failedLoginAttempts.get(LoginAttemptInformation.createLoginAttemptKey(domain, user));
}
public AuthenticationSession login(DomainConfiguration domainConfig, String username, String password) throws AuthenticationException {
LoginAttemptInformation inf = getLoginAttemptInformation(domainConfig.getName(), username);
if (inf != null && inf.isBlocked()) {
LOG.warn("Failed login for '" + username + "': Username is blocked because of too many wrong login attempts (Brute force login blocker on domain " + domainConfig.getName() + ")");
return null;
}
AuthenticationSession authSession = domainConfig.getAuthModule().login(username, password);
if (authSession != null) {
if (inf != null) {
inf.reset();
}
return authSession;
}
if (inf == null) {
inf = new LoginAttemptInformation(domainConfig.getName(), username, domainConfig.getMaximumLoginAttempts());
inf.map(_failedLoginAttempts);
}
inf.addFailedAttempt();
if (inf.isBlocked()) {
WGAMailNotification mail = new WGAMailNotification(WGAMailNotification.TYPE_LOGIN_BLOCKED);
mail.setSubject("Login for user '" + domainConfig.getName() + "/" + username + "' has been blocked bc. of '" + inf.getFailedAttempts() + "' failed login attemps.");
mail.append("Login for user <b>'" + domainConfig.getName() + "/" + username + "'</b> has been blocked bc. of <b>'" + inf.getFailedAttempts() + "'</b> failed login attemps.");
_core.send(mail);
}
return null;
}
public int login(WGDatabase db, String username, String password) throws WGAPIException {
String domain = (String) db.getAttribute(WGACore.DBATTRIB_DOMAIN);
DomainConfiguration domainConfig = _core.getDomainConfig(domain);
LoginAttemptInformation inf = getLoginAttemptInformation(domain, username);
if (inf != null && inf.isBlocked()) {
LOG.warn("Failed login for '" + username + "': Username is blocked because of too many wrong login attempts (Brute force login blocker on database " + db.getDbReference() + ")");
return WGDatabase.ACCESSLEVEL_NOTLOGGEDIN;
}
int level = (db.isSessionOpen() ? db.reopenSession(username, password) : db.openSession(username, password));
if (level == WGDatabase.ACCESSLEVEL_NOTLOGGEDIN) {
if (inf == null) {
inf = new LoginAttemptInformation(domain, username, domainConfig.getMaximumLoginAttempts());
inf.map(_failedLoginAttempts);
}
inf.addFailedAttempt();
if (inf.isBlocked()) {
WGAMailNotification mail = new WGAMailNotification(WGAMailNotification.TYPE_LOGIN_BLOCKED);
mail.setSubject("Login for user '" + domain + "/" + username + "' has been blocked bc. of '" + inf.getFailedAttempts() + "' failed login attemps.");
mail.append("Login for user <b>'" + domain + "/" + username + "'</b> has been blocked bc. of <b>'" + inf.getFailedAttempts() + "'</b> failed login attemps.");
_core.send(mail);
}
}
else if (inf != null) {
inf.reset();
}
return level;
}
public boolean login(Administrator admin, String password) {
LoginAttemptInformation inf = getLoginAttemptInformation(DOMAIN_ADMINLOGINS, admin.getUsername());
if (inf != null && inf.isBlocked()) {
LOG.warn("Failed login for administrator '" + admin + "': Username is blocked because of too many wrong login attempts (Brute force login blocker for admin login)");
return false;
}
try {
if (admin.isPasswordCorrect(password)) {
if (inf != null) {
inf.reset();
}
return true;
}
} catch (NoSuchAlgorithmException e) {
_core.getLog().fatal("VM does not support password hash algorithm. Unable to check admin login.", e);
return false;
} catch (UnsupportedEncodingException e) {
_core.getLog().fatal("Unknown encoding of admin password. Unable to check admin login.", e);
return false;
}
if (inf == null) {
inf = new LoginAttemptInformation(DOMAIN_ADMINLOGINS, admin.getUsername(), LoginAttemptInformation.DEFAULT_MAX_FAILED_ATTEMPTS);
inf.map(_failedLoginAttempts);
}
inf.addFailedAttempt();
if (inf.isBlocked()) {
WGAMailNotification mail = new WGAMailNotification(WGAMailNotification.TYPE_LOGIN_BLOCKED);
mail.setSubject("WGA Administrator login '" + admin.getUsername() + "' has been blocked bc. of '" + inf.getFailedAttempts() + "' failed login attemps.");
mail.append("WGA Administrator login <b>'" + admin.getUsername() + "'</b> has been blocked bc. of <b>'" + inf.getFailedAttempts() + "'</b> failed login attemps.");
_core.send(mail);
}
return false;
}
public boolean isLoginBlocked(String domain, String user) {
LoginAttemptInformation info = getLoginAttemptInformation(domain, user);
if (info != null && info.isBlocked()) {
return true;
}
else {
return false;
}
}
}