SetupTrait::setupDriver()   A
last analyzed

Complexity

Conditions 5
Paths 8

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 14
c 1
b 0
f 0
dl 0
loc 23
ccs 13
cts 13
cp 1
rs 9.4888
cc 5
nc 8
nop 0
crap 5
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
 * php version 7.1.0
11
 *
12
 * @category  Web-security
13
 * @package   Shieldon
14
 * @author    Terry Lin <[email protected]>
15
 * @copyright 2019 terrylinooo
16
 * @license   https://github.com/terrylinooo/shieldon/blob/2.x/LICENSE MIT
17
 * @link      https://github.com/terrylinooo/shieldon
18
 * @see       https://shieldon.io
19
 */
20
21
declare(strict_types=1);
22
23
namespace Shieldon\Firewall\Firewall;
24
25
use Psr\Http\Server\MiddlewareInterface;
26
use Shieldon\Firewall\Firewall\Captcha\CaptchaFactory;
27
use Shieldon\Firewall\Firewall\Driver\DriverFactory;
28
use Shieldon\Firewall\Log\ActionLogger;
29
use Shieldon\Firewall\Middleware\HttpAuthentication;
30
use Shieldon\Event\Event;
31
use RuntimeException;
32
use function Shieldon\Firewall\get_request;
33
use function Shieldon\Firewall\get_session_instance;
34
use function strpos;
35
use function time;
36
37
/*
38
 * Main Trait for Firwall class.
39
 */
