| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.syncope.fit.core; |
| |
| import static org.junit.jupiter.api.Assertions.assertEquals; |
| import static org.junit.jupiter.api.Assertions.assertFalse; |
| import static org.junit.jupiter.api.Assertions.assertNotNull; |
| import static org.junit.jupiter.api.Assertions.assertNull; |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| import static org.junit.jupiter.api.Assumptions.assumeTrue; |
| |
| import com.fasterxml.jackson.core.JsonProcessingException; |
| import com.fasterxml.jackson.databind.SerializationFeature; |
| import com.fasterxml.jackson.databind.json.JsonMapper; |
| import com.fasterxml.jackson.databind.node.ArrayNode; |
| import com.fasterxml.jackson.databind.node.ObjectNode; |
| import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; |
| import java.io.IOException; |
| import java.time.OffsetDateTime; |
| import java.time.format.DateTimeFormatter; |
| import java.time.temporal.ChronoUnit; |
| import java.util.List; |
| import java.util.UUID; |
| import javax.ws.rs.core.GenericType; |
| import javax.ws.rs.core.HttpHeaders; |
| import javax.ws.rs.core.Response; |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.cxf.jaxrs.client.WebClient; |
| import org.apache.syncope.common.lib.scim.SCIMComplexConf; |
| import org.apache.syncope.common.lib.scim.SCIMConf; |
| import org.apache.syncope.common.lib.scim.SCIMUserConf; |
| import org.apache.syncope.common.lib.scim.SCIMUserNameConf; |
| import org.apache.syncope.common.lib.scim.types.EmailCanonicalType; |
| import org.apache.syncope.common.lib.to.ProvisioningResult; |
| import org.apache.syncope.common.lib.to.UserTO; |
| import org.apache.syncope.ext.scimv2.api.SCIMConstants; |
| import org.apache.syncope.ext.scimv2.api.data.Group; |
| import org.apache.syncope.ext.scimv2.api.data.ListResponse; |
| import org.apache.syncope.ext.scimv2.api.data.Member; |
| import org.apache.syncope.ext.scimv2.api.data.ResourceType; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMComplexValue; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMError; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMGroup; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMSearchRequest; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMUser; |
| import org.apache.syncope.ext.scimv2.api.data.SCIMUserName; |
| import org.apache.syncope.ext.scimv2.api.data.ServiceProviderConfig; |
| import org.apache.syncope.ext.scimv2.api.data.Value; |
| import org.apache.syncope.ext.scimv2.api.type.ErrorType; |
| import org.apache.syncope.ext.scimv2.api.type.Resource; |
| import org.apache.syncope.fit.AbstractITCase; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Test; |
| |
| public class SCIMITCase extends AbstractITCase { |
| |
| public static final String SCIM_ADDRESS = "http://localhost:9080/syncope/rest/scim/v2"; |
| |
| private static final SCIMConf CONF; |
| |
| private static Boolean ENABLED; |
| |
| static { |
| CONF = new SCIMConf(); |
| CONF.setUserConf(new SCIMUserConf()); |
| |
| CONF.getUserConf().setDisplayName("cn"); |
| |
| CONF.getUserConf().setName(new SCIMUserNameConf()); |
| CONF.getUserConf().getName().setGivenName("firstname"); |
| CONF.getUserConf().getName().setFamilyName("surname"); |
| CONF.getUserConf().getName().setFormatted("fullname"); |
| |
| SCIMComplexConf<EmailCanonicalType> email = new SCIMComplexConf<>(); |
| email.setValue("userId"); |
| email.setType(EmailCanonicalType.work); |
| CONF.getUserConf().getEmails().add(email); |
| email = new SCIMComplexConf<>(); |
| email.setValue("email"); |
| email.setType(EmailCanonicalType.home); |
| CONF.getUserConf().getEmails().add(email); |
| } |
| |
| @BeforeAll |
| public static void isSCIMAvailable() { |
| if (ENABLED == null) { |
| try { |
| Response response = webClient().path("ServiceProviderConfig").get(); |
| ENABLED = response.getStatus() == 200; |
| } catch (Exception e) { |
| // ignore |
| ENABLED = false; |
| } |
| } |
| |
| assumeTrue(ENABLED); |
| } |
| |
| private static WebClient webClient() { |
| return WebClient.create( |
| SCIM_ADDRESS, |
| List.of(new JacksonJsonProvider(JsonMapper.builder(). |
| findAndAddModules().disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS).build()))). |
| accept(SCIMConstants.APPLICATION_SCIM_JSON_TYPE). |
| type(SCIMConstants.APPLICATION_SCIM_JSON_TYPE). |
| header(HttpHeaders.AUTHORIZATION, "Bearer " + ADMIN_CLIENT.getJWT()); |
| } |
| |
| @Test |
| public void serviceProviderConfig() { |
| Response response = webClient().path("ServiceProviderConfig").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| ServiceProviderConfig serviceProviderConfig = response.readEntity(ServiceProviderConfig.class); |
| assertNotNull(serviceProviderConfig); |
| assertFalse(serviceProviderConfig.getPatch().isSupported()); |
| assertFalse(serviceProviderConfig.getBulk().isSupported()); |
| assertTrue(serviceProviderConfig.getChangePassword().isSupported()); |
| assertTrue(serviceProviderConfig.getEtag().isSupported()); |
| assertTrue(serviceProviderConfig.getSort().isSupported()); |
| } |
| |
| @Test |
| public void resourceTypes() { |
| Response response = webClient().path("ResourceTypes").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| List<ResourceType> resourceTypes = response.readEntity(new GenericType<>() { |
| }); |
| assertNotNull(resourceTypes); |
| assertEquals(2, resourceTypes.size()); |
| |
| response = webClient().path("ResourceTypes").path("User").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| ResourceType user = response.readEntity(ResourceType.class); |
| assertNotNull(user); |
| assertEquals(Resource.User.schema(), user.getSchema()); |
| assertFalse(user.getSchemaExtensions().isEmpty()); |
| } |
| |
| @Test |
| public void schemas() { |
| Response response = webClient().path("Schemas").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| ArrayNode schemas = response.readEntity(ArrayNode.class); |
| assertNotNull(schemas); |
| assertEquals(3, schemas.size()); |
| |
| response = webClient().path("Schemas").path("none").get(); |
| assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); |
| |
| response = webClient().path("Schemas").path(Resource.EnterpriseUser.schema()).get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| ObjectNode enterpriseUser = response.readEntity(ObjectNode.class); |
| assertNotNull(enterpriseUser); |
| assertEquals(Resource.EnterpriseUser.schema(), enterpriseUser.get("id").textValue()); |
| } |
| |
| @Test |
| public void read() throws IOException { |
| Response response = webClient().path("Users").path("missing").get(); |
| assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); |
| |
| SCIMError error = response.readEntity(SCIMError.class); |
| assertEquals(Response.Status.NOT_FOUND.getStatusCode(), error.getStatus()); |
| |
| response = webClient().path("Users").path("1417acbe-cbf6-4277-9372-e75e04f97000").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| SCIMUser user = response.readEntity(SCIMUser.class); |
| assertNotNull(user); |
| assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", user.getId()); |
| assertEquals("rossini", user.getUserName()); |
| assertFalse(user.getGroups().isEmpty()); |
| assertFalse(user.getRoles().isEmpty()); |
| |
| response = webClient().path("Users").path("1417acbe-cbf6-4277-9372-e75e04f97000"). |
| query("attributes", "groups").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| user = response.readEntity(SCIMUser.class); |
| assertNotNull(user); |
| assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", user.getId()); |
| assertNull(user.getUserName()); |
| assertFalse(user.getGroups().isEmpty()); |
| assertTrue(user.getRoles().isEmpty()); |
| } |
| |
| @Test |
| public void conf() { |
| SCIMConf conf = SCIM_CONF_SERVICE.get(); |
| assertNotNull(conf); |
| |
| SCIM_CONF_SERVICE.set(CONF); |
| |
| Response response = webClient().path("Users").path("1417acbe-cbf6-4277-9372-e75e04f97000").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| SCIMUser user = response.readEntity(SCIMUser.class); |
| assertNotNull(user); |
| assertEquals("Rossini, Gioacchino", user.getDisplayName()); |
| } |
| |
| @Test |
| public void list() throws IOException { |
| Response response = webClient().path("Groups").query("count", 1100000).get(); |
| assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); |
| SCIMError error = response.readEntity(SCIMError.class); |
| assertEquals(ErrorType.tooMany, error.getScimType()); |
| |
| response = webClient().path("Groups"). |
| query("sortBy", "displayName"). |
| query("count", 11). |
| get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| ListResponse<SCIMGroup> result = response.readEntity(new GenericType<>() { |
| }); |
| assertNotNull(result); |
| assertTrue(result.getTotalResults() > 0); |
| assertEquals(11, result.getItemsPerPage()); |
| |
| assertFalse(result.getResources().isEmpty()); |
| result.getResources().forEach(group -> { |
| assertNotNull(group.getId()); |
| assertNotNull(group.getDisplayName()); |
| }); |
| } |
| |
| @Test |
| public void search() { |
| // invalid filter |
| Response response = webClient().path("Groups").query("filter", "invalid").get(); |
| assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); |
| |
| SCIMError error = response.readEntity(SCIMError.class); |
| assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), error.getStatus()); |
| assertEquals(ErrorType.invalidFilter, error.getScimType()); |
| |
| // eq |
| response = webClient().path("Groups").query("filter", "displayName eq \"additional\"").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| ListResponse<SCIMGroup> groups = response.readEntity(new GenericType<>() { |
| }); |
| assertNotNull(groups); |
| assertEquals(1, groups.getTotalResults()); |
| |
| SCIMGroup additional = groups.getResources().get(0); |
| assertEquals("additional", additional.getDisplayName()); |
| |
| // eq via POST |
| SCIMSearchRequest request = new SCIMSearchRequest("displayName eq \"additional\"", null, null, null, null); |
| response = webClient().path("Groups").path("/.search").post(request); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| groups = response.readEntity(new GenericType<>() { |
| }); |
| assertNotNull(groups); |
| assertEquals(1, groups.getTotalResults()); |
| |
| additional = groups.getResources().get(0); |
| assertEquals("additional", additional.getDisplayName()); |
| |
| // gt |
| UserTO newUser = USER_SERVICE.create(UserITCase.getUniqueSample("scimsearch@syncope.apache.org")). |
| readEntity(new GenericType<ProvisioningResult<UserTO>>() { |
| }).getEntity(); |
| |
| OffsetDateTime value = newUser.getCreationDate().minusSeconds(1).truncatedTo(ChronoUnit.SECONDS); |
| response = webClient().path("Users").query("filter", "meta.created gt \"" |
| + DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value) + '"').get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| assertEquals( |
| SCIMConstants.APPLICATION_SCIM_JSON, |
| StringUtils.substringBefore(response.getHeaderString(HttpHeaders.CONTENT_TYPE), ";")); |
| |
| ListResponse<SCIMUser> users = response.readEntity(new GenericType<>() { |
| }); |
| assertNotNull(users); |
| assertEquals(1, users.getTotalResults()); |
| |
| SCIMUser newSCIMUser = users.getResources().get(0); |
| assertEquals(newUser.getUsername(), newSCIMUser.getUserName()); |
| } |
| |
| private static SCIMUser getSampleUser(final String username) { |
| SCIMUser user = new SCIMUser(null, List.of(Resource.User.schema()), null, username, true); |
| user.setPassword("password123"); |
| |
| SCIMUserName name = new SCIMUserName(); |
| name.setGivenName(username); |
| name.setFamilyName("surname"); |
| name.setFormatted(username); |
| user.setName(name); |
| |
| SCIMComplexValue userId = new SCIMComplexValue(); |
| userId.setType(EmailCanonicalType.work.name()); |
| userId.setValue(username + "@syncope.apache.org"); |
| user.getEmails().add(userId); |
| |
| SCIMComplexValue email = new SCIMComplexValue(); |
| email.setType(EmailCanonicalType.home.name()); |
| email.setValue(username + "@syncope.apache.org"); |
| user.getEmails().add(email); |
| |
| return user; |
| } |
| |
| @Test |
| public void createUser() throws JsonProcessingException { |
| SCIM_CONF_SERVICE.set(CONF); |
| |
| SCIMUser user = getSampleUser(UUID.randomUUID().toString()); |
| user.getRoles().add(new Value("User reviewer")); |
| user.getGroups().add(new Group("37d15e4c-cdc1-460b-a591-8505c8133806", null, null, null)); |
| |
| Response response = webClient().path("Users").post(user); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| user = response.readEntity(SCIMUser.class); |
| assertNotNull(user.getId()); |
| assertTrue(response.getLocation().toASCIIString().endsWith(user.getId())); |
| |
| UserTO userTO = USER_SERVICE.read(user.getId()); |
| assertEquals(user.getUserName(), userTO.getUsername()); |
| assertTrue(user.isActive()); |
| assertEquals(user.getDisplayName(), userTO.getDerAttr("cn").get().getValues().get(0)); |
| assertEquals(user.getName().getGivenName(), userTO.getPlainAttr("firstname").get().getValues().get(0)); |
| assertEquals(user.getName().getFamilyName(), userTO.getPlainAttr("surname").get().getValues().get(0)); |
| assertEquals(user.getName().getFormatted(), userTO.getPlainAttr("fullname").get().getValues().get(0)); |
| assertEquals(user.getEmails().get(0).getValue(), userTO.getPlainAttr("userId").get().getValues().get(0)); |
| assertEquals(user.getEmails().get(1).getValue(), userTO.getPlainAttr("email").get().getValues().get(0)); |
| assertEquals(user.getRoles().get(0).getValue(), userTO.getRoles().get(0)); |
| assertEquals(user.getGroups().get(0).getValue(), userTO.getMemberships().get(0).getGroupKey()); |
| } |
| |
| @Test |
| public void replaceUser() { |
| SCIM_CONF_SERVICE.set(CONF); |
| |
| SCIMUser user = getSampleUser(UUID.randomUUID().toString()); |
| |
| Response response = webClient().path("Users").post(user); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| user = response.readEntity(SCIMUser.class); |
| assertNotNull(user.getId()); |
| |
| user.getName().setFormatted("new" + user.getUserName()); |
| |
| response = webClient().path("Users").path(user.getId()).put(user); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| user = response.readEntity(SCIMUser.class); |
| assertTrue(user.getName().getFormatted().startsWith("new")); |
| } |
| |
| @Test |
| public void deleteUser() { |
| SCIM_CONF_SERVICE.set(CONF); |
| |
| SCIMUser user = getSampleUser(UUID.randomUUID().toString()); |
| |
| Response response = webClient().path("Users").post(user); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| user = response.readEntity(SCIMUser.class); |
| assertNotNull(user.getId()); |
| |
| response = webClient().path("Users").path(user.getId()).get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| response = webClient().path("Users").path(user.getId()).delete(); |
| assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); |
| |
| response = webClient().path("Users").path(user.getId()).get(); |
| assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); |
| } |
| |
| @Test |
| public void createGroup() { |
| String displayName = UUID.randomUUID().toString(); |
| |
| SCIMGroup group = new SCIMGroup(null, null, displayName); |
| group.getMembers().add(new Member("1417acbe-cbf6-4277-9372-e75e04f97000", null, null)); |
| assertNull(group.getId()); |
| assertEquals(displayName, group.getDisplayName()); |
| |
| Response response = webClient().path("Groups").post(group); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| group = response.readEntity(SCIMGroup.class); |
| assertNotNull(group.getId()); |
| assertTrue(response.getLocation().toASCIIString().endsWith(group.getId())); |
| assertEquals(1, group.getMembers().size()); |
| assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", group.getMembers().get(0).getValue()); |
| |
| response = webClient().path("Users").path("1417acbe-cbf6-4277-9372-e75e04f97000").get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| SCIMUser user = response.readEntity(SCIMUser.class); |
| assertEquals("1417acbe-cbf6-4277-9372-e75e04f97000", user.getId()); |
| |
| response = webClient().path("Groups").post(group); |
| assertEquals(Response.Status.CONFLICT.getStatusCode(), response.getStatus()); |
| |
| SCIMError error = response.readEntity(SCIMError.class); |
| assertEquals(Response.Status.CONFLICT.getStatusCode(), error.getStatus()); |
| assertEquals(ErrorType.uniqueness, error.getScimType()); |
| } |
| |
| @Test |
| public void replaceGroup() { |
| SCIMGroup group = new SCIMGroup(null, null, UUID.randomUUID().toString()); |
| group.getMembers().add(new Member("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee", null, null)); |
| Response response = webClient().path("Groups").post(group); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| group = response.readEntity(SCIMGroup.class); |
| assertNotNull(group.getId()); |
| assertEquals(1, group.getMembers().size()); |
| assertEquals("b3cbc78d-32e6-4bd4-92e0-bbe07566a2ee", group.getMembers().get(0).getValue()); |
| |
| group.setDisplayName("other" + group.getId()); |
| group.getMembers().add(new Member("c9b2dec2-00a7-4855-97c0-d854842b4b24", null, null)); |
| |
| response = webClient().path("Groups").path(group.getId()).put(group); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| group = response.readEntity(SCIMGroup.class); |
| assertTrue(group.getDisplayName().startsWith("other")); |
| assertEquals(2, group.getMembers().size()); |
| |
| group.getMembers().clear(); |
| group.getMembers().add(new Member("c9b2dec2-00a7-4855-97c0-d854842b4b24", null, null)); |
| |
| response = webClient().path("Groups").path(group.getId()).put(group); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| group = response.readEntity(SCIMGroup.class); |
| assertEquals(1, group.getMembers().size()); |
| assertEquals("c9b2dec2-00a7-4855-97c0-d854842b4b24", group.getMembers().get(0).getValue()); |
| } |
| |
| @Test |
| public void deleteGroup() { |
| SCIMGroup group = new SCIMGroup(null, null, UUID.randomUUID().toString()); |
| Response response = webClient().path("Groups").post(group); |
| assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); |
| |
| group = response.readEntity(SCIMGroup.class); |
| assertNotNull(group.getId()); |
| |
| response = webClient().path("Groups").path(group.getId()).get(); |
| assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); |
| |
| response = webClient().path("Groups").path(group.getId()).delete(); |
| assertEquals(Response.Status.NO_CONTENT.getStatusCode(), response.getStatus()); |
| |
| response = webClient().path("Groups").path(group.getId()).get(); |
| assertEquals(Response.Status.NOT_FOUND.getStatusCode(), response.getStatus()); |
| } |
| } |