mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-09 15:05:44 +01:00
added configuration options for login attempt limits
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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 --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user