From 0f52768064b00fa796af3795c1577bbaef2d2d31 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Mon, 28 Mar 2016 14:25:05 -0400 Subject: [PATCH] KEYCLOAK-2619: Partial Import doesn't support groups --- .../idm/PartialImportRepresentation.java | 15 +++- .../partialimport/GroupsPartialImport.java | 81 +++++++++++++++++++ .../partialimport/PartialImportManager.java | 9 ++- .../keycloak/partialimport/ResourceType.java | 4 +- .../partialimport/PartialImportTest.java | 44 +++++++++- .../messages/admin-messages_en.properties | 1 + .../admin/resources/js/controllers/realm.js | 19 +++-- .../resources/partials/partial-import.html | 7 ++ 8 files changed, 168 insertions(+), 12 deletions(-) create mode 100644 services/src/main/java/org/keycloak/partialimport/GroupsPartialImport.java diff --git a/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java index a960bee2fb..fab1f56ecc 100644 --- a/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/PartialImportRepresentation.java @@ -21,7 +21,7 @@ import java.util.List; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** - * Used for partial import of users, clients, roles, and identity providers. + * Used for partial import of users, groups, clients, roles, and identity providers. * * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ @@ -32,6 +32,7 @@ public class PartialImportRepresentation { protected Policy policy = Policy.FAIL; protected String ifResourceExists = ""; protected List users; + protected List groups; protected List clients; protected List identityProviders; protected RolesRepresentation roles; @@ -40,6 +41,10 @@ public class PartialImportRepresentation { return (users != null) && !users.isEmpty(); } + public boolean hasGroups() { + return (groups != null) && !groups.isEmpty(); + } + public boolean hasClients() { return (clients != null) && !clients.isEmpty(); } @@ -81,6 +86,14 @@ public class PartialImportRepresentation { return clients; } + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + public void setClients(List clients) { this.clients = clients; } diff --git a/services/src/main/java/org/keycloak/partialimport/GroupsPartialImport.java b/services/src/main/java/org/keycloak/partialimport/GroupsPartialImport.java new file mode 100644 index 0000000000..e9632d6d16 --- /dev/null +++ b/services/src/main/java/org/keycloak/partialimport/GroupsPartialImport.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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.partialimport; + +import java.util.List; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.PartialImportRepresentation; + +/** + * Partial import handler for Groups. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class GroupsPartialImport extends AbstractPartialImport { + + @Override + public List getRepList(PartialImportRepresentation partialImportRep) { + return partialImportRep.getGroups(); + } + + @Override + public String getName(GroupRepresentation group) { + return group.getName(); + } + + private GroupModel findGroupModel(RealmModel realm, GroupRepresentation groupRep) { + return KeycloakModelUtils.findGroupByPath(realm, groupRep.getPath()); + } + + @Override + public String getModelId(RealmModel realm, KeycloakSession session, GroupRepresentation groupRep) { + return findGroupModel(realm, groupRep).getId(); + } + + @Override + public boolean exists(RealmModel realm, KeycloakSession session, GroupRepresentation groupRep) { + return findGroupModel(realm, groupRep) != null; + } + + @Override + public String existsMessage(GroupRepresentation groupRep) { + return "Group '" + groupRep.getPath() + "' already exists"; + } + + @Override + public ResourceType getResourceType() { + return ResourceType.GROUP; + } + + @Override + public void remove(RealmModel realm, KeycloakSession session, GroupRepresentation groupRep) { + GroupModel group = realm.getGroupById(getModelId(realm, session, groupRep)); + realm.removeGroup(group); + } + + @Override + public void create(RealmModel realm, KeycloakSession session, GroupRepresentation groupRep) { + RepresentationToModel.importGroup(realm, null, groupRep); + } + +} diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java index d01d8dd42e..59d6217d15 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java @@ -50,6 +50,7 @@ public class PartialImportManager { partialImports.add(new ClientsPartialImport()); partialImports.add(new RolesPartialImport()); partialImports.add(new IdentityProvidersPartialImport()); + partialImports.add(new GroupsPartialImport()); partialImports.add(new UsersPartialImport()); } @@ -78,8 +79,8 @@ public class PartialImportManager { for (PartialImportResult result : results.getResults()) { switch (result.getAction()) { - case ADDED : addedEvent(result); break; - case OVERWRITTEN: overwrittenEvent(result); break; + case ADDED : fireCreatedEvent(result); break; + case OVERWRITTEN: fireUpdateEvent(result); break; } } @@ -90,14 +91,14 @@ public class PartialImportManager { return Response.ok(results).build(); } - private void addedEvent(PartialImportResult result) { + private void fireCreatedEvent(PartialImportResult result) { adminEvent.operation(OperationType.CREATE) .resourcePath(result.getResourceType().getPath(), result.getId()) .representation(result.getRepresentation()) .success(); }; - private void overwrittenEvent(PartialImportResult result) { + private void fireUpdateEvent(PartialImportResult result) { adminEvent.operation(OperationType.UPDATE) .resourcePath(result.getResourceType().getPath(), result.getId()) .representation(result.getRepresentation()) diff --git a/services/src/main/java/org/keycloak/partialimport/ResourceType.java b/services/src/main/java/org/keycloak/partialimport/ResourceType.java index 3cc158f56b..9e97ecdfb4 100644 --- a/services/src/main/java/org/keycloak/partialimport/ResourceType.java +++ b/services/src/main/java/org/keycloak/partialimport/ResourceType.java @@ -23,7 +23,7 @@ package org.keycloak.partialimport; * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. */ public enum ResourceType { - USER, CLIENT, IDP, REALM_ROLE, CLIENT_ROLE; + USER, GROUP, CLIENT, IDP, REALM_ROLE, CLIENT_ROLE; /** * Used to create the admin path in events. @@ -33,6 +33,7 @@ public enum ResourceType { public String getPath() { switch(this) { case USER: return "users"; + case GROUP: return "groups"; case CLIENT: return "clients"; case IDP: return "identity-provider-settings"; case REALM_ROLE: return "realms"; @@ -45,6 +46,7 @@ public enum ResourceType { public String toString() { switch(this) { case USER: return "User"; + case GROUP: return "Group"; case CLIENT: return "Client"; case IDP: return "Identity Provider"; case REALM_ROLE: return "Realm Role"; diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java index 0440f7b0c7..1d746c3c0a 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -32,6 +32,7 @@ import org.keycloak.admin.client.resource.UserResource; import org.keycloak.partialimport.PartialImportResult; import org.keycloak.partialimport.PartialImportResults; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.UserRepresentation; @@ -56,9 +57,10 @@ import org.keycloak.testsuite.admin.ApiUtil; */ public class PartialImportTest extends AbstractAuthTest { - private static final int NUM_RESOURCE_TYPES = 5; + private static final int NUM_RESOURCE_TYPES = 6; private static final String CLIENT_ROLES_CLIENT = "clientRolesClient"; private static final String USER_PREFIX = "user"; + private static final String GROUP_PREFIX = "group"; private static final String CLIENT_PREFIX = "client"; private static final String REALM_ROLE_PREFIX = "realmRole"; private static final String CLIENT_ROLE_PREFIX = "clientRole"; @@ -94,6 +96,14 @@ public class PartialImportTest extends AbstractAuthTest { } } + @Before + public void removeGroups() { + List toRemove = testRealmResource().groups().groups(); + for (GroupRepresentation group: toRemove) { + testRealmResource().groups().group(group.getId()).remove(); + } + } + @Before public void removeClients() { List toRemove = testRealmResource().clients().findAll(); @@ -164,6 +174,19 @@ public class PartialImportTest extends AbstractAuthTest { piRep.setUsers(users); } + private void addGroups() { + List groups = new ArrayList<>(); + + for (int i=0; i < NUM_ENTITIES; i++) { + GroupRepresentation group = new GroupRepresentation(); + group.setName(GROUP_PREFIX + i); + group.setPath("/" + GROUP_PREFIX + i); + groups.add(group); + } + + piRep.setGroups(groups); + } + private void addClients() { List clients = new ArrayList<>(); @@ -322,6 +345,12 @@ public class PartialImportTest extends AbstractAuthTest { testFail(); } + @Test + public void testAddGroupsFail() { + addGroups(); + testFail(); + } + @Test public void testAddClientsFail() { addClients(); @@ -361,6 +390,12 @@ public class PartialImportTest extends AbstractAuthTest { testSkip(); } + @Test + public void testAddGroupsSkip() { + addGroups(); + testSkip(); + } + @Test public void testAddClientsSkip() { addClients(); @@ -400,6 +435,12 @@ public class PartialImportTest extends AbstractAuthTest { testOverwrite(); } + @Test + public void testAddGroupsOverwrite() { + addGroups(); + testOverwrite(); + } + @Test public void testAddClientsOverwrite() { addClients(); @@ -427,6 +468,7 @@ public class PartialImportTest extends AbstractAuthTest { private void importEverything() { addUsers(); + addGroups(); addClients(); addProviders(); addRealmRoles(); 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 a86792abec..6658b51a3c 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 @@ -548,6 +548,7 @@ file=File exported-json-file=Exported json file import-from-realm=Import from realm import-users=Import users +import-groups=Import groups import-clients=Import clients import-identity-providers=Import identity providers import-realm-roles=Import realm roles 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 6172f4170a..e228daac5a 100755 --- 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 @@ -2116,6 +2116,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, $scope.overwrite = false; $scope.skip = false; $scope.importUsers = false; + $scope.importGroups = false; $scope.importClients = false; $scope.importIdentityProviders = false; $scope.importRealmRoles = false; @@ -2146,11 +2147,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, } $scope.importing = true; - $scope.importUsers = $scope.hasArray('users'); - $scope.importClients = $scope.hasArray('clients'); - $scope.importIdentityProviders = $scope.hasArray('identityProviders'); - $scope.importRealmRoles = $scope.hasRealmRoles(); - $scope.importClientRoles = $scope.hasClientRoles(); + setOnOffSwitchDefaults(); $scope.results = {}; if (!$scope.hasResources()) { $scope.nothingToImport(); @@ -2179,6 +2176,15 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, return endIndex; } + function setOnOffSwitchDefaults() { + $scope.importUsers = $scope.hasArray('users'); + $scope.importGroups = $scope.hasArray('groups'); + $scope.importClients = $scope.hasArray('clients'); + $scope.importIdentityProviders = $scope.hasArray('identityProviders'); + $scope.importRealmRoles = $scope.hasRealmRoles(); + $scope.importClientRoles = $scope.hasClientRoles(); + } + $scope.setFirstPage = function() { $scope.currentPage = 0; } @@ -2255,6 +2261,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, $scope.hasResources = function() { return ($scope.importUsers && $scope.hasArray('users')) || + ($scope.importGroups && $scope.hasArray('groups')) || ($scope.importClients && $scope.hasArray('clients')) || ($scope.importIdentityProviders && $scope.hasArray('identityProviders')) || ($scope.importRealmRoles && $scope.hasRealmRoles()) || @@ -2269,6 +2276,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, if (!angular.equals($scope.fileContent, oldCopy)) { $scope.changed = true; } + setOnOffSwitchDefaults(); }, true); $scope.successMessage = function() { @@ -2286,6 +2294,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route, var json = angular.copy($scope.fileContent); json.ifResourceExists = $scope.ifResourceExists; if (!$scope.importUsers) delete json.users; + if (!$scope.importGroups) delete json.groups; if (!$scope.importIdentityProviders) delete json.identityProviders; if (!$scope.importClients) delete json.clients; diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html b/themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html index f4ab51d6f9..4df12073b7 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/partial-import.html @@ -39,6 +39,13 @@ +
+ +
+ +
+
+