Ensure the group being joined is not an organization group in GroupLDAPStorageMapper

Closes #37393

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2025-02-17 09:09:07 -03:00 committed by Pedro Igor
parent 61e48f1cd3
commit 5babc6c1a3
4 changed files with 131 additions and 0 deletions

View File

@ -807,6 +807,9 @@ public class GroupLDAPStorageMapper extends AbstractLDAPStorageMapper implements
}
protected boolean isGroupInGroupPath(RealmModel realm, GroupModel group) {
if (group.getType() == GroupModel.Type.ORGANIZATION) {
return false; // always skip organization groups as those are internal groups.
}
if (config.isTopLevelGroupsPath()) {
return true; // any group is in the path of the top level path.
}

View File

@ -20,6 +20,7 @@ package org.keycloak.admin.client.resource;
import java.util.List;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
@ -39,6 +40,10 @@ public interface OrganizationMembersResource {
@Consumes(MediaType.APPLICATION_JSON)
Response addMember(String userId);
@Path("{member-id}")
@DELETE
Response removeMember(@PathParam("member-id") String memberId);
/**
* Return all members in the organization.
*

View File

@ -458,6 +458,17 @@ public class LDAPTestUtils {
}
}
public static LDAPObject getLdapGroupByName(KeycloakSession session, RealmModel realm, String mapperName, String groupName) {
ComponentModel ldapModel = LDAPTestUtils.getLdapProviderModel(realm);
ComponentModel mapperModel = getSubcomponentByName(realm, ldapModel, mapperName);
LDAPStorageProvider ldapProvider = getLdapProvider(session, ldapModel);
if (GroupLDAPStorageMapperFactory.PROVIDER_ID.equals(mapperModel.getProviderId())) {
return getGroupMapper(mapperModel, ldapProvider, realm).loadLDAPGroupByName(groupName);
} else {
return getRoleMapper(mapperModel, ldapProvider, realm).loadLDAPRoleByName(groupName);
}
}
public static LDAPObject updateLDAPGroup(KeycloakSession session, RealmModel appRealm, ComponentModel ldapModel, LDAPObject ldapObject) {
ComponentModel mapperModel = getSubcomponentByName(appRealm, ldapModel, "groupsMapper");
LDAPStorageProvider ldapProvider = LDAPTestUtils.getLdapProvider(session, ldapModel);

View File

@ -0,0 +1,112 @@
/*
* Copyright 2025 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.organization.member;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.ws.rs.core.Response;
import org.junit.ClassRule;
import org.junit.Test;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.ldap.mappers.membership.LDAPGroupMapperMode;
import org.keycloak.storage.ldap.mappers.membership.group.GroupMapperConfig;
import org.keycloak.testsuite.federation.ldap.LDAPTestContext;
import org.keycloak.testsuite.organization.admin.AbstractOrganizationTest;
import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
public class OrganizationMemberWithLdapTest extends AbstractOrganizationTest {
@ClassRule
public static LDAPRule ldapRule = new LDAPRule();
@Override
public void importTestRealms() {
super.importTestRealms();
// add an LDAP provider with a group mapper
Map<String, String> cfg = ldapRule.getConfig();
testingClient.testing().ldap(TEST_REALM_NAME).createLDAPProvider(cfg, true);
testingClient.testing().ldap(TEST_REALM_NAME).prepareGroupsLDAPTest();
}
@Test
public void testLdapUserJoiningAndLeavingOrganization() {
testingClient.server().run(session -> {
LDAPTestContext ctx = LDAPTestContext.init(session);
RealmModel appRealm = ctx.getRealm();
// ensure groups mapper is in LDAP_ONLY mode - we want to check that upon joining the org, the org group is NOT pushed to LDAP.
ComponentModel mapperModel = LDAPTestUtils.getSubcomponentByName(appRealm, ctx.getLdapModel(), "groupsMapper");
LDAPTestUtils.updateConfigOptions(mapperModel, GroupMapperConfig.MODE, LDAPGroupMapperMode.LDAP_ONLY.toString());
appRealm.updateComponent(mapperModel);
// check that the LDAP provider is working - i.e. users are available and groups have been properly synced.
UserModel john = session.users().getUserByUsername(appRealm, "johnkeycloak");
assertThat(john, notNullValue());
GroupModel testGroup = KeycloakModelUtils.findGroupByPath(session, appRealm, "/group1");
assertThat(testGroup, notNullValue());
});
OrganizationResource organization = testRealm().organizations().get(createOrganization().getId());
OrganizationRepresentation orgRepresentation = organization.toRepresentation();
UserRepresentation ldapUser = testRealm().users().searchByUsername("johnkeycloak", true).get(0);
// make the LDAP user join the organization and check it was successful.
try (Response response = organization.members().addMember(ldapUser.getId())) {
assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus());
}
List<OrganizationRepresentation> orgMemberships = organization.members().member(ldapUser.getId()).getOrganizations();
assertThat(orgMemberships, notNullValue());
assertThat(orgMemberships, hasSize(1));
assertThat(orgMemberships.get(0).getId(), equalTo(orgRepresentation.getId()));
// check that the org group was NOT pushed to LDAP as a result of joining the org.
AtomicReference<String> orgId = new AtomicReference<>(orgRepresentation.getId());
testingClient.server(TEST_REALM_NAME).run(session -> {
LDAPTestContext context = LDAPTestContext.init(session);
assertThat(LDAPTestUtils.getLdapGroupByName(session, context.getRealm(), "groupsMapper", orgId.get()), is(nullValue()));
});
// make the user leave the organization and check it was successful.
try (Response response = organization.members().removeMember(ldapUser.getId())) {
assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus());
}
List<MemberRepresentation> orgMembers = organization.members().list(-1, -1);
assertThat(orgMembers, hasSize(0));
}
}