Issues (63)

Security Analysis    not enabled

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.

SonataNotificationExtension.php (1 issue)

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
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\NotificationBundle\DependencyInjection;
15
16
use Sonata\Doctrine\Mapper\DoctrineCollector;
17
use Sonata\EasyExtendsBundle\Mapper\DoctrineCollector as DeprecatedDoctrineCollector;
18
use Sonata\NotificationBundle\Backend\AMQPBackend;
19
use Sonata\NotificationBundle\Backend\MessageManagerBackend;
20
use Sonata\NotificationBundle\Model\MessageInterface;
21
use Symfony\Component\Config\FileLocator;
22
use Symfony\Component\DependencyInjection\ContainerBuilder;
23
use Symfony\Component\DependencyInjection\Definition;
24
use Symfony\Component\DependencyInjection\Loader;
25
use Symfony\Component\DependencyInjection\Reference;
26
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
27
28
class SonataNotificationExtension extends Extension
29
{
30
    /**
31
     * @var int
32
     */
33
    protected $amqpCounter = 0;
34
35
    /**
36
     * {@inheritdoc}
37
     */
38
    public function load(array $configs, ContainerBuilder $container): void
39
    {
40
        $configuration = new Configuration();
41
        $config = $this->processConfiguration($configuration, $configs);
42
43
        $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
44
45
        $this->checkConfiguration($config);
46
47
        $loader->load('core.xml');
48
49
        $loader->load('backend.xml');
50
        $loader->load('consumer.xml');
51
        $loader->load('command.xml');
52
53
        $bundles = $container->getParameter('kernel.bundles');
54
55
        if ('sonata.notification.backend.doctrine' === $config['backend']) {
56
            $loader->load('doctrine_orm.xml');
57
            $loader->load('selector.xml');
58
            $loader->load('event.xml');
59
60
            if (isset($bundles['FOSRestBundle'], $bundles['NelmioApiDocBundle'])) {
61
                $loader->load('api_controllers.xml');
62
                $loader->load('api_form.xml');
63
            }
64
65
            // for now, only support for ORM
66
            if ($config['admin']['enabled'] && isset($bundles['SonataDoctrineORMAdminBundle'])) {
67
                $loader->load('admin.xml');
68
            }
69
        }
70
71
        if ($config['consumers']['register_default']) {
72
            $loader->load('default_consumers.xml');
73
        }
74
75
        if (isset($bundles['LiipMonitorBundle'])) {
76
            $loader->load('checkmonitor.xml');
77
        }
78
79
        $container->setAlias('sonata.notification.backend', $config['backend'])->setPublic(true);
80
        $container->setParameter('sonata.notification.backend', $config['backend']);
81
82
        if (isset($bundles['SonataDoctrineBundle'])) {
83
            $this->registerSonataDoctrineMapping($config);
84
        } else {
85
            // NEXT MAJOR: Remove next line and throw error when not registering SonataDoctrineBundle
86
            $this->registerDoctrineMapping($config);
87
        }
88
89
        $this->registerParameters($container, $config);
90
        $this->configureBackends($container, $config);
91
        $this->configureClass($container, $config);
92
        $this->configureListeners($container, $config);
93
        $this->configureAdmin($container, $config);
94
    }
95
96
    /**
97
     * @param array $config
98
     */
99
    public function configureClass(ContainerBuilder $container, $config): void
100
    {
101
        // admin configuration
102
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
103
104
        // manager configuration
105
        $container->setParameter('sonata.notification.manager.message.entity', $config['class']['message']);
106
    }
107
108
    /**
109
     * @param array $config
110
     */
111
    public function configureAdmin(ContainerBuilder $container, $config): void
112
    {
113
        $container->setParameter('sonata.notification.admin.message.class', $config['admin']['message']['class']);
114
        $container->setParameter('sonata.notification.admin.message.controller', $config['admin']['message']['controller']);
115
        $container->setParameter('sonata.notification.admin.message.translation_domain', $config['admin']['message']['translation']);
116
    }
117
118
    /**
119
     * @param array $config
120
     */
121
    public function registerParameters(ContainerBuilder $container, $config): void
122
    {
123
        $container->setParameter('sonata.notification.message.class', $config['class']['message']);
124
        $container->setParameter('sonata.notification.admin.message.entity', $config['class']['message']);
125
    }
126
127
    /**
128
     * @param array $config
129
     */
130
    public function configureBackends(ContainerBuilder $container, $config): void
131
    {
132
        if (isset($config['backends']['rabbitmq']) && 'sonata.notification.backend.rabbitmq' === $config['backend']) {
133
            $this->configureRabbitmq($container, $config);
134
        } else {
135
            $container->removeDefinition('sonata.notification.backend.rabbitmq');
136
        }
137
138
        if (isset($config['backends']['doctrine']) && 'sonata.notification.backend.doctrine' === $config['backend']) {
139
            $checkLevel = [
140
                MessageInterface::STATE_DONE => $config['backends']['doctrine']['states']['done'],
141
                MessageInterface::STATE_ERROR => $config['backends']['doctrine']['states']['error'],
142
                MessageInterface::STATE_IN_PROGRESS => $config['backends']['doctrine']['states']['in_progress'],
143
                MessageInterface::STATE_OPEN => $config['backends']['doctrine']['states']['open'],
144
            ];
145
146
            $pause = $config['backends']['doctrine']['pause'];
147
            $maxAge = $config['backends']['doctrine']['max_age'];
148
            $batchSize = $config['backends']['doctrine']['batch_size'];
149
            $container
150
                ->setAlias('sonata.notification.manager.message', $config['backends']['doctrine']['message_manager'])
151
                ->setPublic(true);
152
153
            $this->configureDoctrineBackends($container, $config, $checkLevel, $pause, $maxAge, $batchSize);
154
        } else {
155
            $container->removeDefinition('sonata.notification.backend.doctrine');
156
        }
157
    }
158
159
    /**
160
     * NEXT_MAJOR: Remove this method.
161
     */
162
    public function registerDoctrineMapping(array $config): void
163
    {
164
        @trigger_error(
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
165
            'Using SonataEasyExtendsBundle is deprecated since sonata-project/notification-bundle 3.9. Please register SonataDoctrineBundle as a bundle instead.',
166
            E_USER_DEPRECATED
167
        );
168
169
        $collector = DeprecatedDoctrineCollector::getInstance();
170
171
        $collector->addIndex($config['class']['message'], 'idx_state', [
172
            'state',
173
        ]);
174
175
        $collector->addIndex($config['class']['message'], 'idx_created_at', [
176
            'created_at',
177
        ]);
178
    }
179
180
    protected function checkConfiguration(array $config): void
181
    {
182
        if (isset($config['backends']) && \count($config['backends']) > 1) {
183
            throw new \RuntimeException('more than one backend configured, you can have only one backend configuration');
184
        }
185
186
        if (!isset($config['backends']['rabbitmq']) && 'sonata.notification.backend.rabbitmq' === $config['backend']) {
187
            throw new \RuntimeException('Please configure the sonata_notification.backends.rabbitmq section');
188
        }
189
190
        if (!isset($config['backends']['doctrine']) && 'sonata.notification.backend.doctrine' === $config['backend']) {
191
            throw new \RuntimeException('Please configure the sonata_notification.backends.doctrine section');
192
        }
193
    }
194
195
    protected function configureListeners(ContainerBuilder $container, array $config): void
196
    {
197
        $ids = $config['iteration_listeners'];
198
199
        if ('sonata.notification.backend.doctrine' === $config['backend']) {
200
            // this one clean the unit of work after every iteration
201
            $ids[] = 'sonata.notification.event.doctrine_optimize';
202
203
            if ($config['backends']['doctrine']['batch_size'] > 1) {
204
                // if the backend is doctrine and the batch size > 1, then
205
                // the unit of work must be cleaned wisely to avoid any issue
206
                // while persisting entities
207
                $ids = [
208
                    'sonata.notification.event.doctrine_backend_optimize',
209
                ];
210
            }
211
        }
212
213
        $container->setParameter('sonata.notification.event.iteration_listeners', $ids);
214
    }
215
216
    /**
217
     * @param bool $checkLevel
218
     * @param int  $pause
219
     * @param int  $maxAge
220
     * @param int  $batchSize
221
     *
222
     * @throws \RuntimeException
223
     */
224
    protected function configureDoctrineBackends(ContainerBuilder $container, array $config, $checkLevel, $pause, $maxAge, $batchSize): void
225
    {
226
        $queues = $config['queues'];
227
        $qBackends = [];
228
229
        $definition = $container->getDefinition('sonata.notification.backend.doctrine');
230
231
        // no queue defined, set a default one
232
        if (0 === \count($queues)) {
233
            $queues = [[
234
                'queue' => 'default',
235
                'default' => true,
236
                'types' => [],
237
            ]];
238
        }
239
240
        $defaultSet = false;
241
        $declaredQueues = [];
242
243
        foreach ($queues as $pos => &$queue) {
244
            if (\in_array($queue['queue'], $declaredQueues, true)) {
245
                throw new \RuntimeException('The doctrine backend does not support 2 identicals queue name, please rename one queue');
246
            }
247
248
            $declaredQueues[] = $queue['queue'];
249
250
            // make the configuration compatible with old code and rabbitmq
251
            if (isset($queue['routing_key']) && \strlen($queue['routing_key']) > 0) {
252
                $queue['types'] = [$queue['routing_key']];
253
            }
254
255
            if (empty($queue['types']) && false === $queue['default']) {
256
                throw new \RuntimeException('You cannot declared a doctrine queue with no type defined with default = false');
257
            }
258
259
            if (!empty($queue['types']) && true === $queue['default']) {
260
                throw new \RuntimeException('You cannot declared a doctrine queue with types defined with default = true');
261
            }
262
263
            $id = $this->createDoctrineQueueBackend($container, $definition->getArgument(0), $checkLevel, $pause, $maxAge, $batchSize, $queue['queue'], $queue['types']);
264
            $qBackends[$pos] = [
265
                'types' => $queue['types'],
266
                'backend' => new Reference($id),
267
            ];
268
269
            if (true === $queue['default']) {
270
                if (true === $defaultSet) {
271
                    throw new \RuntimeException('You can only set one doctrine default queue in your sonata notification configuration.');
272
                }
273
274
                $defaultSet = true;
275
                $defaultQueue = $queue['queue'];
276
            }
277
        }
278
279
        if (false === $defaultSet) {
280
            throw new \RuntimeException('You need to specify a valid default queue for the doctrine backend!');
281
        }
282
283
        $definition
284
            ->replaceArgument(1, $queues)
285
            ->replaceArgument(2, $defaultQueue)
286
            ->replaceArgument(3, $qBackends)
287
        ;
288
    }
289
290
    /**
291
     * @param string $manager
292
     * @param bool   $checkLevel
293
     * @param int    $pause
294
     * @param int    $maxAge
295
     * @param int    $batchSize
296
     * @param string $key
297
     *
298
     * @return string
299
     */
300
    protected function createDoctrineQueueBackend(ContainerBuilder $container, $manager, $checkLevel, $pause, $maxAge, $batchSize, $key, array $types = [])
301
    {
302
        if ('' === $key) {
303
            $id = 'sonata.notification.backend.doctrine.default_'.$this->amqpCounter++;
304
        } else {
305
            $id = 'sonata.notification.backend.doctrine.'.$key;
306
        }
307
308
        $definition = new Definition(MessageManagerBackend::class, [$manager, $checkLevel, $pause, $maxAge, $batchSize, $types]);
309
        $definition->setPublic(false);
310
311
        $container->setDefinition($id, $definition);
312
313
        return $id;
314
    }
315
316
    protected function configureRabbitmq(ContainerBuilder $container, array $config): void
317
    {
318
        $queues = $config['queues'];
319
        $connection = $config['backends']['rabbitmq']['connection'];
320
        $baseExchange = $config['backends']['rabbitmq']['exchange'];
321
        $amqBackends = [];
322
323
        if (0 === \count($queues)) {
324
            $queues = [[
325
                'queue' => 'default',
326
                'default' => true,
327
                'routing_key' => '',
328
                'recover' => false,
329
                'dead_letter_exchange' => null,
330
                'dead_letter_routing_key' => null,
331
                'ttl' => null,
332
                'prefetch_count' => null,
333
            ]];
334
        }
335
336
        $deadLetterRoutingKeys = $this->getQueuesParameters('dead_letter_routing_key', $queues);
337
        $routingKeys = $this->getQueuesParameters('routing_key', $queues);
338
339
        foreach ($deadLetterRoutingKeys as $key) {
340
            if (!\in_array($key, $routingKeys, true)) {
341
                throw new \RuntimeException(sprintf(
342
                    'You must configure the queue having the routing_key "%s" same as dead_letter_routing_key',
343
                    $key
344
                ));
345
            }
346
        }
347
348
        $declaredQueues = [];
349
350
        $defaultSet = false;
351
        foreach ($queues as $pos => $queue) {
352
            if (\in_array($queue['queue'], $declaredQueues, true)) {
353
                throw new \RuntimeException('The RabbitMQ backend does not support 2 identicals queue name, please rename one queue');
354
            }
355
356
            $declaredQueues[] = $queue['queue'];
357
358
            if ($queue['dead_letter_routing_key']) {
359
                if (null === $queue['dead_letter_exchange']) {
360
                    throw new \RuntimeException(
361
                        'dead_letter_exchange must be configured when dead_letter_routing_key is set'
362
                    );
363
                }
364
            }
365
366
            if (\in_array($queue['routing_key'], $deadLetterRoutingKeys, true)) {
367
                $exchange = $this->getAMQPDeadLetterExchangeByRoutingKey($queue['routing_key'], $queues);
368
            } else {
369
                $exchange = $baseExchange;
370
            }
371
372
            $id = $this->createAMQPBackend(
373
                $container,
374
                $exchange,
375
                $queue['queue'],
376
                $queue['recover'],
377
                $queue['routing_key'],
378
                $queue['dead_letter_exchange'],
379
                $queue['dead_letter_routing_key'],
380
                $queue['ttl'],
381
                $queue['prefetch_count']
382
            );
383
384
            $amqBackends[$pos] = [
385
                'type' => $queue['routing_key'],
386
                'backend' => new Reference($id),
387
            ];
388
389
            if (true === $queue['default']) {
390
                if (true === $defaultSet) {
391
                    throw new \RuntimeException('You can only set one rabbitmq default queue in your sonata notification configuration.');
392
                }
393
                $defaultSet = true;
394
                $defaultQueue = $queue['routing_key'];
395
            }
396
        }
397
398
        if (false === $defaultSet) {
399
            throw new \RuntimeException('You need to specify a valid default queue for the rabbitmq backend!');
400
        }
401
402
        $container->getDefinition('sonata.notification.backend.rabbitmq')
403
            ->replaceArgument(0, $connection)
404
            ->replaceArgument(1, $queues)
405
            ->replaceArgument(2, $defaultQueue)
406
            ->replaceArgument(3, $amqBackends)
407
        ;
408
    }
409
410
    /**
411
     * @param string   $exchange
412
     * @param string   $name
413
     * @param string   $recover
414
     * @param string   $key
415
     * @param string   $deadLetterExchange
416
     * @param string   $deadLetterRoutingKey
417
     * @param int|null $ttl
418
     * @param int|null $prefetchCount
419
     *
420
     * @return string
421
     */
422
    protected function createAMQPBackend(ContainerBuilder $container, $exchange, $name, $recover, $key = '', $deadLetterExchange = null, $deadLetterRoutingKey = null, $ttl = null, $prefetchCount = null)
423
    {
424
        $id = 'sonata.notification.backend.rabbitmq.'.$this->amqpCounter++;
425
426
        $definition = new Definition(
427
            AMQPBackend::class,
428
            [
429
                $exchange,
430
                $name,
431
                $recover,
432
                $key,
433
                $deadLetterExchange,
434
                $deadLetterRoutingKey,
435
                $ttl,
436
                $prefetchCount,
437
            ]
438
        );
439
        $definition->setPublic(false);
440
        $container->setDefinition($id, $definition);
441
442
        return $id;
443
    }
444
445
    private function registerSonataDoctrineMapping(array $config): void
446
    {
447
        $collector = DoctrineCollector::getInstance();
448
449
        $collector->addIndex($config['class']['message'], 'idx_state', ['state']);
450
        $collector->addIndex($config['class']['message'], 'idx_created_at', ['created_at']);
451
    }
452
453
    /**
454
     * @param string $name
455
     *
456
     * @return string[]
457
     */
458
    private function getQueuesParameters($name, array $queues)
459
    {
460
        $params = array_unique(array_map(static function ($q) use ($name) {
461
            return $q[$name];
462
        }, $queues));
463
464
        $idx = array_search(null, $params, true);
465
        if (false !== $idx) {
466
            unset($params[$idx]);
467
        }
468
469
        return $params;
470
    }
471
472
    /**
473
     * @param string $key
474
     *
475
     * @return string
476
     */
477
    private function getAMQPDeadLetterExchangeByRoutingKey($key, array $queues)
478
    {
479
        foreach ($queues as $queue) {
480
            if ($queue['dead_letter_routing_key'] === $key) {
481
                return $queue['dead_letter_exchange'];
482
            }
483
        }
484
    }
485
}
486