Passed
Push — 2.x ( d40732...596a61 )
by Terry
01:44
created

MainTrait::setChannel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 7
rs 10
c 2
b 0
f 0
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 function Shieldon\Firewall\get_request;
31
use function Shieldon\Firewall\get_session;
32
33
use RuntimeException;
34
use function strpos;
35
use function time;
36
37
/*
38
 * Main Trait for Firwall class.
39
 */
40
trait MainTrait
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
    protected function setDriver(): void
109
    {
110
        $driverType = $this->getOption('driver_type');
111
        $driverSetting = $this->getOption($driverType, 'drivers');
112
113
        if ($this->hasCheckpoint()) {
114
            $this->kernel->disableDbBuilder();
115
        } else {
116
            $this->setCheckpoint();
117
        }
118
119
        if (isset($driverSetting['directory_path'])) {
120
            $driverSetting['directory_path'] = $driverSetting['directory_path'] ?: $this->directory;
121
        }
122
123
        $driverInstance = DriverFactory::getInstance($driverType, $driverSetting);
124
125
        $this->status = false;
126
        if ($driverInstance !== null) {
127
            $this->kernel->setDriver($driverInstance);
128
            $this->status = true;
129
        }
130
    }
131
132
    /**
133
     * Filters
134
     *
135
     * (1) Session.
136
     * (2) Cookie generated by JavaScript code.
137
     * (3) HTTP referrer information.
138
     * (4) Pageview frequency.
139
     *
140
     * @return void
141
     */
142
    protected function setFilters(): void
143
    {
144
        $filters = [
145
            'session',
146
            'cookie',
147
            'referer',
148
        ];
149
150
        foreach ($filters as $filter) {
151
            $setting = $this->getOption($filter, 'filters');
152
153
            $settings[$filter] = $setting;
154
            $filterConfig[$filter] = $setting['enable'];
155
            $filterLimit[$filter] = $setting['config']['quota']; // default: 5
156
157
            unset($setting);
158
        }
159
160
        $settings['frequency'] = $this->getOption('frequency', 'filters');
161
        $filterConfig['frequency'] = $settings['frequency']['enable'];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $settings seems to be defined by a foreach iteration on line 150. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
162
163
        $this->kernel->setFilters($filterConfig);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filterConfig seems to be defined by a foreach iteration on line 150. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
164
165
        $this->kernel->setProperty(
166
            'limit_unusual_behavior',
167
            $filterLimit
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filterLimit seems to be defined by a foreach iteration on line 150. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
168
        );
169
170
        $frequencyQuota = [
171
            's' => $settings['frequency']['config']['quota_s'],
172
            'm' => $settings['frequency']['config']['quota_m'],
173
            'h' => $settings['frequency']['config']['quota_h'],
174
            'd' => $settings['frequency']['config']['quota_d'],
175
        ];
176
177
        $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
178
179
        $this->kernel->setProperty(
180
            'cookie_name',
181
            $settings['cookie']['config']['cookie_name'] // default: ssjd
182
        );
183
184
        $this->kernel->setProperty(
185
            'cookie_domain',
186
            $settings['cookie']['config']['cookie_domain'] // default: ''
187
        );
188
189
        $this->kernel->setProperty(
190
            'cookie_value',
191
            $settings['cookie']['config']['cookie_value'] // default: 1
192
        );
193
194
        $this->kernel->setProperty(
195
            'interval_check_referer',
196
            $settings['referer']['config']['time_buffer']
197
        );
198
199
        $this->kernel->setProperty(
200
            'interval_check_session',
201
            $settings['referer']['config']['time_buffer']
202
        );
203
    }
204
205
    /**
206
     * Components
207
     * 
208
     * (1) Ip
209
     * (2) Rdns
210
     * (3) Header
211
     * (4) User-agent
212
     * (5) Trusted bot
213
     *
214
     * @return void
215
     */
216
    protected function setComponents(): void
