Passed
Push — 2.x ( 75d9da...077994 )
by Terry
01:57
created

Firewall::setMessengers()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 18
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 30
rs 9.6666
1
<?php
2
/*
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
declare(strict_types=1);
12
13
namespace Shieldon\Firewall;
14
15
use Psr\Http\Message\ServerRequestInterface;
16
use Psr\Http\Message\ResponseInterface;
17
use Psr\Http\Server\MiddlewareInterface;
18
use Shieldon\Firewall\Kernel;
19
use Shieldon\Firewall\Captcha as Captcha;
20
use Shieldon\Firewall\Component as Component;
21
use Shieldon\Firewall\Driver as Driver;
22
use Shieldon\Firewall\Middleware as Middleware;
23
use Shieldon\Firewall\Security as Security;
24
use Shieldon\Messenger as Messenger;
25
use Shieldon\Firewall\Messenger\MessengerFactory;
26
use Shieldon\Firewall\Utils\Container;
27
use Shieldon\Firewall\Log\ActionLogger;
28
use Shieldon\Firewall\FirewallTrait;
29
use Shieldon\Psr15\RequestHandler;
30
use function Shieldon\Firewall\get_request;
31
use function Shieldon\Firewall\get_response;
32
33
use PDO;
34
use PDOException;
35
use Redis;
36
use RedisException;
37
38
use function array_column;
39
use function defined;
40
use function file_exists;
41
use function file_get_contents;
42
use function file_put_contents;
43
use function is_dir;
44
use function json_decode;
45
use function json_encode;
46
use function mkdir;
47
use function rtrim;
48
use function strpos;
49
use function umask;
50
use function time;
51
use function strtotime;
52
use function date;
53
54
/**
55
 * Managed Firewall.
56
 */
