Skip to content

Commit

Permalink
KEYCLOAK-2619: Partial Import doesn't support groups
Browse files Browse the repository at this point in the history
  • Loading branch information
ssilvert committed Mar 28, 2016
1 parent 2641a93 commit 0f52768
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 12 deletions.
Expand Up @@ -21,7 +21,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 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. * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/ */
Expand All @@ -32,6 +32,7 @@ public enum Policy { SKIP, OVERWRITE, FAIL };
protected Policy policy = Policy.FAIL; protected Policy policy = Policy.FAIL;
protected String ifResourceExists = ""; protected String ifResourceExists = "";
protected List<UserRepresentation> users; protected List<UserRepresentation> users;
protected List<GroupRepresentation> groups;
protected List<ClientRepresentation> clients; protected List<ClientRepresentation> clients;
protected List<IdentityProviderRepresentation> identityProviders; protected List<IdentityProviderRepresentation> identityProviders;
protected RolesRepresentation roles; protected RolesRepresentation roles;
Expand All @@ -40,6 +41,10 @@ public boolean hasUsers() {
return (users != null) && !users.isEmpty(); return (users != null) && !users.isEmpty();
} }


public boolean hasGroups() {
return (groups != null) && !groups.isEmpty();
}

public boolean hasClients() { public boolean hasClients() {
return (clients != null) && !clients.isEmpty(); return (clients != null) && !clients.isEmpty();
} }
Expand Down Expand Up @@ -81,6 +86,14 @@ public List<ClientRepresentation> getClients() {
return clients; return clients;
} }


public List<GroupRepresentation> getGroups() {
return groups;
}

public void setGroups(List<GroupRepresentation> groups) {
this.groups = groups;
}

public void setClients(List<ClientRepresentation> clients) { public void setClients(List<ClientRepresentation> clients) {
this.clients = clients; this.clients = clients;
} }
Expand Down
@@ -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<GroupRepresentation> {

@Override
public List<GroupRepresentation> 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);
}

}
Expand Up @@ -50,6 +50,7 @@ public PartialImportManager(PartialImportRepresentation rep, KeycloakSession ses
partialImports.add(new ClientsPartialImport()); partialImports.add(new ClientsPartialImport());
partialImports.add(new RolesPartialImport()); partialImports.add(new RolesPartialImport());
partialImports.add(new IdentityProvidersPartialImport()); partialImports.add(new IdentityProvidersPartialImport());
partialImports.add(new GroupsPartialImport());
partialImports.add(new UsersPartialImport()); partialImports.add(new UsersPartialImport());
} }


Expand Down Expand Up @@ -78,8 +79,8 @@ public Response saveResources() {


for (PartialImportResult result : results.getResults()) { for (PartialImportResult result : results.getResults()) {
switch (result.getAction()) { switch (result.getAction()) {
case ADDED : addedEvent(result); break; case ADDED : fireCreatedEvent(result); break;
case OVERWRITTEN: overwrittenEvent(result); break; case OVERWRITTEN: fireUpdateEvent(result); break;
} }
} }


Expand All @@ -90,14 +91,14 @@ public Response saveResources() {
return Response.ok(results).build(); return Response.ok(results).build();
} }


private void addedEvent(PartialImportResult result) { private void fireCreatedEvent(PartialImportResult result) {
adminEvent.operation(OperationType.CREATE) adminEvent.operation(OperationType.CREATE)
.resourcePath(result.getResourceType().getPath(), result.getId()) .resourcePath(result.getResourceType().getPath(), result.getId())
.representation(result.getRepresentation()) .representation(result.getRepresentation())
.success(); .success();
}; };


