added configuration options for login attempt limits

This commit is contained in:
Sebastian Sdorra
2013-09-16 17:36:16 +02:00
parent bfa4372626
commit b2c1336b08
3 changed files with 258 additions and 28 deletions

View File

@@ -52,6 +52,7 @@ import java.io.File;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAccessorType;
@@ -152,7 +153,6 @@ public class ScmConfiguration
*/ */
public void load(ScmConfiguration other) public void load(ScmConfiguration other)
{ {
this.servername = other.servername;
this.dateFormat = other.dateFormat; this.dateFormat = other.dateFormat;
this.pluginUrl = other.pluginUrl; this.pluginUrl = other.pluginUrl;
this.anonymousAccessEnabled = other.anonymousAccessEnabled; this.anonymousAccessEnabled = other.anonymousAccessEnabled;
@@ -168,8 +168,11 @@ public class ScmConfiguration
this.baseUrl = other.baseUrl; this.baseUrl = other.baseUrl;
this.disableGroupingGrid = other.disableGroupingGrid; this.disableGroupingGrid = other.disableGroupingGrid;
this.enableRepositoryArchive = other.enableRepositoryArchive; this.enableRepositoryArchive = other.enableRepositoryArchive;
this.loginAttemptLimit = other.loginAttemptLimit;
this.loginAttemptLimitTimeout = other.loginAttemptLimitTimeout;
// deprecated fields // deprecated fields
this.servername = other.servername;
this.sslPort = other.sslPort; this.sslPort = other.sslPort;
this.enableSSL = other.enableSSL; this.enableSSL = other.enableSSL;
this.enablePortForward = other.enablePortForward; this.enablePortForward = other.enablePortForward;
@@ -249,6 +252,31 @@ public class ScmConfiguration
return forwardPort; return forwardPort;
} }
/**
* Returns maximum allowed login attempts.
*
* @return maximum allowed login attempts
*
* @since 1.34
*/
public int getLoginAttemptLimit()
{
return loginAttemptLimit;
}
/**
* Returns the timeout in seconds for users which are temporary disabled,
* because of too many failed login attempts.
*
* @return login attempt timeout in seconds
*
* @since 1.34
*/
public long getLoginAttemptLimitTimeout()
{
return loginAttemptLimitTimeout;
}
/** /**
* Returns the url of the plugin repository. This url can contain placeholders. * Returns the url of the plugin repository. This url can contain placeholders.
* Explanation of the {placeholders}: * Explanation of the {placeholders}:
@@ -581,6 +609,32 @@ public class ScmConfiguration
this.forwardPort = forwardPort; this.forwardPort = forwardPort;
} }
/**
* Set maximum allowed login attempts.
*
*
* @param loginAttemptLimit login attempt limit
*
* @since 1.34
*/
public void setLoginAttemptLimit(int loginAttemptLimit)
{
this.loginAttemptLimit = loginAttemptLimit;
}
/**
* Sets the timeout in seconds for users which are temporary disabled,
* because of too many failed login attempts.
*
* @param loginAttemptLimitTimeout login attempt timeout in seconds
*
* @since 1.34
*/
public void setLoginAttemptLimitTimeout(long loginAttemptLimitTimeout)
{
this.loginAttemptLimitTimeout = loginAttemptLimitTimeout;
}
/** /**
* Method description * Method description
* *
@@ -692,9 +746,6 @@ public class ScmConfiguration
@XmlElement(name = "base-url") @XmlElement(name = "base-url")
private String baseUrl; private String baseUrl;
/** Field description */
private boolean enableProxy = false;
/** Field description */ /** Field description */
@XmlElement(name = "force-base-url") @XmlElement(name = "force-base-url")
private boolean forceBaseUrl; private boolean forceBaseUrl;
@@ -703,6 +754,24 @@ public class ScmConfiguration
@Deprecated @Deprecated
private int forwardPort = 80; private int forwardPort = 80;
/**
* Maximum allowed login attempts.
*
* @since 1.34
*/
@XmlElement(name = "login-attempt-limit")
private int loginAttemptLimit = -1;
/**
* Login attempt timeout.
*
* @since 1.34
*/
private long loginAttemptLimitTimeout = TimeUnit.MINUTES.toSeconds(5l);
/** Field description */
private boolean enableProxy = false;
/** Field description */ /** Field description */
@XmlElement(name = "plugin-url") @XmlElement(name = "plugin-url")
private String pluginUrl = DEFAULT_PLUGINURL; private String pluginUrl = DEFAULT_PLUGINURL;

View File

