Export import realm with organizations
Closes #30006 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
1b821f3267
commit
f8d55ca7cd
10 changed files with 376 additions and 44 deletions
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.representations.idm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
@ -32,6 +33,8 @@ public class OrganizationRepresentation {
|
|||
private String description;
|
||||
private Map<String, List<String>> attributes;
|
||||
private Set<OrganizationDomainRepresentation> domains;
|
||||
private List<UserRepresentation> members;
|
||||
private List<IdentityProviderRepresentation> identityProviders;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
|
@ -107,6 +110,36 @@ public class OrganizationRepresentation {
|
|||
getDomains().remove(domain);
|
||||
}
|
||||
|
||||
public List<UserRepresentation> getMembers() {
|
||||
return members;
|
||||
}
|
||||
|
||||
public void setMembers(List<UserRepresentation> members) {
|
||||
this.members = members;
|
||||
}
|
||||
|
||||
public void addMember(UserRepresentation user) {
|
||||
if (members == null) {
|
||||
members = new ArrayList<>();
|
||||
}
|
||||
members.add(user);
|
||||
}
|
||||
|
||||
public List<IdentityProviderRepresentation> getIdentityProviders() {
|
||||
return identityProviders;
|
||||
}
|
||||
|
||||
public void setIdentityProviders(List<IdentityProviderRepresentation> identityProviders) {
|
||||
this.identityProviders = identityProviders;
|
||||
}
|
||||
|
||||
public void addIdentityProvider(IdentityProviderRepresentation idp) {
|
||||
if (identityProviders == null) {
|
||||
identityProviders = new ArrayList<>();
|
||||
}
|
||||
identityProviders.add(idp);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -215,6 +215,7 @@ public class RealmRepresentation {
|
|||
protected Boolean userManagedAccessAllowed;
|
||||
|
||||
protected Boolean organizationsEnabled;
|
||||
private List<OrganizationRepresentation> organizations;
|
||||
|
||||
@Deprecated
|
||||
protected Boolean social;
|
||||
|
@ -1434,4 +1435,19 @@ public class RealmRepresentation {
|
|||
public Map<String, String> getAttributesOrEmpty() {
|
||||
return (Map<String, String>) (attributes == null ? Collections.emptyMap() : attributes);
|
||||
}
|
||||
|
||||
public List<OrganizationRepresentation> getOrganizations() {
|
||||
return organizations;
|
||||
}
|
||||
|
||||
public void setOrganizations(List<OrganizationRepresentation> organizations) {
|
||||
this.organizations = organizations;
|
||||
}
|
||||
|
||||
public void addOrganization(OrganizationRepresentation org) {
|
||||
if (organizations == null) {
|
||||
organizations = new ArrayList<>();
|
||||
}
|
||||
organizations.add(org);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
private final EntityManager em;
|
||||
private final GroupProvider groupProvider;
|
||||
private final UserProvider userProvider;
|
||||
private final RealmModel realm;
|
||||
private final KeycloakSession session;
|
||||
|
||||
public JpaOrganizationProvider(KeycloakSession session) {
|
||||
|
@ -68,10 +67,6 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||
groupProvider = session.groups();
|
||||
userProvider = session.users();
|
||||
realm = session.getContext().getRealm();
|
||||
if (realm == null) {
|
||||
throw new IllegalArgumentException("Session not bound to a realm");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,6 +79,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
throw new ModelDuplicateException("A organization with the same name already exists.");
|
||||
}
|
||||
|
||||
RealmModel realm = getRealm();
|
||||
OrganizationAdapter adapter = new OrganizationAdapter(realm, this);
|
||||
|
||||
try {
|
||||
|
@ -108,7 +104,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
|
||||
try {
|
||||
session.setAttribute(OrganizationModel.class.getName(), organization);
|
||||
RealmModel realm = session.realms().getRealm(this.realm.getId());
|
||||
RealmModel realm = session.realms().getRealm(getRealm().getId());
|
||||
|
||||
// check if the realm is being removed so that we don't need to remove manually remove any other data but the org
|
||||
if (realm != null) {
|
||||
|
@ -116,8 +112,8 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
|
||||
if (group != null) {
|
||||
//TODO: won't scale, requires a better mechanism for bulk deleting users
|
||||
userProvider.getGroupMembersStream(this.realm, group).forEach(userModel -> removeMember(organization, userModel));
|
||||
groupProvider.removeGroup(this.realm, group);
|
||||
userProvider.getGroupMembersStream(realm, group).forEach(userModel -> removeMember(organization, userModel));
|
||||
groupProvider.removeGroup(realm, group);
|
||||
}
|
||||
|
||||
organization.getIdentityProviders().forEach((model) -> removeIdentityProvider(organization, model));
|
||||
|
@ -179,13 +175,14 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
@Override
|
||||
public OrganizationModel getById(String id) {
|
||||
OrganizationEntity entity = getEntity(id, false);
|
||||
return entity == null ? null : new OrganizationAdapter(realm, entity, this);
|
||||
return entity == null ? null : new OrganizationAdapter(getRealm(), entity, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganizationModel getByDomainName(String domain) {
|
||||
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByDomainName", OrganizationEntity.class);
|
||||
query.setParameter("realmId", this.realm.getId());
|
||||
RealmModel realm = getRealm();
|
||||
query.setParameter("realmId", realm.getId());
|
||||
query.setParameter("name", domain.toLowerCase());
|
||||
try {
|
||||
OrganizationEntity entity = query.getSingleResult();
|
||||
|
@ -207,6 +204,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
query = em.createNamedQuery("getByNameOrDomainContained", OrganizationEntity.class);
|
||||
query.setParameter("search", search.toLowerCase());
|
||||
}
|
||||
RealmModel realm = getRealm();
|
||||
query.setParameter("realmId", realm.getId());
|
||||
|
||||
return closing(paginateQuery(query, first, max).getResultStream()
|
||||
|
@ -221,6 +219,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
Root<GroupEntity> group = query.from(GroupEntity.class);
|
||||
|
||||
List<Predicate> predicates = new ArrayList<>();
|
||||
RealmModel realm = getRealm();
|
||||
predicates.add(builder.equal(org.get("realmId"), realm.getId()));
|
||||
predicates.add(builder.equal(org.get("groupId"), group.get("id")));
|
||||
|
||||
|
@ -244,13 +243,13 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
throwExceptionIfObjectIsNull(organization, "Organization");
|
||||
GroupModel group = getOrganizationGroup(organization);
|
||||
|
||||
return userProvider.getGroupMembersStream(realm, group, search, exact, first, max);
|
||||
return userProvider.getGroupMembersStream(getRealm(), group, search, exact, first, max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getMemberById(OrganizationModel organization, String id) {
|
||||
throwExceptionIfObjectIsNull(organization, "Organization");
|
||||
UserModel user = userProvider.getUserById(realm, id);
|
||||
UserModel user = userProvider.getUserById(getRealm(), id);
|
||||
|
||||
if (user == null) {
|
||||
return null;
|
||||
|
@ -299,7 +298,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
}
|
||||
|
||||
identityProvider.setOrganizationId(organizationEntity.getId());
|
||||
realm.updateIdentityProvider(identityProvider);
|
||||
getRealm().updateIdentityProvider(identityProvider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -311,7 +310,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
|
||||
OrganizationEntity organizationEntity = getEntity(organization.getId());
|
||||
|
||||
return realm.getIdentityProvidersStream().filter(model -> organizationEntity.getId().equals(model.getOrganizationId()));
|
||||
return getRealm().getIdentityProvidersStream().filter(model -> organizationEntity.getId().equals(model.getOrganizationId()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -328,7 +327,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
identityProvider.setOrganizationId(null);
|
||||
identityProvider.getConfig().remove(ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||
identityProvider.getConfig().remove(BROKER_PUBLIC);
|
||||
realm.updateIdentityProvider(identityProvider);
|
||||
getRealm().updateIdentityProvider(identityProvider);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -347,6 +346,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
RealmModel realm = getRealm();
|
||||
List<FederatedIdentityModel> federatedIdentities = userProvider.getFederatedIdentitiesStream(realm, member)
|
||||
.map(federatedIdentityModel -> realm.getIdentityProviderByAlias(federatedIdentityModel.getIdentityProvider()))
|
||||
.filter(brokers::contains)
|
||||
|
@ -368,7 +368,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
}
|
||||
|
||||
if (isManagedMember(organization, member)) {
|
||||
userProvider.removeUser(realm, member);
|
||||
userProvider.removeUser(getRealm(), member);
|
||||
} else {
|
||||
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());
|
||||
|
||||
|
@ -395,14 +395,14 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
public long count() {
|
||||
TypedQuery<Long> query;
|
||||
query = em.createNamedQuery("getCount", Long.class);
|
||||
query.setParameter("realmId", realm.getId());
|
||||
query.setParameter("realmId", getRealm().getId());
|
||||
|
||||
return query.getSingleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return realm.isOrganizationsEnabled();
|
||||
return getRealm().isOrganizationsEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -426,6 +426,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
return null;
|
||||
}
|
||||
|
||||
RealmModel realm = getRealm();
|
||||
if (!realm.getId().equals(entity.getRealmId())) {
|
||||
throw new ModelException("Organization [" + entity.getId() + "] does not belong to realm [" + realm.getId() + "]");
|
||||
}
|
||||
|
@ -434,7 +435,8 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
}
|
||||
|
||||
private GroupModel createOrganizationGroup(String orgId) {
|
||||
GroupModel group = groupProvider.createGroup(realm, null, orgId);
|
||||
GroupModel group = groupProvider.createGroup(getRealm(), null, orgId);
|
||||
|
||||
group.setSingleAttribute(ORGANIZATION_ATTRIBUTE, orgId);
|
||||
|
||||
return group;
|
||||
|
@ -453,7 +455,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
}
|
||||
|
||||
private GroupModel getOrganizationGroup(OrganizationEntity entity) {
|
||||
return groupProvider.getGroupById(realm, entity.getGroupId());
|
||||
return groupProvider.getGroupById(getRealm(), entity.getGroupId());
|
||||
}
|
||||
|
||||
private void throwExceptionIfObjectIsNull(Object object, String objectName) {
|
||||
|
@ -466,7 +468,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByOrgName", OrganizationEntity.class);
|
||||
|
||||
query.setParameter("name", name);
|
||||
query.setParameter("realmId", realm.getId());
|
||||
query.setParameter("realmId", getRealm().getId());
|
||||
|
||||
try {
|
||||
return query.getSingleResult();
|
||||
|
@ -482,4 +484,12 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
|||
|
||||
return orgIdpByAlias != null && orgIdpByAlias.getInternalId().equals(idp.getInternalId());
|
||||
}
|
||||
|
||||
private RealmModel getRealm() {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
if (realm == null) {
|
||||
throw new IllegalArgumentException("Session not bound to a realm");
|
||||
}
|
||||
return realm;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.credential.CredentialModel;
|
||||
|
@ -31,15 +32,20 @@ import org.keycloak.models.ClientModel;
|
|||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleContainerModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.RoleRepresentation;
|
||||
import org.keycloak.representations.idm.RolesRepresentation;
|
||||
|
@ -61,20 +67,18 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ExportUtils {
|
||||
|
||||
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, boolean includeUsers, boolean internal) {
|
||||
ExportOptions opts = new ExportOptions(includeUsers, true, true, false);
|
||||
ExportOptions opts = new ExportOptions(includeUsers, true, true, false, false);
|
||||
return exportRealm(session, realm, opts, internal);
|
||||
}
|
||||
|
||||
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean internal) {
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal);
|
||||
RealmRepresentation rep = ModelToRepresentation.toRepresentation(session, realm, internal, true);
|
||||
ModelToRepresentation.exportAuthenticationFlows(session, realm, rep);
|
||||
ModelToRepresentation.exportRequiredActions(realm, rep);
|
||||
|
||||
|
@ -255,6 +259,41 @@ public class ExportUtils {
|
|||
// Message Bundle
|
||||
rep.setLocalizationTexts(realm.getRealmLocalizationTexts());
|
||||
|
||||
if (Profile.isFeatureEnabled(Feature.ORGANIZATION) && !options.isPartial()) {
|
||||
OrganizationProvider orgProvider = session.getProvider(OrganizationProvider.class);
|
||||
orgProvider.getAllStream().map(m -> {
|
||||
OrganizationRepresentation org = new OrganizationRepresentation();
|
||||
|
||||
org.setName(m.getName());
|
||||
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, -1, -1)
|
||||
.map(user -> {
|
||||
UserRepresentation member = new UserRepresentation();
|
||||
member.setUsername(user.getUsername());
|
||||
return member;
|
||||
}).forEach(org::addMember);
|
||||
|
||||
orgProvider.getIdentityProviders(m)
|
||||
.map(b -> {
|
||||
IdentityProviderRepresentation broker = new IdentityProviderRepresentation();
|
||||
broker.setAlias(b.getAlias());
|
||||
return broker;
|
||||
}).forEach(org::addIdentityProvider);
|
||||
|
||||
return org;
|
||||
}).forEach(rep::addOrganization);
|
||||
}
|
||||
|
||||
return rep;
|
||||
}
|
||||
|
||||
|
@ -417,9 +456,16 @@ public class ExportUtils {
|
|||
}
|
||||
|
||||
if (options.isGroupsAndRolesIncluded()) {
|
||||
List<String> groups = user.getGroupsStream().map(ModelToRepresentation::buildGroupPath).collect(Collectors.toList());
|
||||
List<String> groups = user.getGroupsStream()
|
||||
.filter(g -> !g.getAttributes().containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE))
|
||||
.map(ModelToRepresentation::buildGroupPath).collect(Collectors.toList());
|
||||
userRep.setGroups(groups);
|
||||
}
|
||||
|
||||
if (userRep.getAttributes() != null) {
|
||||
userRep.getAttributes().remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||
}
|
||||
|
||||
return userRep;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.storage.datastore;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.common.enums.SslRequired;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
|
@ -40,11 +42,14 @@ import org.keycloak.models.ClientScopeModel;
|
|||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.LDAPConstants;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OAuth2DeviceConfig;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.OrganizationDomainModel;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.models.ParConfig;
|
||||
import org.keycloak.models.PasswordPolicy;
|
||||
import org.keycloak.models.RealmModel;
|
||||
|
@ -61,6 +66,7 @@ import org.keycloak.models.utils.DefaultRequiredActions;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.organization.OrganizationProvider;
|
||||
import org.keycloak.partialimport.PartialImportResults;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||
|
@ -79,6 +85,8 @@ import org.keycloak.representations.idm.GroupRepresentation;
|
|||
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationDomainRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
|
@ -107,11 +115,13 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -460,6 +470,8 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||
DefaultKeyProviders.createProviders(newRealm);
|
||||
}
|
||||
}
|
||||
|
||||
importOrganizations(rep, newRealm);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1572,4 +1584,24 @@ public class DefaultExportImportManager implements ExportImportManager {
|
|||
}
|
||||
}
|
||||
|
||||
private void importOrganizations(RealmRepresentation rep, RealmModel newRealm) {
|
||||
if (Profile.isFeatureEnabled(Feature.ORGANIZATION)) {
|
||||
OrganizationProvider provider = session.getProvider(OrganizationProvider.class);
|
||||
|
||||
for (OrganizationRepresentation orgRep : Optional.ofNullable(rep.getOrganizations()).orElse(Collections.emptyList())) {
|
||||
OrganizationModel org = provider.create(orgRep.getName());
|
||||
org.setDomains(orgRep.getDomains().stream().map(r -> new OrganizationDomainModel(r.getName(), r.isVerified())).collect(Collectors.toSet()));
|
||||
|
||||
for (IdentityProviderRepresentation identityProvider : orgRep.getIdentityProviders()) {
|
||||
IdentityProviderModel idp = newRealm.getIdentityProviderByAlias(identityProvider.getAlias());
|
||||
provider.addIdentityProvider(org, idp);
|
||||
}
|
||||
|
||||
for (UserRepresentation member : orgRep.getMembers()) {
|
||||
UserModel m = session.users().getUserByUsername(newRealm, member.getUsername());
|
||||
provider.addMember(org, m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,15 +26,17 @@ public class ExportOptions {
|
|||
private boolean clientsIncluded = true;
|
||||
private boolean groupsAndRolesIncluded = true;
|
||||
private boolean onlyServiceAccountsIncluded = false;
|
||||
private boolean partial;
|
||||
|
||||
public ExportOptions() {
|
||||
}
|
||||
|
||||
public ExportOptions(boolean users, boolean clients, boolean groupsAndRoles, boolean onlyServiceAccounts) {
|
||||
public ExportOptions(boolean users, boolean clients, boolean groupsAndRoles, boolean onlyServiceAccounts, boolean partial) {
|
||||
usersIncluded = users;
|
||||
clientsIncluded = clients;
|
||||
groupsAndRolesIncluded = groupsAndRoles;
|
||||
onlyServiceAccountsIncluded = onlyServiceAccounts;
|
||||
this.partial = partial;
|
||||
}
|
||||
|
||||
public boolean isUsersIncluded() {
|
||||
|
@ -68,4 +70,8 @@ public class ExportOptions {
|
|||
public void setOnlyServiceAccountsIncluded(boolean value) {
|
||||
onlyServiceAccountsIncluded = value;
|
||||
}
|
||||
|
||||
public boolean isPartial() {
|
||||
return partial;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,6 +171,7 @@ public class ModelToRepresentation {
|
|||
@Deprecated
|
||||
public static Stream<GroupRepresentation> toGroupHierarchy(KeycloakSession session, RealmModel realm, boolean full) {
|
||||
return session.groups().getTopLevelGroupsStream(realm, null, null)
|
||||
.filter(g -> !g.getAttributes().containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE))
|
||||
.map(g -> toGroupHierarchy(g, full));
|
||||
}
|
||||
|
||||
|
@ -343,6 +344,10 @@ public class ModelToRepresentation {
|
|||
}
|
||||
|
||||
public static RealmRepresentation toRepresentation(KeycloakSession session, RealmModel realm, boolean internal) {
|
||||
return toRepresentation(session, realm, internal, false);
|
||||
}
|
||||
|
||||
public static RealmRepresentation toRepresentation(KeycloakSession session, RealmModel realm, boolean internal, boolean export) {
|
||||
RealmRepresentation rep = new RealmRepresentation();
|
||||
rep.setId(realm.getId());
|
||||
rep.setRealm(realm.getName());
|
||||
|
@ -499,7 +504,7 @@ public class ModelToRepresentation {
|
|||
}
|
||||
|
||||
List<IdentityProviderRepresentation> identityProviders = realm.getIdentityProvidersStream()
|
||||
.map(provider -> toRepresentation(realm, provider)).collect(Collectors.toList());
|
||||
.map(provider -> toRepresentation(realm, provider, export)).collect(Collectors.toList());
|
||||
rep.setIdentityProviders(identityProviders);
|
||||
|
||||
List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream()
|
||||
|
@ -787,6 +792,10 @@ public class ModelToRepresentation {
|
|||
}
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
|
||||
return toRepresentation(realm, identityProviderModel, false);
|
||||
}
|
||||
|
||||
public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel, boolean export) {
|
||||
IdentityProviderRepresentation providerRep = toBriefRepresentation(realm, identityProviderModel);
|
||||
|
||||
providerRep.setLinkOnly(identityProviderModel.isLinkOnly());
|
||||
|
@ -820,6 +829,10 @@ public class ModelToRepresentation {
|
|||
providerRep.setPostBrokerLoginFlowAlias(flow.getAlias());
|
||||
}
|
||||
|
||||
if (export) {
|
||||
providerRep.getConfig().remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||
}
|
||||
|
||||
return providerRep;
|
||||
}
|
||||
|
||||
|
|
|
@ -137,13 +137,7 @@ public class RepresentationToModel {
|
|||
|
||||
|
||||
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
||||
KeycloakContext context = session.getContext();
|
||||
try {
|
||||
context.setRealm(newRealm);
|
||||
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, skipUserDependent);
|
||||
} finally {
|
||||
context.setRealm(null);
|
||||
}
|
||||
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, skipUserDependent);
|
||||
}
|
||||
|
||||
public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
|
||||
|
|
|
@ -1115,7 +1115,7 @@ public class RealmAdminResource {
|
|||
auth.realm().requireManageRealm();
|
||||
try {
|
||||
return Response.ok(
|
||||
KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), kcSession -> {
|
||||
KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), session.getContext(), kcSession -> {
|
||||
RealmModel realmClone = kcSession.realms().getRealm(realm.getId());
|
||||
AdminEventBuilder adminEventClone = adminEvent.clone(kcSession);
|
||||
// calling a static method to avoid using the wrong instances
|
||||
|
@ -1186,7 +1186,7 @@ public class RealmAdminResource {
|
|||
// service accounts are exported if the clients are exported
|
||||
// this means that if clients is true but groups/roles is false the service account is exported without roles
|
||||
// the other option is just include service accounts if clientsExported && groupsAndRolesExported
|
||||
ExportOptions options = new ExportOptions(false, clientsExported, groupsAndRolesExported, clientsExported);
|
||||
ExportOptions options = new ExportOptions(false, clientsExported, groupsAndRolesExported, clientsExported, true);
|
||||
|
||||
ExportImportManager exportProvider = session.getProvider(DatastoreProvider.class).getExportImportManager();
|
||||
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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.testsuite.organization.admin;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.keycloak.testsuite.broker.BrokerTestTools.waitForPage;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.admin.client.resource.OrganizationResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.admin.client.resource.UsersResource;
|
||||
import org.keycloak.common.Profile.Feature;
|
||||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
|
||||
import org.keycloak.models.OrganizationModel;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.OrganizationRepresentation;
|
||||
import org.keycloak.representations.idm.PartialImportRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
import org.keycloak.testsuite.arquillian.annotation.EnableFeature;
|
||||
import org.keycloak.testsuite.client.resources.TestingExportImportResource;
|
||||
import org.keycloak.testsuite.pages.AppPage;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
||||
@EnableFeature(Feature.ORGANIZATION)
|
||||
public class OrganizationExportTest extends AbstractOrganizationTest {
|
||||
|
||||
@Test
|
||||
public void testExport() {
|
||||
RealmResource providerRealm = realmsResouce().realm(bc.providerRealmName());
|
||||
List<String> expectedOrganizations = new ArrayList<>();
|
||||
Map<String, List<String>> expectedManagedMembers = new HashMap<>();
|
||||
Map<String, List<String>> expectedUnmanagedMembers = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < 2; i++) {
|
||||
IdentityProviderRepresentation broker = bc.setUpIdentityProvider();
|
||||
broker.setAlias("broker-org-" + i);
|
||||
broker.setInternalId(null);
|
||||
String domain = "org-" + i + ".org";
|
||||
OrganizationRepresentation orgRep = createOrganization(testRealm(), getCleanup(), "org-" + i, broker, domain);
|
||||
OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
|
||||
|
||||
expectedOrganizations.add(orgRep.getName());
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
UserRepresentation member = addMember(organization, "realmuser-" + j + "@" + domain);
|
||||
expectedUnmanagedMembers.computeIfAbsent(orgRep.getName(), s -> new ArrayList<>()).add(member.getUsername());
|
||||
}
|
||||
|
||||
UsersResource federatedUsers = providerRealm.users();
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
String email = "feduser" + j + "@" + domain;
|
||||
|
||||
federatedUsers.create(UserBuilder.create()
|
||||
.username(email)
|
||||
.email(email)
|
||||
.firstName("f")
|
||||
.lastName("l")
|
||||
.enabled(true)
|
||||
.password("password")
|
||||
.build()).close();
|
||||
|
||||
expectedManagedMembers.computeIfAbsent(orgRep.getName(), s -> new ArrayList<>()).add(email);
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
loginPage.loginUsername(email);
|
||||
// user automatically redirected to the organization identity provider
|
||||
waitForPage(driver, "sign in to", true);
|
||||
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||
// login to the organization identity provider and run the configured first broker login flow
|
||||
loginPage.login(email, bc.getUserPassword());
|
||||
assertIsMember(email, organization);
|
||||
testRealm().logoutAll();
|
||||
providerRealm.logoutAll();
|
||||
}
|
||||
}
|
||||
|
||||
// export
|
||||
TestingExportImportResource exportImport = testingClient.testing().exportImport();
|
||||
exportImport.setProvider(SingleFileExportProviderFactory.PROVIDER_ID);
|
||||
exportImport.setAction(ExportImportConfig.ACTION_EXPORT);
|
||||
exportImport.setRealmName(testRealm().toRepresentation().getRealm());
|
||||
String targetFilePath = exportImport.getExportImportTestDirectory() + File.separator + "org-export.json";
|
||||
exportImport.setFile(targetFilePath);
|
||||
exportImport.runExport();
|
||||
|
||||
// remove the realm and import it back
|
||||
testRealm().remove();
|
||||
exportImport = testingClient.testing().exportImport();
|
||||
exportImport.setProvider(SingleFileImportProviderFactory.PROVIDER_ID);
|
||||
exportImport.setAction(ExportImportConfig.ACTION_IMPORT);
|
||||
exportImport.setFile(targetFilePath);
|
||||
exportImport.runImport();
|
||||
getCleanup().addCleanup(() -> testRealm().remove());
|
||||
|
||||
RealmRepresentation importedRealm = testRealm().toRepresentation();
|
||||
|
||||
assertTrue(importedRealm.isOrganizationsEnabled());
|
||||
|
||||
List<OrganizationRepresentation> organizations = testRealm().organizations().getAll();
|
||||
assertEquals(expectedOrganizations.size(), organizations.size());
|
||||
assertThat(organizations.stream().map(OrganizationRepresentation::getName).toList(), Matchers.containsInAnyOrder(expectedOrganizations.toArray()));
|
||||
|
||||
for (OrganizationRepresentation orgRep : organizations) {
|
||||
OrganizationResource organization = testRealm().organizations().get(orgRep.getId());
|
||||
List<String> members = organization.members().getAll().stream().map(UserRepresentation::getEmail).toList();
|
||||
assertEquals(members.size(), expectedUnmanagedMembers.get(orgRep.getName()).size() + expectedManagedMembers.get(orgRep.getName()).size());
|
||||
assertTrue(members.containsAll(expectedUnmanagedMembers.get(orgRep.getName())));
|
||||
assertTrue(members.containsAll(expectedManagedMembers.get(orgRep.getName())));
|
||||
}
|
||||
|
||||
// make sure a managed user can authenticate through the broker associated with an org
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
log.debug("Logging in");
|
||||
String email = expectedManagedMembers.values().stream().findAny().get().get(0);
|
||||
loginPage.loginUsername(email);
|
||||
// user automatically redirected to the organization identity provider
|
||||
waitForPage(driver, "sign in to", true);
|
||||
Assert.assertTrue("Driver should be on the provider realm page right now",
|
||||
driver.getCurrentUrl().contains("/auth/realms/" + bc.providerRealmName() + "/"));
|
||||
// login to the organization identity provider and run the configured first broker login flow
|
||||
loginPage.login(email, bc.getUserPassword());
|
||||
assertThat(appPage.getRequestType(),is(AppPage.RequestType.AUTH_RESPONSE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPartialExport() {
|
||||
createOrganization();
|
||||
assertPartialExportImport(false, false);
|
||||
assertPartialExportImport(true, false);
|
||||
assertPartialExportImport(true, true);
|
||||
assertPartialExportImport(false, true);
|
||||
}
|
||||
|
||||
private void assertPartialExportImport(boolean exportGroupsAndRoles, boolean exportClients) {
|
||||
RealmRepresentation export = testRealm().partialExport(exportGroupsAndRoles, exportClients);
|
||||
assertTrue(Optional.ofNullable(export.getGroups()).orElse(List.of()).stream().noneMatch(g -> g.getAttributes().containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE)));
|
||||
assertTrue(Optional.ofNullable(export.getOrganizations()).orElse(List.of()).isEmpty());
|
||||
assertTrue(Optional.ofNullable(export.getIdentityProviders()).orElse(List.of()).stream().noneMatch(g -> g.getConfig().containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE)));
|
||||
PartialImportRepresentation rep = new PartialImportRepresentation();
|
||||
rep.setUsers(export.getUsers());
|
||||
rep.setClients(export.getClients());
|
||||
rep.setRoles(export.getRoles());
|
||||
rep.setIdentityProviders(export.getIdentityProviders());
|
||||
rep.setGroups(export.getGroups());
|
||||
testRealm().partialImport(rep).close();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue