Merge ActionTokenStoreProvider and SingleUseObjectProvider (#13677)

Closes #13334
This commit is contained in:
Martin Kanis 2022-10-13 09:26:44 +02:00 committed by GitHub
parent 90c1624668
commit 761929d174
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 290 additions and 1018 deletions

View file

@ -1,100 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.*;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.infinispan.Cache;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProvider implements ActionTokenStoreProvider {
private static final Logger LOG = Logger.getLogger(InfinispanActionTokenStoreProvider.class);
private final Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache;
private final InfinispanKeycloakTransaction tx;
private final KeycloakSession session;
public InfinispanActionTokenStoreProvider(KeycloakSession session, Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionKeyCache) {
this.session = session;
this.actionKeyCache = actionKeyCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public void close() {
}
@Override
public void put(ActionTokenKeyModel key, Map<String, String> notes) {
if (key == null || key.getUserId() == null || key.getActionId() == null) {
return;
}
ActionTokenReducedKey tokenKey = new ActionTokenReducedKey(key.getUserId(), key.getActionId(), key.getActionVerificationNonce());
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
LOG.debugf("Adding used action token to actionTokens cache: %s", tokenKey.toString());
this.tx.put(actionKeyCache, tokenKey, tokenValue, key.getExpiration() - Time.currentTime(), TimeUnit.SECONDS);
}
@Override
public ActionTokenValueModel get(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
ActionTokenValueModel value = this.actionKeyCache.getAdvancedCache().get(key);
if (value == null) {
LOG.debugf("Not found any value in actionTokens cache for key: %s", key.toString());
} else {
LOG.debugf("Found value in actionTokens cache for key: %s", key.toString());
}
return value;
}
@Override
public ActionTokenValueModel remove(ActionTokenKeyModel actionTokenKey) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null) {
return null;
}
ActionTokenReducedKey key = new ActionTokenReducedKey(actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce());
ActionTokenValueEntity value = this.actionKeyCache.get(key);
if (value != null) {
this.tx.remove(actionKeyCache, key);
}
return value;
}
}

View file

@ -1,86 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan;
import org.keycloak.Config;
import org.keycloak.Config.Scope;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.*;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
import org.infinispan.Cache;
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY;
/**
*
* @author hmlnarik
*/
public class InfinispanActionTokenStoreProviderFactory implements ActionTokenStoreProviderFactory {
private volatile Cache<ActionTokenReducedKey, ActionTokenValueEntity> actionTokenCache;
public static final String ACTION_TOKEN_EVENTS = "ACTION_TOKEN_EVENTS";
private Config.Scope config;
@Override
public ActionTokenStoreProvider create(KeycloakSession session) {
return new InfinispanActionTokenStoreProvider(session, this.actionTokenCache);
}
@Override
public void init(Scope config) {
this.config = config;
}
private static Cache<ActionTokenReducedKey, ActionTokenValueEntity> initActionTokenCache(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
return cache;
}
@Override
public void postInit(KeycloakSessionFactory factory) {
Cache<ActionTokenReducedKey, ActionTokenValueEntity> cache = this.actionTokenCache;
// It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
// means also listeners will start only after first cache initialization - that would be too late
if (cache == null) {
synchronized (this) {
cache = this.actionTokenCache;
if (cache == null) {
this.actionTokenCache = initActionTokenCache(factory.create());
}
}
}
}
@Override
public void close() {
}
@Override
public String getId() {
return "infinispan";
}
@Override
public int order() {
return PROVIDER_PRIORITY;
}
}

View file

