1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package com.xpn.xwiki.internal.store.hibernate.query

File HqlQueryUtils.java

 

Coverage histogram

../../../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

26
71
11
1
272
163
26
0.37
6.45
11
2.36

Classes

Class Line # Actions
HqlQueryUtils 52 71 0% 26 7
0.935185293.5%
 

Contributing tests

This file is covered by 6 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package com.xpn.xwiki.internal.store.hibernate.query;
21   
22    import java.util.HashMap;
23    import java.util.HashSet;
24    import java.util.List;
25    import java.util.Map;
26    import java.util.Set;
27    import java.util.regex.Pattern;
28   
29    import org.apache.commons.lang3.StringUtils;
30   
31    import net.sf.jsqlparser.JSQLParserException;
32    import net.sf.jsqlparser.expression.Expression;
33    import net.sf.jsqlparser.expression.Function;
34    import net.sf.jsqlparser.parser.CCJSqlParserUtil;
35    import net.sf.jsqlparser.schema.Column;
36    import net.sf.jsqlparser.schema.Table;
37    import net.sf.jsqlparser.statement.Statement;
38    import net.sf.jsqlparser.statement.select.FromItem;
39    import net.sf.jsqlparser.statement.select.Join;
40    import net.sf.jsqlparser.statement.select.PlainSelect;
41    import net.sf.jsqlparser.statement.select.Select;
42    import net.sf.jsqlparser.statement.select.SelectBody;
43    import net.sf.jsqlparser.statement.select.SelectExpressionItem;
44    import net.sf.jsqlparser.statement.select.SelectItem;
45   
46    /**
47    * Provide various SQL related utilities.
48    *
49    * @version $Id: ddb42afdee02d500d362e1352c3cf1b92456b193 $
50    * @since 7.2M2
51    */
 
