Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;

import org.keycloak.common.ClientConnection;
import org.keycloak.models.AbstractKeycloakTransaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
Expand All @@ -33,6 +35,7 @@ public class DefaultBlockingBruteForceProtector extends DefaultBruteForceProtect
// make this configurable?
private static final int DEFAULT_MAX_CONCURRENT_ATTEMPTS = 1000;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final String OFF_THREAD_STARTED = "#brute_force_started";

private final Map<String, String> loginAttempts = Collections.synchronizedMap(new LinkedHashMap<>(100, DEFAULT_LOAD_FACTOR) {
@Override
Expand Down Expand Up @@ -73,46 +76,80 @@ private boolean isLoginInProgress(KeycloakSession session, UserModel user) {
return false;
}

if (isCurrentLoginAttempt(user)) {
return !tryEnlistBlockingTransaction(session, user);
}

return true;
return !tryEnlistBlockingTransactionOrSameThread(session, user);
}

// Return true if this thread successfully enlisted itself
private boolean tryEnlistBlockingTransaction(KeycloakSession session, UserModel user) {
String threadInProgress = loginAttempts.computeIfAbsent(user.getId(), k -> getThreadName());
// Return true if this thread successfully enlisted itself or it was already done by the same thread
private boolean tryEnlistBlockingTransactionOrSameThread(KeycloakSession session, UserModel user) {
AtomicBoolean inserted = new AtomicBoolean(false);
String threadInProgress = loginAttempts.computeIfAbsent(user.getId(), k -> {
inserted.set(true);
return getThreadName();
});

// This means that this thread successfully added itself into the map. We can enlist transaction just in that case
if (threadInProgress.equals(getThreadName())) {
if (inserted.get()) {
session.getTransactionManager().enlistAfterCompletion(new AbstractKeycloakTransaction() {
@Override
protected void commitImpl() {
unblock();
// remove or wait the brute force thread to finish
loginAttempts.computeIfPresent(user.getId(), (k, v) -> v.endsWith(OFF_THREAD_STARTED)? "" : null);
}

@Override
protected void rollbackImpl() {
unblock();
}

private void unblock() {
// remove on rollback
loginAttempts.remove(user.getId());
}
});

return true;
} else {
return false;
return isCurrentThread(threadInProgress);
}
}

private boolean isCurrentLoginAttempt(UserModel user) {
return loginAttempts.getOrDefault(user.getId(), getThreadName()).equals(getThreadName());
private boolean isCurrentThread(String name) {
return name.equals(getThreadName()) || name.equals(getThreadName() + OFF_THREAD_STARTED);
}

private String getThreadName() {
return Thread.currentThread().getName();
}

private void enlistRemoval(KeycloakSession session, String userId) {
session.getTransactionManager().enlistAfterCompletion(new AbstractKeycloakTransaction() {
@Override
protected void commitImpl() {
// remove or wait the main thread to finish
loginAttempts.computeIfPresent(userId, (k, v) -> v.isEmpty()? null : v.substring(0, v.length() - OFF_THREAD_STARTED.length()));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you prefer:

v.endsWith(OFF_THREAD_STARTED)? v.substring(0, v.length() - OFF_THREAD_STARTED.length()) : null

Just let me know. It should be the same but maybe it's more clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might happen to not have the suffix appended to the value? If not, works for me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, or it has the suffix appended (off-thread is finishing first) or it is empty (main thread finished first). I think it's the same.

}

@Override
protected void rollbackImpl() {
loginAttempts.remove(userId);
}
});
}

@Override
protected void processLogin(RealmModel realm, UserModel user, ClientConnection clientConnection, boolean success) {
// mark the off-thread is started for this request
loginAttempts.computeIfPresent(user.getId(), (k, v) -> v + OFF_THREAD_STARTED);
super.processLogin(realm, user, clientConnection, success);
}

@Override
protected void failure(KeycloakSession session, RealmModel realm, String userId, String remoteAddr, long failureTime) {
// remove the user from concurrent login attemps once it's processed
enlistRemoval(session, userId);
super.failure(session, realm, userId, remoteAddr, failureTime);
}

@Override
protected void success(KeycloakSession session, RealmModel realm, String userId) {
// remove the user from concurrent login attemps once it's processed
enlistRemoval(session, userId);
super.success(session, realm, userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public void successfulLogin(RealmModel realm, UserModel user, ClientConnection c
logger.trace("sent success event");
}

private void processLogin(RealmModel realm, UserModel user, ClientConnection clientConnection, boolean success) {
protected void processLogin(RealmModel realm, UserModel user, ClientConnection clientConnection, boolean success) {
ExecutorService executor = KeycloakModelUtils.runJobInTransactionWithResult(factory, session -> {
ExecutorsProvider provider = session.getProvider(ExecutorsProvider.class);
return provider.getExecutor("bruteforce");
Expand Down