@ -16,6 +16,8 @@
*/
package org.keycloak.models.sessions.infinispan;
import org.infinispan.client.hotrod.RemoteCache;
import org.infinispan.commons.api.BasicCache;
import org.keycloak.cluster.ClusterEvent;
import org.keycloak.cluster.ClusterProvider;
import org.infinispan.context.Flag;
@ -97,7 +99,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
}
}
public <K, V> void put(Cache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
public <K, V> void put(BasicCache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_WITH_LIFESPAN, key);
Object taskKey = getTaskKey(cache, key);
@ -179,7 +181,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
tasks.put(taskKey, () -> clusterProvider.notify(taskKey, event, ignoreSender, ClusterProvider.DCNotify.ALL_DCS));
}
public <K, V> void remove(Cache<K, V> cache, K key) {
public <K, V> void remove(BasicCache<K, V> cache, K key) {
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, key);
@ -201,7 +203,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
}
// This is for possibility to lookup for session by id, which was created in this transaction
public <K, V> V get(Cache<K, V> cache, K key) {
public <K, V> V get(BasicCache<K, V> cache, K key) {
Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey);
if (current != null) {
@ -214,7 +216,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
return cache.get(key);
}
private static <K, V> Object getTaskKey(Cache<K, V> cache, K key) {
private static <K, V> Object getTaskKey(BasicCache<K, V> cache, K key) {
if (key instanceof String) {
return new StringBuilder(cache.getName())
.append("::")
@ -245,8 +247,10 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
}
// Ignore return values. Should have better performance within cluster / cross-dc env
private static <K, V> Cache<K, V> decorateCache(Cache<K, V> cache) {
return cache.getAdvancedCache()
private static <K, V> BasicCache<K, V> decorateCache(BasicCache<K, V> cache) {
if (cache instanceof RemoteCache)
return cache;
return ((Cache) cache).getAdvancedCache()
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP);
}
}

View file

@ -27,12 +27,12 @@ import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
import org.keycloak.connections.infinispan.InfinispanUtil;
/**
* TODO: Check if Boolean can be used as single-use cache argument instead of ActionTokenValueEntity. With respect to other single-use cache usecases 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 ActionTokenValueEntity)
* 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" .
* 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:mposolda@redhat.com">Marek Posolda</a>
@ -41,22 +41,24 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
public static final Logger logger = Logger.getLogger(InfinispanSingleUseObjectProvider.class);
private final Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
private final Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache;
private final KeycloakSession session;
private final InfinispanKeycloakTransaction tx;
public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier<BasicCache<String, ActionTokenValueEntity>> actionKeyCache) {
public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache) {
this.session = session;
this.tokenCache = actionKeyCache;
this.singleUseObjectCache = singleUseObjectCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
}
@Override
public void put(String key, long lifespanSeconds, Map<String, String> notes) {
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(notes);
try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds));
cache.put(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
tx.put(cache, key, tokenValue, InfinispanUtil.toHotrodTimeMs(cache, 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()) {
@ -69,17 +71,23 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
@Override
public Map<String, String> get(String key) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
ActionTokenValueEntity actionTokenValueEntity = cache.get(key);
return actionTokenValueEntity != null ? actionTokenValueEntity.getNotes() : null;
SingleUseObjectValueEntity singleUseObjectValueEntity;
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
singleUseObjectValueEntity = tx.get(cache, key);
return singleUseObjectValueEntity != null ? singleUseObjectValueEntity.getNotes() : null;
}
@Override
public Map<String, String> remove(String key) {
try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
ActionTokenValueEntity existing = cache.remove(key);
return existing == null ? null : existing.getNotes();
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
SingleUseObjectValueEntity singleUseObjectValueEntity = tx.get(cache, key);
if (singleUseObjectValueEntity != null) {
tx.remove(cache, key);
return singleUseObjectValueEntity.getNotes();
}
return 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.
@ -93,18 +101,18 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
@Override
public boolean replace(String key, Map<String, String> notes) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
return cache.replace(key, new ActionTokenValueEntity(notes)) != null;
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
return cache.replace(key, new SingleUseObjectValueEntity(notes)) != null;
}
@Override
public boolean putIfAbsent(String key, long lifespanInSeconds) {
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null);
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(null);
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanInSeconds));
ActionTokenValueEntity existing = cache.putIfAbsent(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
SingleUseObjectValueEntity existing = cache.putIfAbsent(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
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.
@ -118,7 +126,7 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
@Override
public boolean contains(String key) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
return cache.containsKey(key);
}

View file

@ -17,7 +17,6 @@
package org.keycloak.models.sessions.infinispan;
import java.util.function.Supplier;
import org.infinispan.Cache;
import org.infinispan.client.hotrod.Flag;
@ -26,11 +25,14 @@ import org.infinispan.commons.api.BasicCache;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanUtil;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.connections.infinispan.InfinispanUtil;
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
import java.util.function.Supplier;
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY;
/**
@ -40,26 +42,14 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
private static final Logger LOG = Logger.getLogger(InfinispanSingleUseObjectProviderFactory.class);
// Reuse "actionTokens" infinispan cache for now
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
private volatile Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache;
@Override
public InfinispanSingleUseObjectProvider create(KeycloakSession session) {
lazyInit(session);
return new InfinispanSingleUseObjectProvider(session, tokenCache);
return new InfinispanSingleUseObjectProvider(session, singleUseObjectCache);
}
private void lazyInit(KeycloakSession session) {
if (tokenCache == null) {
synchronized (this) {
if (tokenCache == null) {
this.tokenCache = getActionTokenCache(session);
}
}
}
}
static Supplier getActionTokenCache(KeycloakSession session) {
static Supplier getSingleUseObjectCache(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
@ -67,15 +57,10 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
if (remoteCache != null) {
LOG.debugf("Having remote stores. Using remote cache '%s' for single-use cache of token", remoteCache.getName());
return () -> {
// Doing this way as flag is per invocation
return remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
};
return () -> remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
} else {
LOG.debugf("Not having remote stores. Using normal cache '%s' for single-use cache of token", cache.getName());
return () -> {
return cache;
};
LOG.debugf("Not having remote stores. Using basic cache '%s' for single-use cache of token", cache.getName());
return () -> cache;
}
}
@ -86,7 +71,11 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
@Override
public void postInit(KeycloakSessionFactory factory) {
// It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
// means also listeners will start only after first cache initialization - that would be too late
if (singleUseObjectCache == null) {
this.singleUseObjectCache = getSingleUseObjectCache(factory.create());
}
}
@Override

View file

@ -1,109 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.sessions.infinispan.entities;
import java.io.*;
import java.util.Objects;
import java.util.UUID;
import org.infinispan.commons.marshall.Externalizer;
import org.infinispan.commons.marshall.SerializeWith;
/**
*
* @author hmlnarik
*/
@SerializeWith(value = ActionTokenReducedKey.ExternalizerImpl.class)
public class ActionTokenReducedKey implements Serializable {
private final String userId;
private final String actionId;
/**
* Nonce that must match.
*/
private final UUID actionVerificationNonce;
public ActionTokenReducedKey(String userId, String actionId, UUID actionVerificationNonce) {
this.userId = userId;
this.actionId = actionId;
this.actionVerificationNonce = actionVerificationNonce;
}
public String getUserId() {
return userId;
}
public String getActionId() {
return actionId;
}
public UUID getActionVerificationNonce() {
return actionVerificationNonce;
}
@Override
public int hashCode() {
int hash = 7;
hash = 71 * hash + Objects.hashCode(this.userId);
hash = 71 * hash + Objects.hashCode(this.actionId);
hash = 71 * hash + Objects.hashCode(this.actionVerificationNonce);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ActionTokenReducedKey other = (ActionTokenReducedKey) obj;
return Objects.equals(this.userId, other.getUserId())
&& Objects.equals(this.actionId, other.getActionId())
&& Objects.equals(this.actionVerificationNonce, other.getActionVerificationNonce());
}
@Override
public String toString() {
return "userId=" + userId + ", actionId=" + actionId + ", actionVerificationNonce=" + actionVerificationNonce;
}
public static class ExternalizerImpl implements Externalizer<ActionTokenReducedKey> {
@Override
public void writeObject(ObjectOutput output, ActionTokenReducedKey t) throws IOException {
output.writeUTF(t.userId);
output.writeUTF(t.actionId);
output.writeLong(t.actionVerificationNonce.getMostSignificantBits());
output.writeLong(t.actionVerificationNonce.getLeastSignificantBits());
}
@Override
public ActionTokenReducedKey readObject(ObjectInput input) throws IOException, ClassNotFoundException {
return new ActionTokenReducedKey(
input.readUTF(),
input.readUTF(),
new UUID(input.readLong(), input.readLong())
);
}
}
}

View file

@ -16,7 +16,7 @@
*/
package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import java.io.*;
import java.util.*;
@ -26,12 +26,12 @@ import org.infinispan.commons.marshall.SerializeWith;
/**
* @author hmlnarik
*/
@SerializeWith(ActionTokenValueEntity.ExternalizerImpl.class)
public class ActionTokenValueEntity implements ActionTokenValueModel {
@SerializeWith(SingleUseObjectValueEntity.ExternalizerImpl.class)
public class SingleUseObjectValueEntity implements SingleUseObjectValueModel {
private final Map<String, String> notes;
public ActionTokenValueEntity(Map<String, String> notes) {
public SingleUseObjectValueEntity(Map<String, String> notes) {
this.notes = notes == null ? Collections.EMPTY_MAP : new HashMap<>(notes);
}
@ -47,15 +47,15 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
@Override
public String toString() {
return String.format("ActionTokenValueEntity [ notes=%s ]", notes.toString());
return String.format("SingleUseObjectValueEntity [ notes=%s ]", notes.toString());
}
public static class ExternalizerImpl implements Externalizer<ActionTokenValueEntity> {
public static class ExternalizerImpl implements Externalizer<SingleUseObjectValueEntity> {
private static final int VERSION_1 = 1;
@Override
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
public void writeObject(ObjectOutput output, SingleUseObjectValueEntity t) throws IOException {
output.writeByte(VERSION_1);
output.writeBoolean(t.notes.isEmpty());
@ -65,7 +65,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
}
@Override
public ActionTokenValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
public SingleUseObjectValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
byte version = input.readByte();
if (version != VERSION_1) {
@ -75,7 +75,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
Map<String, String> notes = notesEmpty ? Collections.EMPTY_MAP : (Map<String, String>) input.readObject();
return new ActionTokenValueEntity(notes);
return new SingleUseObjectValueEntity(notes);
}
}
}

View file

@ -1,28 +0,0 @@
/*
* Copyright 2020 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.keycloak.models.sessions.infinispan.entities.wildfly;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenReducedKey;
public class ActionTokenReducedKeyWFExternalizer extends InfinispanExternalizerAdapter<ActionTokenReducedKey> {
public ActionTokenReducedKeyWFExternalizer() {
super(ActionTokenReducedKey.class, new ActionTokenReducedKey.ExternalizerImpl());
}
}

View file

@ -18,11 +18,11 @@
package org.keycloak.models.sessions.infinispan.entities.wildfly;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
public class ActionTokenValueEntityWFExternalizer extends InfinispanExternalizerAdapter<ActionTokenValueEntity> {
public class ActionTokenValueEntityWFExternalizer extends InfinispanExternalizerAdapter<SingleUseObjectValueEntity> {
public ActionTokenValueEntityWFExternalizer() {
super(ActionTokenValueEntity.class, new ActionTokenValueEntity.ExternalizerImpl());
super(SingleUseObjectValueEntity.class, new SingleUseObjectValueEntity.ExternalizerImpl());
}
}

View file

@ -1 +0,0 @@
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory

View file

@ -18,7 +18,6 @@
org.keycloak.models.sessions.infinispan.entities.wildfly.GroupListPredicateWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.RealmRemovedSessionEventWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.ActionTokenReducedKeyWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.WrapperClusterEventWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.UserConsentsUpdatedEventWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.RoleRemovedEventWFExternalizer

View file

@ -17,11 +17,10 @@
package org.keycloak.models.map.storage.hotRod;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.common.Profile;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@ -192,7 +191,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) getEntityDescriptor(modelType);
if (modelType == ActionTokenValueModel.class) {
if (modelType == SingleUseObjectValueModel.class) {
return new SingleUseObjectHotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
} if (modelType == AuthenticatedClientSessionModel.class) {
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),

View file

@ -18,7 +18,7 @@
package org.keycloak.models.map.storage.hotRod;
import org.infinispan.client.hotrod.RemoteCache;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
@ -45,7 +45,7 @@ import java.util.stream.Stream;
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M>
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel> {
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> {
private final StringKeyConverter<String> keyConverter;
private final HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate> storedEntityDescriptor;
@ -61,18 +61,15 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
}
@Override
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel> createTransactionInternal(KeycloakSession session) {
Map<SearchableModelField<? super ActionTokenValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel>> fieldPredicates =
MapFieldPredicates.getPredicates((Class<ActionTokenValueModel>) storedEntityDescriptor.getModelTypeClass());
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> createTransactionInternal(KeycloakSession session) {
Map<SearchableModelField<? super SingleUseObjectValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel>> fieldPredicates =
MapFieldPredicates.getPredicates((Class<SingleUseObjectValueModel>) storedEntityDescriptor.getModelTypeClass());
return new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
}
@Override
public HotRodSingleUseObjectEntityDelegate create(HotRodSingleUseObjectEntityDelegate value) {
if (value.getId() == null) {
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
}
if (value.getObjectKey() != null) {
value.setId(value.getObjectKey());
}
@ -81,8 +78,8 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
}
@Override
public Stream<HotRodSingleUseObjectEntityDelegate> read(QueryParameters<ActionTokenValueModel> queryParameters) {
DefaultModelCriteria<ActionTokenValueModel> criteria = queryParameters.getModelCriteriaBuilder();
public Stream<HotRodSingleUseObjectEntityDelegate> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
if (criteria == null) {
return Stream.empty();

View file

@ -35,7 +35,7 @@ import java.util.Set;
implementInterface = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity",
inherits = "org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity.AbstractHotRodSingleUseObjectEntityDelegate",
topLevelEntity = true,
modelClass = "org.keycloak.models.ActionTokenValueModel"
modelClass = "org.keycloak.models.SingleUseObjectValueModel"
)
@ProtoDoc("@Indexed")
@ProtoDoc("schema-version: " + HotRodSingleUseObjectEntity.VERSION)
@ -66,15 +66,6 @@ public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity {
@ProtoField(number = 3)
public String objectKey;
@ProtoField(number = 4)
public String userId;
@ProtoField(number = 5)
public String actionId;
@ProtoField(number = 6)
public String actionVerificationNonce;
@ProtoField(number = 7)
public Long expiration;

View file

@ -53,7 +53,7 @@ import org.keycloak.common.util.StringPropertyReplacer;
import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
@ -232,7 +232,7 @@ public class JpaMapStorageProviderFactory implements
//roles
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
//single-use-objects
MODEL_TO_TX.put(ActionTokenValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
MODEL_TO_TX.put(SingleUseObjectValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
//user-login-failures
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
//users

View file

@ -21,7 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
@ -36,10 +36,10 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, ActionTokenValueModel> {
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
public JpaSingleUseObjectMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em);
super(session, JpaSingleUseObjectEntity.class, SingleUseObjectValueModel.class, em);
}
@Override

View file

@ -16,10 +16,7 @@
*/
package org.keycloak.models.map.storage.jpa.singleUseObject;
import java.util.HashMap;
import java.util.Map;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
@ -31,14 +28,7 @@ import org.keycloak.storage.SearchableModelField;
*
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, ActionTokenValueModel, JpaSingleUseObjectModelCriteriaBuilder> {
private static final Map<String, String> FIELD_TO_JSON_PROP = new HashMap<>();
static {
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.USER_ID.getName(), "fUserId");
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_ID.getName(), "fActionId");
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE.getName(), "fActionVerificationNonce");
}
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, SingleUseObjectValueModel, JpaSingleUseObjectModelCriteriaBuilder> {
public JpaSingleUseObjectModelCriteriaBuilder() {
super(JpaSingleUseObjectModelCriteriaBuilder::new);
@ -49,20 +39,10 @@ public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuil
}
@Override
public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField<? super ActionTokenValueModel> modelField, Operator op, Object... value) {
public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField<? super SingleUseObjectValueModel> modelField, Operator op, Object... value) {
switch (op) {
case EQ:
if (modelField == ActionTokenValueModel.SearchableFields.USER_ID ||
modelField == ActionTokenValueModel.SearchableFields.ACTION_ID ||
modelField == ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE) {
validateValue(value, modelField, op, String.class);
return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) ->
cb.equal(cb.function("->>", String.class, root.get("metadata"),
cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), value[0])
);
} else if(modelField == ActionTokenValueModel.SearchableFields.OBJECT_KEY) {
if(modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) {
validateValue(value, modelField, op, String.class);
return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) ->
cb.equal(root.get(modelField.getName()), value[0])

View file

@ -141,26 +141,6 @@ public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractS
return CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
}
@Override
public String getActionId() {
return this.metadata.getActionId();
}
@Override
public void setActionId(String actionId) {
this.metadata.setActionId(actionId);
}
@Override
public String getActionVerificationNonce() {
return this.metadata.getActionVerificationNonce();
}
@Override
public void setActionVerificationNonce(String actionVerificationNonce) {
this.metadata.setActionVerificationNonce(actionVerificationNonce);
}
@Override
public Map<String, String> getNotes() {
return this.notes.stream()
@ -192,16 +172,6 @@ public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractS
}
}
@Override
public String getUserId() {
return this.metadata.getUserId();
}
@Override
public void setUserId(String userId) {
this.metadata.setUserId(userId);
}
@Override
public Long getExpiration() {
if (this.isMetadataInitialized()) return this.metadata.getExpiration();

View file

@ -66,4 +66,8 @@ limitations under the License.
</createIndex>
</changeSet>
<changeSet author="keycloak" id="single-use-object-13334">
<dropIndex tableName="kc_single_use_obj" indexName="kc_single_use_obj_nonce"/>
</changeSet>
</databaseChangeLog>

View file

@ -17,8 +17,7 @@
package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
@ -27,7 +26,7 @@ import java.util.Objects;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> implements ActionTokenKeyModel, ActionTokenValueModel {
public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> implements SingleUseObjectValueModel {
protected final KeycloakSession session;
protected final E entity;
@ -42,7 +41,7 @@ public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> imp
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ActionTokenValueModel)) return false;
if (!(o instanceof SingleUseObjectValueModel)) return false;
MapSingleUseObjectAdapter that = (MapSingleUseObjectAdapter) o;
return Objects.equals(that.entity.getId(), entity.getId());

View file

@ -1,45 +0,0 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.ActionTokenStoreProviderFactory;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapActionTokenProviderFactory extends AbstractMapProviderFactory<MapSingleUseObjectProvider, MapSingleUseObjectEntity, ActionTokenValueModel>
implements ActionTokenStoreProviderFactory<MapSingleUseObjectProvider> {
public MapActionTokenProviderFactory() {
super(ActionTokenValueModel.class, MapSingleUseObjectProvider.class);
}
@Override
public MapSingleUseObjectProvider createNew(KeycloakSession session) {
return new MapSingleUseObjectProvider(session, getStorage(session));
}
@Override
public String getHelpText() {
return "Action token provider";
}
}

View file

@ -18,11 +18,9 @@
package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.TimeAdapter;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
@ -33,28 +31,6 @@ public class MapSingleUseObjectAdapter extends AbstractSingleUseObjectModel<MapS
super(session, entity);
}
@Override
public String getUserId() {
return entity.getUserId();
}
@Override
public String getActionId() {
return entity.getActionId();
}
@Override
public int getExpiration() {
Long expiration = entity.getExpiration();
return expiration != null ? TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(expiration)) : 0;
}
@Override
public UUID getActionVerificationNonce() {
String actionVerificationNonce = entity.getActionVerificationNonce();
return actionVerificationNonce != null ? UUID.fromString(actionVerificationNonce) : null;
}
@Override
public Map<String, String> getNotes() {
Map<String, String> notes = entity.getNotes();

View file

@ -51,18 +51,9 @@ public interface MapSingleUseObjectEntity extends AbstractEntity, UpdatableEntit
}
}
String getUserId();
void setUserId(String userId);
String getObjectKey();
void setObjectKey(String objectKey);
String getActionId();
void setActionId(String actionId);
String getActionVerificationNonce();
void setActionVerificationNonce(String actionVerificationNonce);
Map<String, String> getNotes();
void setNotes(Map<String, String> notes);
String getNote(String name);