217
    {
218
        $componentConfig = [
219
            'Ip'         => $this->getOption('ip', 'components'),
220
            'Rdns'       => $this->getOption('rdns', 'components'),
221
            'Header'     => $this->getOption('header', 'components'),
222
            'UserAgent'  => $this->getOption('user_agent', 'components'),
223
            'TrustedBot' => $this->getOption('trusted_bot', 'components'),
224
        ];
225
226
        foreach ($componentConfig as $className => $config) {
227
            $class = 'Shieldon\Firewall\Component\\' . $className;
228
229
            if ($config['enable']) {
230
                $componentInstance = new $class();
231
232
                if ($className === 'Ip') {
233
                    $this->kernel->setComponent($componentInstance);
234
235
                    // Need Ip component to be loaded before calling this method.
236
                    $this->applyComponentIpManager();
237
                } else {
238
                    $componentInstance->setStrict($config['strict_mode']);
239
                    $this->kernel->setComponent($componentInstance);
240
                }
241
            }
242
        }
243
    }
244
245
    /**
246
     * Captcha modules.
247
     * 
248
     * (1) Google ReCaptcha
249
     * (2) Simple image captcha.
250
     *
251
     * @return void
252
     */
253
    protected function setCaptchas(): void
254
    {
255
        $captchaList = [
256
            'recaptcha',
257
            'image',
258
        ];
259
260
        foreach ($captchaList as $captcha) {
261
            $setting = (array) $this->getOption($captcha, 'captcha_modules');
262
263
            // Initialize messenger instances from the factory/
264
            if (CaptchaFactory::check($setting)) {
265
266
                $this->kernel->setCaptcha(
267
                    CaptchaFactory::getInstance(
268
                        // The ID of the captcha module in the configuration.
269
                        $captcha, 
270
                        // The settings of the captcha module in the configuration.
271
                        $setting    
272
                    )
273
                );
274
            }
275
276
            unset($setting);
277
        }
278
    }
279
280
    /**
281
     * Set up the action logger.
282
     *
283
     * @return void
284
     */
285
    protected function setLogger(): void
286
    {
287
        $loggerSetting = $this->getOption('action', 'loggers');
288
289
        if ($loggerSetting['enable']) {
290
            if (!empty($loggerSetting['config']['directory_path'])) {
291
                $this->kernel->setLogger(
292
                    new ActionLogger($loggerSetting['config']['directory_path'])
293
                );
294
            }
295
        }
296
    }
297
298
    /**
299
     * Apply the denied list and the allowed list to Ip Component.
300
     * 
301
     * @return void
302
     */
303
    protected function applyComponentIpManager(): void
304
    {
305
        $ipList = (array) $this->getOption('ip_manager');
306
307
        $allowedList = [];
308
        $deniedList = [];
309
310
        foreach ($ipList as $ip) {
311
312
            if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
313
314
                if ('allow' === $ip['rule']) {
315
                    $allowedList[] = $ip['ip'];
316
                }
317
318
                if ('deny' === $ip['rule']) {
319
                    $deniedList[] = $ip['ip'];
320
                }
321
            }
322
        }
323
324
        $this->kernel->component['Ip']->setAllowedItems($allowedList);
325
        $this->kernel->component['Ip']->setDeniedItems($deniedList);
326
    }
327
328
    /**
329
     * If you use CDN, please choose the real IP source.
330
     *
331
     * @return void
332
     */
333
    protected function setIpSource(): void
334
    {
335
        $ipSourceType = $this->getOption('ip_variable_source');
336
        $serverParams = get_request()->getServerParams();
337
338
        /**
339
         * REMOTE_ADDR: general
340
         * HTTP_CF_CONNECTING_IP: Cloudflare
341
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
342
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
343
         */
344
        $key = array_search(true, $ipSourceType);
345
        $ip = $serverParams[$key];
346
347
        if (empty($ip)) {
348
349
            // @codeCoverageIgnoreStart
350
            throw new RuntimeException(
351
                'IP source is not set correctly.'
352
            );
353
            // @codeCoverageIgnoreEnd
354
        }
355
356
        $this->kernel->setIp($ip);
357
    }
358
359
    /**
360
     * Set deny attempts.
361
     *
362
     * @return void
363
     */
364
    protected function setDenyTooManyAttempts(): void
