Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(oxauth): corrected race condition during refresh token usage (4.5.5) #1914

Merged
merged 1 commit into from
Jul 31, 2024
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
14 changes: 10 additions & 4 deletions Server/src/main/java/org/gluu/oxauth/service/GrantService.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import javax.inject.Inject;
import java.util.*;

import static org.gluu.oxauth.util.ServerUtil.calculateTtl;
import static org.gluu.oxauth.util.ServerUtil.isTrue;

/**
Expand Down Expand Up @@ -73,13 +74,18 @@ private String tokenBaseDn() {
return staticConfiguration.getBaseDn().getTokens(); // ou=tokens,o=gluu
}

public void merge(TokenLdap p_token) {
ldapEntryManager.merge(p_token);
public void merge(TokenLdap token) {
if (shouldPutInCache(token.getTokenTypeEnum(), token.isImplicitFlow())) {
final int expiration = calculateTtl(new Date(), token.getExpirationDate());
cacheService.put(expiration, token.getTokenCode(), token);
} else {
ldapEntryManager.merge(token);
}
}

public void mergeSilently(TokenLdap p_token) {
public void mergeSilently(TokenLdap token) {
try {
ldapEntryManager.merge(p_token);
merge(token);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.gluu.oxauth.model.configuration.AppConfiguration;
import org.gluu.oxauth.model.crypto.binding.TokenBindingMessage;
import org.gluu.oxauth.model.error.ErrorResponseFactory;
import org.gluu.oxauth.model.ldap.TokenLdap;
import org.gluu.oxauth.model.registration.Client;
import org.gluu.oxauth.model.session.SessionClient;
import org.gluu.oxauth.model.session.SessionId;
Expand Down Expand Up @@ -52,6 +53,9 @@
import javax.ws.rs.core.SecurityContext;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import static org.gluu.oxauth.util.ServerUtil.prepareForLogs;

Expand All @@ -65,6 +69,8 @@
@Path("/")
public class TokenRestWebServiceImpl implements TokenRestWebService {

private static final String NODE_ID = UUID.randomUUID().toString();

@Inject
private Logger log;

Expand Down Expand Up @@ -116,6 +122,8 @@ public class TokenRestWebServiceImpl implements TokenRestWebService {
@Inject
private ExternalUpdateTokenService externalUpdateTokenService;

private final ConcurrentMap<String, TokenLdap> refreshTokenLocalLock = new ConcurrentHashMap<>();

@Override
public Response requestAccessToken(String grantType, String code,
String redirectUri, String username, String password, String scope,
Expand Down Expand Up @@ -270,6 +278,12 @@ grantType, code, redirectUri, username, refreshToken, clientId, prepareForLogs(r
return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Unable to find refresh token or otherwise token type or client does not match."), oAuth2AuditLog);
}

TokenLdap lockedRefreshToken = lockRefreshToken(refreshToken);
if (lockedRefreshToken == null) {
log.trace("Failed to lock refresh token {}", refreshToken);
return response(error(400, TokenErrorResponseType.INVALID_GRANT, "Failed to lock refresh token."), oAuth2AuditLog);
}

checkUser(authorizationGrant, oAuth2AuditLog);
executionContext.setGrant(authorizationGrant);

Expand Down Expand Up @@ -553,6 +567,44 @@ grantType, code, redirectUri, username, refreshToken, clientId, prepareForLogs(r
return response(builder, oAuth2AuditLog);
}

private TokenLdap lockRefreshToken(String refreshTokenCode) {
try {
if (refreshTokenLocalLock.containsKey(refreshTokenCode)) {
log.trace("Refresh token is already used by another request. Refresh token code: {}", refreshTokenCode);
return null;
}

for (int attempt = 1; attempt <= 3; attempt++) {
try {
final TokenLdap token = grantService.getGrantByCode(refreshTokenCode);
if (token == null) {
log.trace("Refresh token is not found by code {}", refreshTokenCode);
return null;
}

refreshTokenLocalLock.put(refreshTokenCode, token);

token.getAttributes().getAttributes().put("lockKey", NODE_ID);
grantService.mergeSilently(token);
final TokenLdap tokenFromDb = grantService.getGrantByCode(refreshTokenCode);
if (NODE_ID.equals(tokenFromDb.getAttributes().getAttributes().get("lockKey"))) {
log.trace("Successfully locked refresh token {}, attempt {}", refreshTokenCode, attempt);
return token;
}

log.trace("Failed to lock refresh token {}, attempt {}", refreshTokenCode, attempt);
Thread.sleep(100);
} catch (InterruptedException e) {
// ignore and make next attempt
log.trace(e.getMessage(), e);
}
}
} finally {
refreshTokenLocalLock.remove(refreshTokenCode);
}
return null;
}

private void checkUser(AuthorizationGrant authorizationGrant, OAuth2AuditLog oAuth2AuditLog) {
if (!appConfiguration.getCheckUserPresenceOnRefreshToken()) {
return;
Expand Down
Loading