Merge ActionTokenStoreProvider and SingleUseObjectProvider (#13677)
Closes #13334
This commit is contained in:
parent
90c1624668
commit
761929d174
63 changed files with 290 additions and 1018 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,8 @@
|
|||
*/
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.infinispan.commons.api.BasicCache;
|
||||
import org.keycloak.cluster.ClusterEvent;
|
||||
import org.keycloak.cluster.ClusterProvider;
|
||||
import org.infinispan.context.Flag;
|
||||
|
@ -97,7 +99,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
|||
}
|
||||
}
|
||||
|
||||
public <K, V> void put(Cache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
|
||||
public <K, V> void put(BasicCache<K, V> cache, K key, V value, long lifespan, TimeUnit lifespanUnit) {
|
||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.ADD_WITH_LIFESPAN, key);
|
||||
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
|
@ -179,7 +181,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
|||
tasks.put(taskKey, () -> clusterProvider.notify(taskKey, event, ignoreSender, ClusterProvider.DCNotify.ALL_DCS));
|
||||
}
|
||||
|
||||
public <K, V> void remove(Cache<K, V> cache, K key) {
|
||||
public <K, V> void remove(BasicCache<K, V> cache, K key) {
|
||||
log.tracev("Adding cache operation: {0} on {1}", CacheOperation.REMOVE, key);
|
||||
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
|
@ -201,7 +203,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
|||
}
|
||||
|
||||
// This is for possibility to lookup for session by id, which was created in this transaction
|
||||
public <K, V> V get(Cache<K, V> cache, K key) {
|
||||
public <K, V> V get(BasicCache<K, V> cache, K key) {
|
||||
Object taskKey = getTaskKey(cache, key);
|
||||
CacheTask current = tasks.get(taskKey);
|
||||
if (current != null) {
|
||||
|
@ -214,7 +216,7 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
|||
return cache.get(key);
|
||||
}
|
||||
|
||||
private static <K, V> Object getTaskKey(Cache<K, V> cache, K key) {
|
||||
private static <K, V> Object getTaskKey(BasicCache<K, V> cache, K key) {
|
||||
if (key instanceof String) {
|
||||
return new StringBuilder(cache.getName())
|
||||
.append("::")
|
||||
|
@ -245,8 +247,10 @@ public class InfinispanKeycloakTransaction implements KeycloakTransaction {
|
|||
}
|
||||
|
||||
// Ignore return values. Should have better performance within cluster / cross-dc env
|
||||
private static <K, V> Cache<K, V> decorateCache(Cache<K, V> cache) {
|
||||
return cache.getAdvancedCache()
|
||||
private static <K, V> BasicCache<K, V> decorateCache(BasicCache<K, V> cache) {
|
||||
if (cache instanceof RemoteCache)
|
||||
return cache;
|
||||
return ((Cache) cache).getAdvancedCache()
|
||||
.withFlags(Flag.IGNORE_RETURN_VALUES, Flag.SKIP_REMOTE_LOOKUP);
|
||||
}
|
||||
}
|
|
@ -27,12 +27,12 @@ import org.jboss.logging.Logger;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.SingleUseObjectProvider;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
|
||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||
|
||||
/**
|
||||
* TODO: Check if Boolean can be used as single-use cache argument instead of ActionTokenValueEntity. With respect to other single-use cache usecases like "Revoke Refresh Token" .
|
||||
* Also with respect to the usage of streams iterating over "actionTokens" cache (check there are no ClassCastExceptions when casting values directly to ActionTokenValueEntity)
|
||||
* TODO: Check if Boolean can be used as single-use cache argument instead of SingleUseObjectValueEntity. With respect to other single-use cache usecases like "Revoke Refresh Token" .
|
||||
* Also with respect to the usage of streams iterating over "actionTokens" cache (check there are no ClassCastExceptions when casting values directly to SingleUseObjectValueEntity)
|
||||
*
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
|
@ -41,22 +41,24 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
|
|||
|
||||
public static final Logger logger = Logger.getLogger(InfinispanSingleUseObjectProvider.class);
|
||||
|
||||
private final Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
||||
private final Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache;
|
||||
private final KeycloakSession session;
|
||||
private final InfinispanKeycloakTransaction tx;
|
||||
|
||||
public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier<BasicCache<String, ActionTokenValueEntity>> actionKeyCache) {
|
||||
public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache) {
|
||||
this.session = session;
|
||||
this.tokenCache = actionKeyCache;
|
||||
this.singleUseObjectCache = singleUseObjectCache;
|
||||
this.tx = new InfinispanKeycloakTransaction();
|
||||
|
||||
session.getTransactionManager().enlistAfterCompletion(tx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, long lifespanSeconds, Map<String, String> notes) {
|
||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
|
||||
|
||||
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(notes);
|
||||
try {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds));
|
||||
cache.put(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
tx.put(cache, key, tokenValue, InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)), TimeUnit.MILLISECONDS);
|
||||
} catch (HotRodClientException re) {
|
||||
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
|
||||
if (logger.isDebugEnabled()) {
|
||||
|
@ -69,17 +71,23 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
|
|||
|
||||
@Override
|
||||
public Map<String, String> get(String key) {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
ActionTokenValueEntity actionTokenValueEntity = cache.get(key);
|
||||
return actionTokenValueEntity != null ? actionTokenValueEntity.getNotes() : null;
|
||||
SingleUseObjectValueEntity singleUseObjectValueEntity;
|
||||
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
singleUseObjectValueEntity = tx.get(cache, key);
|
||||
return singleUseObjectValueEntity != null ? singleUseObjectValueEntity.getNotes() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> remove(String key) {
|
||||
try {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
ActionTokenValueEntity existing = cache.remove(key);
|
||||
return existing == null ? null : existing.getNotes();
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
SingleUseObjectValueEntity singleUseObjectValueEntity = tx.get(cache, key);
|
||||
if (singleUseObjectValueEntity != null) {
|
||||
tx.remove(cache, key);
|
||||
return singleUseObjectValueEntity.getNotes();
|
||||
}
|
||||
return null;
|
||||
} catch (HotRodClientException re) {
|
||||
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
|
||||
// In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place.
|
||||
|
@ -93,18 +101,18 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
|
|||
|
||||
@Override
|
||||
public boolean replace(String key, Map<String, String> notes) {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
return cache.replace(key, new ActionTokenValueEntity(notes)) != null;
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
return cache.replace(key, new SingleUseObjectValueEntity(notes)) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean putIfAbsent(String key, long lifespanInSeconds) {
|
||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null);
|
||||
SingleUseObjectValueEntity tokenValue = new SingleUseObjectValueEntity(null);
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
|
||||
try {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanInSeconds));
|
||||
ActionTokenValueEntity existing = cache.putIfAbsent(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
|
||||
SingleUseObjectValueEntity existing = cache.putIfAbsent(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS);
|
||||
return existing == null;
|
||||
} catch (HotRodClientException re) {
|
||||
// No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened.
|
||||
|
@ -118,7 +126,7 @@ public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvide
|
|||
|
||||
@Override
|
||||
public boolean contains(String key) {
|
||||
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||
BasicCache<String, SingleUseObjectValueEntity> cache = singleUseObjectCache.get();
|
||||
return cache.containsKey(key);
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.keycloak.models.sessions.infinispan;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.infinispan.Cache;
|
||||
import org.infinispan.client.hotrod.Flag;
|
||||
|
@ -26,11 +25,14 @@ import org.infinispan.commons.api.BasicCache;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.SingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY;
|
||||
|
||||
/**
|
||||
|
@ -40,26 +42,14 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
|
|||
|
||||
private static final Logger LOG = Logger.getLogger(InfinispanSingleUseObjectProviderFactory.class);
|
||||
|
||||
// Reuse "actionTokens" infinispan cache for now
|
||||
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
||||
private volatile Supplier<BasicCache<String, SingleUseObjectValueEntity>> singleUseObjectCache;
|
||||
|
||||
@Override
|
||||
public InfinispanSingleUseObjectProvider create(KeycloakSession session) {
|
||||
lazyInit(session);
|
||||
return new InfinispanSingleUseObjectProvider(session, tokenCache);
|
||||
return new InfinispanSingleUseObjectProvider(session, singleUseObjectCache);
|
||||
}
|
||||
|
||||
private void lazyInit(KeycloakSession session) {
|
||||
if (tokenCache == null) {
|
||||
synchronized (this) {
|
||||
if (tokenCache == null) {
|
||||
this.tokenCache = getActionTokenCache(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Supplier getActionTokenCache(KeycloakSession session) {
|
||||
static Supplier getSingleUseObjectCache(KeycloakSession session) {
|
||||
InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
|
||||
Cache cache = connections.getCache(InfinispanConnectionProvider.ACTION_TOKEN_CACHE);
|
||||
|
||||
|
@ -67,15 +57,10 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
|
|||
|
||||
if (remoteCache != null) {
|
||||
LOG.debugf("Having remote stores. Using remote cache '%s' for single-use cache of token", remoteCache.getName());
|
||||
return () -> {
|
||||
// Doing this way as flag is per invocation
|
||||
return remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
|
||||
};
|
||||
return () -> remoteCache.withFlags(Flag.FORCE_RETURN_VALUE);
|
||||
} else {
|
||||
LOG.debugf("Not having remote stores. Using normal cache '%s' for single-use cache of token", cache.getName());
|
||||
return () -> {
|
||||
return cache;
|
||||
};
|
||||
LOG.debugf("Not having remote stores. Using basic cache '%s' for single-use cache of token", cache.getName());
|
||||
return () -> cache;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +71,11 @@ public class InfinispanSingleUseObjectProviderFactory implements SingleUseObject
|
|||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
// It is necessary to put the cache initialization here, otherwise the cache would be initialized lazily, that
|
||||
// means also listeners will start only after first cache initialization - that would be too late
|
||||
if (singleUseObjectCache == null) {
|
||||
this.singleUseObjectCache = getSingleUseObjectCache(factory.create());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.models.sessions.infinispan.entities;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
@ -26,12 +26,12 @@ import org.infinispan.commons.marshall.SerializeWith;
|
|||
/**
|
||||
* @author hmlnarik
|
||||
*/
|
||||
@SerializeWith(ActionTokenValueEntity.ExternalizerImpl.class)
|
||||
public class ActionTokenValueEntity implements ActionTokenValueModel {
|
||||
@SerializeWith(SingleUseObjectValueEntity.ExternalizerImpl.class)
|
||||
public class SingleUseObjectValueEntity implements SingleUseObjectValueModel {
|
||||
|
||||
private final Map<String, String> notes;
|
||||
|
||||
public ActionTokenValueEntity(Map<String, String> notes) {
|
||||
public SingleUseObjectValueEntity(Map<String, String> notes) {
|
||||
this.notes = notes == null ? Collections.EMPTY_MAP : new HashMap<>(notes);
|
||||
}
|
||||
|
||||
|
@ -47,15 +47,15 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("ActionTokenValueEntity [ notes=%s ]", notes.toString());
|
||||
return String.format("SingleUseObjectValueEntity [ notes=%s ]", notes.toString());
|
||||
}
|
||||
|
||||
public static class ExternalizerImpl implements Externalizer<ActionTokenValueEntity> {
|
||||
public static class ExternalizerImpl implements Externalizer<SingleUseObjectValueEntity> {
|
||||
|
||||
private static final int VERSION_1 = 1;
|
||||
|
||||
@Override
|
||||
public void writeObject(ObjectOutput output, ActionTokenValueEntity t) throws IOException {
|
||||
public void writeObject(ObjectOutput output, SingleUseObjectValueEntity t) throws IOException {
|
||||
output.writeByte(VERSION_1);
|
||||
|
||||
output.writeBoolean(t.notes.isEmpty());
|
||||
|
@ -65,7 +65,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ActionTokenValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
public SingleUseObjectValueEntity readObject(ObjectInput input) throws IOException, ClassNotFoundException {
|
||||
byte version = input.readByte();
|
||||
|
||||
if (version != VERSION_1) {
|
||||
|
@ -75,7 +75,7 @@ public class ActionTokenValueEntity implements ActionTokenValueModel {
|
|||
|
||||
Map<String, String> notes = notesEmpty ? Collections.EMPTY_MAP : (Map<String, String>) input.readObject();
|
||||
|
||||
return new ActionTokenValueEntity(notes);
|
||||
return new SingleUseObjectValueEntity(notes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -18,11 +18,11 @@
|
|||
|
||||
package org.keycloak.models.sessions.infinispan.entities.wildfly;
|
||||
|
||||
import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||
import org.keycloak.models.sessions.infinispan.entities.SingleUseObjectValueEntity;
|
||||
|
||||
public class ActionTokenValueEntityWFExternalizer extends InfinispanExternalizerAdapter<ActionTokenValueEntity> {
|
||||
public class ActionTokenValueEntityWFExternalizer extends InfinispanExternalizerAdapter<SingleUseObjectValueEntity> {
|
||||
|
||||
public ActionTokenValueEntityWFExternalizer() {
|
||||
super(ActionTokenValueEntity.class, new ActionTokenValueEntity.ExternalizerImpl());
|
||||
super(SingleUseObjectValueEntity.class, new SingleUseObjectValueEntity.ExternalizerImpl());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory
|
|
@ -18,7 +18,6 @@
|
|||
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.GroupListPredicateWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.RealmRemovedSessionEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.ActionTokenReducedKeyWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.WrapperClusterEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.UserConsentsUpdatedEventWFExternalizer
|
||||
org.keycloak.models.sessions.infinispan.entities.wildfly.RoleRemovedEventWFExternalizer
|
||||
|
|
|
@ -17,11 +17,10 @@
|
|||
|
||||
package org.keycloak.models.map.storage.hotRod;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.AmphibianProviderFactory;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
@ -192,7 +191,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
HotRodConnectionProvider connectionProvider = session.getProvider(HotRodConnectionProvider.class);
|
||||
HotRodEntityDescriptor<E, V> entityDescriptor = (HotRodEntityDescriptor<E, V>) getEntityDescriptor(modelType);
|
||||
|
||||
if (modelType == ActionTokenValueModel.class) {
|
||||
if (modelType == SingleUseObjectValueModel.class) {
|
||||
return new SingleUseObjectHotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()), StringKeyConverter.StringKey.INSTANCE, entityDescriptor, CLONER);
|
||||
} if (modelType == AuthenticatedClientSessionModel.class) {
|
||||
return new HotRodMapStorage(connectionProvider.getRemoteCache(entityDescriptor.getCacheName()),
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package org.keycloak.models.map.storage.hotRod;
|
||||
|
||||
import org.infinispan.client.hotrod.RemoteCache;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
|
@ -45,7 +45,7 @@ import java.util.stream.Stream;
|
|||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity, V extends HotRodEntityDelegate<E> & AbstractEntity, M>
|
||||
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel> {
|
||||
extends HotRodMapStorage<String, HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> {
|
||||
|
||||
private final StringKeyConverter<String> keyConverter;
|
||||
private final HotRodEntityDescriptor<HotRodSingleUseObjectEntity, HotRodSingleUseObjectEntityDelegate> storedEntityDescriptor;
|
||||
|
@ -61,18 +61,15 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
|
|||
}
|
||||
|
||||
@Override
|
||||
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel> createTransactionInternal(KeycloakSession session) {
|
||||
Map<SearchableModelField<? super ActionTokenValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodSingleUseObjectEntityDelegate, ActionTokenValueModel>> fieldPredicates =
|
||||
MapFieldPredicates.getPredicates((Class<ActionTokenValueModel>) storedEntityDescriptor.getModelTypeClass());
|
||||
protected MapKeycloakTransaction<HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel> createTransactionInternal(KeycloakSession session) {
|
||||
Map<SearchableModelField<? super SingleUseObjectValueModel>, MapModelCriteriaBuilder.UpdatePredicatesFunc<K, HotRodSingleUseObjectEntityDelegate, SingleUseObjectValueModel>> fieldPredicates =
|
||||
MapFieldPredicates.getPredicates((Class<SingleUseObjectValueModel>) storedEntityDescriptor.getModelTypeClass());
|
||||
return new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HotRodSingleUseObjectEntityDelegate create(HotRodSingleUseObjectEntityDelegate value) {
|
||||
if (value.getId() == null) {
|
||||
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
|
||||
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
|
||||
}
|
||||
if (value.getObjectKey() != null) {
|
||||
value.setId(value.getObjectKey());
|
||||
}
|
||||
|
@ -81,8 +78,8 @@ public class SingleUseObjectHotRodMapStorage<K, E extends AbstractHotRodEntity,
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<HotRodSingleUseObjectEntityDelegate> read(QueryParameters<ActionTokenValueModel> queryParameters) {
|
||||
DefaultModelCriteria<ActionTokenValueModel> criteria = queryParameters.getModelCriteriaBuilder();
|
||||
public Stream<HotRodSingleUseObjectEntityDelegate> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
|
||||
DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
|
||||
|
||||
if (criteria == null) {
|
||||
return Stream.empty();
|
||||
|
|
|
@ -35,7 +35,7 @@ import java.util.Set;
|
|||
implementInterface = "org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity",
|
||||
inherits = "org.keycloak.models.map.storage.hotRod.singleUseObject.HotRodSingleUseObjectEntity.AbstractHotRodSingleUseObjectEntityDelegate",
|
||||
topLevelEntity = true,
|
||||
modelClass = "org.keycloak.models.ActionTokenValueModel"
|
||||
modelClass = "org.keycloak.models.SingleUseObjectValueModel"
|
||||
)
|
||||
@ProtoDoc("@Indexed")
|
||||
@ProtoDoc("schema-version: " + HotRodSingleUseObjectEntity.VERSION)
|
||||
|
@ -66,15 +66,6 @@ public class HotRodSingleUseObjectEntity extends AbstractHotRodEntity {
|
|||
@ProtoField(number = 3)
|
||||
public String objectKey;
|
||||
|
||||
@ProtoField(number = 4)
|
||||
public String userId;
|
||||
|
||||
@ProtoField(number = 5)
|
||||
public String actionId;
|
||||
|
||||
@ProtoField(number = 6)
|
||||
public String actionVerificationNonce;
|
||||
|
||||
@ProtoField(number = 7)
|
||||
public Long expiration;
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ import org.keycloak.common.util.StringPropertyReplacer;
|
|||
import org.keycloak.component.AmphibianProviderFactory;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
|
@ -232,7 +232,7 @@ public class JpaMapStorageProviderFactory implements
|
|||
//roles
|
||||
MODEL_TO_TX.put(RoleModel.class, JpaRoleMapKeycloakTransaction::new);
|
||||
//single-use-objects
|
||||
MODEL_TO_TX.put(ActionTokenValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
|
||||
MODEL_TO_TX.put(SingleUseObjectValueModel.class, JpaSingleUseObjectMapKeycloakTransaction::new);
|
||||
//user-login-failures
|
||||
MODEL_TO_TX.put(UserLoginFailureModel.class, JpaUserLoginFailureMapKeycloakTransaction::new);
|
||||
//users
|
||||
|
|
|
@ -21,7 +21,7 @@ import javax.persistence.criteria.CriteriaBuilder;
|
|||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.criteria.Selection;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
|
||||
import org.keycloak.models.map.storage.jpa.JpaMapKeycloakTransaction;
|
||||
|
@ -36,10 +36,10 @@ import static org.keycloak.models.map.storage.jpa.Constants.CURRENT_SCHEMA_VERSI
|
|||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, ActionTokenValueModel> {
|
||||
public class JpaSingleUseObjectMapKeycloakTransaction extends JpaMapKeycloakTransaction<JpaSingleUseObjectEntity, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
|
||||
|
||||
public JpaSingleUseObjectMapKeycloakTransaction(KeycloakSession session, final EntityManager em) {
|
||||
super(session, JpaSingleUseObjectEntity.class, ActionTokenValueModel.class, em);
|
||||
super(session, JpaSingleUseObjectEntity.class, SingleUseObjectValueModel.class, em);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,10 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.jpa.singleUseObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.map.storage.CriterionNotSupportedException;
|
||||
import org.keycloak.models.map.storage.jpa.JpaModelCriteriaBuilder;
|
||||
import org.keycloak.models.map.storage.jpa.JpaPredicateFunction;
|
||||
|
@ -31,14 +28,7 @@ import org.keycloak.storage.SearchableModelField;
|
|||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, ActionTokenValueModel, JpaSingleUseObjectModelCriteriaBuilder> {
|
||||
|
||||
private static final Map<String, String> FIELD_TO_JSON_PROP = new HashMap<>();
|
||||
static {
|
||||
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.USER_ID.getName(), "fUserId");
|
||||
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_ID.getName(), "fActionId");
|
||||
FIELD_TO_JSON_PROP.put(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE.getName(), "fActionVerificationNonce");
|
||||
}
|
||||
public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuilder<JpaSingleUseObjectEntity, SingleUseObjectValueModel, JpaSingleUseObjectModelCriteriaBuilder> {
|
||||
|
||||
public JpaSingleUseObjectModelCriteriaBuilder() {
|
||||
super(JpaSingleUseObjectModelCriteriaBuilder::new);
|
||||
|
@ -49,20 +39,10 @@ public class JpaSingleUseObjectModelCriteriaBuilder extends JpaModelCriteriaBuil
|
|||
}
|
||||
|
||||
@Override
|
||||
public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField<? super ActionTokenValueModel> modelField, Operator op, Object... value) {
|
||||
public JpaSingleUseObjectModelCriteriaBuilder compare(SearchableModelField<? super SingleUseObjectValueModel> modelField, Operator op, Object... value) {
|
||||
switch (op) {
|
||||
case EQ:
|
||||
if (modelField == ActionTokenValueModel.SearchableFields.USER_ID ||
|
||||
modelField == ActionTokenValueModel.SearchableFields.ACTION_ID ||
|
||||
modelField == ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE) {
|
||||
|
||||
validateValue(value, modelField, op, String.class);
|
||||
|
||||
return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(cb.function("->>", String.class, root.get("metadata"),
|
||||
cb.literal(FIELD_TO_JSON_PROP.get(modelField.getName()))), value[0])
|
||||
);
|
||||
} else if(modelField == ActionTokenValueModel.SearchableFields.OBJECT_KEY) {
|
||||
if(modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) {
|
||||
validateValue(value, modelField, op, String.class);
|
||||
return new JpaSingleUseObjectModelCriteriaBuilder((cb, query, root) ->
|
||||
cb.equal(root.get(modelField.getName()), value[0])
|
||||
|
|
|
@ -141,26 +141,6 @@ public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractS
|
|||
return CURRENT_SCHEMA_VERSION_SINGLE_USE_OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return this.metadata.getActionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActionId(String actionId) {
|
||||
this.metadata.setActionId(actionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionVerificationNonce() {
|
||||
return this.metadata.getActionVerificationNonce();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setActionVerificationNonce(String actionVerificationNonce) {
|
||||
this.metadata.setActionVerificationNonce(actionVerificationNonce);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getNotes() {
|
||||
return this.notes.stream()
|
||||
|
@ -192,16 +172,6 @@ public class JpaSingleUseObjectEntity extends MapSingleUseObjectEntity.AbstractS
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserId() {
|
||||
return this.metadata.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUserId(String userId) {
|
||||
this.metadata.setUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getExpiration() {
|
||||
if (this.isMetadataInitialized()) return this.metadata.getExpiration();
|
||||
|
|
|
@ -66,4 +66,8 @@ limitations under the License.
|
|||
</createIndex>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="single-use-object-13334">
|
||||
<dropIndex tableName="kc_single_use_obj" indexName="kc_single_use_obj_nonce"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
|
||||
package org.keycloak.models.map.singleUseObject;
|
||||
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
|
||||
|
@ -27,7 +26,7 @@ import java.util.Objects;
|
|||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> implements ActionTokenKeyModel, ActionTokenValueModel {
|
||||
public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> implements SingleUseObjectValueModel {
|
||||
|
||||
protected final KeycloakSession session;
|
||||
protected final E entity;
|
||||
|
@ -42,7 +41,7 @@ public abstract class AbstractSingleUseObjectModel<E extends AbstractEntity> imp
|
|||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof ActionTokenValueModel)) return false;
|
||||
if (!(o instanceof SingleUseObjectValueModel)) return false;
|
||||
|
||||
MapSingleUseObjectAdapter that = (MapSingleUseObjectAdapter) o;
|
||||
return Objects.equals(that.entity.getId(), entity.getId());
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
||||
}
|
|
@ -18,11 +18,9 @@
|
|||
package org.keycloak.models.map.singleUseObject;
|
||||
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.common.TimeAdapter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
|
@ -33,28 +31,6 @@ public class MapSingleUseObjectAdapter extends AbstractSingleUseObjectModel<MapS
|
|||
super(session, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserId() {
|
||||
return entity.getUserId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionId() {
|
||||
return entity.getActionId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getExpiration() {
|
||||
Long expiration = entity.getExpiration();
|
||||
return expiration != null ? TimeAdapter.fromLongWithTimeInSecondsToIntegerWithTimeInSeconds(TimeAdapter.fromMilliSecondsToSeconds(expiration)) : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getActionVerificationNonce() {
|
||||
String actionVerificationNonce = entity.getActionVerificationNonce();
|
||||
return actionVerificationNonce != null ? UUID.fromString(actionVerificationNonce) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getNotes() {
|
||||
Map<String, String> notes = entity.getNotes();
|
||||
|
|
|
@ -51,18 +51,9 @@ public interface MapSingleUseObjectEntity extends AbstractEntity, UpdatableEntit
|
|||
}
|
||||
}
|
||||
|
||||
String getUserId();
|
||||
void setUserId(String userId);
|
||||
|
||||
String getObjectKey();
|
||||
void setObjectKey(String objectKey);
|
||||
|
||||
String getActionId();
|
||||
void setActionId(String actionId);
|
||||
|
||||
String getActionVerificationNonce();
|
||||
void setActionVerificationNonce(String actionVerificationNonce);
|
||||
|
||||
Map<String, String> getNotes();
|
||||
void setNotes(Map<String, String> notes);
|
||||
String getNote(String name);
|
||||
|
|
|
@ -19,9 +19,7 @@ package org.keycloak.models.map.singleUseObject;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.ActionTokenStoreProvider;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelDuplicateException;
|
||||
import org.keycloak.models.SingleUseObjectProvider;
|
||||
|
@ -42,97 +40,17 @@ import static org.keycloak.models.map.storage.criteria.DefaultModelCriteria.crit
|
|||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, SingleUseObjectProvider {
|
||||
public class MapSingleUseObjectProvider implements SingleUseObjectProvider {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MapSingleUseObjectProvider.class);
|
||||
private final KeycloakSession session;
|
||||
protected final MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> actionTokenStoreTx;
|
||||
protected final MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> singleUseObjectTx;
|
||||
|
||||
public MapSingleUseObjectProvider(KeycloakSession session, MapStorage<MapSingleUseObjectEntity, ActionTokenValueModel> storage) {
|
||||
public MapSingleUseObjectProvider(KeycloakSession session, MapStorage<MapSingleUseObjectEntity, SingleUseObjectValueModel> storage) {
|
||||
this.session = session;
|
||||
actionTokenStoreTx = storage.createTransaction(session);
|
||||
singleUseObjectTx = storage.createTransaction(session);
|
||||
|
||||
session.getTransactionManager().enlistAfterCompletion(actionTokenStoreTx);
|
||||
}
|
||||
|
||||
private ActionTokenValueModel singleUseEntityToAdapter(MapSingleUseObjectEntity origEntity) {
|
||||
if (isExpired(origEntity, false)) {
|
||||
actionTokenStoreTx.delete(origEntity.getId());
|
||||
return null;
|
||||
} else {
|
||||
return new MapSingleUseObjectAdapter(session, origEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(ActionTokenKeyModel actionTokenKey, Map<String, String> notes) {
|
||||
if (actionTokenKey == null || actionTokenKey.getUserId() == null || actionTokenKey.getActionId() == null || actionTokenKey.getActionVerificationNonce() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG.tracef("put(%s, %s, %s)%s", actionTokenKey.getUserId(), actionTokenKey.getActionId(), actionTokenKey.getActionVerificationNonce(), getShortStackTrace());
|
||||
|
||||
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
|
||||
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getUserId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getActionId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, actionTokenKey.getActionVerificationNonce().toString());
|
||||
|
||||
ActionTokenValueModel existing = actionTokenStoreTx.read(withCriteria(mcb))
|
||||
.findFirst().map(this::singleUseEntityToAdapter).orElse(null);
|
||||
|
||||
if (existing == null) {
|
||||
MapSingleUseObjectEntity actionTokenStoreEntity = new MapSingleUseObjectEntityImpl();
|
||||
actionTokenStoreEntity.setUserId(actionTokenKey.getUserId());
|
||||
actionTokenStoreEntity.setActionId(actionTokenKey.getActionId());
|
||||
actionTokenStoreEntity.setActionVerificationNonce(actionTokenKey.getActionVerificationNonce().toString());
|
||||
actionTokenStoreEntity.setExpiration(TimeAdapter.fromSecondsToMilliseconds(actionTokenKey.getExpiration()));
|
||||
actionTokenStoreEntity.setNotes(notes);
|
||||
|
||||
LOG.debugf("Adding used action token to actionTokens cache: %s", actionTokenKey.toString());
|
||||
|
||||
actionTokenStoreTx.create(actionTokenStoreEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionTokenValueModel get(ActionTokenKeyModel key) {
|
||||
if (key == null || key.getUserId() == null || key.getActionId() == null || key.getActionVerificationNonce() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.tracef("get(%s, %s, %s)%s", key.getUserId(), key.getActionId(), key.getActionVerificationNonce(), getShortStackTrace());
|
||||
|
||||
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
|
||||
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, key.getUserId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, key.getActionId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, key.getActionVerificationNonce().toString());
|
||||
|
||||
return actionTokenStoreTx.read(withCriteria(mcb))
|
||||
.findFirst().map(this::singleUseEntityToAdapter).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionTokenValueModel remove(ActionTokenKeyModel key) {
|
||||
if (key == null || key.getUserId() == null || key.getActionId() == null || key.getActionVerificationNonce() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LOG.tracef("remove(%s, %s, %s)%s", key.getUserId(), key.getActionId(), key.getActionVerificationNonce(), getShortStackTrace());
|
||||
|
||||
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
|
||||
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.USER_ID, ModelCriteriaBuilder.Operator.EQ, key.getUserId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_ID, ModelCriteriaBuilder.Operator.EQ, key.getActionId())
|
||||
.compare(ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, ModelCriteriaBuilder.Operator.EQ, key.getActionVerificationNonce().toString());
|
||||
MapSingleUseObjectEntity mapSingleUseObjectEntity = actionTokenStoreTx.read(withCriteria(mcb)).findFirst().orElse(null);
|
||||
if (mapSingleUseObjectEntity != null) {
|
||||
ActionTokenValueModel actionToken = singleUseEntityToAdapter(mapSingleUseObjectEntity);
|
||||
if (actionToken != null) {
|
||||
actionTokenStoreTx.delete(mapSingleUseObjectEntity.getId());
|
||||
return actionToken;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
session.getTransactionManager().enlistAfterCompletion(singleUseObjectTx);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -150,16 +68,16 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
|||
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanSeconds));
|
||||
singleUseEntity.setNotes(notes);
|
||||
|
||||
actionTokenStoreTx.create(singleUseEntity);
|
||||
singleUseObjectTx.create(singleUseEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> get(String key) {
|
||||
LOG.tracef("get(%s)%s", key, getShortStackTrace());
|
||||
|
||||
MapSingleUseObjectEntity actionToken = getWithExpiration(key);
|
||||
if (actionToken != null) {
|
||||
Map<String, String> notes = actionToken.getNotes();
|
||||
MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
|
||||
if (singleUseObject != null) {
|
||||
Map<String, String> notes = singleUseObject.getNotes();
|
||||
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
|
||||
}
|
||||
|
||||
|
@ -174,7 +92,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
|||
|
||||
if (singleUseEntity != null) {
|
||||
Map<String, String> notes = singleUseEntity.getNotes();
|
||||
if (actionTokenStoreTx.delete(singleUseEntity.getId())) {
|
||||
if (singleUseObjectTx.delete(singleUseEntity.getId())) {
|
||||
return notes == null ? Collections.emptyMap() : Collections.unmodifiableMap(notes);
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +125,7 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
|||
singleUseEntity.setObjectKey(key);
|
||||
singleUseEntity.setExpiration(Time.currentTimeMillis() + TimeAdapter.fromSecondsToMilliseconds(lifespanInSeconds));
|
||||
|
||||
actionTokenStoreTx.create(singleUseEntity);
|
||||
singleUseObjectTx.create(singleUseEntity);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -217,9 +135,9 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
|||
public boolean contains(String key) {
|
||||
LOG.tracef("contains(%s)%s", key, getShortStackTrace());
|
||||
|
||||
MapSingleUseObjectEntity actionToken = getWithExpiration(key);
|
||||
MapSingleUseObjectEntity singleUseObject = getWithExpiration(key);
|
||||
|
||||
return actionToken != null;
|
||||
return singleUseObject != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -228,13 +146,13 @@ public class MapSingleUseObjectProvider implements ActionTokenStoreProvider, Sin
|
|||
}
|
||||
|
||||
private MapSingleUseObjectEntity getWithExpiration(String key) {
|
||||
DefaultModelCriteria<ActionTokenValueModel> mcb = criteria();
|
||||
mcb = mcb.compare(ActionTokenValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key);
|
||||
DefaultModelCriteria<SingleUseObjectValueModel> mcb = criteria();
|
||||
mcb = mcb.compare(SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, ModelCriteriaBuilder.Operator.EQ, key);
|
||||
|
||||
MapSingleUseObjectEntity singleUseEntity = actionTokenStoreTx.read(withCriteria(mcb)).findFirst().orElse(null);
|
||||
MapSingleUseObjectEntity singleUseEntity = singleUseObjectTx.read(withCriteria(mcb)).findFirst().orElse(null);
|
||||
if (singleUseEntity != null) {
|
||||
if (isExpired(singleUseEntity, false)) {
|
||||
actionTokenStoreTx.delete(singleUseEntity.getId());
|
||||
singleUseObjectTx.delete(singleUseEntity.getId());
|
||||
} else {
|
||||
return singleUseEntity;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.models.map.singleUseObject;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.SingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
||||
|
@ -25,11 +25,11 @@ import org.keycloak.models.map.common.AbstractMapProviderFactory;
|
|||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class MapSingleUseObjectProviderFactory extends AbstractMapProviderFactory<MapSingleUseObjectProvider, MapSingleUseObjectEntity, ActionTokenValueModel>
|
||||
public class MapSingleUseObjectProviderFactory extends AbstractMapProviderFactory<MapSingleUseObjectProvider, MapSingleUseObjectEntity, SingleUseObjectValueModel>
|
||||
implements SingleUseObjectProviderFactory<MapSingleUseObjectProvider> {
|
||||
|
||||
public MapSingleUseObjectProviderFactory() {
|
||||
super(ActionTokenValueModel.class, MapSingleUseObjectProvider.class);
|
||||
super(SingleUseObjectValueModel.class, MapSingleUseObjectProvider.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.keycloak.authorization.model.Resource;
|
|||
import org.keycloak.authorization.model.ResourceServer;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
|
@ -66,7 +66,7 @@ public class ModelEntityUtil {
|
|||
|
||||
private static final Map<Class<?>, String> MODEL_TO_NAME = new HashMap<>();
|
||||
static {
|
||||
MODEL_TO_NAME.put(ActionTokenValueModel.class, "single-use-objects");
|
||||
MODEL_TO_NAME.put(SingleUseObjectValueModel.class, "single-use-objects");
|
||||
MODEL_TO_NAME.put(ClientScopeModel.class, "client-scopes");
|
||||
MODEL_TO_NAME.put(ClientModel.class, "clients");
|
||||
MODEL_TO_NAME.put(GroupModel.class, "groups");
|
||||
|
@ -92,7 +92,7 @@ public class ModelEntityUtil {
|
|||
|
||||
private static final Map<Class<?>, Class<? extends AbstractEntity>> MODEL_TO_ENTITY_TYPE = new HashMap<>();
|
||||
static {
|
||||
MODEL_TO_ENTITY_TYPE.put(ActionTokenValueModel.class, MapSingleUseObjectEntity.class);
|
||||
MODEL_TO_ENTITY_TYPE.put(SingleUseObjectValueModel.class, MapSingleUseObjectEntity.class);
|
||||
MODEL_TO_ENTITY_TYPE.put(ClientScopeModel.class, MapClientScopeEntity.class);
|
||||
MODEL_TO_ENTITY_TYPE.put(ClientModel.class, MapClientEntity.class);
|
||||
MODEL_TO_ENTITY_TYPE.put(GroupModel.class, MapGroupEntity.class);
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
*/
|
||||
package org.keycloak.models.map.storage.chm;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
|
||||
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
|
||||
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntityImpl;
|
||||
|
@ -35,10 +35,8 @@ import org.keycloak.component.AmphibianProviderFactory;
|
|||
import org.keycloak.Config.Scope;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.ComponentModelScope;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.map.client.MapClientEntityImpl;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntityImpl;
|
||||
|
@ -240,7 +238,7 @@ public class ConcurrentHashMapStorageProviderFactory implements AmphibianProvide
|
|||
LOG.debugf("Initializing new map storage: %s", mapName);
|
||||
|
||||
ConcurrentHashMapStorage<K, V, M> store;
|
||||
if(modelType == ActionTokenValueModel.class) {
|
||||
if(modelType == SingleUseObjectValueModel.class) {
|
||||
store = new SingleUseObjectConcurrentHashMapStorage(kc, CLONER) {
|
||||
@Override
|
||||
public String toString() {
|
||||
|
|
|
@ -23,7 +23,7 @@ import org.keycloak.authorization.model.ResourceServer;
|
|||
import org.keycloak.authorization.model.Scope;
|
||||
import org.keycloak.events.Event;
|
||||
import org.keycloak.events.admin.AdminEvent;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
|
@ -98,7 +98,7 @@ public class MapFieldPredicates {
|
|||
public static final Map<SearchableModelField<UserSessionModel>, UpdatePredicatesFunc<Object, MapUserSessionEntity, UserSessionModel>> USER_SESSION_PREDICATES = basePredicates(UserSessionModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<Event>, UpdatePredicatesFunc<Object, MapAuthEventEntity, Event>> AUTH_EVENTS_PREDICATES = basePredicates(Event.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<AdminEvent>, UpdatePredicatesFunc<Object, MapAdminEventEntity, AdminEvent>> ADMIN_EVENTS_PREDICATES = basePredicates(AdminEvent.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<ActionTokenValueModel>, UpdatePredicatesFunc<Object, MapSingleUseObjectEntity, ActionTokenValueModel>> ACTION_TOKEN_PREDICATES = basePredicates(ActionTokenValueModel.SearchableFields.ID);
|
||||
public static final Map<SearchableModelField<SingleUseObjectValueModel>, UpdatePredicatesFunc<Object, MapSingleUseObjectEntity, SingleUseObjectValueModel>> ACTION_TOKEN_PREDICATES = basePredicates(SingleUseObjectValueModel.SearchableFields.ID);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static final Map<Class<?>, Map> PREDICATES = new HashMap<>();
|
||||
|
@ -221,10 +221,7 @@ public class MapFieldPredicates {
|
|||
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_TYPE, MapAdminEventEntity::getResourceType);
|
||||
put(ADMIN_EVENTS_PREDICATES, AdminEvent.SearchableFields.RESOURCE_PATH, MapAdminEventEntity::getResourcePath);
|
||||
|
||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.USER_ID, MapSingleUseObjectEntity::getUserId);
|
||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_ID, MapSingleUseObjectEntity::getActionId);
|
||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE, MapSingleUseObjectEntity::getActionVerificationNonce);
|
||||
put(ACTION_TOKEN_PREDICATES, ActionTokenValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
|
||||
put(ACTION_TOKEN_PREDICATES, SingleUseObjectValueModel.SearchableFields.OBJECT_KEY, MapSingleUseObjectEntity::getObjectKey);
|
||||
}
|
||||
|
||||
static {
|
||||
|
@ -244,7 +241,7 @@ public class MapFieldPredicates {
|
|||
PREDICATES.put(UserLoginFailureModel.class, USER_LOGIN_FAILURE_PREDICATES);
|
||||
PREDICATES.put(Event.class, AUTH_EVENTS_PREDICATES);
|
||||
PREDICATES.put(AdminEvent.class, ADMIN_EVENTS_PREDICATES);
|
||||
PREDICATES.put(ActionTokenValueModel.class, ACTION_TOKEN_PREDICATES);
|
||||
PREDICATES.put(SingleUseObjectValueModel.class, ACTION_TOKEN_PREDICATES);
|
||||
}
|
||||
|
||||
private static <K, V extends AbstractEntity, M, L extends Comparable<L>> void put(
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.models.map.storage.chm;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.map.common.AbstractEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
|
@ -32,31 +32,28 @@ import java.util.stream.Stream;
|
|||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity, M> extends ConcurrentHashMapStorage<K, MapSingleUseObjectEntity, ActionTokenValueModel> {
|
||||
public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity, M> extends ConcurrentHashMapStorage<K, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
|
||||
|
||||
public SingleUseObjectConcurrentHashMapStorage(StringKeyConverter<K> keyConverter, DeepCloner cloner) {
|
||||
super(ActionTokenValueModel.class, keyConverter, cloner);
|
||||
super(SingleUseObjectValueModel.class, keyConverter, cloner);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> createTransaction(KeycloakSession session) {
|
||||
MapKeycloakTransaction<MapSingleUseObjectEntity, ActionTokenValueModel> actionTokenTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
|
||||
public MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> createTransaction(KeycloakSession session) {
|
||||
MapKeycloakTransaction<MapSingleUseObjectEntity, SingleUseObjectValueModel> singleUseObjectTransaction = session.getAttribute("map-transaction-" + hashCode(), MapKeycloakTransaction.class);
|
||||
|
||||
if (actionTokenTransaction == null) {
|
||||
actionTokenTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
|
||||
session.setAttribute("map-transaction-" + hashCode(), actionTokenTransaction);
|
||||
if (singleUseObjectTransaction == null) {
|
||||
singleUseObjectTransaction = new SingleUseObjectKeycloakTransaction(this, keyConverter, cloner, fieldPredicates);
|
||||
session.setAttribute("map-transaction-" + hashCode(), singleUseObjectTransaction);
|
||||
}
|
||||
|
||||
return actionTokenTransaction;
|
||||
return singleUseObjectTransaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
|
||||
if (value.getId() == null) {
|
||||
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
|
||||
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
|
||||
}
|
||||
if (value.getObjectKey() != null) {
|
||||
value.setId(value.getObjectKey());
|
||||
}
|
||||
|
@ -65,8 +62,8 @@ public class SingleUseObjectConcurrentHashMapStorage<K, V extends AbstractEntity
|
|||
}
|
||||
|
||||
@Override
|
||||
public Stream<MapSingleUseObjectEntity> read(QueryParameters<ActionTokenValueModel> queryParameters) {
|
||||
DefaultModelCriteria<ActionTokenValueModel> criteria = queryParameters.getModelCriteriaBuilder();
|
||||
public Stream<MapSingleUseObjectEntity> read(QueryParameters<SingleUseObjectValueModel> queryParameters) {
|
||||
DefaultModelCriteria<SingleUseObjectValueModel> criteria = queryParameters.getModelCriteriaBuilder();
|
||||
|
||||
if (criteria == null) {
|
||||
return Stream.empty();
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.models.map.storage.chm;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.common.StringKeyConverter;
|
||||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectEntity;
|
||||
|
@ -28,22 +28,19 @@ import java.util.Map;
|
|||
/**
|
||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||
*/
|
||||
public class SingleUseObjectKeycloakTransaction<K> extends ConcurrentHashMapKeycloakTransaction<K, MapSingleUseObjectEntity, ActionTokenValueModel> {
|
||||
public class SingleUseObjectKeycloakTransaction<K> extends ConcurrentHashMapKeycloakTransaction<K, MapSingleUseObjectEntity, SingleUseObjectValueModel> {
|
||||
|
||||
public SingleUseObjectKeycloakTransaction(ConcurrentHashMapCrudOperations<MapSingleUseObjectEntity, ActionTokenValueModel> map,
|
||||
public SingleUseObjectKeycloakTransaction(ConcurrentHashMapCrudOperations<MapSingleUseObjectEntity, SingleUseObjectValueModel> map,
|
||||
StringKeyConverter<K> keyConverter,
|
||||
DeepCloner cloner,
|
||||
Map<SearchableModelField<? super ActionTokenValueModel>,
|
||||
MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, ActionTokenValueModel>> fieldPredicates) {
|
||||
Map<SearchableModelField<? super SingleUseObjectValueModel>,
|
||||
MapModelCriteriaBuilder.UpdatePredicatesFunc<K, MapSingleUseObjectEntity, SingleUseObjectValueModel>> fieldPredicates) {
|
||||
super(map, keyConverter, cloner, fieldPredicates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapSingleUseObjectEntity create(MapSingleUseObjectEntity value) {
|
||||
if (value.getId() == null) {
|
||||
if (value.getUserId() != null && value.getActionId() != null && value.getActionVerificationNonce() != null) {
|
||||
value.setId(value.getUserId() + ":" + value.getActionId() + ":" + value.getActionVerificationNonce());
|
||||
}
|
||||
if (value.getObjectKey() != null) {
|
||||
value.setId(value.getObjectKey());
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.models.map.storage.chm;
|
||||
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.map.storage.ModelCriteriaBuilder;
|
||||
import org.keycloak.storage.SearchableModelField;
|
||||
|
||||
|
@ -26,61 +26,34 @@ import org.keycloak.storage.SearchableModelField;
|
|||
*/
|
||||
public class SingleUseObjectModelCriteriaBuilder implements ModelCriteriaBuilder {
|
||||
|
||||
private String userId;
|
||||
|
||||
private String actionId;
|
||||
|
||||
private String actionVerificationNonce;
|
||||
|
||||
private String objectKey;
|
||||
|
||||
public SingleUseObjectModelCriteriaBuilder() {
|
||||
}
|
||||
|
||||
public SingleUseObjectModelCriteriaBuilder(String userId, String actionId, String actionVerificationNonce, String objectKey) {
|
||||
this.userId = userId;
|
||||
this.actionId = actionId;
|
||||
this.actionVerificationNonce = actionVerificationNonce;
|
||||
public SingleUseObjectModelCriteriaBuilder(String objectKey) {
|
||||
this.objectKey = objectKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelCriteriaBuilder compare(SearchableModelField modelField, Operator op, Object... value) {
|
||||
if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.USER_ID) {
|
||||
userId = value[0].toString();
|
||||
} else if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.ACTION_ID) {
|
||||
actionId = value[0].toString();
|
||||
} else if (modelField == org.keycloak.models.ActionTokenValueModel.SearchableFields.ACTION_VERIFICATION_NONCE) {
|
||||
actionVerificationNonce = value[0].toString();
|
||||
} else if (modelField == ActionTokenValueModel.SearchableFields.OBJECT_KEY) {
|
||||
if (modelField == SingleUseObjectValueModel.SearchableFields.OBJECT_KEY) {
|
||||
objectKey = value[0].toString();
|
||||
}
|
||||
return new SingleUseObjectModelCriteriaBuilder(userId, actionId, actionVerificationNonce, objectKey);
|
||||
return new SingleUseObjectModelCriteriaBuilder(objectKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelCriteriaBuilder and(ModelCriteriaBuilder[] builders) {
|
||||
String userId = null;
|
||||
String actionId = null;
|
||||
String actionVerificationNonce = null;
|
||||
String objectKey = null;
|
||||
|
||||
for (ModelCriteriaBuilder builder: builders) {
|
||||
SingleUseObjectModelCriteriaBuilder suoMcb = (SingleUseObjectModelCriteriaBuilder) builder;
|
||||
if (suoMcb.userId != null) {
|
||||
userId = suoMcb.userId;
|
||||
}
|
||||
if (suoMcb.actionId != null) {
|
||||
actionId = suoMcb.actionId;
|
||||
}
|
||||
if (suoMcb.actionVerificationNonce != null) {
|
||||
actionVerificationNonce = suoMcb.actionVerificationNonce;
|
||||
}
|
||||
if (suoMcb.objectKey != null) {
|
||||
objectKey = suoMcb.objectKey;
|
||||
}
|
||||
}
|
||||
return new SingleUseObjectModelCriteriaBuilder(userId, actionId, actionVerificationNonce, objectKey);
|
||||
return new SingleUseObjectModelCriteriaBuilder(objectKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,11 +67,10 @@ public class SingleUseObjectModelCriteriaBuilder implements ModelCriteriaBuilder
|
|||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return (userId != null && actionId != null && actionVerificationNonce != null) || objectKey != null;
|
||||
return objectKey != null;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
if (objectKey != null) return objectKey;
|
||||
return userId + ":" + actionId + ":" + actionVerificationNonce;
|
||||
return objectKey;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
@ -217,18 +217,6 @@ public class StorageOptions {
|
|||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<String> STORAGE_ACTION_TOKEN_PROVIDER = new OptionBuilder<>("storage-action-token-provider", String.class)
|
||||
.category(OptionCategory.STORAGE)
|
||||
.hidden()
|
||||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<StorageType> STORAGE_ACTION_TOKEN_STORE = new OptionBuilder<>("storage-area-action-token", StorageType.class)
|
||||
.category(OptionCategory.STORAGE)
|
||||
.description(descriptionForStorageAreas("action tokens"))
|
||||
.buildTime(true)
|
||||
.build();
|
||||
|
||||
public static final Option<String> STORAGE_DBLOCK = new OptionBuilder<>("storage-dblock", String.class)
|
||||
.category(OptionCategory.STORAGE)
|
||||
.hidden()
|
||||
|
|
|
@ -22,8 +22,6 @@ import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.
|
|||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.SecretGenerator;
|
||||
import org.keycloak.config.StorageOptions;
|
||||
import org.keycloak.config.StorageOptions.StorageType;
|
||||
|
||||
|
@ -197,18 +195,6 @@ final class StoragePropertyMappers {
|
|||
.transformer(StoragePropertyMappers::resolveMapStorageProvider)
|
||||
.paramLabel("type")
|
||||
.build(),
|
||||
fromOption(StorageOptions.STORAGE_ACTION_TOKEN_PROVIDER)
|
||||
.to("kc.spi-action-token-provider")
|
||||
.mapFrom("storage")
|
||||
.transformer(StoragePropertyMappers::getCacheStorage)
|
||||
.paramLabel("type")
|
||||
.build(),
|
||||
fromOption(StorageOptions.STORAGE_ACTION_TOKEN_STORE)
|
||||
.to("kc.spi-action-token-map-storage-provider")
|
||||
.mapFrom("storage")
|
||||
.transformer(StoragePropertyMappers::resolveMapStorageProvider)
|
||||
.paramLabel("type")
|
||||
.build(),
|
||||
fromOption(StorageOptions.STORAGE_DBLOCK)
|
||||
.to("kc.spi-dblock-provider")
|
||||
.mapFrom("storage")
|
||||
|
|
|
@ -32,9 +32,6 @@ Storage (Experimental):
|
|||
|
||||
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
--storage-area-action-token <type>
|
||||
Experimental: Sets a storage mechanism for action tokens. Possible values are:
|
||||
jpa, chm, hotrod.
|
||||
--storage-area-auth-session <type>
|
||||
Experimental: Sets a storage mechanism for authentication sessions. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
|
|
|
@ -32,9 +32,6 @@ Storage (Experimental):
|
|||
|
||||
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
--storage-area-action-token <type>
|
||||
Experimental: Sets a storage mechanism for action tokens. Possible values are:
|
||||
jpa, chm, hotrod.
|
||||
--storage-area-auth-session <type>
|
||||
Experimental: Sets a storage mechanism for authentication sessions. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
|
|
|
@ -38,9 +38,6 @@ Storage (Experimental):
|
|||
|
||||
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
--storage-area-action-token <type>
|
||||
Experimental: Sets a storage mechanism for action tokens. Possible values are:
|
||||
jpa, chm, hotrod.
|
||||
--storage-area-auth-session <type>
|
||||
Experimental: Sets a storage mechanism for authentication sessions. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
|
|
|
@ -38,9 +38,6 @@ Storage (Experimental):
|
|||
|
||||
--storage <type> Experimental: Sets the default storage mechanism for all areas. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
--storage-area-action-token <type>
|
||||
Experimental: Sets a storage mechanism for action tokens. Possible values are:
|
||||
jpa, chm, hotrod.
|
||||
--storage-area-auth-session <type>
|
||||
Experimental: Sets a storage mechanism for authentication sessions. Possible
|
||||
values are: jpa, chm, hotrod.
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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> {
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -22,7 +22,7 @@ import org.keycloak.provider.Provider;
|
|||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Provides a cache to store data for single-use use case. Data are represented by a {@code String} key.
|
||||
* Provides a cache to store data for single-use use case or the details about used action tokens.
|
||||
*
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
|
|
@ -23,7 +23,6 @@ org.keycloak.models.GroupSpi
|
|||
org.keycloak.models.RealmSpi
|
||||
org.keycloak.models.RoleSpi
|
||||
org.keycloak.models.DeploymentStateSpi
|
||||
org.keycloak.models.ActionTokenStoreSpi
|
||||
org.keycloak.models.OAuth2DeviceUserCodeSpi
|
||||
org.keycloak.models.SingleUseObjectSpi
|
||||
org.keycloak.models.UserSessionSpi
|
||||
|
|
|
@ -14,10 +14,9 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.authentication.actiontoken;
|
||||
package org.keycloak.models;
|
||||
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.representations.JsonWebToken;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
@ -30,7 +29,7 @@ import java.util.regex.Pattern;
|
|||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class DefaultActionTokenKey extends JsonWebToken implements ActionTokenKeyModel {
|
||||
public class DefaultActionTokenKey extends JsonWebToken implements SingleUseObjectKeyModel {
|
||||
|
||||
/** The authenticationSession note with ID of the user authenticated via the action token */
|
||||
public static final String ACTION_TOKEN_USER_ID = "ACTION_TOKEN_USER";
|
|
@ -24,7 +24,7 @@ import java.util.UUID;
|
|||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public interface ActionTokenKeyModel {
|
||||
public interface SingleUseObjectKeyModel {
|
||||
|
||||
/**
|
||||
* @return ID of user which this token is for.
|
|
@ -24,14 +24,11 @@ import java.util.Map;
|
|||
* This model represents contents of an action token shareable among Keycloak instances in the cluster.
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public interface ActionTokenValueModel {
|
||||
public interface SingleUseObjectValueModel {
|
||||
|
||||
class SearchableFields {
|
||||
public static final SearchableModelField<ActionTokenValueModel> ID = new SearchableModelField<>("id", String.class);
|
||||
public static final SearchableModelField<ActionTokenValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class);
|
||||
public static final SearchableModelField<ActionTokenValueModel> USER_ID = new SearchableModelField<>("userId", String.class);
|
||||
public static final SearchableModelField<ActionTokenValueModel> ACTION_ID = new SearchableModelField<>("actionId", String.class);
|
||||
public static final SearchableModelField<ActionTokenValueModel> ACTION_VERIFICATION_NONCE = new SearchableModelField<>("actionVerificationNonce", String.class);
|
||||
public static final SearchableModelField<SingleUseObjectValueModel> ID = new SearchableModelField<>("id", String.class);
|
||||
public static final SearchableModelField<SingleUseObjectValueModel> OBJECT_KEY = new SearchableModelField<>("objectKey", String.class);
|
||||
}
|
||||
|
||||
/**
|
|
@ -22,7 +22,8 @@ import com.fasterxml.jackson.annotation.JsonProperty;
|
|||
import org.keycloak.TokenVerifier.Predicate;
|
||||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.SingleUseObjectValueModel;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.Urls;
|
||||
|
@ -38,7 +39,7 @@ import java.util.UUID;
|
|||
*
|
||||
* @author hmlnarik
|
||||
*/
|
||||
public class DefaultActionToken extends DefaultActionTokenKey implements ActionTokenValueModel {
|
||||
public class DefaultActionToken extends DefaultActionTokenKey implements SingleUseObjectValueModel {
|
||||
|
||||
public static final String JSON_FIELD_AUTHENTICATION_SESSION_ID = "asid";
|
||||
public static final String JSON_FIELD_EMAIL = "eml";
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.authentication.authenticators.resetcred;
|
||||
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.*;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package org.keycloak.authentication.authenticators.resetcred;
|
||||
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.authentication.*;
|
||||
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionToken;
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.keycloak.authentication.RequiredActionContext;
|
|||
import org.keycloak.authentication.RequiredActionContextResult;
|
||||
import org.keycloak.authentication.RequiredActionFactory;
|
||||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
|
||||
import org.keycloak.authentication.authenticators.browser.AbstractUsernameFormAuthenticator;
|
||||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.common.ClientConnection;
|
||||
|
@ -47,16 +46,17 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.ActionTokenStoreProvider;
|
||||
import org.keycloak.models.SingleUseObjectKeyModel;
|
||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.ClientSessionContext;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
import org.keycloak.models.SingleUseObjectProvider;
|
||||
import org.keycloak.models.UserConsentModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
|
@ -1037,11 +1037,10 @@ public class AuthenticationManager {
|
|||
ClientConnection clientConnection, HttpRequest request, UriInfo uriInfo, EventBuilder event) {
|
||||
String actionTokenKeyToInvalidate = authSession.getAuthNote(INVALIDATE_ACTION_TOKEN);
|
||||
if (actionTokenKeyToInvalidate != null) {
|
||||
ActionTokenKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate);
|
||||
|
||||
SingleUseObjectKeyModel actionTokenKey = DefaultActionTokenKey.from(actionTokenKeyToInvalidate);
|
||||
if (actionTokenKey != null) {
|
||||
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
|
||||
actionTokenStore.put(actionTokenKey, null); // Token is invalidated
|
||||
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||
singleUseObjectProvider.put(actionTokenKeyToInvalidate, actionTokenKey.getExpiration() - Time.currentTime(), null); // Token is invalidated
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ import org.keycloak.authentication.RequiredActionFactory;
|
|||
import org.keycloak.authentication.RequiredActionProvider;
|
||||
import org.keycloak.authentication.actiontoken.ActionTokenContext;
|
||||
import org.keycloak.authentication.actiontoken.ActionTokenHandler;
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationException;
|
||||
import org.keycloak.authentication.actiontoken.resetcred.ResetCredentialsActionTokenHandler;
|
||||
import org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator;
|
||||
|
@ -47,9 +47,7 @@ import org.keycloak.events.Errors;
|
|||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.exceptions.TokenNotActiveException;
|
||||
import org.keycloak.locale.LocaleSelectorProvider;
|
||||
import org.keycloak.locale.LocaleUpdaterProvider;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.SingleUseObjectKeyModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
|
@ -79,7 +77,6 @@ import org.keycloak.services.managers.AuthenticationManager;
|
|||
import org.keycloak.services.managers.AuthenticationSessionManager;
|
||||
import org.keycloak.services.managers.ClientSessionCode;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.util.AuthenticationFlowURLHelper;
|
||||
import org.keycloak.services.util.BrowserHistoryHelper;
|
||||
import org.keycloak.services.util.CacheControlUtil;
|
||||
|
@ -461,7 +458,7 @@ public class LoginActionsService {
|
|||
return handleActionToken(key, execution, clientId, tabId);
|
||||
}
|
||||
|
||||
protected <T extends JsonWebToken & ActionTokenKeyModel> Response handleActionToken(String tokenString, String execution, String clientId, String tabId) {
|
||||
protected <T extends JsonWebToken & SingleUseObjectKeyModel> Response handleActionToken(String tokenString, String execution, String clientId, String tabId) {
|
||||
T token;
|
||||
ActionTokenHandler<T> handler;
|
||||
ActionTokenContext<T> tokenContext;
|
||||
|
|
|
@ -24,12 +24,12 @@ import org.keycloak.authentication.actiontoken.ExplainedTokenVerificationExcepti
|
|||
import org.keycloak.common.VerificationException;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.forms.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.ActionTokenStoreProvider;
|
||||
import org.keycloak.models.SingleUseObjectKeyModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.SingleUseObjectProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
|
@ -165,7 +165,7 @@ public class LoginActionsServiceChecks {
|
|||
* Verifies whether the user given by ID both exists in the current realm. If yes,
|
||||
* it optionally also injects the user using the given function (e.g. into session context).
|
||||
*/
|
||||
public static <T extends JsonWebToken & ActionTokenKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context) throws VerificationException {
|
||||
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkIsUserValid(T token, ActionTokenContext<T> context) throws VerificationException {
|
||||
try {
|
||||
checkIsUserValid(context.getSession(), context.getRealm(), token.getUserId(), context.getAuthenticationSession()::setAuthenticatedUser);
|
||||
} catch (ExplainedVerificationException ex) {
|
||||
|
@ -299,10 +299,10 @@ public class LoginActionsServiceChecks {
|
|||
return true;
|
||||
}
|
||||
|
||||
public static <T extends JsonWebToken & ActionTokenKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException {
|
||||
ActionTokenStoreProvider actionTokenStore = context.getSession().getProvider(ActionTokenStoreProvider.class);
|
||||
public static <T extends JsonWebToken & SingleUseObjectKeyModel> void checkTokenWasNotUsedYet(T token, ActionTokenContext<T> context) throws VerificationException {
|
||||
SingleUseObjectProvider singleUseObjectProvider = context.getSession().getProvider(SingleUseObjectProvider.class);
|
||||
|
||||
if (actionTokenStore.get(token) != null) {
|
||||
if (singleUseObjectProvider.get(token.serializeKey()) != null) {
|
||||
throw new ExplainedTokenVerificationException(token, Errors.EXPIRED_CODE, Messages.EXPIRED_ACTION);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -811,7 +811,6 @@
|
|||
<keycloak.loginFailure.provider>map</keycloak.loginFailure.provider>
|
||||
<keycloak.authorization.provider>map</keycloak.authorization.provider>
|
||||
<keycloak.eventsStore.provider>map</keycloak.eventsStore.provider>
|
||||
<keycloak.actionToken.provider>map</keycloak.actionToken.provider>
|
||||
<keycloak.singleUseObject.provider>map</keycloak.singleUseObject.provider>
|
||||
<keycloak.publicKeyStorage.provider>map</keycloak.publicKeyStorage.provider>
|
||||
<keycloak.authorizationCache.enabled>false</keycloak.authorizationCache.enabled>
|
||||
|
@ -865,7 +864,6 @@
|
|||
<keycloak.loginFailure.map.storage.provider>jpa</keycloak.loginFailure.map.storage.provider>
|
||||
<keycloak.realm.map.storage.provider>jpa</keycloak.realm.map.storage.provider>
|
||||
<keycloak.role.map.storage.provider>jpa</keycloak.role.map.storage.provider>
|
||||
<keycloak.actionToken.map.storage.provider>jpa</keycloak.actionToken.map.storage.provider>
|
||||
<keycloak.singleUseObject.map.storage.provider>jpa</keycloak.singleUseObject.map.storage.provider>
|
||||
<keycloak.user.map.storage.provider>jpa</keycloak.user.map.storage.provider>
|
||||
<keycloak.userSession.map.storage.provider>jpa</keycloak.userSession.map.storage.provider>
|
||||
|
|
|
@ -139,15 +139,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
"actionToken": {
|
||||
"provider": "${keycloak.actionToken.provider:infinispan}",
|
||||
"map": {
|
||||
"storage": {
|
||||
"provider": "${keycloak.actionToken.map.storage.provider:concurrenthashmap}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"singleUseObject": {
|
||||
"provider": "${keycloak.singleUseObject.provider:infinispan}",
|
||||
"map": {
|
||||
|
|
|
@ -1360,7 +1360,6 @@
|
|||
<keycloak.userSession.map.storage.provider>hotrod</keycloak.userSession.map.storage.provider>
|
||||
<keycloak.authorization.map.storage.provider>hotrod</keycloak.authorization.map.storage.provider>
|
||||
<keycloak.eventStore.map.storage.provider>hotrod</keycloak.eventStore.map.storage.provider>
|
||||
<keycloak.actionToken.map.storage.provider>hotrod</keycloak.actionToken.map.storage.provider>
|
||||
<keycloak.singleUseObject.map.storage.provider>hotrod</keycloak.singleUseObject.map.storage.provider>
|
||||
<infinispan.version>${infinispan.version}</infinispan.version>
|
||||
<keycloak.testsuite.start-hotrod-container>${keycloak.testsuite.start-hotrod-container}</keycloak.testsuite.start-hotrod-container>
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet;
|
|||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
import org.keycloak.models.UserLoginFailureSpi;
|
||||
|
@ -81,7 +80,6 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
|||
@Override
|
||||
public void updateConfig(Config cf) {
|
||||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -22,13 +22,11 @@ import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
|
|||
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||
import org.keycloak.keys.infinispan.InfinispanCachePublicKeyProviderFactory;
|
||||
import org.keycloak.keys.infinispan.InfinispanPublicKeyStorageProviderFactory;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
import org.keycloak.models.cache.authorization.CachedStoreFactorySpi;
|
||||
import org.keycloak.models.cache.infinispan.authorization.InfinispanCacheStoreFactoryProviderFactory;
|
||||
import org.keycloak.models.cache.CachePublicKeyProviderSpi;
|
||||
import org.keycloak.models.session.UserSessionPersisterSpi;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanActionTokenStoreProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.sessions.infinispan.InfinispanUserLoginFailureProviderFactory;
|
||||
|
@ -68,7 +66,6 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
.add(InfinispanConnectionSpi.class)
|
||||
.add(StickySessionEncoderSpi.class)
|
||||
.add(UserSessionPersisterSpi.class)
|
||||
.add(ActionTokenStoreSpi.class)
|
||||
.add(SingleUseObjectSpi.class)
|
||||
.add(PublicKeyStorageSpi.class)
|
||||
.add(CachePublicKeyProviderSpi.class)
|
||||
|
@ -86,7 +83,6 @@ public class Infinispan extends KeycloakModelParameters {
|
|||
.add(InfinispanUserCacheProviderFactory.class)
|
||||
.add(InfinispanUserSessionProviderFactory.class)
|
||||
.add(InfinispanUserLoginFailureProviderFactory.class)
|
||||
.add(InfinispanActionTokenStoreProviderFactory.class)
|
||||
.add(InfinispanSingleUseObjectProviderFactory.class)
|
||||
.add(StickySessionEncoderProviderFactory.class)
|
||||
.add(TimerProviderFactory.class)
|
||||
|
|
|
@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet;
|
|||
import java.util.Set;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
|
@ -99,7 +98,6 @@ public class JpaMapStorage extends KeycloakModelParameters {
|
|||
.spi("user").provider(MapUserProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserLoginFailureSpi.NAME).provider(MapUserLoginFailureProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("dblock").provider(NoLockingDBLockProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).provider(MapSingleUseObjectProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").provider(MapPublicKeyStorageProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(UserSessionSpi.NAME).provider(MapUserSessionProviderFactory.PROVIDER_ID) .config(STORAGE_CONFIG, JpaMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.junit.runner.Description;
|
|||
import org.junit.runners.model.Statement;
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
|
@ -103,7 +102,6 @@ public class LdapMapStorage extends KeycloakModelParameters {
|
|||
.spi("authenticationSessions").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(EventStoreSpi.NAME).config("map.storage-admin-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(EventStoreSpi.NAME).config("map.storage-auth-events.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("publicKeyStorage").config("map.storage.provider", ConcurrentHashMapStorageProviderFactory.PROVIDER_ID);
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@ package org.keycloak.testsuite.model.parameters;
|
|||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||
import org.keycloak.models.ActionTokenStoreProviderFactory;
|
||||
import org.keycloak.models.ActionTokenStoreSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
import org.keycloak.models.SingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.SingleUseObjectSpi;
|
||||
|
@ -59,7 +57,6 @@ public class Map extends KeycloakModelParameters {
|
|||
|
||||
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(AuthenticationSessionSpi.class)
|
||||
.add(ActionTokenStoreSpi.class)
|
||||
.add(SingleUseObjectSpi.class)
|
||||
.add(PublicKeyStorageSpi.class)
|
||||
.add(MapStorageSpi.class)
|
||||
|
@ -80,7 +77,6 @@ public class Map extends KeycloakModelParameters {
|
|||
.add(MapUserLoginFailureProviderFactory.class)
|
||||
.add(NoLockingDBLockProviderFactory.class)
|
||||
.add(MapEventStoreProviderFactory.class)
|
||||
.add(ActionTokenStoreProviderFactory.class)
|
||||
.add(SingleUseObjectProviderFactory.class)
|
||||
.add(MapPublicKeyStorageProviderFactory.class)
|
||||
.build();
|
||||
|
@ -92,7 +88,6 @@ public class Map extends KeycloakModelParameters {
|
|||
@Override
|
||||
public void updateConfig(Config cf) {
|
||||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).defaultProvider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID)
|
||||
.spi(ActionTokenStoreSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
|
||||
.spi(SingleUseObjectSpi.NAME).defaultProvider(MapSingleUseObjectProviderFactory.PROVIDER_ID)
|
||||
.spi("client").defaultProvider(MapClientProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").defaultProvider(MapClientScopeProviderFactory.PROVIDER_ID)
|
||||
|
|
|
@ -20,26 +20,28 @@ package org.keycloak.testsuite.model.singleUseObject;
|
|||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.authentication.actiontoken.DefaultActionTokenKey;
|
||||
import org.keycloak.models.DefaultActionTokenKey;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.models.ActionTokenKeyModel;
|
||||
import org.keycloak.models.ActionTokenStoreProvider;
|
||||
import org.keycloak.models.ActionTokenValueModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.SingleUseObjectProvider;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
|
||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||
import org.keycloak.testsuite.model.RequireProvider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
|
||||
@RequireProvider(ActionTokenStoreProvider.class)
|
||||
@RequireProvider(SingleUseObjectProvider.class)
|
||||
public class SingleUseObjectModelTest extends KeycloakModelTest {
|
||||
|
||||
|
@ -64,46 +66,47 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
|||
|
||||
@Test
|
||||
public void testActionTokens() {
|
||||
ActionTokenKeyModel key = withRealm(realmId, (session, realm) -> {
|
||||
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
|
||||
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), Time.currentTime() + 60, null);
|
||||
DefaultActionTokenKey key = withRealm(realmId, (session, realm) -> {
|
||||
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||
int time = Time.currentTime();
|
||||
DefaultActionTokenKey actionTokenKey = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), time + 60, null);
|
||||
Map<String, String> notes = new HashMap<>();
|
||||
notes.put("foo", "bar");
|
||||
actionTokenStore.put(actionTokenKey, notes);
|
||||
singleUseObjectProvider.put(actionTokenKey.serializeKey(), actionTokenKey.getExp() - time, notes);
|
||||
return actionTokenKey;
|
||||
});
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
|
||||
ActionTokenValueModel valueModel = actionTokenStore.get(key);
|
||||
Assert.assertNotNull(valueModel);
|
||||
Assert.assertEquals("bar", valueModel.getNote("foo"));
|
||||
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
||||
Assert.assertNotNull(notes);
|
||||
Assert.assertEquals("bar", notes.get("foo"));
|
||||
|
||||
valueModel = actionTokenStore.remove(key);
|
||||
Assert.assertNotNull(valueModel);
|
||||
Assert.assertEquals("bar", valueModel.getNote("foo"));
|
||||
notes = singleUseObjectProvider.remove(key.serializeKey());
|
||||
Assert.assertNotNull(notes);
|
||||
Assert.assertEquals("bar", notes.get("foo"));
|
||||
});
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
|
||||
ActionTokenValueModel valueModel = actionTokenStore.get(key);
|
||||
Assert.assertNull(valueModel);
|
||||
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
||||
Assert.assertNull(notes);
|
||||
|
||||
Map<String, String> notes = new HashMap<>();
|
||||
notes = new HashMap<>();
|
||||
notes.put("foo", "bar");
|
||||
actionTokenStore.put(key, notes);
|
||||
singleUseObjectProvider.put(key.serializeKey(), key.getExp() - Time.currentTime(), notes);
|
||||
});
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
ActionTokenStoreProvider actionTokenStore = session.getProvider(ActionTokenStoreProvider.class);
|
||||
ActionTokenValueModel valueModel = actionTokenStore.get(key);
|
||||
Assert.assertNotNull(valueModel);
|
||||
Assert.assertEquals("bar", valueModel.getNote("foo"));
|
||||
SingleUseObjectProvider singleUseObjectProvider = session.getProvider(SingleUseObjectProvider.class);
|
||||
Map<String, String> notes = singleUseObjectProvider.get(key.serializeKey());
|
||||
Assert.assertNotNull(notes);
|
||||
Assert.assertEquals("bar", notes.get("foo"));
|
||||
|
||||
Time.setOffset(70);
|
||||
|
||||
valueModel = actionTokenStore.get(key);
|
||||
Assert.assertNull(valueModel);
|
||||
notes = singleUseObjectProvider.get(key.serializeKey());
|
||||
Assert.assertNull(notes);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -156,4 +159,89 @@ public class SingleUseObjectModelTest extends KeycloakModelTest {
|
|||
Assert.assertNull(singleUseStore.get(key));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCluster() throws InterruptedException {
|
||||
// Skip the test if MapUserSessionProvider == CHM
|
||||
String usProvider = CONFIG.getConfig().get("userSessions.provider");
|
||||
String usMapStorageProvider = CONFIG.getConfig().get("userSessions.map.storage.provider");
|
||||
assumeFalse(MapUserSessionProviderFactory.PROVIDER_ID.equals(usProvider) &&
|
||||
(usMapStorageProvider == null || ConcurrentHashMapStorageProviderFactory.PROVIDER_ID.equals(usMapStorageProvider)));
|
||||
|
||||
AtomicInteger index = new AtomicInteger();
|
||||
CountDownLatch afterFirstNodeLatch = new CountDownLatch(1);
|
||||
CountDownLatch afterDeleteLatch = new CountDownLatch(1);
|
||||
CountDownLatch clusterJoined = new CountDownLatch(4);
|
||||
CountDownLatch replicationDone = new CountDownLatch(4);
|
||||
|
||||
String key = UUID.randomUUID().toString();
|
||||
AtomicReference<String> actionTokenKey = new AtomicReference<>();
|
||||
Map<String, String> notes = new HashMap<>();
|
||||
notes.put("foo", "bar");
|
||||
|
||||
inIndependentFactories(4, 60, () -> {
|
||||
log.debug("Joining the cluster");
|
||||
clusterJoined.countDown();
|
||||
awaitLatch(clusterJoined);
|
||||
log.debug("Cluster joined");
|
||||
|
||||
if (index.incrementAndGet() == 1) {
|
||||
actionTokenKey.set(withRealm(realmId, (session, realm) -> {
|
||||
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||
singleUseStore.put(key, 60, notes);
|
||||
|
||||
int time = Time.currentTime();
|
||||
DefaultActionTokenKey atk = new DefaultActionTokenKey(userId, UUID.randomUUID().toString(), time + 60, null);
|
||||
singleUseStore.put(atk.serializeKey(), atk.getExp() - time, notes);
|
||||
|
||||
return atk.serializeKey();
|
||||
}));
|
||||
|
||||
afterFirstNodeLatch.countDown();
|
||||
}
|
||||
awaitLatch(afterFirstNodeLatch);
|
||||
|
||||
// check if single-use object/action token is available on all nodes
|
||||
inComittedTransaction(session -> {
|
||||
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||
while (singleUseStore.get(key) == null || singleUseStore.get(actionTokenKey.get()) == null) {
|
||||
sleep(1000);
|
||||
}
|
||||
replicationDone.countDown();
|
||||
});
|
||||
|
||||
awaitLatch(replicationDone);
|
||||
|
||||
// remove objects on one node
|
||||
if (index.incrementAndGet() == 5) {
|
||||
inComittedTransaction(session -> {
|
||||
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||
singleUseStore.remove(key);
|
||||
singleUseStore.remove(actionTokenKey.get());
|
||||
});
|
||||
|
||||
afterDeleteLatch.countDown();
|
||||
}
|
||||
|
||||
awaitLatch(afterDeleteLatch);
|
||||
|
||||
// check if single-use object/action token is removed
|
||||
inComittedTransaction(session -> {
|
||||
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||
|
||||
while (singleUseStore.get(key) != null && singleUseStore.get(actionTokenKey.get()) != null) {
|
||||
sleep(1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void awaitLatch(CountDownLatch latch) {
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -327,7 +327,6 @@
|
|||
<systemProperty><key>keycloak.userSession.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.loginFailure.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.authorization.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.actionToken.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.singleUseObject.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.eventsStore.provider</key><value>map</value></systemProperty>
|
||||
<systemProperty><key>keycloak.publicKeyStorage.provider</key><value>map</value></systemProperty>
|
||||
|
|
|
@ -111,15 +111,6 @@
|
|||
}
|
||||
},
|
||||
|
||||
"actionToken": {
|
||||
"provider": "${keycloak.actionToken.provider:infinispan}",
|
||||
"map": {
|
||||
"storage": {
|
||||
"provider": "${keycloak.actionToken.map.storage.provider:concurrenthashmap}"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"singleUseObject": {
|
||||
"provider": "${keycloak.singleUseObject.provider:infinispan}",
|
||||
"map": {
|
||||
|
|
Loading…
Reference in a new issue