From 0d6bbd437fa41c8b23a91f339bf782c7fb8f0364 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Wed, 11 May 2022 13:58:58 +0200 Subject: [PATCH] Merge single-use token providers into one Fixes first part of: #11173 * Merge single-use token providers into one * Remove PushedAuthzRequestStoreProvider * Remove OAuth2DeviceTokenStoreProvider * Delete SamlArtifactSessionMappingStoreProvider * SingleUseTokenStoreProvider cleanup * Addressing Michal's comments * Add contains method * Add revoked suffix * Rename to SingleUseObjectProvider --- .../InfinispanCodeToTokenStoreProvider.java | 91 ------- ...nispanCodeToTokenStoreProviderFactory.java | 83 ------ ...inispanOAuth2DeviceTokenStoreProvider.java | 243 ------------------ ...OAuth2DeviceTokenStoreProviderFactory.java | 77 ------ ...nispanPushedAuthzRequestStoreProvider.java | 85 ------ ...ushedAuthzRequestStoreProviderFactory.java | 78 ------ ...mlArtifactSessionMappingStoreProvider.java | 103 -------- ...actSessionMappingStoreProviderFactory.java | 82 ------ .../InfinispanSingleUseObjectProvider.java | 129 ++++++++++ ...nispanSingleUseObjectProviderFactory.java} | 10 +- ...InfinispanSingleUseTokenStoreProvider.java | 77 ------ ...nfinispanTokenRevocationStoreProvider.java | 99 ------- ...anTokenRevocationStoreProviderFactory.java | 81 ------ ...dels.OAuth2DeviceTokenStoreProviderFactory | 18 -- ...els.PushedAuthzRequestStoreProviderFactory | 18 -- ...ArtifactSessionMappingStoreProviderFactory | 18 -- ...oak.models.SingleUseObjectProviderFactory} | 2 +- ....models.SingleUseTokenStoreProviderFactory | 18 -- ...models.TokenRevocationStoreProviderFactory | 19 -- .../models/ActionTokenStoreProvider.java | 2 +- .../models/CodeToTokenStoreProvider.java | 53 ---- .../OAuth2DeviceTokenStoreProvider.java | 103 -------- ...OAuth2DeviceTokenStoreProviderFactory.java | 26 -- .../models/OAuth2DeviceTokenStoreSpi.java | 50 ---- .../PushedAuthzRequestStoreProvider.java | 49 ---- ...ushedAuthzRequestStoreProviderFactory.java | 23 -- .../models/PushedAuthzRequestStoreSpi.java | 47 ---- ...mlArtifactSessionMappingStoreProvider.java | 55 ---- ...actSessionMappingStoreProviderFactory.java | 26 -- .../SamlArtifactSessionMappingStoreSpi.java | 50 ---- .../models/SingleUseObjectProvider.java | 82 ++++++ ...va => SingleUseObjectProviderFactory.java} | 2 +- ...nStoreSpi.java => SingleUseObjectSpi.java} | 8 +- .../models/SingleUseTokenStoreProvider.java | 41 --- .../SingleUseTokenStoreProviderFactory.java | 26 -- .../models/SingleUseTokenStoreSpi.java | 50 ---- .../models/TokenRevocationStoreProvider.java | 50 ---- .../TokenRevocationStoreProviderFactory.java | 26 -- .../models/TokenRevocationStoreSpi.java | 52 ---- .../services/org.keycloak.provider.Spi | 7 +- .../SamlArtifactSessionMappingModel.java | 20 -- .../client/JWTClientAuthenticator.java | 5 +- .../client/JWTClientSecretAuthenticator.java | 8 +- .../protocol/oidc/OIDCLoginProtocol.java | 2 +- .../keycloak/protocol/oidc/TokenManager.java | 8 +- .../endpoints/TokenRevocationEndpoint.java | 7 +- .../oidc/grants/ciba/CibaGrantType.java | 11 +- ...channelAuthenticationCallbackEndpoint.java | 20 +- .../BackchannelAuthenticationEndpoint.java | 9 +- .../oidc/grants/device/DeviceGrantType.java | 66 ++++- .../device/endpoints/DeviceEndpoint.java | 29 ++- .../oidc/par/endpoints/ParEndpoint.java | 10 +- .../request/AuthzEndpointParParser.java | 11 +- .../protocol/oidc/utils/OAuth2Code.java | 9 +- .../protocol/oidc/utils/OAuth2CodeParser.java | 15 +- .../keycloak/protocol/saml/SamlProtocol.java | 22 +- .../keycloak/protocol/saml/SamlService.java | 25 +- .../util/saml/HandleArtifactStepBuilder.java | 7 +- .../OAuth2DeviceAuthorizationGrantTest.java | 2 +- 59 files changed, 385 insertions(+), 2060 deletions(-) delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProviderFactory.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProviderFactory.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProviderFactory.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java rename model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/{InfinispanSingleUseTokenStoreProviderFactory.java => InfinispanSingleUseObjectProviderFactory.java} (89%) delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProvider.java delete mode 100644 model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProviderFactory.java delete mode 100644 model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.OAuth2DeviceTokenStoreProviderFactory delete mode 100644 model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.PushedAuthzRequestStoreProviderFactory delete mode 100644 model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SamlArtifactSessionMappingStoreProviderFactory rename model/infinispan/src/main/resources/META-INF/services/{org.keycloak.models.CodeToTokenStoreProviderFactory => org.keycloak.models.SingleUseObjectProviderFactory} (89%) delete mode 100644 model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseTokenStoreProviderFactory delete mode 100644 model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.TokenRevocationStoreProviderFactory delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java delete mode 100755 server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProviderFactory.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreSpi.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProviderFactory.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreSpi.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProviderFactory.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreSpi.java create mode 100644 server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProvider.java rename server-spi-private/src/main/java/org/keycloak/models/{CodeToTokenStoreProviderFactory.java => SingleUseObjectProviderFactory.java} (89%) rename server-spi-private/src/main/java/org/keycloak/models/{CodeToTokenStoreSpi.java => SingleUseObjectSpi.java} (85%) delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProviderFactory.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreSpi.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProvider.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProviderFactory.java delete mode 100644 server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreSpi.java delete mode 100644 server-spi/src/main/java/org/keycloak/models/SamlArtifactSessionMappingModel.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java deleted file mode 100644 index ff5f130d7d1..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProvider.java +++ /dev/null @@ -1,91 +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 java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.CodeToTokenStoreProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import org.keycloak.connections.infinispan.InfinispanUtil; - -/** - * @author Marek Posolda - */ -public class InfinispanCodeToTokenStoreProvider implements CodeToTokenStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanCodeToTokenStoreProvider.class); - - private final Supplier> codeCache; - private final KeycloakSession session; - - public InfinispanCodeToTokenStoreProvider(KeycloakSession session, Supplier> actionKeyCache) { - this.session = session; - this.codeCache = actionKeyCache; - } - - - @Override - public void put(UUID codeId, int lifespanSeconds, Map codeData) { - ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(codeData); - - try { - BasicCache cache = codeCache.get(); - long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)); - cache.put(codeId, tokenValue, lifespanMs, TimeUnit.MILLISECONDS); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when adding code %s", codeId); - } - - throw re; - } - } - - - @Override - public Map remove(UUID codeId) { - try { - BasicCache cache = codeCache.get(); - ActionTokenValueEntity existing = cache.remove(codeId); - return existing == null ? null : existing.getNotes(); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when removing code %s", codeId); - } - - return null; - } - } - - - @Override - public void close() { - - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java deleted file mode 100644 index f5944ced300..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanCodeToTokenStoreProviderFactory.java +++ /dev/null @@ -1,83 +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 java.util.UUID; -import java.util.function.Supplier; - -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.models.CodeToTokenStoreProvider; -import org.keycloak.models.CodeToTokenStoreProviderFactory; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; - -/** - * @author Marek Posolda - */ -public class InfinispanCodeToTokenStoreProviderFactory implements CodeToTokenStoreProviderFactory { - - private static final Logger LOG = Logger.getLogger(InfinispanCodeToTokenStoreProviderFactory.class); - - // Reuse "actionTokens" infinispan cache for now - private volatile Supplier> codeCache; - - @Override - public CodeToTokenStoreProvider create(KeycloakSession session) { - lazyInit(session); - return new InfinispanCodeToTokenStoreProvider(session, codeCache); - } - - private void lazyInit(KeycloakSession session) { - if (codeCache == null) { - synchronized (this) { - if (codeCache == null) { - this.codeCache = InfinispanSingleUseTokenStoreProviderFactory.getActionTokenCache(session); - } - } - } - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "infinispan"; - } - - @Override - public int order() { - return PROVIDER_PRIORITY; - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProvider.java deleted file mode 100644 index bf43fb8192f..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProvider.java +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2019 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.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.authorization.policy.evaluation.Realm; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; -import org.keycloak.models.OAuth2DeviceUserCodeModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; - -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -/** - * @author Hiroyuki Wada - */ -public class InfinispanOAuth2DeviceTokenStoreProvider implements OAuth2DeviceTokenStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanOAuth2DeviceTokenStoreProvider.class); - - private final Supplier> codeCache; - private final KeycloakSession session; - - public InfinispanOAuth2DeviceTokenStoreProvider(KeycloakSession session, Supplier> actionKeyCache) { - this.session = session; - this.codeCache = actionKeyCache; - } - - @Override - public OAuth2DeviceCodeModel getByDeviceCode(RealmModel realm, String deviceCode) { - try { - BasicCache cache = codeCache.get(); - ActionTokenValueEntity existing = cache.get(OAuth2DeviceCodeModel.createKey(deviceCode)); - - if (existing == null) { - return null; - } - - return OAuth2DeviceCodeModel.fromCache(realm, deviceCode, existing.getNotes()); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when getting device code %s", deviceCode); - } - - return null; - } - } - - @Override - public void close() { - - } - - @Override - public void put(OAuth2DeviceCodeModel deviceCode, OAuth2DeviceUserCodeModel userCode, int lifespanSeconds) { - ActionTokenValueEntity deviceCodeValue = new ActionTokenValueEntity(deviceCode.toMap()); - ActionTokenValueEntity userCodeValue = new ActionTokenValueEntity(userCode.serializeValue()); - - try { - BasicCache cache = codeCache.get(); - cache.put(deviceCode.serializeKey(), deviceCodeValue, lifespanSeconds, TimeUnit.SECONDS); - cache.put(userCode.serializeKey(), userCodeValue, lifespanSeconds, TimeUnit.SECONDS); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when adding device code %s and user code %s", - deviceCode.getDeviceCode(), userCode.getUserCode()); - } - throw re; - } - } - - @Override - public boolean isPollingAllowed(OAuth2DeviceCodeModel deviceCode) { - try { - BasicCache cache = codeCache.get(); - String key = deviceCode.serializePollingKey(); - ActionTokenValueEntity value = new ActionTokenValueEntity(null); - ActionTokenValueEntity existing = cache.putIfAbsent(key, value, deviceCode.getPollingInterval(), TimeUnit.SECONDS); - return existing == null; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when putting polling key for device code %s", deviceCode.getDeviceCode()); - } - - return false; - } - } - - @Override - public OAuth2DeviceCodeModel getByUserCode(RealmModel realm, String userCode) { - try { - OAuth2DeviceCodeModel deviceCode = findDeviceCodeByUserCode(realm, userCode); - if (deviceCode == null) { - return null; - } - - return deviceCode; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when getting device code by user code %s", userCode); - } - - return null; - } - } - - private OAuth2DeviceCodeModel findDeviceCodeByUserCode(RealmModel realm, String userCode) throws HotRodClientException { - BasicCache cache = codeCache.get(); - String userCodeKey = OAuth2DeviceUserCodeModel.createKey(realm, userCode); - ActionTokenValueEntity existing = cache.get(userCodeKey); - - if (existing == null) { - return null; - } - - OAuth2DeviceUserCodeModel data = OAuth2DeviceUserCodeModel.fromCache(realm, userCode, existing.getNotes()); - String deviceCode = data.getDeviceCode(); - - String deviceCodeKey = OAuth2DeviceCodeModel.createKey(deviceCode); - ActionTokenValueEntity existingDeviceCode = cache.get(deviceCodeKey); - - if (existingDeviceCode == null) { - return null; - } - - return OAuth2DeviceCodeModel.fromCache(realm, deviceCode, existingDeviceCode.getNotes()); - } - - @Override - public boolean approve(RealmModel realm, String userCode, String userSessionId, Map additionalParams) { - try { - OAuth2DeviceCodeModel deviceCode = findDeviceCodeByUserCode(realm, userCode); - if (deviceCode == null) { - return false; - } - - OAuth2DeviceCodeModel approved = deviceCode.approve(userSessionId, additionalParams); - - // Update the device code with approved status - BasicCache cache = codeCache.get(); - cache.replace(approved.serializeKey(), new ActionTokenValueEntity(approved.toMap())); - - return true; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when verifying device user code %s", userCode); - } - - return false; - } - } - - @Override - public boolean deny(RealmModel realm, String userCode) { - try { - OAuth2DeviceCodeModel deviceCode = findDeviceCodeByUserCode(realm, userCode); - if (deviceCode == null) { - return false; - } - - OAuth2DeviceCodeModel denied = deviceCode.deny(); - - BasicCache cache = codeCache.get(); - cache.replace(denied.serializeKey(), new ActionTokenValueEntity(denied.toMap())); - - return true; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when denying device user code %s", userCode); - } - - return false; - } - } - - @Override - public boolean removeDeviceCode(RealmModel realm, String deviceCode) { - try { - BasicCache cache = codeCache.get(); - String key = OAuth2DeviceCodeModel.createKey(deviceCode); - ActionTokenValueEntity existing = cache.remove(key); - return existing == null ? false : true; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when removing device code %s", deviceCode); - } - - return false; - } - } - - @Override - public boolean removeUserCode(RealmModel realm, String userCode) { - try { - BasicCache cache = codeCache.get(); - String key = OAuth2DeviceUserCodeModel.createKey(realm, userCode); - ActionTokenValueEntity existing = cache.remove(key); - return existing == null ? false : true; - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when removing user code %s", userCode); - } - - return false; - } - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProviderFactory.java deleted file mode 100644 index 3d628f275d9..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanOAuth2DeviceTokenStoreProviderFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2019 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.infinispan.commons.api.BasicCache; -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; -import org.keycloak.models.OAuth2DeviceTokenStoreProviderFactory; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; - -import java.util.function.Supplier; -import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; - -/** - * @author Hiroyuki Wada - */ -public class InfinispanOAuth2DeviceTokenStoreProviderFactory implements OAuth2DeviceTokenStoreProviderFactory { - - // Reuse "actionTokens" infinispan cache for now - private volatile Supplier> codeCache; - - @Override - public OAuth2DeviceTokenStoreProvider create(KeycloakSession session) { - lazyInit(session); - return new InfinispanOAuth2DeviceTokenStoreProvider(session, codeCache); - } - - private void lazyInit(KeycloakSession session) { - if (codeCache == null) { - synchronized (this) { - codeCache = InfinispanSingleUseTokenStoreProviderFactory.getActionTokenCache(session); - } - } - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "infinispan"; - } - - @Override - public int order() { - return PROVIDER_PRIORITY; - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProvider.java deleted file mode 100644 index 4a3e78781d9..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProvider.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2021 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.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.PushedAuthzRequestStoreProvider; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import org.keycloak.connections.infinispan.InfinispanUtil; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - - -public class InfinispanPushedAuthzRequestStoreProvider implements PushedAuthzRequestStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanPushedAuthzRequestStoreProvider.class); - - private final Supplier> parDataCache; - - public InfinispanPushedAuthzRequestStoreProvider(KeycloakSession session, Supplier> actionKeyCache) { - this.parDataCache = actionKeyCache; - } - - @Override - public void put(UUID key, int lifespanSeconds, Map codeData) { - ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(codeData); - - try { - BasicCache cache = parDataCache.get(); - long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)); - cache.put(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when adding PAR data for redirect URI: %s", key); - } - - throw re; - } - } - - @Override - public Map remove(UUID key) { - try { - BasicCache cache = parDataCache.get(); - ActionTokenValueEntity existing = cache.remove(key); - return existing == null ? null : existing.getNotes(); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when removing PAR data for redirect URI %s", key); - } - - return null; - } - } - - - @Override - public void close() { - - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProviderFactory.java deleted file mode 100644 index ce819607610..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanPushedAuthzRequestStoreProviderFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2021 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.infinispan.commons.api.BasicCache; -import org.keycloak.Config; -import org.keycloak.common.Profile; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.PushedAuthzRequestStoreProvider; -import org.keycloak.models.PushedAuthzRequestStoreProviderFactory; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import org.keycloak.provider.EnvironmentDependentProviderFactory; - -import java.util.UUID; -import java.util.function.Supplier; - -public class InfinispanPushedAuthzRequestStoreProviderFactory implements PushedAuthzRequestStoreProviderFactory, EnvironmentDependentProviderFactory { - - // Reuse "actionTokens" infinispan cache for now - private volatile Supplier> codeCache; - - @Override - public PushedAuthzRequestStoreProvider create(KeycloakSession session) { - lazyInit(session); - return new InfinispanPushedAuthzRequestStoreProvider(session, codeCache); - } - - private void lazyInit(KeycloakSession session) { - if (codeCache == null) { - synchronized (this) { - if (codeCache == null) { - this.codeCache = InfinispanSingleUseTokenStoreProviderFactory.getActionTokenCache(session); - } - } - } - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "infinispan"; - } - - @Override - public boolean isSupported() { - return Profile.isFeatureEnabled(Profile.Feature.PAR); - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProvider.java deleted file mode 100644 index db4aeeeaa89..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProvider.java +++ /dev/null @@ -1,103 +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.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.SamlArtifactSessionMappingModel; -import org.keycloak.models.SamlArtifactSessionMappingStoreProvider; -import org.keycloak.connections.infinispan.InfinispanUtil; - -import java.nio.charset.StandardCharsets; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - - -/** - * @author mhajas - */ -public class InfinispanSamlArtifactSessionMappingStoreProvider implements SamlArtifactSessionMappingStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanSamlArtifactSessionMappingStoreProvider.class); - - private final Supplier> cacheSupplier; - - public InfinispanSamlArtifactSessionMappingStoreProvider(Supplier> actionKeyCache) { - this.cacheSupplier = actionKeyCache; - } - - @Override - public void put(String artifact, int lifespanSeconds, AuthenticatedClientSessionModel clientSessionModel) { - try { - BasicCache cache = cacheSupplier.get(); - long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)); - cache.put(UUID.nameUUIDFromBytes(artifact.getBytes(StandardCharsets.UTF_8)), new String[]{artifact, clientSessionModel.getUserSession().getId(), clientSessionModel.getClient().getId()}, lifespanMs, TimeUnit.MILLISECONDS); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed adding artifact %s", artifact); - } - - throw re; - } - } - - @Override - public SamlArtifactSessionMappingModel get(String artifact) { - try { - BasicCache cache = cacheSupplier.get(); - String[] existing = cache.get(UUID.nameUUIDFromBytes(artifact.getBytes(StandardCharsets.UTF_8))); - if (existing == null || existing.length != 3) return null; - if (!artifact.equals(existing[0])) return null; // Check - return new SamlArtifactSessionMappingModel(existing[1], existing[2]); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when obtaining data for artifact %s", artifact); - } - - return null; - } - } - - @Override - public void remove(String artifact) { - try { - BasicCache cache = cacheSupplier.get(); - if (cache.remove(UUID.nameUUIDFromBytes(artifact.getBytes(StandardCharsets.UTF_8))) == null) { - logger.debugf("Artifact %s was already removed", artifact); - } - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed to remove artifact %s", artifact); - } - } - } - - @Override - public void close() { - - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProviderFactory.java deleted file mode 100644 index d802be4fbd0..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSamlArtifactSessionMappingStoreProviderFactory.java +++ /dev/null @@ -1,82 +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.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.SamlArtifactSessionMappingStoreProvider; -import org.keycloak.models.SamlArtifactSessionMappingStoreProviderFactory; - -import java.util.UUID; -import java.util.function.Supplier; -import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; - -/** - * @author mhajas - */ -public class InfinispanSamlArtifactSessionMappingStoreProviderFactory implements SamlArtifactSessionMappingStoreProviderFactory { - - private static final Logger LOG = Logger.getLogger(InfinispanSamlArtifactSessionMappingStoreProviderFactory.class); - - // Reuse "actionTokens" infinispan cache for now - private volatile Supplier> codeCache; - - @Override - public SamlArtifactSessionMappingStoreProvider create(KeycloakSession session) { - lazyInit(session); - return new InfinispanSamlArtifactSessionMappingStoreProvider(codeCache); - } - - private void lazyInit(KeycloakSession session) { - if (codeCache == null) { - synchronized (this) { - if (codeCache == null) { - this.codeCache = InfinispanSingleUseTokenStoreProviderFactory.getActionTokenCache(session); - } - } - } - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "infinispan"; - } - - @Override - public int order() { - return PROVIDER_PRIORITY; - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java new file mode 100644 index 00000000000..61a84bf37db --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProvider.java @@ -0,0 +1,129 @@ +/* + * 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 java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; + +import org.infinispan.client.hotrod.exceptions.HotRodClientException; +import org.infinispan.commons.api.BasicCache; +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.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) + * + * + * @author Marek Posolda + */ +public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvider { + + public static final Logger logger = Logger.getLogger(InfinispanSingleUseObjectProvider.class); + + private final Supplier> tokenCache; + private final KeycloakSession session; + + public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier> actionKeyCache) { + this.session = session; + this.tokenCache = actionKeyCache; + } + + @Override + public void put(String key, long lifespanSeconds, Map notes) { + ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes); + + try { + BasicCache cache = tokenCache.get(); + long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds)); + cache.put(key, tokenValue, lifespanMs, TimeUnit.MILLISECONDS); + } catch (HotRodClientException re) { + // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. + if (logger.isDebugEnabled()) { + logger.debugf(re, "Failed when adding code %s", key); + } + + throw re; + } + } + + @Override + public Map get(String key) { + BasicCache cache = tokenCache.get(); + ActionTokenValueEntity actionTokenValueEntity = cache.get(key); + return actionTokenValueEntity != null ? actionTokenValueEntity.getNotes() : null; + } + + @Override + public Map remove(String key) { + try { + BasicCache cache = tokenCache.get(); + ActionTokenValueEntity existing = cache.remove(key); + return existing == null ? null : existing.getNotes(); + } catch (HotRodClientException re) { + // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. + // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to remove the code from different place. + if (logger.isDebugEnabled()) { + logger.debugf(re, "Failed when removing code %s", key); + } + + return null; + } + } + + @Override + public boolean replace(String key, Map notes) { + BasicCache cache = tokenCache.get(); + return cache.replace(key, new ActionTokenValueEntity(notes)) != null; + } + + @Override + public boolean putIfAbsent(String key, long lifespanInSeconds) { + ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null); + + try { + BasicCache cache = tokenCache.get(); + long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanInSeconds)); + ActionTokenValueEntity 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. + // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to use the token from different place. + logger.debugf(re, "Failed when adding token %s", key); + + return false; + } + + } + + @Override + public boolean contains(String key) { + BasicCache cache = tokenCache.get(); + return cache.containsKey(key); + } + + @Override + public void close() { + + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProviderFactory.java similarity index 89% rename from model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProviderFactory.java rename to model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProviderFactory.java index 5143e3206e8..0b60b8b614a 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseObjectProviderFactory.java @@ -28,7 +28,7 @@ import org.keycloak.Config; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.SingleUseTokenStoreProviderFactory; +import org.keycloak.models.SingleUseObjectProviderFactory; import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; import org.keycloak.connections.infinispan.InfinispanUtil; import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; @@ -36,17 +36,17 @@ import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSe /** * @author Marek Posolda */ -public class InfinispanSingleUseTokenStoreProviderFactory implements SingleUseTokenStoreProviderFactory { +public class InfinispanSingleUseObjectProviderFactory implements SingleUseObjectProviderFactory { - private static final Logger LOG = Logger.getLogger(InfinispanSingleUseTokenStoreProviderFactory.class); + private static final Logger LOG = Logger.getLogger(InfinispanSingleUseObjectProviderFactory.class); // Reuse "actionTokens" infinispan cache for now private volatile Supplier> tokenCache; @Override - public InfinispanSingleUseTokenStoreProvider create(KeycloakSession session) { + public InfinispanSingleUseObjectProvider create(KeycloakSession session) { lazyInit(session); - return new InfinispanSingleUseTokenStoreProvider(session, tokenCache); + return new InfinispanSingleUseObjectProvider(session, tokenCache); } private void lazyInit(KeycloakSession session) { diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProvider.java deleted file mode 100644 index cd9a0066272..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanSingleUseTokenStoreProvider.java +++ /dev/null @@ -1,77 +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 java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.SingleUseTokenStoreProvider; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -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) - * - * - * @author Marek Posolda - */ -public class InfinispanSingleUseTokenStoreProvider implements SingleUseTokenStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanSingleUseTokenStoreProvider.class); - - private final Supplier> tokenCache; - private final KeycloakSession session; - - public InfinispanSingleUseTokenStoreProvider(KeycloakSession session, Supplier> actionKeyCache) { - this.session = session; - this.tokenCache = actionKeyCache; - } - - @Override - public boolean putIfAbsent(String tokenId, int lifespanInSeconds) { - ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(null); - - // Rather keep the items in the cache for a bit longer - lifespanInSeconds = lifespanInSeconds + 10; - - try { - BasicCache cache = tokenCache.get(); - long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanInSeconds)); - ActionTokenValueEntity existing = cache.putIfAbsent(tokenId, 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. - // In case of lock conflict, we don't want to retry anyway as there was likely an attempt to use the token from different place. - logger.debugf(re, "Failed when adding token %s", tokenId); - - return false; - } - - } - - @Override - public void close() { - - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProvider.java deleted file mode 100644 index 3e9a6399c1a..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProvider.java +++ /dev/null @@ -1,99 +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; - -import java.util.Collections; -import java.util.Map; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -import org.infinispan.client.hotrod.exceptions.HotRodClientException; -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.common.util.Time; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.TokenRevocationStoreProvider; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import org.keycloak.connections.infinispan.InfinispanUtil; - -/** - * @author Marek Posolda - */ -public class InfinispanTokenRevocationStoreProvider implements TokenRevocationStoreProvider { - - public static final Logger logger = Logger.getLogger(InfinispanTokenRevocationStoreProvider.class); - - private final Supplier> tokenCache; - private final KeycloakSession session; - - // Key in the data, which indicates that token is considered revoked - private final String REVOKED_KEY = "revoked"; - - public InfinispanTokenRevocationStoreProvider(KeycloakSession session, Supplier> tokenCache) { - this.session = session; - this.tokenCache = tokenCache; - } - - - @Override - public void putRevokedToken(String tokenId, long lifespanSeconds) { - Map data = Collections.singletonMap(REVOKED_KEY, "true"); - ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(data); - - try { - BasicCache cache = tokenCache.get(); - long lifespanMs = InfinispanUtil.toHotrodTimeMs(cache, Time.toMillis(lifespanSeconds + 1)); - cache.put(tokenId, tokenValue, lifespanMs, TimeUnit.MILLISECONDS); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when adding revoked token %s", tokenId); - } - - throw re; - } - } - - - @Override - public boolean isRevoked(String tokenId) { - try { - BasicCache cache = tokenCache.get(); - ActionTokenValueEntity existing = cache.get(tokenId); - - if (existing == null) { - return false; - } - - return existing.getNotes().containsKey(REVOKED_KEY); - } catch (HotRodClientException re) { - // No need to retry. The hotrod (remoteCache) has some retries in itself in case of some random network error happened. - if (logger.isDebugEnabled()) { - logger.debugf(re, "Failed when trying to get revoked token %s", tokenId); - } - - return false; - } - } - - @Override - public void close() { - - } -} diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProviderFactory.java deleted file mode 100644 index 6e9c8308ab6..00000000000 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanTokenRevocationStoreProviderFactory.java +++ /dev/null @@ -1,81 +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; - -import java.util.function.Supplier; - -import org.infinispan.commons.api.BasicCache; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.TokenRevocationStoreProvider; -import org.keycloak.models.TokenRevocationStoreProviderFactory; -import org.keycloak.models.sessions.infinispan.entities.ActionTokenValueEntity; -import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY; - -/** - * @author Marek Posolda - */ -public class InfinispanTokenRevocationStoreProviderFactory implements TokenRevocationStoreProviderFactory { - - // Reuse "actionTokens" infinispan cache for now - private volatile Supplier> tokenCache; - - @Override - public TokenRevocationStoreProvider create(KeycloakSession session) { - lazyInit(session); - return new InfinispanTokenRevocationStoreProvider(session, tokenCache); - } - - private void lazyInit(KeycloakSession session) { - if (tokenCache == null) { - synchronized (this) { - if (tokenCache == null) { - this.tokenCache = InfinispanSingleUseTokenStoreProviderFactory.getActionTokenCache(session); - } - } - } - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "infinispan"; - } - - @Override - public int order() { - return PROVIDER_PRIORITY; - } -} diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.OAuth2DeviceTokenStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.OAuth2DeviceTokenStoreProviderFactory deleted file mode 100644 index 4c77370f6b4..00000000000 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.OAuth2DeviceTokenStoreProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2019 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.sessions.infinispan.InfinispanOAuth2DeviceTokenStoreProviderFactory \ No newline at end of file diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.PushedAuthzRequestStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.PushedAuthzRequestStoreProviderFactory deleted file mode 100644 index 131bf6dd8b6..00000000000 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.PushedAuthzRequestStoreProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 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.sessions.infinispan.InfinispanPushedAuthzRequestStoreProviderFactory \ No newline at end of file diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SamlArtifactSessionMappingStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SamlArtifactSessionMappingStoreProviderFactory deleted file mode 100644 index c615b1f1fe4..00000000000 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SamlArtifactSessionMappingStoreProviderFactory +++ /dev/null @@ -1,18 +0,0 @@ -# -# Copyright 2021 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.sessions.infinispan.InfinispanSamlArtifactSessionMappingStoreProviderFactory diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory similarity index 89% rename from model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory rename to model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory index e42a1ad1d6a..af73aedf8cb 100644 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.CodeToTokenStoreProviderFactory +++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseObjectProviderFactory @@ -15,4 +15,4 @@ # limitations under the License. # -org.keycloak.models.sessions.infinispan.InfinispanCodeToTokenStoreProviderFactory \ No newline at end of file +org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory \ No newline at end of file diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseTokenStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseTokenStoreProviderFactory deleted file mode 100644 index 56134b0e81a..00000000000 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.SingleUseTokenStoreProviderFactory +++ /dev/null @@ -1,18 +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. -# - -org.keycloak.models.sessions.infinispan.InfinispanSingleUseTokenStoreProviderFactory \ No newline at end of file diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.TokenRevocationStoreProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.TokenRevocationStoreProviderFactory deleted file mode 100644 index 140d4ffd517..00000000000 --- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.TokenRevocationStoreProviderFactory +++ /dev/null @@ -1,19 +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. -# -# - -org.keycloak.models.sessions.infinispan.InfinispanTokenRevocationStoreProviderFactory \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java index 9061c96174b..d71aa90870b 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java +++ b/server-spi-private/src/main/java/org/keycloak/models/ActionTokenStoreProvider.java @@ -23,7 +23,7 @@ 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 CodeToTokenStoreProvider}, + * 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 diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java deleted file mode 100644 index a6ffa2fcf65..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProvider.java +++ /dev/null @@ -1,53 +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 java.util.Map; -import java.util.UUID; - -import org.keycloak.provider.Provider; - -/** - * Provides single-use cache for OAuth2 code parameter. Used to ensure that particular value of code parameter is used once. - * - * For now, it is separate provider as it's a bit different use-case than {@link ActionTokenStoreProvider}, however it may reuse some components (eg. same infinispan cache) - * - * @author Marek Posolda - */ -public interface CodeToTokenStoreProvider extends Provider { - - /** - * Stores the given data and guarantees that data should be available in the store for at least the time specified by {@param lifespanSeconds} parameter - * @param codeId - * @param lifespanSeconds - * @param codeData - * @return true if data were successfully put - */ - void put(UUID codeId, int lifespanSeconds, Map codeData); - - - /** - * This method returns data just if removal was successful. Implementation should guarantee that "remove" is single-use. So if - * 2 threads (even on different cluster nodes or on different cross-dc nodes) calls "remove(123)" concurrently, then just one of them - * is allowed to succeed and return data back. It can't happen that both will succeed. - * - * @param codeId - * @return context data related to OAuth2 code. It returns null if there are not context data available. - */ - Map remove(UUID codeId); -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProvider.java deleted file mode 100755 index fb566881a93..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProvider.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2019 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; - - -/** - * Provides cache for OAuth2 Device Authorization Grant tokens. - * - * @author Hiroyuki Wada - */ -public interface OAuth2DeviceTokenStoreProvider extends Provider { - - /** - * Stores the given device code and user code - * - * @param deviceCode - * @param userCode - * @param lifespanSeconds - */ - void put(OAuth2DeviceCodeModel deviceCode, OAuth2DeviceUserCodeModel userCode, int lifespanSeconds); - - /** - * Get the model object by the given device code - * - * @param realm - * @param deviceCode - * @return - */ - OAuth2DeviceCodeModel getByDeviceCode(RealmModel realm, String deviceCode); - - /** - * Check the device code is allowed to poll - * - * @param deviceCode - * @return Return true if the given device code is allowed to poll - */ - boolean isPollingAllowed(OAuth2DeviceCodeModel deviceCode); - - /** - * Get the model object by the given user code - * - * @param realm - * @param userCode - * @return - */ - OAuth2DeviceCodeModel getByUserCode(RealmModel realm, String userCode); - - /** - * Approve the given user code - * - * @param realm - * @param userCode - * @param userSessionId - * @return Return true if approving successful. If the code is already expired and cleared, it returns false. - */ - boolean approve(RealmModel realm, String userCode, String userSessionId, Map additionalParams); - - /** - * Deny the given user code - * - * @param realm - * @param userCode - * @return Return true if denying successful. If the code is already expired and cleared, it returns false. - */ - boolean deny(RealmModel realm, String userCode); - - /** - * Remove the given device code - * - * @param realm - * @param deviceCode - * @return Return true if removing successful. If the code is already expired and cleared, it returns false. - */ - boolean removeDeviceCode(RealmModel realm, String deviceCode); - - /** - * Remove the given user code - * - * @param realm - * @param userCode - * @return Return true if removing successful. If the code is already expired and cleared, it returns false. - */ - boolean removeUserCode(RealmModel realm, String userCode); -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProviderFactory.java deleted file mode 100644 index 076dc9e6143..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreProviderFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019 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 Hiroyuki Wada - */ -public interface OAuth2DeviceTokenStoreProviderFactory extends ProviderFactory { -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreSpi.java deleted file mode 100644 index 699076760c9..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/OAuth2DeviceTokenStoreSpi.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2019 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 org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Hiroyuki Wada - */ -public class OAuth2DeviceTokenStoreSpi implements Spi { - - public static final String NAME = "oauth2DeviceTokenStore"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return OAuth2DeviceTokenStoreProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return OAuth2DeviceTokenStoreProviderFactory.class; - } -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProvider.java deleted file mode 100644 index 9bb8069dc39..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProvider.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2021 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; -import java.util.UUID; - -/** - * Provides single-use cache for Pushed Authorization Request. The data of this request may be used only once. - */ -public interface PushedAuthzRequestStoreProvider extends Provider { - - /** - * Stores the given data and guarantees that data should be available in the store for at least the time specified by {@param lifespanSeconds} parameter. - * - * @param key unique identifier - * @param lifespanSeconds time to live - * @param codeData the data to store - */ - void put(UUID key, int lifespanSeconds, Map codeData); - - - /** - * This method returns data just if removal was successful. Implementation should guarantee that "remove" is single-use. So if - * 2 threads (even on different cluster nodes or on different cross-dc nodes) calls "remove(123)" concurrently, then just one of them - * is allowed to succeed and return data back. It can't happen that both will succeed. - * - * @param key unique identifier - * @return context data related Pushed Authorization Request. It returns null if there is no context data available. - */ - Map remove(UUID key); -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProviderFactory.java deleted file mode 100644 index f6c38794c79..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreProviderFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2021 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; - -public interface PushedAuthzRequestStoreProviderFactory extends ProviderFactory { -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreSpi.java deleted file mode 100644 index 5471a0fad59..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/PushedAuthzRequestStoreSpi.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2021 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 org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -public class PushedAuthzRequestStoreSpi implements Spi { - - public static final String NAME = "pushedAuthzRequestStore"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return PushedAuthzRequestStoreProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return PushedAuthzRequestStoreProviderFactory.class; - } -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProvider.java deleted file mode 100644 index 451652048eb..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProvider.java +++ /dev/null @@ -1,55 +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; - -/** - * Provides cache for session mapping for SAML artifacts. - * - * For now, it is separate provider as it's a bit different use-case than {@link ActionTokenStoreProvider}, however it may reuse some components (eg. same infinispan cache) - * - * @author mhajas - */ -public interface SamlArtifactSessionMappingStoreProvider extends Provider { - - /** - * Stores the given data and guarantees that data should be available in the store for at least the time specified by {@param lifespanSeconds} parameter - * - * @param artifact - * @param lifespanSeconds - * @param clientSessionModel - */ - void put(String artifact, int lifespanSeconds, AuthenticatedClientSessionModel clientSessionModel); - - - /** - * This method returns session mapping associated with the given {@param artifact} - * - * @param artifact - * @return session mapping corresponding to given artifact or {@code null} if it does not exist. - */ - SamlArtifactSessionMappingModel get(String artifact); - - /** - * Removes data for the given {@param artifact} from the store - * - * @param artifact - */ - void remove(String artifact); -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProviderFactory.java deleted file mode 100644 index c8d003d23cd..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreProviderFactory.java +++ /dev/null @@ -1,26 +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 mhajas - */ -public interface SamlArtifactSessionMappingStoreProviderFactory extends ProviderFactory { -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreSpi.java deleted file mode 100644 index 73115679980..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SamlArtifactSessionMappingStoreSpi.java +++ /dev/null @@ -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.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author mhajas - */ -public class SamlArtifactSessionMappingStoreSpi implements Spi { - - public static final String NAME = "samlArtifactSessionMappingStore"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return SamlArtifactSessionMappingStoreProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return SamlArtifactSessionMappingStoreProviderFactory.class; - } -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProvider.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProvider.java new file mode 100644 index 00000000000..08a65245d04 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProvider.java @@ -0,0 +1,82 @@ +/* + * 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; + +/** + * Provides a cache to store data for single-use use case. Data are represented by a {@code String} key. + * + * @author Marek Posolda + */ +public interface SingleUseObjectProvider extends Provider { + + // suffix to a key to indicate that token is considered revoked + String REVOKED_KEY = ".revoked"; + + /** + * Stores the given data and guarantees that data should be available in the store for at least the time specified by {@param lifespanSeconds} parameter + * @param key + * @param lifespanSeconds + * @param notes + */ + void put(String key, long lifespanSeconds, Map notes); + + /** + * Gets data associated with the given key. + * @param key String + * @return Map Data associated with the given key or {@code null} if there is no associated data. + */ + Map get(String key); + + /** + * This method returns data just if removal was successful. Implementation should guarantee that "remove" is single-use. So if + * 2 threads (even on different cluster nodes or on different cross-dc nodes) calls "remove(123)" concurrently, then just one of them + * is allowed to succeed and return data back. It can't happen that both will succeed. + * + * @param key + * @return context data associated to the key. It returns {@code null} if there are no context data available. + */ + Map remove(String key); + + /** + * Replaces data associated with the given key in the store if the store contains the key. + * @param key String + * @param notes Map New data to be stored + * @return {@code true} if the store contains the key and data was replaced, otherwise {@code false}. + */ + boolean replace(String key, Map notes); + + /** + * Will try to put the key into the cache. It will succeed just if key is not already there. + * + * @param key + * @param lifespanInSeconds Minimum lifespan for which successfully added key will be kept in the cache. + * @return true if the key was successfully put into the cache. This means that same key wasn't in the cache before + */ + boolean putIfAbsent(String key, long lifespanInSeconds); + + /** + * Checks if there is a record in the store for the given key. + * @param key String + * @return {@code true} if the record is present in the store, {@code false} otherwise. + */ + boolean contains(String key); +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProviderFactory.java similarity index 89% rename from server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java rename to server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProviderFactory.java index 85b240100d1..974b5e23726 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectProviderFactory.java @@ -22,5 +22,5 @@ import org.keycloak.provider.ProviderFactory; /** * @author Marek Posolda */ -public interface CodeToTokenStoreProviderFactory extends ProviderFactory { +public interface SingleUseObjectProviderFactory extends ProviderFactory { } diff --git a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectSpi.java similarity index 85% rename from server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java rename to server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectSpi.java index e71ade63034..b52efcfaf2d 100644 --- a/server-spi-private/src/main/java/org/keycloak/models/CodeToTokenStoreSpi.java +++ b/server-spi-private/src/main/java/org/keycloak/models/SingleUseObjectSpi.java @@ -24,9 +24,9 @@ import org.keycloak.provider.Spi; /** * @author Marek Posolda */ -public class CodeToTokenStoreSpi implements Spi { +public class SingleUseObjectSpi implements Spi { - public static final String NAME = "codeToTokenStore"; + public static final String NAME = "singleUseObject"; @Override public boolean isInternal() { @@ -40,11 +40,11 @@ public class CodeToTokenStoreSpi implements Spi { @Override public Class getProviderClass() { - return CodeToTokenStoreProvider.class; + return SingleUseObjectProvider.class; } @Override public Class getProviderFactoryClass() { - return CodeToTokenStoreProviderFactory.class; + return SingleUseObjectProviderFactory.class; } } diff --git a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProvider.java deleted file mode 100644 index 349b21dde5c..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProvider.java +++ /dev/null @@ -1,41 +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; - -/** - * Provides single-use cache for OAuth2 code parameter. Used to ensure that particular value of code parameter is used once. - * - * TODO: For now, it is separate provider as {@link CodeToTokenStoreProvider}, however will be good to merge those 2 providers to "SingleUseCacheProvider" - * in the future as they provide very similar thing - * - * @author Marek Posolda - */ -public interface SingleUseTokenStoreProvider extends Provider { - - /** - * Will try to put the token into the cache. It will success just if token is not already there. - * - * @param tokenId - * @param lifespanInSeconds Minimum lifespan for which successfully added token will be kept in the cache. - * @return true if token was successfully put into the cache. This means that same token wasn't in the cache before - */ - boolean putIfAbsent(String tokenId, int lifespanInSeconds); - -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProviderFactory.java deleted file mode 100644 index 84f8fb12797..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreProviderFactory.java +++ /dev/null @@ -1,26 +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 Marek Posolda - */ -public interface SingleUseTokenStoreProviderFactory extends ProviderFactory { -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreSpi.java deleted file mode 100644 index f63a760088e..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/SingleUseTokenStoreSpi.java +++ /dev/null @@ -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.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Marek Posolda - */ -public class SingleUseTokenStoreSpi implements Spi { - - public static final String NAME = "singleUseTokenStore"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return SingleUseTokenStoreProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return SingleUseTokenStoreProviderFactory.class; - } -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProvider.java b/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProvider.java deleted file mode 100644 index 73765b24569..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProvider.java +++ /dev/null @@ -1,50 +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; - -import java.util.UUID; - -import org.keycloak.provider.Provider; - -/** - * Provides the cache for store revoked tokens. - * - * For now, it is separate provider as it is bit different use-case that existing providers like {@link CodeToTokenStoreProvider}, - * {@link SingleUseTokenStoreProvider} and {@link ActionTokenStoreProvider} - * - * @author Marek Posolda - */ -public interface TokenRevocationStoreProvider extends Provider { - - /** - * Mark given token as revoked. Parameter "lifespanSeconds" is the time for which the token is considered revoked. After this time, it may be removed from this store, - * which means that {@link #isRevoked} method will return false. In reality, the token will usually still be invalid due the "expiration" claim on it, however - * that is out of scope of this provider. - * - * @param tokenId - * @oaran lifespanSeconds - */ - void putRevokedToken(String tokenId, long lifespanSeconds); - - /** - * @param tokenId - * @return true if token exists in the store, which indicates that it is revoked. - */ - boolean isRevoked(String tokenId); -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProviderFactory.java deleted file mode 100644 index 4f3d78fa677..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreProviderFactory.java +++ /dev/null @@ -1,26 +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; -import org.keycloak.provider.ProviderFactory; - -/** - * @author Marek Posolda - */ -public interface TokenRevocationStoreProviderFactory extends ProviderFactory { -} diff --git a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreSpi.java b/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreSpi.java deleted file mode 100644 index 1a458ab1793..00000000000 --- a/server-spi-private/src/main/java/org/keycloak/models/TokenRevocationStoreSpi.java +++ /dev/null @@ -1,52 +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; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Marek Posolda - */ -public class TokenRevocationStoreSpi implements Spi { - - public static final String NAME = "tokenRevocationStore"; - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return NAME; - } - - @Override - public Class getProviderClass() { - return TokenRevocationStoreProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return TokenRevocationStoreProviderFactory.class; - } - -} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index eb7c5d8311b..597c19c74ca 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -26,13 +26,8 @@ org.keycloak.models.RealmSpi org.keycloak.models.RoleSpi org.keycloak.models.DeploymentStateSpi org.keycloak.models.ActionTokenStoreSpi -org.keycloak.models.CodeToTokenStoreSpi -org.keycloak.models.OAuth2DeviceTokenStoreSpi org.keycloak.models.OAuth2DeviceUserCodeSpi -org.keycloak.models.SamlArtifactSessionMappingStoreSpi -org.keycloak.models.PushedAuthzRequestStoreSpi -org.keycloak.models.SingleUseTokenStoreSpi -org.keycloak.models.TokenRevocationStoreSpi +org.keycloak.models.SingleUseObjectSpi org.keycloak.models.UserSessionSpi org.keycloak.models.UserLoginFailureSpi org.keycloak.models.UserSpi diff --git a/server-spi/src/main/java/org/keycloak/models/SamlArtifactSessionMappingModel.java b/server-spi/src/main/java/org/keycloak/models/SamlArtifactSessionMappingModel.java deleted file mode 100644 index 2c24779280e..00000000000 --- a/server-spi/src/main/java/org/keycloak/models/SamlArtifactSessionMappingModel.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.keycloak.models; - -public class SamlArtifactSessionMappingModel { - - private final String userSessionId; - private final String clientSessionId; - - public SamlArtifactSessionMappingModel(String userSessionId, String clientSessionId) { - this.userSessionId = userSessionId; - this.clientSessionId = clientSessionId; - } - - public String getUserSessionId() { - return userSessionId; - } - - public String getClientSessionId() { - return clientSessionId; - } -} diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java index ebf1ebc6dc3..f91781ac96e 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientAuthenticator.java @@ -29,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -44,7 +43,7 @@ import org.keycloak.keys.loader.PublicKeyStorageManager; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.SingleUseTokenStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; @@ -190,7 +189,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator { throw new RuntimeException("Missing ID on the token"); } - SingleUseTokenStoreProvider singleUseCache = context.getSession().getProvider(SingleUseTokenStoreProvider.class); + SingleUseObjectProvider singleUseCache = context.getSession().getProvider(SingleUseObjectProvider.class); int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10); if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) { logger.tracef("Added token '%s' to single-use cache. Lifespan: %d seconds, client: %s", token.getId(), lifespanInSecs, clientId); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java index 7126294d568..31a04f233de 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/client/JWTClientSecretAuthenticator.java @@ -25,11 +25,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; -import org.apache.http.client.utils.CloneUtils; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.authentication.AuthenticationFlowError; @@ -37,9 +35,7 @@ import org.keycloak.authentication.ClientAuthenticationFlowContext; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSInput; import org.keycloak.models.AuthenticationExecutionModel.Requirement; -import org.keycloak.models.SingleUseTokenStoreProvider; -import org.keycloak.models.delegate.ClientModelLazyDelegate; -import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; @@ -196,7 +192,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator { throw new RuntimeException("Missing ID on the token"); } - SingleUseTokenStoreProvider singleUseCache = context.getSession().getProvider(SingleUseTokenStoreProvider.class); + SingleUseObjectProvider singleUseCache = context.getSession().getProvider(SingleUseObjectProvider.class); int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10); if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java index 5446a29ad25..00553dc97ff 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java @@ -231,7 +231,7 @@ public class OIDCLoginProtocol implements LoginProtocol { // Standard or hybrid flow String code = null; if (responseType.hasResponseType(OIDCResponseType.CODE)) { - OAuth2Code codeData = new OAuth2Code(UUID.randomUUID(), + OAuth2Code codeData = new OAuth2Code(UUID.randomUUID().toString(), Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(), nonce, authSession.getClientNote(OAuth2Constants.SCOPE), diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index 9a0fe0cf22a..0ff5570658d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -25,7 +25,6 @@ import org.keycloak.OAuthErrorException; import org.keycloak.TokenCategory; import org.keycloak.TokenVerifier; import org.keycloak.authentication.authenticators.util.AcrStore; -import org.keycloak.authentication.authenticators.util.LoAUtil; import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.cluster.ClusterProvider; @@ -50,7 +49,7 @@ import org.keycloak.models.Constants; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; -import org.keycloak.models.TokenRevocationStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; @@ -64,7 +63,6 @@ import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper; import org.keycloak.rar.AuthorizationDetails; import org.keycloak.representations.AuthorizationDetailsJSONRepresentation; import org.keycloak.rar.AuthorizationRequestContext; -import org.keycloak.protocol.oidc.utils.AcrUtils; import org.keycloak.protocol.oidc.utils.OIDCResponseType; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessTokenResponse; @@ -1354,8 +1352,8 @@ public class TokenManager { @Override public boolean test(AccessToken token) { - TokenRevocationStoreProvider revocationStore = session.getProvider(TokenRevocationStoreProvider.class); - return !revocationStore.isRevoked(token.getId()); + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + return !singleUseStore.contains(token.getId() + SingleUseObjectProvider.REVOKED_KEY); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java index 26b6e3c0588..42138445839 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenRevocationEndpoint.java @@ -17,6 +17,7 @@ package org.keycloak.protocol.oidc.endpoints; +import java.util.Collections; import java.util.Objects; import java.util.stream.Collectors; @@ -41,7 +42,7 @@ import org.keycloak.headers.SecurityHeadersProvider; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; -import org.keycloak.models.TokenRevocationStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.oidc.TokenManager; @@ -257,9 +258,9 @@ public class TokenRevocationEndpoint { } private void revokeAccessToken() { - TokenRevocationStoreProvider revocationStore = session.getProvider(TokenRevocationStoreProvider.class); + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); int currentTime = Time.currentTime(); long lifespanInSecs = Math.max(token.getExp() - currentTime, 10); - revocationStore.putRevokedToken(token.getId(), lifespanInSecs); + singleUseStore.put(token.getId() + SingleUseObjectProvider.REVOKED_KEY, lifespanInSecs, Collections.emptyMap()); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/CibaGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/CibaGrantType.java index 3fbfb9fc058..6a90a8c26d7 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/CibaGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/CibaGrantType.java @@ -36,7 +36,6 @@ import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientSessionContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; import org.keycloak.models.RealmModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserModel; @@ -49,6 +48,7 @@ import org.keycloak.protocol.oidc.grants.ciba.clientpolicy.context.BackchannelTo import org.keycloak.protocol.oidc.grants.ciba.endpoints.CibaRootEndpoint; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.endpoints.TokenEndpoint; +import org.keycloak.protocol.oidc.grants.device.DeviceGrantType; import org.keycloak.services.CorsErrorResponseException; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.Urls; @@ -160,8 +160,7 @@ public class CibaGrantType { throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST); } - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - OAuth2DeviceCodeModel deviceCode = store.getByDeviceCode(realm, request.getId()); + OAuth2DeviceCodeModel deviceCode = DeviceGrantType.getDeviceByDeviceCode(session, realm, request.getId()); if (deviceCode == null) { // Auth Req ID has not put onto cache, no need to remove Auth Req ID. @@ -179,8 +178,8 @@ public class CibaGrantType { throw new CorsErrorResponseException(cors, OAuthErrorException.EXPIRED_TOKEN, "authentication timed out", Response.Status.BAD_REQUEST); } - if (!store.isPollingAllowed(deviceCode)) { - logDebug("pooling.", request); + if (!DeviceGrantType.isPollingAllowed(session, deviceCode)) { + logDebug("polling.", request); throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "too early to access", Response.Status.BAD_REQUEST); } @@ -198,7 +197,7 @@ public class CibaGrantType { UserSessionModel userSession = createUserSession(request, deviceCode.getAdditionalParams()); UserModel user = userSession.getUser(); - store.removeDeviceCode(realm, request.getId()); + DeviceGrantType.removeDeviceByDeviceCode(session, request. getId()); // Compute client scopes again from scope parameter. Check if user still has them granted // (but in code-to-token request, it could just theoretically happen that they are not available) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationCallbackEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationCallbackEndpoint.java index e553b7a6b45..93d0a4101f8 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationCallbackEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationCallbackEndpoint.java @@ -29,9 +29,10 @@ import org.keycloak.models.CibaConfig; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse; import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse.Status; +import org.keycloak.protocol.oidc.grants.device.DeviceGrantType; +import org.keycloak.protocol.oidc.grants.device.endpoints.DeviceEndpoint; import org.keycloak.representations.AccessToken; import org.keycloak.services.ErrorResponseException; import org.keycloak.services.Urls; @@ -123,8 +124,7 @@ public class BackchannelAuthenticationCallbackEndpoint extends AbstractCibaEndpo throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN); } - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, bearerToken.getId()); + OAuth2DeviceCodeModel deviceCode = DeviceEndpoint.getDeviceByUserCode(session, realm, bearerToken.getId()); if (deviceCode == null) { throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN); @@ -154,15 +154,13 @@ public class BackchannelAuthenticationCallbackEndpoint extends AbstractCibaEndpo } private void cancelRequest(String authResultId) { - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - OAuth2DeviceCodeModel userCode = store.getByUserCode(realm, authResultId); - store.removeDeviceCode(realm, userCode.getDeviceCode()); - store.removeUserCode(realm, authResultId); + OAuth2DeviceCodeModel userCode = DeviceEndpoint.getDeviceByUserCode(session, realm, authResultId); + DeviceGrantType.removeDeviceByDeviceCode(session, userCode.getDeviceCode()); + DeviceGrantType.removeDeviceByUserCode(session, realm, authResultId); } private void approveRequest(AccessToken authReqId, Map additionalParams) { - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - store.approve(realm, authReqId.getId(), "fake", additionalParams); + DeviceGrantType.approveUserCode(session, realm, authReqId.getId(), "fake", additionalParams); } private void denyRequest(AccessToken authReqId, Status status) { @@ -172,9 +170,7 @@ public class BackchannelAuthenticationCallbackEndpoint extends AbstractCibaEndpo event.error(Errors.CONSENT_DENIED); } - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - - store.deny(realm, authReqId.getId()); + DeviceGrantType.denyUserCode(session, realm, authReqId.getId()); } protected void sendClientNotificationRequest(ClientModel client, CibaConfig cibaConfig, OAuth2DeviceCodeModel deviceModel) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java index 9ffdf373c9e..6cfeb6e0ce2 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/ciba/endpoints/BackchannelAuthenticationEndpoint.java @@ -27,9 +27,9 @@ import org.keycloak.models.CibaConfig; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; import org.keycloak.models.OAuth2DeviceUserCodeModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserModel; import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType; import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelProvider; @@ -123,7 +123,7 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint { /** * TODO: Leverage the device code storage for tracking authentication requests. Not sure if we need a specific storage, - * but probably make the {@link OAuth2DeviceTokenStoreProvider} more generic for ciba, device, or any other use case + * or we can leverage the {@link SingleUseObjectProvider} for ciba, device, or any other use case * that relies on cross-references for unsolicited user authentication requests from devices. */ private void storeAuthenticationRequest(CIBAAuthenticationRequest request, CibaConfig cibaConfig, String authReqId) { @@ -147,9 +147,10 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint { // To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code int lifespanSeconds = expiresIn + poolingInterval + 10; - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); - store.put(deviceCode, userCode, lifespanSeconds); + singleUseStore.put(deviceCode.serializeKey(), lifespanSeconds, deviceCode.toMap()); + singleUseStore.put(userCode.serializeKey(), lifespanSeconds, userCode.serializeValue()); } private CIBAAuthenticationRequest authorizeClient(MultivaluedMap params) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java index aa2f9ef05bd..04a700678a0 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/DeviceGrantType.java @@ -26,14 +26,14 @@ import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; -import org.keycloak.models.ClientScopeModel; import org.keycloak.models.ClientSessionContext; import org.keycloak.models.KeycloakContext; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakUriInfo; import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; +import org.keycloak.models.OAuth2DeviceUserCodeModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.LoginProtocol; @@ -60,7 +60,7 @@ import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.net.URI; -import java.util.stream.Stream; +import java.util.Map; /** * @author Hiroyuki Wada @@ -101,8 +101,7 @@ public class DeviceGrantType { String errorType = OAuthErrorException.SERVER_ERROR; if (error == LoginProtocol.Error.CONSENT_DENIED) { String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE); - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - if (!store.deny(realm, verifiedUserCode)) { + if (!denyUserCode(session, realm, verifiedUserCode)) { // Already expired and removed in the store errorType = OAuthErrorException.EXPIRED_TOKEN; } else { @@ -123,8 +122,7 @@ public class DeviceGrantType { String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE); String userSessionId = clientSession.getUserSession().getId(); - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - if (!store.approve(realm, verifiedUserCode, userSessionId, null)) { + if (!approveUserCode(session, realm, verifiedUserCode, userSessionId, null)) { // Already expired and removed in the store return Response.status(302).location( uriBuilder.queryParam(OAuth2Constants.ERROR, OAuthErrorException.EXPIRED_TOKEN) @@ -133,7 +131,7 @@ public class DeviceGrantType { } // Now, remove the verified user code - store.removeUserCode(realm, verifiedUserCode); + removeDeviceByUserCode(session, realm, verifiedUserCode); return Response.status(302).location( uriBuilder.build(realm.getName()) @@ -145,6 +143,51 @@ public class DeviceGrantType { return flow != null; } + public static OAuth2DeviceCodeModel getDeviceByDeviceCode(KeycloakSession session, RealmModel realm, String deviceCode) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + Map notes = singleUseStore.get(OAuth2DeviceCodeModel.createKey(deviceCode)); + return notes != null ? OAuth2DeviceCodeModel.fromCache(realm, deviceCode, notes) : null; + } + + public static void removeDeviceByDeviceCode(KeycloakSession session, String deviceCode) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + singleUseStore.remove(OAuth2DeviceCodeModel.createKey(deviceCode)); + } + + public static void removeDeviceByUserCode(KeycloakSession session, RealmModel realm, String userCode) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + singleUseStore.remove(OAuth2DeviceUserCodeModel.createKey(realm, userCode)); + } + + public static boolean isPollingAllowed(KeycloakSession session, OAuth2DeviceCodeModel deviceCodeModel) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + return singleUseStore.putIfAbsent(deviceCodeModel.serializePollingKey(), deviceCodeModel.getPollingInterval()); + } + + public static boolean approveUserCode(KeycloakSession session, RealmModel realm, String userCode, String userSessionId, Map additionalParams) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + OAuth2DeviceCodeModel deviceCodeModel = DeviceEndpoint.getDeviceByUserCode(session, realm, userCode); + + if (deviceCodeModel != null) { + OAuth2DeviceCodeModel approvedDeviceCode = deviceCodeModel.approve(userSessionId, additionalParams); + return singleUseStore.replace(approvedDeviceCode.serializeKey(), approvedDeviceCode.toMap()); + } + + return false; + } + + public static boolean denyUserCode(KeycloakSession session, RealmModel realm, String userCode) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + OAuth2DeviceCodeModel deviceCodeModel = DeviceEndpoint.getDeviceByUserCode(session, realm, userCode); + + if (deviceCodeModel != null) { + OAuth2DeviceCodeModel deniedDeviceCode = deviceCodeModel.deny(); + return singleUseStore.replace(deniedDeviceCode.serializeKey(), deniedDeviceCode.toMap()); + } + + return false; + } + private MultivaluedMap formParams; private ClientModel client; @@ -182,8 +225,7 @@ public class DeviceGrantType { "Missing parameter: " + OAuth2Constants.DEVICE_CODE, Response.Status.BAD_REQUEST); } - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - OAuth2DeviceCodeModel deviceCodeModel = store.getByDeviceCode(realm, deviceCode); + OAuth2DeviceCodeModel deviceCodeModel = getDeviceByDeviceCode(session, realm, deviceCode); if (deviceCodeModel == null) { event.error(Errors.INVALID_OAUTH2_DEVICE_CODE); @@ -197,7 +239,7 @@ public class DeviceGrantType { Response.Status.BAD_REQUEST); } - if (!store.isPollingAllowed(deviceCodeModel)) { + if (!isPollingAllowed(session, deviceCodeModel)) { event.error(Errors.SLOW_DOWN); throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "Slow down", Response.Status.BAD_REQUEST); } @@ -245,7 +287,7 @@ public class DeviceGrantType { } // Now, remove the device code - store.removeDeviceCode(realm, deviceCode); + removeDeviceByDeviceCode(session, deviceCode); UserModel user = userSession.getUser(); if (user == null) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java index 662cb1c0909..6bb4630d69f 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java @@ -30,11 +30,12 @@ import org.keycloak.forms.login.LoginFormsProvider; import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; +import org.keycloak.models.KeycloakSession; import org.keycloak.models.OAuth2DeviceCodeModel; -import org.keycloak.models.OAuth2DeviceTokenStoreProvider; import org.keycloak.models.OAuth2DeviceUserCodeModel; import org.keycloak.models.OAuth2DeviceUserCodeProvider; import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker; @@ -70,6 +71,8 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import java.util.Map; + import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.OAUTH2_DEVICE_USER_CODE; /** @@ -161,8 +164,10 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe // To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code int lifespanSeconds = expiresIn + interval + 10; - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); - store.put(deviceCode, userCode, lifespanSeconds); + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + + singleUseStore.put(deviceCode.serializeKey(), lifespanSeconds, deviceCode.toMap()); + singleUseStore.put(userCode.serializeKey(), lifespanSeconds, userCode.serializeValue()); try { String deviceUrl = DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName()) @@ -212,10 +217,9 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe } else { // code exists, probably due to using a verification_uri_complete. Start the authentication considering the client // that started the flow. - OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class); OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class); String formattedUserCode = userCodeProvider.format(userCode); - OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, formattedUserCode); + OAuth2DeviceCodeModel deviceCode = getDeviceByUserCode(session, realm, formattedUserCode); if (deviceCode == null) { return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_INVALID_USER_CODE, "device code not found (it may already have been used)"); @@ -287,6 +291,21 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe } } + public static OAuth2DeviceCodeModel getDeviceByUserCode(KeycloakSession session, RealmModel realm, String userCode) { + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + Map notes = singleUseStore.get(OAuth2DeviceUserCodeModel.createKey(realm, userCode)); + + if (notes != null) { + OAuth2DeviceUserCodeModel data = OAuth2DeviceUserCodeModel.fromCache(realm, userCode, notes); + String deviceCode = data.getDeviceCode(); + notes = singleUseStore.get(OAuth2DeviceCodeModel.createKey(deviceCode)); + + return notes != null ? OAuth2DeviceCodeModel.fromCache(realm, deviceCode, notes) : null; + } + + return null; + } + /** * @param errorMessage Message code for the verification page * @param reason For event details; not exposed to end user diff --git a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java index adf58b6ed82..6aaf8759ac0 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/ParEndpoint.java @@ -24,7 +24,7 @@ import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; import org.keycloak.headers.SecurityHeadersProvider; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.PushedAuthzRequestStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.protocol.oidc.OIDCLoginProtocolService; import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; @@ -146,8 +146,8 @@ public class ParEndpoint extends AbstractParEndpoint { Map params = new HashMap<>(); - UUID key = UUID.randomUUID(); - String requestUri = REQUEST_URI_PREFIX + key.toString(); + String key = UUID.randomUUID().toString(); + String requestUri = REQUEST_URI_PREFIX + key; int expiresIn = realm.getParPolicy().getRequestUriLifespan(); @@ -158,8 +158,8 @@ public class ParEndpoint extends AbstractParEndpoint { }); params.put(PAR_CREATED_TIME, String.valueOf(System.currentTimeMillis())); - PushedAuthzRequestStoreProvider parStore = session.getProvider(PushedAuthzRequestStoreProvider.class); - parStore.put(key, expiresIn, params); + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + singleUseStore.put(key, expiresIn, params); ParResponse parResponse = new ParResponse(requestUri, expiresIn); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/request/AuthzEndpointParParser.java b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/request/AuthzEndpointParParser.java index a19f6961657..986e244d750 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/request/AuthzEndpointParParser.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/par/endpoints/request/AuthzEndpointParParser.java @@ -20,13 +20,12 @@ package org.keycloak.protocol.oidc.par.endpoints.request; import java.util.Map; import java.util.Set; -import java.util.UUID; import org.jboss.logging.Logger; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.PushedAuthzRequestStoreProvider; import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest; import org.keycloak.protocol.oidc.endpoints.request.AuthzEndpointRequestObjectParser; @@ -51,15 +50,15 @@ public class AuthzEndpointParParser extends AuthzEndpointRequestParser { public AuthzEndpointParParser(KeycloakSession session, ClientModel client, String requestUri) { this.session = session; this.client = client; - PushedAuthzRequestStoreProvider parStore = session.getProvider(PushedAuthzRequestStoreProvider.class); - UUID key; + SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class); + String key; try { - key = UUID.fromString(requestUri.substring(ParEndpoint.REQUEST_URI_PREFIX_LENGTH)); + key = requestUri.substring(ParEndpoint.REQUEST_URI_PREFIX_LENGTH); } catch (RuntimeException re) { logger.warnf(re,"Unable to parse request_uri: %s", requestUri); throw new RuntimeException("Unable to parse request_uri"); } - Map retrievedRequest = parStore.remove(key); + Map retrievedRequest = singleUseStore.remove(key); if (retrievedRequest == null) { throw new RuntimeException("PAR not found. not issued or used multiple times."); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2Code.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2Code.java index fd9159922dd..83050aaec15 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2Code.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2Code.java @@ -19,7 +19,6 @@ package org.keycloak.protocol.oidc.utils; import java.util.HashMap; import java.util.Map; -import java.util.UUID; /** * Data associated with the oauth2 code. @@ -40,7 +39,7 @@ public class OAuth2Code { private static final String CODE_CHALLENGE_NOTE = "code_challenge"; private static final String CODE_CHALLENGE_METHOD_NOTE = "code_challenge_method"; - private final UUID id; + private final String id; private final int expiration; @@ -55,7 +54,7 @@ public class OAuth2Code { private final String codeChallengeMethod; - public OAuth2Code(UUID id, int expiration, String nonce, String scope, String redirectUriParam, + public OAuth2Code(String id, int expiration, String nonce, String scope, String redirectUriParam, String codeChallenge, String codeChallengeMethod) { this.id = id; this.expiration = expiration; @@ -68,7 +67,7 @@ public class OAuth2Code { private OAuth2Code(Map data) { - id = UUID.fromString(data.get(ID_NOTE)); + id = data.get(ID_NOTE); expiration = Integer.parseInt(data.get(EXPIRATION_NOTE)); nonce = data.get(NONCE_NOTE); scope = data.get(SCOPE_NOTE); @@ -98,7 +97,7 @@ public class OAuth2Code { } - public UUID getId() { + public String getId() { return id; } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java index d5657897821..75a48148f2e 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OAuth2CodeParser.java @@ -18,7 +18,6 @@ package org.keycloak.protocol.oidc.utils; import java.util.Map; -import java.util.UUID; import java.util.regex.Pattern; import org.jboss.logging.Logger; @@ -26,9 +25,9 @@ import org.keycloak.common.util.Time; import org.keycloak.events.Details; import org.keycloak.events.EventBuilder; import org.keycloak.models.AuthenticatedClientSessionModel; -import org.keycloak.models.CodeToTokenStoreProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserSessionModel; import org.keycloak.services.managers.UserSessionCrossDCManager; @@ -51,16 +50,16 @@ public class OAuth2CodeParser { * @return code parameter to be used in OAuth2 handshake */ public static String persistCode(KeycloakSession session, AuthenticatedClientSessionModel clientSession, OAuth2Code codeData) { - CodeToTokenStoreProvider codeStore = session.getProvider(CodeToTokenStoreProvider.class); + SingleUseObjectProvider codeStore = session.getProvider(SingleUseObjectProvider.class); - UUID key = codeData.getId(); + String key = codeData.getId(); if (key == null) { throw new IllegalStateException("ID not present in the data"); } Map serialized = codeData.serializeCode(); codeStore.put(key, clientSession.getUserSession().getRealm().getAccessCodeLifespan(), serialized); - return key.toString() + "." + clientSession.getUserSession().getId() + "." + clientSession.getClient().getId(); + return key + "." + clientSession.getUserSession().getId() + "." + clientSession.getClient().getId(); } @@ -91,9 +90,9 @@ public class OAuth2CodeParser { event.session(userSessionId); // Parse UUID - UUID codeUUID; + String codeUUID; try { - codeUUID = UUID.fromString(parsed[0]); + codeUUID = parsed[0]; } catch (IllegalArgumentException re) { logger.warn("Invalid format of the UUID in the code"); return result.illegalCode(); @@ -111,7 +110,7 @@ public class OAuth2CodeParser { result.clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID); - CodeToTokenStoreProvider codeStore = session.getProvider(CodeToTokenStoreProvider.class); + SingleUseObjectProvider codeStore = session.getProvider(SingleUseObjectProvider.class); Map codeData = codeStore.remove(codeUUID); // Either code not available or was already used diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 44b813d9f20..7797416d08d 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -17,7 +17,6 @@ package org.keycloak.protocol.saml; -import org.apache.http.HttpEntity; import org.apache.http.NameValuePair; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; @@ -43,7 +42,7 @@ import org.keycloak.models.KeyManager; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; -import org.keycloak.models.SamlArtifactSessionMappingStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.LoginProtocol; @@ -85,7 +84,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.io.IOException; -import java.io.InputStream; import java.net.URI; import java.security.PublicKey; import java.util.ArrayList; @@ -140,6 +138,9 @@ public class SamlProtocol implements LoginProtocol { public static final String SAML_LOGIN_REQUEST_FORCEAUTHN = "SAML_LOGIN_REQUEST_FORCEAUTHN"; public static final String SAML_FORCEAUTHN_REQUIREMENT = "true"; public static final String SAML_LOGOUT_INITIATOR_CLIENT_ID = "SAML_LOGOUT_INITIATOR_CLIENT_ID"; + public static final String USER_SESSION_ID = "userSessionId"; + public static final String CLIENT_SESSION_ID = "clientSessionId"; + protected static final Logger logger = Logger.getLogger(SamlProtocol.class); @@ -154,7 +155,7 @@ public class SamlProtocol implements LoginProtocol { protected EventBuilder event; protected ArtifactResolver artifactResolver; - protected SamlArtifactSessionMappingStoreProvider artifactSessionMappingStore; + protected SingleUseObjectProvider singleUseStore; @Override public SamlProtocol setSession(KeycloakSession session) { @@ -193,11 +194,11 @@ public class SamlProtocol implements LoginProtocol { return artifactResolver; } - private SamlArtifactSessionMappingStoreProvider getArtifactSessionMappingStore() { - if (artifactSessionMappingStore == null) { - artifactSessionMappingStore = session.getProvider(SamlArtifactSessionMappingStoreProvider.class); + private SingleUseObjectProvider getSingleUseStore() { + if (singleUseStore == null) { + singleUseStore = session.getProvider(SingleUseObjectProvider.class); } - return artifactSessionMappingStore; + return singleUseStore; } @Override @@ -929,7 +930,10 @@ public class SamlProtocol implements LoginProtocol { // Create artifact and store session mapping SAMLDataMarshaller marshaller = new SAMLDataMarshaller(); String artifact = getArtifactResolver().buildArtifact(clientSessionModel, entityId, marshaller.serialize(artifactResponseType)); - getArtifactSessionMappingStore().put(artifact, realm.getAccessCodeLifespan(), clientSessionModel); + HashMap notes = new HashMap<>(); + notes.put(USER_SESSION_ID, clientSessionModel.getUserSession().getId()); + notes.put(CLIENT_SESSION_ID, clientSessionModel.getClient().getId()); + getSingleUseStore().put(artifact, realm.getAccessCodeLifespan(), notes); return artifact; } diff --git a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java index 387ecb8f953..a6fb156ac02 100755 --- a/services/src/main/java/org/keycloak/protocol/saml/SamlService.java +++ b/services/src/main/java/org/keycloak/protocol/saml/SamlService.java @@ -60,8 +60,7 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.KeycloakUriInfo; import org.keycloak.models.RealmModel; -import org.keycloak.models.SamlArtifactSessionMappingModel; -import org.keycloak.models.SamlArtifactSessionMappingStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; import org.keycloak.models.UserSessionModel; import org.keycloak.protocol.AuthorizationEndpointBase; import org.keycloak.protocol.LoginProtocol; @@ -137,6 +136,7 @@ import java.security.PublicKey; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; @@ -1104,8 +1104,8 @@ public class SamlService extends AuthorizationEndpointBase { } - private SamlArtifactSessionMappingStoreProvider getArtifactSessionMappingStore() { - return session.getProvider(SamlArtifactSessionMappingStoreProvider.class); + private SingleUseObjectProvider getSingleUseStore() { + return session.getProvider(SingleUseObjectProvider.class); } /** @@ -1136,26 +1136,27 @@ public class SamlService extends AuthorizationEndpointBase { } // Obtain details of session that issued artifact and check if it corresponds to issuer of Resolve message - SamlArtifactSessionMappingModel sessionMapping = getArtifactSessionMappingStore().get(artifact); + Map sessionMapping = getSingleUseStore().get(artifact); if (sessionMapping == null) { logger.errorf("No data stored for artifact %s", artifact); return emptyArtifactResponseMessage(artifactResolveMessage, null); } - UserSessionModel userSessionModel = session.sessions().getUserSession(realm, sessionMapping.getUserSessionId()); + UserSessionModel userSessionModel = session.sessions().getUserSession(realm, sessionMapping.get(SamlProtocol.USER_SESSION_ID)); if (userSessionModel == null) { - logger.errorf("UserSession with id: %s, that corresponds to artifact: %s does not exist.", sessionMapping.getUserSessionId(), artifact); + logger.errorf("UserSession with id: %s, that corresponds to artifact: %s does not exist.", sessionMapping.get(SamlProtocol.USER_SESSION_ID), artifact); return emptyArtifactResponseMessage(artifactResolveMessage, null); } - AuthenticatedClientSessionModel clientSessionModel = userSessionModel.getAuthenticatedClientSessions().get(sessionMapping.getClientSessionId()); + AuthenticatedClientSessionModel clientSessionModel = userSessionModel.getAuthenticatedClientSessions().get(sessionMapping.get(SamlProtocol.CLIENT_SESSION_ID)); if (clientSessionModel == null) { - logger.errorf("ClientSession with id: %s, that corresponds to artifact: %s and UserSession: %s does not exist.", sessionMapping.getClientSessionId(), artifact, sessionMapping.getUserSessionId()); + logger.errorf("ClientSession with id: %s, that corresponds to artifact: %s and UserSession: %s does not exist.", + sessionMapping.get(SamlProtocol.CLIENT_SESSION_ID), artifact, sessionMapping.get(SamlProtocol.USER_SESSION_ID)); return emptyArtifactResponseMessage(artifactResolveMessage, null); } - ClientModel clientModel = getAndCheckClientModel(sessionMapping.getClientSessionId(), artifactResolveMessage.getIssuer().getValue()); + ClientModel clientModel = getAndCheckClientModel(sessionMapping.get(SamlProtocol.CLIENT_SESSION_ID), artifactResolveMessage.getIssuer().getValue()); SamlClient samlClient = new SamlClient(clientModel); // Check signature within ArtifactResolve request if client requires it @@ -1178,7 +1179,9 @@ public class SamlService extends AuthorizationEndpointBase { } // Artifact is successfully resolved, we can remove session mapping from storage - getArtifactSessionMappingStore().remove(artifact); + if (getSingleUseStore().remove(artifact) == null) { + logger.debugf("Artifact %s was already removed", artifact); + } Document artifactResponseDocument = null; ArtifactResponseType artifactResponseType = null; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/HandleArtifactStepBuilder.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/HandleArtifactStepBuilder.java index cf744c9839b..26d12d0460a 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/HandleArtifactStepBuilder.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/saml/HandleArtifactStepBuilder.java @@ -11,7 +11,8 @@ import org.apache.http.util.EntityUtils; import org.keycloak.common.util.KeyUtils; import org.keycloak.dom.saml.v2.assertion.NameIDType; import org.keycloak.dom.saml.v2.protocol.ArtifactResolveType; -import org.keycloak.models.SamlArtifactSessionMappingStoreProvider; +import org.keycloak.models.SingleUseObjectProvider; +import org.keycloak.protocol.saml.SamlProtocol; import org.keycloak.protocol.saml.SamlService; import org.keycloak.protocol.saml.profile.util.Soap; import org.keycloak.saml.BaseSAML2BindingBuilder; @@ -195,8 +196,8 @@ public class HandleArtifactStepBuilder extends SamlDocumentStepBuilder session.getProvider(SamlArtifactSessionMappingStoreProvider.class).get(artifact).getUserSessionId()); - sessionStateChecker.setClientSessionProvider(session -> session.getProvider(SamlArtifactSessionMappingStoreProvider.class).get(artifact).getClientSessionId()); + sessionStateChecker.setUserSessionProvider(session -> session.getProvider(SingleUseObjectProvider.class).get(artifact).get(SamlProtocol.USER_SESSION_ID)); + sessionStateChecker.setClientSessionProvider(session -> session.getProvider(SingleUseObjectProvider.class).get(artifact).get(SamlProtocol.CLIENT_SESSION_ID)); } HttpPost post = Soap.createMessage().addToBody(DocumentUtil.getDocument(transformed)).buildHttpPost(authServerSamlUrl); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java index a2367ec03cd..4ff15c85b8c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java @@ -707,7 +707,7 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { } @Test - public void testPooling() throws Exception { + public void testPolling() throws Exception { getTestingClient().testing().setTestingInfinispanTimeService(); try {