40
trait SetupTrait
41
{
42
    /**
43
     * If status is false and then Sheldon will stop working.
44
     *
45
     * @var bool
46
     */
47
    protected $status = true;
48
49
    /**
50
     * Get options from the configuration file.
51
     * This method is same as `$this->getConfig()` but returning value from array directly.
52
     *
53
     * @param string $option  The option of the section in the the configuration.
54
     * @param string $section The section in the configuration.
55
     *
56
     * @return mixed
57
     */
58
    abstract protected function getOption(string $option, string $section = '');
59
60
    /**
61
     * Update configuration file.
62
     *
63
     * @return void
64
     */
65
    abstract protected function updateConfig(): void;
66
67
    /**
68
     * Set a variable to the configuration.
69
     *
70
     * @param string $field The field of the configuration.
71
     * @param mixed  $value The vale of a field in the configuration.
72
     *
73
     * @return void
74
     */
75
    abstract public function setConfig(string $field, $value): void;
76
77
    /**
78
     * Add middlewares and use them before going into Shieldon kernal.
79
     *
80
     * @param MiddlewareInterface $middleware A PSR-15 middlewares.
81
     *
82
     * @return void
83
     */
84
    abstract public function add(MiddlewareInterface $middleware): void;
85
86
    /**
87
     * Are database tables created?
88
     *
89
     * @return bool
90
     */
91
    abstract protected function hasCheckpoint(): bool;
92
93
    /**
94
     * Are database tables created?
95
     *
96
     * @param bool $create Is create the checkpoint file, or not.
97
     *
98
     * @return void
99
     */
100
    abstract protected function setCheckpoint(bool $create = true): void;
101
102
    /**
103
     * Set a data driver for the use of Shiedon Firewall.
104
     * Currently supports File, Redis, MySQL and SQLite.
105
     *
106
     * @return void
107
     */
108 83
    public function setupDriver(): void
109
    {
110 83
        $driverType = $this->getOption('driver_type');
111 83
        $driverSetting = $this->getOption($driverType, 'drivers');
112
113 83
        if (isset($driverSetting['directory_path'])) {
114 83
            $driverSetting['directory_path'] = $driverSetting['directory_path'] ?:
115
                $this->directory . '/data_driver_' . $driverType;
116
        }
117 83
118
        $driverInstance = DriverFactory::getInstance($driverType, $driverSetting);
119 83
120 81
        if ($this->hasCheckpoint()) {
121
            $this->kernel->disableDbBuilder();
122 5
        } else {
123
            $this->setCheckpoint();
124
        }
125 83
126
        $this->status = false;
127 83
128 83
        if ($driverInstance !== null) {
129 83
            $this->kernel->setDriver($driverInstance);
130
            $this->status = true;
131
        }
132
    }
133
134
    /**
135
     * Filters
136
     *
137
     * (1) Session.
138
     * (2) Cookie generated by JavaScript code.
139
     * (3) HTTP referrer information.
140
     * (4) Pageview frequency.
141
     *
142
     * @return void
143 83
     */
144
    protected function setupFilters(): void
145 83
    {
146 83
        $filters = [
147 83
            'session',
148 83
            'cookie',
149 83
            'referer',
150
        ];
151 83
152 83
        $settings = [];
153 83
        $filterConfig = [];
154
        $filterLimit = [];
155 83
156 83
        foreach ($filters as $filter) {
157
            $setting = $this->getOption($filter, 'filters');
158 83
159 83
            $settings[$filter] = $setting;
160 83
            $filterConfig[$filter] = $setting['enable'];
161
            $filterLimit[$filter] = $setting['config']['quota']; // default: 5
162 83
163
            unset($setting);
164
        }
165 83
166 83
        $settings['frequency'] = $this->getOption('frequency', 'filters');
167
        $filterConfig['frequency'] = $settings['frequency']['enable'];
168 83
169
        $this->kernel->setFilters($filterConfig);
170 83
171 83
        $this->kernel->setProperty(
172 83
            'limit_unusual_behavior',
173 83
            $filterLimit
174
        );
175 83
176 83
        $frequencyQuota = [
177 83
            's' => $settings['frequency']['config']['quota_s'],
178 83
            'm' => $settings['frequency']['config']['quota_m'],
179 83
            'h' => $settings['frequency']['config']['quota_h'],
180 83
            'd' => $settings['frequency']['config']['quota_d'],
181
        ];
182 83
183
        $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
184 83
185 83
        $this->kernel->setProperty(
186 83
            'cookie_name',
187 83
            $settings['cookie']['config']['cookie_name'] // default: ssjd
188
        );
189 83
190 83
        $this->kernel->setProperty(
191 83
            'cookie_domain',
192 83
            $settings['cookie']['config']['cookie_domain'] // default: ''
193
        );
194 83
195 83
        $this->kernel->setProperty(
196 83
            'cookie_value',
197 83
            $settings['cookie']['config']['cookie_value'] // default: 1
198
        );
199 83
200 83
        $this->kernel->setProperty(
201 83
            'interval_check_referer',
202 83
            $settings['referer']['config']['time_buffer']
203
        );
204 83
205 83
        $this->kernel->setProperty(
206 83
            'interval_check_session',
207 83
            $settings['session']['config']['time_buffer']
208
        );
209
    }
210
211
    /**
212
     * Components
213
     *
214
     * (1) Ip
215
     * (2) Rdns
216
     * (3) Header
217
     * (4) User-agent
218
     * (5) Trusted bot
219
     *
220
     * @return void
221 83
     */
222
    protected function setupComponents(): void
223 83
    {
224 83
        $componentConfig = [
225 83
            'Ip'         => $this->getOption('ip', 'components'),
226 83
            'Rdns'       => $this->getOption('rdns', 'components'),
227 83
            'Header'     => $this->getOption('header', 'components'),
228 83
            'UserAgent'  => $this->getOption('user_agent', 'components'),
229 83
            'TrustedBot' => $this->getOption('trusted_bot', 'components'),
230
        ];
231 83
232 83
        foreach ($componentConfig as $className => $config) {
233
            $class = 'Shieldon\Firewall\Component\\' . $className;
234 83
235 83
            if ($config['enable']) {
236
                $componentInstance = new $class();
237 83
238 83
                if ($className === 'Ip') {
239
                    $this->kernel->setComponent($componentInstance);
240
241 83
                    // Need Ip component to be loaded before calling this method.
242
                    $this->setupAndApplyComponentIpManager();
243 83
                } else {
244 83
                    $componentInstance->setStrict($config['strict_mode']);
245
                    $this->kernel->setComponent($componentInstance);
246
                }
247
            }
248
        }
249
    }
250
251
    /**
252
     * Captcha modules.
253
     *
254
     * (1) Google ReCaptcha
255
     * (2) Simple image captcha.
256
     *
257
     * @return void
258 83
     */
259
    protected function setupCaptchas(): void
260 83
    {
261 83
        $captchaList = [
262 83
            'recaptcha',
263 83
            'image',
264
        ];
265 83
266 83
        foreach ($captchaList as $captcha) {
267
            $setting = (array) $this->getOption($captcha, 'captcha_modules');
268
269 83
            // Initialize messenger instances from the factory/
270
            if (CaptchaFactory::check($setting)) {
271 19
                $this->kernel->setCaptcha(
272 19
                    CaptchaFactory::getInstance(
273
                        // The ID of the captcha module in the configuration.
274 19
                        $captcha,
275
                        // The settings of the captcha module in the configuration.
276 19
                        $setting
277 19
                    )
278 19
                );
279
            }
280
281 83
            unset($setting);
282
        }
283
    }
284
285
    /**
286
     * Set up the action logger.
287
     *
288
     * @return void
289
     */
290 83
    protected function setupLogger(): void
291
    {
292 83
        $loggerSetting = $this->getOption('action', 'loggers');
293
294 83
        if ($loggerSetting['enable']) {
295 82
            if (!empty($loggerSetting['config']['directory_path'])) {
296 76
                $this->kernel->setLogger(
297 76
                    new ActionLogger($loggerSetting['config']['directory_path'])
298 76
                );
299
            }
300
        }
301
    }
302
303
    /**
304
     * Apply the denied list and the allowed list to Ip Component.
305
     *
306
     * @return void
307
     */
308 83
    protected function setupAndApplyComponentIpManager(): void
309
    {
310 83
        $ipList = (array) $this->getOption('ip_manager');
311
312 83
        $allowedList = [];
313 83
        $deniedList = [];
314
315 83
        foreach ($ipList as $ip) {
316 83
            if (empty($ip)) {
317
                continue;
318
            }
319 83
320
            if (0 === strpos($this->kernel->getCurrentUrl(), empty($ip['url']) ? '/' : $ip['url'])) {
321 82
                if ('allow' === $ip['rule']) {
322 82
                    $allowedList[] = $ip['ip'];
323
                }
324
325 82
                if ('deny' === $ip['rule']) {
326 83
                    $deniedList[] = $ip['ip'];
327
                }
328
            }
329
        }
330
331
        // @scrutinizer ignore-call
332 83
        $this->kernel->component['Ip']->setAllowedItems($allowedList);
333
334
        // @scrutinizer ignore-call
335 83
        $this->kernel->component['Ip']->setDeniedItems($deniedList);
336
    }
337
338
    /**
339
     * If you use CDN, please choose the real IP source.
340
     *
341
     * @return void
342
     */
343 83
    protected function setupIpSource(): void
344
    {
345 83
        $ipSourceType = $this->getOption('ip_variable_source');
346 83
        $serverParams = get_request()->getServerParams();
347
348
        /**
349
         * REMOTE_ADDR: general
350
         * HTTP_CF_CONNECTING_IP: Cloudflare
351
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
352
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
353
         */
354 83
        $key = array_search(true, $ipSourceType);
355 83
        $ip = $serverParams[$key];
356
357 83
        if (empty($ip)) {
358
            // @codeCoverageIgnoreStart
359
            throw new RuntimeException(
360
                'IP source is not set correctly.'
361
            );
362
            // @codeCoverageIgnoreEnd
363
        }
364
365
        $this->kernel->setIp($ip, true);
366 83
    }
367
368
    /**
369
     * Set deny attempts.
370
     *
371
     * @return void
372
     */
373
    protected function setupDenyTooManyAttempts(): void
374 83
    {
375
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
376 83
377
        $this->kernel->setProperty(
378 83
            'deny_attempt_enable',
379 83
            [
380 83
                'data_circle'     => $setting['data_circle']['enable'],     // false
381 83
                'system_firewall' => $setting['system_firewall']['enable'], // false
382 83
            ]
383 83
        );
384 83
385
        $this->kernel->setProperty(
386 83
            'deny_attempt_buffer',
387 83
            [
388 83
                'data_circle'     => $setting['data_circle']['buffer'],     // 10
389 83
                'system_firewall' => $setting['system_firewall']['buffer'], // 10
390 83
            ]
391 83
        );
392 83
393
        // Check the time of the last failed attempt.
394
        $recordAttempt = $this->getOption('record_attempt');
395 83
396
        $this->kernel->setProperty(
397 83
            'record_attempt_detection_period',
398 83
            $recordAttempt['detection_period'] // 5
399 83
        );
400 83
401
        $this->kernel->setProperty(
402 83
            'reset_attempt_counter',
403 83
            $recordAttempt['time_to_reset'] // 1800
404 83
        );
405 83
    }
406
407
    /**
408
     * Set iptables working folder.
409
     *
410
     * @return void
411
     */
412
    protected function setupiptablesBridgeDirectory(): void
413 83
    {
414
        $iptablesSetting = $this->getOption('config', 'iptables');
415 83
416
        $this->kernel->setProperty(
417 83
            'iptables_watching_folder',
418 83
            $iptablesSetting['watching_folder']
419 83
        );
420 83
    }
421
422
    /**
423
     * Set the online session limit.
424
     *
425
     * @return void
426
     */
427
    protected function setupLimitSession(): void
428 83
    {
429
        $sessionLimitSetting = $this->getOption('online_session_limit');
430 83
431
        if ($sessionLimitSetting['enable']) {
432 83
            $onlineUsers = $sessionLimitSetting['config']['count'];       // default: 100
433
            $alivePeriod = $sessionLimitSetting['config']['period'];      // default: 300
434 82
            $isUniqueIp  = $sessionLimitSetting['config']['unique_only']; // false
435 82
436 82
            $this->kernel->limitSession($onlineUsers, $alivePeriod, $isUniqueIp);
437
        }
438 82
    }
439
440
    /**
441
     * Set the cron job.
442
     * This is triggered by the pageviews, not system cron job.
443
     *
444
     * @return void
445
     */
446
    protected function setupCronJob(): void
447
    {
448 83
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
449
450 83
        if ($cronjobSetting['enable']) {
451
            $nowTime = time();
452 83
453
            $lastResetTime = $cronjobSetting['config']['last_update'];
454 59
455
            if (!empty($lastResetTime)) {
456 59
                $lastResetTime = strtotime($lastResetTime);
457
            } else {
458 59
                // @codeCoverageIgnoreStart
459 59
460
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
461
462
                // @codeCoverageIgnoreEnd
463
            }
464
465
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
466
                $updateResetTime = date('Y-m-d 00:00:00');
467
468 59
                // Update new reset time.
469
                $this->setConfig(
470 12
                    'cronjob.reset_circle.config.last_update',
471
                    $updateResetTime
472
                );
473 12
474 12
                $this->updateConfig();
475 12
476 12
                // Remove all logs.
477
                // @scrutinizer ignore-call
478 12
                $this->kernel->driver->rebuild();
479
            }
480
        }
481
    }
