Issues (59)

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.

Service/SettingsManager.php (4 issues)

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
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\SettingsBundle\Service;
13
14
use Doctrine\Common\Cache\CacheProvider;
15
use ONGR\CookiesBundle\Cookie\Model\GenericCookie;
16
use ONGR\ElasticsearchBundle\Result\Aggregation\AggregationValue;
17
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
18
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\FilterAggregation;
19
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\TermsAggregation;
20
use ONGR\ElasticsearchDSL\Aggregation\Metric\TopHitsAggregation;
21
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
22
use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery;
23
use ONGR\SettingsBundle\Event\Events;
24
use ONGR\SettingsBundle\Event\SettingActionEvent;
25
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
use ONGR\ElasticsearchBundle\Service\Repository;
27
use ONGR\ElasticsearchBundle\Service\Manager;
28
use ONGR\SettingsBundle\Document\Setting;
29
use Symfony\Component\Serializer\Exception\LogicException;
30
31
/**
32
 * Class SettingsManager responsible for managing settings actions.
33
 */
34
class SettingsManager
35
{
36
    /**
37
     * Symfony event dispatcher.
38
     *
39
     * @var EventDispatcherInterface
40
     */
41
    private $eventDispatcher;
42
43
    /**
44
     * Elasticsearch manager which handles setting repository.
45
     *
46
     * @var Manager
47
     */
48
    private $manager;
49
50
    /**
51
     * Settings repository.
52
     *
53
     * @var Repository
54
     */
55
    private $repo;
56
57
    /**
58
     * Cache pool container.
59
     *
60
     * @var CacheProvider
61
     */
62
    private $cache;
63
64
    /**
65
     * Cookie storage for active cookies.
66
     *
67
     * @var GenericCookie
68
     */
69
    private $activeProfilesCookie;
70
71
    /**
72
     * Cookie storage for active cookies.
73
     *
74
     * @var GenericCookie
75
     */
76
    private $activeExperimentProfilesCookie;
77
78
    /**
79
     * Active profiles setting name to store in the cache engine.
80
     *
81
     * @var string
82
     */
83
    private $activeProfilesSettingName;
84
85
    /**
86
     * Active profiles list collected from es, cache and cookie.
87
     *
88
     * @var array
89
     */
90
    private $activeProfilesList = [];
91
92
    /**
93
     * Active experiments setting name to store in the cache engine.
94
     *
95
     * @var string
96
     */
97
    private $activeExperimentsSettingName;
98
99
    /**
100
     * @param Repository               $repo
101
     * @param EventDispatcherInterface $eventDispatcher
102
     */
103
    public function __construct(
104
        $repo,
105
        EventDispatcherInterface $eventDispatcher
106
    ) {
107
        $this->repo = $repo;
108
        $this->manager = $repo->getManager();
109
        $this->eventDispatcher = $eventDispatcher;
110
    }
111
112
    /**
113
     * @return CacheProvider
114
     */
115
    public function getCache()
116
    {
117
        return $this->cache;
118
    }
119
120
    /**
121
     * @param CacheProvider $cache
122
     */
123
    public function setCache($cache)
124
    {
125
        $this->cache = $cache;
126
    }
127
128
    /**
129
     * @return GenericCookie
130
     */
131
    public function getActiveProfilesCookie()
132
    {
133
        return $this->activeProfilesCookie;
134
    }
135
136
    /**
137
     * @param GenericCookie $activeProfilesCookie
138
     */
139
    public function setActiveProfilesCookie($activeProfilesCookie)
140
    {
141
        $this->activeProfilesCookie = $activeProfilesCookie;
142
    }
143
144
    /**
145
     * @return GenericCookie
146
     */
147
    public function getActiveExperimentProfilesCookie()
148
    {
149
        return $this->activeExperimentProfilesCookie;
150
    }
151
152
    /**
153
     * @param GenericCookie $activeExperimentProfilesCookie
154
     */
155
    public function setActiveExperimentProfilesCookie($activeExperimentProfilesCookie)
156
    {
157
        $this->activeExperimentProfilesCookie = $activeExperimentProfilesCookie;
158
    }
159
160
    /**
161
     * @return string
162
     */
163
    public function getActiveProfilesSettingName()
164
    {
165
        return $this->activeProfilesSettingName;
166
    }
167
168
    /**
169
     * @param string $activeProfilesSettingName
170
     */
171
    public function setActiveProfilesSettingName($activeProfilesSettingName)
172
    {
173
        $this->activeProfilesSettingName = $activeProfilesSettingName;
174
    }
175
176
    /**
177
     * @return array
178
     */
179
    public function getActiveProfilesList()
180
    {
181
        return $this->activeProfilesList;
182
    }
183
184
    /**
185
     * @param array $activeProfilesList
186
     */
187
    public function setActiveProfilesList(array $activeProfilesList)
188
    {
189
        $this->activeProfilesList = $activeProfilesList;
190
    }
191
192
    /**
193
     * @param array $activeProfilesList
194
     */
195
    public function appendActiveProfilesList(array $activeProfilesList)
196
    {
197
        $this->activeProfilesList = array_merge($this->activeProfilesList, $activeProfilesList);
198
    }
199
200
    /**
201
     * @return string
202
     */
203
    public function getActiveExperimentsSettingName()
204
    {
205
        return $this->activeExperimentsSettingName;
206
    }
207
208
    /**
209
     * @param string $activeExperimentsSettingName
210
     */
211
    public function setActiveExperimentsSettingName($activeExperimentsSettingName)
212
    {
213
        $this->activeExperimentsSettingName = $activeExperimentsSettingName;
214
    }
215
216
    /**
217
     * Creates setting.
218
     *
219
     * @param array        $data
220
     *
221
     * @return Setting
222
     */
223
    public function create(array $data = [])
224
    {
225
        $data = array_filter($data);
226
        if (!isset($data['name']) || !isset($data['type'])) {
227
            throw new \LogicException('Missing one of the mandatory field!');
228
        }
229
230
        if (!isset($data['value'])) {
231
            $data['value'] = 0;
232
        }
233
234
        $name = $data['name'];
235
        $existingSetting = $this->get($name);
236
237
        if ($existingSetting) {
238
            throw new \LogicException(sprintf('Setting %s already exists.', $name));
239
        }
240
241
        $settingClass = $this->repo->getClassName();
242
        /** @var Setting $setting */
243
        $setting = new $settingClass();
244
245
        $this->eventDispatcher->dispatch(Events::PRE_CREATE, new SettingActionEvent($name, $data, $setting));
246
247
        #TODO Introduce array populate function in Setting document instead of this foreach.
248
        foreach ($data as $key => $value) {
249
            $setting->{'set'.ucfirst($key)}($value);
250
        }
251
252
        $this->manager->persist($setting);
253
        $this->manager->commit();
254
255
        $this->eventDispatcher->dispatch(Events::POST_CREATE, new SettingActionEvent($name, $data, $setting));
256
257
        return $setting;
258
    }
259
260
    /**
261
     * Overwrites setting parameters with given name.
262
     *
263
     * @param string      $name
264
     * @param array       $data
265
     *
266
     * @return Setting
267
     */
268
    public function update($name, $data = [])
269
    {
270
        $setting = $this->get($name);
271
272
        if (!$setting) {
273
            throw new \LogicException(sprintf('Setting %s not exist.', $name));
274
        }
275
276
        $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $data, $setting));
