mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-21 06:20:05 -06:00
Make organization domains optional
Closes #31285 Signed-off-by: Alexis Rico <sferadev@gmail.com>
This commit is contained in:
@@ -47,7 +47,7 @@ Redirect URL::
|
||||
After completing registration or accepting an invitation to the organization sent via email, the user is automatically redirected to the specified redirect url. If left empty, the user will be redirected to the account console by default.
|
||||
|
||||
Domains::
|
||||
A set of one or more domains that belongs to this organization. A domain cannot be shared by different organizations within a realm.
|
||||
A set of zero or more domains that belongs to this organization. A domain cannot be shared by different organizations within a realm. When no domains are specified, organization members will not be validated against domain restrictions during authentication and profile validation.
|
||||
|
||||
Description::
|
||||
A free-text field to describe the organization.
|
||||
|
||||
@@ -68,14 +68,12 @@ export const OrganizationForm = ({
|
||||
fieldLabelId="domain"
|
||||
/>
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<MultiLineInput
|
||||
id="domain"
|
||||
name="domains"
|
||||
aria-label={t("domain")}
|
||||
addButtonLabel="addDomain"
|
||||
isRequired
|
||||
/>
|
||||
{errors?.["domains"]?.message && (
|
||||
<FormErrorText message={errors["domains"].message.toString()} />
|
||||
|
||||
@@ -42,6 +42,7 @@ import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
import jakarta.persistence.criteria.From;
|
||||
import jakarta.persistence.criteria.Join;
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
import jakarta.persistence.criteria.Predicate;
|
||||
import jakarta.persistence.criteria.Root;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
@@ -302,26 +303,28 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||
|
||||
private Predicate buildStringSearchPredicate(CriteriaBuilder builder, CriteriaQuery<?> query, Root<OrganizationEntity> org, String search,
|
||||
Boolean exact) {
|
||||
Root<OrganizationDomainEntity> domain = query.from(OrganizationDomainEntity.class);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
RealmModel realm = getRealm();
|
||||
predicates.add(builder.equal(org.get("realmId"), realm.getId()));
|
||||
predicates.add(builder.equal(org.get("id"), domain.get("organization").get("id")));
|
||||
Predicate realmPredicate = builder.equal(org.get("realmId"), realm.getId());
|
||||
|
||||
if (StringUtil.isBlank(search)) {
|
||||
return realmPredicate;
|
||||
}
|
||||
|
||||
predicates.add(realmPredicate);
|
||||
|
||||
Join<OrganizationEntity, OrganizationDomainEntity> domain = org.join("domains", JoinType.LEFT);
|
||||
Predicate namePredicate;
|
||||
Predicate domainPredicate;
|
||||
if (StringUtil.isBlank(search)) {
|
||||
namePredicate = builder.conjunction(); // constant true
|
||||
domainPredicate = builder.conjunction();
|
||||
|
||||
} else if (Boolean.TRUE.equals(exact)) {
|
||||
if (Boolean.TRUE.equals(exact)) {
|
||||
namePredicate = builder.equal(org.get("name"), search);
|
||||
domainPredicate = builder.equal(domain.get("name"), search);
|
||||
} else {
|
||||
namePredicate = builder.like(builder.lower(org.get("name")), "%" + search.toLowerCase() + "%");
|
||||
domainPredicate = builder.like(domain.get("name"), "%" + search.toLowerCase() + "%");
|
||||
}
|
||||
|
||||
predicates.add(builder.or(namePredicate, domainPredicate));
|
||||
|
||||
return builder.and(predicates.toArray(new Predicate[0]));
|
||||
|
||||
@@ -175,8 +175,8 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
||||
|
||||
@Override
|
||||
public void setDomains(Set<OrganizationDomainModel> domains) {
|
||||
if (domains == null || domains.isEmpty()) {
|
||||
throw new ModelValidationException("You must provide at least one domain");
|
||||
if (domains == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, OrganizationDomainModel> modelMap = domains.stream()
|
||||
|
||||
@@ -21,6 +21,8 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
@@ -30,6 +32,7 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -39,6 +42,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
@@ -466,6 +470,69 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
||||
assertNotNull(existing.getDomain("acme.com"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutDomains() {
|
||||
// test create organization without any domains
|
||||
OrganizationRepresentation orgWithoutDomains = new OrganizationRepresentation();
|
||||
orgWithoutDomains.setName("no-domain-org");
|
||||
orgWithoutDomains.setAlias("no-domain-org");
|
||||
orgWithoutDomains.setDescription("Organization without domains");
|
||||
|
||||
String orgWithoutDomainsId;
|
||||
try (Response response = testRealm().organizations().create(orgWithoutDomains)) {
|
||||
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
||||
orgWithoutDomainsId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
OrganizationRepresentation created = testRealm().organizations().get(orgWithoutDomainsId).toRepresentation();
|
||||
assertEquals("no-domain-org", created.getName());
|
||||
assertEquals("no-domain-org", created.getAlias());
|
||||
assertThat(created.getDomains() == null || created.getDomains().isEmpty(), is(true));
|
||||
|
||||
// verify that the organization can be retrieved
|
||||
OrganizationRepresentation orgWithDomains = createRepresentation("org-with-domains", "example.com");
|
||||
String orgWithDomainsId;
|
||||
try (Response response = testRealm().organizations().create(orgWithDomains)) {
|
||||
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
||||
orgWithDomainsId = ApiUtil.getCreatedId(response);
|
||||
}
|
||||
|
||||
try {
|
||||
List<OrganizationRepresentation> allOrgs = testRealm().organizations().list(-1, -1);
|
||||
assertThat(allOrgs.size(), greaterThanOrEqualTo(2));
|
||||
|
||||
Optional<OrganizationRepresentation> foundOrgWithDomains = allOrgs.stream()
|
||||
.filter(org -> org.getId().equals(orgWithDomainsId))
|
||||
.findFirst();
|
||||
Optional<OrganizationRepresentation> foundOrgWithoutDomains = allOrgs.stream()
|
||||
.filter(org -> org.getId().equals(orgWithoutDomainsId))
|
||||
.findFirst();
|
||||
|
||||
assertTrue("Organization with domains should be in the list", foundOrgWithDomains.isPresent());
|
||||
assertTrue("Organization without domains should be in the list", foundOrgWithoutDomains.isPresent());
|
||||
|
||||
assertThat("Organization with domains should have domains",
|
||||
foundOrgWithDomains.get().getDomains(), is(notNullValue()));
|
||||
assertThat("Organization with domains should have at least one domain",
|
||||
foundOrgWithDomains.get().getDomains().size(), greaterThan(0));
|
||||
|
||||
assertThat("Organization without domains should have no domains",
|
||||
foundOrgWithoutDomains.get().getDomains() == null ||
|
||||
foundOrgWithoutDomains.get().getDomains().isEmpty(), is(true));
|
||||
|
||||
List<OrganizationRepresentation> search = testRealm().organizations().search("with-domains", false, -1, -1);
|
||||
|
||||
assertThat(search, hasSize(1));
|
||||
|
||||
search = testRealm().organizations().search("no-domain", false, -1, -1);
|
||||
|
||||
assertThat(search, hasSize(1));
|
||||
} finally {
|
||||
testRealm().organizations().get(orgWithDomainsId).delete().close();
|
||||
testRealm().organizations().get(orgWithoutDomainsId).delete().close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFilterEmptyDomain() {
|
||||
//org should be created with only one domain
|
||||
|
||||
Reference in New Issue
Block a user