Ensure organization id is preserved on export/import

- Also fixes issues with description, enabled, and custom attributes missing when re-importing the orgs.

Closes #33207

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen 2024-09-24 11:25:54 -03:00 committed by Alexander Schwartz
parent c054a086cf
commit 6424708695
14 changed files with 136 additions and 133 deletions

View file

@ -57,9 +57,9 @@ public class InfinispanOrganizationProvider implements OrganizationProvider {
} }
@Override @Override
public OrganizationModel create(String name, String alias) { public OrganizationModel create(String id, String name, String alias) {
registerCountInvalidation(); registerCountInvalidation();
return orgDelegate.create(name, alias); return orgDelegate.create(id, name, alias);
} }
@Override @Override

View file

@ -55,6 +55,7 @@ import org.keycloak.models.jpa.entities.GroupEntity;
import org.keycloak.models.jpa.entities.OrganizationEntity; import org.keycloak.models.jpa.entities.OrganizationEntity;
import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserGroupMembershipEntity; import org.keycloak.models.jpa.entities.UserGroupMembershipEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.MembershipType; import org.keycloak.representations.idm.MembershipType;
import org.keycloak.organization.utils.Organizations; import org.keycloak.organization.utils.Organizations;
@ -76,7 +77,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
} }
@Override @Override
public OrganizationModel create(String name, String alias) { public OrganizationModel create(String id, String name, String alias) {
if (StringUtil.isBlank(name)) { if (StringUtil.isBlank(name)) {
throw new ModelValidationException("Name can not be null"); throw new ModelValidationException("Name can not be null");
} }
@ -98,8 +99,10 @@ public class JpaOrganizationProvider implements OrganizationProvider {
throw new ModelDuplicateException("A organization with the same alias already exists"); throw new ModelDuplicateException("A organization with the same alias already exists");
} }
RealmModel realm = getRealm(); OrganizationEntity entity = new OrganizationEntity();
OrganizationAdapter adapter = new OrganizationAdapter(session, realm, this); entity.setId(id != null ? id : KeycloakModelUtils.generateId());
entity.setRealmId(getRealm().getId());
OrganizationAdapter adapter = new OrganizationAdapter(session, getRealm(), entity, this);
try { try {
session.getContext().setOrganization(adapter); session.getContext().setOrganization(adapter);

View file

@ -53,15 +53,6 @@ public final class OrganizationAdapter implements OrganizationModel, JpaModel<Or
private GroupModel group; private GroupModel group;
private Map<String, List<String>> attributes; private Map<String, List<String>> attributes;
public OrganizationAdapter(KeycloakSession session, RealmModel realm, OrganizationProvider provider) {
this.session = session;
entity = new OrganizationEntity();
entity.setId(KeycloakModelUtils.generateId());
entity.setRealmId(realm.getId());
this.realm = realm;
this.provider = provider;
}
public OrganizationAdapter(KeycloakSession session, RealmModel realm, OrganizationEntity entity, OrganizationProvider provider) { public OrganizationAdapter(KeycloakSession session, RealmModel realm, OrganizationEntity entity, OrganizationProvider provider) {
this.session = session; this.session = session;
this.realm = realm; this.realm = realm;

View file

@ -130,7 +130,7 @@ public class ExportUtils {
List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles); List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles);
clientRolesReps.put(client.getClientId(), currentAppRoleReps); clientRolesReps.put(client.getClientId(), currentAppRoleReps);
} }
if (clientRolesReps.size() > 0) { if (!clientRolesReps.isEmpty()) {
rolesRep.setClient(clientRolesReps); rolesRep.setClient(clientRolesReps);
} }
} }
@ -156,11 +156,7 @@ public class ExportUtils {
} else { } else {
ClientModel app = (ClientModel) scope.getContainer(); ClientModel app = (ClientModel) scope.getContainer();
String appName = app.getClientId(); String appName = app.getClientId();
List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName); List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.computeIfAbsent(appName, k -> new ArrayList<>());
if (currentAppScopes == null) {
currentAppScopes = new ArrayList<>();
clientScopeReps.put(appName, currentAppScopes);
}
ScopeMappingRepresentation currentClientScope = null; ScopeMappingRepresentation currentClientScope = null;
for (ScopeMappingRepresentation scopeMapping : currentAppScopes) { for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
@ -193,11 +189,7 @@ public class ExportUtils {
} else { } else {
ClientModel app = (ClientModel)scope.getContainer(); ClientModel app = (ClientModel)scope.getContainer();
String appName = app.getClientId(); String appName = app.getClientId();
List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName); List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.computeIfAbsent(appName, k -> new ArrayList<>());
if (currentAppScopes == null) {
currentAppScopes = new ArrayList<>();
clientScopeReps.put(appName, currentAppScopes);
}
ScopeMappingRepresentation currentClientTemplateScope = null; ScopeMappingRepresentation currentClientTemplateScope = null;
for (ScopeMappingRepresentation scopeMapping : currentAppScopes) { for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
@ -216,7 +208,7 @@ public class ExportUtils {
} }
}); });
if (clientScopeReps.size() > 0) { if (!clientScopeReps.isEmpty()) {
rep.setClientScopeMappings(clientScopeReps); rep.setClientScopeMappings(clientScopeReps);
} }
@ -226,7 +218,7 @@ public class ExportUtils {
.map(user -> exportUser(session, realm, user, options, internal)) .map(user -> exportUser(session, realm, user, options, internal))
.collect(Collectors.toList()); .collect(Collectors.toList());
if (users.size() > 0) { if (!users.isEmpty()) {
rep.setUsers(users); rep.setUsers(users);
} }
@ -234,7 +226,7 @@ public class ExportUtils {
if (userFederatedStorageProvider != null) { if (userFederatedStorageProvider != null) {
List<UserRepresentation> federatedUsers = userFederatedStorage(session).getStoredUsersStream(realm, 0, -1) List<UserRepresentation> federatedUsers = userFederatedStorage(session).getStoredUsersStream(realm, 0, -1)
.map(user -> exportFederatedUser(session, realm, user, options)).collect(Collectors.toList()); .map(user -> exportFederatedUser(session, realm, user, options)).collect(Collectors.toList());
if (federatedUsers.size() > 0) { if (!federatedUsers.isEmpty()) {
rep.setFederatedUsers(federatedUsers); rep.setFederatedUsers(federatedUsers);
} }
} }
@ -251,7 +243,7 @@ public class ExportUtils {
} }
} }
if (users.size() > 0) { if (!users.isEmpty()) {
rep.setUsers(users); rep.setUsers(users);
} }
} }
@ -265,32 +257,19 @@ public class ExportUtils {
if (Profile.isFeatureEnabled(Feature.ORGANIZATION) && !options.isPartial()) { if (Profile.isFeatureEnabled(Feature.ORGANIZATION) && !options.isPartial()) {
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class); OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);
orgProvider.getAllStream().map(m -> { orgProvider.getAllStream().map(model -> {
OrganizationRepresentation org = new OrganizationRepresentation(); OrganizationRepresentation org = ModelToRepresentation.toRepresentation(model);
org.setName(m.getName()); orgProvider.getMembersStream(model, null, null, null, null)
org.setAlias(m.getAlias());
org.setEnabled(m.isEnabled());
org.setDescription(m.getDescription());
m.getDomains().map(d -> {
OrganizationDomainRepresentation domain = new OrganizationDomainRepresentation();
domain.setName(d.getName());
domain.setVerified(d.isVerified());
return domain;
}).forEach(org::addDomain);
orgProvider.getMembersStream(m, null, null, null, null)
.forEach(user -> { .forEach(user -> {
MemberRepresentation member = new MemberRepresentation(); MemberRepresentation member = new MemberRepresentation();
member.setUsername(user.getUsername()); member.setUsername(user.getUsername());
member.setMembershipType(orgProvider.isManagedMember(m, user) ? MembershipType.MANAGED : MembershipType.UNMANAGED); member.setMembershipType(orgProvider.isManagedMember(model, user) ? MembershipType.MANAGED : MembershipType.UNMANAGED);
org.addMember(member); org.addMember(member);
}); });
orgProvider.getIdentityProviders(m) orgProvider.getIdentityProviders(model)
.map(b -> { .map(b -> {
IdentityProviderRepresentation broker = new IdentityProviderRepresentation(); IdentityProviderRepresentation broker = new IdentityProviderRepresentation();
broker.setAlias(b.getAlias()); broker.setAlias(b.getAlias());

View file

@ -48,7 +48,6 @@ import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelException; import org.keycloak.models.ModelException;
import org.keycloak.models.OAuth2DeviceConfig; import org.keycloak.models.OAuth2DeviceConfig;
import org.keycloak.models.OTPPolicy; import org.keycloak.models.OTPPolicy;
import org.keycloak.models.OrganizationDomainModel;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.ParConfig; import org.keycloak.models.ParConfig;
import org.keycloak.models.PasswordPolicy; import org.keycloak.models.PasswordPolicy;
@ -1590,21 +1589,20 @@ public class DefaultExportImportManager implements ExportImportManager {
OrganizationProvider provider = session.getProvider(OrganizationProvider.class); OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
for (OrganizationRepresentation orgRep : Optional.ofNullable(rep.getOrganizations()).orElse(Collections.emptyList())) { for (OrganizationRepresentation orgRep : Optional.ofNullable(rep.getOrganizations()).orElse(Collections.emptyList())) {
OrganizationModel org = provider.create(orgRep.getName(), orgRep.getAlias()); OrganizationModel orgModel = provider.create(orgRep.getId(), orgRep.getName(), orgRep.getAlias());
RepresentationToModel.toModel(orgRep, orgModel);
org.setDomains(orgRep.getDomains().stream().map(r -> new OrganizationDomainModel(r.getName(), r.isVerified())).collect(Collectors.toSet()));
for (IdentityProviderRepresentation identityProvider : Optional.ofNullable(orgRep.getIdentityProviders()).orElse(Collections.emptyList())) { for (IdentityProviderRepresentation identityProvider : Optional.ofNullable(orgRep.getIdentityProviders()).orElse(Collections.emptyList())) {
IdentityProviderModel idp = session.identityProviders().getByAlias(identityProvider.getAlias()); IdentityProviderModel idp = session.identityProviders().getByAlias(identityProvider.getAlias());
provider.addIdentityProvider(org, idp); provider.addIdentityProvider(orgModel, idp);
} }
for (MemberRepresentation member : Optional.ofNullable(orgRep.getMembers()).orElse(Collections.emptyList())) { for (MemberRepresentation member : Optional.ofNullable(orgRep.getMembers()).orElse(Collections.emptyList())) {
UserModel m = session.users().getUserByUsername(newRealm, member.getUsername()); UserModel m = session.users().getUserByUsername(newRealm, member.getUsername());
if (MembershipType.MANAGED.equals(member.getMembershipType())) { if (MembershipType.MANAGED.equals(member.getMembershipType())) {
provider.addManagedMember(org, m); provider.addManagedMember(orgModel, m);
} else { } else {
provider.addMember(org, m); provider.addMember(orgModel, m);
} }
} }
} }

View file

@ -1291,4 +1291,35 @@ public class ModelToRepresentation {
rep.setConfig(model.getConfig()); rep.setConfig(model.getConfig());
return rep; return rep;
} }
public static OrganizationRepresentation toRepresentation(OrganizationModel model) {
OrganizationRepresentation rep = toBriefRepresentation(model);
if (rep == null) {
return null;
}
rep.setAttributes(model.getAttributes());
return rep;
}
public static OrganizationRepresentation toBriefRepresentation(OrganizationModel model) {
if (model == null) {
return null;
}
OrganizationRepresentation rep = new OrganizationRepresentation();
rep.setId(model.getId());
rep.setName(model.getName());
rep.setAlias(model.getAlias());
rep.setEnabled(model.isEnabled());
rep.setDescription(model.getDescription());
model.getDomains().filter(Objects::nonNull).map(ModelToRepresentation::toRepresentation)
.forEach(rep::addDomain);
return rep;
}
public static OrganizationDomainRepresentation toRepresentation(OrganizationDomainModel model) {
OrganizationDomainRepresentation representation = new OrganizationDomainRepresentation();
representation.setName(model.getName());
representation.setVerified(model.isVerified());
return representation;
}
} }

View file

@ -108,6 +108,8 @@ import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.RoleRepresentation;
@ -127,6 +129,7 @@ import org.keycloak.storage.DatastoreProvider;
import org.keycloak.util.JsonSerialization; import org.keycloak.util.JsonSerialization;
import org.keycloak.utils.StringUtil; import org.keycloak.utils.StringUtil;
import static java.util.Optional.ofNullable;
import static org.keycloak.protocol.saml.util.ArtifactBindingUtils.computeArtifactBindingIdentifierString; import static org.keycloak.protocol.saml.util.ArtifactBindingUtils.computeArtifactBindingIdentifierString;
public class RepresentationToModel { public class RepresentationToModel {
@ -1674,4 +1677,27 @@ public class RepresentationToModel {
representation.setOrganizationId(orgId); representation.setOrganizationId(orgId);
} }
} }
public static OrganizationModel toModel(OrganizationRepresentation rep, OrganizationModel model) {
if (rep == null) {
return null;
}
model.setName(rep.getName());
model.setAlias(rep.getAlias());
model.setEnabled(rep.isEnabled());
model.setDescription(rep.getDescription());
model.setAttributes(rep.getAttributes());
model.setDomains(ofNullable(rep.getDomains()).orElse(Set.of()).stream()
.filter(Objects::nonNull)
.filter(domain -> StringUtil.isNotBlank(domain.getName()))
.map(RepresentationToModel::toModel)
.collect(Collectors.toSet()));
return model;
}
public static OrganizationDomainModel toModel(OrganizationDomainRepresentation domainRepresentation) {
return new OrganizationDomainModel(domainRepresentation.getName(), domainRepresentation.isVerified());
}
} }

View file

@ -31,14 +31,26 @@ import org.keycloak.provider.Provider;
public interface OrganizationProvider extends Provider { public interface OrganizationProvider extends Provider {
/** /**
* Creates a new organization with given {@code name} to the realm. * Creates a new organization with given {@code name} and {@code alias} to the realm.
* The internal ID of the organization will be created automatically. * The internal ID of the organization will be created automatically.
* @param name String name of the organization. * @param name the name of the organization.
* @param alias the alias of the organization. If not set, defaults to the value set to {@code name}. Once set, the alias is immutable. * @param alias the alias of the organization. If not set, defaults to the value set to {@code name}. Once set, the alias is immutable.
* @throws ModelDuplicateException If there is already an organization with the given name or alias * @throws ModelDuplicateException If there is already an organization with the given name or alias
* @return Model of the created organization. * @return Model of the created organization.
*/ */
OrganizationModel create(String name, String alias); default OrganizationModel create(String name, String alias) {
return create(null, name, alias);
}
/**
* Creates a new organization with given {@code id}, {@code name}, and {@code alias} to the realm
* @param id the id of the organization.
* @param name the name of the organization.
* @param alias the alias of the organization. If not set, defaults to the value set to {@code name}. Once set, the alias is immutable.
* @throws ModelDuplicateException If there is already an organization with the given name or alias
* @return Model of the created organization.
*/
OrganizationModel create(String id, String name, String alias);
/** /**
* Returns a {@link OrganizationModel} by its {@code id}; * Returns a {@link OrganizationModel} by its {@code id};

View file

@ -17,7 +17,6 @@
package org.keycloak.organization.admin.resource; package org.keycloak.organization.admin.resource;
import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
@ -49,11 +48,9 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.organization.utils.Organizations;
import org.keycloak.representations.idm.MemberRepresentation; import org.keycloak.representations.idm.MemberRepresentation;
import org.keycloak.representations.idm.MembershipType; import org.keycloak.representations.idm.MembershipType;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.KeycloakOpenAPI; import org.keycloak.services.resources.KeycloakOpenAPI;
import org.keycloak.services.resources.admin.AdminEventBuilder; import org.keycloak.services.resources.admin.AdminEventBuilder;
@ -193,7 +190,7 @@ public class OrganizationMemberResource {
UserModel member = getUser(id); UserModel member = getUser(id);
return provider.getByMember(member).map(Organizations::toRepresentation); return provider.getByMember(member).map(ModelToRepresentation::toRepresentation);
} }
@Path("count") @Path("count")

View file

@ -33,8 +33,9 @@ import org.jboss.resteasy.reactive.NoCache;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.organization.utils.Organizations;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
import org.keycloak.services.ErrorResponse; import org.keycloak.services.ErrorResponse;
import org.keycloak.services.resources.KeycloakOpenAPI; import org.keycloak.services.resources.KeycloakOpenAPI;
@ -67,7 +68,7 @@ public class OrganizationResource {
@Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS) @Tag(name = KeycloakOpenAPI.Admin.Tags.ORGANIZATIONS)
@Operation(summary = "Returns the organization representation") @Operation(summary = "Returns the organization representation")
public OrganizationRepresentation get() { public OrganizationRepresentation get() {
return Organizations.toRepresentation(organization); return ModelToRepresentation.toRepresentation(organization);
} }
@DELETE @DELETE
@ -84,7 +85,7 @@ public class OrganizationResource {
@Operation(summary = "Updates the organization") @Operation(summary = "Updates the organization")
public Response update(OrganizationRepresentation organizationRep) { public Response update(OrganizationRepresentation organizationRep) {
try { try {
Organizations.toModel(organizationRep, organization); RepresentationToModel.toModel(organizationRep, organization);
return Response.noContent().build(); return Response.noContent().build();
} catch (ModelValidationException mve) { } catch (ModelValidationException mve) {
throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST); throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST);

View file

@ -42,6 +42,8 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelValidationException; import org.keycloak.models.ModelValidationException;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.organization.OrganizationProvider; import org.keycloak.organization.OrganizationProvider;
import org.keycloak.organization.utils.Organizations; import org.keycloak.organization.utils.Organizations;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
@ -96,9 +98,7 @@ public class OrganizationsResource {
try { try {
OrganizationModel model = provider.create(organization.getName(), organization.getAlias()); OrganizationModel model = provider.create(organization.getName(), organization.getAlias());
RepresentationToModel.toModel(organization, model);
Organizations.toModel(organization, model);
return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build(); return Response.created(session.getContext().getUri().getAbsolutePathBuilder().path(model.getId()).build()).build();
} catch (ModelValidationException mve) { } catch (ModelValidationException mve) {
throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST); throw ErrorResponse.error(mve.getMessage(), Response.Status.BAD_REQUEST);
@ -137,9 +137,9 @@ public class OrganizationsResource {
// check if are searching orgs by attribute. // check if are searching orgs by attribute.
if (StringUtil.isNotBlank(searchQuery)) { if (StringUtil.isNotBlank(searchQuery)) {
Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery); Map<String, String> attributes = SearchQueryUtils.getFields(searchQuery);
return provider.getAllStream(attributes, first, max).map(Organizations::toBriefRepresentation); return provider.getAllStream(attributes, first, max).map(ModelToRepresentation::toBriefRepresentation);
} else { } else {
return provider.getAllStream(search, exact, first, max).map(Organizations::toBriefRepresentation); return provider.getAllStream(search, exact, first, max).map(ModelToRepresentation::toBriefRepresentation);
} }
} }

View file

@ -151,66 +151,6 @@ public class Organizations {
} }
} }
public static OrganizationRepresentation toRepresentation(OrganizationModel model) {
OrganizationRepresentation rep = toBriefRepresentation(model);
if (rep == null) {
return null;
}
rep.setAttributes(model.getAttributes());
return rep;
}
public static OrganizationRepresentation toBriefRepresentation(OrganizationModel model) {
if (model == null) {
return null;
}
OrganizationRepresentation rep = new OrganizationRepresentation();
rep.setId(model.getId());
rep.setName(model.getName());
rep.setAlias(model.getAlias());
rep.setEnabled(model.isEnabled());
rep.setDescription(model.getDescription());
model.getDomains().filter(Objects::nonNull).map(Organizations::toRepresentation)
.forEach(rep::addDomain);
return rep;
}
public static OrganizationDomainRepresentation toRepresentation(OrganizationDomainModel model) {
OrganizationDomainRepresentation representation = new OrganizationDomainRepresentation();
representation.setName(model.getName());
representation.setVerified(model.isVerified());
return representation;
}
public static OrganizationModel toModel(OrganizationRepresentation rep, OrganizationModel model) {
if (rep == null) {
return null;
}
model.setName(rep.getName());
model.setAlias(rep.getAlias());
model.setEnabled(rep.isEnabled());
model.setDescription(rep.getDescription());
model.setAttributes(rep.getAttributes());
model.setDomains(ofNullable(rep.getDomains()).orElse(Set.of()).stream()
.filter(Objects::nonNull)
.filter(domain -> StringUtil.isNotBlank(domain.getName()))
.map(Organizations::toModel)
.collect(Collectors.toSet()));
return model;
}
public static OrganizationDomainModel toModel(OrganizationDomainRepresentation domainRepresentation) {
return new OrganizationDomainModel(domainRepresentation.getName(), domainRepresentation.isVerified());
}
public static InviteOrgActionToken parseInvitationToken(HttpRequest request) throws VerificationException { public static InviteOrgActionToken parseInvitationToken(HttpRequest request) throws VerificationException {
MultivaluedMap<String, String> queryParameters = request.getUri().getQueryParameters(); MultivaluedMap<String, String> queryParameters = request.getUri().getQueryParameters();
String tokenFromQuery = queryParameters.getFirst(Constants.TOKEN); String tokenFromQuery = queryParameters.getFirst(Constants.TOKEN);

View file

@ -152,6 +152,7 @@ public abstract class AbstractOrganizationTest extends AbstractAdminTest {
OrganizationRepresentation org = new OrganizationRepresentation(); OrganizationRepresentation org = new OrganizationRepresentation();
org.setName(name); org.setName(name);
org.setAlias(name); org.setAlias(name);
org.setDescription(name + " is a test organization!");
for (String orgDomain : orgDomains) { for (String orgDomain : orgDomains) {
OrganizationDomainRepresentation domainRep = new OrganizationDomainRepresentation(); OrganizationDomainRepresentation domainRep = new OrganizationDomainRepresentation();

View file

@ -17,8 +17,12 @@
package org.keycloak.testsuite.organization.exportimport; package org.keycloak.testsuite.organization.exportimport;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -42,6 +46,7 @@ import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory; import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
import org.keycloak.models.OrganizationModel; import org.keycloak.models.OrganizationModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.models.utils.DefaultAuthenticationFlows;
import org.keycloak.organization.OrganizationProvider;
import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionInfoRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.OrganizationRepresentation; import org.keycloak.representations.idm.OrganizationRepresentation;
@ -58,7 +63,7 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
@Test @Test
public void testExport() { public void testExport() {
RealmResource providerRealm = realmsResouce().realm(bc.providerRealmName()); RealmResource providerRealm = realmsResouce().realm(bc.providerRealmName());
List<String> expectedOrganizations = new ArrayList<>(); List<OrganizationRepresentation> expectedOrganizations = new ArrayList<>();
Map<String, List<String>> expectedManagedMembers = new HashMap<>(); Map<String, List<String>> expectedManagedMembers = new HashMap<>();
Map<String, List<String>> expectedUnmanagedMembers = new HashMap<>(); Map<String, List<String>> expectedUnmanagedMembers = new HashMap<>();
@ -70,7 +75,7 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
OrganizationRepresentation orgRep = createOrganization(testRealm(), getCleanup(), "org-" + i, broker, domain); OrganizationRepresentation orgRep = createOrganization(testRealm(), getCleanup(), "org-" + i, broker, domain);
OrganizationResource organization = testRealm().organizations().get(orgRep.getId()); OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
expectedOrganizations.add(orgRep.getName()); expectedOrganizations.add(orgRep);
for (int j = 0; j < 3; j++) { for (int j = 0; j < 3; j++) {
UserRepresentation member = addMember(organization, "realmuser-" + j + "@" + domain); UserRepresentation member = addMember(organization, "realmuser-" + j + "@" + domain);
@ -109,8 +114,27 @@ public class OrganizationExportTest extends AbstractOrganizationTest {
List<OrganizationRepresentation> organizations = testRealm().organizations().getAll(); List<OrganizationRepresentation> organizations = testRealm().organizations().getAll();
assertEquals(expectedOrganizations.size(), organizations.size()); assertEquals(expectedOrganizations.size(), organizations.size());
assertThat(organizations.stream().map(OrganizationRepresentation::getName).toList(), Matchers.containsInAnyOrder(expectedOrganizations.toArray())); // id, name, alias, and description should have all been preserved.
assertThat(organizations.stream().map(OrganizationRepresentation::getAlias).toList(), Matchers.containsInAnyOrder(expectedOrganizations.toArray())); assertThat(organizations.stream().map(OrganizationRepresentation::getId).toList(),
Matchers.containsInAnyOrder(expectedOrganizations.stream().map(OrganizationRepresentation::getId).toArray()));
assertThat(organizations.stream().map(OrganizationRepresentation::getName).toList(),
Matchers.containsInAnyOrder(expectedOrganizations.stream().map(OrganizationRepresentation::getName).toArray()));
assertThat(organizations.stream().map(OrganizationRepresentation::getAlias).toList(),
Matchers.containsInAnyOrder(expectedOrganizations.stream().map(OrganizationRepresentation::getAlias).toArray()));
assertThat(organizations.stream().map(OrganizationRepresentation::getDescription).toList(),
Matchers.containsInAnyOrder(expectedOrganizations.stream().map(OrganizationRepresentation::getDescription).toArray()));
// the endpoint search method returns brief representations of orgs - to get full rep we need to fetch by id.
for (OrganizationRepresentation organization : organizations) {
OrganizationRepresentation fullRep = testRealm().organizations().get(organization.getId()).toRepresentation();
// attributes should have been imported.
assertThat(fullRep.getAttributes(), notNullValue());
assertThat(fullRep.getAttributes().keySet(), hasSize(1));
assertThat(fullRep.getAttributes().keySet(), hasItem("key"));
List<String> attrValues = fullRep.getAttributes().get("key");
assertThat(attrValues, notNullValue());
assertThat(attrValues, containsInAnyOrder("value1", "value2"));
}
for (OrganizationRepresentation orgRep : organizations) { for (OrganizationRepresentation orgRep : organizations) {
OrganizationResource organization = testRealm().organizations().get(orgRep.getId()); OrganizationResource organization = testRealm().organizations().get(orgRep.getId());