277
278
        #TODO Add populate function to document class
279
        foreach ($data as $key => $value) {
280
            $setting->{'set'.ucfirst($key)}($value);
281
        }
282
283
        $this->manager->persist($setting);
284
        $this->manager->commit();
285
        $this->cache->delete($name);
286
287
        if ($setting->getType() == 'experiment' || $setting->getName() == 'ongr_active_experiments') {
288
            $this->getActiveExperimentProfilesCookie()->setClear(true);
289
        }
290
291
        $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $data, $setting));
292
293
        return $setting;
294
    }
295
296
    /**
297
     * Deletes a setting.
298
     *
299
     * @param string    $name
300
     *
301
     * @throws \LogicException
302
     * @return array
303
     */
304
    public function delete($name)
305
    {
306
        if ($this->has($name)) {
307
            $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, [], null));
308
309
            $setting = $this->get($name);
310
            $this->cache->delete($name);
311
            $response = $this->repo->remove($setting->getId());
312
313
            if ($setting->getType() == 'experiment') {
314
                $this->cache->delete($this->activeExperimentsSettingName);
315
                $this->getActiveExperimentProfilesCookie()->setClear(true);
316
            }
317
318
            $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $response, $setting));
0 ignored issues
show
$response is of type callable, but the function expects a null|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
319
320
            return $response;
321
        }
322
323
        throw new \LogicException(sprintf('Setting with name %s doesn\'t exist.', $name));
