keycloak-aplcache

RHSSO-402 need a way to dump configuration (including ldap

4/26/2017 9:27:38 AM

Details

diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
index ed7e071..cba7eb3 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/RealmResource.java
@@ -163,8 +163,13 @@ public interface RealmResource {
     @POST
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    public Response partialImport(PartialImportRepresentation rep);
+    Response partialImport(PartialImportRepresentation rep);
 
+    @Path("partial-export")
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
+                                             @QueryParam("exportClients") Boolean exportClients);
     @Path("authentication")
     @Consumes(MediaType.APPLICATION_JSON)
     AuthenticationManagementResource flows();
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
index 98c1ce3..1d41f0d 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/ComponentUtil.java
@@ -54,7 +54,7 @@ public class ComponentUtil {
         return getComponentFactory(session, component.getProviderType(), component.getProviderId());
     }
 
-    private static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
+    public static Map<String, ProviderConfigProperty> getComponentConfigProperties(KeycloakSession session, String providerType, String providerId) {
         try {
             ComponentFactory componentFactory = getComponentFactory(session, providerType, providerId);
             List<ProviderConfigProperty> l = componentFactory.getConfigProperties();
diff --git a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
index 26f5adc..5bdd018 100644
--- a/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
+++ b/server-spi-private/src/main/java/org/keycloak/models/utils/StripSecretsUtils.java
@@ -17,15 +17,18 @@
 
 package org.keycloak.models.utils;
 
+import org.keycloak.common.util.MultivaluedHashMap;
 import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
 import org.keycloak.provider.ProviderConfigProperty;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentExportRepresentation;
 import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.IdentityProviderRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
 
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -70,4 +73,86 @@ public class StripSecretsUtils {
         return rep;
     }
 
+    public static RealmRepresentation stripForExport(KeycloakSession session, RealmRepresentation rep) {
+        strip(rep);
+
+        List<ClientRepresentation> clients = rep.getClients();
+        if (clients != null) {
+            for (ClientRepresentation c : clients) {
+                strip(c);
+            }
+        }
+        List<IdentityProviderRepresentation> providers = rep.getIdentityProviders();
+        if (providers != null) {
+            for (IdentityProviderRepresentation r : providers) {
+                strip(r);
+            }
+        }
+
+        MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents();
+        if (components != null) {
+            for (Map.Entry<String, List<ComponentExportRepresentation>> ent : components.entrySet()) {
+                for (ComponentExportRepresentation c : ent.getValue()) {
+                    strip(session, ent.getKey(), c);
+                }
+            }
+        }
+
+        List<UserRepresentation> users = rep.getUsers();
+        if (users != null) {
+            for (UserRepresentation u: users) {
+                strip(u);
+            }
+        }
+
+        users = rep.getFederatedUsers();
+        if (users != null) {
+            for (UserRepresentation u: users) {
+                strip(u);
+            }
+        }
+
+        return rep;
+    }
+
+    public static UserRepresentation strip(UserRepresentation user) {
+        user.setCredentials(null);
+        return user;
+    }
+
+    public static ClientRepresentation strip(ClientRepresentation rep) {
+        if (rep.getSecret() != null) {
+            rep.setSecret(ComponentRepresentation.SECRET_VALUE);
+        }
+        return rep;
+    }
+
+    public static ComponentExportRepresentation strip(KeycloakSession session, String providerType, ComponentExportRepresentation rep) {
+        Map<String, ProviderConfigProperty> configProperties = ComponentUtil.getComponentConfigProperties(session, providerType, rep.getProviderId());
+        if (rep.getConfig() == null) {
+            return rep;
+        }
+
+        Iterator<Map.Entry<String, List<String>>> itr = rep.getConfig().entrySet().iterator();
+        while (itr.hasNext()) {
+            Map.Entry<String, List<String>> next = itr.next();
+            ProviderConfigProperty configProperty = configProperties.get(next.getKey());
+            if (configProperty != null) {
+                if (configProperty.isSecret()) {
+                    next.setValue(Collections.singletonList(ComponentRepresentation.SECRET_VALUE));
+                }
+            } else {
+                itr.remove();
+            }
+        }
+
+        MultivaluedHashMap<String, ComponentExportRepresentation> sub = rep.getSubComponents();
+        for (Map.Entry<String, List<ComponentExportRepresentation>> ent: sub.entrySet()) {
+            for (ComponentExportRepresentation c: ent.getValue()) {
+                strip(session, ent.getKey(), c);
+            }
+        }
+        return rep;
+    }
+
 }
\ No newline at end of file
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java b/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java
new file mode 100644
index 0000000..b23f756
--- /dev/null
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportOptions.java
@@ -0,0 +1,44 @@
+package org.keycloak.exportimport.util;
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class ExportOptions {
+
+    private boolean usersIncluded = true;
+    private boolean clientsIncluded = true;
+    private boolean groupsAndRolesIncluded = true;
+
+    public ExportOptions() {
+    }
+
+    public ExportOptions(boolean users, boolean clients, boolean groupsAndRoles) {
+        usersIncluded = users;
+        clientsIncluded = clients;
+        groupsAndRolesIncluded = groupsAndRoles;
+    }
+
+    public boolean isUsersIncluded() {
+        return usersIncluded;
+    }
+
+    public boolean isClientsIncluded() {
+        return clientsIncluded;
+    }
+
+    public boolean isGroupsAndRolesIncluded() {
+        return groupsAndRolesIncluded;
+    }
+
+    public void setUsersIncluded(boolean value) {
+        usersIncluded = value;
+    }
+
+    public void setClientsIncluded(boolean value) {
+        clientsIncluded = value;
+    }
+
+    public void setGroupsAndRolesIncluded(boolean value) {
+        groupsAndRolesIncluded = value;
+    }
+}
diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 51ed405..ce3d694 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -84,7 +85,17 @@ import com.fasterxml.jackson.databind.SerializationFeature;
 public class ExportUtils {
 
     public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, boolean includeUsers) {
-        RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, true);
+        ExportOptions opts = new ExportOptions(false, true, true);
+        if (includeUsers) {
+            opts.setUsersIncluded(true);
+        }
+        return exportRealm(session, realm, opts);
+    }
+
+    public static RealmRepresentation exportRealm(KeycloakSession session, RealmModel realm, ExportOptions options) {
+        RealmRepresentation rep = ModelToRepresentation.toRepresentation(realm, false);
+        ModelToRepresentation.exportAuthenticationFlows(realm, rep);
+        ModelToRepresentation.exportRequiredActions(realm, rep);
 
         // Project/product version
         rep.setKeycloakVersion(Version.VERSION);
@@ -99,73 +110,87 @@ public class ExportUtils {
         rep.setClientTemplates(templateReps);
 
         // Clients
-        List<ClientModel> clients = realm.getClients();
-        List<ClientRepresentation> clientReps = new ArrayList<>();
-        for (ClientModel app : clients) {
-            ClientRepresentation clientRep = exportClient(session, app);
-            clientReps.add(clientRep);
+        List<ClientModel> clients = Collections.emptyList();
+
+        if (options.isClientsIncluded()) {
+            clients = realm.getClients();
+            List<ClientRepresentation> clientReps = new ArrayList<>();
+            for (ClientModel app : clients) {
+                ClientRepresentation clientRep = exportClient(session, app);
+                clientReps.add(clientRep);
+            }
+            rep.setClients(clientReps);
         }
-        rep.setClients(clientReps);
 
-        // Roles
-        List<RoleRepresentation> realmRoleReps = null;
-        Map<String, List<RoleRepresentation>> clientRolesReps = new HashMap<>();
+        // Groups and Roles
+        if (options.isGroupsAndRolesIncluded()) {
+            ModelToRepresentation.exportGroups(realm, rep);
 
-        Set<RoleModel> realmRoles = realm.getRoles();
-        if (realmRoles != null && realmRoles.size() > 0) {
-            realmRoleReps = exportRoles(realmRoles);
-        }
-        for (ClientModel client : clients) {
-            Set<RoleModel> currentAppRoles = client.getRoles();
-            List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles);
-            clientRolesReps.put(client.getClientId(), currentAppRoleReps);
-        }
+            List<RoleRepresentation> realmRoleReps = null;
+            Map<String, List<RoleRepresentation>> clientRolesReps = new HashMap<>();
 
-        RolesRepresentation rolesRep = new RolesRepresentation();
-        if (realmRoleReps != null) {
-            rolesRep.setRealm(realmRoleReps);
-        }
-        if (clientRolesReps.size() > 0) {
-            rolesRep.setClient(clientRolesReps);
+            Set<RoleModel> realmRoles = realm.getRoles();
+            if (realmRoles != null && realmRoles.size() > 0) {
+                realmRoleReps = exportRoles(realmRoles);
+            }
+
+            RolesRepresentation rolesRep = new RolesRepresentation();
+            if (realmRoleReps != null) {
+                rolesRep.setRealm(realmRoleReps);
+            }
+
+            if (options.isClientsIncluded()) {
+                for (ClientModel client : clients) {
+                    Set<RoleModel> currentAppRoles = client.getRoles();
+                    List<RoleRepresentation> currentAppRoleReps = exportRoles(currentAppRoles);
+                    clientRolesReps.put(client.getClientId(), currentAppRoleReps);
+                }
+                if (clientRolesReps.size() > 0) {
+                    rolesRep.setClient(clientRolesReps);
+                }
+            }
+            rep.setRoles(rolesRep);
         }
-        rep.setRoles(rolesRep);
 
         // Scopes
-        List<ClientModel> allClients = new ArrayList<>(clients);
         Map<String, List<ScopeMappingRepresentation>> clientScopeReps = new HashMap<>();
 
-        // Scopes of clients
-        for (ClientModel client : allClients) {
-            Set<RoleModel> clientScopes = client.getScopeMappings();
-            ScopeMappingRepresentation scopeMappingRep = null;
-            for (RoleModel scope : clientScopes) {
-                if (scope.getContainer() instanceof RealmModel) {
-                    if (scopeMappingRep == null) {
-                        scopeMappingRep = rep.clientScopeMapping(client.getClientId());
-                    }
-                    scopeMappingRep.role(scope.getName());
-                } else {
-                    ClientModel app = (ClientModel)scope.getContainer();
-                    String appName = app.getClientId();
-                    List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName);
-                    if (currentAppScopes == null) {
-                        currentAppScopes = new ArrayList<>();
-                        clientScopeReps.put(appName, currentAppScopes);
-                    }
+        if (options.isClientsIncluded()) {
+            List<ClientModel> allClients = new ArrayList<>(clients);
+
+            // Scopes of clients
+            for (ClientModel client : allClients) {
+                Set<RoleModel> clientScopes = client.getScopeMappings();
+                ScopeMappingRepresentation scopeMappingRep = null;
+                for (RoleModel scope : clientScopes) {
+                    if (scope.getContainer() instanceof RealmModel) {
+                        if (scopeMappingRep == null) {
+                            scopeMappingRep = rep.clientScopeMapping(client.getClientId());
+                        }
+                        scopeMappingRep.role(scope.getName());
+                    } else {
+                        ClientModel app = (ClientModel) scope.getContainer();
+                        String appName = app.getClientId();
+                        List<ScopeMappingRepresentation> currentAppScopes = clientScopeReps.get(appName);
+                        if (currentAppScopes == null) {
+                            currentAppScopes = new ArrayList<>();
+                            clientScopeReps.put(appName, currentAppScopes);
+                        }
 
-                    ScopeMappingRepresentation currentClientScope = null;
-                    for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
-                        if (client.getClientId().equals(scopeMapping.getClient())) {
-                            currentClientScope = scopeMapping;
-                            break;
+                        ScopeMappingRepresentation currentClientScope = null;
+                        for (ScopeMappingRepresentation scopeMapping : currentAppScopes) {
+                            if (client.getClientId().equals(scopeMapping.getClient())) {
+                                currentClientScope = scopeMapping;
+                                break;
+                            }
                         }
+                        if (currentClientScope == null) {
+                            currentClientScope = new ScopeMappingRepresentation();
+                            currentClientScope.setClient(client.getClientId());
+                            currentAppScopes.add(currentClientScope);
+                        }
+                        currentClientScope.role(scope.getName());
                     }
-                    if (currentClientScope == null) {
-                        currentClientScope = new ScopeMappingRepresentation();
-                        currentClientScope.setClient(client.getClientId());
-                        currentAppScopes.add(currentClientScope);
-                    }
-                    currentClientScope.role(scope.getName());
                 }
             }
         }
@@ -211,11 +236,11 @@ public class ExportUtils {
         }
 
         // Finally users if needed
-        if (includeUsers) {
+        if (options.isUsersIncluded()) {
             List<UserModel> allUsers = session.users().getUsers(realm, true);
             List<UserRepresentation> users = new LinkedList<>();
             for (UserModel user : allUsers) {
-                UserRepresentation userRep = exportUser(session, realm, user);
+                UserRepresentation userRep = exportUser(session, realm, user, options);
                 users.add(userRep);
             }
 
@@ -225,7 +250,7 @@ public class ExportUtils {
 
             List<UserRepresentation> federatedUsers = new LinkedList<>();
             for (String userId : session.userFederatedStorage().getStoredUsers(realm, 0, -1)) {
-                UserRepresentation userRep = exportFederatedUser(session, realm, userId);
+                UserRepresentation userRep = exportFederatedUser(session, realm, userId, options);
                 federatedUsers.add(userRep);
             }
             if (federatedUsers.size() > 0) {
@@ -462,7 +487,7 @@ public class ExportUtils {
      * @param user
      * @return fully exported user representation
      */
-    public static UserRepresentation exportUser(KeycloakSession session, RealmModel realm, UserModel user) {
+    public static UserRepresentation exportUser(KeycloakSession session, RealmModel realm, UserModel user, ExportOptions options) {
         UserRepresentation userRep = ModelToRepresentation.toRepresentation(session, realm, user);
 
         // Social links
@@ -533,12 +558,13 @@ public class ExportUtils {
             }
         }
 
-        List<String> groups = new LinkedList<>();
-        for (GroupModel group : user.getGroups()) {
-            groups.add(ModelToRepresentation.buildGroupPath(group));
+        if (options.isGroupsAndRolesIncluded()) {
+            List<String> groups = new LinkedList<>();
+            for (GroupModel group : user.getGroups()) {
+                groups.add(ModelToRepresentation.buildGroupPath(group));
+            }
+            userRep.setGroups(groups);
         }
-        userRep.setGroups(groups);
-
         return userRep;
     }
 
@@ -569,6 +595,10 @@ public class ExportUtils {
     // Streaming API
 
     public static void exportUsersToStream(KeycloakSession session, RealmModel realm, List<UserModel> usersToExport, ObjectMapper mapper, OutputStream os) throws IOException {
+        exportUsersToStream(session, realm, usersToExport, mapper, os, new ExportOptions());
+    }
+
+    public static void exportUsersToStream(KeycloakSession session, RealmModel realm, List<UserModel> usersToExport, ObjectMapper mapper, OutputStream os, ExportOptions options) throws IOException {
         JsonFactory factory = mapper.getFactory();
         JsonGenerator generator = factory.createGenerator(os, JsonEncoding.UTF8);
         try {
@@ -582,7 +612,7 @@ public class ExportUtils {
             generator.writeStartArray();
 
             for (UserModel user : usersToExport) {
-                UserRepresentation userRep = ExportUtils.exportUser(session, realm, user);
+                UserRepresentation userRep = ExportUtils.exportUser(session, realm, user, options);
                 generator.writeObject(userRep);
             }
 
@@ -594,6 +624,10 @@ public class ExportUtils {
     }
 
     public static void exportFederatedUsersToStream(KeycloakSession session, RealmModel realm, List<String> usersToExport, ObjectMapper mapper, OutputStream os) throws IOException {
+        exportFederatedUsersToStream(session, realm, usersToExport, mapper, os, new ExportOptions());
+    }
+
+    public static void exportFederatedUsersToStream(KeycloakSession session, RealmModel realm, List<String> usersToExport, ObjectMapper mapper, OutputStream os, ExportOptions options) throws IOException {
         JsonFactory factory = mapper.getFactory();
         JsonGenerator generator = factory.createGenerator(os, JsonEncoding.UTF8);
         try {
@@ -607,7 +641,7 @@ public class ExportUtils {
             generator.writeStartArray();
 
             for (String userId : usersToExport) {
-                UserRepresentation userRep = ExportUtils.exportFederatedUser(session, realm, userId);
+                UserRepresentation userRep = ExportUtils.exportFederatedUser(session, realm, userId, options);
                 generator.writeObject(userRep);
             }
 
@@ -624,7 +658,7 @@ public class ExportUtils {
      * @param id
      * @return fully exported user representation
      */
-    public static UserRepresentation exportFederatedUser(KeycloakSession session, RealmModel realm, String id) {
+    public static UserRepresentation exportFederatedUser(KeycloakSession session, RealmModel realm, String id, ExportOptions options) {
         UserRepresentation userRep = new UserRepresentation();
         userRep.setId(id);
         MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, id);
@@ -654,30 +688,32 @@ public class ExportUtils {
         }
 
         // Role mappings
-        Set<RoleModel> roles = session.userFederatedStorage().getRoleMappings(realm, id);
-        List<String> realmRoleNames = new ArrayList<>();
-        Map<String, List<String>> clientRoleNames = new HashMap<>();
-        for (RoleModel role : roles) {
-            if (role.getContainer() instanceof RealmModel) {
-                realmRoleNames.add(role.getName());
-            } else {
-                ClientModel client = (ClientModel)role.getContainer();
-                String clientId = client.getClientId();
-                List<String> currentClientRoles = clientRoleNames.get(clientId);
-                if (currentClientRoles == null) {
-                    currentClientRoles = new ArrayList<>();
-                    clientRoleNames.put(clientId, currentClientRoles);
-                }
+        if (options.isGroupsAndRolesIncluded()) {
+            Set<RoleModel> roles = session.userFederatedStorage().getRoleMappings(realm, id);
+            List<String> realmRoleNames = new ArrayList<>();
+            Map<String, List<String>> clientRoleNames = new HashMap<>();
+            for (RoleModel role : roles) {
+                if (role.getContainer() instanceof RealmModel) {
+                    realmRoleNames.add(role.getName());
+                } else {
+                    ClientModel client = (ClientModel) role.getContainer();
+                    String clientId = client.getClientId();
+                    List<String> currentClientRoles = clientRoleNames.get(clientId);
+                    if (currentClientRoles == null) {
+                        currentClientRoles = new ArrayList<>();
+                        clientRoleNames.put(clientId, currentClientRoles);
+                    }
 
-                currentClientRoles.add(role.getName());
+                    currentClientRoles.add(role.getName());
+                }
             }
-        }
 
-        if (realmRoleNames.size() > 0) {
-            userRep.setRealmRoles(realmRoleNames);
-        }
-        if (clientRoleNames.size() > 0) {
-            userRep.setClientRoles(clientRoleNames);
+            if (realmRoleNames.size() > 0) {
+                userRep.setRealmRoles(realmRoleNames);
+            }
+            if (clientRoleNames.size() > 0) {
+                userRep.setClientRoles(clientRoleNames);
+            }
         }
 
         // Credentials
@@ -700,13 +736,13 @@ public class ExportUtils {
             userRep.setClientConsents(consentReps);
         }
 
-
-        List<String> groups = new LinkedList<>();
-        for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
-            groups.add(ModelToRepresentation.buildGroupPath(group));
+        if (options.isGroupsAndRolesIncluded()) {
+            List<String> groups = new LinkedList<>();
+            for (GroupModel group : session.userFederatedStorage().getGroups(realm, id)) {
+                groups.add(ModelToRepresentation.buildGroupPath(group));
+            }
+            userRep.setGroups(groups);
         }
-        userRep.setGroups(groups);
-
         return userRep;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 146ef86..dffa4f8 100644
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -36,6 +36,8 @@ import org.keycloak.events.admin.OperationType;
 import org.keycloak.events.admin.ResourceType;
 import org.keycloak.exportimport.ClientDescriptionConverter;
 import org.keycloak.exportimport.ClientDescriptionConverterFactory;
+import org.keycloak.exportimport.util.ExportOptions;
+import org.keycloak.exportimport.util.ExportUtils;
 import org.keycloak.keys.PublicKeyStorageProvider;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.Constants;
@@ -58,7 +60,6 @@ import org.keycloak.representations.adapters.action.GlobalRequestResult;
 import org.keycloak.representations.idm.AdminEventRepresentation;
 import org.keycloak.representations.idm.ClientRepresentation;
 import org.keycloak.representations.idm.ComponentRepresentation;
-import org.keycloak.representations.idm.CredentialRepresentation;
 import org.keycloak.representations.idm.EventRepresentation;
 import org.keycloak.representations.idm.GroupRepresentation;
 import org.keycloak.representations.idm.PartialImportRepresentation;
@@ -99,6 +100,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.regex.PatternSyntaxException;
 
+import static org.keycloak.models.utils.StripSecretsUtils.stripForExport;
+
 /**
  * Base resource class for the admin REST api of one realm
  *
@@ -857,6 +860,27 @@ public class RealmAdminResource {
     }
 
     /**
+     * Partial export of existing realm into a JSON file.
+     *
+     * @param exportGroupsAndRoles
+     * @param exportClients
+     * @return
+     */
+    @Path("partial-export")
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    public RealmRepresentation partialExport(@QueryParam("exportGroupsAndRoles") Boolean exportGroupsAndRoles,
+                                                     @QueryParam("exportClients") Boolean exportClients) {
+
+        boolean groupsAndRolesExported = exportGroupsAndRoles != null && exportGroupsAndRoles;
+        boolean clientsExported = exportClients != null && exportClients;
+
+        ExportOptions options = new ExportOptions(false, clientsExported, groupsAndRolesExported);
+        RealmRepresentation rep = ExportUtils.exportRealm(session, realm, options);
+        return stripForExport(session, rep);
+    }
+
+    /**
      * Clear realm cache
      *
      */
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java
new file mode 100644
index 0000000..ad9fbc7
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialexport/PartialExportTest.java
@@ -0,0 +1,272 @@
+package org.keycloak.testsuite.admin.partialexport;
+
+import org.junit.Test;
+import org.keycloak.common.util.MultivaluedHashMap;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.ComponentExportRepresentation;
+import org.keycloak.representations.idm.ComponentRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
+import org.keycloak.representations.idm.ScopeMappingRepresentation;
+import org.keycloak.testsuite.Assert;
+import org.keycloak.testsuite.admin.AbstractAdminTest;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
+ */
+public class PartialExportTest extends AbstractAdminTest {
+
+    private static final String EXPORT_TEST_REALM = "partial-export-test";
+
+    @Override
+    public void addTestRealms(List<RealmRepresentation> testRealms) {
+        super.addTestRealms(testRealms);
+
+        RealmRepresentation realmRepresentation = loadJson(getClass().getResourceAsStream("/export/partialexport-testrealm.json"), RealmRepresentation.class);
+        testRealms.add(realmRepresentation);
+    }
+
+    @Test
+    public void testExport() {
+
+        // exportGroupsAndRoles == false, exportClients == false
+        RealmRepresentation rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(false, false);
+        Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+        Assert.assertNull("Groups are empty", rep.getGroups());
+
+        Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+        checkDefaultRoles(rep.getDefaultRoles());
+
+        Assert.assertNull("Realm and client roles are empty", rep.getRoles());
+        Assert.assertNull("Clients are empty", rep.getClients());
+
+        Assert.assertNull("Scope mappings empty", rep.getScopeMappings());
+        Assert.assertNull("Client scope mappings empty", rep.getClientScopeMappings());
+
+
+        // exportGroupsAndRoles == true, exportClients == false
+        rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(true, false);
+        Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+        Assert.assertNotNull("Groups not empty", rep.getGroups());
+        checkGroups(rep.getGroups());
+
+        Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+        checkDefaultRoles(rep.getDefaultRoles());
+
+        Assert.assertNotNull("Realm and client roles not empty", rep.getRoles());
+        Assert.assertNotNull("Realm roles not empty", rep.getRoles().getRealm());
+        checkRealmRoles(rep.getRoles().getRealm());
+
+        Assert.assertNull("Client roles are empty", rep.getRoles().getClient());
+        Assert.assertNull("Clients are empty", rep.getClients());
+
+        Assert.assertNull("Scope mappings empty", rep.getScopeMappings());
+        Assert.assertNull("Client scope mappings empty", rep.getClientScopeMappings());
+
+
+        // exportGroupsAndRoles == false, exportClients == true
+        rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(false, true);
+        Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+        Assert.assertNull("Groups are empty", rep.getGroups());
+        Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+        checkDefaultRoles(rep.getDefaultRoles());
+
+        Assert.assertNull("Realm and client roles are empty", rep.getRoles());
+        Assert.assertNotNull("Clients not empty", rep.getClients());
+        checkClients(rep.getClients());
+
+        checkScopeMappings(rep.getScopeMappings());
+        checkClientScopeMappings(rep.getClientScopeMappings());
+
+
+        // exportGroupsAndRoles == true, exportClients == true
+        rep = adminClient.realm(EXPORT_TEST_REALM).partialExport(true, true);
+        Assert.assertNull("Default groups are empty", rep.getDefaultGroups());
+        Assert.assertNotNull("Groups not empty", rep.getGroups());
+        checkGroups(rep.getGroups());
+
+        Assert.assertNotNull("Default roles not empty", rep.getDefaultRoles());
+        checkDefaultRoles(rep.getDefaultRoles());
+
+        Assert.assertNotNull("Realm and client roles not empty", rep.getRoles());
+        Assert.assertNotNull("Realm roles not empty", rep.getRoles().getRealm());
+        checkRealmRoles(rep.getRoles().getRealm());
+
+        Assert.assertNotNull("Client roles not empty", rep.getRoles().getClient());
+        checkClientRoles(rep.getRoles().getClient());
+
+        Assert.assertNotNull("Clients not empty", rep.getClients());
+        checkClients(rep.getClients());
+
+        checkScopeMappings(rep.getScopeMappings());
+        checkClientScopeMappings(rep.getClientScopeMappings());
+
+
+        // check that secrets are masked
+        checkSecretsAreMasked(rep);
+    }
+
+    private void checkSecretsAreMasked(RealmRepresentation rep) {
+
+        // Client secret
+        for (ClientRepresentation client: rep.getClients()) {
+            Assert.assertEquals("Client secret masked", ComponentRepresentation.SECRET_VALUE, client.getSecret());
+        }
+
+        // IdentityProvider clientSecret
+        for (IdentityProviderRepresentation idp: rep.getIdentityProviders()) {
+            Assert.assertEquals("IdentityProvider clientSecret masked", ComponentRepresentation.SECRET_VALUE, idp.getConfig().get("clientSecret"));
+        }
+
+        // smtpServer password
+        Assert.assertEquals("SMTP password masked", ComponentRepresentation.SECRET_VALUE, rep.getSmtpServer().get("password"));
+
+        // components rsa KeyProvider privateKey
+        MultivaluedHashMap<String, ComponentExportRepresentation> components = rep.getComponents();
+
+        List<ComponentExportRepresentation> keys = components.get("org.keycloak.keys.KeyProvider");
+        Assert.assertNotNull("Keys not null", keys);
+        Assert.assertTrue("At least one key returned", keys.size() > 0);
+        boolean found = false;
+        for (ComponentExportRepresentation component: keys) {
+            if ("rsa".equals(component.getProviderId())) {
+                Assert.assertEquals("RSA KeyProvider privateKey masked", ComponentRepresentation.SECRET_VALUE, component.getConfig().getFirst("privateKey"));
+                found = true;
+            }
+        }
+        Assert.assertTrue("Found rsa private key", found);
+
+        // components ldap UserStorageProvider bindCredential
+        List<ComponentExportRepresentation> userStorage = components.get("org.keycloak.storage.UserStorageProvider");
+        Assert.assertNotNull("UserStorageProvider not null", userStorage);
+        Assert.assertTrue("At least one UserStorageProvider returned", userStorage.size() > 0);
+        found = false;
+        for (ComponentExportRepresentation component: userStorage) {
+            if ("ldap".equals(component.getProviderId())) {
+                Assert.assertEquals("LDAP provider bindCredential masked", ComponentRepresentation.SECRET_VALUE, component.getConfig().getFirst("bindCredential"));
+                found = true;
+            }
+        }
+        Assert.assertTrue("Found ldap bindCredential", found);
+    }
+
+    private void checkClientScopeMappings(Map<String, List<ScopeMappingRepresentation>> mappings) {
+        Map<String, Set<String>> map = extractScopeMappings(mappings.get("test-app"));
+        Set<String> set = map.get("test-app-scope");
+        Assert.assertTrue("Client test-app / test-app-scope contains customer-admin-composite-role", set.contains("customer-admin-composite-role"));
+
+        set = map.get("third-party");
+        Assert.assertTrue("Client test-app / third-party contains customer-user", set.contains("customer-user"));
+
+        map = extractScopeMappings(mappings.get("test-app-scope"));
+        set = map.get("test-app-scope");
+        Assert.assertTrue("Client test-app-scope / test-app-scope contains test-app-allowed-by-scope", set.contains("test-app-allowed-by-scope"));
+    }
+
+    private void checkScopeMappings(List<ScopeMappingRepresentation> scopeMappings) {
+        Map<String, Set<String>> map = extractScopeMappings(scopeMappings);
+
+        Set<String> set = map.get("test-app");
+        Assert.assertTrue("Client test-app contains user", set.contains("user"));
+
+        set = map.get("test-app-scope");
+        Assert.assertTrue("Client test-app contains user", set.contains("user"));
+        Assert.assertTrue("Client test-app contains admin", set.contains("admin"));
+
+        set = map.get("third-party");
+        Assert.assertTrue("Client test-app contains third-party", set.contains("user"));
+    }
+
+    private Map<String, Set<String>> extractScopeMappings(List<ScopeMappingRepresentation> scopeMappings) {
+        Map<String, Set<String>> map = new HashMap<>();
+        for (ScopeMappingRepresentation r: scopeMappings) {
+            map.put(r.getClient(), r.getRoles());
+        }
+        return map;
+    }
+
+    private void checkClientRoles(Map<String, List<RoleRepresentation>> clientRoles) {
+        Map<String, RoleRepresentation> roles = collectRoles(clientRoles.get("test-app"));
+        Assert.assertTrue("Client role customer-admin for test-app", roles.containsKey("customer-admin"));
+        Assert.assertTrue("Client role sample-client-role for test-app", roles.containsKey("sample-client-role"));
+        Assert.assertTrue("Client role customer-user for test-app", roles.containsKey("customer-user"));
+
+        Assert.assertTrue("Client role customer-admin-composite-role for test-app", roles.containsKey("customer-admin-composite-role"));
+        RoleRepresentation.Composites cmp = roles.get("customer-admin-composite-role").getComposites();
+        Assert.assertTrue("customer-admin-composite-role / realm / customer-user-premium", cmp.getRealm().contains("customer-user-premium"));
+        Assert.assertTrue("customer-admin-composite-role / client['test-app'] / customer-admin", cmp.getClient().get("test-app").contains("customer-admin"));
+
+
+        roles = collectRoles(clientRoles.get("test-app-scope"));
+        Assert.assertTrue("Client role test-app-disallowed-by-scope for test-app-scope", roles.containsKey("test-app-disallowed-by-scope"));
+        Assert.assertTrue("Client role test-app-allowed-by-scope for test-app-scope", roles.containsKey("test-app-allowed-by-scope"));
+    }
+
+    private Map<String, RoleRepresentation> collectRoles(List<RoleRepresentation> roles) {
+        HashMap<String, RoleRepresentation> map = new HashMap<>();
+        if (roles == null) {
+            return map;
+        }
+        for (RoleRepresentation r: roles) {
+            map.put(r.getName(), r);
+        }
+        return map;
+    }
+
+    private void checkClients(List<ClientRepresentation> clients) {
+        HashSet<String> set = new HashSet<>();
+        for (ClientRepresentation c: clients) {
+            set.add(c.getClientId());
+        }
+        Assert.assertTrue("Client test-app", set.contains("test-app"));
+        Assert.assertTrue("Client test-app-scope", set.contains("test-app-scope"));
+        Assert.assertTrue("Client third-party", set.contains("third-party"));
+    }
+
+    private void checkRealmRoles(List<RoleRepresentation> realmRoles) {
+        Set<String> set = new HashSet<>();
+        for (RoleRepresentation r: realmRoles) {
+            set.add(r.getName());
+        }
+        Assert.assertTrue("Role sample-realm-role", set.contains("sample-realm-role"));
+        Assert.assertTrue("Role realm-composite-role", set.contains("realm-composite-role"));
+        Assert.assertTrue("Role customer-user-premium", set.contains("customer-user-premium"));
+        Assert.assertTrue("Role admin", set.contains("admin"));
+        Assert.assertTrue("Role user", set.contains("user"));
+    }
+
+    private void checkGroups(List<GroupRepresentation> groups) {
+        HashSet<String> set = new HashSet<>();
+        for (GroupRepresentation g: groups) {
+            compileGroups(set, g);
+        }
+        Assert.assertTrue("Group /roleRichGroup", set.contains("/roleRichGroup"));
+        Assert.assertTrue("Group /roleRichGroup/level2group", set.contains("/roleRichGroup/level2group"));
+        Assert.assertTrue("Group /topGroup", set.contains("/topGroup"));
+        Assert.assertTrue("Group /topGroup/level2group", set.contains("/topGroup/level2group"));
+    }
+
+    private void compileGroups(Set<String> found, GroupRepresentation g) {
+        found.add(g.getPath());
+        if (g.getSubGroups() != null) {
+            for (GroupRepresentation s: g.getSubGroups()) {
+                compileGroups(found, s);
+            }
+        }
+    }
+    private void checkDefaultRoles(List<String> defaultRoles) {
+        HashSet<String> roles = new HashSet<>(defaultRoles);
+        Assert.assertTrue("Default role 'uma_authorization'", roles.contains("uma_authorization"));
+        Assert.assertTrue("Default role 'offline_access'", roles.contains("offline_access"));
+        Assert.assertTrue("Default role 'user'", roles.contains("user"));
+    }
+}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json
new file mode 100644
index 0000000..933a170
--- /dev/null
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/export/partialexport-testrealm.json
@@ -0,0 +1,989 @@
+{
+  "id": "partial-export-test",
+  "realm": "partial-export-test",
+
+  "roles": {
+    "realm": [
+      {
+        "name": "sample-realm-role",
+        "description": "Sample realm role",
+        "scopeParamRequired": false,
+        "composite": false,
+        "clientRole": false,
+        "containerId": "test"
+      },
+      {
+        "name": "realm-composite-role",
+        "description": "Realm composite role containing client role",
+        "scopeParamRequired": false,
+        "composite": true,
+        "composites": {
+          "realm": [
+            "sample-realm-role"
+          ],
+          "client": {
+            "test-app": [
+              "sample-client-role"
+            ],
+            "account": [
+              "view-profile"
+            ]
+          }
+        },
+        "clientRole": false,
+        "containerId": "test"
+      },
+      {
+        "name": "customer-user-premium",
+        "description": "Have User Premium privileges",
+        "scopeParamRequired": false,
+        "composite": false,
+        "clientRole": false,
+        "containerId": "test"
+      },
+      {
+        "name": "admin",
+        "description": "Have Administrator privileges",
+        "scopeParamRequired": false,
+        "composite": false,
+        "clientRole": false,
+        "containerId": "test"
+      },
+      {
+        "name": "user",
+        "description": "Have User privileges",
+        "scopeParamRequired": false,
+        "composite": false,
+        "clientRole": false,
+        "containerId": "test"
+      }
+    ],
+    "client": {
+      "test-app": [
+        {
+          "name": "customer-admin",
+          "description": "Have Customer Admin privileges",
+          "scopeParamRequired": false,
+          "composite": false,
+          "clientRole": true,
+          "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+        },
+        {
+          "name": "sample-client-role",
+          "description": "Sample client role",
+          "scopeParamRequired": false,
+          "composite": false,
+          "clientRole": true,
+          "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+        },
+        {
+          "name": "customer-admin-composite-role",
+          "description": "Have Customer Admin privileges via composite role",
+          "scopeParamRequired": false,
+          "composite": true,
+          "composites": {
+            "realm": [
+              "customer-user-premium"
+            ],
+            "client": {
+              "test-app": [
+                "customer-admin"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+        },
+        {
+          "name": "customer-user",
+          "description": "Have Customer User privileges",
+          "scopeParamRequired": false,
+          "composite": false,
+          "clientRole": true,
+          "containerId": "c1a37c9e-6ba4-4d77-988d-ab11462d5668"
+        }
+      ],
+      "test-app-scope": [
+        {
+          "name": "test-app-disallowed-by-scope",
+          "description": "Role disallowed by scope in test-app-scope",
+          "scopeParamRequired": false,
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f3ff0b0d-e922-4874-a34c-cdfa1b3305fe"
+        },
+        {
+          "name": "test-app-allowed-by-scope",
+          "description": "Role allowed by scope in test-app-scope",
+          "scopeParamRequired": false,
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f3ff0b0d-e922-4874-a34c-cdfa1b3305fe"
+        }
+      ]
+    }
+  },
+  "groups": [
+    {
+      "name": "roleRichGroup",
+      "path": "/roleRichGroup",
+      "attributes": {
+        "topAttribute": [
+          "true"
+        ]
+      },
+      "realmRoles": [
+        "realm-composite-role",
+        "user"
+      ],
+      "clientRoles": {
+        "account": [
+          "manage-account"
+        ]
+      },
+      "subGroups": [
+        {
+          "name": "level2group",
+          "path": "/roleRichGroup/level2group",
+          "attributes": {
+            "level2Attribute": [
+              "true"
+            ]
+          },
+          "realmRoles": [
+            "admin"
+          ],
+          "clientRoles": {
+            "test-app": [
+              "customer-admin-composite-role",
+              "customer-user"
+            ]
+          },
+          "subGroups": []
+        }
+      ]
+    },
+    {
+      "name": "topGroup",
+      "path": "/topGroup",
+      "attributes": {
+        "topAttribute": [
+          "true"
+        ]
+      },
+      "realmRoles": [
+        "user"
+      ],
+      "clientRoles": {},
+      "subGroups": [
+        {
+          "name": "level2group",
+          "path": "/topGroup/level2group",
+          "attributes": {
+            "level2Attribute": [
+              "true"
+            ]
+          },
+          "realmRoles": [
+            "admin"
+          ],
+          "clientRoles": {
+            "test-app": [
+              "customer-user"
+            ]
+          },
+          "subGroups": []
+        }
+      ]
+    }
+  ],
+  "defaultRoles": [
+    "user",
+    "offline_access",
+    "uma_authorization"
+  ],
+  "smtpServer": {
+    "from": "auto@keycloak.org",
+    "host": "localhost",
+    "port": "3025",
+    "user": "user",
+    "password": "secret"
+  },
+  "scopeMappings": [
+    {
+      "client": "test-app",
+      "roles": [
+        "user"
+      ]
+    },
+    {
+      "client": "test-app-scope",
+      "roles": [
+        "admin",
+        "user"
+      ]
+    },
+    {
+      "client": "third-party",
+      "roles": [
+        "user"
+      ]
+    }
+  ],
+  "clientScopeMappings": {
+    "realm-management": [
+      {
+        "client": "admin-cli",
+        "roles": [
+          "realm-admin"
+        ]
+      },
+      {
+        "client": "security-admin-console",
+        "roles": [
+          "realm-admin"
+        ]
+      }
+    ],
+    "test-app": [
+      {
+        "client": "test-app-scope",
+        "roles": [
+          "customer-admin-composite-role"
+        ]
+      },
+      {
+        "client": "third-party",
+        "roles": [
+          "customer-user"
+        ]
+      }
+    ],
+    "test-app-scope": [
+      {
+        "client": "test-app-scope",
+        "roles": [
+          "test-app-allowed-by-scope"
+        ]
+      }
+    ]
+  },
+  "clients": [
+    {
+      "clientId": "test-app",
+      "adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
+      "baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "password",
+      "redirectUris": [
+        "http://localhost:8180/auth/realms/master/app/auth/*"
+      ],
+      "webOrigins": [
+        "http://localhost:8180"
+      ],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "attributes": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        },
+        {
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": true,
+          "consentText": "${fullName}",
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true"
+          }
+        },
+        {
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${email}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${username}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${givenName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${familyName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "useTemplateConfig": false,
+      "useTemplateScope": false,
+      "useTemplateMappers": false
+    },
+    {
+      "clientId": "test-app-scope",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "password",
+      "redirectUris": [
+        "http://localhost:8180/auth/realms/master/app/*"
+      ],
+      "webOrigins": [
+        "http://localhost:8180"
+      ],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "attributes": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${email}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": true,
+          "consentText": "${fullName}",
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true"
+          }
+        },
+        {
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${username}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${familyName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        },
+        {
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${givenName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "useTemplateConfig": false,
+      "useTemplateScope": false,
+      "useTemplateMappers": false
+    },
+    {
+      "clientId": "third-party",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "password",
+      "redirectUris": [
+        "http://localhost:8180/auth/realms/master/app/*"
+      ],
+      "webOrigins": [
+        "http://localhost:8180"
+      ],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": true,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "attributes": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": -1,
+      "protocolMappers": [
+        {
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${familyName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${email}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${username}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": true,
+          "consentText": "${givenName}",
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        },
+        {
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": true,
+          "consentText": "${fullName}",
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true"
+          }
+        }
+      ],
+      "useTemplateConfig": false,
+      "useTemplateScope": false,
+      "useTemplateMappers": false
+    }],
+  "components": {
+    "org.keycloak.keys.KeyProvider": [
+      {
+        "name": "rsa",
+        "providerId": "rsa",
+        "subComponents": {},
+        "config": {
+          "privateKey": [
+            "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="
+          ],
+          "certificate": [
+            "MIIBkTCB+wIGAVufbLMuMA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMMBHRlc3QwHhcNMTcwNDI0MTAwNDEyWhcNMjcwNDI0MTAwNTUyWjAPMQ0wCwYDVQQDDAR0ZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAKKj6Ygftq7iSfvi8G6IoJ4RbknpA0+g+s1fYgmpdHdBEfAfbODmWrNR8GLWQDU0ccnHT0oQDc66ShfluMZ0KAVcfxNJUFP2OYdrGNRJNZbGT9WMcD8LUF8mlACa8uKVfhMU4LssOdEBnW2RpM4xEe1DYPRC+AWoFODb0wsYDwll"
+          ],
+          "priority": [
+            "100"
+          ]
+        }
+      }
+    ],
+    "org.keycloak.storage.UserStorageProvider": [
+      {
+        "name": "ldap-apacheds",
+        "providerId": "ldap",
+        "subComponents": {
+          "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [
+            {
+              "name": "username",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "uid"
+                ],
+                "is.mandatory.in.ldap": [
+                  "true"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "username"
+                ]
+              }
+            },
+            {
+              "name": "first name",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "cn"
+                ],
+                "is.mandatory.in.ldap": [
+                  "true"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "firstName"
+                ]
+              }
+            },
+            {
+              "name": "last name",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "sn"
+                ],
+                "is.mandatory.in.ldap": [
+                  "true"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "lastName"
+                ]
+              }
+            },
+            {
+              "name": "email",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "mail"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "email"
+                ]
+              }
+            },
+            {
+              "name": "creation date",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "createTimestamp"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "read.only": [
+                  "true"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "createTimestamp"
+                ]
+              }
+            },
+            {
+              "name": "modify date",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "modifyTimestamp"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "read.only": [
+                  "true"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "modifyTimestamp"
+                ]
+              }
+            },
+            {
+              "name": "postal code",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "postalCode"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "postal_code"
+                ]
+              }
+            },
+            {
+              "name": "street",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "street"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "false"
+                ],
+                "user.model.attribute": [
+                  "street"
+                ]
+              }
+            },
+            {
+              "name": "picture",
+              "providerId": "user-attribute-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "ldap.attribute": [
+                  "jpegPhoto"
+                ],
+                "is.mandatory.in.ldap": [
+                  "false"
+                ],
+                "is.binary.attribute": [
+                  "true"
+                ],
+                "read.only": [
+                  "false"
+                ],
+                "always.read.value.from.ldap": [
+                  "true"
+                ],
+                "user.model.attribute": [
+                  "picture"
+                ]
+              }
+            },
+            {
+              "name": "realm roles",
+              "providerId": "role-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "mode": [
+                  "LDAP_ONLY"
+                ],
+                "roles.dn": [
+                  "ou=RealmRoles,dc=keycloak,dc=org"
+                ],
+                "membership.ldap.attribute": [
+                  "member"
+                ],
+                "role.name.ldap.attribute": [
+                  "cn"
+                ],
+                "use.realm.roles.mapping": [
+                  "true"
+                ],
+                "role.object.classes": [
+                  "groupOfNames"
+                ]
+              }
+            },
+            {
+              "name": "finance roles",
+              "providerId": "role-ldap-mapper",
+              "subComponents": {
+
+              },
+              "config": {
+                "mode": [
+                  "LDAP_ONLY"
+                ],
+                "roles.dn": [
+                  "ou=FinanceRoles,dc=keycloak,dc=org"
+                ],
+                "membership.ldap.attribute": [
+                  "member"
+                ],
+                "role.name.ldap.attribute": [
+                  "cn"
+                ],
+                "use.realm.roles.mapping": [
+                  "false"
+                ],
+                "role.object.classes": [
+                  "groupOfNames"
+                ],
+                "client.id": [
+                  "finance"
+                ]
+              }
+            }
+          ]
+        },
+        "config": {
+          "fullSyncPeriod": [
+            "-1"
+          ],
+          "pagination": [
+            "true"
+          ],
+          "debug": [
+            "false"
+          ],
+          "searchScope": [
+            "1"
+          ],
+          "connectionPooling": [
+            "true"
+          ],
+          "usersDn": [
+            "ou=People,dc=keycloak,dc=org"
+          ],
+          "priority": [
+            "1"
+          ],
+          "userObjectClasses": [
+            "inetOrgPerson, organizationalPerson"
+          ],
+          "changedSyncPeriod": [
+            "-1"
+          ],
+          "usernameLDAPAttribute": [
+            "uid"
+          ],
+          "bindDn": [
+            "uid=admin,ou=system"
+          ],
+          "bindCredential": [
+            "secret"
+          ],
+          "rdnLDAPAttribute": [
+            "uid"
+          ],
+          "lastSync": [
+            "0"
+          ],
+          "vendor": [
+            "other"
+          ],
+          "editMode": [
+            "WRITABLE"
+          ],
+          "uuidLDAPAttribute": [
+            "entryUUID"
+          ],
+          "connectionUrl": [
+            "ldap://localhost:10389"
+          ],
+          "syncRegistrations": [
+            "true"
+          ],
+          "authType": [
+            "simple"
+          ]
+        }
+      }
+    ]
+  },
+  "identityProviders" : [
+    {
+      "providerId" : "google",
+      "alias" : "google1",
+      "enabled": true,
+      "config": {
+        "clientId": "googleId",
+        "clientSecret": "googleSecret"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index dc4ef93..65a598b 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -671,6 +671,11 @@ skip=Skip
 overwrite=Overwrite
 if-resource-exists.tooltip=Specify what should be done if you try to import a resource that already exists.
 
+partial-export=Partial Export
+partial-export.tooltip=Partial export allows you to export realm configuration, and other associated resources into a json file.
+export-groups-and-roles=Export groups and roles
+export-clients=Export clients
+
 action=Action
 role-selector=Role Selector
 realm-roles.tooltip=Realm roles that can be selected.
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 5f58e88..ad10be3 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -550,6 +550,15 @@ module.config([ '$routeProvider', function($routeProvider) {
             },
             controller : 'RealmImportCtrl'
         })
+        .when('/realms/:realm/partial-export', {
+            templateUrl : resourceUrl + '/partials/partial-export.html',
+            resolve : {
+                realm : function(RealmLoader) {
+                    return RealmLoader();
+                }
+            },
+            controller : 'RealmExportCtrl'
+        })
         .when('/create/user/:realm', {
             templateUrl : resourceUrl + '/partials/user-detail.html',
             resolve : {
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
index bcb0d0c..78101d5 100644
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js
@@ -2709,3 +2709,40 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
     }
 
 });
+
+module.controller('RealmExportCtrl', function($scope, realm, $http,
+                                              $httpParamSerializer, Notifications, Dialog) {
+    $scope.realm = realm;
+    $scope.exportGroupsAndRoles = false;
+    $scope.exportClients = false;
+
+    $scope.export = function() {
+        if ($scope.exportGroupsAndRoles || $scope.exportClients) {
+            Dialog.confirm('Export', 'This operation may make server unresponsive for a while.\n\nAre you sure you want to proceed?', download);
+        } else {
+            download();
+        }
+    }
+
+    function download() {
+        var exportUrl = authUrl + '/admin/realms/' + realm.realm + '/partial-export';
+        var params = {};
+        if ($scope.exportGroupsAndRoles) {
+            params['exportGroupsAndRoles'] = true;
+        }
+        if ($scope.exportClients) {
+            params['exportClients'] = true;
+        }
+        if (Object.keys(params).length > 0) {
+            exportUrl += '?' + $httpParamSerializer(params);
+        }
+        $http.post(exportUrl)
+            .success(function(data, status, headers) {
+                var download = angular.fromJson(data);
+                download = angular.toJson(download, true);
+                saveAs(new Blob([download], { type: 'application/json' }), 'realm-export.json');
+            }).error(function() {
+                Notifications.error("Sorry, something went wrong.");
+            });
+    }
+});
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html b/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html
new file mode 100644
index 0000000..1ceed8c
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/partial-export.html
@@ -0,0 +1,34 @@
+<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
+    <h1>
+        <span>{{:: 'partial-export' | translate}}</span>
+        <kc-tooltip>{{:: 'partial-export.tooltip' | translate}}</kc-tooltip>
+    </h1>
+
+    <form class="form-horizontal" name="partialExportForm" novalidate>
+        <fieldset class="border-top">
+
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="exportGroupsAndRoles">{{:: 'export-groups-and-roles' | translate}}</label>
+                <div class="col-sm-6">
+                    <input ng-model="exportGroupsAndRoles" name="exportGroupsAndRoles" id="exportGroupsAndRoles" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+            
+            <div class="form-group">
+                <label class="col-md-2 control-label" for="exportClients">{{:: 'export-clients' | translate}}</label>
+                <div class="col-sm-6">
+                    <input ng-model="exportClients" name="exportClients" id="exportClients" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
+                </div>
+            </div>
+
+            <div class="col-sm-12">
+                <button class="btn btn-primary" data-ng-click="export()" type="submit">{{:: 'export' | translate}}</button>
+            </div>
+
+        </fieldset>
+
+    </form>
+</div>
+
+<kc-menu></kc-menu>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index 898e1a0..25d22d7 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -56,6 +56,7 @@
                         || path[2] == 'events-settings'
                         || path[2] == 'admin-events') && 'active'"><a href="#/realms/{{realm.realm}}/events"><i class="fa fa-calendar"></i> {{:: 'events' | translate}}</a></li>
             <li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-import') && 'active'"><a href="#/realms/{{realm.realm}}/partial-import"><span class="pficon pficon-import"></span> {{:: 'import' | translate}}</a></li>
+            <li data-ng-show="access.manageRealm" ng-class="(path[2] =='partial-export') && 'active'"><a href="#/realms/{{realm.realm}}/partial-export"><span class="pficon pficon-export"></span> {{:: 'export' | translate}}</a></li>
         </ul>
     </div>
 </div>
\ No newline at end of file