19
19
20
20
package org .apache .pinot .broker .requesthandler ;
21
21
22
+ import com .fasterxml .jackson .core .JsonProcessingException ;
22
23
import com .google .common .collect .ImmutableMap ;
23
24
import java .util .Map ;
24
25
import org .apache .pinot .common .request .PinotQuery ;
26
+ import org .apache .pinot .segment .local .function .GroovyFunctionEvaluator ;
27
+ import org .apache .pinot .segment .local .function .GroovyStaticAnalyzerConfig ;
25
28
import org .apache .pinot .sql .parsers .CalciteSqlParser ;
26
29
import org .testng .Assert ;
27
30
import org .testng .annotations .Test ;
28
31
32
+ import static org .testng .Assert .assertTrue ;
33
+ import static org .testng .Assert .fail ;
34
+
29
35
30
36
public class QueryValidationTest {
31
37
@@ -93,24 +99,64 @@ public void testNonExistingColumns() {
93
99
}
94
100
95
101
@ Test
96
- public void testRejectGroovyQuery () {
97
- testRejectGroovyQuery (
102
+ public void testValidateGroovyQuery () {
103
+ testValidateGroovyQuery (
98
104
"SELECT groovy('{\" returnType\" :\" INT\" ,\" isSingleValue\" :true}', 'arg0 + arg1', colA, colB) FROM foo" , true );
99
- testRejectGroovyQuery (
105
+ testValidateGroovyQuery (
100
106
"SELECT GROOVY('{\" returnType\" :\" INT\" ,\" isSingleValue\" :true}', 'arg0 + arg1', colA, colB) FROM foo" , true );
101
- testRejectGroovyQuery (
107
+ testValidateGroovyQuery (
102
108
"SELECT groo_vy('{\" returnType\" :\" INT\" ,\" isSingleValue\" :true}', 'arg0 + arg1', colA, colB) FROM foo" , true );
103
- testRejectGroovyQuery (
109
+ testValidateGroovyQuery (
104
110
"SELECT foo FROM bar WHERE GROOVY('{\" returnType\" :\" STRING\" ,\" isSingleValue\" :true}', 'arg0 + arg1', colA,"
105
111
+ " colB) = 'foobarval'" , true );
106
- testRejectGroovyQuery (
112
+ testValidateGroovyQuery (
107
113
"SELECT COUNT(colA) FROM bar GROUP BY GROOVY('{\" returnType\" :\" STRING\" ,\" isSingleValue\" :true}', "
108
114
+ "'arg0 + arg1', colA, colB)" , true );
109
- testRejectGroovyQuery (
115
+ testValidateGroovyQuery (
110
116
"SELECT foo FROM bar HAVING GROOVY('{\" returnType\" :\" STRING\" ,\" isSingleValue\" :true}', 'arg0 + arg1', colA,"
111
117
+ " colB) = 'foobarval'" , true );
112
118
113
- testRejectGroovyQuery ("SELECT foo FROM bar" , false );
119
+ testValidateGroovyQuery ("SELECT foo FROM bar" , false );
120
+ }
121
+
122
+ @ Test
123
+ public void testGroovyScripts ()
124
+ throws JsonProcessingException {
125
+ // setup secure groovy config
126
+ GroovyFunctionEvaluator .setGroovyStaticAnalyzerConfig (GroovyStaticAnalyzerConfig .createDefault ());
127
+
128
+ String inValidGroovyQuery = "SELECT groovy('{\" returnType\" :\" INT\" ,\" isSingleValue\" :true}') FROM foo" ;
129
+ runUnsupportedGroovy (inValidGroovyQuery , "Groovy transform function must have at least 2 argument" );
130
+
131
+ String groovyInvalidMethodInvokeQuery =
132
+ "SELECT groovy('{\" returnType\" :\" STRING\" ,\" isSingleValue\" :true}', 'return [\" bash\" , \" -c\" , \" echo Hello,"
133
+ + " World!\" ].execute().text') FROM foo" ;
134
+ runUnsupportedGroovy (groovyInvalidMethodInvokeQuery , "Expression [MethodCallExpression] is not allowed" );
135
+
136
+ String groovyInvalidImportsQuery =
137
+ "SELECT groovy( '{\" returnType\" :\" INT\" ,\" isSingleValue\" :true}', 'def args = [\" QuickStart\" , \" -type\" , "
138
+ + "\" REALTIME\" ] as String[]; org.apache.pinot.tools.admin.PinotAdministrator.main(args); 2') FROM foo" ;
139
+ runUnsupportedGroovy (groovyInvalidImportsQuery , "Indirect import checks prevents usage of expression" );
140
+
141
+ String groovyInOrderByClause =
142
+ "SELECT colA, colB FROM foo ORDER BY groovy('{\" returnType\" :\" STRING\" ,\" isSingleValue\" :true}', 'return "
143
+ + "[\" bash\" , \" -c\" , \" echo Hello, World!\" ].execute().text') DESC" ;
144
+ runUnsupportedGroovy (groovyInOrderByClause , "Expression [MethodCallExpression] is not allowed" );
145
+
146
+ String groovyInHavingClause =
147
+ "SELECT colA, SUM(colB) AS totalB, groovy('{\" returnType\" :\" DOUBLE\" ,\" isSingleValue\" :true}', 'arg0 / "
148
+ + "arg1', SUM(colB), COUNT(*)) AS avgB FROM foo GROUP BY colA HAVING groovy('{\" returnType\" :\" BOOLEAN\" ,"
149
+ + "\" isSingleValue\" :true}', 'System.metaClass.methods.each { method -> if (method.name.md5() == "
150
+ + "\" f24f62eeb789199b9b2e467df3b1876b\" ) {method.invoke(System, 10)} }', SUM(colB))" ;
151
+ runUnsupportedGroovy (groovyInHavingClause , "Indirect import checks prevents usage of expression" );
152
+
153
+ String groovyInWhereClause =
154
+ "SELECT colA, colB FROM foo WHERE groovy('{\" returnType\" :\" BOOLEAN\" ,\" isSingleValue\" :true}', 'System.exit"
155
+ + "(10)', colA)" ;
156
+ runUnsupportedGroovy (groovyInWhereClause , "Indirect import checks prevents usage of expression" );
157
+
158
+ // Reset groovy config for rest of the testing
159
+ GroovyFunctionEvaluator .setGroovyStaticAnalyzerConfig (null );
114
160
}
115
161
116
162
@ Test
@@ -121,24 +167,34 @@ public void testReplicaGroupToQueryInvalidQuery() {
121
167
() -> BaseSingleStageBrokerRequestHandler .validateRequest (pinotQuery , 10 ));
122
168
}
123
169
124
- private void testRejectGroovyQuery (String query , boolean queryContainsGroovy ) {
170
+ private void testValidateGroovyQuery (String query , boolean queryContainsGroovy ) {
125
171
PinotQuery pinotQuery = CalciteSqlParser .compileToPinotQuery (query );
126
172
127
173
try {
128
- BaseSingleStageBrokerRequestHandler .rejectGroovyQuery (pinotQuery );
174
+ BaseSingleStageBrokerRequestHandler .validateGroovyScript (pinotQuery , queryContainsGroovy );
129
175
if (queryContainsGroovy ) {
130
- Assert . fail ("Query should have failed since groovy was found in query: " + pinotQuery );
176
+ fail ("Query should have failed since groovy was found in query: " + pinotQuery );
131
177
}
132
178
} catch (Exception e ) {
133
179
Assert .assertEquals (e .getMessage (), "Groovy transform functions are disabled for queries" );
134
180
}
135
181
}
136
182
183
+ private static void runUnsupportedGroovy (String query , String errorMsg ) {
184
+ try {
185
+ PinotQuery pinotQuery = CalciteSqlParser .compileToPinotQuery (query );
186
+ BaseSingleStageBrokerRequestHandler .validateGroovyScript (pinotQuery , false );
187
+ fail ("Query should have failed since malicious groovy was found in query" );
188
+ } catch (Exception e ) {
189
+ assertTrue (e .getMessage ().contains (errorMsg ));
190
+ }
191
+ }
192
+
137
193
private void testUnsupportedQuery (String query , String errorMessage ) {
138
194
try {
139
195
PinotQuery pinotQuery = CalciteSqlParser .compileToPinotQuery (query );
140
196
BaseSingleStageBrokerRequestHandler .validateRequest (pinotQuery , 1000 );
141
- Assert . fail ("Query should have failed" );
197
+ fail ("Query should have failed" );
142
198
} catch (Exception e ) {
143
199
Assert .assertEquals (e .getMessage (), errorMessage );
144
200
}
@@ -149,7 +205,7 @@ private void testNonExistingColumns(String rawTableName, boolean isCaseInsensiti
149
205
try {
150
206
PinotQuery pinotQuery = CalciteSqlParser .compileToPinotQuery (query );
151
207
BaseSingleStageBrokerRequestHandler .updateColumnNames (rawTableName , pinotQuery , isCaseInsensitive , columnNameMap );
152
- Assert . fail ("Query should have failed" );
208
+ fail ("Query should have failed" );
153
209
} catch (Exception e ) {
154
210
Assert .assertEquals (errorMessage , e .getMessage ());
155
211
}
@@ -161,7 +217,7 @@ private void testExistingColumns(String rawTableName, boolean isCaseInsensitive,
161
217
PinotQuery pinotQuery = CalciteSqlParser .compileToPinotQuery (query );
162
218
BaseSingleStageBrokerRequestHandler .updateColumnNames (rawTableName , pinotQuery , isCaseInsensitive , columnNameMap );
163
219
} catch (Exception e ) {
164
- Assert . fail ("Query should have succeeded" );
220
+ fail ("Query should have succeeded" );
165
221
}
166
222
}
167
223
}
0 commit comments