KEYCLOAK-16489 Add ability to run model tests with LDAP

This commit is contained in:
Hynek Mlnarik 2020-11-30 08:53:31 +01:00 committed by Hynek Mlnařík
parent f6be378eca
commit 8c0c542f09
25 changed files with 475 additions and 100 deletions

View file

@ -82,6 +82,45 @@ jobs:
path: reports-unit-tests.zip
if-no-files-found: ignore
model-tests:
name: Model Tests
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2/repository
key: cache-1-${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: cache-1-${{ runner.os }}-m2
- name: Cleanup org.keycloak artifacts
run: rm -rf ~/.m2/repository/org/keycloak >/dev/null || true
- name: Download built keycloak
id: download-keycloak
uses: actions/download-artifact@v2
with:
path: ~/.m2/repository/org/keycloak/
name: keycloak-artifacts.zip
- name: Run model tests
run: |
if ! testsuite/model/test-all-profiles.sh; then
find . -path '*/target/surefire-reports*/*.xml' | zip -q reports-model-tests.zip -@
exit 1
fi
- name: Model test reports
uses: actions/upload-artifact@v2
if: failure()
with:
name: reports-model-tests
retention-days: 14
path: reports-model-tests.zip
if-no-files-found: ignore
test:
name: Base testsuite
needs: build

View file

@ -171,8 +171,11 @@ public class UserStorageSyncManager {
return;
}
UserStorageProviderClusterEvent event = UserStorageProviderClusterEvent.createEvent(removed, realm.getId(), provider);
session.getProvider(ClusterProvider.class).notify(USER_STORAGE_TASK_KEY, event, false, ClusterProvider.DCNotify.ALL_DCS);
final ClusterProvider cp = session.getProvider(ClusterProvider.class);
if (cp != null) {
UserStorageProviderClusterEvent event = UserStorageProviderClusterEvent.createEvent(removed, realm.getId(), provider);
cp.notify(USER_STORAGE_TASK_KEY, event, false, ClusterProvider.DCNotify.ALL_DCS);
}
}

View file

@ -74,12 +74,15 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
this.users = users;
}
private static String translateUserName(String userName) {
return userName == null ? null : userName.toLowerCase();
}
@Override
public UserModel getUserById(String id, RealmModel realm) {
StorageId storageId = new StorageId(id);
final String username = storageId.getExternalId();
if (!users.containsKey(username)) return null;
if (!users.containsKey(translateUserName(username))) return null;
return createUser(realm, username);
}
@ -152,7 +155,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
assertNotNull(newPassword.getValue());
assertNotNull(newPassword.getSalt());
users.get(user.getUsername()).hashedPassword = newPassword;
users.get(translateUserName(user.getUsername())).hashedPassword = newPassword;
UserCache userCache = session.userCache();
if (userCache != null) {
@ -179,7 +182,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
newOTP.setAlgorithm(otpPolicy.getAlgorithm());
newOTP.setPeriod(otpPolicy.getPeriod());
users.get(user.getUsername()).otp = newOTP;
users.get(translateUserName(user.getUsername())).otp = newOTP;
return true;
} else {
@ -208,7 +211,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
}
private MyUser getMyUser(UserModel user) {
return users.get(user.getUsername());
return users.get(translateUserName(user.getUsername()));
}
@Override
@ -240,7 +243,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
MyUser myUser = users.get(user.getUsername());
MyUser myUser = users.get(translateUserName(user.getUsername()));
if (myUser == null) return false;
if (input.getType().equals(UserCredentialModel.PASSWORD)) {
@ -289,7 +292,7 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
if (!users.containsKey(username)) return null;
if (!users.containsKey(translateUserName(username))) return null;
return createUser(realm, username);
}
@ -301,13 +304,13 @@ public class BackwardsCompatibilityUserStorage implements UserLookupProvider, Us
@Override
public UserModel addUser(RealmModel realm, String username) {
users.put(username, new MyUser(username));
users.put(translateUserName(username), new MyUser(username));
return createUser(realm, username);
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
return users.remove(user.getUsername()) != null;
return users.remove(translateUserName(user.getUsername())) != null;
}

