Add ability to set one or more internet domain to an organization.
Closed #28274 Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
parent
adc8d388dc
commit
9a466f90ab
15 changed files with 498 additions and 16 deletions
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2024 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.representations.idm;
|
||||
|
||||
/**
|
||||
* Representation implementation of an organization internet domain.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class OrganizationDomainRepresentation {
|
||||
|
||||
private String name;
|
||||
private Boolean verified;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean isVerified() {
|
||||
return this.verified;
|
||||
}
|
||||
|
||||
public void setVerified(Boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof OrganizationDomainRepresentation)) return false;
|
||||
|
||||
OrganizationDomainRepresentation that = (OrganizationDomainRepresentation) o;
|
||||
return name != null && name.equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (name == null) {
|
||||
return super.hashCode();
|
||||
}
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
|
@ -21,12 +21,15 @@ import java.util.Arrays;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class OrganizationRepresentation {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
private Map<String, List<String>> attributes = new HashMap<>();
|
||||
private Set<OrganizationDomainRepresentation> domains = new HashSet<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -58,6 +61,18 @@ public class OrganizationRepresentation {
|
|||
return this;
|
||||
}
|
||||
|
||||
public Set<OrganizationDomainRepresentation> getDomains() {
|
||||
return this.domains;
|
||||
}
|
||||
|
||||
public void addDomain(OrganizationDomainRepresentation domain) {
|
||||
this.domains.add(domain);
|
||||
}
|
||||
|
||||
public void removeDomain(OrganizationDomainRepresentation domain) {
|
||||
this.domains.remove(domain);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -25,6 +25,7 @@ import jakarta.ws.rs.POST;
|
|||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
|
@ -40,5 +41,7 @@ public interface OrganizationsResource {
|
|||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
List<OrganizationRepresentation> getAll();
|
||||
List<OrganizationRepresentation> getAll(
|
||||
@QueryParam("domain-name") String domainName
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2024 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.models.jpa.entities;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.NamedQueries;
|
||||
import jakarta.persistence.NamedQuery;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
/**
|
||||
* JPA entity representing an internet domain that can be associated with an organization.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
@Entity
|
||||
@Table(name="ORG_DOMAIN")
|
||||
@NamedQueries({
|
||||
@NamedQuery(name="getByName", query="select o from OrganizationDomainEntity o where o.name = :name")
|
||||
})
|
||||
public class OrganizationDomainEntity {
|
||||
|
||||
@Id
|
||||
@Column(name="NAME")
|
||||
protected String name;
|
||||
|
||||
@Column(name="VERIFIED")
|
||||
protected Boolean verified;
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "ORG_ID")
|
||||
private OrganizationEntity organization;
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean isVerified() {
|
||||
return this.verified;
|
||||
}
|
||||
|
||||
public void setVerified(Boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
public OrganizationEntity getOrganization() {
|
||||
return this.organization;
|
||||
}
|
||||
|
||||
public void setOrganization(OrganizationEntity organization) {
|
||||
this.organization = organization;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof OrganizationDomainEntity)) return false;
|
||||
|
||||
OrganizationDomainEntity that = (OrganizationDomainEntity) o;
|
||||
return name != null && name.equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (name == null) {
|
||||
return super.hashCode();
|
||||
}
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
}
|
|
@ -17,14 +17,23 @@
|
|||
|
||||
package org.keycloak.models.jpa.entities;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.Access;
|
||||
import jakarta.persistence.AccessType;
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.NamedQueries;
|
||||
import jakarta.persistence.NamedQuery;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import org.hibernate.annotations.BatchSize;
|
||||
import org.hibernate.annotations.Fetch;
|
||||
import org.hibernate.annotations.FetchMode;
|
||||
|
||||
@Table(name="ORG")
|
||||
@Entity
|
||||
|
@ -50,6 +59,11 @@ public class OrganizationEntity {
|
|||
@Column(name = "IPD_ALIAS")
|
||||
private String idpAlias;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="organization")
|
||||
@Fetch(FetchMode.SELECT)
|
||||
@BatchSize(size = 20)
|
||||
protected Set<OrganizationDomainEntity> domains = new HashSet<>();
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -90,6 +104,21 @@ public class OrganizationEntity {
|
|||
this.idpAlias = idpAlias;
|
||||
}
|
||||
|
||||
public Collection<OrganizationDomainEntity> getDomains() {
|
||||
if (this.domains == null) {
|
||||
this.domains = new HashSet<>();
|
||||
}
|
||||
return this.domains;
|
||||
}
|
||||
|
||||
public void addDomain(OrganizationDomainEntity domainEntity) {
|
||||
this.domains.add(domainEntity);
|
||||
}
|
||||
|
||||
public void removeDomain(OrganizationDomainEntity domainEntity) {
|
||||
this.domains.remove(domainEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.keycloak.utils.StreamsUtil.closing;
|
|||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.NoResultException;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import org.keycloak.connections.jpa.JpaConnectionProvider;
|
||||
import org.keycloak.models.GroupModel;
|
||||
|
@ -35,6 +36,7 @@ import org.keycloak.models.OrganizationModel;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.UserProvider;
|
||||
import org.keycloak.models.jpa.entities.OrganizationDomainEntity;
|
||||
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
|
@ -122,6 +124,18 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
return entity == null ? null : new OrganizationAdapter(realm, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganizationModel getByDomainName(String domain) {
|
||||
TypedQuery<OrganizationDomainEntity> query = em.createNamedQuery("getByName", OrganizationDomainEntity.class);
|
||||
query.setParameter("name", domain.toLowerCase());
|
||||
try {
|
||||
OrganizationDomainEntity entity = query.getSingleResult();
|
||||
return new OrganizationAdapter(realm, entity.getOrganization());
|
||||
} catch (NoResultException nre) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<OrganizationModel> getAllStream() {
|
||||
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByRealm", OrganizationEntity.class);
|
||||
|
|
|
@ -18,14 +18,20 @@
|
|||
package org.keycloak.organization.jpa;
|
||||
|
||||
import org.keycloak.models.GroupModel;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.jpa.JpaModel;
|
||||
import org.keycloak.models.jpa.entities.OrganizationDomainEntity;
|
||||
import org.keycloak.models.jpa.entities.OrganizationEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class OrganizationAdapter implements OrganizationModel, JpaModel<OrganizationEntity> {
|
||||
|
||||
|
@ -91,6 +97,37 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
|||
return getGroup().getAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<OrganizationDomainModel> getDomains() {
|
||||
return entity.getDomains().stream().map(this::toModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomains(Collection<OrganizationDomainModel> domains) {
|
||||
Map<String, OrganizationDomainModel> modelMap = domains.stream()
|
||||
.collect(Collectors.toMap(model -> model.getName(), Function.identity()));
|
||||
for (OrganizationDomainEntity domainEntity : this.entity.getDomains()) {
|
||||
// update the existing domain (for now, only the verified flag can be changed).
|
||||
if (modelMap.containsKey(domainEntity.getName())) {
|
||||
domainEntity.setVerified(modelMap.get(domainEntity.getName()).getVerified());
|
||||
modelMap.remove(domainEntity.getName());
|
||||
}
|
||||
// remove domain that is not found in the new set.
|
||||
else {
|
||||
this.entity.removeDomain(domainEntity);
|
||||
}
|
||||
}
|
||||
|
||||
// create the remaining domains.
|
||||
for (OrganizationDomainModel model : modelMap.values()) {
|
||||
OrganizationDomainEntity domainEntity = new OrganizationDomainEntity();
|
||||
domainEntity.setName(model.getName().toLowerCase());
|
||||
domainEntity.setVerified(model.getVerified() == null ? Boolean.FALSE : model.getVerified());
|
||||
domainEntity.setOrganization(this.entity);
|
||||
this.entity.addDomain(domainEntity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganizationEntity getEntity() {
|
||||
return entity;
|
||||
|
@ -118,4 +155,8 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
|
|||
.append("groupId=")
|
||||
.append(getGroupId()).toString();
|
||||
}
|
||||
|
||||
private OrganizationDomainModel toModel(OrganizationDomainEntity entity) {
|
||||
return new OrganizationDomainModel(entity.getName(), entity.isVerified());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
<changeSet author="keycloak" id="25.0.0-org">
|
||||
<createTable tableName="ORG">
|
||||
<column name="ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="REALM_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
|
@ -90,10 +90,20 @@
|
|||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey columnNames="ID" tableName="ORG"/>
|
||||
<addUniqueConstraint tableName="ORG" columnNames="REALM_ID, NAME" constraintName="UK_ORG_NAME"/>
|
||||
<addUniqueConstraint tableName="ORG" columnNames="GROUP_ID" constraintName="UK_ORG_GROUP"/>
|
||||
|
||||
<createTable tableName="ORG_DOMAIN">
|
||||
<column name="NAME" type="VARCHAR(255)">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="VERIFIED" type="boolean">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="ORG_ID" type="VARCHAR(255)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<changeSet author="keycloak" id="unique-consentuser">
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
|
||||
<!-- Organization -->
|
||||
<class>org.keycloak.models.jpa.entities.OrganizationEntity</class>
|
||||
<class>org.keycloak.models.jpa.entities.OrganizationDomainEntity</class>
|
||||
|
||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||
|
||||
|
|
|
@ -45,6 +45,20 @@ public interface OrganizationProvider extends Provider {
|
|||
*/
|
||||
OrganizationModel getById(String id);
|
||||
|
||||
/**
|
||||
* Returns a {@link OrganizationModel} by its internet domain.
|
||||
*
|
||||
* @param domainName the organization's internet domain (e.g. redhat.com)
|
||||
* @return the organization that is linked to the given internet domain
|
||||
*/
|
||||
OrganizationModel getByDomainName(String domainName);
|
||||
|
||||
/**
|
||||
* Returns the organizations of the given realm as a stream.
|
||||
* @return Stream of the organizations. Never returns {@code null}.
|
||||
*/
|
||||
Stream<OrganizationModel> getAllStream();
|
||||
|
||||
/**
|
||||
* Removes the given organization from the realm together with the data associated with it, e.g. its members etc.
|
||||
*
|
||||
|
@ -69,12 +83,6 @@ public interface OrganizationProvider extends Provider {
|
|||
*/
|
||||
boolean addMember(OrganizationModel organization, UserModel user);
|
||||
|
||||
/**
|
||||
* Returns the organizations of the realm as a stream.
|
||||
* @return Stream of the organizations. Never returns {@code null}.
|
||||
*/
|
||||
Stream<OrganizationModel> getAllStream();
|
||||
|
||||
/**
|
||||
* Returns the members of a given {@link OrganizationModel}.
|
||||
*
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright 2024 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.models;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Model implementation of an organization internet domain.
|
||||
*
|
||||
* @author <a href="mailto:sguilhen@redhat.com">Stefan Guilhen</a>
|
||||
*/
|
||||
public class OrganizationDomainModel implements Serializable {
|
||||
|
||||
private String name;
|
||||
private Boolean verified;
|
||||
|
||||
public OrganizationDomainModel(String name, Boolean verified) {
|
||||
this.name = name;
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Boolean getVerified() {
|
||||
return this.verified;
|
||||
}
|
||||
|
||||
public void setVerified(Boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null) return false;
|
||||
if (!(o instanceof OrganizationDomainModel)) return false;
|
||||
|
||||
OrganizationDomainModel that = (OrganizationDomainModel) o;
|
||||
return name != null && name.equals(that.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
if (name == null) {
|
||||
return super.hashCode();
|
||||
}
|
||||
return name.hashCode();
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ package org.keycloak.models;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public interface OrganizationModel {
|
||||
|
@ -42,4 +43,8 @@ public interface OrganizationModel {
|
|||
Stream<String> getAttributeStream(String name);
|
||||
|
||||
Map<String, List<String>> getAttributes();
|
||||
|
||||
Stream<OrganizationDomainModel> getDomains();
|
||||
|
||||
void setDomains(Collection<OrganizationDomainModel> domains);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.organization.admin.resource;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import jakarta.ws.rs.BadRequestException;
|
||||
|
@ -30,13 +32,18 @@ import jakarta.ws.rs.PUT;
|
|||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.Provider;
|
||||
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.services.ErrorResponse;
|
||||
import org.keycloak.services.resources.admin.AdminEventBuilder;
|
||||
import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
@ -69,14 +76,23 @@ public class OrganizationResource {
|
|||
}
|
||||
|
||||
OrganizationModel model = provider.create(organization.getName());
|
||||
toModel(organization, model);
|
||||
|
||||
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Stream<OrganizationRepresentation> get() {
|
||||
return provider.getAllStream().map(this::toRepresentation);
|
||||
public Stream<OrganizationRepresentation> search(
|
||||
@Parameter(description = "A String representing an organization internet domain") @QueryParam("domain-name") String domainName
|
||||
) {
|
||||
if (domainName == null || domainName.trim().isEmpty()) {
|
||||
return provider.getAllStream().map(this::toRepresentation);
|
||||
} else {
|
||||
// search for the organization associated with the given domain
|
||||
OrganizationModel org = provider.getByDomainName(domainName.trim());
|
||||
return org == null ? Stream.empty() : Stream.of(toRepresentation(org));
|
||||
}
|
||||
}
|
||||
|
||||
@Path("{id}")
|
||||
|
@ -107,7 +123,6 @@ public class OrganizationResource {
|
|||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response update(@PathParam("id") String id, OrganizationRepresentation organization) {
|
||||
OrganizationModel model = getOrganization(id);
|
||||
|
||||
toModel(organization, model);
|
||||
|
||||
return Response.noContent().build();
|
||||
|
@ -148,10 +163,19 @@ public class OrganizationResource {
|
|||
rep.setId(model.getId());
|
||||
rep.setName(model.getName());
|
||||
rep.setAttributes(model.getAttributes());
|
||||
model.getDomains().filter(Objects::nonNull).map(this::toRepresentation)
|
||||
.forEach(rep::addDomain);
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
private OrganizationDomainRepresentation toRepresentation(OrganizationDomainModel model) {
|
||||
OrganizationDomainRepresentation representation = new OrganizationDomainRepresentation();
|
||||
representation.setName(model.getName());
|
||||
representation.setVerified(model.getVerified());
|
||||
return representation;
|
||||
}
|
||||
|
||||
private OrganizationModel toModel(OrganizationRepresentation rep, OrganizationModel model) {
|
||||
if (rep == null) {
|
||||
return null;
|
||||
|
@ -167,6 +191,25 @@ public class OrganizationResource {
|
|||
rep.getAttributes().entrySet().forEach(entry -> model.setAttribute(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
if (rep.getDomains() != null) {
|
||||
model.setDomains(rep.getDomains().stream().filter(this::validateDomainRepresentation)
|
||||
.peek(domainRep -> {
|
||||
OrganizationModel orgModel = provider.getByDomainName(domainRep.getName());
|
||||
if (orgModel != null && !Objects.equals(model.getId(), orgModel.getId())) {
|
||||
throw ErrorResponse.error("Domain " + domainRep.getName() + " is already linked to another organization", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
})
|
||||
.map(this::toModel)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
private OrganizationDomainModel toModel(OrganizationDomainRepresentation domainRepresentation) {
|
||||
return new OrganizationDomainModel(domainRepresentation.getName(), domainRepresentation.isVerified());
|
||||
}
|
||||
|
||||
private boolean validateDomainRepresentation(OrganizationDomainRepresentation rep) {
|
||||
return rep != null && rep.getName() != null && !rep.getName().trim().isEmpty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import static org.junit.Assert.assertNotNull;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.core.Response.Status;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.admin.AbstractAdminTest;
|
||||
|
@ -43,12 +44,23 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
|
|||
}
|
||||
|
||||
protected OrganizationRepresentation createOrganization(String name) {
|
||||
return createOrganization(name, null);
|
||||
}
|
||||
|
||||
protected OrganizationRepresentation createOrganization(String name, String orgDomain) {
|
||||
OrganizationRepresentation org = new OrganizationRepresentation();
|
||||
|
||||
org.setName(name);
|
||||
|
||||
String id;
|
||||
|
||||
if (orgDomain != null) {
|
||||
OrganizationDomainRepresentation domainRep = new OrganizationDomainRepresentation();
|
||||
domainRep.setName(orgDomain);
|
||||
domainRep.setVerified(true);
|
||||
org.addDomain(domainRep);
|
||||
}
|
||||
|
||||
try (Response response = testRealm().organizations().create(org)) {
|
||||
assertEquals(Status.CREATED.getStatusCode(), response.getStatus());
|
||||
id = ApiUtil.getCreatedId(response);
|
||||
|
|
|
@ -21,6 +21,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;
|
||||
|
@ -35,6 +36,7 @@ import org.hamcrest.Matchers;
|
|||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
|
||||
|
@ -48,6 +50,12 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
|||
assertEquals(organizationName, expected.getName());
|
||||
expected.setName("acme");
|
||||
|
||||
// add an internet domain to the organization.
|
||||
OrganizationDomainRepresentation orgDomain = new OrganizationDomainRepresentation();
|
||||
orgDomain.setName("neworg.org");
|
||||
orgDomain.setVerified(true);
|
||||
expected.addDomain(orgDomain);
|
||||
|
||||
OrganizationResource organization = testRealm().organizations().get(expected.getId());
|
||||
|
||||
try (Response response = organization.update(expected)) {
|
||||
|
@ -57,6 +65,49 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
|||
OrganizationRepresentation existing = organization.toRepresentation();
|
||||
assertEquals(expected.getId(), existing.getId());
|
||||
assertEquals(expected.getName(), existing.getName());
|
||||
assertEquals(1, existing.getDomains().size());
|
||||
|
||||
OrganizationDomainRepresentation existingDomain = existing.getDomains().iterator().next();
|
||||
assertEquals(orgDomain.getName(), existingDomain.getName());
|
||||
assertEquals(orgDomain.isVerified(), existingDomain.isVerified());
|
||||
|
||||
// now test updating an existing internet domain (change verified to false and check the model was updated).
|
||||
orgDomain.setVerified(false);
|
||||
try (Response response = organization.update(expected)) {
|
||||
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
||||
}
|
||||
existing = organization.toRepresentation();
|
||||
assertEquals(1, existing.getDomains().size());
|
||||
existingDomain = existing.getDomains().iterator().next();
|
||||
assertEquals(false, existingDomain.isVerified());
|
||||
|
||||
// now replace the internet domain for a different one.
|
||||
orgDomain.setName("acme.com");
|
||||
try (Response response = organization.update(expected)) {
|
||||
assertEquals(Status.NO_CONTENT.getStatusCode(), response.getStatus());
|
||||
}
|
||||
existing = organization.toRepresentation();
|
||||
assertEquals(1, existing.getDomains().size());
|
||||
existingDomain = existing.getDomains().iterator().next();
|
||||
assertEquals("acme.com", existingDomain.getName());
|
||||
assertEquals(false, existingDomain.isVerified());
|
||||
|
||||
// create another org and attempt to set the same internet domain during update - should not be possible.
|
||||
OrganizationRepresentation anotherOrg = createOrganization("another-org");
|
||||
anotherOrg.addDomain(orgDomain);
|
||||
|
||||
organization = testRealm().organizations().get(anotherOrg.getId());
|
||||
try (Response response = organization.update(anotherOrg)) {
|
||||
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
}
|
||||
|
||||
// finally, attempt to create a new org with an existing internet domain in the representation - should not be possible.
|
||||
OrganizationRepresentation newOrg = new OrganizationRepresentation();
|
||||
newOrg.setName("new-org");
|
||||
newOrg.addDomain(orgDomain);
|
||||
try (Response response = testRealm().organizations().create(newOrg)) {
|
||||
assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -76,11 +127,33 @@ public class OrganizationTest extends AbstractOrganizationTest {
|
|||
expected.add(createOrganization("kc.org." + i));
|
||||
}
|
||||
|
||||
List<OrganizationRepresentation> existing = testRealm().organizations().getAll();
|
||||
List<OrganizationRepresentation> existing = testRealm().organizations().getAll(null);
|
||||
assertFalse(existing.isEmpty());
|
||||
MatcherAssert.assertThat(expected, Matchers.containsInAnyOrder(existing.toArray()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetByDomain() {
|
||||
// create some organizations with a domain already set.
|
||||
for (int i = 0; i < 5; i++) {
|
||||
createOrganization("test-org-" + i, "testorg" + i + ".org");
|
||||
}
|
||||
|
||||
// search for an organization with an existing domain.
|
||||
List<OrganizationRepresentation> existing = testRealm().organizations().getAll("testorg2.org");
|
||||
assertEquals(1, existing.size());
|
||||
OrganizationRepresentation orgRep = existing.get(0);
|
||||
assertEquals("test-org-2", orgRep.getName());
|
||||
assertEquals(1, orgRep.getDomains().size());
|
||||
OrganizationDomainRepresentation domainRep = orgRep.getDomains().iterator().next();
|
||||
assertEquals("testorg2.org", domainRep.getName());
|
||||
assertTrue(domainRep.isVerified());
|
||||
|
||||
// search for an organization with an non-existent domain.
|
||||
existing = testRealm().organizations().getAll("someother.org");
|
||||
assertEquals(0, existing.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDelete() {
|
||||
OrganizationRepresentation expected = createOrganization();
|
||||
|
|
Loading…
Reference in a new issue