Stabilizing asynchronous login failure processing - #52503
Conversation
000c053 to
ebd5280
Compare
Closes keycloak#52502 Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
ebd5280 to
b08a6ec
Compare
Unreported flaky test detectedIf the flaky tests below are affected by the changes, please review and update the changes accordingly. Otherwise, a maintainer should report the flaky tests prior to merging the PR. org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsLoginAndPassiveCheckKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredInTheMiddleKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#loginActionWithoutExecutionInRequiredActionsKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRefreshInTab1Keycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#testLoginPageRefreshKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#loginWithDifferentClientsKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestWithAuthSessionExpiredAndRegisterClickKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#multipleTabsParallelLoginTestKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#expiredAuthenticationAction_expiredCodeCurrentExecutionKeycloak CI - Forms IT (chrome) org.keycloak.testsuite.forms.MultipleTabsLoginTest#testInjectRedirectUriInClientDataAfterAuthSessionExpirationKeycloak CI - Forms IT (chrome) |
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Stabilizes legacy Arquillian brute-force tests by waiting for asynchronous login processing.
Changes:
- Adds waits between repeated invalid login attempts.
- Centralizes waits in shared login helpers.
- Documents the wait utility and shortens its polling interval.
File summaries
| File | Description |
|---|---|
| testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java | Updated as part of this pull request. |
| testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/ldap/LDAPReadOnlyTest.java | Updated as part of this pull request. |
| testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/WaitUtils.java | Updated as part of this pull request. |
Review details
Suppressed comments (5)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:320
- The first invalid OTP grant can still race with the asynchronous successful grant at line 310, which uses the same executor and
loginAttemptstracking. Wait for that successful setup task before sending the request at line 316.
WaitUtils.waitForBruteForceExecutors(testingClient);
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:370
- The first missing-OTP grant can still race with the asynchronous successful grant at line 360, which keeps the same
loginAttemptsentry until its executor task commits. Add the barrier after the successful setup request too.
WaitUtils.waitForBruteForceExecutors(testingClient);
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:422
- The first loop iteration is not serialized with the successful grant at line 412. Since successful logins are processed by this executor too, add a wait after that setup request or the first OTP failure can be force-challenged without incrementing the counter.
WaitUtils.waitForBruteForceExecutors(testingClient);
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:1452
lockUserWithPasswordGrant()sends its first invalid request immediately after a successful grant, but successful logins also occupyloginAttemptsuntil their asynchronous executor task commits. Drain the executor after that setup grant so the first loop iteration cannot be skipped.
WaitUtils.waitForBruteForceExecutors(testingClient);
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:1164
- The first
wrongpassrequest is still unsynchronized with the successful browser login above. That success also runs asynchronously and can keeploginAttemptsoccupied, so wait afterexpectLoginSuccess(...)before sending the first failure.
WaitUtils.waitForBruteForceExecutors(testingClient);
- Files reviewed: 3/3 changed files
- Comments generated: 4
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| String sessionId = EventAssertion.expectLoginSuccess(events.poll()).getEvent().getSessionId(); | ||
|
|
||
| getTestToken("wrongpass", totpSecret); | ||
| WaitUtils.waitForBruteForceExecutors(testingClient); |
There was a problem hiding this comment.
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review details
Suppressed comments (1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:1052
- This drains the failed request, but the successful token request below also clears failures asynchronously and is followed immediately by the
numFailures == 0assertion. Drain the executor after that successful response too, otherwise this test can still observe the old count of 1.
WaitUtils.waitForBruteForceExecutors(testingClient);
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
ba041b9 to
074065a
Compare
be4fd6f to
074065a
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review details
Suppressed comments (1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java:1403
- Adding the barrier only to the password helper leaves the same race in the browser OTP paths:
loginWithTotpFailure()and repeatedcontinueLoginWithInvalidTotp()calls enqueue OTP failures without waiting, whileOTPFormAuthenticatorchecks brute-force state before each attempt. An in-flight OTP failure can therefore turn the next attempt into a force challenge and skip its counter increment; add the wait to those failure helpers too.
WaitUtils.waitForBruteForceExecutors(testingClient);
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com>
Closes #52502
Root cause
Same as #52428 / #52429:
DefaultBlockingBruteForceProtectorprocesses failure counting asynchronously via an executor. When two login failures for the same user arrive in quick succession, the second request hitsisLoginInProgress() == true, gets aforceChallenge()instead offailureChallenge(), andfailedLogin()is never called — so the failure counter is not incremented. The counter stays at 1 instead of reachingfailureFactor=2, and subsequent lockout assertions fail.PR #52429 fixed this for
AttackDetectionResourceTest(new testsuite, password grant). The same race exists inBruteForceTestandLDAPReadOnlyTest(old testsuite), both for browser-based and password-grant login attempts.Approach
Added
WaitUtils.waitForBruteForceExecutors(testingClient)after each invalid login attempt to ensure the async executor task completes before the next request is sent. This is the old-testsuite equivalent of theawaitNumFailures()polling used in PR #52429 for the new testsuite.The wait is added inside the shared helper methods (
loginInvalidPassword(),sendInvalidPasswordPasswordGrant()) so all callers are covered, plus explicit waits at directgetTestToken()call sites. Redundant standalonewaitForBruteForceExecutorscalls that existed before are removed.Once #52128 makes brute force processing synchronous, these waits become unnecessary.