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; 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.ClusterEvent;
import org.keycloak.cluster.ClusterProvider; import org.keycloak.cluster.ClusterProvider;
import org.infinispan.context.Flag; 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); log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_WITH_LIFESPAN, key);
Object taskKey = getTaskKey(cache, 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)); 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); log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
Object taskKey = getTaskKey(cache, 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 // 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); Object taskKey = getTaskKey(cache, key);
CacheTask current = tasks.get(taskKey); CacheTask current = tasks.get(taskKey);
if (current != null) { if (current != null) {
@ -214,7 +216,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
return cache.get(key); 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) { if (key instanceof String) {
return new StringBuilder(cache.getName()) return new StringBuilder(cache.getName())
.append("::") .append("::")
@ -245,8 +247,10 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
} }
// Ignore return values. Should have better performance within cluster / cross-dc env // Ignore return values. Should have better performance within cluster / cross-dc env
private static <K, V> Cache<K, V> decorateCache(Cache<K, V> cache) { private static <K, V> BasicCache<K, V> decorateCache(BasicCache<K, V> cache) {
return cache.getAdvancedCache() if (cache instanceof RemoteCache)
return cache;
return ((Cache) cache).getAdvancedCache()
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP); .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.common.util.Time;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.SingleUseObjectProvider; 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; 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" . * 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 ActionTokenValueEntity) * 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> * @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); 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 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.session = session;
this.tokenCache = actionKeyCache; this.singleUseObjectCache = singleUseObjectCache;
this.tx = new InfinispanKeycloakTransaction();
session.getTransactionManager().enlistAfterCompletion(tx);
} }
@Override @Override
public void put(String key, long lifespanSeconds, Map<String, String> notes) { public void put(String key, long lifespanSeconds, Map<String, String> notes) {
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes); SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(notes);
try { try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get(); BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)); tx.put(cache, key, tokenValue, InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)), TimeUnit.MILLISECONDS);
cache.put(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
} catch (HotRodClientException re) { } catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@ -69,17 +71,23 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
@Override @Override
public Map<String, String> get(String key) { public Map<String, String> get(String key) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get(); SingleUseObjectValueEntity singleUseObjectValueEntity;
ActionTokenValueEntity actionTokenValueEntity = cache.get(key);
return actionTokenValueEntity != null ? actionTokenValueEntity.getNotes() : null; BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
singleUseObjectValueEntity = tx.get(cache, key);
return singleUseObjectValueEntity != null ? singleUseObjectValueEntity.getNotes() : null;
} }
@Override @Override
public Map<String, String> remove(String key) { public Map<String, String> remove(String key) {
try { try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get(); BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
ActionTokenValueEntity existing = cache.remove(key); SingleUseObjectValueEntity singleUseObjectValueEntity = tx.get(cache, key);
return existing == null ? null : existing.getNotes(); if (singleUseObjectValueEntity != null) {
tx.remove(cache, key);
return singleUseObjectValueEntity.getNotes();
}
return null;
} catch (HotRodClientException re) { } catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. // 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. // 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 @Override
public boolean replace(String key, Map<String, String> notes) { public boolean replace(String key, Map<String, String> notes) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get(); BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
return cache.replace(key, new ActionTokenValueEntity(notes)) != null; return cache.replace(key, new SingleUseObjectValueEntity(notes)) != null;
} }
@Override @Override
public boolean putIfAbsent(String key, long lifespanInSeconds) { public boolean putIfAbsent(String key, long lifespanInSeconds) {
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null); SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(null);
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
try { try {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanInSeconds)); 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; return existing == null;
} catch (HotRodClientException re) { } catch (HotRodClientException re) {
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. // 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 @Override
public boolean contains(String key) { public boolean contains(String key) {
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get(); BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
return cache.containsKey(key); return cache.containsKey(key);
} }

View file

@ -17,7 +17,6 @@
package org.keycloak.models.sessions.infinispan; package org.keycloak.models.sessions.infinispan;
import java.util.function.Supplier;
import org.infinispan.Cache; import org.infinispan.Cache;
import org.infinispan.client.hotrod.Flag; import org.infinispan.client.hotrod.Flag;
@ -26,11 +25,14 @@ import org.infinispan.commons.api.BasicCache;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.connections.infinispan.InfinispanUtil;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.SingleUseObjectProviderFactory; import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
import org.keycloak.connections.infinispan.InfinispanUtil;
import java.util.function.Supplier;
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; 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); private static final Logger LOG = Logger.getLogger(InfinispanSingleUseObjectProviderFactory.class);
// Reuse "actionTokens" infinispan cache for now private volatile Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache;
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
@Override @Override
public InfinispanSingleUseObjectProvider create(KeycloakSession session) { public InfinispanSingleUseObjectProvider create(KeycloakSession session) {
lazyInit(session); return new InfinispanSingleUseObjectProvider(session, singleUseObjectCache);
return new InfinispanSingleUseObjectProvider(session, tokenCache);
} }
private void lazyInit(KeycloakSession session) { static Supplier getSingleUseObjectCache(KeycloakSession session) {
if (tokenCache == null) {
synchronized (this) {
if (tokenCache == null) {
this.tokenCache = getActionTokenCache(session);
}
}
}
}
static Supplier getActionTokenCache(KeycloakSession session) {
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class); InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE); Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
@ -67,15 +57,10 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
if (remoteCache != null) { if (remoteCache != null) {
LOG.debugf("Having remote stores. Using remote cache '%s' for single-use cache of token", remoteCache.getName()); LOG.debugf("Having remote stores. Using remote cache '%s' for single-use cache of token", remoteCache.getName());
return () -> { return () -> remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
// Doing this way as flag is per invocation
return remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
};
} else { } else {
LOG.debugf("Not having remote stores. Using normal cache '%s' for single-use cache of token", cache.getName()); LOG.debugf("Not having remote stores. Using basic cache '%s' for single-use cache of token", cache.getName());
return () -> { return () -> cache;
return cache;
};
} }
} }
@ -86,7 +71,11 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
@Override @Override
public void postInit(KeycloakSessionFactory factory) { 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 @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; package org.keycloak.models.sessions.infinispan.entities;
import org.keycloak.models.ActionTokenValueModel; import org.keycloak.models.SingleUseObjectValueModel;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
@ -26,12 +26,12 @@ import org.infinispan.commons.marshall.SerializeWith;
/** /**
* @author hmlnarik * @author hmlnarik
*/ */
@SerializeWith(ActionTokenValueEntity.ExternalizerImpl.class) @SerializeWith(SingleUseObjectValueEntity.ExternalizerImpl.class)
public class ActionTokenValueEntity implements ActionTokenValueModel { public class SingleUseObjectValueEntity implements SingleUseObjectValueModel {
private final Map<String, String> notes; 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); this.notes = notes == null ? Collections.EMPTY_MAP : new HashMap<>(notes);
} }
@ -47,15 +47,15 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
@Override @Override
public String toString() { 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; private static final int VERSION_1 = 1;
@Override @Override
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException { public void writeObject(ObjectOutput output, SingleUseObjectValueEntity t) throws IOException {
output.writeByte(VERSION_1); output.writeByte(VERSION_1);
output.writeBoolean(t.notes.isEmpty()); output.writeBoolean(t.notes.isEmpty());
@ -65,7 +65,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
} }
@Override @Override
public ActionTokenValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException { public SingleUseObjectValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
byte version = input.readByte(); byte version = input.readByte();
if (version != VERSION_1) { 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(); 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; 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() { 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.GroupListPredicateWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.RealmRemovedSessionEventWFExternalizer 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.WrapperClusterEventWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.UserConsentsUpdatedEventWFExternalizer org.keycloak.models.sessions.infinispan.entities.wildfly.UserConsentsUpdatedEventWFExternalizer
org.keycloak.models.sessions.infinispan.entities.wildfly.RoleRemovedEventWFExternalizer org.keycloak.models.sessions.infinispan.entities.wildfly.RoleRemovedEventWFExternalizer

View file

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

View file

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

View file

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

View file

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

View file

@ -21,7 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Root; import javax.persistence.criteria.Root;
import javax.persistence.criteria.Selection; import javax.persistence.criteria.Selection;
import org.keycloak.models.ActionTokenValueModel; import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction; 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> * @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) { public JpaSingleUseObjectMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
super(session, JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em); super(session, JpaSingleUseObjectEntity.class, SingleUseObjectValueModel.class, em);
} }
@Override @Override

View file

@ -16,10 +16,7 @@
*/ */
package org.keycloak.models.map.storage.jpa.singleUseObject; package org.keycloak.models.map.storage.jpa.singleUseObject;
import java.util.HashMap; import org.keycloak.models.SingleUseObjectValueModel;
import java.util.Map;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.map.storage.CriterionNotSupportedException; import org.keycloak.models.map.storage.CriterionNotSupportedException;
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder; import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction; 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> * @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
*/ */
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, ActionTokenValueModel, JpaSingleUseObjectModelCriteriaBuilder> { public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, SingleUseObjectValueModel, 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 JpaSingleUseObjectModelCriteriaBuilder() { public JpaSingleUseObjectModelCriteriaBuilder() {
super(JpaSingleUseObjectModelCriteriaBuilder::new); super(JpaSingleUseObjectModelCriteriaBuilder::new);
@ -49,20 +39,10 @@ public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuil
} }
@Override @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) { switch (op) {
case EQ: case EQ:
if (modelField == ActionTokenValueModel.SearchableFields.USER_ID || if(modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) {
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) {
validateValue(value, modelField, op, String.class); validateValue(value, modelField, op, String.class);
return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) -> return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) ->
cb.equal(root.get(modelField.getName()), value[0]) 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; 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 @Override
public Map<String, String> getNotes() { public Map<String, String> getNotes() {
return this.notes.stream() 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 @Override
public Long getExpiration() { public Long getExpiration() {
if (this.isMetadataInitialized()) return this.metadata.getExpiration(); if (this.isMetadataInitialized()) return this.metadata.getExpiration();

View file

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

View file

@ -17,8 +17,7 @@
package org.keycloak.models.map.singleUseObject; package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity; 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> * @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 KeycloakSession session;
protected final E entity; protected final E entity;
@ -42,7 +41,7 @@ public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> imp
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (!(o instanceof ActionTokenValueModel)) return false; if (!(o instanceof SingleUseObjectValueModel)) return false;
MapSingleUseObjectAdapter that = (MapSingleUseObjectAdapter) o; MapSingleUseObjectAdapter that = (MapSingleUseObjectAdapter) o;
return Objects.equals(that.entity.getId(), entity.getId()); 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; package org.keycloak.models.map.singleUseObject;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.map.common.TimeAdapter;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.UUID;
/** /**
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a> * @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
@ -33,28 +31,6 @@ public class MapSingleUseObjectAdapter extends AbstractSingleUseObjectModel<MapS
super(session, entity); 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 @Override
public Map<String, String> getNotes() { public Map<String, String> getNotes() {
Map<String, String> notes = entity.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(); String getObjectKey();
void setObjectKey(String objectKey); void setObjectKey(String objectKey);
String getActionId();
void setActionId(String actionId);
String getActionVerificationNonce();
void setActionVerificationNonce(String actionVerificationNonce);
Map<String, String> getNotes(); Map<String, String> getNotes();
void setNotes(Map<String, String> notes); void setNotes(Map<String, String> notes);
String getNote(String name); String getNote(String name);

View file

@ -19,9 +19,7 @@ package org.keycloak.models.map.singleUseObject;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.common.util.Time; import org.keycloak.common.util.Time;
import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.SingleUseObjectValueModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.ActionTokenValueModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.SingleUseObjectProvider; 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> * @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 static final Logger LOG = Logger.getLogger(MapSingleUseObjectProvider.class);
private final KeycloakSession session; 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; this.session = session;
actionTokenStoreTx = storage.createTransaction(session); singleUseObjectTx = storage.createTransaction(session);
session.getTransactionManager().enlistAfterCompletion(actionTokenStoreTx); session.getTransactionManager().enlistAfterCompletion(singleUseObjectTx);
}
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;
} }
@Override @Override
@ -150,16 +68,16 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds)); singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds));
singleUseEntity.setNotes(notes); singleUseEntity.setNotes(notes);
actionTokenStoreTx.create(singleUseEntity); singleUseObjectTx.create(singleUseEntity);
} }
@Override @Override
public Map<String, String> get(String key) { public Map<String, String> get(String key) {
LOG.tracef("get(%s)%s", key, getShortStackTrace()); LOG.tracef("get(%s)%s", key, getShortStackTrace());
MapSingleUseObjectEntity actionToken = getWithExpiration(key); MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
if (actionToken != null) { if (singleUseObject != null) {
Map<String, String> notes = actionToken.getNotes(); Map<String, String> notes = singleUseObject.getNotes();
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
} }
@ -174,7 +92,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
if (singleUseEntity != null) { if (singleUseEntity != null) {
Map<String, String> notes = singleUseEntity.getNotes(); Map<String, String> notes = singleUseEntity.getNotes();
if (actionTokenStoreTx.delete(singleUseEntity.getId())) { if (singleUseObjectTx.delete(singleUseEntity.getId())) {
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes); return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
} }
} }
@ -207,7 +125,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
singleUseEntity.setObjectKey(key); singleUseEntity.setObjectKey(key);
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds)); singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds));
actionTokenStoreTx.create(singleUseEntity); singleUseObjectTx.create(singleUseEntity);
return true; return true;
} }
@ -217,9 +135,9 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
public boolean contains(String key) { public boolean contains(String key) {
LOG.tracef("contains(%s)%s", key, getShortStackTrace()); LOG.tracef("contains(%s)%s", key, getShortStackTrace());
MapSingleUseObjectEntity actionToken = getWithExpiration(key); MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
return actionToken != null; return singleUseObject != null;
} }
@Override @Override
@ -228,13 +146,13 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
} }
private MapSingleUseObjectEntity getWithExpiration(String key) { private MapSingleUseObjectEntity getWithExpiration(String key) {
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria(); DefaultModelCriteria<SingleUseObjectValueModel> mcb = criteria();
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key); 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 (singleUseEntity != null) {
if (isExpired(singleUseEntity, false)) { if (isExpired(singleUseEntity, false)) {
actionTokenStoreTx.delete(singleUseEntity.getId()); singleUseObjectTx.delete(singleUseEntity.getId());
} else { } else {
return singleUseEntity; return singleUseEntity;
} }

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.singleUseObject; 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.KeycloakSession;
import org.keycloak.models.SingleUseObjectProviderFactory; import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.map.common.AbstractMapProviderFactory; 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> * @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> { implements SingleUseObjectProviderFactory<MapSingleUseObjectProvider> {
public MapSingleUseObjectProviderFactory() { public MapSingleUseObjectProviderFactory() {
super(ActionTokenValueModel.class, MapSingleUseObjectProvider.class); super(SingleUseObjectValueModel.class, MapSingleUseObjectProvider.class);
} }
@Override @Override

View file

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

View file

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

View file

@ -23,7 +23,7 @@ import org.keycloak.authorization.model.ResourceServer;
import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.model.Scope;
import org.keycloak.events.Event; import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent; 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.ClientModel;
import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.GroupModel; 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<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<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<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") @SuppressWarnings("unchecked")
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>(); 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_TYPE, MapAdminEventEntity::getResourceType);
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath); put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath);
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.USER_ID, MapSingleUseObjectEntity::getUserId); put(ACTION_TOKEN_PREDICATES, SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
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);
} }
static { static {
@ -244,7 +241,7 @@ public class MapFieldPredicates {
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES); PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES); PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES);
PREDICATES.put(AdminEvent.class, ADMIN_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( 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; 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.KeycloakSession;
import org.keycloak.models.map.common.AbstractEntity; import org.keycloak.models.map.common.AbstractEntity;
import org.keycloak.models.map.common.DeepCloner; 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> * @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) { public SingleUseObjectConcurrentHashMapStorage(StringKeyConverter<K> keyConverter, DeepCloner cloner) {
super(ActionTokenValueModel.class, keyConverter, cloner); super(SingleUseObjectValueModel.class, keyConverter, cloner);
} }
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> createTransaction(KeycloakSession session) { public MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> createTransaction(KeycloakSession session) {
MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> actionTokenTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class); MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> singleUseObjectTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
if (actionTokenTransaction == null) { if (singleUseObjectTransaction == null) {
actionTokenTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates); singleUseObjectTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
session.setAttribute("map-transaction-" + hashCode(), actionTokenTransaction); session.setAttribute("map-transaction-" + hashCode(), singleUseObjectTransaction);
} }
return actionTokenTransaction; return singleUseObjectTransaction;
} }
@Override @Override
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) { public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
if (value.getId() == null) { 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) { if (value.getObjectKey() != null) {
value.setId(value.getObjectKey()); value.setId(value.getObjectKey());
} }
@ -65,8 +62,8 @@ public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity
} }
@Override @Override
public Stream<MapSingleUseObjectEntity> read(QueryParameters<ActionTokenValueModel> queryParameters) { public Stream<MapSingleUseObjectEntity> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
DefaultModelCriteria<ActionTokenValueModel> criteria = queryParameters.getModelCriteriaBuilder(); DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
if (criteria == null) { if (criteria == null) {
return Stream.empty(); return Stream.empty();

View file

@ -17,7 +17,7 @@
package org.keycloak.models.map.storage.chm; 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.DeepCloner;
import org.keycloak.models.map.common.StringKeyConverter; import org.keycloak.models.map.common.StringKeyConverter;
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity; 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> * @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, StringKeyConverter<K> keyConverter,
DeepCloner cloner, DeepCloner cloner,
Map<SearchableModelField<? super ActionTokenValueModel>, Map<SearchableModelField<? super SingleUseObjectValueModel>,
MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, ActionTokenValueModel>> fieldPredicates) { MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, SingleUseObjectValueModel>> fieldPredicates) {
super(map, keyConverter, cloner, fieldPredicates); super(map, keyConverter, cloner, fieldPredicates);
} }
@Override @Override
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) { public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
if (value.getId() == null) { 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) { if (value.getObjectKey() != null) {
value.setId(value.getObjectKey()); value.setId(value.getObjectKey());
} }

View file

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

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) .buildTime(true)
.build(); .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) public static final Option<String> STORAGE_DBLOCK = new OptionBuilder<>("storage-dblock", String.class)
.category(OptionCategory.STORAGE) .category(OptionCategory.STORAGE)
.hidden() .hidden()

View file

@ -22,8 +22,6 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.
import java.util.Optional; 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;
import org.keycloak.config.StorageOptions.StorageType; import org.keycloak.config.StorageOptions.StorageType;
@ -197,18 +195,6 @@ final class StoragePropertyMappers {
.transformer(StoragePropertyMappers::resolveMapStorageProvider) .transformer(StoragePropertyMappers::resolveMapStorageProvider)
.paramLabel("type") .paramLabel("type")
.build(), .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) fromOption(StorageOptions.STORAGE_DBLOCK)
.to("kc.spi-dblock-provider") .to("kc.spi-dblock-provider")
.mapFrom("storage") .mapFrom("storage")

View file

@ -32,9 +32,6 @@ Storage (Experimental):
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible --storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod. 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> --storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod. 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 --storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod. 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> --storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod. 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 --storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod. 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> --storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod. 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 --storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
values are: jpa, chm, hotrod. 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> --storage-area-auth-session <type>
Experimental: Sets a storage mechanism for authentication sessions. Possible Experimental: Sets a storage mechanism for authentication sessions. Possible
values are: jpa, chm, hotrod. 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; 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> * @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.RealmSpi
org.keycloak.models.RoleSpi org.keycloak.models.RoleSpi
org.keycloak.models.DeploymentStateSpi org.keycloak.models.DeploymentStateSpi
org.keycloak.models.ActionTokenStoreSpi
org.keycloak.models.OAuth2DeviceUserCodeSpi org.keycloak.models.OAuth2DeviceUserCodeSpi
org.keycloak.models.SingleUseObjectSpi org.keycloak.models.SingleUseObjectSpi
org.keycloak.models.UserSessionSpi org.keycloak.models.UserSessionSpi

View file

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

View file

@ -24,7 +24,7 @@ import java.util.UUID;
* *
* @author hmlnarik * @author hmlnarik
*/ */
public interface ActionTokenKeyModel { public interface SingleUseObjectKeyModel {
/** /**
* @return ID of user which this token is for. * @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. * This model represents contents of an action token shareable among Keycloak instances in the cluster.
* @author hmlnarik * @author hmlnarik
*/ */
public interface ActionTokenValueModel { public interface SingleUseObjectValueModel {
class SearchableFields { class SearchableFields {
public static final SearchableModelField<ActionTokenValueModel> ID = new SearchableModelField<>("id", String.class); public static final SearchableModelField<SingleUseObjectValueModel> ID = new SearchableModelField<>("id", String.class);
public static final SearchableModelField<ActionTokenValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class); public static final SearchableModelField<SingleUseObjectValueModel> 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);
} }
/** /**

View file

@ -22,7 +22,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.TokenVerifier.Predicate; import org.keycloak.TokenVerifier.Predicate;
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.common.util.Time; 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.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.services.Urls; import org.keycloak.services.Urls;
@ -38,7 +39,7 @@ import java.util.UUID;
* *
* @author hmlnarik * @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_AUTHENTICATION_SESSION_ID = "asid";
public static final String JSON_FIELD_EMAIL = "eml"; public static final String JSON_FIELD_EMAIL = "eml";

View file

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

View file

@ -17,7 +17,7 @@
package org.keycloak.authentication.authenticators.resetcred; package org.keycloak.authentication.authenticators.resetcred;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.authentication.*; import org.keycloak.authentication.*;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken; 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.RequiredActionContextResult;
import org.keycloak.authentication.RequiredActionFactory; import org.keycloak.authentication.RequiredActionFactory;
import org.keycloak.authentication.RequiredActionProvider; import org.keycloak.authentication.RequiredActionProvider;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator; import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProvider;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
@ -47,16 +46,17 @@ import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder; import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ClientSessionContext; import org.keycloak.models.ClientSessionContext;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredActionProviderModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
@ -1037,11 +1037,10 @@ public class AuthenticationManager {
ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) { ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) {
String actionTokenKeyToInvalidate = authSession.getAuthNote(INVALIDATE_ACTION_TOKEN); String actionTokenKeyToInvalidate = authSession.getAuthNote(INVALIDATE_ACTION_TOKEN);
if (actionTokenKeyToInvalidate != null) { if (actionTokenKeyToInvalidate != null) {
ActionTokenKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate); SingleUseObjectKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate);
if (actionTokenKey != null) { if (actionTokenKey != null) {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class); SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
actionTokenStore.put(actionTokenKey, null); // Token is invalidated 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.RequiredActionProvider;
import org.keycloak.authentication.actiontoken.ActionTokenContext; import org.keycloak.authentication.actiontoken.ActionTokenContext;
import org.keycloak.authentication.actiontoken.ActionTokenHandler; 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.ExplainedTokenVerificationException;
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler; import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator; 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.EventBuilder;
import org.keycloak.events.EventType; import org.keycloak.events.EventType;
import org.keycloak.exceptions.TokenNotActiveException; import org.keycloak.exceptions.TokenNotActiveException;
import org.keycloak.locale.LocaleSelectorProvider; import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.locale.LocaleUpdaterProvider;
import org.keycloak.models.ActionTokenKeyModel;
import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel; 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.AuthenticationSessionManager;
import org.keycloak.services.managers.ClientSessionCode; import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages; import org.keycloak.services.messages.Messages;
import org.keycloak.services.resource.RealmResourceProvider;
import org.keycloak.services.util.AuthenticationFlowURLHelper; import org.keycloak.services.util.AuthenticationFlowURLHelper;
import org.keycloak.services.util.BrowserHistoryHelper; import org.keycloak.services.util.BrowserHistoryHelper;
import org.keycloak.services.util.CacheControlUtil; import org.keycloak.services.util.CacheControlUtil;
@ -461,7 +458,7 @@ public class LoginActionsService {
return handleActionToken(key, execution, clientId, tabId); 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; T token;
ActionTokenHandler<T> handler; ActionTokenHandler<T> handler;
ActionTokenContext<T> tokenContext; ActionTokenContext<T> tokenContext;

View file

@ -24,12 +24,12 @@ import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationExcepti
import org.keycloak.common.VerificationException; import org.keycloak.common.VerificationException;
import org.keycloak.events.Errors; import org.keycloak.events.Errors;
import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.ActionTokenKeyModel; import org.keycloak.models.SingleUseObjectKeyModel;
import org.keycloak.models.ActionTokenStoreProvider;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants; import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel; import org.keycloak.models.UserSessionModel;
import org.keycloak.protocol.oidc.utils.RedirectUtils; 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, * 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). * 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 { try {
checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser); checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser);
} catch (ExplainedVerificationException ex) { } catch (ExplainedVerificationException ex) {
@ -299,10 +299,10 @@ public class LoginActionsServiceChecks {
return true; return true;
} }
public static <T extends JsonWebToken & ActionTokenKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException { public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException {
ActionTokenStoreProvider actionTokenStore = context.getSession().getProvider(ActionTokenStoreProvider.class); 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); throw new ExplainedTokenVerificationException(token, Errors.EXPIRED_CODE, Messages.EXPIRED_ACTION);
} }
} }

View file

@ -811,7 +811,6 @@
<keycloak.loginFailure.provider>map</keycloak.loginFailure.provider> <keycloak.loginFailure.provider>map</keycloak.loginFailure.provider>
<keycloak.authorization.provider>map</keycloak.authorization.provider> <keycloak.authorization.provider>map</keycloak.authorization.provider>
<keycloak.eventsStore.provider>map</keycloak.eventsStore.provider> <keycloak.eventsStore.provider>map</keycloak.eventsStore.provider>
<keycloak.actionToken.provider>map</keycloak.actionToken.provider>
<keycloak.singleUseObject.provider>map</keycloak.singleUseObject.provider> <keycloak.singleUseObject.provider>map</keycloak.singleUseObject.provider>
<keycloak.publicKeyStorage.provider>map</keycloak.publicKeyStorage.provider> <keycloak.publicKeyStorage.provider>map</keycloak.publicKeyStorage.provider>
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled> <keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
@ -865,7 +864,6 @@
<keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider> <keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider>
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.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.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.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
<keycloak.user.map.storage.provider>jpa</keycloak.user.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> <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": { "singleUseObject": {
"provider": "${keycloak.singleUseObject.provider:infinispan}", "provider": "${keycloak.singleUseObject.provider:infinispan}",
"map": { "map": {

View file

@ -1360,7 +1360,6 @@
<keycloak.userSession.map.storage.provider>hotrod</keycloak.userSession.map.storage.provider> <keycloak.userSession.map.storage.provider>hotrod</keycloak.userSession.map.storage.provider>
<keycloak.authorization.map.storage.provider>hotrod</keycloak.authorization.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.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> <keycloak.singleUseObject.map.storage.provider>hotrod</keycloak.singleUseObject.map.storage.provider>
<infinispan.version>${infinispan.version}</infinispan.version> <infinispan.version>${infinispan.version}</infinispan.version>
<keycloak.testsuite.start-hotrod-container>${keycloak.testsuite.start-hotrod-container}</keycloak.testsuite.start-hotrod-container> <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.jboss.logging.Logger;
import org.keycloak.authorization.store.StoreFactorySpi; import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi; import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectSpi; import org.keycloak.models.SingleUseObjectSpi;
import org.keycloak.models.UserLoginFailureSpi; import org.keycloak.models.UserLoginFailureSpi;
@ -81,7 +80,6 @@ public class HotRodMapStorage extends KeycloakModelParameters {
@Override @Override
public void updateConfig(Config cf) { public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID) 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(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("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.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.PublicKeyStorageSpi;
import org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory; import org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory;
import org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProviderFactory; import org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProviderFactory;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.SingleUseObjectSpi; import org.keycloak.models.SingleUseObjectSpi;
import org.keycloak.models.cache.authorization.CachedStoreFactorySpi; import org.keycloak.models.cache.authorization.CachedStoreFactorySpi;
import org.keycloak.models.cache.infinispan.authorization.InfinispanCacheStoreFactoryProviderFactory; import org.keycloak.models.cache.infinispan.authorization.InfinispanCacheStoreFactoryProviderFactory;
import org.keycloak.models.cache.CachePublicKeyProviderSpi; import org.keycloak.models.cache.CachePublicKeyProviderSpi;
import org.keycloak.models.session.UserSessionPersisterSpi; 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.InfinispanAuthenticationSessionProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory;
import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory; import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory;
@ -68,7 +66,6 @@ public class Infinispan extends KeycloakModelParameters {
.add(InfinispanConnectionSpi.class) .add(InfinispanConnectionSpi.class)
.add(StickySessionEncoderSpi.class) .add(StickySessionEncoderSpi.class)
.add(UserSessionPersisterSpi.class) .add(UserSessionPersisterSpi.class)
.add(ActionTokenStoreSpi.class)
.add(SingleUseObjectSpi.class) .add(SingleUseObjectSpi.class)
.add(PublicKeyStorageSpi.class) .add(PublicKeyStorageSpi.class)
.add(CachePublicKeyProviderSpi.class) .add(CachePublicKeyProviderSpi.class)
@ -86,7 +83,6 @@ public class Infinispan extends KeycloakModelParameters {
.add(InfinispanUserCacheProviderFactory.class) .add(InfinispanUserCacheProviderFactory.class)
.add(InfinispanUserSessionProviderFactory.class) .add(InfinispanUserSessionProviderFactory.class)
.add(InfinispanUserLoginFailureProviderFactory.class) .add(InfinispanUserLoginFailureProviderFactory.class)
.add(InfinispanActionTokenStoreProviderFactory.class)
.add(InfinispanSingleUseObjectProviderFactory.class) .add(InfinispanSingleUseObjectProviderFactory.class)
.add(StickySessionEncoderProviderFactory.class) .add(StickySessionEncoderProviderFactory.class)
.add(TimerProviderFactory.class) .add(TimerProviderFactory.class)

View file

@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet;
import java.util.Set; import java.util.Set;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.authorization.store.StoreFactorySpi; import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.DeploymentStateSpi; import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectSpi; 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("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(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("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(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("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.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.junit.runners.model.Statement;
import org.keycloak.authorization.store.StoreFactorySpi; import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventStoreSpi;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi; import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.LDAPConstants; import org.keycloak.models.LDAPConstants;
import org.keycloak.models.SingleUseObjectSpi; import org.keycloak.models.SingleUseObjectSpi;
@ -103,7 +102,6 @@ public class LdapMapStorage extends KeycloakModelParameters {
.spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID) .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-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi(EventStoreSpi.NAME).config("map.storage-auth-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(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
.spi("publicKeyStorage").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.authorization.store.StoreFactorySpi;
import org.keycloak.events.EventStoreSpi; import org.keycloak.events.EventStoreSpi;
import org.keycloak.keys.PublicKeyStorageSpi; import org.keycloak.keys.PublicKeyStorageSpi;
import org.keycloak.models.ActionTokenStoreProviderFactory;
import org.keycloak.models.ActionTokenStoreSpi;
import org.keycloak.models.DeploymentStateSpi; import org.keycloak.models.DeploymentStateSpi;
import org.keycloak.models.SingleUseObjectProviderFactory; import org.keycloak.models.SingleUseObjectProviderFactory;
import org.keycloak.models.SingleUseObjectSpi; 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() static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(AuthenticationSessionSpi.class) .add(AuthenticationSessionSpi.class)
.add(ActionTokenStoreSpi.class)
.add(SingleUseObjectSpi.class) .add(SingleUseObjectSpi.class)
.add(PublicKeyStorageSpi.class) .add(PublicKeyStorageSpi.class)
.add(MapStorageSpi.class) .add(MapStorageSpi.class)
@ -80,7 +77,6 @@ public class Map extends KeycloakModelParameters {
.add(MapUserLoginFailureProviderFactory.class) .add(MapUserLoginFailureProviderFactory.class)
.add(NoLockingDBLockProviderFactory.class) .add(NoLockingDBLockProviderFactory.class)
.add(MapEventStoreProviderFactory.class) .add(MapEventStoreProviderFactory.class)
.add(ActionTokenStoreProviderFactory.class)
.add(SingleUseObjectProviderFactory.class) .add(SingleUseObjectProviderFactory.class)
.add(MapPublicKeyStorageProviderFactory.class) .add(MapPublicKeyStorageProviderFactory.class)
.build(); .build();
@ -92,7 +88,6 @@ public class Map extends KeycloakModelParameters {
@Override @Override
public void updateConfig(Config cf) { public void updateConfig(Config cf) {
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID) cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID)
.spi(ActionTokenStoreSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
.spi(SingleUseObjectSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .spi(SingleUseObjectSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID) .spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.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.hamcrest.Matchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey; import org.keycloak.models.DefaultActionTokenKey;
import org.keycloak.common.util.Time; 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.Constants;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.SingleUseObjectProvider;
import org.keycloak.models.UserModel; 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.KeycloakModelTest;
import org.keycloak.testsuite.model.RequireProvider; import org.keycloak.testsuite.model.RequireProvider;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; 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.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assume.assumeFalse;
@RequireProvider(ActionTokenStoreProvider.class)
@RequireProvider(SingleUseObjectProvider.class) @RequireProvider(SingleUseObjectProvider.class)
public class SingleUseObjectModelTest extends KeycloakModelTest { public class SingleUseObjectModelTest extends KeycloakModelTest {
@ -64,46 +66,47 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
@Test @Test
public void testActionTokens() { public void testActionTokens() {
ActionTokenKeyModel key = withRealm(realmId, (session, realm) -> { DefaultActionTokenKey key = withRealm(realmId, (session, realm) -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class); SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), Time.currentTime() + 60, null); int time = Time.currentTime();
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), time + 60, null);
Map<String, String> notes = new HashMap<>(); Map<String, String> notes = new HashMap<>();
notes.put("foo", "bar"); notes.put("foo", "bar");
actionTokenStore.put(actionTokenKey, notes); singleUseObjectProvider.put(actionTokenKey.serializeKey(), actionTokenKey.getExp() - time, notes);
return actionTokenKey; return actionTokenKey;
}); });
inComittedTransaction(session -> { inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class); SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key); Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNotNull(valueModel); Assert.assertNotNull(notes);
Assert.assertEquals("bar", valueModel.getNote("foo")); Assert.assertEquals("bar", notes.get("foo"));
valueModel = actionTokenStore.remove(key); notes = singleUseObjectProvider.remove(key.serializeKey());
Assert.assertNotNull(valueModel); Assert.assertNotNull(notes);
Assert.assertEquals("bar", valueModel.getNote("foo")); Assert.assertEquals("bar", notes.get("foo"));
}); });
inComittedTransaction(session -> { inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class); SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key); Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNull(valueModel); Assert.assertNull(notes);
Map<String, String> notes = new HashMap<>(); notes = new HashMap<>();
notes.put("foo", "bar"); notes.put("foo", "bar");
actionTokenStore.put(key, notes); singleUseObjectProvider.put(key.serializeKey(), key.getExp() - Time.currentTime(), notes);
}); });
inComittedTransaction(session -> { inComittedTransaction(session -> {
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class); SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
ActionTokenValueModel valueModel = actionTokenStore.get(key); Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNotNull(valueModel); Assert.assertNotNull(notes);
Assert.assertEquals("bar", valueModel.getNote("foo")); Assert.assertEquals("bar", notes.get("foo"));
Time.setOffset(70); Time.setOffset(70);
valueModel = actionTokenStore.get(key); notes = singleUseObjectProvider.get(key.serializeKey());
Assert.assertNull(valueModel); Assert.assertNull(notes);
}); });
} }
@ -156,4 +159,89 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
Assert.assertNull(singleUseStore.get(key)); 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.userSession.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.loginFailure.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.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.singleUseObject.provider</key><value>map</value></systemProperty>
<systemProperty><key>keycloak.eventsStore.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> <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": { "singleUseObject": {
"provider": "${keycloak.singleUseObject.provider:infinispan}", "provider": "${keycloak.singleUseObject.provider:infinispan}",
"map": { "map": {