From bdccfef5132538abacf61ac87f09b91e08e062c6 Mon Sep 17 00:00:00 2001 From: mhajas Date: Tue, 11 Aug 2020 14:27:39 +0200 Subject: [PATCH] KEYCLOAK-14973 Create GroupStorageManager --- .../cache/infinispan/RealmCacheSession.java | 2 +- .../group/GroupStorageProviderFactory.java | 109 ++++++++ .../group/GroupStorageProviderSpi.java | 82 ++++++ .../services/org.keycloak.provider.Spi | 1 + .../org/keycloak/models/GroupProvider.java | 38 +-- .../org/keycloak/models/KeycloakSession.java | 5 + .../java/org/keycloak/models/RealmModel.java | 20 ++ .../storage/group/GroupLookupProvider.java | 64 +++++ .../storage/group/GroupStorageProvider.java | 22 ++ .../group/GroupStorageProviderModel.java | 56 ++++ .../services/DefaultKeycloakSession.java | 12 +- .../storage/AbstractStorageManager.java | 152 +++++++++++ .../keycloak/storage/GroupStorageManager.java | 124 +++++++++ .../HardcodedGroupStorageProvider.java | 192 +++++++++++++ .../HardcodedGroupStorageProviderFactory.java | 66 +++++ ....storage.group.GroupStorageProviderFactory | 1 + .../federation/storage/GroupStorageTest.java | 257 ++++++++++++++++++ .../base/src/test/resources/testrealm.json | 3 + 18 files changed, 1168 insertions(+), 38 deletions(-) create mode 100644 server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderSpi.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/group/GroupLookupProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProviderModel.java create mode 100644 services/src/main/java/org/keycloak/storage/AbstractStorageManager.java create mode 100644 services/src/main/java/org/keycloak/storage/GroupStorageManager.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProviderFactory.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.group.GroupStorageProviderFactory create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/GroupStorageTest.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index a0cdfc6931..3495913bab 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -167,7 +167,7 @@ public class RealmCacheSession implements CacheRealmProvider { public GroupProvider getGroupDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (groupDelegate != null) return groupDelegate; - groupDelegate = session.groupLocalStorage(); + groupDelegate = session.groupStorageManager(); return groupDelegate; } diff --git a/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderFactory.java new file mode 100644 index 0000000000..148381501d --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderFactory.java @@ -0,0 +1,109 @@ +/* + * 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.storage.group; + +import org.keycloak.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public interface GroupStorageProviderFactory extends ComponentFactory { + + + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + @Override + T create(KeycloakSession session, ComponentModel model); + + /** + * This is the name of the provider. + * + * @return + */ + @Override + String getId(); + + @Override + default void init(Config.Scope config) { + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + } + + @Override + default void close() { + } + + @Override + default String getHelpText() { + return ""; + } + + @Override + default List getConfigProperties() { + return Collections.EMPTY_LIST; + } + + @Override + default void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + } + + /** + * Called when GroupStorageProviderModel is created. This allows you to do initialization of any additional configuration + * you need to add. + * + * @param session + * @param realm + * @param model + */ + @Override + default void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { + } + + /** + * configuration properties that are common across all GroupStorageProvider implementations + * + * @return + */ + @Override + default + List getCommonProviderConfigProperties() { + return GroupStorageProviderSpi.commonConfig(); + } + + @Override + default + Map getTypeMetadata() { + return new HashMap<>(); + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderSpi.java b/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderSpi.java new file mode 100644 index 0000000000..32c7d6c6d3 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/storage/group/GroupStorageProviderSpi.java @@ -0,0 +1,82 @@ +/* + * 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.storage.group; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; +import org.keycloak.storage.role.RoleStorageProvider; +import org.keycloak.storage.role.RoleStorageProviderFactory; + +import java.util.Collections; +import java.util.List; + +public class GroupStorageProviderSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "group-storage"; + } + + @Override + public Class getProviderClass() { + return GroupStorageProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return GroupStorageProviderFactory.class; + } + + private static final List commonConfig; + + static { + //corresponds to properties defined in CacheableStorageProviderModel and PrioritizedComponentModel + List config = ProviderConfigurationBuilder.create() + .property() + .name("enabled").type(ProviderConfigProperty.BOOLEAN_TYPE).add() + .property() + .name("priority").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("cachePolicy").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("maxLifespan").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("evictionHour").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("evictionMinute").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("evictionDay").type(ProviderConfigProperty.STRING_TYPE).add() + .property() + .name("cacheInvalidBefore").type(ProviderConfigProperty.STRING_TYPE).add() + .build(); + commonConfig = Collections.unmodifiableList(config); + } + + public static List commonConfig() { + return commonConfig; + } + +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 280ff6f1e6..49f5402f52 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -76,6 +76,7 @@ org.keycloak.keys.PublicKeyStorageSpi org.keycloak.keys.KeySpi org.keycloak.storage.client.ClientStorageProviderSpi org.keycloak.storage.role.RoleStorageProviderSpi +org.keycloak.storage.group.GroupStorageProviderSpi org.keycloak.crypto.SignatureSpi org.keycloak.crypto.ClientSignatureVerifierSpi org.keycloak.crypto.HashSpi diff --git a/server-spi/src/main/java/org/keycloak/models/GroupProvider.java b/server-spi/src/main/java/org/keycloak/models/GroupProvider.java index 96482a3623..51333418fe 100644 --- a/server-spi/src/main/java/org/keycloak/models/GroupProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/GroupProvider.java @@ -18,6 +18,7 @@ package org.keycloak.models; import org.keycloak.provider.Provider; +import org.keycloak.storage.group.GroupLookupProvider; import java.util.List; import java.util.stream.Collectors; @@ -29,7 +30,7 @@ import java.util.stream.Stream; * @author mhajas * */ -public interface GroupProvider extends Provider { +public interface GroupProvider extends Provider, GroupLookupProvider { /** * Returns a group from the given realm with the corresponding id @@ -43,15 +44,6 @@ public interface GroupProvider extends Provider { return getGroupById(realm, id); } - /** - * Returns a group from the given realm with the corresponding id - * - * @param realm Realm. - * @param id Id. - * @return GroupModel with the corresponding id. - */ - GroupModel getGroupById(RealmModel realm, String id); - /** * Returns groups for the given realm. * @@ -160,32 +152,6 @@ public interface GroupProvider extends Provider { */ Stream getTopLevelGroupsStream(RealmModel realm, Integer firstResult, Integer maxResults); - /** - * Returns groups with the given string in name for the given realm. - * - * @param realm Realm. - * @param search Searched string. - * @param firstResult First result to return. Ignored if {@code null}. - * @param maxResults Maximum number of results to return. Ignored if {@code null}. - * @return List of groups with the given string in name. - * @deprecated Use {@link #searchForGroupByNameStream(RealmModel, String, Integer, Integer)} searchForGroupByNameStream} instead. - */ - @Deprecated - default List searchForGroupByName(RealmModel realm, String search, Integer firstResult, Integer maxResults) { - return searchForGroupByNameStream(realm, search, firstResult, maxResults).collect(Collectors.toList()); - } - - /** - * Returns groups with the given string in name for the given realm. - * - * @param realm Realm. - * @param search Searched string. - * @param firstResult First result to return. Ignored if {@code null}. - * @param maxResults Maximum number of results to return. Ignored if {@code null}. - * @return Stream of groups with the given string in name. - */ - Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults); - /** * Creates a new group with the given name in the given realm. * Effectively the same as {@code createGroup(realm, null, name, null)}. diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 721bf0bf43..42dc44871d 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -170,6 +170,11 @@ public interface KeycloakSession { */ RoleProvider roleStorageManager(); + /** + * @return GroupStorageManager instance + */ + GroupProvider groupStorageManager(); + /** * Un-cached view of all users in system including users loaded by UserStorageProviders * diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index ecca698d24..0765714bf1 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -19,6 +19,7 @@ package org.keycloak.models; import org.keycloak.common.enums.SslRequired; import org.keycloak.component.ComponentModel; +import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderEvent; import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.UserStorageProviderModel; @@ -423,6 +424,16 @@ public interface RealmModel extends RoleContainerModel { void removeComponents(String parentId); List getComponents(String parentId, String providerType); + /** + * Returns stream of ComponentModels for specific parentId and providerType. + * @param parentId id of parent + * @param providerType type of provider + * @return stream of ComponentModels + */ + default Stream getComponentsStream(String parentId, String providerType) { + return getComponents(parentId, providerType).stream(); + } + List getComponents(String parentId); List getComponents(); @@ -458,6 +469,15 @@ public interface RealmModel extends RoleContainerModel { return list; } + /** + * Returns stream of ComponentModels that represent StorageProviders for class storageProviderClass in this realm + * @param storageProviderClass class + * @return stream of StorageProviders + */ + default Stream getStorageProviders(Class storageProviderClass) { + return getComponentsStream(getId(), storageProviderClass.getName()); + } + String getLoginTheme(); void setLoginTheme(String name); diff --git a/server-spi/src/main/java/org/keycloak/storage/group/GroupLookupProvider.java b/server-spi/src/main/java/org/keycloak/storage/group/GroupLookupProvider.java new file mode 100644 index 0000000000..136ef7b351 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/group/GroupLookupProvider.java @@ -0,0 +1,64 @@ +/* + * 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.storage.group; + +import org.keycloak.models.GroupModel; +import org.keycloak.models.RealmModel; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public interface GroupLookupProvider { + + /** + * Returns a group from the given realm with the corresponding id + * + * @param realm Realm. + * @param id Id. + * @return GroupModel with the corresponding id. + */ + GroupModel getGroupById(RealmModel realm, String id); + + /** + * Returns groups with the given string in name for the given realm. + * + * @param realm Realm. + * @param search Searched string. + * @param firstResult First result to return. Ignored if {@code null}. + * @param maxResults Maximum number of results to return. Ignored if {@code null}. + * @return List of groups with the given string in name. + * @deprecated Use {@link #searchForGroupByNameStream(RealmModel, String, Integer, Integer)} searchForGroupByNameStream} instead. + */ + @Deprecated + default List searchForGroupByName(RealmModel realm, String search, Integer firstResult, Integer maxResults) { + return searchForGroupByNameStream(realm, search, firstResult, maxResults).collect(Collectors.toList()); + } + + /** + * Returns groups with the given string in name for the given realm. + * + * @param realm Realm. + * @param search Searched string. + * @param firstResult First result to return. Ignored if {@code null}. + * @param maxResults Maximum number of results to return. Ignored if {@code null}. + * @return Stream of groups with the given string in name. + */ + Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults); + + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProvider.java new file mode 100644 index 0000000000..50b291ee7c --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProvider.java @@ -0,0 +1,22 @@ +/* + * 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.storage.group; + +import org.keycloak.provider.Provider; + +public interface GroupStorageProvider extends Provider, GroupLookupProvider { +} diff --git a/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProviderModel.java new file mode 100644 index 0000000000..3662ff0a38 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/group/GroupStorageProviderModel.java @@ -0,0 +1,56 @@ +/* + * 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.storage.group; + +import org.keycloak.component.ComponentModel; +import org.keycloak.storage.CacheableStorageProviderModel; + +/** + * Stored configuration of a Group Storage provider instance. + */ +public class GroupStorageProviderModel extends CacheableStorageProviderModel { + + public GroupStorageProviderModel() { + setProviderType(GroupStorageProvider.class.getName()); + } + + public GroupStorageProviderModel(ComponentModel copy) { + super(copy); + } + + private transient Boolean enabled; + + @Override + public void setEnabled(boolean flag) { + enabled = flag; + getConfig().putSingle(ENABLED, Boolean.toString(flag)); + } + + @Override + public boolean isEnabled() { + if (enabled == null) { + String val = getConfig().getFirst(ENABLED); + if (val == null) { + enabled = true; + } else { + enabled = Boolean.valueOf(val); + } + } + return enabled; + + } +} diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 1c23a03d40..22627b2e3c 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -43,6 +43,7 @@ import org.keycloak.services.clientpolicy.ClientPolicyManager; import org.keycloak.services.clientpolicy.DefaultClientPolicyManager; import org.keycloak.sessions.AuthenticationSessionProvider; import org.keycloak.storage.ClientStorageManager; +import org.keycloak.storage.GroupStorageManager; import org.keycloak.storage.RoleStorageManager; import org.keycloak.storage.UserStorageManager; import org.keycloak.storage.federated.UserFederatedStorageProvider; @@ -75,6 +76,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private UserStorageManager userStorageManager; private ClientStorageManager clientStorageManager; private RoleStorageManager roleStorageManager; + private GroupStorageManager groupStorageManager; private UserCredentialStoreManager userCredentialStorageManager; private UserSessionProvider sessionProvider; private AuthenticationSessionProvider authenticationSessionProvider; @@ -122,7 +124,7 @@ public class DefaultKeycloakSession implements KeycloakSession { if (cache != null) { return cache; } else { - return groupLocalStorage(); + return groupStorageManager(); } } @@ -228,6 +230,14 @@ public class DefaultKeycloakSession implements KeycloakSession { return roleStorageManager; } + @Override + public GroupProvider groupStorageManager() { + if (groupStorageManager == null) { + groupStorageManager = new GroupStorageManager(this); + } + return groupStorageManager; + } + @Override public UserProvider userStorageManager() { diff --git a/services/src/main/java/org/keycloak/storage/AbstractStorageManager.java b/services/src/main/java/org/keycloak/storage/AbstractStorageManager.java new file mode 100644 index 0000000000..5b9102fe8b --- /dev/null +++ b/services/src/main/java/org/keycloak/storage/AbstractStorageManager.java @@ -0,0 +1,152 @@ +/* + * 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.storage; + +import org.keycloak.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.Provider; +import org.keycloak.utils.ServicesUtils; + +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * + * @param This type will be used for looking for factories that produce instances of desired providers + * @param Type of model used for creating provider, it needs to extend + * CacheableStorageProviderModel as it has {@code isEnabled()} method and also extend + * PrioritizedComponentModel which is required for sorting providers based on its + * priorities + */ +public abstract class AbstractStorageManager { + + /** + * Timeouts are used as time boundary for obtaining models from an external storage. Default value is set + * to 3000 milliseconds and it's configurable. + */ + private static final Long STORAGE_PROVIDER_DEFAULT_TIMEOUT = 3000L; + protected final KeycloakSession session; + private final Class providerTypeClass; + private final Function toStorageProviderModelTypeFunction; + private final String configScope; + private Long storageProviderTimeout; + + public AbstractStorageManager(KeycloakSession session, Class providerTypeClass, Function toStorageProviderModelTypeFunction, String configScope) { + this.session = session; + this.providerTypeClass = providerTypeClass; + this.toStorageProviderModelTypeFunction = toStorageProviderModelTypeFunction; + this.configScope = configScope; + } + + protected Long getStorageProviderTimeout() { + if (storageProviderTimeout == null) { + storageProviderTimeout = Config.scope(configScope).getLong("storageProviderTimeout", STORAGE_PROVIDER_DEFAULT_TIMEOUT); + } + return storageProviderTimeout; + } + + /** + * Returns a factory with the providerId, which produce instances of type CreatedProviderType + * @param providerId id of factory that produce desired instances + * @return A factory that implements {@code ComponentFactory} + */ + protected ComponentFactory getStorageProviderFactory(String providerId) { + return (ComponentFactory) session.getKeycloakSessionFactory() + .getProviderFactory(providerTypeClass, providerId); + } + + /** + * + * @param realm realm + * @return enabled storage providers for realm and @{code getProviderTypeClass()} + */ + protected Stream getEnabledStorageProviders(RealmModel realm) { + return getStorageProviderModels(realm, providerTypeClass) + .map(toStorageProviderModelTypeFunction) + .filter(StorageProviderModelType::isEnabled) + .sorted(StorageProviderModelType.comparator) + .map(this::getStorageProviderInstance); + } + + /** + * Gets all enabled StorageProviders, applies applyFunction on each of them and then join the results together. + * + * !! Each StorageProvider has a limited time to respond, if it fails to do it, empty stream is returned !! + * + * @param realm realm + * @param applyFunction function that is applied on StorageProviders + * @param result of applyFunction + * @return a stream with all results from all StorageProviders + */ + protected Stream applyOnEnabledStorageProvidersWithTimeout(RealmModel realm, Function> applyFunction) { + return getEnabledStorageProviders(realm).flatMap(ServicesUtils.timeBound(session, + getStorageProviderTimeout(), applyFunction)); + } + + /** + * Returns an instance of provider with the providerId within the realm. + * @param realm realm + * @param providerId id of ComponentModel within database/storage + * @return an instance of type CreatedProviderType + */ + protected ProviderType getStorageProviderInstance(RealmModel realm, String providerId) { + ComponentModel componentModel = realm.getComponent(providerId); + if (componentModel == null) { + return null; + } + + return getStorageProviderInstance(toStorageProviderModelTypeFunction.apply(componentModel)); + } + + /** + * Returns an instance of provider for the model + * @param model StorageProviderModel obtained from database/storage + * @return an instance of type CreatedProviderType + */ + protected ProviderType getStorageProviderInstance(StorageProviderModelType model) { + if (model == null || !model.isEnabled()) { + return null; + } + + @SuppressWarnings("unchecked") + ProviderType instance = (ProviderType) session.getAttribute(model.getId()); + if (instance != null) return instance; + + ComponentFactory factory = getStorageProviderFactory(model.getProviderId()); + instance = factory.create(session, model); + if (instance == null) { + throw new IllegalStateException("StorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance"); + } + session.enlistForClose(instance); + session.setAttribute(model.getId(), instance); + return instance; + } + + /** + * Stream of ComponentModels of storageType. + * @param realm Realm. + * @param storageType Type. + * @return Stream of ComponentModels + */ + public static Stream getStorageProviderModels(RealmModel realm, Class storageType) { + return realm.getStorageProviders(storageType); + } +} diff --git a/services/src/main/java/org/keycloak/storage/GroupStorageManager.java b/services/src/main/java/org/keycloak/storage/GroupStorageManager.java new file mode 100644 index 0000000000..4a928a9231 --- /dev/null +++ b/services/src/main/java/org/keycloak/storage/GroupStorageManager.java @@ -0,0 +1,124 @@ +/* + * 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.storage; + +import org.keycloak.models.GroupModel; +import org.keycloak.models.GroupProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.storage.group.GroupLookupProvider; +import org.keycloak.storage.group.GroupStorageProvider; +import org.keycloak.storage.group.GroupStorageProviderModel; + +import java.util.stream.Stream; + +public class GroupStorageManager extends AbstractStorageManager implements GroupProvider { + + public GroupStorageManager(KeycloakSession session) { + super(session, GroupStorageProvider.class, GroupStorageProviderModel::new, "group"); + } + + /* GROUP PROVIDER LOOKUP METHODS - implemented by group storage providers */ + + @Override + public GroupModel getGroupById(RealmModel realm, String id) { + StorageId storageId = new StorageId(id); + if (storageId.getProviderId() == null) { + return session.groupLocalStorage().getGroupById(realm, id); + } + + GroupLookupProvider provider = getStorageProviderInstance(realm, storageId.getProviderId()); + if (provider == null) return null; + + return provider.getGroupById(realm, id); + } + + /** + * Obtaining groups from an external client storage is time-bounded. In case the external group storage + * isn't available at least groups from a local storage are returned. For this purpose + * the {@link org.keycloak.services.DefaultKeycloakSessionFactory#getClientStorageProviderTimeout()} property is used. + * Default value is 3000 milliseconds and it's configurable. + * See {@link org.keycloak.services.DefaultKeycloakSessionFactory} for details. + * + */ + @Override + public Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { + Stream local = session.groupLocalStorage().searchForGroupByNameStream(realm, search, firstResult, maxResults); + Stream ext = applyOnEnabledStorageProvidersWithTimeout(realm, + p -> ((GroupLookupProvider) p).searchForGroupByNameStream(realm, search, firstResult, maxResults)); + + return Stream.concat(local, ext); + } + + /* GROUP PROVIDER METHODS - provided only by local storage (e.g. not supported by storage providers) */ + + @Override + public Stream getGroupsStream(RealmModel realm) { + return session.groupLocalStorage().getGroupsStream(realm); + } + + @Override + public Long getGroupsCount(RealmModel realm, Boolean onlyTopGroups) { + return session.groupLocalStorage().getGroupsCount(realm, onlyTopGroups); + } + + @Override + public Long getGroupsCountByNameContaining(RealmModel realm, String search) { + return session.groupLocalStorage().getGroupsCountByNameContaining(realm, search); + } + + @Override + public Stream getGroupsByRoleStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) { + return session.groupLocalStorage().getGroupsByRoleStream(realm, role, firstResult, maxResults); + } + + @Override + public Stream getTopLevelGroupsStream(RealmModel realm) { + return session.groupLocalStorage().getTopLevelGroupsStream(realm); + } + + @Override + public Stream getTopLevelGroupsStream(RealmModel realm, Integer firstResult, Integer maxResults) { + return session.groupLocalStorage().getTopLevelGroupsStream(realm, firstResult, maxResults); + } + + @Override + public GroupModel createGroup(RealmModel realm, String id, String name, GroupModel toParent) { + return session.groupLocalStorage().createGroup(realm, id, name, toParent); + } + + @Override + public boolean removeGroup(RealmModel realm, GroupModel group) { + return session.groupLocalStorage().removeGroup(realm, group); + } + + @Override + public void moveGroup(RealmModel realm, GroupModel group, GroupModel toParent) { + session.groupLocalStorage().moveGroup(realm, group, toParent); + } + + @Override + public void addTopLevelGroup(RealmModel realm, GroupModel subGroup) { + session.groupLocalStorage().addTopLevelGroup(realm, subGroup); + } + + @Override + public void close() { + + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java new file mode 100644 index 0000000000..9717995023 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProvider.java @@ -0,0 +1,192 @@ +/* + * 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.testsuite.federation; + +import org.jboss.logging.Logger; +import org.keycloak.models.ClientModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.storage.ReadOnlyException; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.group.GroupStorageProvider; +import org.keycloak.storage.group.GroupStorageProviderModel; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class HardcodedGroupStorageProvider implements GroupStorageProvider { + private final GroupStorageProviderModel component; + private final String groupName; + + + public HardcodedGroupStorageProvider(GroupStorageProviderModel component) { + this.component = component; + this.groupName = component.getConfig().getFirst(HardcodedGroupStorageProviderFactory.GROUP_NAME); + } + + @Override + public void close() { + } + + @Override + public GroupModel getGroupById(RealmModel realm, String id) { + StorageId storageId = new StorageId(id); + final String groupName = storageId.getExternalId(); + if (this.groupName.equals(groupName)) return new HardcodedGroupAdapter(realm); + return null; + } + + @Override + public Stream searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) { + if (Boolean.parseBoolean(component.getConfig().getFirst(HardcodedGroupStorageProviderFactory.DELAYED_SEARCH))) try { + Thread.sleep(5000l); + } catch (InterruptedException ex) { + Logger.getLogger(HardcodedGroupStorageProvider.class).warn(ex.getCause()); + } + if (search != null && this.groupName.toLowerCase().contains(search.toLowerCase())) { + return Stream.of(new HardcodedGroupAdapter(realm)); + } + + return Stream.empty(); + } + + + public class HardcodedGroupAdapter implements GroupModel { + + private final RealmModel realm; + private StorageId storageId; + + public HardcodedGroupAdapter(RealmModel realm) { + this.realm = realm; + } + + @Override + public String getId() { + if (storageId == null) { + storageId = new StorageId(component.getId(), getName()); + } + return storageId.getId(); + } + + @Override + public String getName() { + return groupName; + } + + @Override + public Set getRealmRoleMappings() { + return null; + } + + @Override + public Set getClientRoleMappings(ClientModel app) { + return null; + } + + @Override + public boolean hasRole(RoleModel role) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Set getRoleMappings() { + return null; + } + + @Override + public String getFirstAttribute(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Stream getAttributeStream(String name) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public Map> getAttributes() { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public GroupModel getParent() { + return null; + } + + @Override + public String getParentId() { + return null; + } + + @Override + public Stream getSubGroupsStream() { + return Stream.empty(); + } + + @Override + public void deleteRoleMapping(RoleModel role) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void grantRole(RoleModel role) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void setParent(GroupModel group) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void addChild(GroupModel subGroup) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void removeChild(GroupModel subGroup) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void setName(String name) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void setSingleAttribute(String name, String value) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void setAttribute(String name, List values) { + throw new ReadOnlyException("group is read only"); + } + + @Override + public void removeAttribute(String name) { + throw new ReadOnlyException("group is read only"); + } + } + + +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProviderFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProviderFactory.java new file mode 100644 index 0000000000..599c795a31 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/HardcodedGroupStorageProviderFactory.java @@ -0,0 +1,66 @@ +/* + * 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.testsuite.federation; + +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.storage.group.GroupStorageProviderFactory; +import org.keycloak.storage.group.GroupStorageProviderModel; + +import java.util.List; + +public class HardcodedGroupStorageProviderFactory implements GroupStorageProviderFactory { + @Override + public HardcodedGroupStorageProvider create(KeycloakSession session, ComponentModel model) { + return new HardcodedGroupStorageProvider(new GroupStorageProviderModel(model)); + } + + public static final String PROVIDER_ID = "hardcoded-group"; + public static final String GROUP_NAME = "gorup_name"; + public static final String DELAYED_SEARCH = "delayed_search"; + + protected static final List CONFIG_PROPERTIES; + + static { + CONFIG_PROPERTIES = ProviderConfigurationBuilder.create() + .property().name(GROUP_NAME) + .type(ProviderConfigProperty.STRING_TYPE) + .label("Hardcoded Group Name") + .helpText("Only this group name is available for lookup") + .defaultValue("hardcoded-group") + .add() + .property().name(DELAYED_SEARCH) + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .label("Delayes provider by 5s.") + .helpText("If true it delayes search for clients within the provider by 5s.") + .defaultValue("false") + .add() + .build(); + } + + @Override + public List getConfigProperties() { + return CONFIG_PROPERTIES; + } + + @Override + public String getId() { + return PROVIDER_ID; + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.group.GroupStorageProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.group.GroupStorageProviderFactory new file mode 100644 index 0000000000..ee58aa8eb6 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.group.GroupStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.testsuite.federation.HardcodedGroupStorageProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/GroupStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/GroupStorageTest.java new file mode 100644 index 0000000000..8e624487d8 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/GroupStorageTest.java @@ -0,0 +1,257 @@ +/* + * 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.testsuite.federation.storage; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.RealmModel; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.group.GroupStorageProvider; +import org.keycloak.testsuite.AbstractTestRealmKeycloakTest; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude; +import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer; +import org.keycloak.testsuite.auth.page.AuthRealm; +import org.keycloak.testsuite.federation.HardcodedGroupStorageProviderFactory; + +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +@AuthServerContainerExclude(AuthServer.REMOTE) +public class GroupStorageTest extends AbstractTestRealmKeycloakTest { + + private String providerId; + + @Override + public void configureTestRealm(RealmRepresentation testRealm) { + } + + protected String addComponent(ComponentRepresentation component) { + try (Response resp = adminClient.realm("test").components().add(component)) { + String id = ApiUtil.getCreatedId(resp); + getCleanup().addComponentId(id); + return id; + } + } + + @Before + public void addProvidersBeforeTest() throws URISyntaxException, IOException { + ComponentRepresentation provider = new ComponentRepresentation(); + provider.setName("group-storage-hardcoded"); + provider.setProviderId(HardcodedGroupStorageProviderFactory.PROVIDER_ID); + provider.setProviderType(GroupStorageProvider.class.getName()); + provider.setConfig(new MultivaluedHashMap<>()); + provider.getConfig().putSingle(HardcodedGroupStorageProviderFactory.GROUP_NAME, "hardcoded-group"); + provider.getConfig().putSingle(HardcodedGroupStorageProviderFactory.DELAYED_SEARCH, Boolean.toString(false)); + + providerId = addComponent(provider); + } + + @Test + public void testGetGroupById() { + String providerId = this.providerId; + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + StorageId storageId = new StorageId(providerId, "hardcoded-group"); + GroupModel hardcoded = session.groups().getGroupById(realm, storageId.getId()); + assertNotNull(hardcoded); + }); + } + + @Test(timeout = 4000) + public void testSearchTimeout() { + String hardcodedGroup = HardcodedGroupStorageProviderFactory.PROVIDER_ID; + String delayedSearch = HardcodedGroupStorageProviderFactory.DELAYED_SEARCH; + String providerId = this.providerId; + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName(AuthRealm.TEST); + + assertThat(session.groupStorageManager() + .searchForGroupByName(realm, "group", null, null).stream() + .map(GroupModel::getName) + .collect(Collectors.toList()), + allOf( + hasItem(hardcodedGroup), + hasItem("sample-realm-group")) + ); + + //update the provider to simulate delay during the search + ComponentModel memoryProvider = realm.getComponent(providerId); + memoryProvider.getConfig().putSingle(delayedSearch, Boolean.toString(true)); + realm.updateComponent(memoryProvider); + }); + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName(AuthRealm.TEST); + // search for groups and check hardcoded-group is not present + assertThat(session.groupStorageManager() + .searchForGroupByName(realm, "group", null, null).stream() + .map(GroupModel::getName) + .collect(Collectors.toList()), + allOf( + not(hasItem(hardcodedGroup)), + hasItem("sample-realm-group") + )); + }); + } + + /* + TODO review caching of groups, it behaves a little bit different than clients so following tests fails. + Tracked as KEYCLOAK-15135. + */ +// @Test +// public void testDailyEviction() { +// testNotCached(); + +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleStorageProviderModel model = realm.getRoleStorageProviders().get(0); +// Calendar eviction = Calendar.getInstance(); +// eviction.add(Calendar.HOUR, 1); +// model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.EVICT_DAILY); +// model.setEvictionHour(eviction.get(HOUR_OF_DAY)); +// model.setEvictionMinute(eviction.get(MINUTE)); +// realm.updateComponent(model); +// }); +// testIsCached(); +// setTimeOffset(2 * 60 * 60); // 2 hours in future +// testNotCached(); +// testIsCached(); +// +// setDefaultCachePolicy(); +// testIsCached(); + +// } + +// @Test +// public void testWeeklyEviction() { +// testNotCached(); +// +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleStorageProviderModel model = realm.getRoleStorageProviders().get(0); +// Calendar eviction = Calendar.getInstance(); +// eviction.add(Calendar.HOUR, 4 * 24); +// model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.EVICT_WEEKLY); +// model.setEvictionDay(eviction.get(DAY_OF_WEEK)); +// model.setEvictionHour(eviction.get(HOUR_OF_DAY)); +// model.setEvictionMinute(eviction.get(MINUTE)); +// realm.updateComponent(model); +// }); +// testIsCached(); +// setTimeOffset(2 * 24 * 60 * 60); // 2 days in future +// testIsCached(); +// setTimeOffset(5 * 24 * 60 * 60); // 5 days in future +// testNotCached(); +// testIsCached(); +// +// setDefaultCachePolicy(); +// testIsCached(); +// +// } +// +// @Test +// public void testMaxLifespan() { +// testNotCached(); +// +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleStorageProviderModel model = realm.getRoleStorageProviders().get(0); +// model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.MAX_LIFESPAN); +// model.setMaxLifespan(1 * 60 * 60 * 1000); +// realm.updateComponent(model); +// }); +// testIsCached(); +// +// setTimeOffset(1/2 * 60 * 60); // 1/2 hour in future +// +// testIsCached(); +// +// setTimeOffset(2 * 60 * 60); // 2 hours in future +// +// testNotCached(); +// testIsCached(); +// +// setDefaultCachePolicy(); +// testIsCached(); +// +// } + +// private void testNotCached() { +// String providerId = this.providerId; +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// StorageId storageId = new StorageId(providerId, "hardcoded-group"); +// GroupModel hardcoded = session.groups().getGroupById(realm, storageId.getId()); +// Assert.assertNotNull(hardcoded); +// Assert.assertThat(hardcoded, not(instanceOf(GroupAdapter.class))); +// }); +// } + +// private void testIsCached() { +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleModel hardcoded = realm.getRole("hardcoded-role"); +// Assert.assertNotNull(hardcoded); +// Assert.assertThat(hardcoded, instanceOf(RoleAdapter.class)); +// }); +// } + +// @Test +// public void testNoCache() { +// testNotCached(); +// +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleStorageProviderModel model = realm.getRoleStorageProviders().get(0); +// model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.NO_CACHE); +// realm.updateComponent(model); +// }); +// +// testNotCached(); +// +// // test twice because updating component should evict +// testNotCached(); +// +// // set it back +// setDefaultCachePolicy(); +// testIsCached(); +// } + +// private void setDefaultCachePolicy() { +// testingClient.server().run(session -> { +// RealmModel realm = session.realms().getRealmByName("test"); +// RoleStorageProviderModel model = realm.getRoleStorageProviders().get(0); +// model.setCachePolicy(CacheableStorageProviderModel.CachePolicy.DEFAULT); +// realm.updateComponent(model); +// }); +// } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json index aaaec43f0a..1c41b986d0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/testrealm.json @@ -575,6 +575,9 @@ } } ] + }, + { + "name": "sample-realm-group" } ],