/**
* OLAT - Online Learning and Training<br>
* http://www.olat.org
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br>
* University of Zurich, Switzerland.
* <p>
*/
package org.olat.login;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.olat.admin.sysinfo.InfoMessageManager;
import org.olat.core.commons.fullWebApp.util.GlobalStickyMessage;
import org.olat.core.configuration.OLATModule;
import org.olat.core.logging.StartupException;
import org.olat.core.logging.Tracing;
import org.olat.core.util.cache.n.CacheWrapper;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.login.auth.AuthenticationProvider;
import org.olat.properties.Property;
import org.olat.properties.PropertyManager;
import com.anthonyeden.lib.config.Configuration;
/**
* Initial Date: 04.08.2004
*
* @author Mike Stock
*/
public class LoginModule implements OLATModule {
private static final String CONF_AUTHPROVIDER = "AuthProvider";
private static final String CONF_ATTACK = "AttackPrevention";
private static final String CONF_ATTACK_ENABLED = "enabled";
private static final String CONF_ATTACK_MAXATTEMPTS = "maxattempts";
private static final String CONF_ATTACK_TIMEOUTMIN = "timeoutmin";
private static final String CONF_GUESTLINKS = "GuestLoginLinks";
private static final String CONF_GUESTLINKS_ENABLED = "enabled";
private static final String ALLOW_LOGIN_USING_EMAIL = "allowLoginUsingEmail";
private static HashMap authenticationProviders;
private static boolean attackPreventionEnabled = false;
private static int attackPreventionMaxAttempts;
private static int attackPreventionTimeout;
private static boolean guestLoginLinksEnabled = true;
private static CacheWrapper failedLoginCache;
private static String defaultProviderName;
private static boolean allowLoginUsingEmail = true;
/**
* @see org.olat.core.configuration.OLATModule#init(com.anthonyeden.lib.config.Configuration)
*/
public void init(Configuration moduleConfig) {
// fetch authentication providers
List authProviders = moduleConfig.getChildren(CONF_AUTHPROVIDER);
Iterator iter = authProviders.iterator();
authenticationProviders = new HashMap(authProviders.size());
while (iter.hasNext()) {
Configuration authProviderConfig = (Configuration)iter.next();
AuthenticationProvider authProvider = new AuthenticationProvider(authProviderConfig);
if (!authProvider.isEnabled()) continue;
authenticationProviders.put(authProvider.getName(), authProvider);
Tracing.logInfo("Added authentication Provider: name='"
+ authProvider.getName() + "' enabled='" + (authProvider.isEnabled() ? "yes" : "no") + "'.", LoginModule.class);
}
Configuration defProviderConf = moduleConfig.getChild("DefaultAuthProvider");
if (defProviderConf == null)
throw new StartupException("No DefaultAuthProvider defined. Please fix.");
defaultProviderName = defProviderConf.getAttribute("name");
if (defaultProviderName == null)
throw new StartupException("No DefaultAuthProvider defined. Please fix.");
AuthenticationProvider defaultProvider = getAuthenticationProvider(defaultProviderName);
if (defaultProvider == null || !defaultProvider.isEnabled())
throw new StartupException("Defined DefaultAuthProvider::" + defaultProviderName + " not existent or not enabled. Please fix.");
Tracing.logInfo("Using default authentication provider '" + defaultProviderName + "'.", LoginModule.class);
// check attack prevention
Configuration attack_conf = moduleConfig.getChild(CONF_ATTACK);
if (attack_conf != null) {
String attack_enabled = attack_conf.getAttribute(CONF_ATTACK_ENABLED);
if (attack_enabled != null &&
(attack_enabled.toLowerCase().equals("true") || attack_enabled.toLowerCase().equals("yes"))) {
attackPreventionEnabled = true;
String attack_maxattempts = attack_conf.getAttribute(CONF_ATTACK_MAXATTEMPTS);
String attack_timeoutmin = attack_conf.getAttribute(CONF_ATTACK_TIMEOUTMIN);
try {
attackPreventionMaxAttempts = Integer.parseInt(attack_maxattempts);
attackPreventionTimeout = Integer.parseInt(attack_timeoutmin);
} catch (NumberFormatException nfe) {
throw new StartupException("Attribute 'attempts' and/or 'timeoutmin' of config Parameter AttackPrevention are either missing or not a number. Please fix!");
}
// configure timed cache default params: refresh 1 minute, timeout according to configuration
failedLoginCache = CoordinatorManager.getCoordinator().getCacher().getOrCreateCache(this.getClass(), "blockafterfailedattempts");
Tracing.logInfo("Attack prevention enabled. Max number of attempts: " + attack_maxattempts + ", timeout: " + attack_timeoutmin + " minutes.", LoginModule.class);
} else {
attackPreventionEnabled = false;
Tracing.logInfo("Attack prevention is disabled.", LoginModule.class);
}
}
Configuration guestLinkConf = moduleConfig.getChild(CONF_GUESTLINKS);
if (guestLinkConf != null) {
String enabled = guestLinkConf.getAttribute(CONF_GUESTLINKS_ENABLED);
if (enabled != null && (enabled.toLowerCase().equals("true") || enabled.toLowerCase().equals("yes"))) {
guestLoginLinksEnabled = true;
Tracing.logInfo("Guest login links on login page enabled", LoginModule.class);
} else {
guestLoginLinksEnabled = false;
Tracing.logInfo("Guest login links on login page disabled or not properly configured. Check your olat_config.xml for element " + CONF_GUESTLINKS, LoginModule.class);
}
}
/*
* ensure that the infomessage property exists on the system
*/
PropertyManager pm = PropertyManager.getInstance();
Property p = pm.findProperty(null, null, null, "_o3_", "InfoMsg");
if (p == null) {
p = pm.createPropertyInstance(null, null, null, "_o3_", "InfoMsg", null, null, null, InfoMessageManager.EMPTY_MESSAGE);
pm.saveProperty(p);
}
/*
* ensure that the GlobalStickyMessage is initialized and registered to listen
* -> OLAT-4168
*/
GlobalStickyMessage.getInstance();
/*
* OLAT-4339 Allow login using email address. Configurable.
*/
String configLoginByEmail = moduleConfig.getChildValue(ALLOW_LOGIN_USING_EMAIL);
allowLoginUsingEmail = configLoginByEmail.equalsIgnoreCase("true");
}
/**
* @see org.olat.core.configuration.OLATModule#destroy()
*/
public void destroy() {
//any caches are destroyed by the coordinator, we do not need to take care here.
}
/**
* @return The configured default login provider.
*/
public static String getDefaultProviderName() {
return defaultProviderName;
}
/**
* @param provider
* @return AuthenticationProvider implementation.
*/
public static AuthenticationProvider getAuthenticationProvider(String provider) {
return (AuthenticationProvider)authenticationProviders.get(provider);
}
/**
* @return Collection of available AuthenticationProviders
*/
public static Collection getAuthenticationProviders() {
return authenticationProviders.values();
}
/**
* Must be called upon each login attempt. Returns true
* if number of login attempts has reached the set limit.
* @param login
* @return True if further logins will be prevented (i.e. max attempts reached).
*/
public static final boolean registerFailedLoginAttempt(String login) {
if (!attackPreventionEnabled) return false;
Integer numAttempts = (Integer)failedLoginCache.get(login);
if (numAttempts == null) { // create new entry
numAttempts = new Integer(1);
} else { // update entry
numAttempts = new Integer(numAttempts.intValue() + 1);
}
// do not use putSilent(...) here, since failed login attempts should propagate to all cluster nodes
// o_clusterREVIEW todo: this is fine, however loading the data (numAttempts == null) ... should be via db e.g properties table,
// otherwise it cannot be clustersafe
failedLoginCache.update(login, numAttempts);
return (numAttempts.intValue() > attackPreventionMaxAttempts);
}
/**
* Clear all failed login attempts for a given login.
* @param login
*/
public static final void clearFailedLoginAttempts(String login) {
if (!attackPreventionEnabled) return;
//EHCacheManager.getInstance().removeFromCache(failedLoginCache, login);
failedLoginCache.remove(login);
}
/**
* Tells wether a login is blocked to prevent brute force attacks or not.
* @param login
* @return True if login is blocked by attack prevention mechanism
*/
public static final boolean isLoginBlocked(String login) {
if (!attackPreventionEnabled) return false;
//Integer numAttempts = (Integer)EHCacheManager.getInstance().get(failedLoginCache, login);
Integer numAttempts = (Integer)failedLoginCache.get(login);
if (numAttempts == null) return false;
else return (numAttempts.intValue() > attackPreventionMaxAttempts);
}
/**
* @return True if guest login kinks must be shown on login screen, false
* otherwhise
*/
public static final boolean isGuestLoginLinksEnabled() {
return guestLoginLinksEnabled;
}
/**
* @return Number of minutes a login gets blocked after too many attempts.
*/
public static Integer getAttackPreventionTimeoutMin() {
return new Integer(attackPreventionTimeout);
}
/**
* @return True if login with email is allowed (set in olat_config.xml)
*/
public static boolean allowLoginUsingEmail() {
return allowLoginUsingEmail;
}
}