diff --git a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/jsonpatch/JsonPatchManager.java b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/jsonpatch/JsonPatchManager.java index 6e4ea60fe237..36188dbfb754 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/jsonpatch/JsonPatchManager.java +++ b/dhis-2/dhis-services/dhis-service-core/src/main/java/org/hisp/dhis/jsonpatch/JsonPatchManager.java @@ -39,6 +39,7 @@ import org.hisp.dhis.commons.collection.CollectionUtils; import org.hisp.dhis.commons.jackson.jsonpatch.JsonPatch; import org.hisp.dhis.commons.jackson.jsonpatch.JsonPatchException; +import org.hisp.dhis.commons.jackson.jsonpatch.JsonPatchOperation; import org.hisp.dhis.schema.Property; import org.hisp.dhis.schema.Schema; import org.hisp.dhis.schema.SchemaService; @@ -87,6 +88,8 @@ public T apply(JsonPatch patch, T object) throws JsonPatchException { // correctly made into json nodes. handleCollectionUpdates(object, schema, (ObjectNode) node); + validatePatchPath(patch, schema); + node = patch.apply(node); return (T) jsonToObject( @@ -137,4 +140,17 @@ private BaseIdentifiableObject shallowCopyIdentifiableObject(BaseIdentifiableObj clone.setUid(source.getUid()); return clone; } + + /** Check if all patch paths are valid for the given schema. */ + private void validatePatchPath(JsonPatch patch, Schema schema) throws JsonPatchException { + for (JsonPatchOperation op : patch.getOperations()) { + if (!schema.haveProperty(op.getPath().getMatchingProperty())) { + throw new JsonPatchException( + "Property " + + op.getPath().getMatchingProperty() + + " does not exist on " + + schema.getClass().getSimpleName()); + } + } + } } diff --git a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/jsonpatch/JsonPatchManagerTest.java b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/jsonpatch/JsonPatchManagerTest.java index bf86f0f06488..1c804df04d32 100644 --- a/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/jsonpatch/JsonPatchManagerTest.java +++ b/dhis-2/dhis-services/dhis-service-core/src/test/java/org/hisp/dhis/jsonpatch/JsonPatchManagerTest.java @@ -29,6 +29,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -42,6 +43,7 @@ import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementGroup; import org.hisp.dhis.user.User; +import org.hisp.dhis.user.UserRole; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -198,4 +200,70 @@ void testAddAndRemoveSharingUser() throws JsonProcessingException, JsonPatchExce DataElement removedPatchedDE = jsonPatchManager.apply(removePatch, patchedDE); assertEquals(0, removedPatchedDE.getSharing().getUsers().size()); } + + @Test + void testReplaceNullProperty() throws JsonProcessingException, JsonPatchException { + UserRole userRole = createUserRole("test", "ALL"); + userRole.setCode(null); + manager.save(userRole); + JsonPatch patch = + jsonMapper.readValue( + "[" + "{\"op\": \"replace\", \"path\": \"/code\", \"value\": \"updated\"}" + "]", + JsonPatch.class); + assertNotNull(patch); + UserRole patchedUserRole = jsonPatchManager.apply(patch, userRole); + assertEquals("updated", patchedUserRole.getCode()); + } + + @Test + void testReplaceNotExistProperty() throws JsonProcessingException { + UserRole userRole = createUserRole("test", "ALL"); + userRole.setCode(null); + manager.save(userRole); + JsonPatch patch = + jsonMapper.readValue( + "[" + "{\"op\": \"replace\", \"path\": \"/notexist\", \"value\": \"updated\"}" + "]", + JsonPatch.class); + assertNotNull(patch); + assertThrows(JsonPatchException.class, () -> jsonPatchManager.apply(patch, userRole)); + } + + @Test + void testAddNotExistProperty() throws JsonProcessingException { + UserRole userRole = createUserRole("test", "ALL"); + userRole.setCode(null); + manager.save(userRole); + JsonPatch patch = + jsonMapper.readValue( + "[" + "{\"op\": \"add\", \"path\": \"/notexist\", \"value\": \"updated\"}" + "]", + JsonPatch.class); + assertNotNull(patch); + assertThrows(JsonPatchException.class, () -> jsonPatchManager.apply(patch, userRole)); + } + + @Test + void testRemoveNotExistProperty() throws JsonProcessingException { + UserRole userRole = createUserRole("test", "ALL"); + userRole.setCode(null); + manager.save(userRole); + JsonPatch patch = + jsonMapper.readValue( + "[" + "{\"op\": \"remove\", \"path\": \"/notexist\", \"value\": \"updated\"}" + "]", + JsonPatch.class); + assertNotNull(patch); + assertThrows(JsonPatchException.class, () -> jsonPatchManager.apply(patch, userRole)); + } + + @Test + void testRemoveByIdNotExistProperty() throws JsonProcessingException { + UserRole userRole = createUserRole("test", "ALL"); + userRole.setCode(null); + manager.save(userRole); + JsonPatch patch = + jsonMapper.readValue( + "[" + "{\"op\": \"remove\", \"path\": \"/notexist\", \"id\": \"uid\"}" + "]", + JsonPatch.class); + assertNotNull(patch); + assertThrows(JsonPatchException.class, () -> jsonPatchManager.apply(patch, userRole)); + } } diff --git a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/jackson/RemoveOperationTest.java b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/jackson/RemoveOperationTest.java index b929818dcc59..4372124313e0 100644 --- a/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/jackson/RemoveOperationTest.java +++ b/dhis-2/dhis-support/dhis-support-commons/src/test/java/org/hisp/dhis/commons/jackson/RemoveOperationTest.java @@ -30,11 +30,9 @@ 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.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -55,17 +53,6 @@ class RemoveOperationTest { private final ObjectMapper jsonMapper = JacksonObjectMapperConfig.staticJsonMapper(); @Disabled("for now we will allow 'removal' of invalid path keys") - @Test - void testRemoveInvalidKeyShouldThrowException() throws JsonProcessingException { - JsonPatch patch = - jsonMapper.readValue( - "[" + "{\"op\": \"remove\", \"path\": \"/aaa\"}" + "]", JsonPatch.class); - assertNotNull(patch); - JsonNode root = jsonMapper.createObjectNode(); - assertFalse(root.has("aaa")); - assertThrows(JsonPatchException.class, () -> patch.apply(root)); - } - @Test void testRemoveProperty() throws JsonProcessingException, JsonPatchException { JsonPatch patch =