57
class Firewall
58
{
59
    use FirewallTrait;
60
61
    /**
62
     * Collection of PSR-7 or PSR-15 middlewares.
63
     *
64
     * @var array
65
     */
66
    protected $middleware = [];
67
68
    /**
69
     * Constructor.
70
     */
71
    public function __construct(?ServerRequestInterface $request = null, ?ResponseInterface $response = null)
72
    {
73
        Container::set('firewall', $this);
74
75
        $this->kernel = new Kernel($request, $response);
76
    }
77
78
    /**
79
     * Set up the path of the configuration file.
80
     *
81
     * @param string $source The path.
82
     * @param string $type   The type.
83
     * 
84
     * @return void
85
     */
86
    public function configure(string $source, string $type = 'json')
87
    {
88
        if ($type === 'json') {
89
            $this->directory = rtrim($source, '\\/');
90
            $configFilePath = $this->directory . '/' . $this->filename;
91
92
            if (file_exists($configFilePath)) {
93
                $jsonString = file_get_contents($configFilePath);
94
95
            } else {
96
                $jsonString = file_get_contents(__DIR__ . '/../../config.json');
97
98
                if (defined('PHP_UNIT_TEST')) {
99
                    $jsonString = file_get_contents(__DIR__ . '/../../tests/config.json');
100
                }
101
            }
102
103
            $this->configuration = json_decode($jsonString, true);
104
            $this->kernel->managedBy('managed');
105
106
        } elseif ($type === 'php') {
107
            $this->configuration = require $source;
108
            $this->kernel->managedBy('config');
109
        }
110
111
        $this->setup();
112
    }
113
114
    /**
115
     * Add middlewares and use them before executing Shieldon kernal.
116
     *
117
     * @param array $middleware Array of PSR-7 or PSR-15 middlewares.
118
     *
119
     * @return void
120
     */
121
    public function add($middleware)
122
    {
123
        if ($middleware instanceof MiddlewareInterface) {
0 ignored issues
show
introduced by
$middleware is never a sub-type of Psr\Http\Server\MiddlewareInterface.
Loading history...
124
            $this->middleware[] = $middleware;
125
        }
126
    }
127
128
    /**
129
     * Setup everything we need.
130
     *
131
     * @return void
132
     */
133
    public function setup(): void
134
    {
135
        $this->status = $this->getOption('daemon');
136
137
        $this->setDriver();
138
139
        $this->setChannel();
140
141
        $this->setIpSource();
142
143
        $this->setLogger();
144
145
        $this->setFilters();
146
147
        $this->setComponents();
148
149
        $this->setCaptchas();
150
151
        $this->setSessionLimit();
152
153
        $this->setCronJob();
154
155
        $this->setExcludedUrls();
156
157
        $this->setXssProtection();
158
159
        $this->setAuthentication();
160
161
        $this->setDialogUI();
162
163
        $this->setMessengers();
164
165
        $this->setMessageEvents();
166
167
        $this->setDenyAttempts();
168
169
        $this->setIptablesWatchingFolder();
170
    }
171
172
    /**
173
     * Just, run!
174
     *
175
     * @return \Psr\Http\Message\ResponseInterface
176
     */
177
    public function run(): ResponseInterface
178
    {
179
        // If settings are ready, let's start monitoring requests.
180
        if ($this->status) {
181
182
            $response = get_request();
183
184
            // PSR-15 request handler.
185
            $requestHandler = new RequestHandler();
186
187
            foreach ($this->middleware as $middleware) {
188
                $requestHandler->add($middleware);
189
            }
190
191
            $response = $requestHandler->handle($response);
192
193
            // Something is detected by Middlewares, return.
194
            if ($response->getStatusCode() !== 200) {
195
                return $response;
196
            }
197
198
            $result = $this->kernel->run();
199
200
            if ($result !== $this->kernel::RESPONSE_ALLOW) {
201
202
                if ($this->kernel->captchaResponse()) {
203
                    $this->kernel->unban();
204
205
                    $response = $response->withHeader('Location', $this->kernel->getCurrentUrl());
206
                    $response = $response->withStatus(303);
207
208
                    return $response;
209
                }
210
            }
211
        }
212
213
        return $this->kernel->respond();
214
    }
215
216
    /**
217
     * Set the channel ID.
218
     *
219
     * @return void
220
     */
221
    protected function setChannel(): void
222
    {
223
        $channelId = $this->getOption('channel_id');
224
225
        if ($channelId) {
226
            $this->kernel->setChannel($channelId);
227
        }
228
    }
229
230
    /**
231
     * Set a data driver for Shieldon use.
232
     *
233
     * @return void
234
     */
235
    protected function setDriver(): void
236
    {
237
        $driverType = $this->getOption('driver_type');
238
239
        switch ($driverType) {
240
241
            case 'redis':
242
            
243
                $redisSetting = $this->getOption('redis', 'drivers');
244
245
                try {
246
247
                    $host = '127.0.0.1';
248
                    $port = 6379;
249
250
                    if (!empty($redisSetting['host'])) {
251
                        $host = $redisSetting['host'];
252
                    }
253
254
                    if (!empty($redisSetting['port'])) {
255
                        $port = $redisSetting['port'];
256
                    }
257
258
                    // Create a Redis instance.
259
                    $redis = new Redis();
260
                    $redis->connect($host, $port);
261
262
                    if (!empty($redisSetting['auth'])) {
263
264
                        // @codeCoverageIgnoreStart
265
                        $redis->auth($redisSetting['auth']);
266
                        // @codeCoverageIgnoreEnd
267
                    }
268
269
                    // Use Redis data driver.
270
                    $this->kernel->add(new Driver\RedisDriver($redis));
271
272
                // @codeCoverageIgnoreStart
273
                } catch(RedisException $e) {
274
                    $this->status = false;
275
276
                    echo $e->getMessage();
277
                }
278
                // @codeCoverageIgnoreEnd
279
280
                break;
281
282
            case 'file':
283
            
284
                $fileSetting = $this->getOption('file', 'drivers');
285
286
                if (empty($fileSetting['directory_path'])) {
287
                    $fileSetting['directory_path'] = $this->directory;
288
                }
289
290
                // Use File data driver.
291
                $this->kernel->add(new Driver\FileDriver($fileSetting['directory_path']));
292
293
                break;
294
295
            case 'sqlite':
296
            
297
                $sqliteSetting = $this->getOption('sqlite', 'drivers');
298
299
                if (empty($sqliteSetting['directory_path'])) {
300
                    $sqliteSetting['directory_path'] = '';
301
                    $this->status = false;
302
                }
303
304
                try {
305
                    
306
                    // Specific the sqlite file location.
307
                    $sqliteLocation = $sqliteSetting['directory_path'] . '/shieldon.sqlite3';
308
309
                    // Create a PDO instance.
310
                    $pdoInstance = new PDO('sqlite:' . $sqliteLocation);
311
312
                    // Use Sqlite data driver.
313
                    $this->kernel->add(new Driver\SqliteDriver($pdoInstance));
314
    
315
                // @codeCoverageIgnoreStart
316
                } catch(PDOException $e) {
317
                    $this->status = false;
318
319
                    echo $e->getMessage();
320
                }
321
                // @codeCoverageIgnoreEnd
322
323
                break;
324
325
            case 'mysql':
326
            default:
327
328
                $mysqlSetting = $this->getOption('mysql', 'drivers');
329
330
                try {
331
332
                    // Create a PDO instance.
333
                    $pdoInstance = new PDO(
334
                        'mysql:host=' 
335
                            . $mysqlSetting['host']   . ';dbname=' 
336
                            . $mysqlSetting['dbname'] . ';charset=' 
337
                            . $mysqlSetting['charset']
338
                        , (string) $mysqlSetting['user']
339
                        , (string) $mysqlSetting['pass']
340
                    );
341
342
                    // Use MySQL data driver.
343
                    $this->kernel->add(new Driver\MysqlDriver($pdoInstance));
344
345
                // @codeCoverageIgnoreStart
346
                } catch(PDOException $e) {
347
                    echo $e->getMessage();
348
                }
349
                // @codeCoverageIgnoreEnd
350
            // end switch.
351
        }
352
    }
353
354
    /**
355
     * Set up the action logger.
356
     *
357
     * @return void
358
     */
359
    protected function setLogger(): void
360
    {
361
        $loggerSetting = $this->getOption('action', 'loggers');
362
363
        if ($loggerSetting['enable']) {
364
            if (!empty($loggerSetting['config']['directory_path'])) {
365
                $this->kernel->add(new ActionLogger($loggerSetting['config']['directory_path']));
366
            }
367
        }
368
    }
369
370
    /**
371
     * If you use CDN, please choose the real IP source.
372
     *
373
     * @return void
374
     */
375
    protected function setIpSource(): void
376
    {
377
        $ipSourceType = $this->getOption('ip_variable_source');
378
        $serverParams = get_request()->getServerParams();
379
380
        if ($ipSourceType['REMOTE_ADDR']) {
381
            $ip = $serverParams['REMOTE_ADDR'];
382
383
        // Cloudflare
384
        } elseif ($ipSourceType['HTTP_CF_CONNECTING_IP']) {
385
            $ip = $serverParams['HTTP_CF_CONNECTING_IP'];
386
387
        // Google Cloud CDN, Google Load-balancer, AWS.
388
        } elseif ($ipSourceType['HTTP_X_FORWARDED_FOR']) {
389
            $ip = $serverParams['HTTP_X_FORWARDED_FOR'];
390
391
        // KeyCDN, or other CDN providers not listed here.
392
        } elseif ($ipSourceType['HTTP_X_FORWARDED_HOST']) {
393
            $ip = $serverParams['HTTP_X_FORWARDED_HOST'];
394
395
        // Fallback.
396
        } else {
397
398
            // @codeCoverageIgnoreStart
399
            $ip = $serverParams['REMOTE_ADDR'];
400
            // @codeCoverageIgnoreEnd
401
        }
402
403
        if (empty($ip)) {
404
            // @codeCoverageIgnoreStart
405
            throw new RuntimeException('IP source is not set correctly.');
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\RuntimeException was not found. Did you mean RuntimeException? If so, make sure to prefix the type with \.
Loading history...
406
            // @codeCoverageIgnoreEnd
407
        }
408
409
        $this->kernel->setIp($ip);
410
    }
411
412
    /**
413
     * Set the filiters.
414
     *
415
     * @return void
416
     */
417
    protected function setFilters(): void
418
    {
419
        $sessionSetting = $this->getOption('session', 'filters');
420
        $cookieSetting = $this->getOption('cookie', 'filters');
421
        $refererSetting = $this->getOption('referer', 'filters');
422
        $frequencySetting = $this->getOption('frequency', 'filters');
423
424
        $filterConfig = [
425
            'session'   => $sessionSetting['enable'],
426
            'cookie'    => $cookieSetting['enable'],
427
            'referer'   => $refererSetting['enable'],
428
            'frequency' => $frequencySetting['enable'],
429
        ];
430
431
        $this->kernel->setFilters($filterConfig);
432
433
        $this->kernel->setProperty('limit_unusual_behavior', [
434
            'session' => $sessionSetting['config']['quota'] ?? 5,
435
            'cookie'  => $cookieSetting['config']['quota'] ?? 5,
436
            'referer' => $refererSetting['config']['quota'] ?? 5,
437
        ]);
438
439
        if ($frequencySetting['enable']) {
440
441
            $frequencyQuota = [
442
                's' => $frequencySetting['config']['quota_s'] ?? 2,
443
                'm' => $frequencySetting['config']['quota_m'] ?? 10,
444
                'h' => $frequencySetting['config']['quota_h'] ?? 30,
445
                'd' => $frequencySetting['config']['quota_d'] ?? 60,
446
            ];
447
448
            $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
449
        }
450
451
        if ($cookieSetting['enable']) {
452
453
            $cookieName = $cookieSetting['config']['cookie_name'] ?? 'ssjd';
454
            $cookieDomain = $cookieSetting['config']['cookie_domain'] ?? '';
455
            $cookieValue = $cookieSetting['config']['cookie_value'] ?? '1';
456
    
457
            $this->kernel->setProperty('cookie_name', $cookieName);
458
            $this->kernel->setProperty('cookie_domain', $cookieDomain);
459
            $this->kernel->setProperty('cookie_value', $cookieValue);
460
        }
461
462
        if ($refererSetting['enable']) {
463
            $this->kernel->setProperty('interval_check_referer', $refererSetting['config']['time_buffer']);
464
        }
465
466
        if ($sessionSetting['enable']) {
467
            $this->kernel->setProperty('interval_check_session', $sessionSetting['config']['time_buffer']);
468
        }
469
    }
470
471
    /**
472
     * Set the components.
473
     *
474
     * @return void
475
     */
476
    protected function setComponents(): void
477
    {
478
        $ipSetting = $this->getOption('ip', 'components');
479
        $rdnsSetting = $this->getOption('rdns', 'components');
480
        $headerSetting = $this->getOption('header', 'components');
481
        $userAgentSetting = $this->getOption('user_agent', 'components');
482
        $trustedBotSetting = $this->getOption('trusted_bot', 'components');
483
484
        if ($ipSetting['enable']) {
485
            $componentIp = new Component\Ip();
486
            $this->kernel->add($componentIp);
487
            $this->ipManager();
488
        }
489
490
        if ($trustedBotSetting['enable']) {
491
            $componentTrustedBot = new Component\TrustedBot();
492
493
            if ($trustedBotSetting['strict_mode']) {
494
                $componentTrustedBot->setStrict(true);
495
            }
496
497
            // This component will only allow popular search engline.
498
            // Other bots will go into the checking process.
499
            $this->kernel->add($componentTrustedBot);
500
        }
501
502
        if ($headerSetting['enable']) {
503
            $componentHeader = new Component\Header();
504
505
            // Deny all vistors without common header information.
506
            if ($headerSetting['strict_mode']) {
507
                $componentHeader->setStrict(true);
508
            }
509
510
            $this->kernel->add($componentHeader);
511
        }
512
513
        if ($userAgentSetting['enable']) {
514
            $componentUserAgent = new Component\UserAgent();
515
516
            // Deny all vistors without user-agent information.
517
            if ($userAgentSetting['strict_mode']) {
518
                $componentUserAgent->setStrict(true);
519
            }
520
521
            $this->kernel->add($componentUserAgent);
522
        }
523
524
        if ($rdnsSetting['enable']) {
525
            $componentRdns = new Component\Rdns();
526
527
            // Visitors with empty RDNS record will be blocked.
528
            // IP resolved hostname (RDNS) and IP address must conform with each other.
529
            if ($rdnsSetting['strict_mode']) {
530
                $componentRdns->setStrict(true);
531
            }
532
533
            $this->kernel->add($componentRdns);
534
        }
535
    }
536
537
    /**
538
     * Set the Captcha modules.
539
     *
540
     * @return void
541
     */
542
    protected function setCaptchas(): void
543
    {
544
        $recaptchaSetting = $this->getOption('recaptcha', 'captcha_modules');
545
        $imageSetting = $this->getOption('image', 'captcha_modules');
546
547
        if ($recaptchaSetting['enable']) {
548
549
            $googleRecaptcha = [
550
                'key'     => $recaptchaSetting['config']['site_key'],
551
                'secret'  => $recaptchaSetting['config']['secret_key'],
552
                'version' => $recaptchaSetting['config']['version'],
553
                'lang'    => $recaptchaSetting['config']['lang'],
554
            ];
555
556
            $this->kernel->add(new Captcha\Recaptcha($googleRecaptcha));
557
        }
558
559
        if ($imageSetting['enable']) {
560
561
            $type = $imageSetting['config']['type'] ?? 'alnum';
562
            $length = $imageSetting['config']['length'] ?? 8;
563
564
            switch ($type) {
565
                case 'numeric':
566
                    $imageCaptchaConfig['pool'] = '0123456789';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$imageCaptchaConfig was never initialized. Although not strictly required by PHP, it is generally a good practice to add $imageCaptchaConfig = array(); before regardless.
Loading history...
567
                    break;
568
569
                case 'alpha':
570
                    $imageCaptchaConfig['pool'] = '0123456789abcdefghijklmnopqrstuvwxyz';
571
                    break;
572
573
                case 'alnum':
574
                default:
575
                    $imageCaptchaConfig['pool'] = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
576
            }
577
578
            $imageCaptchaConfig['word_length'] = $length;
579
580
            $this->kernel->add(new Captcha\ImageCaptcha($imageCaptchaConfig));
581
        }
582
    }
583
584
    /**
585
     * Set the messenger modules.
586
     *
587
     * @return void
588
     */
589
    protected function setMessengers(): void
590
    {
591
        // // The ID list of the messenger modules.
592
        $messengerList = [
593
            'telegram',
594
            'line_notify',
595
            'sendgrid',
596
            'native_php_mail',
597
            'smtp',
598
            'mailgun',
599
            'rocket_chat',
600
            'slack',
601
            'slack_webhook',
602
        ];
603
604
        foreach ($messengerList as $messenger) {
605
            $setting = $this->getOption($messenger, 'messengers');
606
607
            // Initialize messenger instances from the factory/
608
            if (MessengerFactory::check($messenger, $setting)) {
0 ignored issues
show
Bug introduced by
It seems like $setting can also be of type false; however, parameter $setting of Shieldon\Firewall\Messen...ssengerFactory::check() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

608
            if (MessengerFactory::check($messenger, /** @scrutinizer ignore-type */ $setting)) {
Loading history...
609
                $this->kernel->add(
610
                    MessengerFactory::getInstance(
611
                        // The ID of the messenger module in the configuration.
612
                        $messenger, 
613
                        // The settings of the messenger module in the configuration.
614
                        $setting    
0 ignored issues
show
Bug introduced by
It seems like $setting can also be of type false; however, parameter $setting of Shieldon\Firewall\Messen...rFactory::getInstance() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

614
                        /** @scrutinizer ignore-type */ $setting    
Loading history...
615
                    )
616
                );
617
            }
618
            unset($setting);
619
        }
620
    }
621
622
    /**
623
     * Set message events.
624
     *
625
     * @return void
626
     */
627
    protected function setMessageEvents(): void
628
    {
629
        $eventSetting = $this->getOption('failed_attempts_in_a_row', 'events');
630
631
        $notifyDataCircle = false;
632
        $notifySystemFirewall = false;
633
634
        if ($eventSetting['data_circle']['messenger']) {
635
            $notifyDataCircle = true;
636
        }
637
638
        if ($eventSetting['system_firewall']['messenger']) {
639
            $notifyDataCircle = true;
640
        }
641
642
        $this->kernel->setProperty('deny_attempt_notify', [
643
            'data_circle' => $notifyDataCircle,
644
            'system_firewall' => $notifySystemFirewall,
645
        ]);
646
    }
647
648
    /**
649
     * Set deny attempts.
650
     *
651
     * @return void
652
     */
653
    protected function setDenyAttempts(): void
654
    {
655
        $eventSetting = $this->getOption('failed_attempts_in_a_row', 'events');
656
657
        $enableDataCircle = false;
658
        $enableSystemFirewall = false;
659
660
        if ($eventSetting['data_circle']['enable']) {
661
            $enableDataCircle = true;
662
        }
663
664
        if ($eventSetting['system_firewall']['enable']) {
665
            $enableSystemFirewall = true;
666
        }
667
668
        $this->kernel->setProperty('deny_attempt_enable', [
669
            'data_circle' => $enableDataCircle,
670
            'system_firewall' => $enableSystemFirewall,
671
        ]);
672
673
        $this->kernel->setProperty('deny_attempt_buffer', [
674
            'data_circle' => $eventSetting['data_circle']['buffer'] ?? 10,
675
            'system_firewall' => $eventSetting['data_circle']['buffer'] ?? 10,
676
        ]);
677
678
        // Check the time of the last failed attempt. @since 0.2.0
679
        $recordAttempt = $this->getOption('record_attempt');
680
681
        $detectionPeriod = $recordAttempt['detection_period'] ?? 5;
682
        $timeToReset = $recordAttempt['time_to_reset'] ?? 1800;
683
684
        $this->kernel->setProperty('record_attempt_detection_period', $detectionPeriod);
685
        $this->kernel->setProperty('reset_attempt_counter', $timeToReset);
686
    }
687
688
    /**
689
     * Set iptables working folder.
690
     *
691
     * @return void
692
     */
693
    protected function setIptablesWatchingFolder(): void
694
    {
695
        $iptablesSetting = $this->getOption('config', 'iptables');
696
        $this->kernel->setProperty('iptables_watching_folder',  $iptablesSetting['watching_folder']);
697
    }
698
699
    /**
700
     * Set the online session limit.
701
     *
702
     * @return void
703
     */
704
    protected function setSessionLimit(): void
705
    {
706
        $sessionLimitSetting = $this->getOption('online_session_limit');
707
708
        if ($sessionLimitSetting['enable']) {
709
710
            $onlineUsers = $sessionLimitSetting['config']['count'] ?? 100;
711
            $alivePeriod = $sessionLimitSetting['config']['period'] ?? 300;
712
713
            $this->kernel->limitSession($onlineUsers, $alivePeriod);
714
        }
715
    }
716
717
    /**
718
     * Set the cron job.
719
     * This is triggered by the pageviews, not system cron job.
720
     *
721
     * @return void
722
     */
723
    protected function setCronJob(): void 
724
    {
725
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
726
727
        if ($cronjobSetting['enable']) {
728
729
            $nowTime = time();
730
731
            $lastResetTime = $cronjobSetting['config']['last_update'];
732
733
            if (!empty($lastResetTime) ) {
734
                $lastResetTime = strtotime($lastResetTime);
735
            } else {
736
                // @codeCoverageIgnoreStart
737
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
738
                // @codeCoverageIgnoreEnd
739
            }
740
741
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
742
743
                $updateResetTime = date('Y-m-d 00:00:00');
744
745
                // Update new reset time.
746
                $this->setConfig('cronjob.reset_circle.config.last_update', $updateResetTime);
747
                $this->updateConfig();
748
749
                // Remove all logs.
750
                $this->kernel->driver->rebuild();
751
            }
752
        }
753
    }
754
755
    /**
756
     * Set the URLs that want to be excluded from Shieldon protection.
757
     *
758
     * @return void
759
     */
760
    protected function setExcludedUrls(): void
761
    {
762
        $excludedUrls = $this->getOption('excluded_urls');
763
764
        if (!empty($excludedUrls)) {
765
            $list = array_column($excludedUrls, 'url');
766
767
            $this->kernel->setExcludedUrls($list);
768
        }
769
    }
770
771
    /**
772
     * Set XSS protection.
773
     *
774
     * @return void
775
     */
776
    protected function setXssProtection(): void
777
    {
778
        $xssProtectionOptions = $this->getOption('xss_protection');
779
780
        $xssFilter = new Security\Xss();
781
782
        if ($xssProtectionOptions['post']) {
783
            $this->kernel->setClosure('xss_post', function() use ($xssFilter) {
784
                if (!empty($_POST)) {
785
                    foreach (array_keys($_POST) as $k) {
786
                        $_POST[$k] = $xssFilter->clean($_POST[$k]);
787
                    }
788
                }
789
            });
790
        }
791
792
        if ($xssProtectionOptions['get']) {
793
            $this->kernel->setClosure('xss_get', function() use ($xssFilter) {
794
                if (!empty($_GET)) {
795
                    foreach (array_keys($_GET) as $k) {
796
                        $_GET[$k] = $xssFilter->clean($_GET[$k]);
797
                    }
798
                }
799
            });
800
        }
801
802
        if ($xssProtectionOptions['cookie']) {
803
            $this->kernel->setClosure('xss_cookie', function() use ($xssFilter) {
804
                if (!empty($_COOKIE)) {
805
                    foreach (array_keys($_COOKIE) as $k) {
806
                        $_COOKIE[$k] = $xssFilter->clean($_COOKIE[$k]);
807
                    }
808
                }
809
            });
810
        }
811
812
        $xssProtectedList = $this->getOption('xss_protected_list');
813
814
        if (!empty($xssProtectedList)) {
815
        
816
            $this->kernel->setClosure('xss_protection', function() use ($xssFilter, $xssProtectedList) {
817
818
                foreach ($xssProtectedList as $v) {
819
                    $k = $v['variable'] ?? 'undefined';
820
    
821
                    switch ($v['type']) {
822
823
                        case 'get':
824
825
                            if (!empty($_GET[$k])) {
826
                                $_GET[$k] = $xssFilter->clean($_GET[$k]);
827
                            }
828
                            break;
829
    
830
                        case 'post':
831
    
832
                            if (!empty($_POST[$k])) {
833
                                $_POST[$k] = $xssFilter->clean($_POST[$k]);
834
                            }
835
                            break;
836
    
837
                        case 'cookie':
838
839
                            if (!empty($_COOKIE[$k])) {
840
                                $_COOKIE[$k] = $xssFilter->clean($_COOKIE[$k]);
841
                            }
842
                            break;
843
    
844
                        default:
845
                    }
846
                }
847
            });
848
        }
849
    }
850
851
    /**
852
     * WWW-Athentication.
853
     *
854
     * @return void
855
     */
856
    protected function setAuthentication(): void
857
    {
858
        $authenticateList = $this->getOption('www_authenticate');
859
860
        $this->add(new Middleware\httpAuthentication($authenticateList));
0 ignored issues
show
Bug introduced by
new Shieldon\Firewall\Mi...tion($authenticateList) of type Shieldon\Firewall\Middleware\httpAuthentication is incompatible with the type array expected by parameter $middleware of Shieldon\Firewall\Firewall::add(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

860
        $this->add(/** @scrutinizer ignore-type */ new Middleware\httpAuthentication($authenticateList));
Loading history...
Bug introduced by
It seems like $authenticateList can also be of type false; however, parameter $protectedUrlList of Shieldon\Firewall\Middle...tication::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

860
        $this->add(new Middleware\httpAuthentication(/** @scrutinizer ignore-type */ $authenticateList));
Loading history...
861
    }
862
863
    /**
864
     * IP manager.
865
     */
866
    protected function ipManager()
867
    {
868
        $ipList = $this->getOption('ip_manager');
869
870
        $allowedList = [];
871
        $deniedList = [];
872
873
        if (!empty($ipList)) {
874
            foreach ($ipList as $ip) {
875
876
                if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
877
    
878
                    if ('allow' === $ip['rule']) {
879
                        $allowedList[] = $ip['ip'];
880
                    }
881
    
882
                    if ('deny' === $ip['rule']) {
883
                        $deniedList[] = $ip['ip'];
884
                    }
885
                }
886
            }
887
        }
888
889
        if (!empty($allowedList)) {
890
            $this->kernel->component['Ip']->setAllowedItems($allowedList);
891
        }
892
893
        if (!empty($deniedList)) {
894
            $this->kernel->component['Ip']->setDeniedItems($deniedList);
895
        }
896
    }
897
898
    /**
899
     * Set dialog UI.
900
     *
901
     * @return void
902
     */
903
    protected function setDialogUI()
904
    {
905
        $ui = $this->getOption('dialog_ui');
906
907
        if (!empty($ui)) {
908
            get_session()->set('shieldon_ui_lang', $ui['lang']);
909
            $this->kernel->setDialogUI($this->getOption('dialog_ui'));
910
        }
911
    }
912
913
    /**
914
     * Get options from the configuration file.
915
     * 
916
     * This method is same as `$this->getConfig()` but returning value from array directly, 
917
     * saving a `explode()` process.
918
     *
919
     * @param string $option
920
     * @param string $section
921
     *
922
     * @return mixed
923
     */
924
    protected function getOption(string $option, string $section = '')
925
    {
926
        if (!empty($this->configuration[$section][$option])) {
927
            return $this->configuration[$section][$option];
928
        }
929
930
        if (!empty($this->configuration[$option]) && $section === '') {
931
            return $this->configuration[$option];
932
        }
933
934
        return false;
935
    }
936
937
    /**
938
     * Update configuration file.
939
     *
940
     * @return void
941
     */
942
    protected function updateConfig()
943
    {
944
        $configFilePath = $this->directory . '/' . $this->filename;
945
946
        if (!file_exists($configFilePath)) {
947
            if (!is_dir($this->directory)) {
948
                // @codeCoverageIgnoreStart
949
                $originalUmask = umask(0);
950
                @mkdir($this->directory, 0777, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

950
                /** @scrutinizer ignore-unhandled */ @mkdir($this->directory, 0777, true);

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...
951
                umask($originalUmask);
952
                // @codeCoverageIgnoreEnd
953
            }
954
        }
955
956
        file_put_contents($configFilePath, json_encode($this->configuration));
957
    }
958
}
959