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 old mode 100755 new mode 100644 index 498b3acd9f..25d521c74e --- 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 @@ -18,17 +18,19 @@ package org.keycloak.admin.client.resource; import org.jboss.resteasy.annotations.cache.NoCache; +import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.PartialImportRepresentation; +import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import java.util.List; import java.util.Map; -import org.keycloak.representations.idm.AdminEventRepresentation; -import org.keycloak.representations.idm.RealmEventsConfigRepresentation; /** * @author rodrigo.sasaki@icarros.com.br @@ -142,6 +144,11 @@ public interface RealmResource { @Path("clients-initial-access") ClientInitialAccessResource clientInitialAccess(); + @Path("partialImport") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response partialImport(PartialImportRepresentation rep); @Path("authentication") @Consumes(MediaType.APPLICATION_JSON) AuthenticationManagementResource flows(); diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java index 915731cb8d..d341e35567 100755 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java @@ -37,9 +37,9 @@ public interface UsersResource { List search(@QueryParam("username") String username, @QueryParam("firstName") String firstName, @QueryParam("lastName") String lastName, - @QueryParam("email") String email, - @QueryParam("first") Integer firstResult, - @QueryParam("max") Integer maxResults); + @QueryParam("email") String email, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults); @GET @Produces(MediaType.APPLICATION_JSON) diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java b/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java index fdf3f69f17..f512848242 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportResult.java @@ -26,11 +26,13 @@ import com.fasterxml.jackson.annotation.JsonIgnore; */ public class PartialImportResult { - private final Action action; - private final ResourceType resourceType; - private final String resourceName; - private final String id; - private final Object representation; + private Action action; + private ResourceType resourceType; + private String resourceName; + private String id; + private Object representation; + + private PartialImportResult() {}; private PartialImportResult(Action action, ResourceType resourceType, String resourceName, String id, Object representation) { this.action = action; @@ -56,18 +58,34 @@ public class PartialImportResult { return action; } + public void setAction(Action action) { + this.action = action; + } + public ResourceType getResourceType() { return resourceType; } + public void setResourceType(ResourceType resourceType) { + this.resourceType = resourceType; + } + public String getResourceName() { return resourceName; } + public void setResourceName(String resourceName) { + this.resourceName = resourceName; + } + public String getId() { return id; } + public void setId(String id) { + this.id = id; + } + @JsonIgnore public Object getRepresentation() { return representation; diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java b/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java index 52288f3ad8..fbab8c179e 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportResults.java @@ -28,6 +28,14 @@ import java.util.Set; */ public class PartialImportResults { + // these fields used only for marsalling from JSON with admin client + // they are never directly set + private int overwritten; + private int added; + private int skipped; + + private String errorMessage; + private final Set importResults = new HashSet<>(); public void addResult(PartialImportResult result) { @@ -68,4 +76,13 @@ public class PartialImportResults { public Set getResults() { return importResults; } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + } 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 new file mode 100644 index 0000000000..0440f7b0c7 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -0,0 +1,464 @@ +/* + * 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.testsuite.admin.partialimport; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.Response; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.IdentityProviderResource; +import org.keycloak.admin.client.resource.RoleResource; +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.IdentityProviderRepresentation; +import org.keycloak.representations.idm.PartialImportRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractAuthTest; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.keycloak.representations.idm.PartialImportRepresentation.Policy; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.RolesRepresentation; +import org.keycloak.testsuite.admin.ApiUtil; + +/** + * Tests for the partial import endpoint in admin client. Also tests the + * server side functionality of each resource along with "fail, skip, overwrite" + * functions. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc. + */ +public class PartialImportTest extends AbstractAuthTest { + + private static final int NUM_RESOURCE_TYPES = 5; + private static final String CLIENT_ROLES_CLIENT = "clientRolesClient"; + private static final String USER_PREFIX = "user"; + private static final String CLIENT_PREFIX = "client"; + private static final String REALM_ROLE_PREFIX = "realmRole"; + private static final String CLIENT_ROLE_PREFIX = "clientRole"; + private static final String[] IDP_ALIASES = {"twitter", "github", "facebook", "google", "linkedin", "microsoft", "stackoverflow"}; + private static final int NUM_ENTITIES = IDP_ALIASES.length; + + private PartialImportRepresentation piRep; + + @Before + public void init() { + piRep = new PartialImportRepresentation(); + } + + @Before + public void createClientForClientRoles() { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(CLIENT_ROLES_CLIENT); + client.setName(CLIENT_ROLES_CLIENT); + client.setRootUrl("foo"); + client.setProtocol("openid-connect"); + Response resp = testRealmResource().clients().create(client); + + // for some reason, findAll() will later fail unless readEntity is called here + resp.readEntity(String.class); + //testRealmResource().clients().findAll(); + } + + @Before + public void removeUsers() { + List toRemove = testRealmResource().users().search(USER_PREFIX, 0, NUM_ENTITIES); + for (UserRepresentation user : toRemove) { + testRealmResource().users().get(user.getId()).remove(); + } + } + + @Before + public void removeClients() { + List toRemove = testRealmResource().clients().findAll(); + for (ClientRepresentation client : toRemove) { + if (client.getName().startsWith(CLIENT_PREFIX)) { + testRealmResource().clients().get(client.getId()).remove(); + } + } + } + + @Before + public void removeProviders() { + List toRemove = testRealmResource().identityProviders().findAll(); + for (IdentityProviderRepresentation idp : toRemove) { + testRealmResource().identityProviders().get(idp.getInternalId()).remove(); + } + } + + @Before + public void removeRealmRoles() { + List toRemove = testRealmResource().roles().list(); + for (RoleRepresentation role : toRemove) { + if (role.getName().startsWith(REALM_ROLE_PREFIX)) { + testRealmResource().roles().get(role.getName()).remove(); + } + } + } + + @Before + public void removeClientRoles() { + List toRemove = clientRolesClient().roles().list(); + for (RoleRepresentation role : toRemove) { + if (role.getName().startsWith(CLIENT_ROLE_PREFIX)) { + testRealmResource().clients().get(CLIENT_ROLES_CLIENT).roles().get(role.getName()).remove(); + } + } + } + + private ClientResource clientRolesClient() { + return ApiUtil.findClientResourceByName(testRealmResource(), CLIENT_ROLES_CLIENT); + } + + private void setFail() { + piRep.setIfResourceExists(Policy.FAIL.toString()); + } + + private void setSkip() { + piRep.setIfResourceExists(Policy.SKIP.toString()); + } + + private void setOverwrite() { + piRep.setIfResourceExists(Policy.OVERWRITE.toString()); + } + + private PartialImportResults doImport() { + Response response = testRealmResource().partialImport(piRep); + return response.readEntity(PartialImportResults.class); + } + + private void addUsers() { + List users = new ArrayList<>(); + + for (int i = 0; i < NUM_ENTITIES; i++) { + UserRepresentation user = createUserRepresentation(USER_PREFIX + i, USER_PREFIX + i + "@foo.com", "foo", "bar", true); + users.add(user); + } + + piRep.setUsers(users); + } + + private void addClients() { + List clients = new ArrayList<>(); + + for (int i = 0; i < NUM_ENTITIES; i++) { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(CLIENT_PREFIX + i); + client.setName(CLIENT_PREFIX + i); + client.setRootUrl("foo"); + clients.add(client); + } + + piRep.setClients(clients); + } + + private void addProviders() { + List providers = new ArrayList<>(); + + for (String alias : IDP_ALIASES) { + IdentityProviderRepresentation idpRep = new IdentityProviderRepresentation(); + idpRep.setAlias(alias); + idpRep.setProviderId(alias); + idpRep.setEnabled(true); + idpRep.setAuthenticateByDefault(false); + idpRep.setFirstBrokerLoginFlowAlias("first broker login"); + + Map config = new HashMap<>(); + config.put("clientSecret", "secret"); + config.put("clientId", alias); + idpRep.setConfig(config); + providers.add(idpRep); + } + + piRep.setIdentityProviders(providers); + } + + private List makeRoles(String prefix) { + List roles = new ArrayList<>(); + + for (int i = 0; i < NUM_ENTITIES; i++) { + RoleRepresentation role = new RoleRepresentation(); + role.setName(prefix + i); + roles.add(role); + } + + return roles; + } + + private void addRealmRoles() { + RolesRepresentation roles = piRep.getRoles(); + if (roles == null) roles = new RolesRepresentation(); + roles.setRealm(makeRoles(REALM_ROLE_PREFIX)); + piRep.setRoles(roles); + } + + private void addClientRoles() { + RolesRepresentation roles = piRep.getRoles(); + if (roles == null) roles = new RolesRepresentation(); + Map> clientRolesMap = new HashMap<>(); + clientRolesMap.put(CLIENT_ROLES_CLIENT, makeRoles(CLIENT_ROLE_PREFIX)); + roles.setClient(clientRolesMap); + piRep.setRoles(roles); + } + + @Test + public void testAddUsers() { + setFail(); + addUsers(); + + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + for (PartialImportResult result : results.getResults()) { + String id = result.getId(); + UserResource userRsc = testRealmResource().users().get(id); + UserRepresentation user = userRsc.toRepresentation(); + assertTrue(user.getUsername().startsWith(USER_PREFIX)); + } + } + + @Test + public void testAddClients() { + setFail(); + addClients(); + + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + for (PartialImportResult result : results.getResults()) { + String id = result.getId(); + ClientResource clientRsc = testRealmResource().clients().get(id); + ClientRepresentation client = clientRsc.toRepresentation(); + assertTrue(client.getName().startsWith(CLIENT_PREFIX)); + } + } + + @Test + public void testAddProviders() { + setFail(); + addProviders(); + + PartialImportResults results = doImport(); + assertEquals(IDP_ALIASES.length, results.getAdded()); + + for (PartialImportResult result : results.getResults()) { + String id = result.getId(); + IdentityProviderResource idpRsc = testRealmResource().identityProviders().get(id); + IdentityProviderRepresentation idp = idpRsc.toRepresentation(); + Map config = idp.getConfig(); + assertTrue(Arrays.asList(IDP_ALIASES).contains(config.get("clientId"))); + } + } + + @Test + public void testAddRealmRoles() { + setFail(); + addRealmRoles(); + + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + for (PartialImportResult result : results.getResults()) { + String name = result.getResourceName(); + RoleResource roleRsc = testRealmResource().roles().get(name); + RoleRepresentation role = roleRsc.toRepresentation(); + assertTrue(role.getName().startsWith(REALM_ROLE_PREFIX)); + } + } + + @Test + public void testAddClientRoles() { + setFail(); + addClientRoles(); + + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + List clientRoles = clientRolesClient().roles().list(); + assertEquals(NUM_ENTITIES, clientRoles.size()); + + for (RoleRepresentation roleRep : clientRoles) { + assertTrue(roleRep.getName().startsWith(CLIENT_ROLE_PREFIX)); + } + } + + private void testFail() { + setFail(); + PartialImportResults results = doImport(); + assertNull(results.getErrorMessage()); + results = doImport(); // second time should fail + assertNotNull(results.getErrorMessage()); + } + + @Test + public void testAddUsersFail() { + addUsers(); + testFail(); + } + + @Test + public void testAddClientsFail() { + addClients(); + testFail(); + } + + @Test + public void testAddProvidersFail() { + addProviders(); + testFail(); + } + + @Test + public void testAddRealmRolesFail() { + addRealmRoles(); + testFail(); + } + + @Test + public void testAddClientRolesFail() { + addClientRoles(); + testFail(); + } + + private void testSkip() { + setSkip(); + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + results = doImport(); + assertEquals(NUM_ENTITIES, results.getSkipped()); + } + + @Test + public void testAddUsersSkip() { + addUsers(); + testSkip(); + } + + @Test + public void testAddClientsSkip() { + addClients(); + testSkip(); + } + + @Test + public void testAddProvidersSkip() { + addProviders(); + testSkip(); + } + + @Test + public void testAddRealmRolesSkip() { + addRealmRoles(); + testSkip(); + } + + @Test + public void testAddClientRolesSkip() { + addClientRoles(); + testSkip(); + } + + private void testOverwrite() { + setOverwrite(); + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES, results.getAdded()); + + results = doImport(); + assertEquals(NUM_ENTITIES, results.getOverwritten()); + } + + @Test + public void testAddUsersOverwrite() { + addUsers(); + testOverwrite(); + } + + @Test + public void testAddClientsOverwrite() { + addClients(); + testOverwrite(); + } + + @Test + public void testAddProvidersOverwrite() { + addProviders(); + testOverwrite(); + } + + @Test + public void testAddRealmRolesOverwrite() { + addRealmRoles(); + testOverwrite(); + } + + @Test + public void testAddClientRolesOverwrite() { + addClientRoles(); + testOverwrite(); + } + + + private void importEverything() { + addUsers(); + addClients(); + addProviders(); + addRealmRoles(); + addClientRoles(); + + PartialImportResults results = doImport(); + assertNull(results.getErrorMessage()); + assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getAdded()); + } + + @Test + public void testEverythingFail() { + setFail(); + importEverything(); + PartialImportResults results = doImport(); // second import will fail because not allowed to skip or overwrite + assertNotNull(results.getErrorMessage()); + } + + @Test + public void testEverythingSkip() { + setSkip(); + importEverything(); + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getSkipped()); + } + + @Test + public void testEverythingOverwrite() { + setOverwrite(); + importEverything(); + PartialImportResults results = doImport(); + assertEquals(NUM_ENTITIES * NUM_RESOURCE_TYPES, results.getOverwritten()); + } + +}