This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/fiql-sql in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 44075e935adb55cace9daeba2a0341091dbbe1aa Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Fri May 8 13:10:35 2026 +0100 Validate SQL expressions after FIQL conversion --- .../jaxrs/ext/search/sql/SQLPrinterVisitor.java | 36 ++++++++++++++++++++++ .../ext/search/sql/SQLHierarchicalQueryTest.java | 8 +++++ .../ext/search/sql/SQLPrinterVisitorTest.java | 23 ++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitor.java b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitor.java index 77ef992043b..04c1c358ae5 100644 --- a/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitor.java +++ b/rt/rs/extensions/search/src/main/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitor.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import org.apache.cxf.jaxrs.ext.search.PrimitiveStatement; import org.apache.cxf.jaxrs.ext.search.SearchCondition; @@ -32,6 +33,10 @@ import org.apache.cxf.jaxrs.ext.search.visitor.AbstractUntypedSearchConditionVis public class SQLPrinterVisitor<T> extends AbstractUntypedSearchConditionVisitor<T, String> { + private static final Pattern SQL_IDENTIFIER_PATTERN = Pattern.compile("[A-Za-z_][A-Za-z0-9_]*"); + private static final Pattern SQL_QUALIFIED_IDENTIFIER_PATTERN = + Pattern.compile("[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*"); + private String primaryTable; private String tableAlias; private List<String> columns; @@ -61,6 +66,7 @@ public class SQLPrinterVisitor<T> extends AbstractUntypedSearchConditionVisitor< this.columns = columns; this.primaryTable = table; this.tableAlias = tableAlias; + validateConfiguredSqlIdentifiers(); prepareTopStringBuilder(); } @@ -81,6 +87,9 @@ public class SQLPrinterVisitor<T> extends AbstractUntypedSearchConditionVisitor< } joinDone = true; String joinTable = getRealPropertyName(properties[0]); + if (!isValidSqlIdentifier(joinTable)) { + throw new SearchParseException("Invalid SQL JOIN table identifier: " + joinTable); + } // Joining key can be pre-configured String joiningKey = primaryTable; if (joiningKey.endsWith("s")) { @@ -96,6 +105,9 @@ public class SQLPrinterVisitor<T> extends AbstractUntypedSearchConditionVisitor< } String name = getRealPropertyName(property); + if (!isValidSqlQualifiedIdentifier(name)) { + throw new SearchParseException("Invalid SQL property identifier: " + name); + } String originalValue = getPropertyValue(name, statement.getValue()); validatePropertyValue(name, originalValue); @@ -151,4 +163,28 @@ public class SQLPrinterVisitor<T> extends AbstractUntypedSearchConditionVisitor< } } + private void validateConfiguredSqlIdentifiers() { + if (primaryTable != null && !isValidSqlIdentifier(primaryTable)) { + throw new IllegalArgumentException("Invalid SQL primary table identifier: " + primaryTable); + } + if (tableAlias != null && !isValidSqlIdentifier(tableAlias)) { + throw new IllegalArgumentException("Invalid SQL table alias identifier: " + tableAlias); + } + if (columns != null) { + for (String column : columns) { + if (!"*".equals(column) && !isValidSqlQualifiedIdentifier(column)) { + throw new IllegalArgumentException("Invalid SQL column identifier: " + column); + } + } + } + } + + private static boolean isValidSqlIdentifier(String identifier) { + return identifier != null && SQL_IDENTIFIER_PATTERN.matcher(identifier).matches(); + } + + private static boolean isValidSqlQualifiedIdentifier(String identifier) { + return identifier != null && SQL_QUALIFIED_IDENTIFIER_PATTERN.matcher(identifier).matches(); + } + } diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLHierarchicalQueryTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLHierarchicalQueryTest.java index 737140e6e02..9f90b8b5edc 100644 --- a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLHierarchicalQueryTest.java +++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLHierarchicalQueryTest.java @@ -56,6 +56,14 @@ public class SQLHierarchicalQueryTest { + " WHERE (name = 'Epson') AND (cartridges.colour = 'blue')", sql); } + + @Test(expected = SearchParseException.class) + public void testJoinTableNameSqlInjectionRejected() throws SearchParseException { + FiqlParser<SearchBean> parser = new FiqlParser<>(SearchBean.class); + SearchCondition<SearchBean> filter = parser.parse("users--.name==admin"); + SQLPrinterVisitor<SearchBean> visitor = new SQLPrinterVisitor<>("accounts"); + filter.accept(visitor.visitor()); + } @Test(expected = SearchParseException.class) public void testLongHierarchicalQuery() { diff --git a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitorTest.java b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitorTest.java index 50b75a8fb2d..2e723d15973 100644 --- a/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitorTest.java +++ b/rt/rs/extensions/search/src/test/java/org/apache/cxf/jaxrs/ext/search/sql/SQLPrinterVisitorTest.java @@ -164,6 +164,29 @@ public class SQLPrinterVisitorTest { assertEquals("SELECT NAMES FROM table WHERE NAMES = 'test'", sql); } + @Test(expected = SearchParseException.class) + public void testInvalidSqlPropertyIdentifierRejected() throws SearchParseException { + FiqlParser<SearchBean> beanParser = new FiqlParser<>(SearchBean.class); + SearchCondition<SearchBean> filter = beanParser.parse("na-me==test"); + SQLPrinterVisitor<SearchBean> visitor = new SQLPrinterVisitor<>("table"); + filter.accept(visitor); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidPrimaryTableIdentifierRejected() { + new SQLPrinterVisitor<SearchBean>(null, "table--", null, Collections.emptyList()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidTableAliasIdentifierRejected() { + new SQLPrinterVisitor<SearchBean>(null, "table", "t--", Collections.emptyList()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidColumnIdentifierRejected() { + new SQLPrinterVisitor<SearchBean>(null, "table", null, Collections.singletonList("name--")); + } + @Ignore public static class Condition { private String name;
