Wait for the brute force off-thread processing in AbstractAdvancedBrokerTest
Closes #30188 Closes #30641 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
3e134f714c
commit
5ea3becef5
2 changed files with 51 additions and 6 deletions
|
@ -27,6 +27,7 @@ import org.junit.rules.TestRule;
|
||||||
import org.junit.runners.model.Statement;
|
import org.junit.runners.model.Statement;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -41,6 +42,7 @@ import java.util.HashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.emptyOrNullString;
|
import static org.hamcrest.Matchers.emptyOrNullString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
@ -369,16 +371,21 @@ public class AssertEvents implements TestRule {
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventRepresentation assertEvent() {
|
public EventRepresentation assertEvent() {
|
||||||
return assertEvent(false);
|
return assertEvent(false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventRepresentation assertEvent(boolean ignorePreviousEvents) {
|
||||||
|
return assertEvent(ignorePreviousEvents, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Assert the expected event was sent to the listener by Keycloak server. Returns this event.
|
* Assert the expected event was sent to the listener by Keycloak server. Returns this event.
|
||||||
*
|
*
|
||||||
* @param ignorePreviousEvents if true, test will ignore all the events, which were already present. Test will poll the events from the queue until it finds the event of expected type
|
* @param ignorePreviousEvents if true, test will ignore all the events, which were already present. Test will poll the events from the queue until it finds the event of expected type
|
||||||
|
* @param seconds The seconds to wait for the next event to come
|
||||||
* @return the expected event
|
* @return the expected event
|
||||||
*/
|
*/
|
||||||
public EventRepresentation assertEvent(boolean ignorePreviousEvents) {
|
public EventRepresentation assertEvent(boolean ignorePreviousEvents, int seconds) {
|
||||||
if (expected.getError() != null && ! expected.getType().endsWith("_ERROR")) {
|
if (expected.getError() != null && ! expected.getType().endsWith("_ERROR")) {
|
||||||
expected.setType(expected.getType() + "_ERROR");
|
expected.setType(expected.getType() + "_ERROR");
|
||||||
}
|
}
|
||||||
|
@ -387,7 +394,7 @@ public class AssertEvents implements TestRule {
|
||||||
// Consider 25 as a "limit" for maximum number of events in the queue for now
|
// Consider 25 as a "limit" for maximum number of events in the queue for now
|
||||||
List<String> presentedEventTypes = new LinkedList<>();
|
List<String> presentedEventTypes = new LinkedList<>();
|
||||||
for (int i = 0 ; i < 25 ; i++) {
|
for (int i = 0 ; i < 25 ; i++) {
|
||||||
EventRepresentation event = fetchNextEvent();
|
EventRepresentation event = fetchNextEvent(seconds);
|
||||||
if (event == null) {
|
if (event == null) {
|
||||||
Assert.fail("Did not find the event of expected type " + expected.getType() +". Events present: " + presentedEventTypes);
|
Assert.fail("Did not find the event of expected type " + expected.getType() +". Events present: " + presentedEventTypes);
|
||||||
}
|
}
|
||||||
|
@ -523,4 +530,26 @@ public class AssertEvents implements TestRule {
|
||||||
private EventRepresentation fetchNextEvent() {
|
private EventRepresentation fetchNextEvent() {
|
||||||
return context.testingClient.testing().pollEvent();
|
return context.testingClient.testing().pollEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private EventRepresentation fetchNextEvent(int seconds) {
|
||||||
|
if (seconds <= 0) {
|
||||||
|
return fetchNextEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
final long millis = TimeUnit.SECONDS.toMillis(seconds);
|
||||||
|
final long start = Time.currentTimeMillis();
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
EventRepresentation event = fetchNextEvent();
|
||||||
|
if (event != null) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
// wait a bit to receive the event
|
||||||
|
TimeUnit.MILLISECONDS.sleep(millis / 10L);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
} while (Time.currentTimeMillis() - start < millis);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package org.keycloak.testsuite.broker;
|
package org.keycloak.testsuite.broker;
|
||||||
|
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.Details;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
import org.keycloak.models.IdentityProviderMapperSyncMode;
|
||||||
import org.keycloak.models.IdentityProviderSyncMode;
|
import org.keycloak.models.IdentityProviderSyncMode;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
import org.keycloak.models.utils.TimeBasedOTP;
|
||||||
|
@ -18,6 +21,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
import org.keycloak.storage.UserStorageProvider;
|
import org.keycloak.storage.UserStorageProvider;
|
||||||
import org.keycloak.testsuite.Assert;
|
import org.keycloak.testsuite.Assert;
|
||||||
|
import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
|
||||||
import org.keycloak.testsuite.util.AccountHelper;
|
import org.keycloak.testsuite.util.AccountHelper;
|
||||||
|
@ -70,6 +74,8 @@ import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
|
public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public AssertEvents events = new AssertEvents(this);
|
||||||
|
|
||||||
protected void createRoleMappersForConsumerRealm() {
|
protected void createRoleMappersForConsumerRealm() {
|
||||||
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.FORCE);
|
createRoleMappersForConsumerRealm(IdentityProviderMapperSyncMode.FORCE);
|
||||||
|
@ -546,7 +552,8 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
|
||||||
totpPage.assertCurrent();
|
totpPage.assertCurrent();
|
||||||
String totpSecret = totpPage.getTotpSecret();
|
String totpSecret = totpPage.getTotpSecret();
|
||||||
totpPage.configure(totp.generateTOTP(totpSecret));
|
totpPage.configure(totp.generateTOTP(totpSecret));
|
||||||
assertNumFederatedIdentities(realm.users().search(bc.getUserLogin()).get(0).getId(), 1);
|
final UserRepresentation user = realm.users().search(bc.getUserLogin()).get(0);
|
||||||
|
assertNumFederatedIdentities(user.getId(), 1);
|
||||||
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
|
AccountHelper.logout(adminClient.realm(bc.consumerRealmName()), bc.getUserLogin());
|
||||||
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
AccountHelper.logout(adminClient.realm(bc.providerRealmName()), bc.getUserLogin());
|
||||||
|
|
||||||
|
@ -563,16 +570,25 @@ public abstract class AbstractAdvancedBrokerTest extends AbstractBrokerTest {
|
||||||
loginTotpPage.login("bad-totp");
|
loginTotpPage.login("bad-totp");
|
||||||
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
||||||
|
|
||||||
|
events.clear();
|
||||||
|
|
||||||
loginTotpPage.login("bad-totp");
|
loginTotpPage.login("bad-totp");
|
||||||
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
||||||
|
|
||||||
|
// wait for the disabled to come
|
||||||
|
events.expect(EventType.USER_DISABLED_BY_TEMPORARY_LOCKOUT)
|
||||||
|
.realm(consumerRealmRep.getId())
|
||||||
|
.user(user.getId())
|
||||||
|
.client((String) null)
|
||||||
|
.detail(Details.REASON, "brute_force_attack detected")
|
||||||
|
.assertEvent(true, 5);
|
||||||
|
|
||||||
// Login with valid TOTP. I should not be able to login
|
// Login with valid TOTP. I should not be able to login
|
||||||
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
loginTotpPage.login(totp.generateTOTP(totpSecret));
|
||||||
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
Assert.assertEquals("Invalid authenticator code.", loginTotpPage.getInputError());
|
||||||
|
|
||||||
// Clear login failures
|
// Clear login failures
|
||||||
String userId = ApiUtil.findUserByUsername(realm, bc.getUserLogin()).getId();
|
realm.attackDetection().clearBruteForceForUser(user.getId());
|
||||||
realm.attackDetection().clearBruteForceForUser(userId);
|
|
||||||
|
|
||||||
setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp);
|
setOtpTimeOffset(TimeBasedOTP.DEFAULT_INTERVAL_SECONDS, totp);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue