diff --git a/tests/utils-shared/pom.xml b/tests/utils-shared/pom.xml
index 53d0a7721c2..8ba86630a90 100755
--- a/tests/utils-shared/pom.xml
+++ b/tests/utils-shared/pom.xml
@@ -51,6 +51,14 @@
org.keycloak
keycloak-services
+
+ org.keycloak
+ keycloak-model-infinispan
+
+
+ org.keycloak
+ keycloak-ldap-federation
+
org.keycloak
keycloak-admin-client-tests
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java
similarity index 100%
rename from testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java
rename to tests/utils-shared/src/main/java/org/keycloak/testsuite/util/LDAPTestUtils.java
diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/CacheHelper.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/CacheHelper.java
new file mode 100644
index 00000000000..14e2cdde430
--- /dev/null
+++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/CacheHelper.java
@@ -0,0 +1,44 @@
+package org.keycloak.testsuite.util.runonserver;
+
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.testframework.remote.providers.runonserver.FetchOnServer;
+import org.keycloak.testframework.remote.providers.runonserver.FetchOnServerWrapper;
+
+public final class CacheHelper {
+
+ public static FetchOnServerWrapper contains(String cacheName, String id) {
+ return new FetchOnServerWrapper<>() {
+
+ @Override
+ public FetchOnServer getRunOnServer() {
+ return session -> {
+ InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
+ return provider.getCache(cacheName).containsKey(id);
+ };
+ }
+
+ @Override
+ public Class getResultClass() {
+ return Boolean.class;
+ }
+ };
+ }
+
+ public static FetchOnServerWrapper size(String cacheName) {
+ return new FetchOnServerWrapper<>() {
+
+ @Override
+ public FetchOnServer getRunOnServer() {
+ return session -> {
+ InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
+ return provider.getCache(cacheName).size();
+ };
+ }
+
+ @Override
+ public Class getResultClass() {
+ return Integer.class;
+ }
+ };
+ }
+}
diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/LdapHelper.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/LdapHelper.java
new file mode 100644
index 00000000000..0593607802d
--- /dev/null
+++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/runonserver/LdapHelper.java
@@ -0,0 +1,139 @@
+package org.keycloak.testsuite.util.runonserver;
+
+import java.util.Map;
+
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.component.ComponentModel;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.storage.UserStorageProvider;
+import org.keycloak.storage.UserStorageProviderModel;
+import org.keycloak.storage.ldap.LDAPStorageProvider;
+import org.keycloak.storage.ldap.LDAPStorageProviderFactory;
+import org.keycloak.storage.ldap.LDAPUtils;
+import org.keycloak.storage.ldap.idm.model.LDAPObject;
+import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
+import org.keycloak.storage.ldap.mappers.membership.MembershipType;
+import org.keycloak.storage.ldap.mappers.membership.group.GroupLDAPStorageMapperFactory;
+import org.keycloak.testframework.remote.providers.runonserver.FetchOnServer;
+import org.keycloak.testframework.remote.providers.runonserver.RunOnServer;
+import org.keycloak.testsuite.util.LDAPTestUtils;
+
+import static org.keycloak.testsuite.util.LDAPTestUtils.getGroupDescriptionLDAPAttrName;
+
+public final class LdapHelper {
+
+ /**
+ * @param ldapCfg configuration of LDAP provider
+ * @param importEnabled specify if LDAP provider will have import enabled
+ * @return ID of newly created provider
+ */
+ public static FetchOnServer createLDAPProvider(Map ldapCfg, boolean importEnabled) {
+ return session -> {
+ MultivaluedHashMap ldapConfig = toComponentConfig(ldapCfg);
+ ldapConfig.putSingle(LDAPConstants.SYNC_REGISTRATIONS, "true");
+ ldapConfig.putSingle(LDAPConstants.EDIT_MODE, UserStorageProvider.EditMode.WRITABLE.toString());
+ UserStorageProviderModel model = new UserStorageProviderModel();
+ model.setLastSync(0);
+ model.setChangedSyncPeriod(-1);
+ model.setFullSyncPeriod(-1);
+ model.setName("test-ldap");
+ model.setPriority(0);
+ model.setProviderId(LDAPStorageProviderFactory.PROVIDER_NAME);
+ model.setConfig(ldapConfig);
+
+ model.setImportEnabled(importEnabled);
+
+ model.setCachePolicy(UserStorageProviderModel.CachePolicy.MAX_LIFESPAN);
+ model.setMaxLifespan(600000); // Lifetime is 10 minutes
+
+ ComponentModel ldapModel = session.getContext().getRealm().addComponentModel(model);
+ return ldapModel.getId();
+ };
+ }
+
+ private static MultivaluedHashMap toComponentConfig(Map ldapConfig) {
+ MultivaluedHashMap config = new MultivaluedHashMap<>();
+ for (Map.Entry entry : ldapConfig.entrySet()) {
+ config.add(entry.getKey(), entry.getValue());
+
+ }
+ return config;
+ }
+
+
+ /**
+ * Prepare groups LDAP tests. Creates some LDAP mappers as well as some built-in GRoups and users in LDAP
+ */
+ public static RunOnServer prepareGroupsLDAPTest() {
+ return session -> {
+ RealmModel realm = session.getContext().getRealm();
+ LDAPTestUtils.addLocalUser(session, realm, "mary", "mary@test.com", "password-app");
+ LDAPTestUtils.addLocalUser(session, realm, "john", "john@test.com", "password-app");
+
+ ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(realm);
+ LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);
+ String descriptionAttrName = getGroupDescriptionLDAPAttrName(ldapFedProvider);
+
+ // Add group mapper
+ LDAPTestUtils.addOrUpdateGroupMapper(realm, ldapModel, LDAPGroupMapperMode.LDAP_ONLY, descriptionAttrName);
+
+ // Remove all LDAP groups
+ LDAPTestUtils.removeAllLDAPGroups(session, realm, ldapModel, "groupsMapper");
+
+ // Add some groups for testing
+ LDAPObject group1 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group1", descriptionAttrName, "group1 - description");
+ LDAPObject group11 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group11");
+ LDAPObject group12 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "group12", descriptionAttrName, "group12 - description");
+
+ LDAPObject defaultGroup1 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup1", descriptionAttrName, "Default Group1 - description");
+ LDAPObject defaultGroup11 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup11");
+ LDAPObject defaultGroup12 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup12", descriptionAttrName, "Default Group12 - description");
+ LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team 2016/2017", descriptionAttrName, "A group with slashes in the name");
+ LDAPObject teamChild20182019 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team Child 2018/2019", descriptionAttrName, "A child group with slashes in the name");
+ LDAPObject teamSubChild20202021 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team SubChild 2020/2021", descriptionAttrName, "A sub child group with slashes in the name");
+ LDAPObject defaultGroup13 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup13", descriptionAttrName, "Default Group13 - description");
+ LDAPObject teamSubChild20222023 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team SubChild 2022/2023/A/B/C/D/E", descriptionAttrName, "A sub child group with slashes in the name");
+ LDAPObject defaultGroup14 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup14", descriptionAttrName, "Default Group14 - description");
+ LDAPObject teamRoot20242025 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team Root 2024/2025/A/B/C/D", descriptionAttrName, "A sub child group with slashes in the name");
+ LDAPObject defaultGroup15 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "defaultGroup15", descriptionAttrName, "Default Group15 - description");
+ LDAPObject teamSubChild20262027 = LDAPTestUtils.createLDAPGroup(session, realm, ldapModel, "Team SubChild 2026/2027", descriptionAttrName, "A sub child group with slashes in the name");
+
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group11);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", group1, group12);
+
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup1, defaultGroup11);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup1, defaultGroup12);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup1, teamChild20182019);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", teamChild20182019, teamSubChild20202021);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup13, teamSubChild20222023);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", teamSubChild20222023, defaultGroup14);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", teamRoot20242025, defaultGroup15);
+ LDAPUtils.addMember(ldapFedProvider, MembershipType.DN, LDAPConstants.MEMBER, "not-used", defaultGroup15, teamSubChild20262027);
+
+ // Sync LDAP groups to Keycloak DB
+ ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(realm, ldapModel, "groupsMapper");
+ new GroupLDAPStorageMapperFactory().create(session, mapperModel).syncDataFromFederationProviderToKeycloak(realm);
+
+ realm.addDefaultGroup(KeycloakModelUtils.findGroupByPath(session, realm, "/defaultGroup1/defaultGroup11"));
+ realm.addDefaultGroup(KeycloakModelUtils.findGroupByPath(session, realm, "/defaultGroup1/defaultGroup12"));
+
+ // Delete all LDAP users
+ LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, realm);
+
+ // Add some LDAP users for testing
+ LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1");
+
+ LDAPObject mary = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "marykeycloak", "Mary", "Kelly", "mary@email.org", null, "5678");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, mary, "Password1");
+
+ LDAPObject rob = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "robkeycloak", "Rob", "Brown", "rob@email.org", null, "8910");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, rob, "Password1");
+
+ LDAPObject james = LDAPTestUtils.addLDAPUser(ldapFedProvider, realm, "jameskeycloak", "James", "Brown", "james@email.org", null, "8910");
+ LDAPTestUtils.updateLDAPPassword(ldapFedProvider, james, "Password1");
+ };
+ }
+}
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
index 874cb63b294..3ffbd761f70 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java
@@ -54,34 +54,19 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.events.Event;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.http.HttpRequest;
-import org.keycloak.models.AuthenticationFlowModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RealmProvider;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserProvider;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.representations.idm.AdminEventRepresentation;
-import org.keycloak.representations.idm.AuthenticationFlowRepresentation;
import org.keycloak.representations.idm.EventRepresentation;
-import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.resource.RealmResourceProvider;
-import org.keycloak.storage.UserStorageProvider;
import org.keycloak.testframework.remote.providers.runonserver.FetchOnServer;
import org.keycloak.testframework.remote.providers.runonserver.RunOnServer;
import org.keycloak.testframework.remote.providers.runonserver.SerializationUtil;
import org.keycloak.testsuite.components.amphibian.TestAmphibianProvider;
import org.keycloak.testsuite.events.TestEventsListenerProvider;
-import org.keycloak.testsuite.federation.DummyUserFederationProviderFactory;
-import org.keycloak.testsuite.forms.PassThroughAuthenticator;
-import org.keycloak.testsuite.forms.PassThroughClientAuthenticator;
import org.keycloak.testsuite.model.infinispan.InfinispanTestUtil;
-import org.keycloak.testsuite.rest.representation.AuthenticatorState;
-import org.keycloak.testsuite.rest.resource.TestCacheResource;
-import org.keycloak.testsuite.rest.resource.TestLDAPResource;
import org.keycloak.testsuite.util.FeatureDeployerUtil;
import org.keycloak.timer.TimerProvider;
import org.keycloak.truststore.FileTruststoreProvider;
@@ -175,107 +160,10 @@ public class TestingResourceProvider implements RealmResourceProvider {
return Response.noContent().build();
}
- @Path("/cache/{cache}")
- public TestCacheResource getCacheResource(@PathParam("cache") String cacheName) {
- return new TestCacheResource(session, cacheName);
- }
-
-
- @Path("/ldap/{realm}")
- public TestLDAPResource ldap(@PathParam("realm") final String realmName) {
- RealmModel realm = session.realms().getRealmByName(realmName);
- return new TestLDAPResource(session, realm);
- }
-
-
@Override
public void close() {
}
- @POST
- @Path("/update-pass-through-auth-state")
- @Produces(MediaType.APPLICATION_JSON)
- public AuthenticatorState updateAuthenticator(AuthenticatorState state) {
- if (state.getClientId() != null) {
- PassThroughClientAuthenticator.clientId = state.getClientId();
- }
- if (state.getUsername() != null) {
- PassThroughAuthenticator.username = state.getUsername();
- }
-
- AuthenticatorState result = new AuthenticatorState();
- result.setClientId(PassThroughClientAuthenticator.clientId);
- result.setUsername(PassThroughAuthenticator.username);
- return result;
- }
-
- @GET
- @Path("/valid-credentials")
- @Produces(MediaType.APPLICATION_JSON)
- public boolean validCredentials(@QueryParam("realmName") String realmName, @QueryParam("userName") String userName, @QueryParam("password") String password) {
- RealmModel realm = session.realms().getRealmByName(realmName);
- if (realm == null) return false;
- UserProvider userProvider = session.getProvider(UserProvider.class);
- UserModel user = userProvider.getUserByUsername(realm, userName);
- return user.credentialManager().isValid(UserCredentialModel.password(password));
- }
-
- @GET
- @Path("/user-by-federated-identity")
- @Produces(MediaType.APPLICATION_JSON)
- public UserRepresentation getUserByFederatedIdentity(@QueryParam("realmName") String realmName,
- @QueryParam("identityProvider") String identityProvider,
- @QueryParam("userId") String userId,
- @QueryParam("userName") String userName) {
- RealmModel realm = getRealmByName(realmName);
- UserModel foundFederatedUser = session.users().getUserByFederatedIdentity(realm, new FederatedIdentityModel(identityProvider, userId, userName));
- if (foundFederatedUser == null) return null;
- return ModelToRepresentation.toRepresentation(session, realm, foundFederatedUser);
- }
-
- @GET
- @Path("/user-by-username-from-fed-factory")
- @Produces(MediaType.APPLICATION_JSON)
- public UserRepresentation getUserByUsernameFromFedProviderFactory(@QueryParam("realmName") String realmName,
- @QueryParam("userName") String userName) {
- RealmModel realm = getRealmByName(realmName);
- DummyUserFederationProviderFactory factory = (DummyUserFederationProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, "dummy");
- UserModel user = factory.create(session, null).getUserByUsername(realm, userName);
- if (user == null) return null;
- return ModelToRepresentation.toRepresentation(session, realm, user);
- }
-
- @GET
- @Path("/get-client-auth-flow")
- @Produces(MediaType.APPLICATION_JSON)
- public AuthenticationFlowRepresentation getClientAuthFlow(@QueryParam("realmName") String realmName) {
- RealmModel realm = getRealmByName(realmName);
- AuthenticationFlowModel flow = realm.getClientAuthenticationFlow();
- if (flow == null) return null;
- return ModelToRepresentation.toRepresentation(session, realm, flow);
- }
-
- @GET
- @Path("/get-reset-cred-flow")
- @Produces(MediaType.APPLICATION_JSON)
- public AuthenticationFlowRepresentation getResetCredFlow(@QueryParam("realmName") String realmName) {
- RealmModel realm = getRealmByName(realmName);
- AuthenticationFlowModel flow = realm.getResetCredentialsFlow();
- if (flow == null) return null;
- return ModelToRepresentation.toRepresentation(session, realm, flow);
- }
-
- @GET
- @Path("/get-user-by-service-account-client")
- @Produces(MediaType.APPLICATION_JSON)
- public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId) {
- RealmModel realm = getRealmByName(realmName);
- ClientModel client = realm.getClientByClientId(clientId);
- UserModel user = session.users().getServiceAccount(client);
- if (user == null) return null;
- return ModelToRepresentation.toRepresentation(session, realm, user);
- }
-
@GET
@Path("/test-amphibian-component")
@Produces(MediaType.APPLICATION_JSON)
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/representation/AuthenticatorState.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/representation/AuthenticatorState.java
index fdc5c3a4663..c857168928c 100644
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/representation/AuthenticatorState.java
+++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/representation/AuthenticatorState.java
@@ -17,10 +17,12 @@
package org.keycloak.testsuite.rest.representation;
+import java.io.Serializable;
+
/**
* @author Marko Strukelj
*/
-public class AuthenticatorState {
+public class AuthenticatorState implements Serializable {
private String clientId;
private String username;
diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
deleted file mode 100644
index 7ddf2553626..00000000000
--- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/resource/TestCacheResource.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright 2016 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.rest.resource;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-import java.util.stream.Collectors;
-
-import jakarta.ws.rs.Consumes;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.POST;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.PathParam;
-import jakarta.ws.rs.Produces;
-
-import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.utils.MediaType;
-
-import org.infinispan.Cache;
-import org.infinispan.stream.CacheCollectors;
-
-/**
- * @author Marek Posolda
- */
-public class TestCacheResource {
-
- private final Cache