Issues (74)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

UQL/Interpreter.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Netdudes\DataSourceryBundle\UQL;
3
4
use Netdudes\DataSourceryBundle\DataSource\Configuration\Field;
5
use Netdudes\DataSourceryBundle\DataSource\Configuration\FieldInterface;
6
use Netdudes\DataSourceryBundle\DataSource\DataSourceInterface;
7
use Netdudes\DataSourceryBundle\Extension\ContextAwareUqlFunction;
8
use Netdudes\DataSourceryBundle\Extension\ContextFactory;
9
use Netdudes\DataSourceryBundle\Extension\Exception\FunctionNotFoundException;
10
use Netdudes\DataSourceryBundle\Extension\UqlExtensionContainer;
11
use Netdudes\DataSourceryBundle\Extension\UqlFunctionInterface;
12
use Netdudes\DataSourceryBundle\Query\Filter;
13
use Netdudes\DataSourceryBundle\Query\FilterCondition;
14
use Netdudes\DataSourceryBundle\Query\FilterConditionFactory;
15
use Netdudes\DataSourceryBundle\UQL\AST\ASTArray;
16
use Netdudes\DataSourceryBundle\UQL\AST\ASTAssertion;
17
use Netdudes\DataSourceryBundle\UQL\AST\ASTFunctionCall;
18
use Netdudes\DataSourceryBundle\UQL\AST\ASTGroup;
19
use Netdudes\DataSourceryBundle\UQL\Exception\UQLInterpreterException;
20
21
/**
22
 * Class Interpreter
23
 *
24
 * The Interpreter transforms the generic Abstract Syntax Tree into Filters
25
 */