View file

@ -94,7 +94,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
public UserModel getUserById(String id, RealmModel realm) {
StorageId storageId = new StorageId(id);
final String username = storageId.getExternalId();
if (!userPasswords.containsKey(username)) {
if (!userPasswords.containsKey(translateUserName(username))) {
return null;
}
@ -159,7 +159,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
return false;
}
if (input.getType().equals(PasswordCredentialModel.TYPE)) {
userPasswords.put(user.getUsername(), input.getChallengeResponse());
userPasswords.put(translateUserName(user.getUsername()), input.getChallengeResponse());
return true;
} else {
@ -189,7 +189,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
return false;
}
if (input.getType().equals(PasswordCredentialModel.TYPE)) {
String pw = userPasswords.get(user.getUsername());
String pw = userPasswords.get(translateUserName(user.getUsername()));
// Using "getValue" on purpose here, to test that backwards compatibility works as expected
return pw != null && pw.equals(((UserCredentialModel) input).getValue());
@ -200,7 +200,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
@Override
public UserModel getUserByUsername(String username, RealmModel realm) {
if (!userPasswords.containsKey(username)) {
if (!userPasswords.containsKey(translateUserName(username))) {
return null;
}
@ -218,7 +218,7 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
throw new ReadOnlyException("Federated storage is not writable");
}
userPasswords.put(username, "");
userPasswords.put(translateUserName(username), "");
return createUser(realm, username);
}
@ -226,21 +226,21 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == UserStorageProvider.EditMode.READ_ONLY || editMode == UserStorageProvider.EditMode.UNSYNCED) {
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", user.getUsername(), editMode.toString());
userPasswords.remove(user.getUsername());
userPasswords.remove(translateUserName(user.getUsername()));
return true;
}
return userPasswords.remove(user.getUsername()) != null;
return userPasswords.remove(translateUserName(user.getUsername())) != null;
}
public boolean removeUserByName(String userName) {
if (editMode == UserStorageProvider.EditMode.READ_ONLY || editMode == UserStorageProvider.EditMode.UNSYNCED) {
log.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'. Deleting user just from Keycloak DB, but he will be re-imported from LDAP again once searched in Keycloak", userName, editMode.toString());
userPasswords.remove(userName);
userPasswords.remove(translateUserName(userName));
return true;
}
return userPasswords.remove(userName) != null;
return userPasswords.remove(translateUserName(userName)) != null;
}
public boolean isImportEnabled() {
@ -307,17 +307,19 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
@Override
public Stream<UserModel> searchForUserStream(String search, RealmModel realm) {
String tSearch = translateUserName(search);
return userPasswords.keySet().stream()
.sorted()
.filter(userName -> userName.contains(search))
.filter(userName -> translateUserName(userName).contains(tSearch))
.map(userName -> createUser(realm, userName));
}
@Override
public Stream<UserModel> searchForUserStream(String search, RealmModel realm, int firstResult, int maxResults) {
String tSearch = translateUserName(search);
Stream<String> userStream = userPasswords.keySet().stream()
.sorted()
.filter(userName -> userName.contains(search));
.filter(userName -> translateUserName(userName).contains(search));
if (firstResult > 0)
userStream = userStream.skip(firstResult);
if (maxResults >= 0)
@ -421,11 +423,15 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
@Override
public UserModel validate(RealmModel realm, UserModel local) {
final boolean userExists = userPasswords.containsKey(local.getUsername());
final boolean userExists = userPasswords.containsKey(translateUserName(local.getUsername()));
if (! userExists) {
userGroups.remove(getUserIdInMap(realm, local.getUsername()));
}
return userExists ? local : null;
}
private static String translateUserName(String userName) {
return userName == null ? null : userName.toLowerCase();
}
}

View file

@ -266,6 +266,10 @@ public class LDAPRule extends ExternalResource {
return ldapTestConfiguration.getSleepTime();
}
public LDAPEmbeddedServer getLdapEmbeddedServer() {
return ldapEmbeddedServer;
}
/** Allows to run particular LDAP test just under specific conditions (eg. some test running just on Active Directory) **/
public interface LDAPAssume {

View file

@ -61,6 +61,11 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-server-spi</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak.testsuite</groupId>
<artifactId>integration-arquillian-tests-base</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${jdbc.mvn.groupId}</groupId>
<artifactId>${jdbc.mvn.artifactId}</artifactId>
@ -127,14 +132,28 @@
<profile>
<id>jpa-federation+infinispan</id>
<properties>
<keycloak.model.parameters>Infinispan,JpaFederation,BackwardsCompatibilityUserStorage</keycloak.model.parameters>
<keycloak.model.parameters>Infinispan,JpaFederation,TestsuiteUserMapStorage</keycloak.model.parameters>
</properties>
</profile>
<profile>
<id>jpa-federation</id>
<properties>
<keycloak.model.parameters>JpaFederation,BackwardsCompatibilityUserStorage</keycloak.model.parameters>
<keycloak.model.parameters>JpaFederation,TestsuiteUserMapStorage</keycloak.model.parameters>
</properties>
</profile>
<profile>
<id>jpa-federation+ldap</id>
<properties>
<keycloak.model.parameters>JpaFederation,LdapUserStorage</keycloak.model.parameters>
</properties>
</profile>
<profile>
<id>jpa-federation+ldap+infinispan</id>
<properties>
<keycloak.model.parameters>JpaFederation,LdapUserStorage,Infinispan</keycloak.model.parameters>
</properties>
</profile>

View file

@ -14,11 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model;
package org.keycloak.testsuite.model;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
*
@ -42,4 +45,23 @@ public class KeycloakModelParameters {
return allowedFactories.stream().anyMatch((c) -> c.isAssignableFrom(factory.getClass()));
}
/**
* Returns stream of parameters of the given type, or an empty stream if no parameters of the given type are supplied
* by this clazz.
* @param <T>
* @param clazz
* @return
*/
public <T> Stream<T> getParameters(Class<T> clazz) {
return Stream.empty();
}
public Statement classRule(Statement base, Description description) {
return base;
}
public Statement instanceRule(Statement base, Description description) {
return base;
}
}

View file

@ -14,14 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model;
package org.keycloak.testsuite.model;
import org.keycloak.Config.Scope;
import org.keycloak.authorization.AuthorizationSpi;
import org.keycloak.authorization.DefaultAuthorizationProviderFactory;
import org.keycloak.authorization.store.StoreFactorySpi;
import org.keycloak.cluster.ClusterSpi;
import org.keycloak.component.ComponentModel;
import org.keycloak.events.EventStoreSpi;
import org.keycloak.executors.DefaultExecutorsProviderFactory;
import org.keycloak.executors.ExecutorsSpi;
@ -30,7 +29,6 @@ import org.keycloak.models.ClientSpi;
import org.keycloak.models.GroupSpi;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmSpi;
import org.keycloak.models.RoleSpi;
import org.keycloak.models.UserSpi;
@ -41,28 +39,29 @@ import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.Spi;
import org.keycloak.services.DefaultKeycloakSession;
import org.keycloak.services.DefaultKeycloakSessionFactory;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import com.google.common.collect.ImmutableSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.hamcrest.Matchers;
import org.jboss.logging.Logger;
import org.junit.After;
import org.junit.Assume;
import org.junit.AssumptionViolatedException;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertThat;
/**
* Base of testcases that operate on session level. The tests derived from this class
@ -87,13 +86,56 @@ public abstract class KeycloakModelTest {
@Override
public Statement apply(Statement base, Description description) {
Class<?> testClass = description.getTestClass();
Stream<RequireProvider> st = Stream.empty();
while (testClass != Object.class) {
for (RequireProvider ann : testClass.getAnnotationsByType(RequireProvider.class)) {
Assume.assumeThat("Provider must exist: " + ann.value(), FACTORY.getProviderFactory(ann.value()), Matchers.notNullValue());
}
st = Stream.concat(Stream.of(testClass.getAnnotationsByType(RequireProvider.class)), st);
testClass = testClass.getSuperclass();
}
return base;
List<Class<? extends Provider>> notFound = st.map(RequireProvider::value)
.filter(pClass -> FACTORY.getProviderFactory(pClass) == null)
.collect(Collectors.toList());
Assume.assumeThat("Some required providers not found", notFound, Matchers.empty());
Statement res = base;
for (KeycloakModelParameters kmp : KeycloakModelTest.MODEL_PARAMETERS) {
res = kmp.classRule(res, description);
}
return res;
}
};
@Rule
public final TestRule guaranteeRequiredFactoryOnMethod = new TestRule() {
@Override
public Statement apply(Statement base, Description description) {
Stream<RequireProvider> st = Optional.ofNullable(description.getAnnotation(RequireProviders.class))
.map(RequireProviders::value)
.map(Stream::of)
.orElseGet(Stream::empty);
RequireProvider rp = description.getAnnotation(RequireProvider.class);
if (rp != null) {
st = Stream.concat(st, Stream.of(rp));
}
for (Iterator<Class<? extends Provider>> iterator = st.map(RequireProvider::value).iterator(); iterator.hasNext();) {
Class<? extends Provider> providerClass = iterator.next();
if (FACTORY.getProviderFactory(providerClass) == null) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
throw new AssumptionViolatedException("Provider must exist: " + providerClass);
}
};
}
}
Statement res = base;
for (KeycloakModelParameters kmp : KeycloakModelTest.MODEL_PARAMETERS) {
res = kmp.instanceRule(res, description);
}
return res;
}
};
@ -124,7 +166,7 @@ public abstract class KeycloakModelTest {
Stream.of(basicParameters),
Stream.of(System.getProperty("keycloak.model.parameters", "").split("\\s*,\\s*"))
.filter(s -> s != null && ! s.trim().isEmpty())
.map(cn -> { try { return Class.forName(cn.indexOf('.') >= 0 ? cn : ("org.keycloak.model.parameters." + cn)); } catch (Exception e) { LOG.error("Cannot find " + cn); return null; }})
.map(cn -> { try { return Class.forName(cn.indexOf('.') >= 0 ? cn : ("org.keycloak.testsuite.model.parameters." + cn)); } catch (Exception e) { LOG.error("Cannot find " + cn); return null; }})
.filter(Objects::nonNull)
.map(c -> { try { return c.newInstance(); } catch (Exception e) { LOG.error("Cannot instantiate " + c); return null; }} )
.filter(KeycloakModelParameters.class::isInstance)
@ -176,21 +218,12 @@ public abstract class KeycloakModelTest {
KeycloakModelUtils.runJobInTransaction(FACTORY, this::cleanEnvironment);
}
protected String registerUserFederationIfAvailable(RealmModel realm) {
final List<ProviderFactory> userFedProviders = FACTORY.getProviderFactories(UserStorageProvider.class);
protected <T> Stream<T> getParameters(Class<T> clazz) {
return MODEL_PARAMETERS.stream().flatMap(mp -> mp.getParameters(clazz)).filter(Objects::nonNull);
}
if (! userFedProviders.isEmpty() && realm != null) {
assertThat("Cannot handle more than 1 user federation provider", userFedProviders, hasSize(1));
UserStorageProviderModel federatedStorage = new UserStorageProviderModel();
federatedStorage.setName(userFedProviders.get(0).getId());
federatedStorage.setProviderId(userFedProviders.get(0).getId());
federatedStorage.setProviderType(UserStorageProvider.class.getName());
federatedStorage.setParentId(realm.getId());
ComponentModel res = realm.addComponentModel(federatedStorage);
log.infof("Added %s user federation provider: %s", federatedStorage.getName(), res.getId());
return res.getId();
}
return null;
protected <T> void withEach(Class<T> parameterClazz, Consumer<T> what) {
getParameters(parameterClazz).forEach(what);
}
protected <T> void inRolledBackTransaction(T parameter, BiConsumer<KeycloakSession, T> what) {

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model;
package org.keycloak.testsuite.model;
import org.keycloak.provider.Provider;
import java.lang.annotation.ElementType;
@ -30,7 +30,7 @@ import java.lang.annotation.Target;
* @author hmlnarik
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.METHOD})
@Repeatable(RequireProviders.class)
public @interface RequireProvider {
Class<? extends Provider> value() default Provider.class;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model;
package org.keycloak.testsuite.model;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@ -26,7 +26,7 @@ import java.lang.annotation.Target;
* @author hmlnarik
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface RequireProviders {
RequireProvider[] value();
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model;
package org.keycloak.testsuite.model;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.GroupModel;
@ -26,7 +26,6 @@ import org.keycloak.models.UserProvider;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderFactory;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
import java.util.ArrayList;
import java.util.HashSet;
@ -55,6 +54,9 @@ import static org.junit.Assume.assumeThat;
public class UserModelTest extends KeycloakModelTest {
protected static final int NUM_GROUPS = 100;
private static final int FIRST_DELETED_USER_INDEX = 10;
private static final int LAST_DELETED_USER_INDEX = 90;
private static final int DELETED_USER_COUNT = LAST_DELETED_USER_INDEX - FIRST_DELETED_USER_INDEX;
private String realmId;
private final List<String> groupIds = new ArrayList<>(NUM_GROUPS);
@ -65,8 +67,6 @@ public class UserModelTest extends KeycloakModelTest {
RealmModel realm = s.realms().createRealm("realm");
this.realmId = realm.getId();
this.userFederationId = registerUserFederationIfAvailable(realm);
IntStream.range(0, NUM_GROUPS).forEach(i -> {
groupIds.add(s.groups().createGroup(realm, "group-" + i).getId());
});
@ -150,12 +150,42 @@ public class UserModelTest extends KeycloakModelTest {
}
@Test
public void testAddDirtyRemoveFederationUsersInTheSameGroupConcurrent() {
assumeThat("Test for federated providers only", userFederationId, Matchers.notNullValue());
@RequireProvider(UserStorageProvider.class)
public void testAddDirtyRemoveFederationUser() {
registerUserFederationWithRealm();
inComittedTransaction(1, (session, i) -> {
final RealmModel realm = session.realms().getRealm(realmId);
final UserModel user = session.users().addUser(realm, "user-A");
});
// Remove user _from the federation_, simulates eg. user being removed from LDAP without Keycloak knowing
inComittedTransaction(1, (session, i) -> {
final RealmModel realm = session.realms().getRealm(realmId);
final UserStorageProvider instance = getUserFederationInstance(session, realm);
log.debugf("Removing selected users from backend");
final UserModel user = session.users().getUserByUsername("user-A", realm);
((UserRegistrationProvider) instance).removeUser(realm, user);
});
inComittedTransaction(1, (session, i) -> {
final RealmModel realm = session.realms().getRealm(realmId);
if (session.userCache() != null) {
session.userCache().clear();
}
final UserModel user = session.users().getUserByUsername("user-A", realm);
assertThat("User should not be found in the main store", user, Matchers.nullValue());
});
}
@Test
@RequireProvider(UserStorageProvider.class)
public void testAddDirtyRemoveFederationUsersInTheSameGroupConcurrent() {
final ConcurrentSkipListSet<String> userIds = new ConcurrentSkipListSet<>();
String groupId = groupIds.get(0);
registerUserFederationWithRealm();
// Create users and let them join first group
IntStream.range(0, 100).parallel().forEach(index -> inComittedTransaction(index, (session, i) -> {
final RealmModel realm = session.realms().getRealm(realmId);
@ -167,25 +197,11 @@ public class UserModelTest extends KeycloakModelTest {
// Remove users _from the federation_, simulates eg. user being removed from LDAP without Keycloak knowing
inComittedTransaction(1, (session, i) -> {
final RealmModel realm = session.realms().getRealm(realmId);
UserStorageProvider instance = (UserStorageProvider)session.getAttribute(userFederationId);
if (instance == null) {
ComponentModel model = realm.getComponent(userFederationId);
UserStorageProviderModel storageModel = new UserStorageProviderModel(model);
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
instance = factory.create(session, model);
if (instance == null) {
throw new RuntimeException("UserStorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance");
}
session.enlistForClose(instance);
session.setAttribute(userFederationId, instance);
}
final UserStorageProvider lambdaInstance = instance;
UserStorageProvider instance = getUserFederationInstance(session, realm);
log.debugf("Removing selected users from backend");
IntStream.range(FIRST_DELETED_USER_INDEX, LAST_DELETED_USER_INDEX).forEach(j -> {
final UserModel user = ((UserLookupProvider) lambdaInstance).getUserByUsername("user-" + j, realm);
((UserRegistrationProvider) lambdaInstance).removeUser(realm, user);
final UserModel user = session.users().getUserByUsername("user-" + j, realm);
((UserRegistrationProvider) instance).removeUser(realm, user);
});
});
@ -229,7 +245,34 @@ public class UserModelTest extends KeycloakModelTest {
assertThat(session.users().getGroupMembersStream(realm, group).collect(Collectors.toList()), Matchers.empty());
});
}
private static final int FIRST_DELETED_USER_INDEX = 10;
private static final int LAST_DELETED_USER_INDEX = 90;
private static final int DELETED_USER_COUNT = LAST_DELETED_USER_INDEX - FIRST_DELETED_USER_INDEX;
private void registerUserFederationWithRealm() {
getParameters(UserStorageProviderModel.class).forEach(fs -> inComittedTransaction(fs, (session, federatedStorage) -> {
assumeThat("Cannot handle more than 1 user federation provider", userFederationId, Matchers.nullValue());
RealmModel realm = session.realms().getRealm(realmId);
federatedStorage.setParentId(realmId);
federatedStorage.setImportEnabled(true);
ComponentModel res = realm.addComponentModel(federatedStorage);
userFederationId = res.getId();
log.infof("Added %s user federation provider: %s", federatedStorage.getName(), userFederationId);
}));
}
private UserStorageProvider getUserFederationInstance(KeycloakSession session, final RealmModel realm) throws RuntimeException {
UserStorageProvider instance = (UserStorageProvider)session.getAttribute(userFederationId);
if (instance == null) {
ComponentModel model = realm.getComponent(userFederationId);
UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId());
instance = factory.create(session, model);
if (instance == null) {
throw new RuntimeException("UserStorageProvideFactory (of type " + factory.getClass().getName() + ") produced a null instance");
}
session.enlistForClose(instance);
session.setAttribute(userFederationId, instance);
}
return instance;
}
}

View file

@ -14,14 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.events;
package org.keycloak.testsuite.model.events;
import org.keycloak.common.ClientConnection;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventStoreProvider;
import org.keycloak.events.EventType;
import org.keycloak.model.KeycloakModelTest;
import org.keycloak.model.RequireProvider;
import org.keycloak.testsuite.model.KeycloakModelTest;
import org.keycloak.testsuite.model.RequireProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import java.util.stream.Collectors;

View file

@ -14,17 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.storage.UserStorageProviderSpi;
import org.keycloak.storage.federated.UserFederatedStorageProviderSpi;
import org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.testsuite.federation.BackwardsCompatibilityUserStorageFactory;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
/**
*
@ -39,7 +40,22 @@ public class BackwardsCompatibilityUserStorage extends KeycloakModelParameters {
.add(BackwardsCompatibilityUserStorageFactory.class)
.build();
private final AtomicInteger counter = new AtomicInteger();
public BackwardsCompatibilityUserStorage() {
super(ALLOWED_SPIS, ALLOWED_FACTORIES);
}
@Override
public <T> Stream<T> getParameters(Class<T> clazz) {
if (UserStorageProviderModel.class.isAssignableFrom(clazz)) {
UserStorageProviderModel federatedStorage = new UserStorageProviderModel();
federatedStorage.setName(BackwardsCompatibilityUserStorageFactory.PROVIDER_ID + ":" + counter.getAndIncrement());
federatedStorage.setProviderId(BackwardsCompatibilityUserStorageFactory.PROVIDER_ID);
federatedStorage.setProviderType(UserStorageProvider.class.getName());
return Stream.of((T) federatedStorage);
} else {
return super.getParameters(clazz);
}
}
}

View file

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.map.client.MapClientProviderFactory;
import org.keycloak.models.map.group.MapGroupProviderFactory;
import org.keycloak.models.map.role.MapRoleProviderFactory;

View file

@ -14,12 +14,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.cluster.infinispan.InfinispanClusterProviderFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
import org.keycloak.connections.infinispan.InfinispanConnectionSpi;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.cache.CacheRealmProviderSpi;
import org.keycloak.models.cache.CacheUserProviderSpi;
import org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.authorization.jpa.store.JPAAuthorizationStoreFactory;
import org.keycloak.connections.jpa.DefaultJpaConnectionProviderFactory;
@ -25,7 +25,7 @@ import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionPr
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionSpi;
import org.keycloak.connections.jpa.updater.liquibase.lock.LiquibaseDBLockProviderFactory;
import org.keycloak.events.jpa.JpaEventStoreProviderFactory;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.dblock.DBLockSpi;
import org.keycloak.models.jpa.JpaClientProviderFactory;
import org.keycloak.models.jpa.JpaGroupProviderFactory;

View file

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.storage.UserStorageProviderSpi;

View file

@ -0,0 +1,101 @@
/*
* 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.model.parameters;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.LDAPConstants;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.storage.UserStorageProviderSpi;
import org.keycloak.storage.federated.UserFederatedStorageProviderSpi;
import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapperFactory;
import org.keycloak.storage.ldap.mappers.LDAPStorageMapperSpi;
import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.util.ldap.LDAPEmbeddedServer;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
/**
*
* @author hmlnarik
*/
public class LdapUserStorage extends KeycloakModelParameters {
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.add(UserStorageProviderSpi.class)
.add(UserFederatedStorageProviderSpi.class)
.add(LDAPStorageMapperSpi.class)
.build();
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(LDAPStorageMapperFactory.class)
.add(LDAPStorageProviderFactory.class)
.build();
private final AtomicInteger counter = new AtomicInteger();
private final LDAPRule ldapRule = new LDAPRule();
public LdapUserStorage() {
super(ALLOWED_SPIS, ALLOWED_FACTORIES);
}
@Override
public <T> Stream<T> getParameters(Class<T> clazz) {
if (UserStorageProviderModel.class.isAssignableFrom(clazz)) {
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
for (java.util.Map.Entry<String, String> entry : ldapRule.getConfig().entrySet()) {
config.add(entry.getKey(), entry.getValue());
}
config.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
config.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
UserStorageProviderModel federatedStorage = new UserStorageProviderModel();
federatedStorage.setName(LDAPStorageProviderFactory.PROVIDER_NAME + ":" + counter.getAndIncrement());
federatedStorage.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
federatedStorage.setProviderType(UserStorageProvider.class.getName());
federatedStorage.setLastSync(0);
federatedStorage.setChangedSyncPeriod(-1);
federatedStorage.setFullSyncPeriod(-1);
federatedStorage.setPriority(0);
federatedStorage.setConfig(config);
return Stream.of((T) federatedStorage);
} else {
return super.getParameters(clazz);
}
}
static {
System.setProperty(LDAPEmbeddedServer.PROPERTY_ENABLE_SSL, "false");
}
@Override
public Statement classRule(Statement base, Description description) {
return ldapRule.apply(base, description);
}
}

View file

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.model.parameters;
package org.keycloak.testsuite.model.parameters;
import org.keycloak.model.KeycloakModelParameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.models.map.client.MapClientProviderFactory;
import org.keycloak.models.map.group.MapGroupProviderFactory;
import org.keycloak.models.map.role.MapRoleProviderFactory;

View file

@ -0,0 +1,62 @@
/*
* 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.model.parameters;
import org.keycloak.testsuite.model.KeycloakModelParameters;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.UserStorageProviderModel;
import org.keycloak.testsuite.federation.BackwardsCompatibilityUserStorageFactory;
import org.keycloak.testsuite.federation.UserMapStorageFactory;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
/**
*
* @author hmlnarik
*/
public class TestsuiteUserMapStorage extends KeycloakModelParameters {
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
.build();
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
.add(UserMapStorageFactory.class)
.build();
private final AtomicInteger counter = new AtomicInteger();
public TestsuiteUserMapStorage() {
super(ALLOWED_SPIS, ALLOWED_FACTORIES);
}
@Override
public <T> Stream<T> getParameters(Class<T> clazz) {
if (UserStorageProviderModel.class.isAssignableFrom(clazz)) {
UserStorageProviderModel federatedStorage = new UserStorageProviderModel();
federatedStorage.setName(UserMapStorageFactory.PROVIDER_ID + ":" + counter.getAndIncrement());
federatedStorage.setProviderId(UserMapStorageFactory.PROVIDER_ID);
federatedStorage.setProviderType(UserStorageProvider.class.getName());
return Stream.of((T) federatedStorage);
} else {
return super.getParameters(clazz);
}
}
}

View file

@ -0,0 +1 @@
../../../../integration-arquillian/tests/base/src/test/resources/kerberos

View file

@ -0,0 +1 @@
../../../../integration-arquillian/tests/base/src/test/resources/keystore

View file

@ -0,0 +1 @@
../../../../integration-arquillian/tests/base/src/test/resources/ldap

View file

@ -23,7 +23,12 @@ keycloak.testsuite.logging.pattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n
log4j.appender.keycloak.layout.ConversionPattern=${keycloak.testsuite.logging.pattern}
# Logging with "info" when running test from IDE, but disabled when running test with "mvn" . Both cases can be overriden by use system property "keycloak.logging.level" (eg. -Dkeycloak.logging.level=debug )
log4j.logger.org.keycloak.models=debug
log4j.logger.org.keycloak=${keycloak.logging.level:info}
keycloak.testsuite.logging.level=debug
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
# Logging with "info" when running test from IDE, but disabled when running test with "mvn" . Both cases can be overriden by use system property "keycloak.logging.level" (eg. -Dkeycloak.logging.level=debug )
# log4j.logger.org.hibernate=debug
# Enable to view loaded SPI and Providers

View file

@ -0,0 +1,16 @@
#!/bin/bash
cd "$(dirname $0)"
EXIT_CODE=0
mvn clean
for I in `perl -ne 'print "$1\n" if (m,<id>([^<]+)</id>,)' pom.xml`; do
echo "========"
echo "======== Profile $I"
echo "========"
mvn test "-P$I" "$@"
EXIT_CODE=$[$EXIT_CODE + $?]
mv target/surefire-reports "target/surefire-reports-$I"
done
exit $EXIT_CODE