324
    }
325
326
    /**
327
     * Returns setting object.
328
     *
329
     * @param string $name
330
     *
331
     * @return Setting
332
     */
333
    public function get($name)
334
    {
335
        $this->eventDispatcher->dispatch(Events::PRE_GET, new SettingActionEvent($name, [], null));
336
337
        /** @var Setting $setting */
338
        $setting = $this->repo->findOneBy(['name.name' => $name]);
339
340
        $this->eventDispatcher->dispatch(Events::PRE_GET, new SettingActionEvent($name, [], $setting));
341
342
        return $setting;
343
    }
344
345
    /**
346
     * Returns setting object.
347
     *
348
     * @param string $name
349
     *
350
     * @return bool
351
     */
352
    public function has($name)
353
    {
354
        /** @var Setting $setting */
355
        $setting = $this->repo->findOneBy(['name.name' => $name]);
356
357
        if ($setting) {
358
            return true;
359
        }
360
361
        return false;
362
    }
363
364
    /**
365
     * Get setting value by current active profiles setting.
366
     *
367
     * @param string $name
368
     * @param bool $default
369
     *
370
     * @return string|array|bool
371
     */
372
    public function getValue($name, $default = null)
373
    {
374
        $setting = $this->get($name);
375
376
        if ($setting) {
377
            return $setting->getValue();
378
        }
379
380
        return $default;
381
    }
382
383
    /**
384
     * Get setting value by checking also from cache engine.
385
     *
386
     * @param string $name
387
     * @param bool   $checkWithActiveProfiles Checks if setting is in active profile.
388
     *
389
     * @return mixed
390
     */
391
    public function getCachedValue($name, $checkWithActiveProfiles = true)
392
    {
393
        if ($this->cache->contains($name)) {
394
            $setting = $this->cache->fetch($name);
395
        } elseif ($this->has($name)) {
396
            $settingDocument = $this->get($name);
397
            $setting = [
398
                'value' => $settingDocument->getValue(),
399
                'profiles' => $settingDocument->getProfile(),
400
            ];
401
            $this->cache->save($name, $setting);
402
        } else {
403
            return null;
404
        }
405
406
        if ($checkWithActiveProfiles) {
407
            if (count(array_intersect($this->getActiveProfiles(), $setting['profiles']))) {
408
                return $setting['value'];
409
            }
410
411
            return null;
412
        }
413
414
        return $setting['value'];
415
    }
416
417
    /**
418
     * Get all full profile information.
419
     *
420
     * @return array
421
     */
422
    public function getAllProfiles()
423
    {
424
        $profiles = [];
425
426
        $search = $this->repo->createSearch();
427
        $filter = new BoolQuery();
428
        $filter->add(new TermQuery('type', 'experiment'), BoolQuery::MUST_NOT);
429
        $topHitsAgg = new TopHitsAggregation('documents', 20);
430
        $termAgg = new TermsAggregation('profiles', 'profile.profile');
431
        $filterAgg = new FilterAggregation('filter', $filter);
432
        $termAgg->addAggregation($topHitsAgg);
433
        $filterAgg->addAggregation($termAgg);
434
        $search->addAggregation($filterAgg);
435
436
        $result = $this->repo->findDocuments($search);
437
438
        /** @var Setting $activeProfiles */
439
        $activeProfiles = $this->getValue($this->activeProfilesSettingName, []);
0 ignored issues
show
array() is of type array, but the function expects a boolean|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
440
441
        /** @var AggregationValue $agg */
442
        foreach ($result->getAggregation('filter')->getAggregation('profiles') as $agg) {
443
            $settings = [];
444
            $docs = $agg->getAggregation('documents');
445
            foreach ($docs['hits']['hits'] as $doc) {
446
                $settings[] = $doc['_source']['name'];
447
            }
448
            $name = $agg->getValue('key');
449
            $profiles[] = [
450
                'active' => $activeProfiles ? in_array($agg->getValue('key'), (array)$activeProfiles) : false,
451
                'name' => $name,
452
                'settings' => implode(', ', $settings),
453
            ];
454
        }
455
456
        return $profiles;
457
    }
458
459
    /**
460
     * Returns profiles settings array
461
     *
462
     * @param string $profile
463
     *
464
     * @return array
465
     */
466
    public function getProfileSettings($profile)