@@ -95,17 +95,83 @@ public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler
@Override @Override
public void beforeAuthentication(AuthenticationToken token) public void beforeAuthentication(AuthenticationToken token)
throws AuthenticationException throws AuthenticationException
{
if (isEnabled())
{
handleBeforeAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
@Override
public void onSuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException
{
if (isEnabled())
{
handleOnSuccessfulAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
@Override
public void onUnsuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException
{
if (isEnabled())
{
handleOnUnsuccessfulAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
*/
private void handleBeforeAuthentication(AuthenticationToken token)
{ {
LoginAttempt attempt = getAttempt(token); LoginAttempt attempt = getAttempt(token);
long time = System.currentTimeMillis() - attempt.lastAttempt; long time = System.currentTimeMillis() - attempt.lastAttempt;
if (time > TimeUnit.SECONDS.toMillis(5l)) if (time > getLoginAttemptLimitTimeout())
{ {
logger.debug("reset login attempts for {}, because of time", logger.debug("reset login attempts for {}, because of time",
token.getPrincipal()); token.getPrincipal());
attempt.reset(); attempt.reset();
} }
else if (attempt.counter >= 5) else if (attempt.counter >= configuration.getLoginAttemptLimit())
{ {
logger.warn("account {} is temporary locked, because of {}", logger.warn("account {} is temporary locked, because of {}",
token.getPrincipal(), attempt); token.getPrincipal(), attempt);
@@ -124,9 +190,7 @@ public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler
* *
* @throws AuthenticationException * @throws AuthenticationException
*/ */
@Override private void handleOnSuccessfulAuthentication(AuthenticationToken token)
public void onSuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException throws AuthenticationException
{ {
logger.debug("reset login attempts for {}, because of successful login", logger.debug("reset login attempts for {}, because of successful login",
@@ -143,9 +207,7 @@ public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler
* *
* @throws AuthenticationException * @throws AuthenticationException
*/ */
@Override private void handleOnUnsuccessfulAuthentication(AuthenticationToken token)
public void onUnsuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException throws AuthenticationException
{ {
logger.debug("increase failed login attempts for {}", token.getPrincipal()); logger.debug("increase failed login attempts for {}", token.getPrincipal());
@@ -179,6 +241,30 @@ public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler
return attempt; return attempt;
} }
/**
* Method description
*
*
* @return
*/
private long getLoginAttemptLimitTimeout()
{
return TimeUnit.SECONDS.toMillis(
configuration.getLoginAttemptLimitTimeout());
}
/**
* Method description
*
*
* @return
*/
private boolean isEnabled()
{
return (configuration.getLoginAttemptLimit() > 0)
&& (configuration.getLoginAttemptLimitTimeout() > 0l);
}
//~--- inner classes -------------------------------------------------------- //~--- inner classes --------------------------------------------------------
/** /**

View File

@@ -27,12 +27,25 @@
* http://bitbucket.org/sdorra/scm-manager * http://bitbucket.org/sdorra/scm-manager
* *
*/ */
package sonia.scm.security; package sonia.scm.security;
import java.util.concurrent.TimeUnit; //~--- non-JDK imports --------------------------------------------------------
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.UsernamePasswordToken;
import org.junit.Test; import org.junit.Test;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.web.security.AuthenticationResult; import sonia.scm.web.security.AuthenticationResult;
import sonia.scm.web.security.AuthenticationState;
//~--- JDK imports ------------------------------------------------------------
import java.util.concurrent.TimeUnit;
/** /**
* *
@@ -41,24 +54,86 @@ import sonia.scm.web.security.AuthenticationResult;
public class ConfigurableLoginAttemptHandlerTest public class ConfigurableLoginAttemptHandlerTest
{ {
@Test /**
public void testLoginAttempt() throws InterruptedException * Method description
*
*/
@Test(expected = ExcessiveAttemptsException.class)
public void testLoginAttemptLimitReached()
{ {
ConfigurableLoginAttemptHandler handler = new ConfigurableLoginAttemptHandler(null); LoginAttemptHandler handler = createHandler(2, 2);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo"); UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token); handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED); handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token); handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED); handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
// asd
Thread.currentThread().sleep(TimeUnit.SECONDS.toMillis(10));
handler.beforeAuthentication(token); handler.beforeAuthentication(token);
} }
/**
* Method description
*
*
* @throws InterruptedException
*/
@Test
public void testLoginAttemptLimitTimeout() throws InterruptedException
{
LoginAttemptHandler handler = createHandler(2, 1);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
Thread.currentThread().sleep(TimeUnit.MILLISECONDS.toMillis(1200l));
handler.beforeAuthentication(token);
}
/**
* Method description
*
*
* @throws InterruptedException
*/
@Test
public void testLoginAttemptResetOnSuccess() throws InterruptedException
{
LoginAttemptHandler handler = createHandler(2, 1);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.onSuccessfulAuthentication(token,
new AuthenticationResult(AuthenticationState.SUCCESS));
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
}
/**
* Method description
*
*
* @param loginAttemptLimit
* @param loginAttemptLimitTimeout
*
* @return
*/
private LoginAttemptHandler createHandler(int loginAttemptLimit,
long loginAttemptLimitTimeout)
{
ScmConfiguration configuration = new ScmConfiguration();
configuration.setLoginAttemptLimit(loginAttemptLimit);
configuration.setLoginAttemptLimitTimeout(loginAttemptLimitTimeout);
return new ConfigurableLoginAttemptHandler(configuration);
}
} }