From efd60f4b69fd0a26999e4c05f6d32f1512c9686b Mon Sep 17 00:00:00 2001 From: Gem Lamont <106068376+gem-neo4j@users.noreply.github.com> Date: Wed, 7 Jun 2023 08:44:17 +0200 Subject: [PATCH] [KUEoGBey] Sanitize text input and add quotes to allow special characters in Atomic procedures (#3613) --- core/src/main/java/apoc/atomic/Atomic.java | 2 +- .../src/test/java/apoc/atomic/AtomicTest.java | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/apoc/atomic/Atomic.java b/core/src/main/java/apoc/atomic/Atomic.java index ee4f29fc4e..7eb6b45ee6 100644 --- a/core/src/main/java/apoc/atomic/Atomic.java +++ b/core/src/main/java/apoc/atomic/Atomic.java @@ -202,7 +202,7 @@ public Stream update(@Name("container") Object nodeOrRelationship retry(executionContext, (context) -> { oldValue[0] = entity.getProperty(property); - String statement = "WITH $container as n with n set n." + property + "=" + operation + ";"; + String statement = "WITH $container as n with n set n." + Util.sanitize(property, true) + "=" + operation + ";"; Map properties = MapUtil.map("container", entity); return context.tx.execute(statement, properties); }, times); diff --git a/core/src/test/java/apoc/atomic/AtomicTest.java b/core/src/test/java/apoc/atomic/AtomicTest.java index 14d0a01e1b..4d36c89ff6 100644 --- a/core/src/test/java/apoc/atomic/AtomicTest.java +++ b/core/src/test/java/apoc/atomic/AtomicTest.java @@ -21,6 +21,7 @@ import apoc.util.TestUtil; import org.apache.commons.lang.ArrayUtils; import org.hamcrest.Matchers; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -30,6 +31,7 @@ import org.neo4j.test.rule.DbmsRule; import org.neo4j.test.rule.ImpermanentDbmsRule; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -370,4 +372,61 @@ public void testConcurrentUpdate() throws Exception { long salary = TestUtil.singleResultFirstColumn(db, "MATCH (n:Person {name:'Tom'}) RETURN n.salary1 as salary;"); assertEquals(100L, salary); } + + @Test + public void testPropertyNamesWithSpecialCharacters(){ + db.executeTransactionally("" + + "CREATE (p:Person { " + + "`person.name`:'Tom', " + + "`person.age`: 1, " + + "`person.friends`: [\"Fred\", \"George\"], " + + "`person.nickname`: 'Tom' " + + "})"); + + String match = "MATCH (n:Person {`person.name`:'Tom'})"; + String returnStmt = "YIELD oldValue, newValue RETURN oldValue, newValue"; + + // ADD + TestUtil.testCall( + db, + match + " CALL apoc.atomic.add(n, 'person.age', 1) " + returnStmt, (r) -> { + Assert.assertEquals(1L, r.get("oldValue")); + Assert.assertEquals(2L, r.get("newValue")); + }); + // SUBTRACT + TestUtil.testCall( + db, + match + " CALL apoc.atomic.subtract(n,'person.age', 1) " + returnStmt, (r) -> { + Assert.assertEquals(2L, r.get("oldValue")); + Assert.assertEquals(1L, r.get("newValue")); + }); + // CONCAT + TestUtil.testCall( + db, + match + " CALL apoc.atomic.concat(n,'person.nickname', \"my\") "+ returnStmt, (r) -> { + Assert.assertEquals("Tom", r.get("oldValue")); + Assert.assertEquals("Tommy", r.get("newValue")); + }); + // INSERT + TestUtil.testCall( + db, + match + " CALL apoc.atomic.insert(n,'person.friends', 1, \"Ron\") " + returnStmt, (r) -> { + assertArrayEquals(new String[]{"Fred", "George"},(String[]) r.get("oldValue")); + assertArrayEquals(new String[]{"Fred", "Ron", "George"},(String[]) r.get("newValue")); + }); + // REMOVE + TestUtil.testCall( + db, + match + " CALL apoc.atomic.remove(n,'person.friends', 1) " + returnStmt, (r) -> { + assertEquals(List.of("Fred", "Ron", "George"), r.get("oldValue")); + assertArrayEquals(new String[]{"Fred", "George"},(String[]) r.get("newValue")); + }); + // UPDATE + TestUtil.testCall( + db, + match + " CALL apoc.atomic.update(n,'person.age','n.`person.age` * 3') " + returnStmt, (r) -> { + Assert.assertEquals(1L, r.get("oldValue")); + Assert.assertEquals(3L, r.get("newValue")); + }); + } }