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;
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -32,6 +33,8 @@ public class OrganizationRepresentation {
|
||||||
private String description;
|
private String description;
|
||||||
private Map<String, List<String>> attributes;
|
private Map<String, List<String>> attributes;
|
||||||
private Set<OrganizationDomainRepresentation> domains;
|
private Set<OrganizationDomainRepresentation> domains;
|
||||||
|
private List<UserRepresentation> members;
|
||||||
|
private List<IdentityProviderRepresentation> identityProviders;
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
@ -107,6 +110,36 @@ public class OrganizationRepresentation {
|
||||||
getDomains().remove(domain);
|
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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|
|
@ -181,15 +181,15 @@ public class RealmRepresentation {
|
||||||
protected String accountTheme;
|
protected String accountTheme;
|
||||||
protected String adminTheme;
|
protected String adminTheme;
|
||||||
protected String emailTheme;
|
protected String emailTheme;
|
||||||
|
|
||||||
protected Boolean eventsEnabled;
|
protected Boolean eventsEnabled;
|
||||||
protected Long eventsExpiration;
|
protected Long eventsExpiration;
|
||||||
protected List<String> eventsListeners;
|
protected List<String> eventsListeners;
|
||||||
protected List<String> enabledEventTypes;
|
protected List<String> enabledEventTypes;
|
||||||
|
|
||||||
protected Boolean adminEventsEnabled;
|
protected Boolean adminEventsEnabled;
|
||||||
protected Boolean adminEventsDetailsEnabled;
|
protected Boolean adminEventsDetailsEnabled;
|
||||||
|
|
||||||
private List<IdentityProviderRepresentation> identityProviders;
|
private List<IdentityProviderRepresentation> identityProviders;
|
||||||
private List<IdentityProviderMapperRepresentation> identityProviderMappers;
|
private List<IdentityProviderMapperRepresentation> identityProviderMappers;
|
||||||
private List<ProtocolMapperRepresentation> protocolMappers;
|
private List<ProtocolMapperRepresentation> protocolMappers;
|
||||||
|
@ -215,6 +215,7 @@ public class RealmRepresentation {
|
||||||
protected Boolean userManagedAccessAllowed;
|
protected Boolean userManagedAccessAllowed;
|
||||||
|
|
||||||
protected Boolean organizationsEnabled;
|
protected Boolean organizationsEnabled;
|
||||||
|
private List<OrganizationRepresentation> organizations;
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
protected Boolean social;
|
protected Boolean social;
|
||||||
|
@ -622,7 +623,7 @@ public class RealmRepresentation {
|
||||||
public void setVerifyEmail(Boolean verifyEmail) {
|
public void setVerifyEmail(Boolean verifyEmail) {
|
||||||
this.verifyEmail = verifyEmail;
|
this.verifyEmail = verifyEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isLoginWithEmailAllowed() {
|
public Boolean isLoginWithEmailAllowed() {
|
||||||
return loginWithEmailAllowed;
|
return loginWithEmailAllowed;
|
||||||
}
|
}
|
||||||
|
@ -630,7 +631,7 @@ public class RealmRepresentation {
|
||||||
public void setLoginWithEmailAllowed(Boolean loginWithEmailAllowed) {
|
public void setLoginWithEmailAllowed(Boolean loginWithEmailAllowed) {
|
||||||
this.loginWithEmailAllowed = loginWithEmailAllowed;
|
this.loginWithEmailAllowed = loginWithEmailAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isDuplicateEmailsAllowed() {
|
public Boolean isDuplicateEmailsAllowed() {
|
||||||
return duplicateEmailsAllowed;
|
return duplicateEmailsAllowed;
|
||||||
}
|
}
|
||||||
|
@ -847,7 +848,7 @@ public class RealmRepresentation {
|
||||||
public void setEventsListeners(List<String> eventsListeners) {
|
public void setEventsListeners(List<String> eventsListeners) {
|
||||||
this.eventsListeners = eventsListeners;
|
this.eventsListeners = eventsListeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getEnabledEventTypes() {
|
public List<String> getEnabledEventTypes() {
|
||||||
return enabledEventTypes;
|
return enabledEventTypes;
|
||||||
}
|
}
|
||||||
|
@ -1434,4 +1435,19 @@ public class RealmRepresentation {
|
||||||
public Map<String, String> getAttributesOrEmpty() {
|
public Map<String, String> getAttributesOrEmpty() {
|
||||||
return (Map<String, String>) (attributes == null ? Collections.emptyMap() : attributes);
|
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 EntityManager em;
|
||||||
private final GroupProvider groupProvider;
|
private final GroupProvider groupProvider;
|
||||||
private final UserProvider userProvider;
|
private final UserProvider userProvider;
|
||||||
private final RealmModel realm;
|
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public JpaOrganizationProvider(KeycloakSession session) {
|
public JpaOrganizationProvider(KeycloakSession session) {
|
||||||
|
@ -68,10 +67,6 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
em = session.getProvider(JpaConnectionProvider.class).getEntityManager();
|
||||||
groupProvider = session.groups();
|
groupProvider = session.groups();
|
||||||
userProvider = session.users();
|
userProvider = session.users();
|
||||||
realm = session.getContext().getRealm();
|
|
||||||
if (realm == null) {
|
|
||||||
throw new IllegalArgumentException("Session not bound to a realm");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,6 +79,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throw new ModelDuplicateException("A organization with the same name already exists.");
|
throw new ModelDuplicateException("A organization with the same name already exists.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealmModel realm = getRealm();
|
||||||
OrganizationAdapter adapter = new OrganizationAdapter(realm, this);
|
OrganizationAdapter adapter = new OrganizationAdapter(realm, this);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -108,7 +104,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.setAttribute(OrganizationModel.class.getName(), organization);
|
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
|
// 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) {
|
if (realm != null) {
|
||||||
|
@ -116,8 +112,8 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
//TODO: won't scale, requires a better mechanism for bulk deleting users
|
//TODO: won't scale, requires a better mechanism for bulk deleting users
|
||||||
userProvider.getGroupMembersStream(this.realm, group).forEach(userModel -> removeMember(organization, userModel));
|
userProvider.getGroupMembersStream(realm, group).forEach(userModel -> removeMember(organization, userModel));
|
||||||
groupProvider.removeGroup(this.realm, group);
|
groupProvider.removeGroup(realm, group);
|
||||||
}
|
}
|
||||||
|
|
||||||
organization.getIdentityProviders().forEach((model) -> removeIdentityProvider(organization, model));
|
organization.getIdentityProviders().forEach((model) -> removeIdentityProvider(organization, model));
|
||||||
|
@ -179,13 +175,14 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
@Override
|
@Override
|
||||||
public OrganizationModel getById(String id) {
|
public OrganizationModel getById(String id) {
|
||||||
OrganizationEntity entity = getEntity(id, false);
|
OrganizationEntity entity = getEntity(id, false);
|
||||||
return entity == null ? null : new OrganizationAdapter(realm, entity, this);
|
return entity == null ? null : new OrganizationAdapter(getRealm(), entity, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public OrganizationModel getByDomainName(String domain) {
|
public OrganizationModel getByDomainName(String domain) {
|
||||||
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByDomainName", OrganizationEntity.class);
|
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());
|
query.setParameter("name", domain.toLowerCase());
|
||||||
try {
|
try {
|
||||||
OrganizationEntity entity = query.getSingleResult();
|
OrganizationEntity entity = query.getSingleResult();
|
||||||
|
@ -207,6 +204,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
query = em.createNamedQuery("getByNameOrDomainContained", OrganizationEntity.class);
|
query = em.createNamedQuery("getByNameOrDomainContained", OrganizationEntity.class);
|
||||||
query.setParameter("search", search.toLowerCase());
|
query.setParameter("search", search.toLowerCase());
|
||||||
}
|
}
|
||||||
|
RealmModel realm = getRealm();
|
||||||
query.setParameter("realmId", realm.getId());
|
query.setParameter("realmId", realm.getId());
|
||||||
|
|
||||||
return closing(paginateQuery(query, first, max).getResultStream()
|
return closing(paginateQuery(query, first, max).getResultStream()
|
||||||
|
@ -221,6 +219,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
Root<GroupEntity> group = query.from(GroupEntity.class);
|
Root<GroupEntity> group = query.from(GroupEntity.class);
|
||||||
|
|
||||||
List<Predicate> predicates = new ArrayList<>();
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
RealmModel realm = getRealm();
|
||||||
predicates.add(builder.equal(org.get("realmId"), realm.getId()));
|
predicates.add(builder.equal(org.get("realmId"), realm.getId()));
|
||||||
predicates.add(builder.equal(org.get("groupId"), group.get("id")));
|
predicates.add(builder.equal(org.get("groupId"), group.get("id")));
|
||||||
|
|
||||||
|
@ -244,13 +243,13 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
throwExceptionIfObjectIsNull(organization, "Organization");
|
throwExceptionIfObjectIsNull(organization, "Organization");
|
||||||
GroupModel group = getOrganizationGroup(organization);
|
GroupModel group = getOrganizationGroup(organization);
|
||||||
|
|
||||||
return userProvider.getGroupMembersStream(realm, group, search, exact, first, max);
|
return userProvider.getGroupMembersStream(getRealm(), group, search, exact, first, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UserModel getMemberById(OrganizationModel organization, String id) {
|
public UserModel getMemberById(OrganizationModel organization, String id) {
|
||||||
throwExceptionIfObjectIsNull(organization, "Organization");
|
throwExceptionIfObjectIsNull(organization, "Organization");
|
||||||
UserModel user = userProvider.getUserById(realm, id);
|
UserModel user = userProvider.getUserById(getRealm(), id);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -299,7 +298,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
identityProvider.setOrganizationId(organizationEntity.getId());
|
identityProvider.setOrganizationId(organizationEntity.getId());
|
||||||
realm.updateIdentityProvider(identityProvider);
|
getRealm().updateIdentityProvider(identityProvider);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -311,7 +310,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
OrganizationEntity organizationEntity = getEntity(organization.getId());
|
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
|
@Override
|
||||||
|
@ -328,7 +327,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
identityProvider.setOrganizationId(null);
|
identityProvider.setOrganizationId(null);
|
||||||
identityProvider.getConfig().remove(ORGANIZATION_DOMAIN_ATTRIBUTE);
|
identityProvider.getConfig().remove(ORGANIZATION_DOMAIN_ATTRIBUTE);
|
||||||
identityProvider.getConfig().remove(BROKER_PUBLIC);
|
identityProvider.getConfig().remove(BROKER_PUBLIC);
|
||||||
realm.updateIdentityProvider(identityProvider);
|
getRealm().updateIdentityProvider(identityProvider);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -347,6 +346,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealmModel realm = getRealm();
|
||||||
List<FederatedIdentityModel> federatedIdentities = userProvider.getFederatedIdentitiesStream(realm, member)
|
List<FederatedIdentityModel> federatedIdentities = userProvider.getFederatedIdentitiesStream(realm, member)
|
||||||
.map(federatedIdentityModel -> realm.getIdentityProviderByAlias(federatedIdentityModel.getIdentityProvider()))
|
.map(federatedIdentityModel -> realm.getIdentityProviderByAlias(federatedIdentityModel.getIdentityProvider()))
|
||||||
.filter(brokers::contains)
|
.filter(brokers::contains)
|
||||||
|
@ -368,7 +368,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isManagedMember(organization, member)) {
|
if (isManagedMember(organization, member)) {
|
||||||
userProvider.removeUser(realm, member);
|
userProvider.removeUser(getRealm(), member);
|
||||||
} else {
|
} else {
|
||||||
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());
|
OrganizationModel current = (OrganizationModel) session.getAttribute(OrganizationModel.class.getName());
|
||||||
|
|
||||||
|
@ -395,14 +395,14 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
public long count() {
|
public long count() {
|
||||||
TypedQuery<Long> query;
|
TypedQuery<Long> query;
|
||||||
query = em.createNamedQuery("getCount", Long.class);
|
query = em.createNamedQuery("getCount", Long.class);
|
||||||
query.setParameter("realmId", realm.getId());
|
query.setParameter("realmId", getRealm().getId());
|
||||||
|
|
||||||
return query.getSingleResult();
|
return query.getSingleResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isEnabled() {
|
public boolean isEnabled() {
|
||||||
return realm.isOrganizationsEnabled();
|
return getRealm().isOrganizationsEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -426,6 +426,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RealmModel realm = getRealm();
|
||||||
if (!realm.getId().equals(entity.getRealmId())) {
|
if (!realm.getId().equals(entity.getRealmId())) {
|
||||||
throw new ModelException("Organization [" + entity.getId() + "] does not belong to realm [" + realm.getId() + "]");
|
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) {
|
private GroupModel createOrganizationGroup(String orgId) {
|
||||||
GroupModel group = groupProvider.createGroup(realm, null, orgId);
|
GroupModel group = groupProvider.createGroup(getRealm(), null, orgId);
|
||||||
|
|
||||||
group.setSingleAttribute(ORGANIZATION_ATTRIBUTE, orgId);
|
group.setSingleAttribute(ORGANIZATION_ATTRIBUTE, orgId);
|
||||||
|
|
||||||
return group;
|
return group;
|
||||||
|
@ -453,7 +455,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
private GroupModel getOrganizationGroup(OrganizationEntity entity) {
|
private GroupModel getOrganizationGroup(OrganizationEntity entity) {
|
||||||
return groupProvider.getGroupById(realm, entity.getGroupId());
|
return groupProvider.getGroupById(getRealm(), entity.getGroupId());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void throwExceptionIfObjectIsNull(Object object, String objectName) {
|
private void throwExceptionIfObjectIsNull(Object object, String objectName) {
|
||||||
|
@ -466,7 +468,7 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByOrgName", OrganizationEntity.class);
|
TypedQuery<OrganizationEntity> query = em.createNamedQuery("getByOrgName", OrganizationEntity.class);
|
||||||
|
|
||||||
query.setParameter("name", name);
|
query.setParameter("name", name);
|
||||||
query.setParameter("realmId", realm.getId());
|
query.setParameter("realmId", getRealm().getId());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return query.getSingleResult();
|
return query.getSingleResult();
|
||||||
|
@ -482,4 +484,12 @@ public class JpaOrganizationProvider implements OrganizationProvider {
|
||||||
|
|
||||||
return orgIdpByAlias != null && orgIdpByAlias.getInternalId().equals(idp.getInternalId());
|
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.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.common.Profile.Feature;
|
||||||
import org.keycloak.common.Version;
|
import org.keycloak.common.Version;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.credential.CredentialModel;
|
import org.keycloak.credential.CredentialModel;
|
||||||
|
@ -31,15 +32,20 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientScopeModel;
|
import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.OrganizationModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
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.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
import org.keycloak.representations.idm.ComponentExportRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
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.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.RoleRepresentation;
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.representations.idm.RolesRepresentation;
|
import org.keycloak.representations.idm.RolesRepresentation;
|
||||||
|
@ -61,20 +67,18 @@ import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.keycloak.models.utils.ModelToRepresentation.toRepresentation;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class ExportUtils {
|
public class ExportUtils {
|
||||||
|
|
||||||
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, boolean includeUsers, boolean internal) {
|
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);
|
return exportRealm(session, realm, opts, internal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options, boolean 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.exportAuthenticationFlows(session, realm, rep);
|
||||||
ModelToRepresentation.exportRequiredActions(realm, rep);
|
ModelToRepresentation.exportRequiredActions(realm, rep);
|
||||||
|
|
||||||
|
@ -255,6 +259,41 @@ public class ExportUtils {
|
||||||
// Message Bundle
|
// Message Bundle
|
||||||
rep.setLocalizationTexts(realm.getRealmLocalizationTexts());
|
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;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -417,9 +456,16 @@ public class ExportUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.isGroupsAndRolesIncluded()) {
|
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);
|
userRep.setGroups(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userRep.getAttributes() != null) {
|
||||||
|
userRep.getAttributes().remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
return userRep;
|
return userRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -576,7 +622,7 @@ public class ExportUtils {
|
||||||
}
|
}
|
||||||
return userRep;
|
return userRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UserFederatedStorageProvider userFederatedStorage(KeycloakSession session) {
|
private static UserFederatedStorageProvider userFederatedStorage(KeycloakSession session) {
|
||||||
return session.getProvider(UserFederatedStorageProvider.class);
|
return session.getProvider(UserFederatedStorageProvider.class);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
package org.keycloak.storage.datastore;
|
package org.keycloak.storage.datastore;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
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.enums.SslRequired;
|
||||||
import org.keycloak.common.util.MultivaluedHashMap;
|
import org.keycloak.common.util.MultivaluedHashMap;
|
||||||
import org.keycloak.component.ComponentModel;
|
import org.keycloak.component.ComponentModel;
|
||||||
|
@ -40,11 +42,14 @@ import org.keycloak.models.ClientScopeModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.LDAPConstants;
|
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.ParConfig;
|
import org.keycloak.models.ParConfig;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
import org.keycloak.models.RealmModel;
|
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.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.organization.OrganizationProvider;
|
||||||
import org.keycloak.partialimport.PartialImportResults;
|
import org.keycloak.partialimport.PartialImportResults;
|
||||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
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.IdentityProviderMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
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.PartialImportRepresentation;
|
||||||
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -107,11 +115,13 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
@ -460,6 +470,8 @@ public class DefaultExportImportManager implements ExportImportManager {
|
||||||
DefaultKeyProviders.createProviders(newRealm);
|
DefaultKeyProviders.createProviders(newRealm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
importOrganizations(rep, newRealm);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 clientsIncluded = true;
|
||||||
private boolean groupsAndRolesIncluded = true;
|
private boolean groupsAndRolesIncluded = true;
|
||||||
private boolean onlyServiceAccountsIncluded = false;
|
private boolean onlyServiceAccountsIncluded = false;
|
||||||
|
private boolean partial;
|
||||||
|
|
||||||
public ExportOptions() {
|
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;
|
usersIncluded = users;
|
||||||
clientsIncluded = clients;
|
clientsIncluded = clients;
|
||||||
groupsAndRolesIncluded = groupsAndRoles;
|
groupsAndRolesIncluded = groupsAndRoles;
|
||||||
onlyServiceAccountsIncluded = onlyServiceAccounts;
|
onlyServiceAccountsIncluded = onlyServiceAccounts;
|
||||||
|
this.partial = partial;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isUsersIncluded() {
|
public boolean isUsersIncluded() {
|
||||||
|
@ -68,4 +70,8 @@ public class ExportOptions {
|
||||||
public void setOnlyServiceAccountsIncluded(boolean value) {
|
public void setOnlyServiceAccountsIncluded(boolean value) {
|
||||||
onlyServiceAccountsIncluded = value;
|
onlyServiceAccountsIncluded = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isPartial() {
|
||||||
|
return partial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,6 +171,7 @@ public class ModelToRepresentation {
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Stream<GroupRepresentation> toGroupHierarchy(KeycloakSession session, RealmModel realm, boolean full) {
|
public static Stream<GroupRepresentation> toGroupHierarchy(KeycloakSession session, RealmModel realm, boolean full) {
|
||||||
return session.groups().getTopLevelGroupsStream(realm, null, null)
|
return session.groups().getTopLevelGroupsStream(realm, null, null)
|
||||||
|
.filter(g -> !g.getAttributes().containsKey(OrganizationModel.ORGANIZATION_ATTRIBUTE))
|
||||||
.map(g -> toGroupHierarchy(g, full));
|
.map(g -> toGroupHierarchy(g, full));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,6 +344,10 @@ public class ModelToRepresentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RealmRepresentation toRepresentation(KeycloakSession session, RealmModel realm, boolean internal) {
|
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();
|
RealmRepresentation rep = new RealmRepresentation();
|
||||||
rep.setId(realm.getId());
|
rep.setId(realm.getId());
|
||||||
rep.setRealm(realm.getName());
|
rep.setRealm(realm.getName());
|
||||||
|
@ -499,7 +504,7 @@ public class ModelToRepresentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<IdentityProviderRepresentation> identityProviders = realm.getIdentityProvidersStream()
|
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);
|
rep.setIdentityProviders(identityProviders);
|
||||||
|
|
||||||
List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream()
|
List<IdentityProviderMapperRepresentation> identityProviderMappers = realm.getIdentityProviderMappersStream()
|
||||||
|
@ -787,6 +792,10 @@ public class ModelToRepresentation {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IdentityProviderRepresentation toRepresentation(RealmModel realm, IdentityProviderModel identityProviderModel) {
|
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);
|
IdentityProviderRepresentation providerRep = toBriefRepresentation(realm, identityProviderModel);
|
||||||
|
|
||||||
providerRep.setLinkOnly(identityProviderModel.isLinkOnly());
|
providerRep.setLinkOnly(identityProviderModel.isLinkOnly());
|
||||||
|
@ -820,6 +829,10 @@ public class ModelToRepresentation {
|
||||||
providerRep.setPostBrokerLoginFlowAlias(flow.getAlias());
|
providerRep.setPostBrokerLoginFlowAlias(flow.getAlias());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (export) {
|
||||||
|
providerRep.getConfig().remove(OrganizationModel.ORGANIZATION_ATTRIBUTE);
|
||||||
|
}
|
||||||
|
|
||||||
return providerRep;
|
return providerRep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -137,13 +137,7 @@ public class RepresentationToModel {
|
||||||
|
|
||||||
|
|
||||||
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
public static void importRealm(KeycloakSession session, RealmRepresentation rep, RealmModel newRealm, boolean skipUserDependent) {
|
||||||
KeycloakContext context = session.getContext();
|
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, skipUserDependent);
|
||||||
try {
|
|
||||||
context.setRealm(newRealm);
|
|
||||||
session.getProvider(DatastoreProvider.class).getExportImportManager().importRealm(rep, newRealm, skipUserDependent);
|
|
||||||
} finally {
|
|
||||||
context.setRealm(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
|
public static void importRoles(RolesRepresentation realmRoles, RealmModel realm) {
|
||||||
|
|
|
@ -1115,7 +1115,7 @@ public class RealmAdminResource {
|
||||||
auth.realm().requireManageRealm();
|
auth.realm().requireManageRealm();
|
||||||
try {
|
try {
|
||||||
return Response.ok(
|
return Response.ok(
|
||||||
KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), kcSession -> {
|
KeycloakModelUtils.runJobInTransactionWithResult(session.getKeycloakSessionFactory(), session.getContext(), kcSession -> {
|
||||||
RealmModel realmClone = kcSession.realms().getRealm(realm.getId());
|
RealmModel realmClone = kcSession.realms().getRealm(realm.getId());
|
||||||
AdminEventBuilder adminEventClone = adminEvent.clone(kcSession);
|
AdminEventBuilder adminEventClone = adminEvent.clone(kcSession);
|
||||||
// calling a static method to avoid using the wrong instances
|
// 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
|
// 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
|
// 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
|
// 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();
|
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