View file

@ -19,9 +19,7 @@ package org.keycloak.models.map.singleUseObject;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.SingleUseObjectProvider;
@ -42,97 +40,17 @@ import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.crit
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, SingleUseObjectProvider {
public class MapSingleUseObjectProvider implements SingleUseObjectProvider {
private static final Logger LOG = Logger.getLogger(MapSingleUseObjectProvider.class);
private final KeycloakSession session;
protected final MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> actionTokenStoreTx;
protected final MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> singleUseObjectTx;
public MapSingleUseObjectProvider(KeycloakSession session, MapStorage<MapSingleUseObjectEntity, ActionTokenValueModel> storage) {
public MapSingleUseObjectProvider(KeycloakSession session, MapStorage<MapSingleUseObjectEntity, SingleUseObjectValueModel> storage) {
this.session = session;
actionTokenStoreTx = storage.createTransaction(session);
singleUseObjectTx = storage.createTransaction(session);
session.getTransactionManager().enlistAfterCompletion(actionTokenStoreTx);
}
private ActionTokenValueModel singleUseEntityToAdapter(MapSingleUseObjectEntity origEntity) {
if (isExpired(origEntity, false)) {
actionTokenStoreTx.delete(origEntity.getId());
return null;
} else {
return new MapSingleUseObjectAdapter(session, origEntity);
}
}
@Override
public void put(ActionTokenKeyModel actionTokenKey, Map<String, String> notes) {
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null || actionTokenKey.getActionVerificationNonce() == null) {
return;
}
LOG.tracef("put(%s, %s, %s)%s", actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce(), getShortStackTrace());
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getUserId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getActionId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getActionVerificationNonce().toString());
ActionTokenValueModel existing = actionTokenStoreTx.read(withCriteria(mcb))
.findFirst().map(this::singleUseEntityToAdapter).orElse(null);
if (existing == null) {
MapSingleUseObjectEntity actionTokenStoreEntity = new MapSingleUseObjectEntityImpl();
actionTokenStoreEntity.setUserId(actionTokenKey.getUserId());
actionTokenStoreEntity.setActionId(actionTokenKey.getActionId());
actionTokenStoreEntity.setActionVerificationNonce(actionTokenKey.getActionVerificationNonce().toString());
actionTokenStoreEntity.setExpiration(TimeAdapter.fromSecondsToMilliseconds(actionTokenKey.getExpiration()));
actionTokenStoreEntity.setNotes(notes);
LOG.debugf("Adding used action token to actionTokens cache: %s", actionTokenKey.toString());
actionTokenStoreTx.create(actionTokenStoreEntity);
}
}
@Override
public ActionTokenValueModel get(ActionTokenKeyModel key) {
if (key == null || key.getUserId() == null || key.getActionId() == null || key.getActionVerificationNonce() == null) {
return null;
}
LOG.tracef("get(%s, %s, %s)%s", key.getUserId(), key.getActionId(), key.getActionVerificationNonce(), getShortStackTrace());
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, key.getUserId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, key.getActionId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, key.getActionVerificationNonce().toString());
return actionTokenStoreTx.read(withCriteria(mcb))
.findFirst().map(this::singleUseEntityToAdapter).orElse(null);
}
@Override
public ActionTokenValueModel remove(ActionTokenKeyModel key) {
if (key == null || key.getUserId() == null || key.getActionId() == null || key.getActionVerificationNonce() == null) {
return null;
}
LOG.tracef("remove(%s, %s, %s)%s", key.getUserId(), key.getActionId(), key.getActionVerificationNonce(), getShortStackTrace());
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, key.getUserId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, key.getActionId())
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, key.getActionVerificationNonce().toString());
MapSingleUseObjectEntity mapSingleUseObjectEntity = actionTokenStoreTx.read(withCriteria(mcb)).findFirst().orElse(null);
if (mapSingleUseObjectEntity != null) {
ActionTokenValueModel actionToken = singleUseEntityToAdapter(mapSingleUseObjectEntity);
if (actionToken != null) {
actionTokenStoreTx.delete(mapSingleUseObjectEntity.getId());
return actionToken;
}
}
return null;
session.getTransactionManager().enlistAfterCompletion(singleUseObjectTx);
}
@Override
@ -150,16 +68,16 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds));
singleUseEntity.setNotes(notes);
actionTokenStoreTx.create(singleUseEntity);
singleUseObjectTx.create(singleUseEntity);
}
@Override
public Map<String, String> get(String key) {
LOG.tracef("get(%s)%s", key, getShortStackTrace());
MapSingleUseObjectEntity actionToken = getWithExpiration(key);
if (actionToken != null) {
Map<String, String> notes = actionToken.getNotes();
MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
if (singleUseObject != null) {
Map<String, String> notes = singleUseObject.getNotes();
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
}
@ -174,7 +92,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
if (singleUseEntity != null) {
Map<String, String> notes = singleUseEntity.getNotes();
if (actionTokenStoreTx.delete(singleUseEntity.getId())) {
if (singleUseObjectTx.delete(singleUseEntity.getId())) {
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
}
}
@ -207,7 +125,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
singleUseEntity.setObjectKey(key);
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds));
actionTokenStoreTx.create(singleUseEntity);
singleUseObjectTx.create(singleUseEntity);
return true;
}
@ -217,9 +135,9 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
public boolean contains(String key) {
LOG.tracef("contains(%s)%s", key, getShortStackTrace());
MapSingleUseObjectEntity actionToken = getWithExpiration(key);
MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
return actionToken != null;
return singleUseObject != null;
}
@Override
@ -228,13 +146,13 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
}
private MapSingleUseObjectEntity getWithExpiration(String key) {
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key);
DefaultModelCriteria<SingleUseObjectValueModel> mcb = criteria();
mcb = mcb.compare(SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key);
MapSingleUseObjectEntity singleUseEntity = actionTokenStoreTx.read(withCriteria(mcb)).findFirst().orElse(null);
MapSingleUseObjectEntity singleUseEntity = singleUseObjectTx.read(withCriteria(mcb)).findFirst().orElse(null);
if (singleUseEntity != null) {
if (isExpired(singleUseEntity, false)) {
actionTokenStoreTx.delete(singleUseEntity.getId());
singleUseObjectTx.delete(singleUseEntity.getId());
} else {
return singleUseEntity;
}

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory;
@ -25,11 +25,11 @@ import org.keycloak.models.map.common.AbstractMapProviderFactory;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class MapSingleUseObjectProviderFactory extends AbstractMapProviderFactory<MapSingleUseObjectProvider, MapSingleUseObjectEntity, ActionTokenValueModel>
public class MapSingleUseObjectProviderFactory extends AbstractMapProviderFactory<MapSingleUseObjectProvider, MapSingleUseObjectEntity, SingleUseObjectValueModel>
implements SingleUseObjectProviderFactory<MapSingleUseObjectProvider> {
public MapSingleUseObjectProviderFactory() {
super(ActionTokenValueModel.class, MapSingleUseObjectProvider.class);
super(SingleUseObjectValueModel.class, MapSingleUseObjectProvider.class);
}
@Override

View file

@ -22,7 +22,7 @@ import org.keycloak.authorization.model.Resource;
import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
@ -66,7 +66,7 @@ public class ModelEntityUtil {
private static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
static {
MODEL_TO_NAME.put(ActionTokenValueModel.class, "single-use-objects");
MODEL_TO_NAME.put(SingleUseObjectValueModel.class, "single-use-objects");
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
MODEL_TO_NAME.put(ClientModel.class, "clients");
MODEL_TO_NAME.put(GroupModel.class, "groups");
@ -92,7 +92,7 @@ public class ModelEntityUtil {
private static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_ENTITY_TYPE = new HashMap<>();
static {
MODEL_TO_ENTITY_TYPE.put(ActionTokenValueModel.class, MapSingleUseObjectEntity.class);
MODEL_TO_ENTITY_TYPE.put(SingleUseObjectValueModel.class, MapSingleUseObjectEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class);
MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class);
MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class);

View file

@ -16,7 +16,7 @@
*/
package org.keycloak.models.map.storage.chm;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntityImpl;
@ -35,10 +35,8 @@ import org.keycloak.component.AmphibianProviderFactory;
import org.keycloak.Config.Scope;
import org.keycloak.common.Profile;
import org.keycloak.component.ComponentModelScope;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserSessionModel;
import org.keycloak.models.map.client.MapClientEntityImpl;
import org.keycloak.models.map.client.MapProtocolMapperEntity;
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
@ -240,7 +238,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
LOG.debugf("Initializing new map storage: %s", mapName);
ConcurrentHashMapStorage<K, V, M> store;
if(modelType == ActionTokenValueModel.class) {
if(modelType == SingleUseObjectValueModel.class) {
store = new SingleUseObjectConcurrentHashMapStorage(kc, CLONER) {
@Override
public String toString() {

View file

@ -23,7 +23,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel;
@ -98,7 +98,7 @@ public class MapFieldPredicates {
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, MapUserSessionEntity, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
public static final Map<SearchableModelField<Event>, UpdatePredicatesFunc<Object, MapAuthEventEntity, Event>> AUTH_EVENTS_PREDICATES = basePredicates(Event.SearchableFields.ID);
public static final Map<SearchableModelField<AdminEvent>, UpdatePredicatesFunc<Object, MapAdminEventEntity, AdminEvent>> ADMIN_EVENTS_PREDICATES = basePredicates(AdminEvent.SearchableFields.ID);
public static final Map<SearchableModelField<ActionTokenValueModel>, UpdatePredicatesFunc<Object, MapSingleUseObjectEntity, ActionTokenValueModel>> ACTION_TOKEN_PREDICATES = basePredicates(ActionTokenValueModel.SearchableFields.ID);
public static final Map<SearchableModelField<SingleUseObjectValueModel>, UpdatePredicatesFunc<Object, MapSingleUseObjectEntity, SingleUseObjectValueModel>> ACTION_TOKEN_PREDICATES = basePredicates(SingleUseObjectValueModel.SearchableFields.ID);
@SuppressWarnings("unchecked")
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
@ -221,10 +221,7 @@ public class MapFieldPredicates {
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_TYPE, MapAdminEventEntity::getResourceType);
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath);
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.USER_ID, MapSingleUseObjectEntity::getUserId);
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_ID, MapSingleUseObjectEntity::getActionId);
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, MapSingleUseObjectEntity::getActionVerificationNonce);
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
put(ACTION_TOKEN_PREDICATES, SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
}
static {
@ -244,7 +241,7 @@ public class MapFieldPredicates {
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES);
PREDICATES.put(AdminEvent.class, ADMIN_EVENTS_PREDICATES);
PREDICATES.put(ActionTokenValueModel.class, ACTION_TOKEN_PREDICATES);
PREDICATES.put(SingleUseObjectValueModel.class, ACTION_TOKEN_PREDICATES);
}
private static <K, V extends AbstractEntity, M, L extends Comparable<L>> void put(

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.storage.chm;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner;
@ -32,31 +32,28 @@ import java.util.stream.Stream;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity, M> extends ConcurrentHashMapStorage<K, MapSingleUseObjectEntity, ActionTokenValueModel> {
public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity, M> extends ConcurrentHashMapStorage<K, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
public SingleUseObjectConcurrentHashMapStorage(StringKeyConverter<K> keyConverter, DeepCloner cloner) {
super(ActionTokenValueModel.class, keyConverter, cloner);
super(SingleUseObjectValueModel.class, keyConverter, cloner);
}
@Override
@SuppressWarnings("unchecked")
public MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> createTransaction(KeycloakSession session) {
MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> actionTokenTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
public MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> createTransaction(KeycloakSession session) {
MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> singleUseObjectTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
if (actionTokenTransaction == null) {
actionTokenTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), actionTokenTransaction);
if (singleUseObjectTransaction == null) {
singleUseObjectTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), singleUseObjectTransaction);
}
return actionTokenTransaction;
return singleUseObjectTransaction;
}
@Override
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
if (value.getId() == null) {
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
}
if (value.getObjectKey() != null) {
value.setId(value.getObjectKey());
}
@ -65,8 +62,8 @@ public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity
}
@Override
public Stream<MapSingleUseObjectEntity> read(QueryParameters<ActionTokenValueModel> queryParameters) {
DefaultModelCriteria<ActionTokenValueModel> criteria = queryParameters.getModelCriteriaBuilder();
public Stream<MapSingleUseObjectEntity> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
if (criteria == null) {
return Stream.empty();

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.storage.chm;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.map.common.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
@ -28,22 +28,19 @@ import java.util.Map;
/**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
*/
public class SingleUseObjectKeycloakTransaction<K> extends ConcurrentHashMapKeycloakTransaction<K, MapSingleUseObjectEntity, ActionTokenValueModel> {
public class SingleUseObjectKeycloakTransaction<K> extends ConcurrentHashMapKeycloakTransaction<K, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
public SingleUseObjectKeycloakTransaction(ConcurrentHashMapCrudOperations<MapSingleUseObjectEntity, ActionTokenValueModel> map,
public SingleUseObjectKeycloakTransaction(ConcurrentHashMapCrudOperations<MapSingleUseObjectEntity, SingleUseObjectValueModel> map,
StringKeyConverter<K> keyConverter,
DeepCloner cloner,
Map<SearchableModelField<? super ActionTokenValueModel>,
MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, ActionTokenValueModel>> fieldPredicates) {
Map<SearchableModelField<? super SingleUseObjectValueModel>,
MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, SingleUseObjectValueModel>> fieldPredicates) {
super(map, keyConverter, cloner, fieldPredicates);
}
@Override
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
if (value.getId() == null) {
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
}
if (value.getObjectKey() != null) {
value.setId(value.getObjectKey());
}

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.storage.chm;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
import org.keycloak.storage.SearchableModelField;
@ -26,61 +26,34 @@ import org.keycloak.storage.SearchableModelField;
*/
public class SingleUseObjectModelCriteriaBuilder implements ModelCriteriaBuilder {
private String userId;
private String actionId;
private String actionVerificationNonce;
private String objectKey;
public SingleUseObjectModelCriteriaBuilder() {
}
public SingleUseObjectModelCriteriaBuilder(String userId, String actionId, String actionVerificationNonce, String objectKey) {
this.userId = userId;
this.actionId = actionId;
this.actionVerificationNonce = actionVerificationNonce;
public SingleUseObjectModelCriteriaBuilder(String objectKey) {
this.objectKey = objectKey;
}
@Override
public ModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) {
if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.USER_ID) {
userId = value[0].toString();
} else if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.ACTION_ID) {
actionId = value[0].toString();
} else if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE) {
actionVerificationNonce = value[0].toString();
} else if (modelField == ActionTokenValueModel.SearchableFields.OBJECT_KEY) {
if (modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) {
objectKey = value[0].toString();
}
return new SingleUseObjectModelCriteriaBuilder(userId, actionId, actionVerificationNonce, objectKey);
return new SingleUseObjectModelCriteriaBuilder(objectKey);
}
@Override
public ModelCriteriaBuilder and(ModelCriteriaBuilder[] builders) {
String userId = null;
String actionId = null;
String actionVerificationNonce = null;
String objectKey = null;
for (ModelCriteriaBuilder builder: builders) {
SingleUseObjectModelCriteriaBuilder suoMcb = (SingleUseObjectModelCriteriaBuilder) builder;
if (suoMcb.userId != null) {
userId = suoMcb.userId;
}
if (suoMcb.actionId != null) {
actionId = suoMcb.actionId;
}
if (suoMcb.actionVerificationNonce != null) {
actionVerificationNonce = suoMcb.actionVerificationNonce;
}
if (suoMcb.objectKey != null) {
objectKey = suoMcb.objectKey;
}
}
return new SingleUseObjectModelCriteriaBuilder(userId, actionId, actionVerificationNonce, objectKey);
return new SingleUseObjectModelCriteriaBuilder(objectKey);
}
@Override
@ -94,11 +67,10 @@ public class SingleUseObjectModelCriteriaBuilder implements ModelCriteriaBuilder
}
public boolean isValid() {
return (userId != null && actionId != null && actionVerificationNonce != null) || objectKey != null;
return objectKey != null;
}
public String getKey() {
if (objectKey != null) return objectKey;
return userId + ":" + actionId + ":" + actionVerificationNonce;
return objectKey;
}
}

View file

@ -1,19 +0,0 @@
#
# Copyright 2022 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
org.keycloak.models.map.singleUseObject.MapActionTokenProviderFactory

View file

@ -217,18 +217,6 @@ public class StorageOptions {
.buildTime(true)
.build();
public static final Option<String> STORAGE_ACTION_TOKEN_PROVIDER = new OptionBuilder<>("storage-action-token-provider", String.class)
.category(OptionCategory.STORAGE)
.hidden()
.buildTime(true)
.build();
public static final Option<StorageType> STORAGE_ACTION_TOKEN_STORE = new OptionBuilder<>("storage-area-action-token", StorageType.class)
.category(OptionCategory.STORAGE)
.description(descriptionForStorageAreas("action tokens"))
.buildTime(true)
.build();
public static final Option<String> STORAGE_DBLOCK = new OptionBuilder<>("storage-dblock", String.class)
.category(OptionCategory.STORAGE)
.hidden()

View file

@ -22,8 +22,6 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.
import java.util.Optional;
import org.jboss.logging.Logger;
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.config.StorageOptions;
import org.keycloak.config.StorageOptions.StorageType;
@ -197,18 +195,6 @@ final class StoragePropertyMappers {
.transformer(StoragePropertyMappers::resolveMapStorageProvider)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_ACTION_TOKEN_PROVIDER)
.to("kc.spi-action-token-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::getCacheStorage)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_ACTION_TOKEN_STORE)
.to("kc.spi-action-token-map-storage-provider")
.mapFrom("storage")
.transformer(StoragePropertyMappers::resolveMapStorageProvider)
.paramLabel("type")
.build(),
fromOption(StorageOptions.STORAGE_DBLOCK)
.to("kc.spi-dblock-provider")
.mapFrom("storage")

View file

@ -32,9 +32,6 @@ Storage (Experimental):
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod.
--storage-area-action-token <type>
Experimental: Sets a storage mechanism for action tokens. Possible values are:
jpa, chm, hotrod.
--storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod.

View file

@ -32,9 +32,6 @@ Storage (Experimental):
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod.
--storage-area-action-token <type>
Experimental: Sets a storage mechanism for action tokens. Possible values are:
jpa, chm, hotrod.
--storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod.

View file

@ -38,9 +38,6 @@ Storage (Experimental):
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod.
--storage-area-action-token <type>
Experimental: Sets a storage mechanism for action tokens. Possible values are:
jpa, chm, hotrod.
--storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod.

View file

@ -38,9 +38,6 @@ Storage (Experimental):
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod.
--storage-area-action-token <type>
Experimental: Sets a storage mechanism for action tokens. Possible values are:
jpa, chm, hotrod.
--storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod.

View file

@ -1,54 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import org.keycloak.provider.Provider;
import java.util.Map;
/**
* Internal action token store provider.
*
* It's used for store the details about used action tokens. There is separate provider for OAuth2 codes - {@link SingleUseObjectProvider},
* which may reuse some components (eg. same infinispan cache)
*
* @author hmlnarik
*/
public interface ActionTokenStoreProvider extends Provider {
/**
* Adds a given token to token store.
* @param actionTokenKey key
* @param notes Optional notes to be stored with the token. Can be {@code null} in which case it is treated as an empty map.
*/
void put(ActionTokenKeyModel actionTokenKey, Map<String, String> notes);
/**
* Returns token corresponding to the given key from the internal action token store
* @param key key
* @return {@code null} if no token is found for given key and nonce, value otherwise
*/
ActionTokenValueModel get(ActionTokenKeyModel key);
/**
* Removes token corresponding to the given key from the internal action token store, and returns the stored value
* @param key key
* @param nonce nonce that must match a given key
* @return {@code null} if no token is found for given key and nonce, value otherwise
*/
ActionTokenValueModel remove(ActionTokenKeyModel key);
}

View file

@ -1,27 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import org.keycloak.provider.ProviderFactory;
/**
*
* @author hmlnarik
*/
public interface ActionTokenStoreProviderFactory<T extends ActionTokenStoreProvider> extends ProviderFactory<T> {
}

View file

@ -1,50 +0,0 @@
/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.models;
import org.keycloak.provider.*;
/**
* SPI for action tokens.
*
* @author hmlnarik
*/
public class ActionTokenStoreSpi implements Spi {
public static final String NAME = "actionToken";
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return NAME;
}
@Override
public Class<? extends Provider> getProviderClass() {
return ActionTokenStoreProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return ActionTokenStoreProviderFactory.class;
}
}

View file

@ -22,7 +22,7 @@ import org.keycloak.provider.Provider;
import java.util.Map;
/**
* Provides a cache to store data for single-use use case. Data are represented by a {@code String} key.
* Provides a cache to store data for single-use use case or the details about used action tokens.
*
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/

View file

@ -23,7 +23,6 @@ org.keycloak.models.GroupSpi
org.keycloak.models.RealmSpi
org.keycloak.models.RoleSpi
org.keycloak.models.DeploymentStateSpi
org.keycloak.models.ActionTokenStoreSpi
org.keycloak.models.OAuth2DeviceUserCodeSpi
org.keycloak.models.SingleUseObjectSpi
org.keycloak.models.UserSessionSpi

View file

@ -14,10 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.authentication.actiontoken;
package org.keycloak.models;
import org.keycloak.common.util.Base64;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.representations.JsonWebToken;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
@ -30,7 +29,7 @@ import java.util.regex.Pattern;
*
* @author hmlnarik
*/
public class DefaultActionTokenKey extends JsonWebToken implements ActionTokenKeyModel {
public class DefaultActionTokenKey extends JsonWebToken implements SingleUseObjectKeyModel {
/** The authenticationSession note with ID of the user authenticated via the action token */
public static final String ACTION_TOKEN_USER_ID = "ACTION_TOKEN_USER";

View file

@ -24,7 +24,7 @@ import java.util.UUID;
*
* @author hmlnarik
*/
public interface ActionTokenKeyModel {
public interface SingleUseObjectKeyModel {
/**
* @return ID of user which this token is for.

View file

@ -24,14 +24,11 @@ import java.util.Map;
* This model represents contents of an action token shareable among Keycloak instances in the cluster.
* @author hmlnarik
*/
public interface ActionTokenValueModel {
public interface SingleUseObjectValueModel {
class SearchableFields {
public static final SearchableModelField<ActionTokenValueModel> ID = new SearchableModelField<>("id", String.class);
public static final SearchableModelField<ActionTokenValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class);
public static final SearchableModelField<ActionTokenValueModel> USER_ID = new SearchableModelField<>("userId", String.class);
public static final SearchableModelField<ActionTokenValueModel> ACTION_ID = new SearchableModelField<>("actionId", String.class);
public static final SearchableModelField<ActionTokenValueModel> ACTION_VERIFICATION_NONCE = new SearchableModelField<>("actionVerificationNonce", String.class);
public static final SearchableModelField<SingleUseObjectValueModel> ID = new SearchableModelField<>("id", String.class);
public static final SearchableModelField<SingleUseObjectValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class);
}
/**

View file

@ -22,7 +22,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.Urls;
@ -38,7 +39,7 @@ import java.util.UUID;
*
* @author hmlnarik
*/
public class DefaultActionToken extends DefaultActionTokenKey implements ActionTokenValueModel {
public class DefaultActionToken extends DefaultActionTokenKey implements SingleUseObjectValueModel {
public static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid";
public static final String JSON_FIELD_EMAIL = "eml";

View file

@ -17,7 +17,7 @@
package org.keycloak.authentication.authenticators.resetcred;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.models.DefaultActionTokenKey;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.authentication.*;

View file

@ -17,7 +17,7 @@
package org.keycloak.authentication.authenticators.resetcred;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.Config;
import org.keycloak.authentication.*;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken;

View file

@ -30,7 +30,6 @@ import org.keycloak.authentication.RequiredActionContext;
import org.keycloak.authentication.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.common.ClientConnection;
@ -47,16 +46,17 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.Constants;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@ -1037,11 +1037,10 @@ public class AuthenticationManager {
ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) {
String actionTokenKeyToInvalidate = authSession.getAuthNote(INVALIDATE_ACTION_TOKEN);
if (actionTokenKeyToInvalidate != null) {
ActionTokenKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate);
SingleUseObjectKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate);
if (actionTokenKey != null) {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
actionTokenStore.put(actionTokenKey, null); // Token is invalidated
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
singleUseObjectProvider.put(actionTokenKeyToInvalidate, actionTokenKey.getExpiration() - Time.currentTime(), null); // Token is invalidated
}
}

View file

@ -29,7 +29,7 @@ import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.actiontoken.ActionTokenContext;
import org.keycloak.authentication.actiontoken.ActionTokenHandler;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationException;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
@ -47,9 +47,7 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.locale.LocaleSelectorProvider;
import org.keycloak.locale.LocaleUpdaterProvider;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
@ -79,7 +77,6 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil;
@ -461,7 +458,7 @@ public class LoginActionsService {
return handleActionToken(key, execution, clientId, tabId);
}
protected <T extends JsonWebToken & ActionTokenKeyModel> Response handleActionToken(String tokenString, String execution, String clientId, String tabId) {
protected <T extends JsonWebToken & SingleUseObjectKeyModel> Response handleActionToken(String tokenString, String execution, String clientId, String tabId) {
T token;
ActionTokenHandler<T> handler;
ActionTokenContext<T> tokenContext;

View file

@ -24,12 +24,12 @@ import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationExcepti
import org.keycloak.common.VerificationException;
import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
@ -165,7 +165,7 @@ public class LoginActionsServiceChecks {
* Verifies whether the user given by ID both exists in the current realm. If yes,
* it optionally also injects the user using the given function (e.g. into session context).
*/
public static <T extends JsonWebToken & ActionTokenKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context) throws VerificationException {
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context) throws VerificationException {
try {
checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser);
} catch (ExplainedVerificationException ex) {
@ -299,10 +299,10 @@ public class LoginActionsServiceChecks {
return true;
}
public static <T extends JsonWebToken & ActionTokenKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException {
ActionTokenStoreProvider actionTokenStore = context.getSession().getProvider(ActionTokenStoreProvider.class);
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException {
SingleUseObjectProvider singleUseObjectProvider = context.getSession().getProvider(SingleUseObjectProvider.class);
if (actionTokenStore.get(token) != null) {
if (singleUseObjectProvider.get(token.serializeKey()) != null) {
throw new ExplainedTokenVerificationException(token, Errors.EXPIRED_CODE, Messages.EXPIRED_ACTION);
}
}

View file

@ -811,7 +811,6 @@
<keycloak.loginFailure.provider>map</keycloak.loginFailure.provider>
<keycloak.authorization.provider>map</keycloak.authorization.provider>
<keycloak.eventsStore.provider>map</keycloak.eventsStore.provider>
<keycloak.actionToken.provider>map</keycloak.actionToken.provider>
<keycloak.singleUseObject.provider>map</keycloak.singleUseObject.provider>
<keycloak.publicKeyStorage.provider>map</keycloak.publicKeyStorage.provider>
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
@ -865,7 +864,6 @@
<keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider>
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.map.storage.provider>
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
<keycloak.actionToken.map.storage.provider>jpa</keycloak.actionToken.map.storage.provider>
<keycloak.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
<keycloak.user.map.storage.provider>jpa</keycloak.user.map.storage.provider>
<keycloak.userSession.map.storage.provider>jpa</keycloak.userSession.map.storage.provider>

View file

@ -139,15 +139,6 @@
}
},
"actionToken": {
"provider": "${keycloak.actionToken.provider:infinispan}",
"map": {
"storage": {
"provider": "${keycloak.actionToken.map.storage.provider:concurrenthashmap}"
}
}
},
"singleUseObject": {
"provider": "${keycloak.singleUseObject.provider:infinispan}",
"map": {

View file

@ -1360,7 +1360,6 @@
<keycloak.userSession.map.storage.provider>hotrod</keycloak.userSession.map.storage.provider>
<keycloak.authorization.map.storage.provider>hotrod</keycloak.authorization.map.storage.provider>
<keycloak.eventStore.map.storage.provider>hotrod</keycloak.eventStore.map.storage.provider>
<keycloak.actionToken.map.storage.provider>hotrod</keycloak.actionToken.map.storage.provider>
<keycloak.singleUseObject.map.storage.provider>hotrod</keycloak.singleUseObject.map.storage.provider>
<infinispan.version>${infinispan.version}</infinispan.version>
<keycloak.testsuite.start-hotrod-container>${keycloak.testsuite.start-hotrod-container}</keycloak.testsuite.start-hotrod-container>

View file

@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet;
import org.jboss.logging.Logger;
import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectSpi;
import org.keycloak.models.UserLoginFailureSpi;
@ -81,7 +80,6 @@ public class HotRodMapStorage extends KeycloakModelParameters {
@Override
public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)

View file

@ -22,13 +22,11 @@ import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
import org.keycloak.keys.PublicKeyStorageSpi;
import org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory;
import org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProviderFactory;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.SingleUseObjectSpi;
import org.keycloak.models.cache.authorization.CachedStoreFactorySpi;
import org.keycloak.models.cache.infinispan.authorization.InfinispanCacheStoreFactoryProviderFactory;
import org.keycloak.models.cache.CachePublicKeyProviderSpi;
import org.keycloak.models.session.UserSessionPersisterSpi;
import org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory;
@ -68,7 +66,6 @@ public class Infinispan extends KeycloakModelParameters {
.add(InfinispanConnectionSpi.class)
.add(StickySessionEncoderSpi.class)
.add(UserSessionPersisterSpi.class)
.add(ActionTokenStoreSpi.class)
.add(SingleUseObjectSpi.class)
.add(PublicKeyStorageSpi.class)
.add(CachePublicKeyProviderSpi.class)
@ -86,7 +83,6 @@ public class Infinispan extends KeycloakModelParameters {
.add(InfinispanUserCacheProviderFactory.class)
.add(InfinispanUserSessionProviderFactory.class)
.add(InfinispanUserLoginFailureProviderFactory.class)
.add(InfinispanActionTokenStoreProviderFactory.class)
.add(InfinispanSingleUseObjectProviderFactory.class)
.add(StickySessionEncoderProviderFactory.class)
.add(TimerProviderFactory.class)

View file

@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.jboss.logging.Logger;
import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectSpi;
@ -99,7 +98,6 @@ public class JpaMapStorage extends KeycloakModelParameters {
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)

View file

@ -22,7 +22,6 @@ import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.SingleUseObjectSpi;
@ -103,7 +102,6 @@ public class LdapMapStorage extends KeycloakModelParameters {
.spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(EventStoreSpi.NAME).config("map.storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(EventStoreSpi.NAME).config("map.storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);

View file

@ -19,8 +19,6 @@ package org.keycloak.testsuite.model.parameters;
import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi;
import org.keycloak.keys.PublicKeyStorageSpi;
import org.keycloak.models.ActionTokenStoreProviderFactory;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.SingleUseObjectSpi;
@ -59,7 +57,6 @@ public class Map extends KeycloakModelParameters {
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(AuthenticationSessionSpi.class)
.add(ActionTokenStoreSpi.class)
.add(SingleUseObjectSpi.class)
.add(PublicKeyStorageSpi.class)
.add(MapStorageSpi.class)
@ -80,7 +77,6 @@ public class Map extends KeycloakModelParameters {
.add(MapUserLoginFailureProviderFactory.class)
.add(NoLockingDBLockProviderFactory.class)
.add(MapEventStoreProviderFactory.class)
.add(ActionTokenStoreProviderFactory.class)
.add(SingleUseObjectProviderFactory.class)
.add(MapPublicKeyStorageProviderFactory.class)
.build();
@ -92,7 +88,6 @@ public class Map extends KeycloakModelParameters {
@Override
public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)

View file

@ -20,26 +20,28 @@ package org.keycloak.testsuite.model.singleUseObject;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.common.util.Time;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserModel;
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
import org.keycloak.testsuite.model.KeycloakModelTest;
import org.keycloak.testsuite.model.RequireProvider;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeFalse;
@RequireProvider(ActionTokenStoreProvider.class)
@RequireProvider(SingleUseObjectProvider.class)
public class SingleUseObjectModelTest extends KeycloakModelTest {
@ -64,46 +66,47 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
@Test
public void testActionTokens() {
ActionTokenKeyModel key = withRealm(realmId, (session, realm) -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), Time.currentTime() + 60, null);
DefaultActionTokenKey key = withRealm(realmId, (session, realm) -> {
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
int time = Time.currentTime();
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), time + 60, null);
Map<String, String> notes = new HashMap<>();
notes.put("foo", "bar");
actionTokenStore.put(actionTokenKey, notes);
singleUseObjectProvider.put(actionTokenKey.serializeKey(), actionTokenKey.getExp() - time, notes);
return actionTokenKey;
});
inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key);
Assert.assertNotNull(valueModel);
Assert.assertEquals("bar", valueModel.getNote("foo"));
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNotNull(notes);
Assert.assertEquals("bar", notes.get("foo"));
valueModel = actionTokenStore.remove(key);
Assert.assertNotNull(valueModel);
Assert.assertEquals("bar", valueModel.getNote("foo"));
notes = singleUseObjectProvider.remove(key.serializeKey());
Assert.assertNotNull(notes);
Assert.assertEquals("bar", notes.get("foo"));
});
inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key);
Assert.assertNull(valueModel);
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNull(notes);
Map<String, String> notes = new HashMap<>();
notes = new HashMap<>();
notes.put("foo", "bar");
actionTokenStore.put(key, notes);
singleUseObjectProvider.put(key.serializeKey(), key.getExp() - Time.currentTime(), notes);
});
inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key);
Assert.assertNotNull(valueModel);
Assert.assertEquals("bar", valueModel.getNote("foo"));
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNotNull(notes);
Assert.assertEquals("bar", notes.get("foo"));
Time.setOffset(70);
valueModel = actionTokenStore.get(key);
Assert.assertNull(valueModel);
notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNull(notes);
});
}
@ -156,4 +159,89 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
Assert.assertNull(singleUseStore.get(key));
});
}
@Test
public void testCluster() throws InterruptedException {
// Skip the test if MapUserSessionProvider == CHM
String usProvider = CONFIG.getConfig().get("userSessions.provider");
String usMapStorageProvider = CONFIG.getConfig().get("userSessions.map.storage.provider");
assumeFalse(MapUserSessionProviderFactory.PROVIDER_ID.equals(usProvider) &&
(usMapStorageProvider == null || ConcurrentHashMapStorageProviderFactory.PROVIDER_ID.equals(usMapStorageProvider)));
AtomicInteger index = new AtomicInteger();
CountDownLatch afterFirstNodeLatch = new CountDownLatch(1);
CountDownLatch afterDeleteLatch = new CountDownLatch(1);
CountDownLatch clusterJoined = new CountDownLatch(4);
CountDownLatch replicationDone = new CountDownLatch(4);
String key = UUID.randomUUID().toString();
AtomicReference<String> actionTokenKey = new AtomicReference<>();
Map<String, String> notes = new HashMap<>();
notes.put("foo", "bar");
inIndependentFactories(4, 60, () -> {
log.debug("Joining the cluster");
clusterJoined.countDown();
awaitLatch(clusterJoined);
log.debug("Cluster joined");
if (index.incrementAndGet() == 1) {
actionTokenKey.set(withRealm(realmId, (session, realm) -> {
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
singleUseStore.put(key, 60, notes);
int time = Time.currentTime();
DefaultActionTokenKey atk = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), time + 60, null);
singleUseStore.put(atk.serializeKey(), atk.getExp() - time, notes);
return atk.serializeKey();
}));
afterFirstNodeLatch.countDown();
}
awaitLatch(afterFirstNodeLatch);
// check if single-use object/action token is available on all nodes
inComittedTransaction(session -> {
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
while (singleUseStore.get(key) == null || singleUseStore.get(actionTokenKey.get()) == null) {
sleep(1000);
}
replicationDone.countDown();
});
awaitLatch(replicationDone);
// remove objects on one node
if (index.incrementAndGet() == 5) {
inComittedTransaction(session -> {
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
singleUseStore.remove(key);
singleUseStore.remove(actionTokenKey.get());
});
afterDeleteLatch.countDown();
}
awaitLatch(afterDeleteLatch);
// check if single-use object/action token is removed
inComittedTransaction(session -> {
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
while (singleUseStore.get(key) != null && singleUseStore.get(actionTokenKey.get()) != null) {
sleep(1000);
}
});
});
}
private void awaitLatch(CountDownLatch latch) {
try {
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}

View file

@ -327,7 +327,6 @@
<systemProperty><key>keycloak.userSession.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.loginFailure.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.authorization.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.actionToken.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.singleUseObject.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.eventsStore.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.publicKeyStorage.provider</key><value>map</value></systemProperty>

View file

@ -111,15 +111,6 @@
}
},
"actionToken": {
"provider": "${keycloak.actionToken.provider:infinispan}",
"map": {
"storage": {
"provider": "${keycloak.actionToken.map.storage.provider:concurrenthashmap}"
}
}
},
"singleUseObject": {
"provider": "${keycloak.singleUseObject.provider:infinispan}",
"map": {