keycloak-aplcache

Merge pull request #2122 from abstractj/KEYCLOAK-2151 Brute

1/29/2016 12:32:28 PM

Details

diff --git a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
index 240bf23..8299489 100644
--- a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
+++ b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java
@@ -21,6 +21,7 @@ import org.keycloak.common.ClientConnection;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
 import org.keycloak.models.UsernameLoginFailureModel;
 import org.keycloak.services.ServicesLogger;
 
@@ -91,44 +92,49 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector
         logger.debug("failure");
         RealmModel realm = getRealmModel(session, event);
         logFailure(event);
-        UsernameLoginFailureModel user = getUserModel(session, event);
-        if (user == null) {
-            user = session.sessions().addUserLoginFailure(realm, event.username.toLowerCase());
-        }
-        user.setLastIPFailure(event.ip);
-        long currentTime = System.currentTimeMillis();
-        long last = user.getLastFailure();
-        long deltaTime = 0;
-        if (last > 0) {
-            deltaTime = currentTime - last;
-        }
-        user.setLastFailure(currentTime);
-        if (deltaTime > 0) {
-            // if last failure was more than MAX_DELTA clear failures
-            if (deltaTime > (long)realm.getMaxDeltaTimeSeconds() *1000L) {
-                user.clearFailures();
+        UserModel user = session.users().getUserByUsername(event.username.toString(), realm);
+        UsernameLoginFailureModel userLoginFailure = getUserModel(session, event);
+        if (user != null) {
+            if (userLoginFailure == null) {
+                userLoginFailure = session.sessions().addUserLoginFailure(realm, event.username.toLowerCase());
             }
-        }
-        user.incrementFailures();
-        logger.debugv("new num failures: {0}" , user.getNumFailures());
-
-        int waitSeconds = realm.getWaitIncrementSeconds() * (user.getNumFailures() / realm.getFailureFactor());
-        logger.debugv("waitSeconds: {0}", waitSeconds);
-        logger.debugv("deltaTime: {0}", deltaTime);
-        if (waitSeconds == 0) {
-            if (last > 0 && deltaTime < realm.getQuickLoginCheckMilliSeconds()) {
-                logger.debugv("quick login, set min wait seconds");
-                waitSeconds = realm.getMinimumQuickLoginWaitSeconds();
+            userLoginFailure.setLastIPFailure(event.ip);
+            long currentTime = System.currentTimeMillis();
+            long last = userLoginFailure.getLastFailure();
+            long deltaTime = 0;
+            if (last > 0) {
+                deltaTime = currentTime - last;
+            }
+            userLoginFailure.setLastFailure(currentTime);
+            if (deltaTime > 0) {
+                // if last failure was more than MAX_DELTA clear failures
+                if (deltaTime > (long) realm.getMaxDeltaTimeSeconds() * 1000L) {
+                    userLoginFailure.clearFailures();
+                }
+            }
+            userLoginFailure.incrementFailures();
+            logger.debugv("new num failures: {0}", userLoginFailure.getNumFailures());
+
+            int waitSeconds = realm.getWaitIncrementSeconds() * (userLoginFailure.getNumFailures() / realm.getFailureFactor());
+            logger.debugv("waitSeconds: {0}", waitSeconds);
+            logger.debugv("deltaTime: {0}", deltaTime);
+
+            if (waitSeconds == 0) {
+                if (last > 0 && deltaTime < realm.getQuickLoginCheckMilliSeconds()) {
+                    logger.debugv("quick login, set min wait seconds");
+                    waitSeconds = realm.getMinimumQuickLoginWaitSeconds();
+                }
+            }
+            if (waitSeconds > 0) {
+                waitSeconds = Math.min(realm.getMaxFailureWaitSeconds(), waitSeconds);
+                int notBefore = (int) (currentTime / 1000) + waitSeconds;
+                logger.debugv("set notBefore: {0}", notBefore);
+                userLoginFailure.setFailedLoginNotBefore(notBefore);
             }
-        }
-        if (waitSeconds > 0) {
-            waitSeconds = Math.min(realm.getMaxFailureWaitSeconds(), waitSeconds);
-            int notBefore = (int) (currentTime / 1000) + waitSeconds;
-            logger.debugv("set notBefore: {0}", notBefore);
-            user.setFailedLoginNotBefore(notBefore);
         }
     }
 
+
     protected UsernameLoginFailureModel getUserModel(KeycloakSession session, LoginEvent event) {
         RealmModel realm = getRealmModel(session, event);
         if (realm == null) return null;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
index 19beaf6..3f9c5c7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java
@@ -41,6 +41,7 @@ import org.keycloak.testsuite.pages.AppPage;
 import org.keycloak.testsuite.pages.AppPage.RequestType;
 import org.keycloak.testsuite.pages.LoginPage;
 import org.keycloak.testsuite.pages.LoginTotpPage;
+import org.keycloak.testsuite.pages.RegisterPage;
 import org.keycloak.testsuite.rule.GreenMailRule;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
@@ -102,12 +103,14 @@ public class BruteForceTest {
     protected LoginPage loginPage;
 
     @WebResource
+    private RegisterPage registerPage;
+
+    @WebResource
     protected LoginTotpPage loginTotpPage;
 
     @WebResource
     protected OAuthClient oauth;
 
-
     private TimeBasedOTP totp = new TimeBasedOTP();
 
     private int lifespan;
@@ -340,6 +343,17 @@ public class BruteForceTest {
         loginSuccess();
     }
 
+    @Test
+    public void testNonExistingAccounts() throws Exception {
+
+        loginInvalidPassword("non-existent-user");
+        loginInvalidPassword("non-existent-user");
+        loginInvalidPassword("non-existent-user");
+
+        registerUser("non-existent-user");
+
+    }
+
     public void expectTemporarilyDisabled() throws Exception {
         expectTemporarilyDisabled("test-user@localhost");
     }
@@ -430,4 +444,16 @@ public class BruteForceTest {
         events.clear();
     }
 
+    public void registerUser(String username){
+        loginPage.open();
+        loginPage.clickRegister();
+        registerPage.assertCurrent();
+
+        registerPage.register("user", "name",  username + "@localhost", username, "password", "password");
+
+        Assert.assertNull(registerPage.getInstruction());
+
+        events.clear();
+    }
+
 }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
index 29b5f9f..9820ad4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/RegisterPage.java
@@ -57,6 +57,10 @@ public class RegisterPage extends AbstractPage {
     @FindBy(className = "alert-error")
     private WebElement loginErrorMessage;
 
+    @FindBy(className = "instruction")
+    private WebElement loginInstructionMessage;
+
+
     public void register(String firstName, String lastName, String email, String username, String password, String passwordConfirm) {
         firstNameInput.clear();
         if (firstName != null) {
@@ -131,6 +135,15 @@ public class RegisterPage extends AbstractPage {
         return loginErrorMessage != null ? loginErrorMessage.getText() : null;
     }
 
+    public String getInstruction() {
+        try {
+            return loginInstructionMessage != null ? loginInstructionMessage.getText() : null;
+        } catch (NoSuchElementException e){
+            // OK
+        }
+        return null;
+    }
+
     public String getFirstName() {
         return firstNameInput.getAttribute("value");
     }
@@ -164,4 +177,4 @@ public class RegisterPage extends AbstractPage {
         throw new UnsupportedOperationException();
     }
 
-}
+}
\ No newline at end of file