private void overwrittenEvent(PartialImportResult result) { private void fireUpdateEvent(PartialImportResult result) {
adminEvent.operation(OperationType.UPDATE) adminEvent.operation(OperationType.UPDATE)
.resourcePath(result.getResourceType().getPath(), result.getId()) .resourcePath(result.getResourceType().getPath(), result.getId())
.representation(result.getRepresentation()) .representation(result.getRepresentation())
Expand Down
Expand Up @@ -23,7 +23,7 @@
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
*/ */
public enum ResourceType { 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. * Used to create the admin path in events.
Expand All @@ -33,6 +33,7 @@ public enum ResourceType {
public String getPath() { public String getPath() {
switch(this) { switch(this) {
case USER: return "users"; case USER: return "users";
case GROUP: return "groups";
case CLIENT: return "clients"; case CLIENT: return "clients";
case IDP: return "identity-provider-settings"; case IDP: return "identity-provider-settings";
case REALM_ROLE: return "realms"; case REALM_ROLE: return "realms";
Expand All @@ -45,6 +46,7 @@ public String getPath() {
public String toString() { public String toString() {
switch(this) { switch(this) {
case USER: return "User"; case USER: return "User";
case GROUP: return "Group";
case CLIENT: return "Client"; case CLIENT: return "Client";
case IDP: return "Identity Provider"; case IDP: return "Identity Provider";
case REALM_ROLE: return "Realm Role"; case REALM_ROLE: return "Realm Role";
Expand Down
Expand Up @@ -32,6 +32,7 @@
import org.keycloak.partialimport.PartialImportResult; import org.keycloak.partialimport.PartialImportResult;
import org.keycloak.partialimport.PartialImportResults; import org.keycloak.partialimport.PartialImportResults;
import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.PartialImportRepresentation; import org.keycloak.representations.idm.PartialImportRepresentation;
import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserRepresentation;
Expand All @@ -56,9 +57,10 @@
*/ */
public class PartialImportTest extends AbstractAuthTest { 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 CLIENT_ROLES_CLIENT = "clientRolesClient";
private static final String USER_PREFIX = "user"; 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 CLIENT_PREFIX = "client";
private static final String REALM_ROLE_PREFIX = "realmRole"; private static final String REALM_ROLE_PREFIX = "realmRole";
private static final String CLIENT_ROLE_PREFIX = "clientRole"; private static final String CLIENT_ROLE_PREFIX = "clientRole";
Expand Down Expand Up @@ -94,6 +96,14 @@ public void removeUsers() {
} }
} }


@Before
public void removeGroups() {
List<GroupRepresentation> toRemove = testRealmResource().groups().groups();
for (GroupRepresentation group: toRemove) {
testRealmResource().groups().group(group.getId()).remove();
}
}

@Before @Before
public void removeClients() { public void removeClients() {
List<ClientRepresentation> toRemove = testRealmResource().clients().findAll(); List<ClientRepresentation> toRemove = testRealmResource().clients().findAll();
Expand Down Expand Up @@ -164,6 +174,19 @@ private void addUsers() {
piRep.setUsers(users); piRep.setUsers(users);
} }


private void addGroups() {
List<GroupRepresentation> 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() { private void addClients() {
List<ClientRepresentation> clients = new ArrayList<>(); List<ClientRepresentation> clients = new ArrayList<>();


Expand Down Expand Up @@ -322,6 +345,12 @@ public void testAddUsersFail() {
testFail(); testFail();
} }


@Test
public void testAddGroupsFail() {
addGroups();
testFail();
}

@Test @Test
public void testAddClientsFail() { public void testAddClientsFail() {
addClients(); addClients();
Expand Down Expand Up @@ -361,6 +390,12 @@ public void testAddUsersSkip() {
testSkip(); testSkip();
} }


@Test
public void testAddGroupsSkip() {
addGroups();
testSkip();
}

@Test @Test
public void testAddClientsSkip() { public void testAddClientsSkip() {
addClients(); addClients();
Expand Down Expand Up @@ -400,6 +435,12 @@ public void testAddUsersOverwrite() {
testOverwrite(); testOverwrite();
} }


@Test
public void testAddGroupsOverwrite() {
addGroups();
testOverwrite();
}

@Test @Test
public void testAddClientsOverwrite() { public void testAddClientsOverwrite() {
addClients(); addClients();
Expand Down Expand Up @@ -427,6 +468,7 @@ public void testAddClientRolesOverwrite() {


private void importEverything() { private void importEverything() {
addUsers(); addUsers();
addGroups();
addClients(); addClients();
addProviders(); addProviders();
addRealmRoles(); addRealmRoles();
Expand Down
Expand Up @@ -548,6 +548,7 @@ file=File
exported-json-file=Exported json file exported-json-file=Exported json file
import-from-realm=Import from realm import-from-realm=Import from realm
import-users=Import users import-users=Import users
import-groups=Import groups
import-clients=Import clients import-clients=Import clients
import-identity-providers=Import identity providers import-identity-providers=Import identity providers
import-realm-roles=Import realm roles import-realm-roles=Import realm roles
Expand Down
Expand Up @@ -2116,6 +2116,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
$scope.overwrite = false; $scope.overwrite = false;
$scope.skip = false; $scope.skip = false;
$scope.importUsers = false; $scope.importUsers = false;
$scope.importGroups = false;
$scope.importClients = false; $scope.importClients = false;
$scope.importIdentityProviders = false; $scope.importIdentityProviders = false;
$scope.importRealmRoles = false; $scope.importRealmRoles = false;
Expand Down Expand Up @@ -2146,11 +2147,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
} }


$scope.importing = true; $scope.importing = true;
$scope.importUsers = $scope.hasArray('users'); setOnOffSwitchDefaults();
$scope.importClients = $scope.hasArray('clients');
$scope.importIdentityProviders = $scope.hasArray('identityProviders');
$scope.importRealmRoles = $scope.hasRealmRoles();
$scope.importClientRoles = $scope.hasClientRoles();
$scope.results = {}; $scope.results = {};
if (!$scope.hasResources()) { if (!$scope.hasResources()) {
$scope.nothingToImport(); $scope.nothingToImport();
Expand Down Expand Up @@ -2179,6 +2176,15 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
return endIndex; 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.setFirstPage = function() {
$scope.currentPage = 0; $scope.currentPage = 0;
} }
Expand Down Expand Up @@ -2255,6 +2261,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,


$scope.hasResources = function() { $scope.hasResources = function() {
return ($scope.importUsers && $scope.hasArray('users')) || return ($scope.importUsers && $scope.hasArray('users')) ||
($scope.importGroups && $scope.hasArray('groups')) ||
($scope.importClients && $scope.hasArray('clients')) || ($scope.importClients && $scope.hasArray('clients')) ||
($scope.importIdentityProviders && $scope.hasArray('identityProviders')) || ($scope.importIdentityProviders && $scope.hasArray('identityProviders')) ||
($scope.importRealmRoles && $scope.hasRealmRoles()) || ($scope.importRealmRoles && $scope.hasRealmRoles()) ||
Expand All @@ -2269,6 +2276,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
if (!angular.equals($scope.fileContent, oldCopy)) { if (!angular.equals($scope.fileContent, oldCopy)) {
$scope.changed = true; $scope.changed = true;
} }
setOnOffSwitchDefaults();
}, true); }, true);


$scope.successMessage = function() { $scope.successMessage = function() {
Expand All @@ -2286,6 +2294,7 @@ module.controller('RealmImportCtrl', function($scope, realm, $route,
var json = angular.copy($scope.fileContent); var json = angular.copy($scope.fileContent);
json.ifResourceExists = $scope.ifResourceExists; json.ifResourceExists = $scope.ifResourceExists;
if (!$scope.importUsers) delete json.users; if (!$scope.importUsers) delete json.users;
if (!$scope.importGroups) delete json.groups;
if (!$scope.importIdentityProviders) delete json.identityProviders; if (!$scope.importIdentityProviders) delete json.identityProviders;
if (!$scope.importClients) delete json.clients; if (!$scope.importClients) delete json.clients;


Expand Down
Expand Up @@ -39,6 +39,13 @@ <h1>
</div> </div>
</div> </div>


<div class="form-group" data-ng-show="importing && hasArray('groups') && !hasResults()">
<label class="col-md-2 control-label" for="importGroups">{{:: 'import-groups' | translate}} ({{itemCount('groups')}})</label>
<div class="col-sm-6">
<input ng-model="importGroups" name="importGroups" id="importGroups" onoffswitch on-text="{{:: 'onText'| translate}}" off-text="{{:: 'offText'| translate}}"/>
</div>
</div>

<div class="form-group" data-ng-show="importing && hasArray('clients') && !hasResults()"> <div class="form-group" data-ng-show="importing && hasArray('clients') && !hasResults()">
<label class="col-md-2 control-label" for="importClients">{{:: 'import-clients' | translate}} ({{itemCount('clients')}})</label> <label class="col-md-2 control-label" for="importClients">{{:: 'import-clients' | translate}} ({{itemCount('clients')}})</label>
<div class="col-sm-6"> <div class="col-sm-6">
Expand Down

0 comments on commit 0f52768

Please sign in to comment.