KEYCLOAK-14973 Create GroupStorageManager
This commit is contained in:
parent
03c07bd2d7
commit
bdccfef513
18 changed files with 1168 additions and 38 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T extends GroupStorageProvider> extends ComponentFactory<T, GroupStorageProvider> {
|
||||
|
||||
|
||||
/**
|
||||
* 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<ProviderConfigProperty> 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<ProviderConfigProperty> getCommonProviderConfigProperties() {
|
||||
return GroupStorageProviderSpi.commonConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
default
|
||||
Map<String, Object> getTypeMetadata() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -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<? extends Provider> getProviderClass() {
|
||||
return GroupStorageProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return GroupStorageProviderFactory.class;
|
||||
}
|
||||
|
||||
private static final List<ProviderConfigProperty> commonConfig;
|
||||
|
||||
static {
|
||||
//corresponds to properties defined in CacheableStorageProviderModel and PrioritizedComponentModel
|
||||
List<ProviderConfigProperty> 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<ProviderConfigProperty> commonConfig() {
|
||||
return commonConfig;
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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<GroupModel> 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<GroupModel> 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<GroupModel> 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)}.
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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<ComponentModel> 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<ComponentModel> getComponentsStream(String parentId, String providerType) {
|
||||
return getComponents(parentId, providerType).stream();
|
||||
}
|
||||
|
||||
List<ComponentModel> getComponents(String parentId);
|
||||
|
||||
List<ComponentModel> 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<ComponentModel> getStorageProviders(Class<? extends Provider> storageProviderClass) {
|
||||
return getComponentsStream(getId(), storageProviderClass.getName());
|
||||
}
|
||||
|
||||
String getLoginTheme();
|
||||
|
||||
void setLoginTheme(String name);
|
||||
|
|
|
@ -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<GroupModel> 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<GroupModel> searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults);
|
||||
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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 <ProviderType> This type will be used for looking for factories that produce instances of desired providers
|
||||
* @param <StorageProviderModelType> 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<ProviderType extends Provider,
|
||||
StorageProviderModelType extends CacheableStorageProviderModel> {
|
||||
|
||||
/**
|
||||
* 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<ProviderType> providerTypeClass;
|
||||
private final Function<ComponentModel, StorageProviderModelType> toStorageProviderModelTypeFunction;
|
||||
private final String configScope;
|
||||
private Long storageProviderTimeout;
|
||||
|
||||
public AbstractStorageManager(KeycloakSession session, Class<ProviderType> providerTypeClass, Function<ComponentModel, StorageProviderModelType> 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<CreatedProviderType, ProviderType>}
|
||||
*/
|
||||
protected <T extends ProviderType> ComponentFactory<T, ProviderType> getStorageProviderFactory(String providerId) {
|
||||
return (ComponentFactory<T, ProviderType>) session.getKeycloakSessionFactory()
|
||||
.getProviderFactory(providerTypeClass, providerId);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param realm realm
|
||||
* @return enabled storage providers for realm and @{code getProviderTypeClass()}
|
||||
*/
|
||||
protected Stream<ProviderType> 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 <R> result of applyFunction
|
||||
* @return a stream with all results from all StorageProviders
|
||||
*/
|
||||
protected <R> Stream<R> applyOnEnabledStorageProvidersWithTimeout(RealmModel realm, Function<ProviderType, ? extends Stream<R>> 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<? extends ProviderType, ProviderType> 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<ComponentModel> getStorageProviderModels(RealmModel realm, Class<? extends Provider> storageType) {
|
||||
return realm.getStorageProviders(storageType);
|
||||
}
|
||||
}
|
|
@ -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<GroupStorageProvider, GroupStorageProviderModel> 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<GroupModel> searchForGroupByNameStream(RealmModel realm, String search, Integer firstResult, Integer maxResults) {
|
||||
Stream<GroupModel> local = session.groupLocalStorage().searchForGroupByNameStream(realm, search, firstResult, maxResults);
|
||||
Stream<GroupModel> 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<GroupModel> 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<GroupModel> getGroupsByRoleStream(RealmModel realm, RoleModel role, int firstResult, int maxResults) {
|
||||
return session.groupLocalStorage().getGroupsByRoleStream(realm, role, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> getTopLevelGroupsStream(RealmModel realm) {
|
||||
return session.groupLocalStorage().getTopLevelGroupsStream(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> 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() {
|
||||
|
||||
}
|
||||
}
|
|
@ -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<GroupModel> 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<RoleModel> getRealmRoleMappings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getClientRoleMappings(ClientModel app) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getRoleMappings() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<String> getAttributeStream(String name) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupModel getParent() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParentId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<GroupModel> 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<String> values) {
|
||||
throw new ReadOnlyException("group is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
throw new ReadOnlyException("group is read only");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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<HardcodedGroupStorageProvider> {
|
||||
@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<ProviderConfigProperty> 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<ProviderConfigProperty> getConfigProperties() {
|
||||
return CONFIG_PROPERTIES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.testsuite.federation.HardcodedGroupStorageProviderFactory
|
|
@ -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);
|
||||
// });
|
||||
// }
|
||||
}
|
|
@ -575,6 +575,9 @@
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sample-realm-group"
|
||||
}
|
||||
],
|
||||
|
||||
|
|
Loading…
Reference in a new issue