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
This commit is contained in:
parent
d3b43a9f59
commit
0d6bbd437f
59 changed files with 385 additions and 2060 deletions
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanCodeToTokenStoreProvider implements CodeToTokenStoreProvider {
|
|
||||||
|
|
||||||
public static final Logger logger = Logger.getLogger(InfinispanCodeToTokenStoreProvider.class);
|
|
||||||
|
|
||||||
private final Supplier<BasicCache<UUID, ActionTokenValueEntity>> codeCache;
|
|
||||||
private final KeycloakSession session;
|
|
||||||
|
|
||||||
public InfinispanCodeToTokenStoreProvider(KeycloakSession session, Supplier<BasicCache<UUID, ActionTokenValueEntity>> actionKeyCache) {
|
|
||||||
this.session = session;
|
|
||||||
this.codeCache = actionKeyCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(UUID codeId, int lifespanSeconds, Map<String, String> codeData) {
|
|
||||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(codeData);
|
|
||||||
|
|
||||||
try {
|
|
||||||
BasicCache<UUID, ActionTokenValueEntity> 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<String, String> remove(UUID codeId) {
|
|
||||||
try {
|
|
||||||
BasicCache<UUID, ActionTokenValueEntity> 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanCodeToTokenStoreProviderFactory implements CodeToTokenStoreProviderFactory {
|
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getLogger(InfinispanCodeToTokenStoreProviderFactory.class);
|
|
||||||
|
|
||||||
// Reuse "actionTokens" infinispan cache for now
|
|
||||||
private volatile Supplier<BasicCache<UUID, ActionTokenValueEntity>> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanOAuth2DeviceTokenStoreProvider implements OAuth2DeviceTokenStoreProvider {
|
|
||||||
|
|
||||||
public static final Logger logger = Logger.getLogger(InfinispanOAuth2DeviceTokenStoreProvider.class);
|
|
||||||
|
|
||||||
private final Supplier<BasicCache<String, ActionTokenValueEntity>> codeCache;
|
|
||||||
private final KeycloakSession session;
|
|
||||||
|
|
||||||
public InfinispanOAuth2DeviceTokenStoreProvider(KeycloakSession session, Supplier<BasicCache<String, ActionTokenValueEntity>> actionKeyCache) {
|
|
||||||
this.session = session;
|
|
||||||
this.codeCache = actionKeyCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OAuth2DeviceCodeModel getByDeviceCode(RealmModel realm, String deviceCode) {
|
|
||||||
try {
|
|
||||||
BasicCache<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, String> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanOAuth2DeviceTokenStoreProviderFactory implements OAuth2DeviceTokenStoreProviderFactory {
|
|
||||||
|
|
||||||
// Reuse "actionTokens" infinispan cache for now
|
|
||||||
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<BasicCache<UUID, ActionTokenValueEntity>> parDataCache;
|
|
||||||
|
|
||||||
public InfinispanPushedAuthzRequestStoreProvider(KeycloakSession session, Supplier<BasicCache<UUID, ActionTokenValueEntity>> actionKeyCache) {
|
|
||||||
this.parDataCache = actionKeyCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(UUID key, int lifespanSeconds, Map<String, String> codeData) {
|
|
||||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(codeData);
|
|
||||||
|
|
||||||
try {
|
|
||||||
BasicCache<UUID, ActionTokenValueEntity> 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<String, String> remove(UUID key) {
|
|
||||||
try {
|
|
||||||
BasicCache<UUID, ActionTokenValueEntity> 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<BasicCache<UUID, ActionTokenValueEntity>> 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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<BasicCache<UUID, String[]>> cacheSupplier;
|
|
||||||
|
|
||||||
public InfinispanSamlArtifactSessionMappingStoreProvider(Supplier<BasicCache<UUID, String[]>> actionKeyCache) {
|
|
||||||
this.cacheSupplier = actionKeyCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void put(String artifact, int lifespanSeconds, AuthenticatedClientSessionModel clientSessionModel) {
|
|
||||||
try {
|
|
||||||
BasicCache<UUID, String[]> 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<UUID, String[]> 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<UUID, String[]> 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<BasicCache<UUID, String[]>> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class InfinispanSingleUseObjectProvider implements SingleUseObjectProvider {
|
||||||
|
|
||||||
|
public static final Logger logger = Logger.getLogger(InfinispanSingleUseObjectProvider.class);
|
||||||
|
|
||||||
|
private final Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
||||||
|
private final KeycloakSession session;
|
||||||
|
|
||||||
|
public InfinispanSingleUseObjectProvider(KeycloakSession session, Supplier<BasicCache<String, ActionTokenValueEntity>> actionKeyCache) {
|
||||||
|
this.session = session;
|
||||||
|
this.tokenCache = actionKeyCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(String key, long lifespanSeconds, Map<String, String> notes) {
|
||||||
|
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(notes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
BasicCache<String, ActionTokenValueEntity> 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<String, String> get(String key) {
|
||||||
|
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||||
|
ActionTokenValueEntity actionTokenValueEntity = cache.get(key);
|
||||||
|
return actionTokenValueEntity != null ? actionTokenValueEntity.getNotes() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> remove(String key) {
|
||||||
|
try {
|
||||||
|
BasicCache<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||||
|
ActionTokenValueEntity existing = cache.remove(key);
|
||||||
|
return existing == null ? null : existing.getNotes();
|
||||||
|
} 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<String, String> notes) {
|
||||||
|
BasicCache<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> cache = tokenCache.get();
|
||||||
|
return cache.containsKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,7 +28,7 @@ import org.keycloak.Config;
|
||||||
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
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.models.sessions.infinispan.entities.ActionTokenValueEntity;
|
||||||
import org.keycloak.connections.infinispan.InfinispanUtil;
|
import org.keycloak.connections.infinispan.InfinispanUtil;
|
||||||
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY;
|
import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSessionProviderFactory.PROVIDER_PRIORITY;
|
||||||
|
@ -36,17 +36,17 @@ import static org.keycloak.models.sessions.infinispan.InfinispanAuthenticationSe
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
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
|
// Reuse "actionTokens" infinispan cache for now
|
||||||
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InfinispanSingleUseTokenStoreProvider create(KeycloakSession session) {
|
public InfinispanSingleUseObjectProvider create(KeycloakSession session) {
|
||||||
lazyInit(session);
|
lazyInit(session);
|
||||||
return new InfinispanSingleUseTokenStoreProvider(session, tokenCache);
|
return new InfinispanSingleUseObjectProvider(session, tokenCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lazyInit(KeycloakSession session) {
|
private void lazyInit(KeycloakSession session) {
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanSingleUseTokenStoreProvider implements SingleUseTokenStoreProvider {
|
|
||||||
|
|
||||||
public static final Logger logger = Logger.getLogger(InfinispanSingleUseTokenStoreProvider.class);
|
|
||||||
|
|
||||||
private final Supplier<BasicCache<String, ActionTokenValueEntity>> tokenCache;
|
|
||||||
private final KeycloakSession session;
|
|
||||||
|
|
||||||
public InfinispanSingleUseTokenStoreProvider(KeycloakSession session, Supplier<BasicCache<String, ActionTokenValueEntity>> 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<String, ActionTokenValueEntity> 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanTokenRevocationStoreProvider implements TokenRevocationStoreProvider {
|
|
||||||
|
|
||||||
public static final Logger logger = Logger.getLogger(InfinispanTokenRevocationStoreProvider.class);
|
|
||||||
|
|
||||||
private final Supplier<BasicCache<String, ActionTokenValueEntity>> 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<BasicCache<String, ActionTokenValueEntity>> tokenCache) {
|
|
||||||
this.session = session;
|
|
||||||
this.tokenCache = tokenCache;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void putRevokedToken(String tokenId, long lifespanSeconds) {
|
|
||||||
Map<String, String> data = Collections.singletonMap(REVOKED_KEY, "true");
|
|
||||||
ActionTokenValueEntity tokenValue = new ActionTokenValueEntity(data);
|
|
||||||
|
|
||||||
try {
|
|
||||||
BasicCache<String, ActionTokenValueEntity> 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<String, ActionTokenValueEntity> 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() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class InfinispanTokenRevocationStoreProviderFactory implements TokenRevocationStoreProviderFactory {
|
|
||||||
|
|
||||||
// Reuse "actionTokens" infinispan cache for now
|
|
||||||
private volatile Supplier<BasicCache<String, ActionTokenValueEntity>> 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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -15,4 +15,4 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.models.sessions.infinispan.InfinispanCodeToTokenStoreProviderFactory
|
org.keycloak.models.sessions.infinispan.InfinispanSingleUseObjectProviderFactory
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -23,7 +23,7 @@ import java.util.Map;
|
||||||
/**
|
/**
|
||||||
* Internal action token store provider.
|
* 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)
|
* which may reuse some components (eg. same infinispan cache)
|
||||||
*
|
*
|
||||||
* @author hmlnarik
|
* @author hmlnarik
|
||||||
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
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<String, String> 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<String, String> remove(UUID codeId);
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
||||||
*/
|
|
||||||
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<String, String> 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);
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
||||||
*/
|
|
||||||
public interface OAuth2DeviceTokenStoreProviderFactory extends ProviderFactory<OAuth2DeviceTokenStoreProvider> {
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
|
||||||
*/
|
|
||||||
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<? extends Provider> getProviderClass() {
|
|
||||||
return OAuth2DeviceTokenStoreProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return OAuth2DeviceTokenStoreProviderFactory.class;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<String, String> 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<String, String> remove(UUID key);
|
|
||||||
}
|
|
|
@ -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<PushedAuthzRequestStoreProvider> {
|
|
||||||
}
|
|
|
@ -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<? extends Provider> getProviderClass() {
|
|
||||||
return PushedAuthzRequestStoreProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return PushedAuthzRequestStoreProviderFactory.class;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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<SamlArtifactSessionMappingStoreProvider> {
|
|
||||||
}
|
|
|
@ -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<? extends Provider> getProviderClass() {
|
|
||||||
return SamlArtifactSessionMappingStoreProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return SamlArtifactSessionMappingStoreProviderFactory.class;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
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<String, String> notes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets data associated with the given key.
|
||||||
|
* @param key String
|
||||||
|
* @return Map<String, String> Data associated with the given key or {@code null} if there is no associated data.
|
||||||
|
*/
|
||||||
|
Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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);
|
||||||
|
}
|
|
@ -22,5 +22,5 @@ import org.keycloak.provider.ProviderFactory;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public interface CodeToTokenStoreProviderFactory extends ProviderFactory<CodeToTokenStoreProvider> {
|
public interface SingleUseObjectProviderFactory extends ProviderFactory<SingleUseObjectProvider> {
|
||||||
}
|
}
|
|
@ -24,9 +24,9 @@ import org.keycloak.provider.Spi;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class CodeToTokenStoreSpi implements Spi {
|
public class SingleUseObjectSpi implements Spi {
|
||||||
|
|
||||||
public static final String NAME = "codeToTokenStore";
|
public static final String NAME = "singleUseObject";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
|
@ -40,11 +40,11 @@ public class CodeToTokenStoreSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends Provider> getProviderClass() {
|
public Class<? extends Provider> getProviderClass() {
|
||||||
return CodeToTokenStoreProvider.class;
|
return SingleUseObjectProvider.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
return CodeToTokenStoreProviderFactory.class;
|
return SingleUseObjectProviderFactory.class;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface SingleUseTokenStoreProviderFactory extends ProviderFactory<SingleUseTokenStoreProvider> {
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
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<? extends Provider> getProviderClass() {
|
|
||||||
return SingleUseTokenStoreProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return SingleUseTokenStoreProviderFactory.class;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface TokenRevocationStoreProviderFactory extends ProviderFactory<TokenRevocationStoreProvider> {
|
|
||||||
}
|
|
|
@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
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<? extends Provider> getProviderClass() {
|
|
||||||
return TokenRevocationStoreProvider.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
|
||||||
return TokenRevocationStoreProviderFactory.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -26,13 +26,8 @@ org.keycloak.models.RealmSpi
|
||||||
org.keycloak.models.RoleSpi
|
org.keycloak.models.RoleSpi
|
||||||
org.keycloak.models.DeploymentStateSpi
|
org.keycloak.models.DeploymentStateSpi
|
||||||
org.keycloak.models.ActionTokenStoreSpi
|
org.keycloak.models.ActionTokenStoreSpi
|
||||||
org.keycloak.models.CodeToTokenStoreSpi
|
|
||||||
org.keycloak.models.OAuth2DeviceTokenStoreSpi
|
|
||||||
org.keycloak.models.OAuth2DeviceUserCodeSpi
|
org.keycloak.models.OAuth2DeviceUserCodeSpi
|
||||||
org.keycloak.models.SamlArtifactSessionMappingStoreSpi
|
org.keycloak.models.SingleUseObjectSpi
|
||||||
org.keycloak.models.PushedAuthzRequestStoreSpi
|
|
||||||
org.keycloak.models.SingleUseTokenStoreSpi
|
|
||||||
org.keycloak.models.TokenRevocationStoreSpi
|
|
||||||
org.keycloak.models.UserSessionSpi
|
org.keycloak.models.UserSessionSpi
|
||||||
org.keycloak.models.UserLoginFailureSpi
|
org.keycloak.models.UserLoginFailureSpi
|
||||||
org.keycloak.models.UserSpi
|
org.keycloak.models.UserSpi
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -29,7 +29,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
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.AuthenticationExecutionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.RealmModel;
|
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.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
|
||||||
|
@ -190,7 +189,7 @@ public class JWTClientAuthenticator extends AbstractClientAuthenticator {
|
||||||
throw new RuntimeException("Missing ID on the token");
|
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);
|
int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10);
|
||||||
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
|
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
|
||||||
logger.tracef("Added token '%s' to single-use cache. Lifespan: %d seconds, client: %s", token.getId(), lifespanInSecs, clientId);
|
logger.tracef("Added token '%s' to single-use cache. Lifespan: %d seconds, client: %s", token.getId(), lifespanInSecs, clientId);
|
||||||
|
|
|
@ -25,11 +25,9 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
import javax.ws.rs.core.MultivaluedMap;
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
import org.apache.http.client.utils.CloneUtils;
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.authentication.AuthenticationFlowError;
|
import org.keycloak.authentication.AuthenticationFlowError;
|
||||||
|
@ -37,9 +35,7 @@ import org.keycloak.authentication.ClientAuthenticationFlowContext;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
import org.keycloak.models.AuthenticationExecutionModel.Requirement;
|
||||||
import org.keycloak.models.SingleUseTokenStoreProvider;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.delegate.ClientModelLazyDelegate;
|
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper;
|
import org.keycloak.protocol.oidc.OIDCClientSecretConfigWrapper;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
|
@ -196,7 +192,7 @@ public class JWTClientSecretAuthenticator extends AbstractClientAuthenticator {
|
||||||
throw new RuntimeException("Missing ID on the token");
|
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);
|
int lifespanInSecs = Math.max(token.getExpiration() - currentTime, 10);
|
||||||
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
|
if (singleUseCache.putIfAbsent(token.getId(), lifespanInSecs)) {
|
||||||
|
|
||||||
|
|
|
@ -231,7 +231,7 @@ public class OIDCLoginProtocol implements LoginProtocol {
|
||||||
// Standard or hybrid flow
|
// Standard or hybrid flow
|
||||||
String code = null;
|
String code = null;
|
||||||
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
|
if (responseType.hasResponseType(OIDCResponseType.CODE)) {
|
||||||
OAuth2Code codeData = new OAuth2Code(UUID.randomUUID(),
|
OAuth2Code codeData = new OAuth2Code(UUID.randomUUID().toString(),
|
||||||
Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(),
|
Time.currentTime() + userSession.getRealm().getAccessCodeLifespan(),
|
||||||
nonce,
|
nonce,
|
||||||
authSession.getClientNote(OAuth2Constants.SCOPE),
|
authSession.getClientNote(OAuth2Constants.SCOPE),
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.TokenCategory;
|
import org.keycloak.TokenCategory;
|
||||||
import org.keycloak.TokenVerifier;
|
import org.keycloak.TokenVerifier;
|
||||||
import org.keycloak.authentication.authenticators.util.AcrStore;
|
import org.keycloak.authentication.authenticators.util.AcrStore;
|
||||||
import org.keycloak.authentication.authenticators.util.LoAUtil;
|
|
||||||
import org.keycloak.broker.oidc.OIDCIdentityProvider;
|
import org.keycloak.broker.oidc.OIDCIdentityProvider;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.cluster.ClusterProvider;
|
import org.keycloak.cluster.ClusterProvider;
|
||||||
|
@ -50,7 +49,7 @@ import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.TokenRevocationStoreProvider;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
|
@ -64,7 +63,6 @@ import org.keycloak.protocol.oidc.mappers.UserInfoTokenMapper;
|
||||||
import org.keycloak.rar.AuthorizationDetails;
|
import org.keycloak.rar.AuthorizationDetails;
|
||||||
import org.keycloak.representations.AuthorizationDetailsJSONRepresentation;
|
import org.keycloak.representations.AuthorizationDetailsJSONRepresentation;
|
||||||
import org.keycloak.rar.AuthorizationRequestContext;
|
import org.keycloak.rar.AuthorizationRequestContext;
|
||||||
import org.keycloak.protocol.oidc.utils.AcrUtils;
|
|
||||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
|
@ -1354,8 +1352,8 @@ public class TokenManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean test(AccessToken token) {
|
public boolean test(AccessToken token) {
|
||||||
TokenRevocationStoreProvider revocationStore = session.getProvider(TokenRevocationStoreProvider.class);
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
return !revocationStore.isRevoked(token.getId());
|
return !singleUseStore.contains(token.getId() + SingleUseObjectProvider.REVOKED_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.protocol.oidc.endpoints;
|
package org.keycloak.protocol.oidc.endpoints;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@ -41,7 +42,7 @@ import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.TokenRevocationStoreProvider;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
|
@ -257,9 +258,9 @@ public class TokenRevocationEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revokeAccessToken() {
|
private void revokeAccessToken() {
|
||||||
TokenRevocationStoreProvider revocationStore = session.getProvider(TokenRevocationStoreProvider.class);
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
int currentTime = Time.currentTime();
|
int currentTime = Time.currentTime();
|
||||||
long lifespanInSecs = Math.max(token.getExp() - currentTime, 10);
|
long lifespanInSecs = Math.max(token.getExp() - currentTime, 10);
|
||||||
revocationStore.putRevokedToken(token.getId(), lifespanInSecs);
|
singleUseStore.put(token.getId() + SingleUseObjectProvider.REVOKED_KEY, lifespanInSecs, Collections.emptyMap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.ClientSessionContext;
|
import org.keycloak.models.ClientSessionContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
import org.keycloak.models.OAuth2DeviceCodeModel;
|
||||||
import org.keycloak.models.OAuth2DeviceTokenStoreProvider;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
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.grants.ciba.endpoints.CibaRootEndpoint;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
import org.keycloak.protocol.oidc.endpoints.TokenEndpoint;
|
||||||
|
import org.keycloak.protocol.oidc.grants.device.DeviceGrantType;
|
||||||
import org.keycloak.services.CorsErrorResponseException;
|
import org.keycloak.services.CorsErrorResponseException;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.Urls;
|
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);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_GRANT, cpe.getErrorDetail(), Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
OAuth2DeviceCodeModel deviceCode = DeviceGrantType.getDeviceByDeviceCode(session, realm, request.getId());
|
||||||
OAuth2DeviceCodeModel deviceCode = store.getByDeviceCode(realm, request.getId());
|
|
||||||
|
|
||||||
if (deviceCode == null) {
|
if (deviceCode == null) {
|
||||||
// Auth Req ID has not put onto cache, no need to remove Auth Req ID.
|
// 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);
|
throw new CorsErrorResponseException(cors, OAuthErrorException.EXPIRED_TOKEN, "authentication timed out", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!store.isPollingAllowed(deviceCode)) {
|
if (!DeviceGrantType.isPollingAllowed(session, deviceCode)) {
|
||||||
logDebug("pooling.", request);
|
logDebug("polling.", request);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "too early to access", Response.Status.BAD_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());
|
UserSessionModel userSession = createUserSession(request, deviceCode.getAdditionalParams());
|
||||||
UserModel user = userSession.getUser();
|
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
|
// 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)
|
// (but in code-to-token request, it could just theoretically happen that they are not available)
|
||||||
|
|
|
@ -29,9 +29,10 @@ import org.keycloak.models.CibaConfig;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
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;
|
||||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelResponse.Status;
|
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.representations.AccessToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.Urls;
|
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);
|
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
OAuth2DeviceCodeModel deviceCode = DeviceEndpoint.getDeviceByUserCode(session, realm, bearerToken.getId());
|
||||||
OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, bearerToken.getId());
|
|
||||||
|
|
||||||
if (deviceCode == null) {
|
if (deviceCode == null) {
|
||||||
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "Invalid token", Response.Status.FORBIDDEN);
|
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) {
|
private void cancelRequest(String authResultId) {
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
OAuth2DeviceCodeModel userCode = DeviceEndpoint.getDeviceByUserCode(session, realm, authResultId);
|
||||||
OAuth2DeviceCodeModel userCode = store.getByUserCode(realm, authResultId);
|
DeviceGrantType.removeDeviceByDeviceCode(session, userCode.getDeviceCode());
|
||||||
store.removeDeviceCode(realm, userCode.getDeviceCode());
|
DeviceGrantType.removeDeviceByUserCode(session, realm, authResultId);
|
||||||
store.removeUserCode(realm, authResultId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void approveRequest(AccessToken authReqId, Map<String, String> additionalParams) {
|
private void approveRequest(AccessToken authReqId, Map<String, String> additionalParams) {
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
DeviceGrantType.approveUserCode(session, realm, authReqId.getId(), "fake", additionalParams);
|
||||||
store.approve(realm, authReqId.getId(), "fake", additionalParams);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void denyRequest(AccessToken authReqId, Status status) {
|
private void denyRequest(AccessToken authReqId, Status status) {
|
||||||
|
@ -172,9 +170,7 @@ public class BackchannelAuthenticationCallbackEndpoint extends AbstractCibaEndpo
|
||||||
event.error(Errors.CONSENT_DENIED);
|
event.error(Errors.CONSENT_DENIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
DeviceGrantType.denyUserCode(session, realm, authReqId.getId());
|
||||||
|
|
||||||
store.deny(realm, authReqId.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void sendClientNotificationRequest(ClientModel client, CibaConfig cibaConfig, OAuth2DeviceCodeModel deviceModel) {
|
protected void sendClientNotificationRequest(ClientModel client, CibaConfig cibaConfig, OAuth2DeviceCodeModel deviceModel) {
|
||||||
|
|
|
@ -27,9 +27,9 @@ import org.keycloak.models.CibaConfig;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
import org.keycloak.models.OAuth2DeviceCodeModel;
|
||||||
import org.keycloak.models.OAuth2DeviceTokenStoreProvider;
|
|
||||||
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
|
import org.keycloak.protocol.oidc.grants.ciba.CibaGrantType;
|
||||||
import org.keycloak.protocol.oidc.grants.ciba.channel.AuthenticationChannelProvider;
|
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,
|
* 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.
|
* that relies on cross-references for unsolicited user authentication requests from devices.
|
||||||
*/
|
*/
|
||||||
private void storeAuthenticationRequest(CIBAAuthenticationRequest request, CibaConfig cibaConfig, String authReqId) {
|
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
|
// To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code
|
||||||
int lifespanSeconds = expiresIn + poolingInterval + 10;
|
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<String, String> params) {
|
private CIBAAuthenticationRequest authorizeClient(MultivaluedMap<String, String> params) {
|
||||||
|
|
|
@ -26,14 +26,14 @@ import org.keycloak.events.Errors;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
|
||||||
import org.keycloak.models.ClientSessionContext;
|
import org.keycloak.models.ClientSessionContext;
|
||||||
import org.keycloak.models.KeycloakContext;
|
import org.keycloak.models.KeycloakContext;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakUriInfo;
|
import org.keycloak.models.KeycloakUriInfo;
|
||||||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
import org.keycloak.models.OAuth2DeviceCodeModel;
|
||||||
import org.keycloak.models.OAuth2DeviceTokenStoreProvider;
|
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -60,7 +60,7 @@ import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.stream.Stream;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||||
|
@ -101,8 +101,7 @@ public class DeviceGrantType {
|
||||||
String errorType = OAuthErrorException.SERVER_ERROR;
|
String errorType = OAuthErrorException.SERVER_ERROR;
|
||||||
if (error == LoginProtocol.Error.CONSENT_DENIED) {
|
if (error == LoginProtocol.Error.CONSENT_DENIED) {
|
||||||
String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE);
|
String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE);
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
if (!denyUserCode(session, realm, verifiedUserCode)) {
|
||||||
if (!store.deny(realm, verifiedUserCode)) {
|
|
||||||
// Already expired and removed in the store
|
// Already expired and removed in the store
|
||||||
errorType = OAuthErrorException.EXPIRED_TOKEN;
|
errorType = OAuthErrorException.EXPIRED_TOKEN;
|
||||||
} else {
|
} else {
|
||||||
|
@ -123,8 +122,7 @@ public class DeviceGrantType {
|
||||||
|
|
||||||
String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE);
|
String verifiedUserCode = authSession.getClientNote(DeviceGrantType.OAUTH2_DEVICE_VERIFIED_USER_CODE);
|
||||||
String userSessionId = clientSession.getUserSession().getId();
|
String userSessionId = clientSession.getUserSession().getId();
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
if (!approveUserCode(session, realm, verifiedUserCode, userSessionId, null)) {
|
||||||
if (!store.approve(realm, verifiedUserCode, userSessionId, null)) {
|
|
||||||
// Already expired and removed in the store
|
// Already expired and removed in the store
|
||||||
return Response.status(302).location(
|
return Response.status(302).location(
|
||||||
uriBuilder.queryParam(OAuth2Constants.ERROR, OAuthErrorException.EXPIRED_TOKEN)
|
uriBuilder.queryParam(OAuth2Constants.ERROR, OAuthErrorException.EXPIRED_TOKEN)
|
||||||
|
@ -133,7 +131,7 @@ public class DeviceGrantType {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now, remove the verified user code
|
// Now, remove the verified user code
|
||||||
store.removeUserCode(realm, verifiedUserCode);
|
removeDeviceByUserCode(session, realm, verifiedUserCode);
|
||||||
|
|
||||||
return Response.status(302).location(
|
return Response.status(302).location(
|
||||||
uriBuilder.build(realm.getName())
|
uriBuilder.build(realm.getName())
|
||||||
|
@ -145,6 +143,51 @@ public class DeviceGrantType {
|
||||||
return flow != null;
|
return flow != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OAuth2DeviceCodeModel getDeviceByDeviceCode(KeycloakSession session, RealmModel realm, String deviceCode) {
|
||||||
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
|
Map<String, String> 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<String, String> 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<String, String> formParams;
|
private MultivaluedMap<String, String> formParams;
|
||||||
private ClientModel client;
|
private ClientModel client;
|
||||||
|
|
||||||
|
@ -182,8 +225,7 @@ public class DeviceGrantType {
|
||||||
"Missing parameter: " + OAuth2Constants.DEVICE_CODE, Response.Status.BAD_REQUEST);
|
"Missing parameter: " + OAuth2Constants.DEVICE_CODE, Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
OAuth2DeviceCodeModel deviceCodeModel = getDeviceByDeviceCode(session, realm, deviceCode);
|
||||||
OAuth2DeviceCodeModel deviceCodeModel = store.getByDeviceCode(realm, deviceCode);
|
|
||||||
|
|
||||||
if (deviceCodeModel == null) {
|
if (deviceCodeModel == null) {
|
||||||
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
event.error(Errors.INVALID_OAUTH2_DEVICE_CODE);
|
||||||
|
@ -197,7 +239,7 @@ public class DeviceGrantType {
|
||||||
Response.Status.BAD_REQUEST);
|
Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!store.isPollingAllowed(deviceCodeModel)) {
|
if (!isPollingAllowed(session, deviceCodeModel)) {
|
||||||
event.error(Errors.SLOW_DOWN);
|
event.error(Errors.SLOW_DOWN);
|
||||||
throw new CorsErrorResponseException(cors, OAuthErrorException.SLOW_DOWN, "Slow down", Response.Status.BAD_REQUEST);
|
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
|
// Now, remove the device code
|
||||||
store.removeDeviceCode(realm, deviceCode);
|
removeDeviceByDeviceCode(session, deviceCode);
|
||||||
|
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
|
|
@ -30,11 +30,12 @@ import org.keycloak.forms.login.LoginFormsProvider;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.OAuth2DeviceCodeModel;
|
import org.keycloak.models.OAuth2DeviceCodeModel;
|
||||||
import org.keycloak.models.OAuth2DeviceTokenStoreProvider;
|
|
||||||
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
import org.keycloak.models.OAuth2DeviceUserCodeModel;
|
||||||
import org.keycloak.models.OAuth2DeviceUserCodeProvider;
|
import org.keycloak.models.OAuth2DeviceUserCodeProvider;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker;
|
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.MultivaluedMap;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.keycloak.protocol.oidc.grants.device.DeviceGrantType.OAUTH2_DEVICE_USER_CODE;
|
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
|
// To inform "expired_token" to the client, the lifespan of the cache provider is longer than device code
|
||||||
int lifespanSeconds = expiresIn + interval + 10;
|
int lifespanSeconds = expiresIn + interval + 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());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String deviceUrl = DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName())
|
String deviceUrl = DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName())
|
||||||
|
@ -212,10 +217,9 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe
|
||||||
} else {
|
} else {
|
||||||
// code exists, probably due to using a verification_uri_complete. Start the authentication considering the client
|
// code exists, probably due to using a verification_uri_complete. Start the authentication considering the client
|
||||||
// that started the flow.
|
// that started the flow.
|
||||||
OAuth2DeviceTokenStoreProvider store = session.getProvider(OAuth2DeviceTokenStoreProvider.class);
|
|
||||||
OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
|
OAuth2DeviceUserCodeProvider userCodeProvider = session.getProvider(OAuth2DeviceUserCodeProvider.class);
|
||||||
String formattedUserCode = userCodeProvider.format(userCode);
|
String formattedUserCode = userCodeProvider.format(userCode);
|
||||||
OAuth2DeviceCodeModel deviceCode = store.getByUserCode(realm, formattedUserCode);
|
OAuth2DeviceCodeModel deviceCode = getDeviceByUserCode(session, realm, formattedUserCode);
|
||||||
|
|
||||||
if (deviceCode == null) {
|
if (deviceCode == null) {
|
||||||
return invalidUserCodeResponse(Messages.OAUTH2_DEVICE_INVALID_USER_CODE, "device code not found (it may already have been used)");
|
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<String, String> 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 errorMessage Message code for the verification page
|
||||||
* @param reason For event details; not exposed to end user
|
* @param reason For event details; not exposed to end user
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.headers.SecurityHeadersProvider;
|
import org.keycloak.headers.SecurityHeadersProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
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.OIDCLoginProtocolService;
|
||||||
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker;
|
import org.keycloak.protocol.oidc.endpoints.AuthorizationEndpointChecker;
|
||||||
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
||||||
|
@ -146,8 +146,8 @@ public class ParEndpoint extends AbstractParEndpoint {
|
||||||
|
|
||||||
Map<String, String> params = new HashMap<>();
|
Map<String, String> params = new HashMap<>();
|
||||||
|
|
||||||
UUID key = UUID.randomUUID();
|
String key = UUID.randomUUID().toString();
|
||||||
String requestUri = REQUEST_URI_PREFIX + key.toString();
|
String requestUri = REQUEST_URI_PREFIX + key;
|
||||||
|
|
||||||
int expiresIn = realm.getParPolicy().getRequestUriLifespan();
|
int expiresIn = realm.getParPolicy().getRequestUriLifespan();
|
||||||
|
|
||||||
|
@ -158,8 +158,8 @@ public class ParEndpoint extends AbstractParEndpoint {
|
||||||
});
|
});
|
||||||
params.put(PAR_CREATED_TIME, String.valueOf(System.currentTimeMillis()));
|
params.put(PAR_CREATED_TIME, String.valueOf(System.currentTimeMillis()));
|
||||||
|
|
||||||
PushedAuthzRequestStoreProvider parStore = session.getProvider(PushedAuthzRequestStoreProvider.class);
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
parStore.put(key, expiresIn, params);
|
singleUseStore.put(key, expiresIn, params);
|
||||||
|
|
||||||
ParResponse parResponse = new ParResponse(requestUri, expiresIn);
|
ParResponse parResponse = new ParResponse(requestUri, expiresIn);
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,12 @@ package org.keycloak.protocol.oidc.par.endpoints.request;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.PushedAuthzRequestStoreProvider;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
import org.keycloak.protocol.oidc.endpoints.request.AuthorizationEndpointRequest;
|
||||||
import org.keycloak.protocol.oidc.endpoints.request.AuthzEndpointRequestObjectParser;
|
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) {
|
public AuthzEndpointParParser(KeycloakSession session, ClientModel client, String requestUri) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
PushedAuthzRequestStoreProvider parStore = session.getProvider(PushedAuthzRequestStoreProvider.class);
|
SingleUseObjectProvider singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
UUID key;
|
String key;
|
||||||
try {
|
try {
|
||||||
key = UUID.fromString(requestUri.substring(ParEndpoint.REQUEST_URI_PREFIX_LENGTH));
|
key = requestUri.substring(ParEndpoint.REQUEST_URI_PREFIX_LENGTH);
|
||||||
} catch (RuntimeException re) {
|
} catch (RuntimeException re) {
|
||||||
logger.warnf(re,"Unable to parse request_uri: %s", requestUri);
|
logger.warnf(re,"Unable to parse request_uri: %s", requestUri);
|
||||||
throw new RuntimeException("Unable to parse request_uri");
|
throw new RuntimeException("Unable to parse request_uri");
|
||||||
}
|
}
|
||||||
Map<String, String> retrievedRequest = parStore.remove(key);
|
Map<String, String> retrievedRequest = singleUseStore.remove(key);
|
||||||
if (retrievedRequest == null) {
|
if (retrievedRequest == null) {
|
||||||
throw new RuntimeException("PAR not found. not issued or used multiple times.");
|
throw new RuntimeException("PAR not found. not issued or used multiple times.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ package org.keycloak.protocol.oidc.utils;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data associated with the oauth2 code.
|
* 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_NOTE = "code_challenge";
|
||||||
private static final String CODE_CHALLENGE_METHOD_NOTE = "code_challenge_method";
|
private static final String CODE_CHALLENGE_METHOD_NOTE = "code_challenge_method";
|
||||||
|
|
||||||
private final UUID id;
|
private final String id;
|
||||||
|
|
||||||
private final int expiration;
|
private final int expiration;
|
||||||
|
|
||||||
|
@ -55,7 +54,7 @@ public class OAuth2Code {
|
||||||
private final String codeChallengeMethod;
|
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) {
|
String codeChallenge, String codeChallengeMethod) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.expiration = expiration;
|
this.expiration = expiration;
|
||||||
|
@ -68,7 +67,7 @@ public class OAuth2Code {
|
||||||
|
|
||||||
|
|
||||||
private OAuth2Code(Map<String, String> data) {
|
private OAuth2Code(Map<String, String> data) {
|
||||||
id = UUID.fromString(data.get(ID_NOTE));
|
id = data.get(ID_NOTE);
|
||||||
expiration = Integer.parseInt(data.get(EXPIRATION_NOTE));
|
expiration = Integer.parseInt(data.get(EXPIRATION_NOTE));
|
||||||
nonce = data.get(NONCE_NOTE);
|
nonce = data.get(NONCE_NOTE);
|
||||||
scope = data.get(SCOPE_NOTE);
|
scope = data.get(SCOPE_NOTE);
|
||||||
|
@ -98,7 +97,7 @@ public class OAuth2Code {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public UUID getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
package org.keycloak.protocol.oidc.utils;
|
package org.keycloak.protocol.oidc.utils;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
@ -26,9 +25,9 @@ import org.keycloak.common.util.Time;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.AuthenticatedClientSessionModel;
|
import org.keycloak.models.AuthenticatedClientSessionModel;
|
||||||
import org.keycloak.models.CodeToTokenStoreProvider;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
import org.keycloak.services.managers.UserSessionCrossDCManager;
|
||||||
|
|
||||||
|
@ -51,16 +50,16 @@ public class OAuth2CodeParser {
|
||||||
* @return code parameter to be used in OAuth2 handshake
|
* @return code parameter to be used in OAuth2 handshake
|
||||||
*/
|
*/
|
||||||
public static String persistCode(KeycloakSession session, AuthenticatedClientSessionModel clientSession, OAuth2Code codeData) {
|
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) {
|
if (key == null) {
|
||||||
throw new IllegalStateException("ID not present in the data");
|
throw new IllegalStateException("ID not present in the data");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, String> serialized = codeData.serializeCode();
|
Map<String, String> serialized = codeData.serializeCode();
|
||||||
codeStore.put(key, clientSession.getUserSession().getRealm().getAccessCodeLifespan(), serialized);
|
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);
|
event.session(userSessionId);
|
||||||
|
|
||||||
// Parse UUID
|
// Parse UUID
|
||||||
UUID codeUUID;
|
String codeUUID;
|
||||||
try {
|
try {
|
||||||
codeUUID = UUID.fromString(parsed[0]);
|
codeUUID = parsed[0];
|
||||||
} catch (IllegalArgumentException re) {
|
} catch (IllegalArgumentException re) {
|
||||||
logger.warn("Invalid format of the UUID in the code");
|
logger.warn("Invalid format of the UUID in the code");
|
||||||
return result.illegalCode();
|
return result.illegalCode();
|
||||||
|
@ -111,7 +110,7 @@ public class OAuth2CodeParser {
|
||||||
|
|
||||||
result.clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID);
|
result.clientSession = userSession.getAuthenticatedClientSessionByClient(clientUUID);
|
||||||
|
|
||||||
CodeToTokenStoreProvider codeStore = session.getProvider(CodeToTokenStoreProvider.class);
|
SingleUseObjectProvider codeStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
Map<String, String> codeData = codeStore.remove(codeUUID);
|
Map<String, String> codeData = codeStore.remove(codeUUID);
|
||||||
|
|
||||||
// Either code not available or was already used
|
// Either code not available or was already used
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.protocol.saml;
|
package org.keycloak.protocol.saml;
|
||||||
|
|
||||||
import org.apache.http.HttpEntity;
|
|
||||||
import org.apache.http.NameValuePair;
|
import org.apache.http.NameValuePair;
|
||||||
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
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.KeycloakSession;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.SamlArtifactSessionMappingStoreProvider;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
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.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.ArrayList;
|
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_LOGIN_REQUEST_FORCEAUTHN = "SAML_LOGIN_REQUEST_FORCEAUTHN";
|
||||||
public static final String SAML_FORCEAUTHN_REQUIREMENT = "true";
|
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 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);
|
protected static final Logger logger = Logger.getLogger(SamlProtocol.class);
|
||||||
|
|
||||||
|
@ -154,7 +155,7 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
protected EventBuilder event;
|
protected EventBuilder event;
|
||||||
|
|
||||||
protected ArtifactResolver artifactResolver;
|
protected ArtifactResolver artifactResolver;
|
||||||
protected SamlArtifactSessionMappingStoreProvider artifactSessionMappingStore;
|
protected SingleUseObjectProvider singleUseStore;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SamlProtocol setSession(KeycloakSession session) {
|
public SamlProtocol setSession(KeycloakSession session) {
|
||||||
|
@ -193,11 +194,11 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
return artifactResolver;
|
return artifactResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SamlArtifactSessionMappingStoreProvider getArtifactSessionMappingStore() {
|
private SingleUseObjectProvider getSingleUseStore() {
|
||||||
if (artifactSessionMappingStore == null) {
|
if (singleUseStore == null) {
|
||||||
artifactSessionMappingStore = session.getProvider(SamlArtifactSessionMappingStoreProvider.class);
|
singleUseStore = session.getProvider(SingleUseObjectProvider.class);
|
||||||
}
|
}
|
||||||
return artifactSessionMappingStore;
|
return singleUseStore;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -929,7 +930,10 @@ public class SamlProtocol implements LoginProtocol {
|
||||||
// Create artifact and store session mapping
|
// Create artifact and store session mapping
|
||||||
SAMLDataMarshaller marshaller = new SAMLDataMarshaller();
|
SAMLDataMarshaller marshaller = new SAMLDataMarshaller();
|
||||||
String artifact = getArtifactResolver().buildArtifact(clientSessionModel, entityId, marshaller.serialize(artifactResponseType));
|
String artifact = getArtifactResolver().buildArtifact(clientSessionModel, entityId, marshaller.serialize(artifactResponseType));
|
||||||
getArtifactSessionMappingStore().put(artifact, realm.getAccessCodeLifespan(), clientSessionModel);
|
HashMap<String, String> 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;
|
return artifact;
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,8 +60,7 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
import org.keycloak.models.KeycloakUriInfo;
|
import org.keycloak.models.KeycloakUriInfo;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.SamlArtifactSessionMappingModel;
|
import org.keycloak.models.SingleUseObjectProvider;
|
||||||
import org.keycloak.models.SamlArtifactSessionMappingStoreProvider;
|
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.AuthorizationEndpointBase;
|
import org.keycloak.protocol.AuthorizationEndpointBase;
|
||||||
import org.keycloak.protocol.LoginProtocol;
|
import org.keycloak.protocol.LoginProtocol;
|
||||||
|
@ -137,6 +136,7 @@ import java.security.PublicKey;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -1104,8 +1104,8 @@ public class SamlService extends AuthorizationEndpointBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private SamlArtifactSessionMappingStoreProvider getArtifactSessionMappingStore() {
|
private SingleUseObjectProvider getSingleUseStore() {
|
||||||
return session.getProvider(SamlArtifactSessionMappingStoreProvider.class);
|
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
|
// Obtain details of session that issued artifact and check if it corresponds to issuer of Resolve message
|
||||||
SamlArtifactSessionMappingModel sessionMapping = getArtifactSessionMappingStore().get(artifact);
|
Map<String, String> sessionMapping = getSingleUseStore().get(artifact);
|
||||||
|
|
||||||
if (sessionMapping == null) {
|
if (sessionMapping == null) {
|
||||||
logger.errorf("No data stored for artifact %s", artifact);
|
logger.errorf("No data stored for artifact %s", artifact);
|
||||||
return emptyArtifactResponseMessage(artifactResolveMessage, null);
|
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) {
|
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);
|
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) {
|
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);
|
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);
|
SamlClient samlClient = new SamlClient(clientModel);
|
||||||
|
|
||||||
// Check signature within ArtifactResolve request if client requires it
|
// 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
|
// 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;
|
Document artifactResponseDocument = null;
|
||||||
ArtifactResponseType artifactResponseType = null;
|
ArtifactResponseType artifactResponseType = null;
|
||||||
|
|
|
@ -11,7 +11,8 @@ import org.apache.http.util.EntityUtils;
|
||||||
import org.keycloak.common.util.KeyUtils;
|
import org.keycloak.common.util.KeyUtils;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.ArtifactResolveType;
|
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.SamlService;
|
||||||
import org.keycloak.protocol.saml.profile.util.Soap;
|
import org.keycloak.protocol.saml.profile.util.Soap;
|
||||||
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
import org.keycloak.saml.BaseSAML2BindingBuilder;
|
||||||
|
@ -195,8 +196,8 @@ public class HandleArtifactStepBuilder extends SamlDocumentStepBuilder<ArtifactR
|
||||||
|
|
||||||
if (beforeStepChecker != null && beforeStepChecker instanceof SessionStateChecker) {
|
if (beforeStepChecker != null && beforeStepChecker instanceof SessionStateChecker) {
|
||||||
SessionStateChecker sessionStateChecker = (SessionStateChecker) beforeStepChecker;
|
SessionStateChecker sessionStateChecker = (SessionStateChecker) beforeStepChecker;
|
||||||
sessionStateChecker.setUserSessionProvider(session -> session.getProvider(SamlArtifactSessionMappingStoreProvider.class).get(artifact).getUserSessionId());
|
sessionStateChecker.setUserSessionProvider(session -> session.getProvider(SingleUseObjectProvider.class).get(artifact).get(SamlProtocol.USER_SESSION_ID));
|
||||||
sessionStateChecker.setClientSessionProvider(session -> session.getProvider(SamlArtifactSessionMappingStoreProvider.class).get(artifact).getClientSessionId());
|
sessionStateChecker.setClientSessionProvider(session -> session.getProvider(SingleUseObjectProvider.class).get(artifact).get(SamlProtocol.CLIENT_SESSION_ID));
|
||||||
}
|
}
|
||||||
|
|
||||||
HttpPost post = Soap.createMessage().addToBody(DocumentUtil.getDocument(transformed)).buildHttpPost(authServerSamlUrl);
|
HttpPost post = Soap.createMessage().addToBody(DocumentUtil.getDocument(transformed)).buildHttpPost(authServerSamlUrl);
|
||||||
|
|
|
@ -707,7 +707,7 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testPooling() throws Exception {
|
public void testPolling() throws Exception {
|
||||||
getTestingClient().testing().setTestingInfinispanTimeService();
|
getTestingClient().testing().setTestingInfinispanTimeService();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
Loading…
Reference in a new issue