52    public final class HqlQueryUtils
53    {
54    private static final String DOCUMENT_FIELD_FULLNAME = "fullName";
55   
56    private static final String DOCUMENT_FIELD_NAME = "name";
57   
58    private static final String DOCUMENT_FIELD_SPACE = "space";
59   
60    private static final String DOCUMENT_FIELD_LANGUAGE = "language";
61   
62    private static final String DOCUMENT_FIELD_DEFAULTLANGUAGE = "defaultLanguage";
63   
64    private static final String DOCUMENT_FIELD_TRANSLATION = "translation";
65   
66    private static final String DOCUMENT_FIELD_HIDDEN = "hidden";
67   
68    private static final String SPACE_FIELD_REFERENCE = "reference";
69   
70    private static final String SPACE_FIELD_NAME = DOCUMENT_FIELD_NAME;
71   
72    private static final String SPACE_FIELD_PARENT = "parent";
73   
74    private static final String SPACE_FIELD_HIDDEN = DOCUMENT_FIELD_HIDDEN;
75   
76    private static final String FROM_REPLACEMENT = "$1";
77   
78    private static final Pattern FROM_DOC = Pattern.compile("com\\.xpn\\.xwiki\\.doc\\.([^ ]+)");
79   
80    private static final Pattern FROM_OBJECT = Pattern.compile("com\\.xpn\\.xwiki\\.objects\\.([^ ]+)");
81   
82    private static final Pattern FROM_RCS = Pattern.compile("com\\.xpn\\.xwiki\\.doc\\.rcs\\.([^ ]+)");
83   
84    private static final Pattern FROM_VERSION = Pattern.compile("com\\.xpn\\.xwiki\\.store\\.migration\\.([^ ]+)");
85   
86    private static final Map<String, Set<String>> ALLOWED_FIELDS;
87   
 
88  5 toggle static {
89  5 ALLOWED_FIELDS = new HashMap<>();
90   
91  5 Set<String> allowedDocFields = new HashSet<>();
92  5 ALLOWED_FIELDS.put("XWikiDocument", allowedDocFields);
93  5 allowedDocFields.add(DOCUMENT_FIELD_FULLNAME);
94  5 allowedDocFields.add(DOCUMENT_FIELD_NAME);
95  5 allowedDocFields.add(DOCUMENT_FIELD_SPACE);
96  5 allowedDocFields.add(DOCUMENT_FIELD_LANGUAGE);
97  5 allowedDocFields.add(DOCUMENT_FIELD_DEFAULTLANGUAGE);
98  5 allowedDocFields.add(DOCUMENT_FIELD_TRANSLATION);
99  5 allowedDocFields.add(DOCUMENT_FIELD_HIDDEN);
100   
101  5 Set<String> allowedSpaceFields = new HashSet<>();
102  5 ALLOWED_FIELDS.put("XWikiSpace", allowedSpaceFields);
103  5 allowedSpaceFields.add(SPACE_FIELD_REFERENCE);
104  5 allowedSpaceFields.add(SPACE_FIELD_NAME);
105  5 allowedSpaceFields.add(SPACE_FIELD_PARENT);
106  5 allowedSpaceFields.add(SPACE_FIELD_HIDDEN);
107    }
108   
 
109  0 toggle private HqlQueryUtils()
110    {
111   
112    }
113   
114    /**
115    * @param statement the statement to evaluate
116    * @return true if the statement is complete, false otherwise
117    */
 
118  27 toggle public static boolean isShortFormStatement(String statement)
119    {
120  27 return StringUtils.startsWithAny(statement.trim().toLowerCase(), ",", "from", "where", "order");
121    }
122   
123    /**
124    * @param statementString the SQL statement to check
125    * @return true if the passed SQL statement is allowed
126    */
 
127  15 toggle public static boolean isSafe(String statementString)
128    {
129  15 Statement statement;
130  15 try {
131    // TODO: should probably use a more specific Hql parser
132   
133    // FIXME: Workaround https://github.com/JSQLParser/JSqlParser/issues/163 (Support class syntax in HQL query)
134  15 String cleanedStatement = statementString;
135  15 cleanedStatement = FROM_DOC.matcher(cleanedStatement).replaceAll(FROM_REPLACEMENT);
136  15 cleanedStatement = FROM_OBJECT.matcher(cleanedStatement).replaceAll(FROM_REPLACEMENT);
137  15 cleanedStatement = FROM_RCS.matcher(cleanedStatement).replaceAll(FROM_REPLACEMENT);
138  15 cleanedStatement = FROM_VERSION.matcher(cleanedStatement).replaceAll(FROM_REPLACEMENT);
139   
140  15 statement = CCJSqlParserUtil.parse(cleanedStatement);
141   
142  15 if (statement instanceof Select) {
143  13 Select select = (Select) statement;
144   
145  13 SelectBody selectBody = select.getSelectBody();
146   
147  13 if (selectBody instanceof PlainSelect) {
148  13 PlainSelect plainSelect = (PlainSelect) selectBody;
149   
150  13 Map<String, String> tables = getTables(plainSelect);
151   
152  13 for (SelectItem selectItem : plainSelect.getSelectItems()) {
153  16 if (!isSelectItemAllowed(selectItem, tables)) {
154  6 return false;
155    }
156    }
157   
158  7 return true;
159    }
160    }
161    } catch (JSQLParserException e) {
162    // We can't parse it so lets say it's not safe
163  0 e.printStackTrace();
164    }
165   
166  2 return false;
167    }
168   
 
169  13 toggle private static Map<String, String> getTables(PlainSelect plainSelect)
170    {
171  13 Map<String, String> tables = new HashMap<>();
172   
173    // Add from item
174  13 addFromItem(plainSelect.getFromItem(), tables);
175   
176    // Add joins
177  13 List<Join> joins = plainSelect.getJoins();
178  13 if (joins != null) {
179  5 for (Join join : joins) {
180  7 addFromItem(join.getRightItem(), tables);
181    }
182    }
183   
184  13 return tables;
185    }
186   
 
187  20 toggle private static void addFromItem(FromItem item, Map<String, String> tables)
188    {
189  20 if (item instanceof Table) {
190  20 String tableName = ((Table) item).getName();
191  20 tables.put(item.getAlias() != null ? item.getAlias().getName() : tableName, tableName);
192    }
193    }
194   
195    /**
196    * @param selectItem the {@link SelectItem} to check
197    * @return true if the passed {@link SelectItem} is allowed
198    */
 
199  16 toggle private static boolean isSelectItemAllowed(SelectItem selectItem, Map<String, String> tables)
200    {
201  16 if (selectItem instanceof SelectExpressionItem) {
202  14 SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;
203   
204  14 return isSelectExpressionAllowed(selectExpressionItem.getExpression(), tables);
205    }
206   
207    // TODO: we could support more select items
208   
209  2 return false;
210    }
211   
 
212  16 toggle private static boolean isSelectExpressionAllowed(Expression expression, Map<String, String> tables)
213    {
214  16 if (expression instanceof Column) {
215  12 Column column = (Column) expression;
216   
217  12 if (isColumnAllowed(column, tables)) {
218  9 return true;
219    }
220  4 } else if (expression instanceof Function) {
221  4 Function function = (Function) expression;
222   
223  4 if (function.isAllColumns()) {
224    // Validate that allowed table is passed to the method
225    // TODO: add support for more that "count" maybe
226  2 return function.getName().equals("count") && tables.size() == 1
227    && isTableAllowed(tables.values().iterator().next());
228    } else {
229    // Validate that allowed columns are used as parameters
230  2 for (Expression parameter : function.getParameters().getExpressions()) {
231  2 if (!isSelectExpressionAllowed(parameter, tables)) {
232  0 return false;
233    }
234    }
235   
236  2 return true;
237    }
238    }
239   
240  3 return false;
241    }
242   
243    /**
244    * @param column the {@link Column} to check
245    * @return true if the passed {@link Column} is allowed
246    */
 
247  12 toggle private static boolean isColumnAllowed(Column column, Map<String, String> tables)
248    {
249  12 Set<String> fields = ALLOWED_FIELDS.get(getTableName(column.getTable(), tables));
250  12 return fields != null && fields.contains(column.getColumnName());
251    }
252   
253    /**
254    * @param tableName the name of the table
255    * @return true if the table has at least one allowed field
256    */
 
257  2 toggle private static boolean isTableAllowed(String tableName)
258    {
259  2 return ALLOWED_FIELDS.containsKey(tableName);
260    }
261   
 
262  12 toggle private static String getTableName(Table table, Map<String, String> tables)
263    {
264  12 String tableName = tables.values().iterator().next();
265   
266  12 if (table != null && StringUtils.isNotEmpty(table.getFullyQualifiedName())) {
267  9 tableName = tables.get(table.getFullyQualifiedName());
268    }
269   
270  12 return tableName;
271    }
272    }