467
    {
468
        $search = $this->repo->createSearch();
469
        $termQuery = new TermQuery('profile', $profile);
470
        $search->addQuery($termQuery);
471
        $search->setSize(1000);
472
473
        $settings = $this->repo->findArray($search);
474
475
        return $settings;
476
    }
477
478
    /**
479
     * Returns cached active profiles names list.
480
     *
481
     * @return array
482
     */
483
    public function getActiveProfiles()
484
    {
485
        if ($this->cache->contains($this->activeProfilesSettingName)) {
486
            $profiles = $this->cache->fetch($this->activeProfilesSettingName);
487
        } else {
488
            $profiles = [];
489
            $allProfiles = $this->getAllProfiles();
490
491
            foreach ($allProfiles as $profile) {
492
                if (!$profile['active']) {
493
                    continue;
494
                }
495
496
                $profiles[] = $profile['name'];
497
            }
498
499
            $this->cache->save($this->activeProfilesSettingName, $profiles);
500
        }
501
502
        $profiles = array_merge($profiles, $this->activeProfilesList);
503
504
        return $profiles;
505
    }
506
507
    /**
508
     * @return DocumentIterator
509
     */
510
    public function getAllExperiments()
511
    {
512
        $experiments = $this->repo->findBy(['type' => 'experiment']);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->repo->findBy(arra...ype' => 'experiment')); of type array|ONGR\Elasticsearch...Result\DocumentIterator adds the type array to the return on line 514 which is incompatible with the return type documented by ONGR\SettingsBundle\Serv...ager::getAllExperiments of type ONGR\ElasticsearchBundle\Result\DocumentIterator.
Loading history...
513
514
        return $experiments;
515
    }
516
517
    /**
518
     * Returns an array of active experiments names either from cache or from es.
519
     * If none are found, the setting with no active experiments is created.
520
     *
521
     * @return array
522
     */
523
    public function getActiveExperiments()
524
    {
525
        if ($this->cache->contains($this->activeExperimentsSettingName)) {
526
            return $this->cache->fetch($this->activeExperimentsSettingName)['value'];
527
        }
528
529
        if ($this->has($this->activeExperimentsSettingName)) {
530
            $experiments = $this->get($this->activeExperimentsSettingName)->getValue();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get($this->active...ttingName)->getValue(); of type string adds the type string to the return on line 544 which is incompatible with the return type documented by ONGR\SettingsBundle\Serv...r::getActiveExperiments of type array.
Loading history...
531
        } else {
532
            $this->create(
533
                [
534
                    'name' => $this->activeExperimentsSettingName,
535
                    'value' => [],
536
                    'type' => 'hidden',
537
                ]
538
            );
539
            $experiments = [];
540
        }
541
542
        $this->cache->save($this->activeExperimentsSettingName, ['value' => $experiments]);
543
544
        return $experiments;
545
    }
546
547
    /**
548
     * @param string $name
549
     */
550
    public function toggleExperiment($name)
551
    {
552
        if (!$this->has($this->activeExperimentsSettingName)) {
553
            throw new LogicException(
554
                sprintf('The setting `%s` is not set', $this->activeExperimentsSettingName)
555
            );
556
        }
557
558
        $setting = $this->get($this->activeExperimentsSettingName);
559
        $experiments = $setting->getValue();
560
561
        if (is_array($experiments)) {
562
            if (($key = array_search($name, $experiments)) !== false) {
563
                unset($experiments[$key]);
564
                $experiments = array_values($experiments);
565
            } else {
566
                $experiments[] = $name;
567
            }
568
        } else {
569
            $experiments = [$name];
570
        }
571
572
        $this->update($setting->getName(), ['value' => $experiments]);
573
    }
574
575
    /**
576
     * Get full experiment by caching.
577
     *
578
     * @param string $name
579
     *
580
     * @return array|null
581
     *
582
     * @throws LogicException
583
     */
584
    public function getCachedExperiment($name)
585
    {
586
        if ($this->cache->contains($name)) {
587
            $experiment = $this->cache->fetch($name);
588
        } elseif ($this->has($name)) {
589
            $experiment = $this->get($name)->getSerializableData();
590
        } else {
591
            return null;
592
        }
593
594
        if (!isset($experiment['type']) || $experiment['type'] !== 'experiment') {
595
            throw new LogicException(
596
                sprintf('The setting `%s` was found but it is not an experiment', $name)
597
            );
598
        }
599
600
        $this->cache->save($name, $experiment);
601
602
        return $experiment;
603
    }
604
}
605