482 12
483
    /**
484
     * Set the URLs that want to be excluded from Shieldon protection.
485
     *
486
     * @return void
487
     */
488
    protected function setupExcludedUrls(): void
489
    {
490
        $excludedUrls = $this->getOption('excluded_urls');
491
492 83
        if (!empty($excludedUrls)) {
493
            $list = array_column($excludedUrls, 'url');
494 83
495
            $this->kernel->setExcludedList($list);
496 83
        }
497 83
    }
498
499 83
    /**
500
     * WWW-Athentication.
501
     *
502
     * @return void
503
     */
504
    protected function setupPageAuthentication(): void
505
    {
506
        $authenticateList = $this->getOption('www_authenticate');
507
508 83
        if (is_array($authenticateList)) {
509
            $this->add(
510 83
                new HttpAuthentication($authenticateList)
511
            );
512 83
        }
513 83
    }
514 83
515 83
    /**
516
     * Set dialog UI.
517
     *
518
     * @return void
519
     */
520
    protected function setupDialogUserInterface()
521
    {
522
        Event::AddListener('session_init', function () {
523
            $ui = $this->getOption('dialog_ui');
524 83
            if (!empty($ui)) {
525
                get_session_instance()->set('shieldon_ui_lang', $ui['lang']);
526 83
                $this->kernel->setDialog($this->getOption('dialog_ui'));
527 72
            }
528
        });
529 72
530 72
        $dialogInfo = $this->getOption('dialog_info_disclosure');
531 72
532
        $this->kernel->setProperty('display_online_info', $dialogInfo['online_user_amount']);
533 83
        $this->kernel->setProperty('display_user_info', $dialogInfo['user_inforamtion']);
534
        $this->kernel->setProperty('display_http_code', $dialogInfo['http_status_code']);
535 83
        $this->kernel->setProperty('display_reason_code', $dialogInfo['reason_code']);
536
        $this->kernel->setProperty('display_reason_text', $dialogInfo['reason_text']);
537 83
    }
538
}
539