Fix location of SCIM resources so IDs don't appear twice in the URL

Closes #49176

Signed-off-by: Stefan Guilhen <sguilhen@redhat.com>
This commit is contained in:
Stefan Guilhen
2026-05-21 18:02:30 -03:00
committed by Pedro Igor
parent 090549c7de
commit d0590bc9b9
3 changed files with 75 additions and 1 deletions
@@ -255,7 +255,10 @@ public class ScimResourceTypeResource<R extends ResourceTypeRepresentation> {
} }
UriBuilder location = session.getContext().getUri().getAbsolutePathBuilder(); UriBuilder location = session.getContext().getUri().getAbsolutePathBuilder();
if (resource.getId() != null) { if (resource.getId() != null) {
location.path(resource.getId()); String path = session.getContext().getUri().getAbsolutePath().getPath();
if (!path.endsWith("/" + resource.getId())) {
location.path(resource.getId());
}
} }
meta.setLocation(location.build().toString()); meta.setLocation(location.build().toString());
resource.setMeta(meta); resource.setMeta(meta);
@@ -129,6 +129,42 @@ public class GroupTest extends AbstractScimTest {
assertEquals(expected.getExternalId(), actual.getExternalId()); assertEquals(expected.getExternalId(), actual.getExternalId());
} }
@Test
public void testMetaLocationUrl() {
Group group = new Group();
group.setDisplayName(KeycloakModelUtils.generateId());
group = client.groups().create(group);
String id = group.getId();
// location from create response should end with /Groups/{id}
assertNotNull(group.getMeta());
assertTrue(group.getMeta().getLocation().endsWith("/Groups/" + id),
"Create location should end with /Groups/" + id + " but was: " + group.getMeta().getLocation());
assertFalse(group.getMeta().getLocation().contains(id + "/" + id),
"Create location should not contain duplicated ID: " + group.getMeta().getLocation());
// location from single-resource GET should also be correct
Group fetched = client.groups().get(id);
assertNotNull(fetched.getMeta());
assertTrue(fetched.getMeta().getLocation().endsWith("/Groups/" + id),
"GET location should end with /Groups/" + id + " but was: " + fetched.getMeta().getLocation());
assertFalse(fetched.getMeta().getLocation().contains(id + "/" + id),
"GET location should not contain duplicated ID: " + fetched.getMeta().getLocation());
// location from list response should match
String filter = ResourceFilter.filter().eq("displayName", group.getDisplayName()).build();
ListResponse<Group> response = client.groups().getAll(filter);
assertFalse(response.getResources().isEmpty());
Group listed = response.getResources().get(0);
assertNotNull(listed.getMeta());
assertTrue(listed.getMeta().getLocation().endsWith("/Groups/" + id),
"List location should end with /Groups/" + id + " but was: " + listed.getMeta().getLocation());
// all three locations should be equal
assertEquals(group.getMeta().getLocation(), fetched.getMeta().getLocation());
assertEquals(group.getMeta().getLocation(), listed.getMeta().getLocation());
}
@Test @Test
public void testMetaTimestamps() { public void testMetaTimestamps() {
Group group = new Group(); Group group = new Group();
@@ -60,6 +60,7 @@ import static org.keycloak.scim.resource.Scim.getCoreSchema;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertNull;
@@ -1252,6 +1253,40 @@ public class UserTest extends AbstractScimTest {
return subGroup; return subGroup;
} }
@Test
public void testMetaLocationUrl() {
User user = client.users().create(createUser());
String id = user.getId();
// location from create response should end with /Users/{id}
assertNotNull(user.getMeta());
assertTrue(user.getMeta().getLocation().endsWith("/Users/" + id),
"Create location should end with /Users/" + id + " but was: " + user.getMeta().getLocation());
assertFalse(user.getMeta().getLocation().contains(id + "/" + id),
"Create location should not contain duplicated ID: " + user.getMeta().getLocation());
// location from single-resource GET should also be correct
User fetched = client.users().get(id);
assertNotNull(fetched.getMeta());
assertTrue(fetched.getMeta().getLocation().endsWith("/Users/" + id),
"GET location should end with /Users/" + id + " but was: " + fetched.getMeta().getLocation());
assertFalse(fetched.getMeta().getLocation().contains(id + "/" + id),
"GET location should not contain duplicated ID: " + fetched.getMeta().getLocation());
// location from list response should match
String filter = ResourceFilter.filter().eq("userName", user.getUserName()).build();
ListResponse<User> response = client.users().getAll(filter);
assertFalse(response.getResources().isEmpty());
User listed = response.getResources().get(0);
assertNotNull(listed.getMeta());
assertTrue(listed.getMeta().getLocation().endsWith("/Users/" + id),
"List location should end with /Users/" + id + " but was: " + listed.getMeta().getLocation());
// all three locations should be equal
assertEquals(user.getMeta().getLocation(), fetched.getMeta().getLocation());
assertEquals(user.getMeta().getLocation(), listed.getMeta().getLocation());
}
@Test @Test
public void testMetaTimestamps() { public void testMetaTimestamps() {
User user = client.users().create(createUser()); User user = client.users().create(createUser());