/** * 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. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. */ package org.olat.login; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import org.olat.core.configuration.AbstractSpringModule; import org.olat.core.logging.OLog; import org.olat.core.logging.StartupException; import org.olat.core.logging.Tracing; import org.olat.core.util.Encoder; import org.olat.core.util.StringHelper; import org.olat.core.util.cache.CacheWrapper; import org.olat.core.util.coordinate.CoordinatorManager; import org.olat.login.auth.AuthenticationProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; /** * Initial Date: 04.08.2004 * * @author Mike Stock * @author guido */ @Service("loginModule") public class LoginModule extends AbstractSpringModule { private static final OLog log = Tracing.createLoggerFor(LoginModule.class); @Autowired private List<AuthenticationProvider> authenticationProviders; @Value("${login.attackPreventionEnabled:true}") private boolean attackPreventionEnabled; @Value("${login.AttackPreventionMaxattempts:5}") private int attackPreventionMaxAttempts; @Value("${login.AttackPreventionTimeoutmin:5}") private int attackPreventionTimeout; @Value("${invitation.login:enabled}") private String invitationEnabled; @Value("${guest.login:enabled}") private String guestLoginEnabled; @Value("${guest.login.links:enabled}") private String guestLoginLinksEnabled; private String defaultProviderName = "OLAT"; @Value("${login.using.username.or.email.enabled:true}") private boolean allowLoginUsingEmail; private CoordinatorManager coordinatorManager; private CacheWrapper<String,Integer> failedLoginCache; @Autowired public LoginModule(CoordinatorManager coordinatorManager) { super(coordinatorManager); this.coordinatorManager = coordinatorManager; } @Override public void init() { // configure timed cache default params: refresh 1 minute, timeout according to configuration failedLoginCache = coordinatorManager.getCoordinator().getCacher().getCache(LoginModule.class.getSimpleName(), "blockafterfailedattempts"); updateProperties(); boolean defaultProviderFound = false; for (Iterator<AuthenticationProvider> iterator = authenticationProviders.iterator(); iterator.hasNext();) { AuthenticationProvider provider = iterator.next(); if (provider.isDefault()) { defaultProviderFound = true; defaultProviderName = provider.getName(); log.info("Using default authentication provider '" + defaultProviderName + "'."); } } if (!defaultProviderFound) { throw new StartupException("Defined DefaultAuthProvider::" + defaultProviderName + " not existent or not enabled. Please fix."); } } @Override protected void initFromChangedProperties() { updateProperties(); } @Override protected void initDefaultProperties() { super.initDefaultProperties(); if (attackPreventionEnabled) { log.info("Attack prevention enabled. Max number of attempts: " + attackPreventionMaxAttempts + ", timeout: " + attackPreventionTimeout + " minutes."); } else { log.info("Attack prevention is disabled."); } //compatibility with older settings if("true".equals(guestLoginEnabled)) { guestLoginEnabled = "enabled"; } else if("false".equals(guestLoginEnabled)) { guestLoginEnabled = "disabled"; } if("true".equals(guestLoginLinksEnabled)) { guestLoginLinksEnabled = "enabled"; } else if("false".equals(guestLoginLinksEnabled)) { guestLoginLinksEnabled = "disabled"; } if("true".equals(invitationEnabled)) { invitationEnabled = "enabled"; } else if("false".equals(guestLoginLinksEnabled)) { invitationEnabled = "disabled"; } if (isGuestLoginEnabled()) { log.info("Guest login on login page enabled"); } else { log.info("Guest login on login page disabled or not properly configured. "); } if (isInvitationEnabled()) { log.info("Invitation login enabled"); } else { log.info("Invitation login disabled"); } } private void updateProperties() { //set properties String invitation = getStringPropertyValue("invitation.login", true); if(StringHelper.containsNonWhitespace(invitation)) { invitationEnabled = invitation; } String guestLogin = getStringPropertyValue("guest.login", true); if(StringHelper.containsNonWhitespace(guestLogin)) { guestLoginEnabled = guestLogin; } String guestLoginLinks = getStringPropertyValue("guest.login.links", true); if(StringHelper.containsNonWhitespace(guestLoginLinks)) { guestLoginLinksEnabled = guestLoginLinks; } String usernameOrEmailLogin = getStringPropertyValue("login.using.username.or.email.enabled", true); if(StringHelper.containsNonWhitespace(usernameOrEmailLogin)) { allowLoginUsingEmail = "true".equals(usernameOrEmailLogin); } } /** * @return The configured default login provider. */ public String getDefaultProviderName() { return defaultProviderName; } public Encoder.Algorithm getDefaultHashAlgorithm() { return Encoder.Algorithm.sha512; } /** * @param provider * @return AuthenticationProvider implementation. */ public AuthenticationProvider getAuthenticationProvider(String provider) { AuthenticationProvider authenticationProvider = null; for(AuthenticationProvider authProvider:authenticationProviders) { if(authProvider.getName().equalsIgnoreCase(provider)) { authenticationProvider = authProvider; } else if(authProvider.accept(provider)) { authenticationProvider = authProvider; } } return authenticationProvider; } /** * This method will always return something and will try to find some * matching provider. It will find LDAP'A0 -> LDAP or return the * default provider. * * @param provider * @return */ public AuthenticationProvider getAuthenticationProviderHeuristic(String provider) { //first exact match AuthenticationProvider authenticationProvider = getAuthenticationProvider(provider); if(authenticationProvider == null && StringHelper.containsNonWhitespace(provider)) { String upperedCaseProvider = provider.toUpperCase(); for(AuthenticationProvider authProvider:authenticationProviders) { if(upperedCaseProvider.contains(authProvider.getName().toUpperCase())) { authenticationProvider = authProvider; break; } } } if(authenticationProvider == null) { //return default for(AuthenticationProvider authProvider:authenticationProviders) { if(authProvider.isDefault()) { authenticationProvider = authProvider; break; } } } return authenticationProvider; } /** * @return Collection of available AuthenticationProviders */ public Collection<AuthenticationProvider> getAuthenticationProviders() { return new ArrayList<>(authenticationProviders); } /** * 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 final boolean registerFailedLoginAttempt(String login) { if (!attackPreventionEnabled) return false; Integer numAttempts = failedLoginCache.get(login); if (numAttempts == null) { // create new entry numAttempts = new Integer(1); failedLoginCache.put(login, numAttempts); } else { // update entry numAttempts = new Integer(numAttempts.intValue() + 1); failedLoginCache.update(login, numAttempts); } return (numAttempts.intValue() > attackPreventionMaxAttempts); } /** * Clear all failed login attempts for a given login. * @param login */ public final void clearFailedLoginAttempts(String login) { if (attackPreventionEnabled) { failedLoginCache.remove(login); } } /** * Tells whether a login is blocked to prevent brute force attacks or not. * @param login * @return True if login is blocked by attack prevention mechanism */ public final boolean isLoginBlocked(String login) { if (!attackPreventionEnabled) return false; Integer numAttempts = failedLoginCache.get(login); if (numAttempts == null) return false; else return (numAttempts.intValue() > attackPreventionMaxAttempts); } /** * @return True if guest login must be shown on login screen, false * otherwise */ public boolean isGuestLoginEnabled() { return "enabled".equals(guestLoginEnabled); } public void setGuestLoginEnabled(boolean enabled) { guestLoginEnabled = enabled ? "enabled" : "disabled"; setStringProperty("guest.login", guestLoginEnabled, true); } public boolean isGuestLoginLinksEnabled() { return "enabled".equalsIgnoreCase(guestLoginLinksEnabled); } public void setGuestLoginLinksEnabled(boolean enabled) { guestLoginLinksEnabled = enabled ? "enabled" : "disabled"; setStringProperty("guest.login.links", guestLoginLinksEnabled, true); } public boolean isInvitationEnabled() { return "enabled".equals(invitationEnabled); } public void setInvitationEnabled(boolean enabled) { invitationEnabled = enabled ? "enabled" : "disabled"; setStringProperty("invitation.login", invitationEnabled, true); } /** * @return Number of minutes a login gets blocked after too many attempts. */ public Integer getAttackPreventionTimeoutMin() { return new Integer(attackPreventionTimeout); } /** * @return True if login with email is allowed (set in olat.properties) */ public boolean isAllowLoginUsingEmail() { return allowLoginUsingEmail; } public void setAllowLoginUsingEmail(boolean allow) { allowLoginUsingEmail = allow; setStringProperty("login.using.username.or.email.enabled", Boolean.toString(allow), true); } }