diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java index 5e1a99fe41..2e4543ae61 100644 --- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java +++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserFederationProviderResource.java @@ -64,6 +64,7 @@ public interface UserFederationProviderResource { @GET @Path("mapper-types") + @Produces(MediaType.APPLICATION_JSON) Map getMapperTypes(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java index 2a56aa0b9c..312690c092 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationProviderResource.java @@ -185,6 +185,7 @@ public class UserFederationProviderResource { */ @GET @Path("mapper-types") + @Produces(MediaType.APPLICATION_JSON) @NoCache public Map getMapperTypes() { auth.requireView(); diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationMapper.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationMapper.java new file mode 100644 index 0000000000..214c488b52 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/DummyUserFederationMapper.java @@ -0,0 +1,140 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.federation; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.keycloak.Config; +import org.keycloak.mappers.FederationConfigValidationException; +import org.keycloak.mappers.UserFederationMapper; +import org.keycloak.mappers.UserFederationMapperFactory; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserFederationSyncResult; +import org.keycloak.models.UserModel; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresentation; + +/** + * @author Marek Posolda + */ +public class DummyUserFederationMapper implements UserFederationMapperFactory, UserFederationMapper { + + public static final String PROVIDER_NAME = "dummy-mapper"; + + @Override + public String getFederationProviderType() { + return DummyUserFederationProviderFactory.PROVIDER_NAME; + } + + @Override + public String getDisplayCategory() { + return "Dummy"; + } + + @Override + public String getDisplayType() { + return "Dummy"; + } + + @Override + public UserFederationMapperSyncConfigRepresentation getSyncConfig() { + return new UserFederationMapperSyncConfigRepresentation(true, "dummyFedToKeycloak", true, "dummyKeycloakToFed"); + } + + @Override + public void validateConfig(RealmModel realm, UserFederationProviderModel fedProviderModel, UserFederationMapperModel mapperModel) throws FederationConfigValidationException { + + } + + @Override + public Map getDefaultConfig(UserFederationProviderModel providerModel) { + return Collections.emptyMap(); + } + + @Override + public String getHelpText() { + return "Dummy"; + } + + @Override + public List getConfigProperties() { + return Collections.emptyList(); + } + + @Override + public UserFederationMapper create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return PROVIDER_NAME; + } + + @Override + public UserFederationSyncResult syncDataFromFederationProviderToKeycloak(final UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) { + return new UserFederationSyncResult() { + + @Override + public String getStatus() { + return "dummyFedToKeycloakSuccess mapper=" + mapperModel.getName(); + } + + }; + } + + @Override + public UserFederationSyncResult syncDataFromKeycloakToFederationProvider(final UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm) { + return new UserFederationSyncResult() { + + @Override + public String getStatus() { + return "dummyKeycloakToFedSuccess mapper=" + mapperModel.getName(); + } + + }; + } + + @Override + public List getGroupMembers(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return Collections.emptyList(); + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory new file mode 100644 index 0000000000..2bc7ca8b28 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.mappers.UserFederationMapperFactory @@ -0,0 +1,52 @@ +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Copyright 2016 Red Hat, Inc. and/or its affiliates +# and other contributors as indicated by the @author tags. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +org.keycloak.testsuite.federation.DummyUserFederationMapper \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java new file mode 100644 index 0000000000..72989a5350 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationMapperTest.java @@ -0,0 +1,292 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.admin; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ws.rs.BadRequestException; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.keycloak.admin.client.resource.UserFederationProviderResource; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory; +import org.keycloak.federation.ldap.mappers.membership.role.RoleLDAPFederationMapperFactory; +import org.keycloak.federation.ldap.mappers.membership.role.RoleMapperConfig; +import org.keycloak.representations.idm.ConfigPropertyRepresentation; +import org.keycloak.representations.idm.UserFederationMapperRepresentation; +import org.keycloak.representations.idm.UserFederationMapperTypeRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; +import org.keycloak.representations.idm.UserFederationSyncResultRepresentation; +import org.keycloak.testsuite.Assert; +import org.keycloak.testsuite.federation.DummyUserFederationMapper; +import org.keycloak.testsuite.util.UserFederationProviderBuilder; + +/** + * @author Marek Posolda + */ +public class UserFederationMapperTest extends AbstractAdminTest { + + private String ldapProviderId; + private String dummyProviderId; + + @Before + public void initFederationProviders() { + UserFederationProviderRepresentation ldapRep = UserFederationProviderBuilder.create() + .displayName("ldap-1") + .providerName("ldap") + .priority(1) + .build(); + Response resp = realm.userFederation().create(ldapRep); + this.ldapProviderId = ApiUtil.getCreatedId(resp); + resp.close(); + + UserFederationProviderRepresentation dummyRep = UserFederationProviderBuilder.create() + .displayName("dummy-1") + .providerName("dummy") + .priority(2) + .build(); + resp = realm.userFederation().create(dummyRep); + this.dummyProviderId = ApiUtil.getCreatedId(resp); + resp.close(); + } + + @After + public void cleanFederationProviders() { + realm.userFederation().get(ldapProviderId).remove(); + realm.userFederation().get(dummyProviderId).remove(); + } + + + @Test + public void testProviderFactories() { + // Test dummy mapper + Map mapperTypes = realm.userFederation().get(dummyProviderId).getMapperTypes(); + Assert.assertEquals(1, mapperTypes.size()); + Assert.assertEquals("Dummy", mapperTypes.get("dummy-mapper").getName()); + + + // Test LDAP mappers + mapperTypes = ldapProviderResource().getMapperTypes(); + Assert.assertTrue(mapperTypes.keySet().containsAll(Arrays.asList("user-attribute-ldap-mapper", "full-name-ldap-mapper", "role-ldap-mapper"))); + + UserFederationMapperTypeRepresentation attrMapper = mapperTypes.get("user-attribute-ldap-mapper"); + Assert.assertEquals("User Attribute", attrMapper.getName()); + Assert.assertFalse(attrMapper.getSyncConfig().isFedToKeycloakSyncSupported()); + Assert.assertFalse(attrMapper.getSyncConfig().isKeycloakToFedSyncSupported()); + Set propNames = getConfigPropertyNames(attrMapper); + Assert.assertTrue(propNames.containsAll(Arrays.asList("user.model.attribute", "ldap.attribute", "read.only"))); + Assert.assertEquals("false", attrMapper.getDefaultConfig().get("always.read.value.from.ldap")); + + UserFederationMapperTypeRepresentation roleMapper = mapperTypes.get("role-ldap-mapper"); + Assert.assertEquals("Role mappings", roleMapper.getName()); + Assert.assertTrue(roleMapper.getSyncConfig().isFedToKeycloakSyncSupported()); + Assert.assertTrue(roleMapper.getSyncConfig().isKeycloakToFedSyncSupported()); + Assert.assertEquals("sync-ldap-roles-to-keycloak", roleMapper.getSyncConfig().getFedToKeycloakSyncMessage()); + Assert.assertEquals("sync-keycloak-roles-to-ldap", roleMapper.getSyncConfig().getKeycloakToFedSyncMessage()); + propNames = getConfigPropertyNames(roleMapper); + Assert.assertTrue(propNames.containsAll(Arrays.asList("roles.dn", "role.name.ldap.attribute", "role.object.classes"))); + Assert.assertEquals("cn", roleMapper.getDefaultConfig().get("role.name.ldap.attribute")); + } + + private Set getConfigPropertyNames(UserFederationMapperTypeRepresentation mapper) { + List cfg = mapper.getProperties(); + Set result = new HashSet<>(); + for (ConfigPropertyRepresentation rep : cfg) { + result.add(rep.getName()); + } + return result; + + } + + + @Test + public void testUserAttributeMapperCRUD() { + // Test create fails with invalid config + UserFederationMapperRepresentation attrMapper = createMapperRep("email-mapper", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID); + Response response = ldapProviderResource().addMapper(attrMapper); + Assert.assertEquals(400, response.getStatus()); + response.close(); + + attrMapper.getConfig().put(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "email"); + response = ldapProviderResource().addMapper(attrMapper); + Assert.assertEquals(400, response.getStatus()); + response.close(); + + // Test create success when all mandatory attributes available + attrMapper.getConfig().put(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail"); + String mapperId = createMapper(attrMapper); + + // Test get + UserFederationMapperRepresentation mapperRep = ldapProviderResource().getMapperById(mapperId); + assertMapper(mapperRep, mapperId, "email-mapper", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "email", UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail"); + + // Test update fails with invalid config + mapperRep.getConfig().put(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail-updated"); + mapperRep.getConfig().remove(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE); + try { + ldapProviderResource().updateMapper(mapperId, mapperRep); + Assert.fail("Not expected update to success"); + } catch (BadRequestException bre) { + // Expected + } + + // Test not updated + mapperRep = ldapProviderResource().getMapperById(mapperId); + assertMapper(mapperRep, mapperId, "email-mapper", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "email", UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail"); + + // Test update success + mapperRep.getConfig().put(UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "email-updated"); + mapperRep.getConfig().put(UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail-updated"); + ldapProviderResource().updateMapper(mapperId, mapperRep); + + mapperRep = ldapProviderResource().getMapperById(mapperId); + assertMapper(mapperRep, mapperId, "email-mapper", UserAttributeLDAPFederationMapperFactory.PROVIDER_ID, UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, "email-updated", UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, "mail-updated"); + + // Test removed successfully + ldapProviderResource().removeMapper(mapperId); + try { + ldapProviderResource().getMapperById(mapperId); + Assert.fail("Not expected find to success as mapper was removed"); + } catch (NotFoundException nfe) { + // Expected + } + } + + private String createMapper(UserFederationMapperRepresentation mapper) { + Response response = ldapProviderResource().addMapper(mapper); + Assert.assertEquals(201, response.getStatus()); + response.close(); + return ApiUtil.getCreatedId(response); + } + + + @Test + public void testRoleMapper() { + // Create role mapper will fail + UserFederationMapperRepresentation roleMapper = createMapperRep("role-mapper", RoleLDAPFederationMapperFactory.PROVIDER_ID, + RoleMapperConfig.ROLES_DN, "ou=roles,dc=keycloak,dc=org", + RoleMapperConfig.MODE, "READ_ONLY"); + Response response = ldapProviderResource().addMapper(roleMapper); + Assert.assertEquals(400, response.getStatus()); + response.close(); + + // Fix config and create successfully + roleMapper.getConfig().put(RoleMapperConfig.USE_REALM_ROLES_MAPPING, "true"); + String roleMapperId = createMapper(roleMapper); + + // Assert builtin mappers + List mappers = ldapProviderResource().getMappers(); + Assert.assertNotNull(findMapperByName(mappers, "email")); + Assert.assertNotNull(findMapperByName(mappers, "first name")); + Assert.assertNull(findMapperByName(mappers, "non-existent")); + + roleMapper = findMapperByName(mappers, "role-mapper"); + assertMapper(roleMapper, roleMapperId, "role-mapper", RoleLDAPFederationMapperFactory.PROVIDER_ID, + RoleMapperConfig.ROLES_DN, "ou=roles,dc=keycloak,dc=org", + RoleMapperConfig.MODE, "READ_ONLY", + RoleMapperConfig.USE_REALM_ROLES_MAPPING, "true"); + + + // Remove role mapper and assert not found anymore + ldapProviderResource().removeMapper(roleMapperId); + mappers = ldapProviderResource().getMappers(); + Assert.assertNull(findMapperByName(mappers, "role-mapper")); + } + + + @Test + public void testSyncMapper() { + // Create dummy mapper + UserFederationMapperRepresentation dummyMapperRep = new UserFederationMapperRepresentation(); + dummyMapperRep.setName("some-dummy"); + dummyMapperRep.setFederationMapperType(DummyUserFederationMapper.PROVIDER_NAME); + dummyMapperRep.setFederationProviderDisplayName("dummy-1"); + String mapperId = createMapper(dummyMapperRep); + + // Try to sync with unknown action - fail + try { + realm.userFederation().get(dummyProviderId).syncMapperData(mapperId, "unknown"); + Assert.fail("Not expected to pass"); + } catch (NotFoundException nfe) { + // Expected + } + + // Try fed To Keycloak sync + UserFederationSyncResultRepresentation result = realm.userFederation().get(dummyProviderId).syncMapperData(mapperId, "fedToKeycloak"); + Assert.assertEquals("dummyFedToKeycloakSuccess mapper=some-dummy", result.getStatus()); + + // Try keycloak to fed + result = realm.userFederation().get(dummyProviderId).syncMapperData(mapperId, "keycloakToFed"); + Assert.assertEquals("dummyKeycloakToFedSuccess mapper=some-dummy", result.getStatus()); + + } + + + private UserFederationProviderResource ldapProviderResource() { + return realm.userFederation().get(ldapProviderId); + } + + private UserFederationMapperRepresentation createMapperRep(String name, String type, String... config) { + UserFederationMapperRepresentation rep = new UserFederationMapperRepresentation(); + rep.setName(name); + rep.setFederationMapperType(type); + rep.setFederationProviderDisplayName("ldap-1"); + + Map cfg = new HashMap<>(); + for (int i=0 ; i mappers, String name) { + for (UserFederationMapperRepresentation rep : mappers) { + if (rep.getName().equals(name)) { + return rep; + } + } + return null; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java index 0cd3508692..7e702a08da 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserFederationTest.java @@ -22,7 +22,6 @@ import java.util.List; import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; -import javax.ws.rs.core.GenericType; import javax.ws.rs.core.Response; import org.junit.Test; @@ -73,7 +72,7 @@ public class UserFederationTest extends AbstractAdminTest { userFederation().getProviderFactory("not-existent"); Assert.fail("Not expected to find not-existent provider"); } catch (NotFoundException nfe) { - nfe.getResponse().close(); + // Expected } } @@ -205,7 +204,7 @@ public class UserFederationTest extends AbstractAdminTest { userFederation().get(id).update(ldapRep); Assert.fail("Not expected to successfull update"); } catch (BadRequestException bre) { - bre.getResponse().close(); + // Expected } // Assert nothing was updated @@ -291,7 +290,7 @@ public class UserFederationTest extends AbstractAdminTest { userFederation().get(id1).syncUsers("unknown"); Assert.fail("Not expected to sync with unknown action"); } catch (NotFoundException nfe) { - nfe.getResponse().close(); + // Expected } // Assert sync didn't happen