26
class Interpreter
27
{
28
    /**
29
     * @var UqlExtensionContainer
30
     */
31
    private $extensionContainer;
32
33
    /**
34
     * @var DataSourceInterface
35
     */
36
    private $dataSource;
37
38
    /**
39
     * @var array
40
     */
41
    private $dataSourceElements;
42
43
    /**
44
     * @var bool
45
     */
46
    private $caseSensitive;
47
48
    /**
49
     * @var FilterConditionFactory
50
     */
51
    private $filterConditionFactory;
52
53
    /**
54
     * @var ContextFactory
55
     */
56
    private $contextFactory;
57
58
    /**
59
     * Constructor needs the columns descriptor to figure out appropriate filtering methods
60
     * and translate identifiers.
61
     *
62
     * @param UqlExtensionContainer  $extensionContainer
63
     * @param DataSourceInterface    $dataSource
64
     * @param FilterConditionFactory $filterConditionFactory
65
     * @param ContextFactory         $contextFactory
66
     * @param bool                   $caseSensitive
67
     */
68
    public function __construct(
69
        UqlExtensionContainer $extensionContainer,
70
        DataSourceInterface $dataSource,
71
        FilterConditionFactory $filterConditionFactory,
72
        ContextFactory $contextFactory,
73
        $caseSensitive = true
74
    ) {
75
        $this->extensionContainer = $extensionContainer;
76
        $this->dataSource = $dataSource;
77
        $this->filterConditionFactory = $filterConditionFactory;
78
        $this->contextFactory = $contextFactory;
79
        $this->caseSensitive = $caseSensitive;
80
81
        // Cache an array of data sources (name => object pairs) for reference during the interpretation
82
        $this->dataSourceElements = array_combine(
83
            array_map(
84
                function (FieldInterface $element) use ($caseSensitive) {
85
                    return $caseSensitive ? $element->getUniqueName() : strtolower($element->getUniqueName());
86
                },
87
                $this->dataSource->getFields()
88
            ),
89
            $this->dataSource->getFields()
90
        );
91
    }
92
93
    /**
94
     * Generate the filter objects corresponding to a UQL string.
95
     *
96
     * @param string $uql
97
     *
98
     * @return Filter
99
     */
100
    public function interpret($uql)
101
    {
102
        if (empty(trim($uql))) {
103
            return new Filter();
104
        }
105
106
        $parser = new Parser();
107
        $AST = $parser->parse($uql);
108
109
        return $this->buildFilter($AST);
110
    }
111
112
    /**
113
     * Helper method: matches filtering operators to valid UQL operators
114
     * in order to do Filter to UQL transformations
115
     *
116
     * @param string $method
117
     *
118
     * @throws UQLInterpreterException
119
     * @return string
120
     */
121
    public static function methodToUQLOperator($method)
122
    {
123
        $translationMap = [
124
            FilterCondition::METHOD_STRING_EQ => "=",
125
            FilterCondition::METHOD_STRING_LIKE => "~",
126
            FilterCondition::METHOD_STRING_NEQ => "!=",
127
            FilterCondition::METHOD_NUMERIC_GT => ">",
128
            FilterCondition::METHOD_NUMERIC_GTE => ">=",
129
            FilterCondition::METHOD_NUMERIC_EQ => "=",
130
            FilterCondition::METHOD_NUMERIC_LTE => "<=",
131
            FilterCondition::METHOD_NUMERIC_LT => "<",
132
            FilterCondition::METHOD_NUMERIC_NEQ => "!=",
133
            FilterCondition::METHOD_IN => "in",
134
            FilterCondition::METHOD_NIN => "not in",
135
            FilterCondition::METHOD_BOOLEAN => "is",
136
            FilterCondition::METHOD_DATETIME_GT => "after",
137
            FilterCondition::METHOD_DATETIME_GTE => "after or at",
138
            FilterCondition::METHOD_DATETIME_EQ => "at",
139
            FilterCondition::METHOD_DATETIME_LTE => "before or at",
140
            FilterCondition::METHOD_DATETIME_LT => "before",
141
            FilterCondition::METHOD_DATETIME_NEQ => "not at",
142
        ];
143
144
        if (isset($translationMap[$method])) {
145
            return $translationMap[$method];
146
        }
147
148
        throw new UQLInterpreterException("Can't translate filtering method '$method'' into a valid UQL operator");
149
    }
150
151
    /**
152
     * Transforms a subtree of the AST into a concrete filter definition.
153
     * This function recursively builds all sub-trees.
154
     *
155
     * @param ASTGroup|ASTAssertion|mixed $astSubtree
156
     *
157
     * TODO: This looks like it should not be public (it is only used in tests).
158
     * We could move it and it's dependencies to its own class so that it can be tested
159
     *
160
     * @return Filter
161
     * @throws \Exception
162
     */
163
    public function buildFilter($astSubtree)
164
    {
165
        if ($astSubtree instanceof ASTGroup) {
166
            return $this->buildFilterFromASTGroup($astSubtree);
167
        }
168
169
        if ($astSubtree instanceof ASTAssertion) {
170
            $filterCondition = $this->buildFilterConditionFromASTAssertion($astSubtree);
171
            // Single filter. Wrap into dummy filter collection for consistency.
172
            $filter = new Filter();
173
            $filter[] = $filterCondition;
174
175
            return $filter;
176
        }
177
178
        throw new UQLInterpreterException('Unexpected Abstract Syntax Tree element');
179
    }
180
181
    /**
182
     * Translate <operator> tokens into Filter Methods.
183
     *
184
     * @param string         $token
185
     * @param FieldInterface $dataSourceElement
186
     *
187
     * @throws UQLInterpreterException
188
     * @return mixed
189
     */
190
    public function translateOperator($token, FieldInterface $dataSourceElement)
191
    {
192
        $translationTable = [
193
            "T_OP_LT" => [
194
                FilterCondition::METHOD_NUMERIC_LT,
195
                FilterCondition::METHOD_DATETIME_LT,
196
            ],
197
            "T_OP_LTE" => [
198
                FilterCondition::METHOD_NUMERIC_LTE,
199
                FilterCondition::METHOD_DATETIME_LTE,
200
            ],
201
            "T_OP_EQ" => [
202
                FilterCondition::METHOD_NUMERIC_EQ,
203
                FilterCondition::METHOD_STRING_EQ,
204
                FilterCondition::METHOD_DATETIME_EQ,
205
                FilterCondition::METHOD_BOOLEAN,
206
            ],
207
            "T_OP_GTE" => [
208
                FilterCondition::METHOD_NUMERIC_GTE,
209
                FilterCondition::METHOD_DATETIME_GTE,
210
            ],
211
            "T_OP_GT" => [
212
                FilterCondition::METHOD_NUMERIC_GT,
213
                FilterCondition::METHOD_DATETIME_GT,
214
            ],
215
            "T_OP_NEQ" => [
216
                FilterCondition::METHOD_NUMERIC_NEQ,
217
                FilterCondition::METHOD_STRING_NEQ,
218
                FilterCondition::METHOD_DATETIME_NEQ,
219
            ],
220
            "T_OP_LIKE" => [
221
                FilterCondition::METHOD_STRING_LIKE,
222
            ],
223
            "T_OP_IN" => [
224
                FilterCondition::METHOD_IN,
225
            ],
226
            "T_OP_NIN" => [
227
                FilterCondition::METHOD_NIN,
228
            ],
229
        ];
230
231
        if (!isset($translationTable[$token])) {
232
            throw new UQLInterpreterException('Unable to translate token ' . $token . ' to a valid filtering method. Unknown token.');
233
        }
234
        $possibleMethods = $translationTable[$token];
235
236
        // See if any of the methods is the default of the data type
237
        $dataType = $dataSourceElement->getDataType();
238
        foreach ($possibleMethods as $possibleMethod) {
239
            if ($possibleMethod === $dataType->getDefaultFilterMethod()) {
240
                return $possibleMethod;
241
            }
242
        }
243
244
        // Else, just accept the first one in the available methods
245
        foreach ($possibleMethods as $possibleMethod) {
246
            if (in_array($possibleMethod, $dataType->getAvailableFilterMethods())) {
247
                return $possibleMethod;
248
            }
249
        }
250
251
        throw new UQLInterpreterException('Unable to translate token ' . $token . ' to a valid filtering method. No methods are valid for the data type "' . $dataType->getName() . '" for data element "' . $dataSourceElement->getUniqueName() . '"');
0 ignored issues
show
Consider using $dataType->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
252
    }
253
254
    /**
255
     * Translate Lexer <logic> tokens into Filter Condition Types.
256
     *
257
     * @param $token
258
     *
259
     * @return string
260
     * @throws \Exception
261
     */
262
    private function translateLogic($token)
263
    {
264
        $translationTable = [
265
            "T_LOGIC_AND" => Filter::CONDITION_TYPE_AND,
266
            "T_LOGIC_OR" => Filter::CONDITION_TYPE_OR,
267
            "T_LOGIC_XOR" => Filter::CONDITION_TYPE_XOR,
268
        ];
269
270
        if (isset($translationTable[$token])) {
271
            return $translationTable[$token];
272
        }
273
274
        throw new \Exception('Unable to translate token ' . $token . ' to a valid filter condition type.');
275
    }
276
277
    /**
278
     * Trim and clean up the value to be set in the filter.
279
     *
280
     * @param mixed $value
281
     *
282
     * @return mixed
283
     */
284
    private function parseValue($value)
285
    {
286
        if (is_bool($value)) {
287
            return $value ? "1" : "0";
288
        }
289
290
        return trim($value, "\"'");
291
    }
292
293
    /**
294
     * @param ASTFunctionCall $functionCall
295
     *
296
     * @return mixed
297
     * @throws FunctionNotFoundException
298
     * @throws UQLInterpreterException
299
     */
300
    private function callFunction(ASTFunctionCall $functionCall)
301
    {
302
        $functionName = $functionCall->getFunctionName();
303
        $function = $this->extensionContainer->getFunction($functionName);
304
        $arguments = $this->getFunctionArguments($functionCall, $function);
305
306
        try {
307
            return $function->call($arguments);
308
        } catch (\Exception $e) {
309
            throw new UQLInterpreterException("The execution of function '$functionName' failed. Please check the arguments are valid. (" . $e->getMessage() . ")");
310
        }
311
    }
312
313
    /**
314
     * @param array $elements
315
     *
316
     * @return array
317
     */
318
    private function parseArray($elements)
319
    {
320
        $array = [];
321
        foreach ($elements as $element) {
322
            $array[] = $this->parseValue($element);
323
        }
324
325
        return $array;
326
    }
327
328
    /**
329
     * @param string $identifier
330
     *
331
     * @return Field
332
     * @throws UQLInterpreterException
333
     */
334
    private function matchDataSourceElement($identifier)
335
    {
336
        if (!$this->caseSensitive) {
337
            $identifier = strtolower($identifier);
338
        }
339
340
        if (!isset($this->dataSourceElements[$identifier])) {
341
            throw new UQLInterpreterException('Unknown filtering element "' . $identifier . '"');
342
        }
343
344
        return $this->dataSourceElements[$identifier];
345
    }
346
347
    /**
348
     * @param ASTAssertion $astSubtree
349
     *
350
     * @throws UQLInterpreterException
351
     *
352
     * @return array|mixed
353
     */
354
    private function getValue(ASTAssertion $astSubtree)
355
    {
356
        $value = $astSubtree->getValue();
357
        $operator = $astSubtree->getOperator();
358
359
        if ($value instanceof ASTFunctionCall) {
360
            return $this->callFunction($value);
361
        }
362
363
        if (in_array($operator, ['T_OP_IN', 'T_OP_NIN'])) {
364
            if (!($value instanceof ASTArray)) {
365
                throw new UQLInterpreterException('Only arrays are valid arguments for IN / NOT IN statements');
366
            }
367
368
            return $this->parseArray($value->getElements());
369
        }
370
371
        if (null === $value) {
372
            if (!in_array($operator, ['T_OP_EQ', 'T_OP_NEQ'])) {
373
                throw new UQLInterpreterException('Only IS / IS NOT operator can be used to compare against null value');
374
            }
375
376
            return null;
377
        }
378
379
        return $this->parseValue($value);
380
    }
381
382
    /**
383
     * @param ASTAssertion $astSubtree
384
     *
385
     * @throws UQLInterpreterException
386
     *
387
     * @return FilterCondition
388
     */
389
    private function buildFilterConditionFromASTAssertion(ASTAssertion $astSubtree)
390
    {
391
        $field = $this->matchDataSourceElement($astSubtree->getIdentifier());
392
        $method = $this->translateOperator($astSubtree->getOperator(), $field);
393
        $value = $this->getValue($astSubtree);
394
395
        return $this->filterConditionFactory->create($field, $method, $value);
396
    }
397
398
    /**
399
     * @param ASTGroup $astSubtree
400
     *
401
     * @throws UQLInterpreterException
402
     * @throws \Exception
403
     *
404
     * @return Filter
405
     */
406
    private function buildFilterFromASTGroup(ASTGroup $astSubtree)
407
    {
408
        $filter = new Filter();
409
        $condition = $this->translateLogic($astSubtree->getLogic());
410
        $filter->setConditionType($condition);
411
        foreach ($astSubtree->getElements() as $element) {
412
            if ($element instanceof ASTGroup) {
413
                $filter[] = $this->buildFilterFromASTGroup($element);
414
            }
415
            if ($element instanceof ASTAssertion) {
416
                $filter[] = $this->buildFilterConditionFromASTAssertion($element);
417
            }
418
        }
419
420
        return $filter;
421
    }
422
423
    /**
424
     * @param ASTFunctionCall      $functionCall
425
     * @param UqlFunctionInterface $function
426
     *
427
     * @return array
428
     */
429
    private function getFunctionArguments(ASTFunctionCall $functionCall, $function)
430
    {
431
        $arguments = $functionCall->getArguments();
432
433
        if ($function instanceof ContextAwareUqlFunction) {
434
            $context = $this->contextFactory->create($this->dataSource->getEntityClass());
435
            array_unshift($arguments, $context);
436
        }
437
438
        return $arguments;
439
    }
440
}
441