Hot Rod map storage: Authentication session no-downtime store
This commit is contained in:
parent
1604fb59e6
commit
0faf3987f6
14 changed files with 460 additions and 14 deletions
|
@ -103,7 +103,8 @@ public class GenerateHotRodEntityImplementationsProcessor extends AbstractGenera
|
||||||
abstractHotRodEntity = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity");
|
abstractHotRodEntity = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity");
|
||||||
hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils");
|
hotRodUtils = elements.getTypeElement("org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils");
|
||||||
|
|
||||||
boolean hasDeepClone = allMembers.stream().filter(el -> el.getKind() == ElementKind.METHOD).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
boolean hasDeepClone = allMembers.stream()
|
||||||
|
.filter(el -> el.getKind() == ElementKind.METHOD && !el.getModifiers().contains(Modifier.ABSTRACT)).anyMatch(el -> "deepClone".equals(el.getSimpleName().toString()));
|
||||||
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
boolean needsDeepClone = fieldGetters(methodsPerAttribute)
|
||||||
.map(ExecutableElement::getReturnType)
|
.map(ExecutableElement::getReturnType)
|
||||||
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
.anyMatch(fieldType -> ! isKnownCollectionOfImmutableFinalTypes(fieldType) && ! isImmutableFinalType(fieldType));
|
||||||
|
|
|
@ -28,9 +28,14 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
|
||||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||||
import org.keycloak.models.map.group.MapGroupEntity;
|
import org.keycloak.models.map.group.MapGroupEntity;
|
||||||
import org.keycloak.models.map.role.MapRoleEntity;
|
import org.keycloak.models.map.role.MapRoleEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntityDelegate;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
|
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntityDelegate;
|
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntityDelegate;
|
||||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||||
|
@ -57,6 +62,7 @@ import org.keycloak.models.map.user.MapUserCredentialEntity;
|
||||||
import org.keycloak.models.map.user.MapUserEntity;
|
import org.keycloak.models.map.user.MapUserEntity;
|
||||||
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
import org.keycloak.models.map.user.MapUserFederatedIdentityEntity;
|
||||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||||
|
import org.keycloak.sessions.RootAuthenticationSessionModel;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -67,6 +73,8 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
||||||
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
|
private static final Logger LOG = Logger.getLogger(HotRodMapStorageProviderFactory.class);
|
||||||
|
|
||||||
private final static DeepCloner CLONER = new DeepCloner.Builder()
|
private final static DeepCloner CLONER = new DeepCloner.Builder()
|
||||||
|
.constructor(MapRootAuthenticationSessionEntity.class, HotRodRootAuthenticationSessionEntityDelegate::new)
|
||||||
|
.constructor(MapAuthenticationSessionEntity.class, HotRodAuthenticationSessionEntityDelegate::new)
|
||||||
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
||||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
||||||
.constructor(MapClientScopeEntity.class, HotRodClientScopeEntityDelegate::new)
|
.constructor(MapClientScopeEntity.class, HotRodClientScopeEntityDelegate::new)
|
||||||
|
@ -80,6 +88,12 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
||||||
|
|
||||||
public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
|
public static final Map<Class<?>, HotRodEntityDescriptor<?, ?>> ENTITY_DESCRIPTOR_MAP = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
|
// Authentication sessions descriptor
|
||||||
|
ENTITY_DESCRIPTOR_MAP.put(RootAuthenticationSessionModel.class,
|
||||||
|
new HotRodEntityDescriptor<>(RootAuthenticationSessionModel.class,
|
||||||
|
HotRodRootAuthenticationSessionEntity.class,
|
||||||
|
HotRodRootAuthenticationSessionEntityDelegate::new));
|
||||||
|
|
||||||
// Clients descriptor
|
// Clients descriptor
|
||||||
ENTITY_DESCRIPTOR_MAP.put(ClientModel.class,
|
ENTITY_DESCRIPTOR_MAP.put(ClientModel.class,
|
||||||
new HotRodEntityDescriptor<>(ClientModel.class,
|
new HotRodEntityDescriptor<>(ClientModel.class,
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.storage.hotRod.authSession;
|
||||||
|
|
||||||
|
import org.infinispan.protostream.annotations.ProtoField;
|
||||||
|
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||||
|
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.HotRodPair;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.HotRodTypesUtils;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
|
||||||
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@GenerateHotRodEntityImplementation(
|
||||||
|
implementInterface = "org.keycloak.models.map.authSession.MapAuthenticationSessionEntity",
|
||||||
|
inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity.AbstractHotRodAuthenticationSessionEntityDelegate"
|
||||||
|
)
|
||||||
|
public class HotRodAuthenticationSessionEntity extends AbstractHotRodEntity {
|
||||||
|
|
||||||
|
@ProtoField(number = 1)
|
||||||
|
public String tabId;
|
||||||
|
|
||||||
|
@ProtoField(number = 2)
|
||||||
|
public String clientUUID;
|
||||||
|
|
||||||
|
@ProtoField(number = 3)
|
||||||
|
public String authUserId;
|
||||||
|
|
||||||
|
@ProtoField(number = 4)
|
||||||
|
public Integer timestamp;
|
||||||
|
|
||||||
|
@ProtoField(number = 5)
|
||||||
|
public String redirectUri;
|
||||||
|
|
||||||
|
@ProtoField(number = 6)
|
||||||
|
public String action;
|
||||||
|
|
||||||
|
@ProtoField(number = 7)
|
||||||
|
public Set<String> clientScopes;
|
||||||
|
|
||||||
|
@ProtoField(number = 8)
|
||||||
|
public Set<HotRodPair<String, HotRodExecutionStatus>> executionStatuses;
|
||||||
|
|
||||||
|
@ProtoField(number = 9)
|
||||||
|
public String protocol;
|
||||||
|
|
||||||
|
@ProtoField(number = 10)
|
||||||
|
public Set<HotRodPair<String, String>> clientNotes;
|
||||||
|
|
||||||
|
@ProtoField(number = 11)
|
||||||
|
public Set<HotRodPair<String, String>> authNotes;
|
||||||
|
|
||||||
|
@ProtoField(number = 12)
|
||||||
|
public Set<String> requiredActions;
|
||||||
|
|
||||||
|
@ProtoField(number = 13)
|
||||||
|
public Set<HotRodPair<String, String>> userSessionNotes;
|
||||||
|
|
||||||
|
public static abstract class AbstractHotRodAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodAuthenticationSessionEntity> implements MapAuthenticationSessionEntity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, AuthenticationSessionModel.ExecutionStatus> getExecutionStatuses() {
|
||||||
|
Set<HotRodPair<String, HotRodExecutionStatus>> executionStatuses = getHotRodEntity().executionStatuses;
|
||||||
|
if (executionStatuses == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return executionStatuses.stream().collect(Collectors.toMap(HotRodPair::getKey,
|
||||||
|
v -> AuthenticationSessionModel.ExecutionStatus.valueOf(v.getValue().name())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExecutionStatuses(Map<String, AuthenticationSessionModel.ExecutionStatus> executionStatus) {
|
||||||
|
HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity();
|
||||||
|
Set<HotRodPair<String, HotRodExecutionStatus>> executionStatusSet = executionStatus == null ? null :
|
||||||
|
executionStatus.entrySet().stream()
|
||||||
|
.map(e -> new HotRodPair<>(e.getKey(), HotRodExecutionStatus.valueOf(e.getValue().name())))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
hotRodEntity.updated |= ! Objects.equals(hotRodEntity.executionStatuses, executionStatusSet);
|
||||||
|
hotRodEntity.executionStatuses = executionStatusSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setExecutionStatus(String authenticator, AuthenticationSessionModel.ExecutionStatus status) {
|
||||||
|
HotRodAuthenticationSessionEntity hotRodEntity = getHotRodEntity();
|
||||||
|
if (hotRodEntity.executionStatuses == null) {
|
||||||
|
hotRodEntity.executionStatuses = new HashSet<>();
|
||||||
|
}
|
||||||
|
boolean valueUndefined = status == null;
|
||||||
|
hotRodEntity.updated |= HotRodTypesUtils.removeFromSetByMapKey(hotRodEntity.executionStatuses, authenticator, HotRodTypesUtils::getKey);
|
||||||
|
hotRodEntity.updated |= !valueUndefined && hotRodEntity.executionStatuses.add(new HotRodPair<>(authenticator, HotRodExecutionStatus.valueOf(status.name())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return HotRodAuthenticationSessionEntityDelegate.entityEquals(this, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return HotRodAuthenticationSessionEntityDelegate.entityHashCode(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.storage.hotRod.authSession;
|
||||||
|
|
||||||
|
import org.infinispan.protostream.annotations.ProtoEnumValue;
|
||||||
|
|
||||||
|
public enum HotRodExecutionStatus {
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 0)
|
||||||
|
FAILED,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 1)
|
||||||
|
SUCCESS,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 2)
|
||||||
|
SETUP_REQUIRED,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 3)
|
||||||
|
ATTEMPTED,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 4)
|
||||||
|
SKIPPED,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 5)
|
||||||
|
CHALLENGED,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 6)
|
||||||
|
EVALUATED_TRUE,
|
||||||
|
|
||||||
|
@ProtoEnumValue(number = 7)
|
||||||
|
EVALUATED_FALSE
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 Red Hat, Inc. and/or its affiliates
|
||||||
|
* and other contributors as indicated by the @author tags.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.keycloak.models.map.storage.hotRod.authSession;
|
||||||
|
|
||||||
|
import org.infinispan.protostream.annotations.ProtoDoc;
|
||||||
|
import org.infinispan.protostream.annotations.ProtoField;
|
||||||
|
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||||
|
import org.keycloak.models.map.authSession.MapAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.common.UpdatableEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@GenerateHotRodEntityImplementation(
|
||||||
|
implementInterface = "org.keycloak.models.map.authSession.MapRootAuthenticationSessionEntity",
|
||||||
|
inherits = "org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity.AbstractHotRodRootAuthenticationSessionEntityDelegate"
|
||||||
|
)
|
||||||
|
@ProtoDoc("@Indexed")
|
||||||
|
public class HotRodRootAuthenticationSessionEntity extends AbstractHotRodEntity {
|
||||||
|
|
||||||
|
@ProtoField(number = 1, required = true)
|
||||||
|
public int entityVersion = 1;
|
||||||
|
|
||||||
|
@ProtoField(number = 2, required = true)
|
||||||
|
public String id;
|
||||||
|
|
||||||
|
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||||
|
@ProtoField(number = 3)
|
||||||
|
public String realmId;
|
||||||
|
|
||||||
|
@ProtoDoc("@Field(index = Index.YES, store = Store.YES)")
|
||||||
|
@ProtoField(number = 4)
|
||||||
|
public Integer timestamp;
|
||||||
|
|
||||||
|
@ProtoField(number = 5)
|
||||||
|
public Set<HotRodAuthenticationSessionEntity> authenticationSessions;
|
||||||
|
|
||||||
|
public static abstract class AbstractHotRodRootAuthenticationSessionEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodRootAuthenticationSessionEntity> implements MapRootAuthenticationSessionEntity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return getHotRodEntity().id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setId(String id) {
|
||||||
|
HotRodRootAuthenticationSessionEntity entity = getHotRodEntity();
|
||||||
|
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||||
|
entity.id = id;
|
||||||
|
entity.updated |= id != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<MapAuthenticationSessionEntity> getAuthenticationSession(String tabId) {
|
||||||
|
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
|
||||||
|
if (rootAuthSession.authenticationSessions == null || rootAuthSession.authenticationSessions.isEmpty()) return Optional.empty();
|
||||||
|
|
||||||
|
return rootAuthSession.authenticationSessions.stream()
|
||||||
|
.filter(as -> Objects.equals(as.tabId, tabId))
|
||||||
|
.findFirst()
|
||||||
|
.map(HotRodAuthenticationSessionEntityDelegate::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean removeAuthenticationSession(String tabId) {
|
||||||
|
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
|
||||||
|
boolean removed = rootAuthSession.authenticationSessions != null &&
|
||||||
|
rootAuthSession.authenticationSessions.removeIf(c -> Objects.equals(c.tabId, tabId));
|
||||||
|
rootAuthSession.updated |= removed;
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isUpdated() {
|
||||||
|
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
|
||||||
|
return rootAuthSession.updated ||
|
||||||
|
Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).stream().anyMatch(MapAuthenticationSessionEntity::isUpdated);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clearUpdatedFlag() {
|
||||||
|
HotRodRootAuthenticationSessionEntity rootAuthSession = getHotRodEntity();
|
||||||
|
rootAuthSession.updated = false;
|
||||||
|
Optional.ofNullable(getAuthenticationSessions()).orElseGet(Collections::emptySet).forEach(UpdatableEntity::clearUpdatedFlag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return HotRodRootAuthenticationSessionEntityDelegate.entityEquals(this, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return HotRodRootAuthenticationSessionEntityDelegate.entityHashCode(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@
|
||||||
package org.keycloak.models.map.storage.hotRod.common;
|
package org.keycloak.models.map.storage.hotRod.common;
|
||||||
|
|
||||||
import org.keycloak.models.map.common.AbstractEntity;
|
import org.keycloak.models.map.common.AbstractEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
|
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
|
||||||
|
|
||||||
|
@ -110,4 +111,8 @@ public class HotRodTypesUtils {
|
||||||
public static <T, V> Set<V> migrateSet(Set<T> p0, Function<T, V> migrator) {
|
public static <T, V> Set<V> migrateSet(Set<T> p0, Function<T, V> migrator) {
|
||||||
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toSet());
|
return p0 == null ? null : p0.stream().map(migrator).collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getKey(HotRodAuthenticationSessionEntity hotRodAuthenticationSessionEntity) {
|
||||||
|
return hotRodAuthenticationSessionEntity.tabId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,9 @@ package org.keycloak.models.map.storage.hotRod.common;
|
||||||
|
|
||||||
import org.infinispan.protostream.GeneratedSchema;
|
import org.infinispan.protostream.GeneratedSchema;
|
||||||
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
|
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodAuthenticationSessionEntity;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodExecutionStatus;
|
||||||
|
import org.keycloak.models.map.storage.hotRod.authSession.HotRodRootAuthenticationSessionEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
import org.keycloak.models.map.storage.hotRod.client.HotRodClientEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
|
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
|
||||||
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity;
|
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity;
|
||||||
|
@ -34,6 +37,11 @@ import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEn
|
||||||
*/
|
*/
|
||||||
@AutoProtoSchemaBuilder(
|
@AutoProtoSchemaBuilder(
|
||||||
includeClasses = {
|
includeClasses = {
|
||||||
|
// Authentication sessions
|
||||||
|
HotRodRootAuthenticationSessionEntity.class,
|
||||||
|
HotRodAuthenticationSessionEntity.class,
|
||||||
|
HotRodExecutionStatus.class,
|
||||||
|
|
||||||
// Clients
|
// Clients
|
||||||
HotRodClientEntity.class,
|
HotRodClientEntity.class,
|
||||||
HotRodProtocolMapperEntity.class,
|
HotRodProtocolMapperEntity.class,
|
||||||
|
|
|
@ -3,6 +3,14 @@
|
||||||
<infinispan>
|
<infinispan>
|
||||||
<cache-container>
|
<cache-container>
|
||||||
<!-- Specify all remote caches that should be created on the Infinispan server. -->
|
<!-- Specify all remote caches that should be created on the Infinispan server. -->
|
||||||
|
<distributed-cache name="auth-sessions" mode="SYNC">
|
||||||
|
<indexing>
|
||||||
|
<indexed-entities>
|
||||||
|
<indexed-entity>kc.HotRodRootAuthenticationSessionEntity</indexed-entity>
|
||||||
|
</indexed-entities>
|
||||||
|
</indexing>
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
</distributed-cache>
|
||||||
<distributed-cache name="clients" mode="SYNC">
|
<distributed-cache name="clients" mode="SYNC">
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
|
|
|
@ -5,6 +5,14 @@
|
||||||
<transport stack="udp"/>
|
<transport stack="udp"/>
|
||||||
|
|
||||||
<!-- Specify all remote caches that should be created on the embedded Infinispan server. -->
|
<!-- Specify all remote caches that should be created on the embedded Infinispan server. -->
|
||||||
|
<distributed-cache name="auth-sessions" mode="SYNC">
|
||||||
|
<indexing>
|
||||||
|
<indexed-entities>
|
||||||
|
<indexed-entity>kc.HotRodRootAuthenticationSessionEntity</indexed-entity>
|
||||||
|
</indexed-entities>
|
||||||
|
</indexing>
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
</distributed-cache>
|
||||||
<distributed-cache name="clients" mode="SYNC">
|
<distributed-cache name="clients" mode="SYNC">
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
|
|
|
@ -266,7 +266,7 @@
|
||||||
"username": "${keycloak.connectionsHotRod.username:myuser}",
|
"username": "${keycloak.connectionsHotRod.username:myuser}",
|
||||||
"password": "${keycloak.connectionsHotRod.password:qwer1234!}",
|
"password": "${keycloak.connectionsHotRod.password:qwer1234!}",
|
||||||
"enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}",
|
"enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}",
|
||||||
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:clients,client-scopes,groups,users,roles}"
|
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:auth-sessions,clients,client-scopes,groups,users,roles}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -1496,6 +1496,7 @@
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
|
<keycloak.authSession.map.storage.provider>hotrod</keycloak.authSession.map.storage.provider>
|
||||||
<keycloak.client.map.storage.provider>hotrod</keycloak.client.map.storage.provider>
|
<keycloak.client.map.storage.provider>hotrod</keycloak.client.map.storage.provider>
|
||||||
<keycloak.clientScope.map.storage.provider>hotrod</keycloak.clientScope.map.storage.provider>
|
<keycloak.clientScope.map.storage.provider>hotrod</keycloak.clientScope.map.storage.provider>
|
||||||
<keycloak.group.map.storage.provider>hotrod</keycloak.group.map.storage.provider>
|
<keycloak.group.map.storage.provider>hotrod</keycloak.group.map.storage.provider>
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
<infinispan>
|
<infinispan>
|
||||||
<cache-container>
|
<cache-container>
|
||||||
<transport stack="udp"/>
|
<transport stack="udp"/>
|
||||||
|
<distributed-cache name="auth-sessions" mode="SYNC">
|
||||||
|
<indexing>
|
||||||
|
<indexed-entities>
|
||||||
|
<indexed-entity>kc.HotRodRootAuthenticationSessionEntity</indexed-entity>
|
||||||
|
</indexed-entities>
|
||||||
|
</indexing>
|
||||||
|
<encoding media-type="application/x-protostream"/>
|
||||||
|
</distributed-cache>
|
||||||
<distributed-cache name="clients" mode="SYNC">
|
<distributed-cache name="clients" mode="SYNC">
|
||||||
<indexing>
|
<indexing>
|
||||||
<indexed-entities>
|
<indexed-entities>
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateConfig(Config cf) {
|
public void updateConfig(Config cf) {
|
||||||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
.spi("group").provider(MapGroupProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.testsuite.model.session;
|
package org.keycloak.testsuite.model.session;
|
||||||
|
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
@ -32,15 +33,17 @@ import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||||
import org.keycloak.testsuite.model.RequireProvider;
|
import org.keycloak.testsuite.model.RequireProvider;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createClients;
|
import static org.keycloak.testsuite.model.session.UserSessionPersisterProviderTest.createClients;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
* @author <a href="mailto:mkanis@redhat.com">Martin Kanis</a>
|
||||||
*/
|
*/
|
||||||
@RequireProvider(value = AuthenticationSessionProvider.class, only = InfinispanAuthenticationSessionProviderFactory.PROVIDER_ID)
|
@RequireProvider(value = AuthenticationSessionProvider.class)
|
||||||
public class AuthenticationSessionTest extends KeycloakModelTest {
|
public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
|
|
||||||
private String realmId;
|
private String realmId;
|
||||||
|
@ -49,6 +52,7 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
public void createEnvironment(KeycloakSession s) {
|
public void createEnvironment(KeycloakSession s) {
|
||||||
RealmModel realm = s.realms().createRealm("test");
|
RealmModel realm = s.realms().createRealm("test");
|
||||||
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
realm.setDefaultRole(s.roles().addRealmRole(realm, Constants.DEFAULT_ROLES_ROLE_PREFIX + "-" + realm.getName()));
|
||||||
|
realm.setAccessCodeLifespanLogin(1800);
|
||||||
|
|
||||||
this.realmId = realm.getId();
|
this.realmId = realm.getId();
|
||||||
|
|
||||||
|
@ -61,10 +65,12 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@RequireProvider(value = AuthenticationSessionProvider.class, only = InfinispanAuthenticationSessionProviderFactory.PROVIDER_ID)
|
||||||
public void testLimitAuthSessions() {
|
public void testLimitAuthSessions() {
|
||||||
RootAuthenticationSessionModel ras = withRealm(realmId, (session, realm) -> session.authenticationSessions().createRootAuthenticationSession(realm));
|
AtomicReference<String> rootAuthSessionId = new AtomicReference<>();
|
||||||
|
|
||||||
List<String> tabIds = withRealm(realmId, (session, realm) -> {
|
List<String> tabIds = withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel ras = session.authenticationSessions().createRootAuthenticationSession(realm);
|
||||||
|
rootAuthSessionId.set(ras.getId());
|
||||||
ClientModel client = realm.getClientByClientId("test-app");
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
return IntStream.range(0, 300)
|
return IntStream.range(0, 300)
|
||||||
.mapToObj(i -> {
|
.mapToObj(i -> {
|
||||||
|
@ -76,6 +82,7 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
});
|
});
|
||||||
|
|
||||||
withRealm(realmId, (session, realm) -> {
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel ras = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
ClientModel client = realm.getClientByClientId("test-app");
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
|
|
||||||
// create 301st auth session
|
// create 301st auth session
|
||||||
|
@ -88,4 +95,101 @@ public class AuthenticationSessionTest extends KeycloakModelTest {
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAuthSessions() {
|
||||||
|
AtomicReference<String> rootAuthSessionId = new AtomicReference<>();
|
||||||
|
List<String> tabIds = withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
|
||||||
|
rootAuthSessionId.set(rootAuthSession.getId());
|
||||||
|
|
||||||
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
|
return IntStream.range(0, 5)
|
||||||
|
.mapToObj(i -> {
|
||||||
|
AuthenticationSessionModel authSession = rootAuthSession.createAuthenticationSession(client);
|
||||||
|
authSession.setExecutionStatus("username", AuthenticationSessionModel.ExecutionStatus.ATTEMPTED);
|
||||||
|
authSession.setAuthNote("foo", "bar");
|
||||||
|
authSession.setClientNote("foo", "bar");
|
||||||
|
return authSession;
|
||||||
|
})
|
||||||
|
.map(AuthenticationSessionModel::getTabId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
|
Assert.assertNotNull(rootAuthSession);
|
||||||
|
Assert.assertEquals(rootAuthSessionId.get(), rootAuthSession.getId());
|
||||||
|
|
||||||
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
|
tabIds.forEach(tabId -> {
|
||||||
|
AuthenticationSessionModel authSession = rootAuthSession.getAuthenticationSession(client, tabId);
|
||||||
|
Assert.assertNotNull(authSession);
|
||||||
|
|
||||||
|
Assert.assertEquals(AuthenticationSessionModel.ExecutionStatus.ATTEMPTED, authSession.getExecutionStatus().get("username"));
|
||||||
|
Assert.assertEquals("bar", authSession.getAuthNote("foo"));
|
||||||
|
Assert.assertEquals("bar", authSession.getClientNote("foo"));
|
||||||
|
});
|
||||||
|
|
||||||
|
// remove first two auth sessions
|
||||||
|
rootAuthSession.removeAuthenticationSessionByTabId(tabIds.get(0));
|
||||||
|
rootAuthSession.removeAuthenticationSessionByTabId(tabIds.get(1));
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
|
Assert.assertNotNull(rootAuthSession);
|
||||||
|
Assert.assertEquals(rootAuthSessionId.get(), rootAuthSession.getId());
|
||||||
|
|
||||||
|
assertThat(rootAuthSession.getAuthenticationSessions(), Matchers.aMapWithSize(3));
|
||||||
|
|
||||||
|
Assert.assertNull(rootAuthSession.getAuthenticationSessions().get(tabIds.get(0)));
|
||||||
|
Assert.assertNull(rootAuthSession.getAuthenticationSessions().get(tabIds.get(1)));
|
||||||
|
IntStream.range(2,4).mapToObj(i -> rootAuthSession.getAuthenticationSessions().get(tabIds.get(i))).forEach(Assert::assertNotNull);
|
||||||
|
|
||||||
|
session.authenticationSessions().removeRootAuthenticationSession(realm, rootAuthSession);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
|
Assert.assertNull(rootAuthSession);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveExpiredAuthSessions() {
|
||||||
|
AtomicReference<String> rootAuthSessionId = new AtomicReference<>();
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().createRootAuthenticationSession(realm);
|
||||||
|
ClientModel client = realm.getClientByClientId("test-app");
|
||||||
|
rootAuthSession.createAuthenticationSession(client);
|
||||||
|
rootAuthSessionId.set(rootAuthSession.getId());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
|
Assert.assertNotNull(rootAuthSession);
|
||||||
|
|
||||||
|
Time.setOffset(1900);
|
||||||
|
// not needed with Infinispan where expiration handles Infinispan itself
|
||||||
|
session.authenticationSessions().removeExpired(realm);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
withRealm(realmId, (session, realm) -> {
|
||||||
|
RootAuthenticationSessionModel rootAuthSession = session.authenticationSessions().getRootAuthenticationSession(realm, rootAuthSessionId.get());
|
||||||
|
Assert.assertNull(rootAuthSession);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue