Ensure that IDP's linked domains are remove when org is deleted or when the domain is removed from the org.

Closes #29481

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2024-05-14 11:02:52 -03:00 committed by Pedro Igor
parent 1d613d9037
commit c4760b8188
4 changed files with 41 additions and 2 deletions

View file

@ -17,7 +17,9 @@
package org.keycloak.organization.jpa;
import static org.keycloak.models.OrganizationModel.BROKER_PUBLIC;
import static org.keycloak.models.OrganizationModel.ORGANIZATION_ATTRIBUTE;
import static org.keycloak.models.OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE;
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
import static org.keycloak.utils.StreamsUtil.closing;
@ -327,7 +329,10 @@ public class JpaOrganizationProvider implements OrganizationProvider {
return false;
}
// clear the organization id and any domain assigned to the IDP.
identityProvider.setOrganizationId(null);
identityProvider.getConfig().remove(ORGANIZATION_DOMAIN_ATTRIBUTE);
identityProvider.getConfig().remove(BROKER_PUBLIC);
realm.updateIdentityProvider(identityProvider);
return true;

View file

@ -136,6 +136,8 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
throw new ModelValidationException("You must provide at least one domain");
}
List<IdentityProviderModel> idps = this.getIdentityProviders().toList();
Map<String, OrganizationDomainModel> modelMap = domains.stream()
.map(this::validateDomain)
.collect(Collectors.toMap(OrganizationDomainModel::getName, Function.identity()));
@ -149,6 +151,12 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
// remove domain that is not found in the new set.
else {
this.entity.removeDomain(domainEntity);
// check if any idp is assigned to the removed domain, and unset the domain if that's the case.
idps.forEach(idp -> {
if (Objects.equals(domainEntity.getName(), idp.getConfig().get(ORGANIZATION_DOMAIN_ATTRIBUTE))) {
idp.getConfig().remove(ORGANIZATION_DOMAIN_ATTRIBUTE);
}
});
}
}

View file

@ -30,7 +30,6 @@ import jakarta.ws.rs.core.Response.Status;
import org.jboss.arquillian.graphene.page.Page;
import org.keycloak.admin.client.resource.OrganizationResource;
import org.keycloak.models.OrganizationModel;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.admin.client.resource.UsersResource;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.admin.client.resource.RealmResource;

View file

@ -20,6 +20,10 @@ package org.keycloak.testsuite.organization.admin;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.keycloak.models.OrganizationModel.BROKER_PUBLIC;
import static org.keycloak.models.OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE;
import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.NotFoundException;
@ -156,6 +160,8 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
IdentityProviderRepresentation idpRep = testRealm().identityProviders().get(bc.getIDPAlias()).toRepresentation();
// broker no longer linked to the org
Assert.assertNull(idpRep.getConfig().get(OrganizationModel.ORGANIZATION_ATTRIBUTE));
Assert.assertNull(idpRep.getConfig().get(ORGANIZATION_DOMAIN_ATTRIBUTE));
Assert.assertNull(idpRep.getConfig().get(BROKER_PUBLIC));
}
@Test
@ -183,7 +189,7 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
OrganizationIdentityProviderResource orgIdPResource = orgResource.identityProviders().get(bc.getIDPAlias());
IdentityProviderRepresentation idpRep = orgIdPResource.toRepresentation();
idpRep.getConfig().put(OrganizationModel.ORGANIZATION_DOMAIN_ATTRIBUTE, "unknown.org");
idpRep.getConfig().put(ORGANIZATION_DOMAIN_ATTRIBUTE, "unknown.org");
try {
testRealm().identityProviders().get(idpRep.getAlias()).update(idpRep);
@ -220,6 +226,27 @@ public class OrganizationIdentityProviderTest extends AbstractOrganizationTest {
});
}
@Test
public void testRemovedDomainUpdatedInIDP() {
OrganizationRepresentation orgRep = createOrganization("testorg", "testorg.com", "testorg.net");
OrganizationResource orgResource = testRealm().organizations().get(orgRep.getId());
OrganizationIdentityProviderResource orgIdPResource = orgResource.identityProviders().get("testorg-identity-provider");
IdentityProviderRepresentation idpRep = orgIdPResource.toRepresentation();
// IDP should have been assigned to the first domain.
assertThat(idpRep.getConfig().get(ORGANIZATION_DOMAIN_ATTRIBUTE), is(equalTo("testorg.com")));
// let's update the organization, removing the domain linked to the IDP.
orgRep.removeDomain(orgRep.getDomain("testorg.com"));
try (Response response = orgResource.update(orgRep)) {
assertThat(response.getStatus(), is(equalTo(Status.NO_CONTENT.getStatusCode())));
}
// fetch the idp config and check if the domain has been unlinked.
idpRep = orgIdPResource.toRepresentation();
assertThat(idpRep.getConfig().get(ORGANIZATION_DOMAIN_ATTRIBUTE), is(nullValue()));
}
private IdentityProviderRepresentation createRep(String alias, String providerId) {
IdentityProviderRepresentation idp = new IdentityProviderRepresentation();