This is an automated email from the ASF dual-hosted git repository. OpenPj pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/manifoldcf.git
commit 6ac22303e78c4de2886e81b9b5d643c34028e8b3 Author: Piergiorgio Lucidi <[email protected]> AuthorDate: Mon Oct 5 12:08:20 2026 +0200 Validate sortcolumn parameters in repository connection queue and history reports. - Add identifier and allowlist checks for sort columns in SortOrder, JobManager, RepositoryHistoryManager, and API endpoints. - Add unit and database-level validation tests. --- .../manifoldcf/crawler/interfaces/SortOrder.java | 6 + .../apache/manifoldcf/crawler/jobs/JobManager.java | 42 +++- .../repository/RepositoryHistoryManager.java | 48 +++- .../manifoldcf/crawler/system/ManifoldCF.java | 136 +++++++++-- .../manifoldcf/crawler/tests/SanityHSQLDBTest.java | 61 ++++- .../manifoldcf/crawler/tests/SortOrderTest.java | 264 +++++++++++++++++++++ 6 files changed, 520 insertions(+), 37 deletions(-) diff --git a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/interfaces/SortOrder.java b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/interfaces/SortOrder.java index 4858491b1..259a48d6c 100644 --- a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/interfaces/SortOrder.java +++ b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/interfaces/SortOrder.java @@ -96,6 +96,8 @@ public class SortOrder */ public void clickColumn(String columnName) { + if (columnName == null || !columnName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) + return; int findIndex = -1; int i = 0; while (i < sortList.size()) @@ -185,6 +187,8 @@ public class SortOrder public SortSpec(String column, int direction) { + if (column == null || !column.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) + throw new IllegalArgumentException("Invalid sort column name: " + column); this.column = column; this.direction = direction; } @@ -216,6 +220,8 @@ public class SortOrder sb.append(y); } column = sb.toString(); + if (!column.matches("^[a-zA-Z_][a-zA-Z0-9_]*$")) + throw new ManifoldCFException("Invalid sort column name: " + column); } public String getColumn() diff --git a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/jobs/JobManager.java b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/jobs/JobManager.java index 3a7d1fea6..0a737f155 100644 --- a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/jobs/JobManager.java +++ b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/jobs/JobManager.java @@ -9346,7 +9346,7 @@ public class JobManager implements IJobManager addCriteria(sb,list,"t0.",connectionName,filterCriteria,true); // The intrinsic ordering is provided by the "id" column, and nothing else. - addOrdering(sb,new String[]{"id"},sortOrder); + addOrdering(sb,validDocumentStatusColumns,new String[]{"id"},sortOrder); addLimits(sb,startRow,rowCount); return database.performQuery(sb.toString(),list,null,null,rowCount,null); } @@ -9499,7 +9499,7 @@ public class JobManager implements IJobManager addCriteria(sb,list,"",connectionName,filterCriteria,false); sb.append(") t1 GROUP BY idbucket"); - addOrdering(sb,new String[]{"idbucket","inactive","processing","expiring","deleting","processready","expireready","processwaiting","expirewaiting","waitingforever","hopcountexceeded"},sortOrder); + addOrdering(sb,validQueueStatusColumns,validQueueStatusColumns,sortOrder); addLimits(sb,startRow,rowCount); return database.performQuery(sb.toString(),list,null,null,rowCount,null); } @@ -9702,12 +9702,26 @@ public class JobManager implements IJobManager return true; } + protected static final String[] validDocumentStatusColumns = new String[]{ + "identifier","job","state","status","scheduled","action","retrycount","retrylimit","id","docid","checktime","failcount","failtime" + }; + protected static final String[] validQueueStatusColumns = new String[]{ + "idbucket","inactive","processing","expiring","deleting","processready","expireready","processwaiting","expirewaiting","waitingforever","hopcountexceeded" + }; + /** Add ordering. */ - protected void addOrdering(StringBuilder sb, String[] completeFieldList, SortOrder sort) + protected void addOrdering(StringBuilder sb, String[] validFieldList, String[] completeFieldList, SortOrder sort) + throws ManifoldCFException { + Map<String,String> validMap = new HashMap<String,String>(); + for (String validCol : validFieldList) + { + validMap.put(validCol.toLowerCase(Locale.ROOT), validCol); + } + // Keep track of the fields we've seen - Map hash = new HashMap(); + Map<String,String> hash = new HashMap<String,String>(); // Emit the "Order by" sb.append(" ORDER BY "); @@ -9717,11 +9731,16 @@ public class JobManager implements IJobManager int count = sort.getCount(); while (i < count) { + String rawColumn = sort.getColumn(i); + String column = validMap.get(rawColumn.toLowerCase(Locale.ROOT)); + if (column == null) + { + throw new ManifoldCFException("Unknown or invalid sort column: '" + rawColumn + "'"); + } if (i > 0) sb.append(","); - String column = sort.getColumn(i); sb.append(column); - if (sort.getDirection(i) == sort.SORT_ASCENDING) + if (sort.getDirection(i) == SortOrder.SORT_ASCENDING) sb.append(" ASC"); else sb.append(" DESC"); @@ -9742,16 +9761,19 @@ public class JobManager implements IJobManager sb.append(","); sb.append(field); sb.append(" DESC"); - //if (j == 0) - // sb.append(" DESC"); - //else - // sb.append(" ASC"); i++; } j++; } } + @Deprecated + protected void addOrdering(StringBuilder sb, String[] completeFieldList, SortOrder sort) + throws ManifoldCFException + { + addOrdering(sb, completeFieldList, completeFieldList, sort); + } + /** Add limit and offset. */ protected void addLimits(StringBuilder sb, int startRow, int maxRowCount) diff --git a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/repository/RepositoryHistoryManager.java b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/repository/RepositoryHistoryManager.java index a19f308df..0194ae1e3 100644 --- a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/repository/RepositoryHistoryManager.java +++ b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/repository/RepositoryHistoryManager.java @@ -295,7 +295,7 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas addCriteria(sb,list,"",connectionName,criteria,false); // Note well: We can't order by "identifier" in all databases, so in order to guarantee order we use "id". This will force a specific internal // order for the OFFSET/LIMIT clause. We include "starttime" because that's the default ordering. - addOrdering(sb,new String[]{"starttime","id"},sort); + addOrdering(sb,validSimpleReportColumns,new String[]{"starttime","id"},sort); addLimits(sb,startRow,maxRowCount); return performQuery(sb.toString(),list,null,null,maxRowCount); } @@ -430,7 +430,7 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas ArrayList newList = new ArrayList(); newsb.append(constructDistinctOnClause(newList,sb.toString(),list,new String[]{"idbucket"}, new String[]{"activitycount"},new boolean[]{false},otherColumns)).append(") t4"); - addOrdering(newsb,new String[]{"activitycount","starttime","endtime","idbucket"},sort); + addOrdering(newsb,validMaxActivityReportColumns,validMaxActivityReportColumns,sort); addLimits(newsb,startRow,maxRowCount); return performQuery(newsb.toString(),newList,null,null,maxRowCount); } @@ -538,7 +538,7 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas ArrayList newList = new ArrayList(); newsb.append(constructDistinctOnClause(newList,sb.toString(),list,new String[]{"idbucket"}, new String[]{"bytecount"},new boolean[]{false},otherColumns)).append(") t4"); - addOrdering(newsb,new String[]{"bytecount","starttime","endtime","idbucket"},sort); + addOrdering(newsb,validMaxBandwidthReportColumns,validMaxBandwidthReportColumns,sort); addLimits(newsb,startRow,maxRowCount); return performQuery(newsb.toString(),newList,null,null,maxRowCount); } @@ -567,7 +567,7 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas sb.append(" AS idbucket FROM ").append(getTableName()); addCriteria(sb,list,"",connectionName,filterCriteria,false); sb.append(") t1 GROUP BY resultcodebucket,idbucket"); - addOrdering(sb,new String[]{"eventcount","resultcodebucket","idbucket"},sort); + addOrdering(sb,validResultCodesReportColumns,validResultCodesReportColumns,sort); addLimits(sb,startRow,maxRowCount); return performQuery(sb.toString(),list,null,null,maxRowCount); } @@ -659,12 +659,32 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas return true; } + protected static final String[] validSimpleReportColumns = new String[]{ + "starttime","resultcode","resultdesc","identifier","activity","bytes","elapsedtime","id","entityid","datasize","activitytype","endtime" + }; + protected static final String[] validMaxActivityReportColumns = new String[]{ + "activitycount","starttime","endtime","idbucket" + }; + protected static final String[] validMaxBandwidthReportColumns = new String[]{ + "bytecount","starttime","endtime","idbucket" + }; + protected static final String[] validResultCodesReportColumns = new String[]{ + "eventcount","resultcodebucket","idbucket" + }; + /** Add ordering. */ - protected void addOrdering(StringBuilder sb, String[] completeFieldList, SortOrder sort) + protected void addOrdering(StringBuilder sb, String[] validFieldList, String[] completeFieldList, SortOrder sort) + throws ManifoldCFException { + Map<String,String> validMap = new HashMap<String,String>(); + for (String validCol : validFieldList) + { + validMap.put(validCol.toLowerCase(Locale.ROOT), validCol); + } + // Keep track of the fields we've seen - Map hash = new HashMap(); + Map<String,String> hash = new HashMap<String,String>(); // Emit the "Order by" sb.append(" ORDER BY "); @@ -674,11 +694,16 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas int count = sort.getCount(); while (i < count) { + String rawColumn = sort.getColumn(i); + String column = validMap.get(rawColumn.toLowerCase(Locale.ROOT)); + if (column == null) + { + throw new ManifoldCFException("Unknown or invalid sort column: '" + rawColumn + "'"); + } if (i > 0) sb.append(","); - String column = sort.getColumn(i); sb.append(column); - if (sort.getDirection(i) == sort.SORT_ASCENDING) + if (sort.getDirection(i) == SortOrder.SORT_ASCENDING) sb.append(" ASC"); else sb.append(" DESC"); @@ -706,6 +731,13 @@ public class RepositoryHistoryManager extends org.apache.manifoldcf.core.databas } } + @Deprecated + protected void addOrdering(StringBuilder sb, String[] completeFieldList, SortOrder sort) + throws ManifoldCFException + { + addOrdering(sb, completeFieldList, completeFieldList, sort); + } + /** Add limit and offset. */ protected void addLimits(StringBuilder sb, int startRow, int maxRowCount) diff --git a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/system/ManifoldCF.java b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/system/ManifoldCF.java index e3f5e74d2..8ef6ac0b5 100644 --- a/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/system/ManifoldCF.java +++ b/framework/pull-agent/src/main/java/org/apache/manifoldcf/crawler/system/ManifoldCF.java @@ -2678,6 +2678,82 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF docStatus.put("hopcountexceeded",new Integer(IJobManager.DOCSTATUS_HOPCOUNTEXCEEDED)); } + protected final static Set<String> queueDocumentSortColumns; + static + { + queueDocumentSortColumns = new HashSet<String>(); + queueDocumentSortColumns.add("identifier"); + queueDocumentSortColumns.add("job"); + queueDocumentSortColumns.add("state"); + queueDocumentSortColumns.add("status"); + queueDocumentSortColumns.add("scheduled"); + queueDocumentSortColumns.add("action"); + queueDocumentSortColumns.add("retrycount"); + queueDocumentSortColumns.add("retrylimit"); + queueDocumentSortColumns.add("id"); + } + + protected final static Set<String> queueStatusSortColumns; + static + { + queueStatusSortColumns = new HashSet<String>(); + queueStatusSortColumns.add("idbucket"); + queueStatusSortColumns.add("inactive"); + queueStatusSortColumns.add("processing"); + queueStatusSortColumns.add("expiring"); + queueStatusSortColumns.add("deleting"); + queueStatusSortColumns.add("processready"); + queueStatusSortColumns.add("expireready"); + queueStatusSortColumns.add("processwaiting"); + queueStatusSortColumns.add("expirewaiting"); + queueStatusSortColumns.add("waitingforever"); + queueStatusSortColumns.add("hopcountexceeded"); + } + + protected final static Set<String> historySimpleSortColumns; + static + { + historySimpleSortColumns = new HashSet<String>(); + historySimpleSortColumns.add("starttime"); + historySimpleSortColumns.add("resultcode"); + historySimpleSortColumns.add("resultdesc"); + historySimpleSortColumns.add("identifier"); + historySimpleSortColumns.add("activity"); + historySimpleSortColumns.add("bytes"); + historySimpleSortColumns.add("elapsedtime"); + historySimpleSortColumns.add("id"); + historySimpleSortColumns.add("entityid"); + } + + protected final static Set<String> historyMaxActivitySortColumns; + static + { + historyMaxActivitySortColumns = new HashSet<String>(); + historyMaxActivitySortColumns.add("starttime"); + historyMaxActivitySortColumns.add("endtime"); + historyMaxActivitySortColumns.add("activitycount"); + historyMaxActivitySortColumns.add("idbucket"); + } + + protected final static Set<String> historyMaxBandwidthSortColumns; + static + { + historyMaxBandwidthSortColumns = new HashSet<String>(); + historyMaxBandwidthSortColumns.add("starttime"); + historyMaxBandwidthSortColumns.add("endtime"); + historyMaxBandwidthSortColumns.add("bytecount"); + historyMaxBandwidthSortColumns.add("idbucket"); + } + + protected final static Set<String> historyResultSortColumns; + static + { + historyResultSortColumns = new HashSet<String>(); + historyResultSortColumns.add("idbucket"); + historyResultSortColumns.add("resultcodebucket"); + historyResultSortColumns.add("eventcount"); + } + /** Queue reports */ protected static int apiReadRepositoryConnectionQueue(IThreadContext tc, Configuration output, String connectionName, Map<String,List<String>> queryParameters, IAuthorizer authorizer) throws ManifoldCFException @@ -2768,6 +2844,23 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF StatusFilterCriteria filterCriteria = new StatusFilterCriteria(jobs,now,idMatch,matchStates,matchStatuses); + List<String> reportTypeList = queryParameters.get("report"); + String reportType; + if (reportTypeList == null || reportTypeList.size() == 0) + reportType = "document"; + else if (reportTypeList.size() > 1) + throw new ManifoldCFException("Multiple report types specified."); + else + reportType = reportTypeList.get(0); + + Set<String> validColumns; + if (reportType.equals("document")) + validColumns = queueDocumentSortColumns; + else if (reportType.equals("status")) + validColumns = queueStatusSortColumns; + else + throw new ManifoldCFException("Unknown report type '"+reportType+"'."); + // Look for sort order parameters... SortOrder sortOrder = new SortOrder(); List<String> sortColumnsList = queryParameters.get("sortcolumn"); @@ -2779,6 +2872,8 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF for (int i = 0; i < sortColumnsList.size(); i++) { String column = sortColumnsList.get(i); + if (!validColumns.contains(column.toLowerCase(Locale.ROOT))) + throw new ManifoldCFException("Unknown sortcolumn '"+column+"' for report '"+reportType+"'."); String dir = sortColumnsDirList.get(i); int dirInt; if (dir.equals("ascending")) @@ -2810,15 +2905,6 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF else rowCount = new Integer(rowCountList.get(0)).intValue(); - List<String> reportTypeList = queryParameters.get("report"); - String reportType; - if (reportTypeList == null || reportTypeList.size() == 0) - reportType = "document"; - else if (reportTypeList.size() > 1) - throw new ManifoldCFException("Multiple report types specified."); - else - reportType = reportTypeList.get(0); - IJobManager jobManager = JobManagerFactory.make(tc); IResultSet result; @@ -2996,6 +3082,27 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF // Filter criteria FilterCriteria filterCriteria = new FilterCriteria(activities,startTime,endTime,entityMatch,resultCodeMatch); + List<String> reportTypeList = queryParameters.get("report"); + String reportType; + if (reportTypeList == null || reportTypeList.size() == 0) + reportType = "simple"; + else if (reportTypeList.size() > 1) + throw new ManifoldCFException("Multiple report types specified."); + else + reportType = reportTypeList.get(0); + + Set<String> validColumns; + if (reportType.equals("simple")) + validColumns = historySimpleSortColumns; + else if (reportType.equals("maxactivity")) + validColumns = historyMaxActivitySortColumns; + else if (reportType.equals("maxbandwidth")) + validColumns = historyMaxBandwidthSortColumns; + else if (reportType.equals("result")) + validColumns = historyResultSortColumns; + else + throw new ManifoldCFException("Unknown report type '"+reportType+"'."); + // Look for sort order parameters... SortOrder sortOrder = new SortOrder(); List<String> sortColumnsList = queryParameters.get("sortcolumn"); @@ -3007,6 +3114,8 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF for (int i = 0; i < sortColumnsList.size(); i++) { String column = sortColumnsList.get(i); + if (!validColumns.contains(column.toLowerCase(Locale.ROOT))) + throw new ManifoldCFException("Unknown sortcolumn '"+column+"' for report '"+reportType+"'."); String dir = sortColumnsDirList.get(i); int dirInt; if (dir.equals("ascending")) @@ -3038,15 +3147,6 @@ public class ManifoldCF extends org.apache.manifoldcf.agents.system.ManifoldCF else rowCount = new Integer(rowCountList.get(0)).intValue(); - List<String> reportTypeList = queryParameters.get("report"); - String reportType; - if (reportTypeList == null || reportTypeList.size() == 0) - reportType = "simple"; - else if (reportTypeList.size() > 1) - throw new ManifoldCFException("Multiple report types specified."); - else - reportType = reportTypeList.get(0); - IRepositoryConnectionManager connectionManager = RepositoryConnectionManagerFactory.make(tc); IResultSet result; diff --git a/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SanityHSQLDBTest.java b/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SanityHSQLDBTest.java index ac73eaa24..797a634fd 100644 --- a/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SanityHSQLDBTest.java +++ b/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SanityHSQLDBTest.java @@ -25,6 +25,8 @@ import org.apache.manifoldcf.agents.system.ManifoldCF; import java.io.*; import java.util.*; import org.junit.*; +import org.apache.manifoldcf.crawler.interfaces.*; +import static org.junit.Assert.*; /** This is a very basic sanity check */ public class SanityHSQLDBTest extends BaseHSQLDB @@ -36,6 +38,63 @@ public class SanityHSQLDBTest extends BaseHSQLDB { // If we get this far, it must mean that the setup was successful, which is all that I'm shooting for in this test. } - + + @Test + public void testJobManagerDocumentStatusSortValidation() + throws Exception + { + IThreadContext tc = ThreadContextFactory.make(); + IJobManager jobManager = JobManagerFactory.make(tc); + StatusFilterCriteria filterCriteria = new StatusFilterCriteria(new Long[0], System.currentTimeMillis(), null, new int[0], new int[0]); + + // Valid sort columns + SortOrder validSort = new SortOrder(); + validSort.addCriteria("job", SortOrder.SORT_ASCENDING); + validSort.addCriteria("status", SortOrder.SORT_DESCENDING); + IResultSet set = jobManager.genDocumentStatus("testconn", filterCriteria, validSort, 0, 20); + assertNotNull(set); + + // Invalid sort column (valid identifier syntax, but not allowed for document status) + SortOrder invalidSort = new SortOrder(); + invalidSort.addCriteria("nonexistent_column", SortOrder.SORT_ASCENDING); + try + { + jobManager.genDocumentStatus("testconn", filterCriteria, invalidSort, 0, 20); + fail("Expected ManifoldCFException for invalid sort column in genDocumentStatus"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown or invalid sort column: 'nonexistent_column'")); + } + } + + @Test + public void testHistoryManagerSortValidation() + throws Exception + { + IThreadContext tc = ThreadContextFactory.make(); + IRepositoryConnectionManager connManager = RepositoryConnectionManagerFactory.make(tc); + FilterCriteria criteria = new FilterCriteria(new String[0], null, null, null, null); + + // Valid sort columns (including entityid alias) + SortOrder validSort = new SortOrder(); + validSort.addCriteria("activity", SortOrder.SORT_ASCENDING); + validSort.addCriteria("entityid", SortOrder.SORT_DESCENDING); + IResultSet set = connManager.genHistorySimple("testconn", criteria, validSort, 0, 20); + assertNotNull(set); + + // Invalid sort column + SortOrder invalidSort = new SortOrder(); + invalidSort.addCriteria("unknown_field", SortOrder.SORT_ASCENDING); + try + { + connManager.genHistorySimple("testconn", criteria, invalidSort, 0, 20); + fail("Expected ManifoldCFException for invalid sort column in genHistorySimple"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown or invalid sort column: 'unknown_field'")); + } + } } diff --git a/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SortOrderTest.java b/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SortOrderTest.java new file mode 100644 index 000000000..2cb727e06 --- /dev/null +++ b/framework/pull-agent/src/test/java/org/apache/manifoldcf/crawler/tests/SortOrderTest.java @@ -0,0 +1,264 @@ +/* $Id$ */ + +/** +* 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.manifoldcf.crawler.tests; + +import java.util.*; +import org.apache.manifoldcf.core.interfaces.*; +import org.apache.manifoldcf.crawler.interfaces.*; +import org.apache.manifoldcf.crawler.jobs.JobManager; +import org.apache.manifoldcf.crawler.repository.RepositoryHistoryManager; +import org.apache.manifoldcf.crawler.system.ManifoldCF; +import org.junit.*; +import static org.junit.Assert.*; + +public class SortOrderTest +{ + @Test + public void testSortOrderValid() + throws Exception + { + SortOrder so = new SortOrder(); + so.addCriteria("identifier", SortOrder.SORT_ASCENDING); + so.addCriteria("job", SortOrder.SORT_DESCENDING); + assertEquals(2, so.getCount()); + assertEquals("job", so.getColumn(0)); + assertEquals(SortOrder.SORT_DESCENDING, so.getDirection(0)); + assertEquals("identifier", so.getColumn(1)); + assertEquals(SortOrder.SORT_ASCENDING, so.getDirection(1)); + + String rep = so.toString(); + SortOrder deserialized = new SortOrder(rep); + assertEquals(2, deserialized.getCount()); + assertEquals("job", deserialized.getColumn(0)); + assertEquals(SortOrder.SORT_DESCENDING, deserialized.getDirection(0)); + assertEquals("identifier", deserialized.getColumn(1)); + assertEquals(SortOrder.SORT_ASCENDING, deserialized.getDirection(1)); + } + + @Test + public void testSortOrderClickColumn() + { + SortOrder so = new SortOrder(); + so.clickColumn("starttime"); + assertEquals(1, so.getCount()); + assertEquals("starttime", so.getColumn(0)); + assertEquals(SortOrder.SORT_ASCENDING, so.getDirection(0)); + + // Clicking again should toggle direction + so.clickColumn("starttime"); + assertEquals(1, so.getCount()); + assertEquals("starttime", so.getColumn(0)); + assertEquals(SortOrder.SORT_DESCENDING, so.getDirection(0)); + + // Clicking an invalid column should be ignored + so.clickColumn("invalid; DROP TABLE repojobs;"); + assertEquals(1, so.getCount()); + assertEquals("starttime", so.getColumn(0)); + } + + @Test + public void testSortOrderInjectionRejection() + { + SortOrder so = new SortOrder(); + try + { + so.addCriteria("job; DROP TABLE repojobs;", SortOrder.SORT_ASCENDING); + fail("Expected IllegalArgumentException for SQL injection in column name"); + } + catch (IllegalArgumentException e) + { + // Expected + } + + try + { + so.addCriteria("(CASE WHEN 1=1 THEN id ELSE starttime END)", SortOrder.SORT_ASCENDING); + fail("Expected IllegalArgumentException for expression in column name"); + } + catch (IllegalArgumentException e) + { + // Expected + } + + try + { + so.addCriteria("starttime DESC--", SortOrder.SORT_ASCENDING); + fail("Expected IllegalArgumentException for comment syntax in column name"); + } + catch (IllegalArgumentException e) + { + // Expected + } + + try + { + new SortOrder("1:+job; DROP TABLE repojobs.."); + fail("Expected ManifoldCFException for SQL injection in serialized SortOrder"); + } + catch (ManifoldCFException e) + { + // Expected + } + } + + @Test + public void testApiQueueSortColumnValidation() + throws Exception + { + IThreadContext tc = ThreadContextFactory.make(); + IAuthorizer authorizer = new IAuthorizer() { + @Override + public boolean checkAllowed(IThreadContext tc, int capability) { + return true; + } + }; + + // Valid queue document sort column + Map<String,List<String>> params = new HashMap<String,List<String>>(); + params.put("report", Collections.singletonList("document")); + params.put("sortcolumn", Arrays.asList("job", "status")); + params.put("sortcolumn_direction", Arrays.asList("ascending", "descending")); + + // Invalid queue document sort column + Map<String,List<String>> badParams = new HashMap<String,List<String>>(); + badParams.put("report", Collections.singletonList("document")); + badParams.put("sortcolumn", Collections.singletonList("nonexistent_column")); + badParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionqueue/TestConn", badParams, authorizer); + fail("Expected ManifoldCFException for invalid sort column in queue document report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn 'nonexistent_column'")); + } + + // SQL injection payload in queue document sort column + Map<String,List<String>> sqlInjParams = new HashMap<String,List<String>>(); + sqlInjParams.put("report", Collections.singletonList("document")); + sqlInjParams.put("sortcolumn", Collections.singletonList("(CASE WHEN (1=1) THEN identifier ELSE scheduled END)")); + sqlInjParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionqueue/TestConn", sqlInjParams, authorizer); + fail("Expected ManifoldCFException for SQL injection payload in queue document report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn")); + } + + // Invalid queue status sort column + Map<String,List<String>> badStatusParams = new HashMap<String,List<String>>(); + badStatusParams.put("report", Collections.singletonList("status")); + badStatusParams.put("sortcolumn", Collections.singletonList("job")); + badStatusParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionqueue/TestConn", badStatusParams, authorizer); + fail("Expected ManifoldCFException for 'job' sort column in queue status report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn 'job' for report 'status'")); + } + } + + @Test + public void testApiHistorySortColumnValidation() + throws Exception + { + IThreadContext tc = ThreadContextFactory.make(); + IAuthorizer authorizer = new IAuthorizer() { + @Override + public boolean checkAllowed(IThreadContext tc, int capability) { + return true; + } + }; + + // Invalid history simple sort column + Map<String,List<String>> badSimpleParams = new HashMap<String,List<String>>(); + badSimpleParams.put("report", Collections.singletonList("simple")); + badSimpleParams.put("sortcolumn", Collections.singletonList("bad_column")); + badSimpleParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionhistory/TestConn", badSimpleParams, authorizer); + fail("Expected ManifoldCFException for invalid sort column in history simple report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn 'bad_column' for report 'simple'")); + } + + // SQL injection payload in history simple sort column + Map<String,List<String>> sqlInjParams = new HashMap<String,List<String>>(); + sqlInjParams.put("report", Collections.singletonList("simple")); + sqlInjParams.put("sortcolumn", Collections.singletonList("starttime; pg_sleep(5);--")); + sqlInjParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionhistory/TestConn", sqlInjParams, authorizer); + fail("Expected ManifoldCFException for SQL injection payload in history simple report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn")); + } + + // Invalid maxactivity sort column + Map<String,List<String>> badMaxActParams = new HashMap<String,List<String>>(); + badMaxActParams.put("report", Collections.singletonList("maxactivity")); + badMaxActParams.put("sortcolumn", Collections.singletonList("bytes")); + badMaxActParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionhistory/TestConn", badMaxActParams, authorizer); + fail("Expected ManifoldCFException for 'bytes' sort column in maxactivity report"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown sortcolumn 'bytes' for report 'maxactivity'")); + } + + // Invalid report type + Map<String,List<String>> badReportParams = new HashMap<String,List<String>>(); + badReportParams.put("report", Collections.singletonList("nonexistent_report")); + badReportParams.put("sortcolumn", Collections.singletonList("starttime")); + badReportParams.put("sortcolumn_direction", Collections.singletonList("ascending")); + + try + { + ManifoldCF.executeReadCommand(tc, new Configuration(), "repositoryconnectionhistory/TestConn", badReportParams, authorizer); + fail("Expected ManifoldCFException for unknown report type"); + } + catch (ManifoldCFException e) + { + assertTrue(e.getMessage().contains("Unknown report type 'nonexistent_report'")); + } + } + +}
