KEYCLOAK-14812 Create RoleStorageManager
This commit is contained in:
parent
bfa21c912c
commit
6b00633c47
17 changed files with 1180 additions and 65 deletions
|
@ -160,8 +160,7 @@ public class RealmCacheSession implements CacheRealmProvider {
|
|||
public RoleProvider getRoleDelegate() {
|
||||
if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
|
||||
if (roleDelegate != null) return roleDelegate;
|
||||
// roleDelegate = session.roleStorageManager();
|
||||
roleDelegate = session.roleLocalStorage();
|
||||
roleDelegate = session.roleStorageManager();
|
||||
return roleDelegate;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.role;
|
||||
|
||||
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 RoleStorageProviderFactory<T extends RoleStorageProvider> extends ComponentFactory<T, RoleStorageProvider> {
|
||||
|
||||
|
||||
/**
|
||||
* 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 RoleStorageProviderModel 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 RoleStorageProvider implementations
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
default
|
||||
List<ProviderConfigProperty> getCommonProviderConfigProperties() {
|
||||
return RoleStorageProviderSpi.commonConfig();
|
||||
}
|
||||
|
||||
@Override
|
||||
default
|
||||
Map<String, Object> getTypeMetadata() {
|
||||
return new HashMap<>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.role;
|
||||
|
||||
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 java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class RoleStorageProviderSpi implements Spi {
|
||||
|
||||
@Override
|
||||
public boolean isInternal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "role-storage";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends Provider> getProviderClass() {
|
||||
return RoleStorageProvider.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||
return RoleStorageProviderFactory.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;
|
||||
}
|
||||
|
||||
}
|
|
@ -74,6 +74,7 @@ org.keycloak.credential.CredentialSpi
|
|||
org.keycloak.keys.PublicKeyStorageSpi
|
||||
org.keycloak.keys.KeySpi
|
||||
org.keycloak.storage.client.ClientStorageProviderSpi
|
||||
org.keycloak.storage.role.RoleStorageProviderSpi
|
||||
org.keycloak.crypto.SignatureSpi
|
||||
org.keycloak.crypto.ClientSignatureVerifierSpi
|
||||
org.keycloak.crypto.HashSpi
|
||||
|
|
|
@ -157,7 +157,10 @@ public interface KeycloakSession {
|
|||
|
||||
ClientProvider clientStorageManager();
|
||||
|
||||
// RoleProvider roleStorageManager();
|
||||
/**
|
||||
* @return RoleStorageManager instance
|
||||
*/
|
||||
RoleProvider roleStorageManager();
|
||||
|
||||
/**
|
||||
* Un-cached view of all users in system including users loaded by UserStorageProviders
|
||||
|
@ -190,7 +193,7 @@ public interface KeycloakSession {
|
|||
ClientProvider clientLocalStorage();
|
||||
|
||||
/**
|
||||
* Keycloak specific local storage for roles. No cache in front, this api talks directly to database configured for Keycloak
|
||||
* Keycloak specific local storage for roles. No cache in front, this api talks directly to storage configured for Keycloak
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
|
|
|
@ -24,6 +24,8 @@ import org.keycloak.storage.UserStorageProvider;
|
|||
import org.keycloak.storage.UserStorageProviderModel;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProviderModel;
|
||||
import org.keycloak.storage.role.RoleStorageProvider;
|
||||
import org.keycloak.storage.role.RoleStorageProviderModel;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -441,6 +443,16 @@ public interface RealmModel extends RoleContainerModel {
|
|||
return list;
|
||||
}
|
||||
|
||||
default
|
||||
List<RoleStorageProviderModel> getRoleStorageProviders() {
|
||||
List<RoleStorageProviderModel> list = new LinkedList<>();
|
||||
for (ComponentModel component : getComponents(getId(), RoleStorageProvider.class.getName())) {
|
||||
list.add(new RoleStorageProviderModel(component));
|
||||
}
|
||||
Collections.sort(list, RoleStorageProviderModel.comparator);
|
||||
return list;
|
||||
}
|
||||
|
||||
String getLoginTheme();
|
||||
|
||||
void setLoginTheme(String name);
|
||||
|
|
|
@ -20,12 +20,13 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.provider.Provider;
|
||||
import org.keycloak.storage.role.RoleLookupProvider;
|
||||
|
||||
/**
|
||||
* Provider of the role records.
|
||||
* @author vramik
|
||||
*/
|
||||
public interface RoleProvider extends Provider {
|
||||
public interface RoleProvider extends Provider, RoleLookupProvider {
|
||||
|
||||
/**
|
||||
* Adds a realm role with given {@code name} to the given realm.
|
||||
|
@ -136,51 +137,4 @@ public interface RoleProvider extends Provider {
|
|||
* @param client Client.
|
||||
*/
|
||||
void removeRoles(ClientModel client);
|
||||
|
||||
//TODO RoleLookupProvider
|
||||
/**
|
||||
* Exact search for a role by given name.
|
||||
* @param realm Realm.
|
||||
* @param name String name of the role.
|
||||
* @return Model of the role, or {@code null} if no role is found.
|
||||
*/
|
||||
RoleModel getRealmRole(RealmModel realm, String name);
|
||||
|
||||
/**
|
||||
* Exact search for a role by its internal ID..
|
||||
* @param realm Realm.
|
||||
* @param id Internal ID of the role.
|
||||
* @return Model of the role.
|
||||
*/
|
||||
RoleModel getRoleById(RealmModel realm, String id);
|
||||
|
||||
/**
|
||||
* Case-insensitive search for roles that contain the given string in their name or description.
|
||||
* @param realm Realm.
|
||||
* @param search Searched substring of the role's name or description.
|
||||
* @param first First result to return. Ignored if negative or {@code null}.
|
||||
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return Stream of the realm roles their name or description contains given search string.
|
||||
* Never returns {@code null}.
|
||||
*/
|
||||
Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max);
|
||||
|
||||
/**
|
||||
* Exact search for a client role by given name.
|
||||
* @param client Client.
|
||||
* @param name String name of the role.
|
||||
* @return Model of the role, or {@code null} if no role is found.
|
||||
*/
|
||||
RoleModel getClientRole(ClientModel client, String name);
|
||||
|
||||
/**
|
||||
* Case-insensitive search for client roles that contain the given string in their name or description.
|
||||
* @param client Client.
|
||||
* @param search String to search by role's name or description.
|
||||
* @param first First result to return. Ignored if negative or {@code null}.
|
||||
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return Stream of the client roles their name or description contains given search string.
|
||||
* Never returns {@code null}.
|
||||
*/
|
||||
Stream<RoleModel> searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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.role;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
|
||||
/**
|
||||
* Abstraction interface for lookup of both realm roles and client roles by id, name and description.
|
||||
*/
|
||||
public interface RoleLookupProvider {
|
||||
|
||||
/**
|
||||
* Exact search for a role by given name.
|
||||
* @param realm Realm.
|
||||
* @param name String name of the role.
|
||||
* @return Model of the role, or {@code null} if no role is found.
|
||||
*/
|
||||
RoleModel getRealmRole(RealmModel realm, String name);
|
||||
|
||||
/**
|
||||
* Exact search for a role by its internal ID..
|
||||
* @param realm Realm.
|
||||
* @param id Internal ID of the role.
|
||||
* @return Model of the role.
|
||||
*/
|
||||
RoleModel getRoleById(RealmModel realm, String id);
|
||||
|
||||
/**
|
||||
* Case-insensitive search for roles that contain the given string in their name or description.
|
||||
* @param realm Realm.
|
||||
* @param search Searched substring of the role's name or description.
|
||||
* @param first First result to return. Ignored if negative or {@code null}.
|
||||
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return Stream of the realm roles their name or description contains given search string.
|
||||
* Never returns {@code null}.
|
||||
*/
|
||||
Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max);
|
||||
|
||||
/**
|
||||
* Exact search for a client role by given name.
|
||||
* @param client Client.
|
||||
* @param name String name of the role.
|
||||
* @return Model of the role, or {@code null} if no role is found.
|
||||
*/
|
||||
RoleModel getClientRole(ClientModel client, String name);
|
||||
|
||||
/**
|
||||
* Case-insensitive search for client roles that contain the given string in their name or description.
|
||||
* @param client Client.
|
||||
* @param search String to search by role's name or description.
|
||||
* @param first First result to return. Ignored if negative or {@code null}.
|
||||
* @param max Maximum number of results to return. Ignored if negative or {@code null}.
|
||||
* @return Stream of the client roles their name or description contains given search string.
|
||||
* Never returns {@code null}.
|
||||
*/
|
||||
Stream<RoleModel> searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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.role;
|
||||
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
/**
|
||||
* Base interface for components that want to provide an alternative storage mechanism for roles
|
||||
*/
|
||||
public interface RoleStorageProvider extends Provider, RoleLookupProvider {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.role;
|
||||
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.storage.CacheableStorageProviderModel;
|
||||
|
||||
/**
|
||||
* Stored configuration of a Role Storage provider instance.
|
||||
*/
|
||||
public class RoleStorageProviderModel extends CacheableStorageProviderModel {
|
||||
|
||||
public RoleStorageProviderModel() {
|
||||
setProviderType(RoleStorageProvider.class.getName());
|
||||
}
|
||||
|
||||
public RoleStorageProviderModel(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;
|
||||
|
||||
}
|
||||
}
|
|
@ -42,7 +42,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.RoleStorageManager;
|
||||
import org.keycloak.storage.RoleStorageManager;
|
||||
import org.keycloak.storage.UserStorageManager;
|
||||
import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
||||
import org.keycloak.vault.DefaultVaultTranscriber;
|
||||
|
@ -72,7 +72,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
private RoleProvider roleProvider;
|
||||
private UserStorageManager userStorageManager;
|
||||
private ClientStorageManager clientStorageManager;
|
||||
// private RoleStorageManager roleStorageManager;
|
||||
private RoleStorageManager roleStorageManager;
|
||||
private UserCredentialStoreManager userCredentialStorageManager;
|
||||
private UserSessionProvider sessionProvider;
|
||||
private AuthenticationSessionProvider authenticationSessionProvider;
|
||||
|
@ -120,8 +120,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
if (cache != null) {
|
||||
return cache;
|
||||
} else {
|
||||
// return roleStorageManager();
|
||||
return roleLocalStorage();
|
||||
return roleStorageManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,11 +203,13 @@ public class DefaultKeycloakSession implements KeycloakSession {
|
|||
return getProvider(RoleProvider.class);
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public RoleProvider roleStorageManager() {
|
||||
// if (roleStorageManager == null) roleStorageManager = new RoleStorageManager(this);
|
||||
// return roleStorageManager;
|
||||
// }
|
||||
@Override
|
||||
public RoleProvider roleStorageManager() {
|
||||
if (roleStorageManager == null) {
|
||||
roleStorageManager = new RoleStorageManager(this, factory.getRoleStorageProviderTimeout());
|
||||
}
|
||||
return roleStorageManager;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
|
|
|
@ -60,10 +60,12 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
|||
protected long serverStartupTimestamp;
|
||||
|
||||
/**
|
||||
* Timeout is used as time boundary for obtaining clients from an external client storage. Default value is set
|
||||
* 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 long clientStorageProviderTimeout;
|
||||
private Long clientStorageProviderTimeout;
|
||||
private Long roleStorageProviderTimeout;
|
||||
|
||||
|
||||
@Override
|
||||
public void register(ProviderEventListener listener) {
|
||||
|
@ -85,8 +87,6 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
|||
public void init() {
|
||||
serverStartupTimestamp = System.currentTimeMillis();
|
||||
|
||||
clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L);
|
||||
|
||||
ProviderManager pm = new ProviderManager(KeycloakDeploymentInfo.create().services(), getClass().getClassLoader(), Config.scope().getArray("providers"));
|
||||
spis.addAll(pm.loadSpis());
|
||||
factoriesMap = loadFactories(pm);
|
||||
|
@ -360,9 +360,19 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr
|
|||
}
|
||||
|
||||
public long getClientStorageProviderTimeout() {
|
||||
if (clientStorageProviderTimeout == null) {
|
||||
clientStorageProviderTimeout = Config.scope("client").getLong("storageProviderTimeout", 3000L);
|
||||
}
|
||||
return clientStorageProviderTimeout;
|
||||
}
|
||||
|
||||
public long getRoleStorageProviderTimeout() {
|
||||
if (roleStorageProviderTimeout == null) {
|
||||
roleStorageProviderTimeout = Config.scope("role").getLong("storageProviderTimeout", 3000L);
|
||||
}
|
||||
return roleStorageProviderTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return timestamp of Keycloak server startup
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,249 @@
|
|||
/*
|
||||
* 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 java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.reflections.Types;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.RoleProvider;
|
||||
import org.keycloak.storage.role.RoleLookupProvider;
|
||||
import org.keycloak.storage.role.RoleStorageProvider;
|
||||
import org.keycloak.storage.role.RoleStorageProviderFactory;
|
||||
import org.keycloak.storage.role.RoleStorageProviderModel;
|
||||
import org.keycloak.utils.ServicesUtils;
|
||||
|
||||
public class RoleStorageManager implements RoleProvider {
|
||||
private static final Logger logger = Logger.getLogger(RoleStorageManager.class);
|
||||
|
||||
protected KeycloakSession session;
|
||||
|
||||
private final long roleStorageProviderTimeout;
|
||||
|
||||
public RoleStorageManager(KeycloakSession session, long roleStorageProviderTimeout) {
|
||||
this.session = session;
|
||||
this.roleStorageProviderTimeout = roleStorageProviderTimeout;
|
||||
}
|
||||
|
||||
public static boolean isStorageProviderEnabled(RealmModel realm, String providerId) {
|
||||
RoleStorageProviderModel model = getStorageProviderModel(realm, providerId);
|
||||
return model.isEnabled();
|
||||
}
|
||||
|
||||
public static RoleStorageProviderModel getStorageProviderModel(RealmModel realm, String componentId) {
|
||||
ComponentModel model = realm.getComponent(componentId);
|
||||
if (model == null) return null;
|
||||
return new RoleStorageProviderModel(model);
|
||||
}
|
||||
|
||||
public static RoleStorageProvider getStorageProvider(KeycloakSession session, RealmModel realm, String componentId) {
|
||||
ComponentModel model = realm.getComponent(componentId);
|
||||
if (model == null) return null;
|
||||
RoleStorageProviderModel storageModel = new RoleStorageProviderModel(model);
|
||||
RoleStorageProviderFactory factory = (RoleStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(RoleStorageProvider.class, model.getProviderId());
|
||||
if (factory == null) {
|
||||
throw new ModelException("Could not find RoletStorageProviderFactory for: " + model.getProviderId());
|
||||
}
|
||||
return getStorageProviderInstance(session, storageModel, factory);
|
||||
}
|
||||
|
||||
|
||||
public static List<RoleStorageProviderModel> getStorageProviders(RealmModel realm) {
|
||||
return realm.getRoleStorageProviders();
|
||||
}
|
||||
|
||||
public static RoleStorageProvider getStorageProviderInstance(KeycloakSession session, RoleStorageProviderModel model, RoleStorageProviderFactory factory) {
|
||||
RoleStorageProvider instance = (RoleStorageProvider)session.getAttribute(model.getId());
|
||||
if (instance != null) return instance;
|
||||
instance = factory.create(session, model);
|
||||
if (instance == null) {
|
||||
throw new IllegalStateException("RoleStorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance");
|
||||
}
|
||||
session.enlistForClose(instance);
|
||||
session.setAttribute(model.getId(), instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<T> getStorageProviders(KeycloakSession session, RealmModel realm, Class<T> type) {
|
||||
List<T> list = new LinkedList<>();
|
||||
for (RoleStorageProviderModel model : getStorageProviders(realm)) {
|
||||
RoleStorageProviderFactory factory = (RoleStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RoleStorageProvider.class, model.getProviderId());
|
||||
if (factory == null) {
|
||||
logger.warnv("Configured RoleStorageProvider {0} of provider id {1} does not exist in realm {2}", model.getName(), model.getProviderId(), realm.getName());
|
||||
continue;
|
||||
}
|
||||
if (Types.supports(type, factory, RoleStorageProviderFactory.class)) {
|
||||
list.add(type.cast(getStorageProviderInstance(session, model, factory)));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public static <T> List<T> getEnabledStorageProviders(KeycloakSession session, RealmModel realm, Class<T> type) {
|
||||
List<T> list = new LinkedList<>();
|
||||
for (RoleStorageProviderModel model : getStorageProviders(realm)) {
|
||||
if (!model.isEnabled()) continue;
|
||||
RoleStorageProviderFactory factory = (RoleStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RoleStorageProvider.class, model.getProviderId());
|
||||
if (factory == null) {
|
||||
logger.warnv("Configured RoleStorageProvider {0} of provider id {1} does not exist in realm {2}", model.getName(), model.getProviderId(), realm.getName());
|
||||
continue;
|
||||
}
|
||||
if (Types.supports(type, factory, RoleStorageProviderFactory.class)) {
|
||||
list.add(type.cast(getStorageProviderInstance(session, model, factory)));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addRealmRole(RealmModel realm, String name) {
|
||||
return session.roleLocalStorage().addRealmRole(realm, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addRealmRole(RealmModel realm, String id, String name) {
|
||||
return session.roleLocalStorage().addRealmRole(realm, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRealmRole(RealmModel realm, String name) {
|
||||
RoleModel realmRole = session.roleLocalStorage().getRealmRole(realm, name);
|
||||
if (realmRole != null) return realmRole;
|
||||
for (RoleLookupProvider enabledStorageProvider : getEnabledStorageProviders(session, realm, RoleLookupProvider.class)) {
|
||||
realmRole = enabledStorageProvider.getRealmRole(realm, name);
|
||||
if (realmRole != null) return realmRole;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(RealmModel realm, String id) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
if (storageId.getProviderId() == null) {
|
||||
return session.roleLocalStorage().getRoleById(realm, id);
|
||||
}
|
||||
RoleLookupProvider provider = (RoleLookupProvider)getStorageProvider(session, realm, storageId.getProviderId());
|
||||
if (provider == null) return null;
|
||||
if (! isStorageProviderEnabled(realm, storageId.getProviderId())) return null;
|
||||
return provider.getRoleById(realm, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getRealmRolesStream(RealmModel realm, Integer first, Integer max) {
|
||||
return session.roleLocalStorage().getRealmRolesStream(realm, first, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtaining roles from an external role storage is time-bounded. In case the external role storage
|
||||
* isn't available at least roles from a local storage are returned. For this purpose
|
||||
* the {@link org.keycloak.services.DefaultKeycloakSessionFactory#getRoleStorageProviderTimeout()} property is used.
|
||||
* Default value is 3000 milliseconds and it's configurable.
|
||||
* See {@link org.keycloak.services.DefaultKeycloakSessionFactory} for details.
|
||||
*/
|
||||
@Override
|
||||
public Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) {
|
||||
Stream<RoleModel> local = session.roleLocalStorage().searchForRolesStream(realm, search, first, max);
|
||||
Stream<RoleModel> ext = getEnabledStorageProviders(session, realm, RoleLookupProvider.class).stream()
|
||||
.flatMap(ServicesUtils.timeBound(session,
|
||||
roleStorageProviderTimeout,
|
||||
p -> ((RoleLookupProvider) p).searchForRolesStream(realm, search, first, max)));
|
||||
|
||||
return Stream.concat(local, ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeRole(RoleModel role) {
|
||||
if (!StorageId.isLocalStorage(role.getId())) {
|
||||
throw new RuntimeException("Federated roles do not support this operation");
|
||||
}
|
||||
return session.roleLocalStorage().removeRole(role);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRoles(RealmModel realm) {
|
||||
session.roleLocalStorage().removeRoles(realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeRoles(ClientModel client) {
|
||||
session.roleLocalStorage().removeRoles(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addClientRole(ClientModel client, String name) {
|
||||
return session.roleLocalStorage().addClientRole(client, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel addClientRole(ClientModel client, String id, String name) {
|
||||
return session.roleLocalStorage().addClientRole(client, id, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getClientRole(ClientModel client, String name) {
|
||||
RoleModel clientRole = session.roleLocalStorage().getClientRole(client, name);
|
||||
if (clientRole != null) return clientRole;
|
||||
for (RoleLookupProvider enabledStorageProvider : getEnabledStorageProviders(session, client.getRealm(), RoleLookupProvider.class)) {
|
||||
clientRole = enabledStorageProvider.getClientRole(client, name);
|
||||
if (clientRole != null) return clientRole;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRolesStream(ClientModel client) {
|
||||
return session.roleLocalStorage().getClientRolesStream(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> getClientRolesStream(ClientModel client, Integer first, Integer max) {
|
||||
return session.roleLocalStorage().getClientRolesStream(client, first, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtaining roles from an external role storage is time-bounded. In case the external role storage
|
||||
* isn't available at least roles from a local storage are returned. For this purpose
|
||||
* the {@link org.keycloak.services.DefaultKeycloakSessionFactory#getRoleStorageProviderTimeout()} property is used.
|
||||
* Default value is 3000 milliseconds and it's configurable.
|
||||
* See {@link org.keycloak.services.DefaultKeycloakSessionFactory} for details.
|
||||
*/
|
||||
@Override
|
||||
public Stream<RoleModel> searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) {
|
||||
Stream<RoleModel> local = session.roleLocalStorage().searchForClientRolesStream(client, search, first, max);
|
||||
Stream<RoleModel> ext = getEnabledStorageProviders(session, client.getRealm(), RoleLookupProvider.class).stream()
|
||||
.flatMap(ServicesUtils.timeBound(session,
|
||||
roleStorageProviderTimeout,
|
||||
p -> ((RoleLookupProvider) p).searchForClientRolesStream(client, search, first, max)));
|
||||
|
||||
return Stream.concat(local, ext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.storage.ReadOnlyException;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.role.RoleStorageProvider;
|
||||
import org.keycloak.storage.role.RoleStorageProviderModel;
|
||||
|
||||
public class HardcodedRoleStorageProvider implements RoleStorageProvider {
|
||||
private final RoleStorageProviderModel component;
|
||||
private final String roleName;
|
||||
|
||||
|
||||
public HardcodedRoleStorageProvider(RoleStorageProviderModel component) {
|
||||
this.component = component;
|
||||
this.roleName = component.getConfig().getFirst(HardcodedRoleStorageProviderFactory.ROLE_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRealmRole(RealmModel realm, String name) {
|
||||
if (this.roleName.equals(name)) return new HardcodedRoleAdapter(realm);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getRoleById(RealmModel realm, String id) {
|
||||
StorageId storageId = new StorageId(id);
|
||||
final String roleName = storageId.getExternalId();
|
||||
if (this.roleName.equals(roleName)) return new HardcodedRoleAdapter(realm);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> searchForRolesStream(RealmModel realm, String search, Integer first, Integer max) {
|
||||
if (Boolean.parseBoolean(component.getConfig().getFirst(HardcodedRoleStorageProviderFactory.DELAYED_SEARCH))) try {
|
||||
Thread.sleep(5000l);
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.getLogger(HardcodedClientStorageProvider.class).warn(ex.getCause());
|
||||
}
|
||||
if (search != null && this.roleName.toLowerCase().contains(search.toLowerCase())) {
|
||||
return Stream.of(new HardcodedRoleAdapter(realm));
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleModel getClientRole(ClientModel client, String name) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<RoleModel> searchForClientRolesStream(ClientModel client, String search, Integer first, Integer max) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
public class HardcodedRoleAdapter implements RoleModel {
|
||||
|
||||
private final RealmModel realm;
|
||||
private StorageId storageId;
|
||||
|
||||
public HardcodedRoleAdapter(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 roleName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "Federated Role";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isComposite() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<RoleModel> getComposites() {
|
||||
return Collections.EMPTY_SET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientRole() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContainerId() {
|
||||
return realm.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleContainerModel getContainer() {
|
||||
return realm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasRole(RoleModel role) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFirstAttribute(String name) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAttribute(String name) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAttributes() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescription(String description) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCompositeRole(RoleModel role) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCompositeRole(RoleModel role) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSingleAttribute(String name, String value) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Collection<String> values) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
throw new ReadOnlyException("role is read only");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 java.util.List;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
import org.keycloak.storage.role.RoleStorageProviderFactory;
|
||||
import org.keycloak.storage.role.RoleStorageProviderModel;
|
||||
|
||||
public class HardcodedRoleStorageProviderFactory implements RoleStorageProviderFactory<HardcodedRoleStorageProvider> {
|
||||
@Override
|
||||
public HardcodedRoleStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
return new HardcodedRoleStorageProvider(new RoleStorageProviderModel(model));
|
||||
}
|
||||
|
||||
public static final String PROVIDER_ID = "hardcoded-role";
|
||||
public static final String ROLE_NAME = "role_name";
|
||||
public static final String DELAYED_SEARCH = "delayed_search";
|
||||
|
||||
protected static final List<ProviderConfigProperty> CONFIG_PROPERTIES;
|
||||
|
||||
static {
|
||||
CONFIG_PROPERTIES = ProviderConfigurationBuilder.create()
|
||||
.property().name(ROLE_NAME)
|
||||
.type(ProviderConfigProperty.STRING_TYPE)
|
||||
.label("Hardcoded Role Name")
|
||||
.helpText("Only this role naem is available for lookup")
|
||||
.defaultValue("hardcoded-role")
|
||||
.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,17 @@
|
|||
#
|
||||
# Copyright 2020 Red Hat, Inc. and/or its affiliates
|
||||
# and other contributors as indicated by the @author tags.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
org.keycloak.testsuite.federation.HardcodedRoleStorageProviderFactory
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.ws.rs.core.Response;
|
||||
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;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.representations.idm.ComponentRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.role.RoleStorageProvider;
|
||||
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.HardcodedRoleStorageProviderFactory;
|
||||
|
||||
@AuthServerContainerExclude(AuthServer.REMOTE)
|
||||
public class RoleStorageTest 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("role-storage-hardcoded");
|
||||
provider.setProviderId(HardcodedRoleStorageProviderFactory.PROVIDER_ID);
|
||||
provider.setProviderType(RoleStorageProvider.class.getName());
|
||||
provider.setConfig(new MultivaluedHashMap<>());
|
||||
provider.getConfig().putSingle(HardcodedRoleStorageProviderFactory.ROLE_NAME, "hardcoded-role");
|
||||
provider.getConfig().putSingle(HardcodedRoleStorageProviderFactory.DELAYED_SEARCH, Boolean.toString(false));
|
||||
|
||||
providerId = addComponent(provider);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRole() {
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
RoleModel hardcoded = realm.getRole("hardcoded-role");
|
||||
assertNotNull(hardcoded);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleById() {
|
||||
String providerId = this.providerId;
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName("test");
|
||||
StorageId storageId = new StorageId(providerId, "hardcoded-role");
|
||||
RoleModel hardcoded = realm.getRoleById(storageId.getId());
|
||||
assertNotNull(hardcoded);
|
||||
});
|
||||
}
|
||||
|
||||
@Test(timeout = 4000)
|
||||
public void testSearchTimeout() {
|
||||
String hardcodedRole = HardcodedRoleStorageProviderFactory.PROVIDER_ID;
|
||||
String delayedSearch = HardcodedRoleStorageProviderFactory.DELAYED_SEARCH;
|
||||
String providerId = this.providerId;
|
||||
testingClient.server().run(session -> {
|
||||
RealmModel realm = session.realms().getRealmByName(AuthRealm.TEST);
|
||||
|
||||
assertThat(session.roleStorageManager()
|
||||
.searchForRolesStream(realm, "role", null, null)
|
||||
.map(RoleModel::getName)
|
||||
.collect(Collectors.toList()),
|
||||
allOf(
|
||||
hasItem(hardcodedRole),
|
||||
hasItem("sample-realm-role"))
|
||||
);
|
||||
|
||||
//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 roles and check hardcoded-role is not present
|
||||
assertThat(session.roleStorageManager()
|
||||
.searchForRolesStream(realm, "role", null, null)
|
||||
.map(RoleModel::getName)
|
||||
.collect(Collectors.toList()),
|
||||
allOf(
|
||||
not(hasItem(hardcodedRole)),
|
||||
hasItem("sample-realm-role")
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
TODO review caching of roles, it behaves a little bit different than clients so following tests fails.
|
||||
Tracked as KEYCLOAK-14938.
|
||||
*/
|
||||
// @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() {
|
||||
// testingClient.server().run(session -> {
|
||||
// RealmModel realm = session.realms().getRealmByName("test");
|
||||
// RoleModel hardcoded = realm.getRole("hardcoded-role");
|
||||
// Assert.assertNotNull(hardcoded);
|
||||
// Assert.assertThat(hardcoded, not(instanceOf(RoleAdapter.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);
|
||||
// });
|
||||
// }
|
||||
}
|
Loading…
Reference in a new issue