Hot Rod map storage: Client scope no-downtime store
This commit is contained in:
parent
9297a5e1b2
commit
6249e34177
12 changed files with 199 additions and 4 deletions
|
@ -1312,7 +1312,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
|
||||
@Override
|
||||
public void removeClientScopes(RealmModel realm) {
|
||||
realm.getClientScopesStream().map(ClientScopeModel::getId).forEach(id -> removeClientScope(realm, id));
|
||||
getClientScopesStream(realm).map(ClientScopeModel::getId).forEach(id -> removeClientScope(realm, id));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,11 +22,13 @@ import org.keycloak.Config;
|
|||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.AmphibianProviderFactory;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||
import org.keycloak.models.map.group.MapGroupEntity;
|
||||
import org.keycloak.models.map.role.MapRoleEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
|
||||
|
@ -37,6 +39,8 @@ import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntityD
|
|||
import org.keycloak.models.map.client.MapClientEntity;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.common.DeepCloner;
|
||||
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntityDelegate;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodEntityDescriptor;
|
||||
import org.keycloak.models.map.storage.hotRod.connections.HotRodConnectionProvider;
|
||||
import org.keycloak.models.map.storage.MapStorageProvider;
|
||||
|
@ -65,6 +69,7 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
private final static DeepCloner CLONER = new DeepCloner.Builder()
|
||||
.constructor(MapClientEntity.class, HotRodClientEntityDelegate::new)
|
||||
.constructor(MapProtocolMapperEntity.class, HotRodProtocolMapperEntityDelegate::new)
|
||||
.constructor(MapClientScopeEntity.class, HotRodClientScopeEntityDelegate::new)
|
||||
.constructor(MapGroupEntity.class, HotRodGroupEntityDelegate::new)
|
||||
.constructor(MapRoleEntity.class, HotRodRoleEntityDelegate::new)
|
||||
.constructor(MapUserEntity.class, HotRodUserEntityDelegate::new)
|
||||
|
@ -81,6 +86,11 @@ public class HotRodMapStorageProviderFactory implements AmphibianProviderFactory
|
|||
HotRodClientEntity.class,
|
||||
HotRodClientEntityDelegate::new));
|
||||
|
||||
ENTITY_DESCRIPTOR_MAP.put(ClientScopeModel.class,
|
||||
new HotRodEntityDescriptor<>(ClientScopeModel.class,
|
||||
HotRodClientScopeEntity.class,
|
||||
HotRodClientScopeEntityDelegate::new));
|
||||
|
||||
// Groups descriptor
|
||||
ENTITY_DESCRIPTOR_MAP.put(GroupModel.class,
|
||||
new HotRodEntityDescriptor<>(GroupModel.class,
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* 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.clientscope;
|
||||
|
||||
import org.infinispan.protostream.annotations.ProtoDoc;
|
||||
import org.infinispan.protostream.annotations.ProtoField;
|
||||
import org.keycloak.models.map.annotations.GenerateHotRodEntityImplementation;
|
||||
import org.keycloak.models.map.client.MapProtocolMapperEntity;
|
||||
import org.keycloak.models.map.clientscope.MapClientScopeEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.client.HotRodProtocolMapperEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.AbstractHotRodEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.common.HotRodAttributeEntityNonIndexed;
|
||||
import org.keycloak.models.map.storage.hotRod.common.UpdatableHotRodEntityDelegateImpl;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@GenerateHotRodEntityImplementation(
|
||||
implementInterface = "org.keycloak.models.map.clientscope.MapClientScopeEntity",
|
||||
inherits = "org.keycloak.models.map.storage.hotRod.clientscope.HotRodClientScopeEntity.AbstractHotRodClientScopeEntityDelegate"
|
||||
)
|
||||
@ProtoDoc("@Indexed")
|
||||
public class HotRodClientScopeEntity 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 String name;
|
||||
|
||||
@ProtoField(number = 5)
|
||||
public String protocol;
|
||||
|
||||
@ProtoField(number = 6)
|
||||
public String description;
|
||||
|
||||
@ProtoField(number = 7)
|
||||
public Collection<String> scopeMappings;
|
||||
|
||||
@ProtoField(number = 8)
|
||||
public Set<HotRodProtocolMapperEntity> protocolMappers;
|
||||
|
||||
@ProtoField(number = 9)
|
||||
public Set<HotRodAttributeEntityNonIndexed> attributes;
|
||||
|
||||
public static abstract class AbstractHotRodClientScopeEntityDelegate extends UpdatableHotRodEntityDelegateImpl<HotRodClientScopeEntity> implements MapClientScopeEntity {
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return getHotRodEntity().id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setId(String id) {
|
||||
HotRodClientScopeEntity entity = getHotRodEntity();
|
||||
if (entity.id != null) throw new IllegalStateException("Id cannot be changed");
|
||||
entity.id = id;
|
||||
entity.updated |= id != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<MapProtocolMapperEntity> getProtocolMapper(String id) {
|
||||
Set<MapProtocolMapperEntity> mappers = getProtocolMappers();
|
||||
if (mappers == null || mappers.isEmpty()) return Optional.empty();
|
||||
|
||||
return mappers.stream().filter(m -> Objects.equals(m.getId(), id)).findFirst();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeProtocolMapper(String id) {
|
||||
HotRodClientScopeEntity entity = getHotRodEntity();
|
||||
entity.updated |= entity.protocolMappers != null && entity.protocolMappers.removeIf(m -> Objects.equals(m.id, id));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return HotRodClientScopeEntityDelegate.entityEquals(this, o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return HotRodClientScopeEntityDelegate.entityHashCode(this);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ import org.keycloak.models.map.common.AbstractEntity;
|
|||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEntity;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -38,7 +39,7 @@ public class HotRodTypesUtils {
|
|||
}
|
||||
|
||||
public static <MapKey, MapValue, SetValue> Map<MapKey, MapValue> migrateSetToMap(Set<SetValue> set, Function<SetValue, MapKey> keyProducer, Function<SetValue, MapValue> valueProducer) {
|
||||
return set == null ? null : set.stream().collect(Collectors.toMap(keyProducer, valueProducer));
|
||||
return set == null ? null : set.stream().collect(HashMap::new, (m, v) -> m.put(keyProducer.apply(v), valueProducer.apply(v)), HashMap::putAll);
|
||||
}
|
||||
|
||||
public static <T, V> HotRodPair<T, V> createHotRodPairFromMapEntry(Map.Entry<T, V> entry) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.infinispan.protostream.GeneratedSchema;
|
|||
import org.infinispan.protostream.annotations.AutoProtoSchemaBuilder;
|
||||
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.clientscope.HotRodClientScopeEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.group.HotRodGroupEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.role.HotRodRoleEntity;
|
||||
import org.keycloak.models.map.storage.hotRod.user.HotRodUserConsentEntity;
|
||||
|
@ -37,6 +38,9 @@ import org.keycloak.models.map.storage.hotRod.user.HotRodUserFederatedIdentityEn
|
|||
HotRodClientEntity.class,
|
||||
HotRodProtocolMapperEntity.class,
|
||||
|
||||
// Client scopes
|
||||
HotRodClientScopeEntity.class,
|
||||
|
||||
// Groups
|
||||
HotRodGroupEntity.class,
|
||||
|
||||
|
|
|
@ -12,6 +12,14 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="client-scopes" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodClientScopeEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="groups" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="client-scopes" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodClientScopeEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="groups" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
|
|
|
@ -260,7 +260,7 @@
|
|||
"username": "${keycloak.connectionsHotRod.username:myuser}",
|
||||
"password": "${keycloak.connectionsHotRod.password:qwer1234!}",
|
||||
"enableSecurity": "${keycloak.connectionsHotRod.enableSecurity:true}",
|
||||
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:clients,groups,users,roles}"
|
||||
"reindexCaches": "${keycloak.connectionsHotRod.reindexCaches:clients,client-scopes,groups,users,roles}"
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1497,6 +1497,7 @@
|
|||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<keycloak.client.map.storage.provider>hotrod</keycloak.client.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.role.map.storage.provider>hotrod</keycloak.role.map.storage.provider>
|
||||
<keycloak.user.map.storage.provider>hotrod</keycloak.user.map.storage.provider>
|
||||
|
|
|
@ -10,6 +10,14 @@
|
|||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="client-scopes" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
<indexed-entity>kc.HotRodClientScopeEntity</indexed-entity>
|
||||
</indexed-entities>
|
||||
</indexing>
|
||||
<encoding media-type="application/x-protostream"/>
|
||||
</distributed-cache>
|
||||
<distributed-cache name="groups" mode="SYNC">
|
||||
<indexing>
|
||||
<indexed-entities>
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package org.keycloak.testsuite.model;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
@ -25,6 +27,7 @@ import static org.hamcrest.Matchers.notNullValue;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.ClientProvider;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -32,8 +35,11 @@ import org.keycloak.models.RealmProvider;
|
|||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -213,4 +219,42 @@ public class ClientModelTest extends KeycloakModelTest {
|
|||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientScopes() {
|
||||
List<String> clientScopes = new LinkedList<>();
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
ClientModel client = session.clients().addClient(realm, "myClientId");
|
||||
|
||||
ClientScopeModel clientScope1 = session.clientScopes().addClientScope(realm, "myClientScope1");
|
||||
clientScopes.add(clientScope1.getId());
|
||||
ClientScopeModel clientScope2 = session.clientScopes().addClientScope(realm, "myClientScope2");
|
||||
clientScopes.add(clientScope2.getId());
|
||||
|
||||
|
||||
client.addClientScope(clientScope1, true);
|
||||
client.addClientScope(clientScope2, false);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
List<String> actualClientScopes = session.clientScopes().getClientScopesStream(realm).map(ClientScopeModel::getId).collect(Collectors.toList());
|
||||
assertThat(actualClientScopes, containsInAnyOrder(clientScopes.toArray()));
|
||||
|
||||
ClientScopeModel clientScopeById = session.clientScopes().getClientScopeById(realm, clientScopes.get(0));
|
||||
assertThat(clientScopeById.getId(), is(clientScopes.get(0)));
|
||||
|
||||
session.clientScopes().removeClientScopes(realm);
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
List<ClientScopeModel> actualClientScopes = session.clientScopes().getClientScopesStream(realm).collect(Collectors.toList());
|
||||
assertThat(actualClientScopes, empty());
|
||||
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ public class HotRodMapStorage extends KeycloakModelParameters {
|
|||
public void updateConfig(Config cf) {
|
||||
cf.spi(AuthenticationSessionSpi.PROVIDER_ID).provider(MapRootAuthenticationSessionProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("client").provider(MapClientProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("clientScope").provider(MapClientScopeProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.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("realm").provider(MapRealmProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, ConcurrentHashMapStorageProviderFactory.PROVIDER_ID)
|
||||
.spi("role").provider(MapRoleProviderFactory.PROVIDER_ID).config(STORAGE_CONFIG, HotRodMapStorageProviderFactory.PROVIDER_ID)
|
||||
|
|
Loading…
Reference in a new issue