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,28 +20,23 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.session.RevokedTokenPersisterProvider;
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;

import org.infinispan.client.hotrod.exceptions.HotRodClientException;
import org.infinispan.commons.api.BasicCache;
import org.jboss.logging.Logger;

/**
* TODO: Check if Boolean can be used as single-use cache argument instead of SingleUseObjectValueEntity. With respect to other single-use cache usecases like "Revoke Refresh Token" .
* TODO: Check if Boolean can be used as single-use cache argument instead of SingleUseObjectValueEntity. With respect to other single-use cache use cases like "Revoke Refresh Token" .
* Also with respect to the usage of streams iterating over "actionTokens" cache (check there are no ClassCastExceptions when casting values directly to SingleUseObjectValueEntity)
*
*
* @author <a href="mailto:[email protected]">Marek Posolda</a>
*/
public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvider {

public static final Logger logger = Logger.getLogger(InfinispanSingleUseObjectProvider.class);

private final KeycloakSession session;
private final BasicCache<String, SingleUseObjectValueEntity> singleUseObjectCache;
private final boolean persistRevokedTokens;
Expand All @@ -57,15 +52,7 @@ public InfinispanSingleUseObjectProvider(KeycloakSession session, BasicCache<Str
@Override
public void put(String key, long lifespanSeconds, Map<String, String> notes) {
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(notes);
try {
tx.put(singleUseObjectCache, key, tokenValue, Time.toMillis(lifespanSeconds), TimeUnit.MILLISECONDS);
} catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
if (logger.isDebugEnabled()) {
logger.debugf(re, "Failed when adding code %s", key);
}
throw re;
}
tx.put(singleUseObjectCache, key, tokenValue, lifespanSeconds, TimeUnit.SECONDS);
if (persistRevokedTokens && key.endsWith(REVOKED_KEY)) {
if (!notes.isEmpty()) {
throw new ModelException("Notes are not supported for revoked tokens");
Expand All @@ -80,9 +67,7 @@ public Map<String, String> get(String key) {
throw new ModelException("Revoked tokens can't be retrieved");
}

SingleUseObjectValueEntity singleUseObjectValueEntity;

singleUseObjectValueEntity = tx.get(singleUseObjectCache, key);
SingleUseObjectValueEntity singleUseObjectValueEntity = tx.get(singleUseObjectCache, key);
return singleUseObjectValueEntity != null ? singleUseObjectValueEntity.getNotes() : null;
}

Expand All @@ -92,18 +77,14 @@ public Map<String, String> remove(String key) {
throw new ModelException("Revoked tokens can't be removed");
}

try {
SingleUseObjectValueEntity existing = singleUseObjectCache.remove(key);
return existing == null ? null : existing.getNotes();
} catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place.
if (logger.isDebugEnabled()) {
logger.debugf(re, "Failed when removing code %s", key);
}

// Using a get-before-remove allows us to return the value even in cases when a state transfer happens in Infinispan
// where it might not return the value in all cases.
// This workaround can be removed once https://github.com/infinispan/infinispan/issues/16703 is implemented.
var data = singleUseObjectCache.get(key);
if (data == null) {
return null;
}
return singleUseObjectCache.remove(key, data) ? data.getNotes() : null;
}

@Override
Expand All @@ -118,21 +99,11 @@ public boolean replace(String key, Map<String, String> notes) {
@Override
public boolean putIfAbsent(String key, long lifespanInSeconds) {
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(null);

try {
SingleUseObjectValueEntity existing = singleUseObjectCache.putIfAbsent(key, tokenValue, Time.toMillis(lifespanInSeconds), TimeUnit.MILLISECONDS);
if (persistRevokedTokens && key.endsWith(REVOKED_KEY)) {
session.getProvider(RevokedTokenPersisterProvider.class).revokeToken(key.substring(0, key.length() - REVOKED_KEY.length()), lifespanInSeconds);
}
return existing == null;
} catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to use the token from different place.
logger.debugf(re, "Failed when adding token %s", key);

return false;
SingleUseObjectValueEntity existing = singleUseObjectCache.putIfAbsent(key, tokenValue, lifespanInSeconds, TimeUnit.SECONDS);
if (persistRevokedTokens && key.endsWith(REVOKED_KEY)) {
session.getProvider(RevokedTokenPersisterProvider.class).revokeToken(key.substring(0, key.length() - REVOKED_KEY.length()), lifespanInSeconds);
}

return existing == null;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,16 @@ public String toString() {
return String.format("SingleUseObjectValueEntity [ notes=%s ]", notes.toString());
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;

SingleUseObjectValueEntity that = (SingleUseObjectValueEntity) o;
return notes.equals(that.notes);
}

@Override
public int hashCode() {
return notes.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ public Map<String, String> get(String key) {
@Override
public Map<String, String> remove(String key) {
try {
return unwrap(withReturnValue().remove(key));
// Using a get-before-remove allows us to return the value even in cases when a state transfer happens in Infinispan
// where it might not return the value in all cases.
// This workaround can be removed once https://github.com/infinispan/infinispan/issues/16703 is implemented.
var data = transaction.getCache().getWithMetadata(key);
if (data == null) {
return null;
}
return transaction.getCache().removeWithVersion(key, data.getVersion()) ?
Copy link
Member

Choose a reason for hiding this comment

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

Curious: Does removeWithVersion() work nicely in a retry? What does a remove-with-version do if the entry doesn't exist on the primary owner?

Copy link
Member Author

Choose a reason for hiding this comment

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

It does what we do for embedded

var e = cache.getEntry(k)
if (e.version == expectedVersion)
  cache.remove(k, e.value)

unwrap(data.getValue()) :
null;
} catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place.
Expand Down
Loading