365
    {
366
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
367
368
        $this->kernel->setProperty(
369
            'deny_attempt_enable',
370
            [
371
                'data_circle'     => $setting['data_circle']['enable'],     // false   
372
                'system_firewall' => $setting['system_firewall']['enable'], // false   
373
            ]
374
        );
375
376
        $this->kernel->setProperty(
377
            'deny_attempt_buffer',
378
            [
379
                'data_circle'     => $setting['data_circle']['buffer'],     // 10
380
                'system_firewall' => $setting['system_firewall']['buffer'], // 10
381
            ]
382
        );
383
384
        // Check the time of the last failed attempt.
385
        $recordAttempt = $this->getOption('record_attempt');
386
387
        $this->kernel->setProperty(
388
            'record_attempt_detection_period',
389
            $recordAttempt['detection_period'] // 5
390
        ); 
391
392
        $this->kernel->setProperty(
393
            'reset_attempt_counter',
394
            $recordAttempt['time_to_reset'] // 1800
395
        );  
396
    }
397
398
    /**
399
     * Set iptables working folder.
400
     *
401
     * @return void
402
     */
403
    protected function setIptablesBridgeDirectory(): void
404
    {
405
        $iptablesSetting = $this->getOption('config', 'iptables');
406
407
        $this->kernel->setProperty(
408
            'iptables_watching_folder',
409
            $iptablesSetting['watching_folder']
410
        );
411
    }
412
413
    /**
414
     * Set the online session limit.
415
     *
416
     * @return void
417
     */
418
    protected function setSessionLimit(): void
419
    {
420
        $sessionLimitSetting = $this->getOption('online_session_limit');
421
422
        if ($sessionLimitSetting['enable']) {
423
424
            $onlineUsers = $sessionLimitSetting['config']['count']; // default: 100
425
            $alivePeriod = $sessionLimitSetting['config']['period']; // default: 300
426
427
            $this->kernel->limitSession($onlineUsers, $alivePeriod);
428
        }
429
    }
430
431
    /**
432
     * Set the cron job.
433
     * This is triggered by the pageviews, not system cron job.
434
     *
435
     * @return void
436
     */
437
    protected function setCronJob(): void 
438
    {
439
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
440
441
        if ($cronjobSetting['enable']) {
442
443
            $nowTime = time();
444
445
            $lastResetTime = $cronjobSetting['config']['last_update'];
446
447
            if (!empty($lastResetTime) ) {
448
                $lastResetTime = strtotime($lastResetTime);
449
            } else {
450
                // @codeCoverageIgnoreStart
451
452
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
453
454
                // @codeCoverageIgnoreEnd
455
            }
456
457
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
458
459
                $updateResetTime = date('Y-m-d 00:00:00');
460
461
                // Update new reset time.
462
                $this->setConfig(
463
                    'cronjob.reset_circle.config.last_update',
464
                    $updateResetTime
465
                );
466
467
                $this->updateConfig();
468
469
                // Remove all logs.
470
                $this->kernel->driver->rebuild();
471
            }
472
        }
473
    }
474
475
    /**
476
     * Set the URLs that want to be excluded from Shieldon protection.
477
     *
478
     * @return void
479
     */
480
    protected function setExcludedUrls(): void
481
    {
482
        $excludedUrls = $this->getOption('excluded_urls');
483
484
        if (!empty($excludedUrls)) {
485
            $list = array_column($excludedUrls, 'url');
486
487
            $this->kernel->setExcludedUrls($list);
488
        }
489
    }
490
491
    /**
492
     * WWW-Athentication.
493
     *
494
     * @return void
495
     */
496
    protected function setPageAuthentication(): void
497
    {
498
        $authenticateList = $this->getOption('www_authenticate');
499
500
        if (is_array($authenticateList)) {
501
            $this->add(
502
                new HttpAuthentication($authenticateList)
503
            );
504
        }
505
    }
506
507
    /**
508
     * Set dialog UI.
509
     *
510
     * @return void
511
     */
512
    protected function setDialogUserInterface()
513
    {
514
        $ui = $this->getOption('dialog_ui');
515
516
        if (!empty($ui)) {
517
            get_session()->set('shieldon_ui_lang', $ui['lang']);
518
            $this->kernel->setDialog($this->getOption('dialog_ui'));
519
        }
520
    }
521
}
522