1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <contact@terryl.in>
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 <contact@terryl.in>
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 3
    public function setupDriver(): void
109
    {
110 3
        $driverType = $this->getOption('driver_type');
111 3
        $driverSetting = $this->getOption($driverType, 'drivers');
112

113 3
        if (isset($driverSetting['directory_path'])) {
114 3
            $driverSetting['directory_path'] = $driverSetting['directory_path'] ?: $this->directory . '/data_driver_' . $driverType;
115
        }
116

117 3
        $driverInstance = DriverFactory::getInstance($driverType, $driverSetting);
118

119 3
        if ($this->hasCheckpoint()) {
120 3
            $this->kernel->disableDbBuilder();
121
        } else {
122 3
            $this->setCheckpoint();
123
        }
124

125 3
        $this->status = false;
126

127 3
        if ($driverInstance !== null) {
128 3
            $this->kernel->setDriver($driverInstance);
129 3
            $this->status = true;
130
        }
131
    }
132

133
    /**
134
     * Filters
135
     *
136
     * (1) Session.
137
     * (2) Cookie generated by JavaScript code.
138
     * (3) HTTP referrer information.
139
     * (4) Pageview frequency.
140
     *
141
     * @return void
142
     */
143 3
    protected function setupFilters(): void
144
    {
145
        $filters = [
146 3
            'session',
147
            'cookie',
148
            'referer',
149
        ];
150

151 3
        $settings = [];
152 3
        $filterConfig = [];
153 3
        $filterLimit = [];
154

155 3
        foreach ($filters as $filter) {
156 3
            $setting = $this->getOption($filter, 'filters');
157

158 3
            $settings[$filter] = $setting;
159 3
            $filterConfig[$filter] = $setting['enable'];
160 3
            $filterLimit[$filter] = $setting['config']['quota']; // default: 5
161

162 3
            unset($setting);
163
        }
164

165 3
        $settings['frequency'] = $this->getOption('frequency', 'filters');
166 3
        $filterConfig['frequency'] = $settings['frequency']['enable'];
167

168 3
        $this->kernel->setFilters($filterConfig);
169

170 3
        $this->kernel->setProperty(
171 3
            'limit_unusual_behavior',
172 2
            $filterLimit
173
        );
174

175
        $frequencyQuota = [
176 3
            's' => $settings['frequency']['config']['quota_s'],
177 3
            'm' => $settings['frequency']['config']['quota_m'],
178 3
            'h' => $settings['frequency']['config']['quota_h'],
179 3
            'd' => $settings['frequency']['config']['quota_d'],
180
        ];
181

182 3
        $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
183

184 3
        $this->kernel->setProperty(
185 3
            'cookie_name',
186 3
            $settings['cookie']['config']['cookie_name'] // default: ssjd
187
        );
188

189 3
        $this->kernel->setProperty(
190 3
            'cookie_domain',
191 3
            $settings['cookie']['config']['cookie_domain'] // default: ''
192
        );
193

194 3
        $this->kernel->setProperty(
195 3
            'cookie_value',
196 3
            $settings['cookie']['config']['cookie_value'] // default: 1
197
        );
198

199 3
        $this->kernel->setProperty(
200 3
            'interval_check_referer',
201 3
            $settings['referer']['config']['time_buffer']
202
        );
203

204 3
        $this->kernel->setProperty(
205 3
            'interval_check_session',
206 3
            $settings['session']['config']['time_buffer']
207
        );
208
    }
209

210
    /**
211
     * Components
212
     * 
213
     * (1) Ip
214
     * (2) Rdns
215
     * (3) Header
216
     * (4) User-agent
217
     * (5) Trusted bot
218
     *
219
     * @return void
220
     */
221 3
    protected function setupComponents(): void
222
    {
223
        $componentConfig = [
224 3
            'Ip'         => $this->getOption('ip', 'components'),
225 3
            'Rdns'       => $this->getOption('rdns', 'components'),
226 3
            'Header'     => $this->getOption('header', 'components'),
227 3
            'UserAgent'  => $this->getOption('user_agent', 'components'),
228 3
            'TrustedBot' => $this->getOption('trusted_bot', 'components'),
229
        ];
230

231 3
        foreach ($componentConfig as $className => $config) {
232 3
            $class = 'Shieldon\Firewall\Component\\' . $className;
233

234 3
            if ($config['enable']) {
235 3
                $componentInstance = new $class();
236

237 3
                if ($className === 'Ip') {
238 3
                    $this->kernel->setComponent($componentInstance);
239

240
                    // Need Ip component to be loaded before calling this method.
241 3
                    $this->setupAndApplyComponentIpManager();
242
                } else {
243 3
                    $componentInstance->setStrict($config['strict_mode']);
244 3
                    $this->kernel->setComponent($componentInstance);
245
                }
246
            }
247
        }
248
    }
249

250
    /**
251
     * Captcha modules.
252
     * 
253
     * (1) Google ReCaptcha
254
     * (2) Simple image captcha.
255
     *
256
     * @return void
257
     */
258 3
    protected function setupCaptchas(): void
259
    {
260
        $captchaList = [
261 3
            'recaptcha',
262
            'image',
263
        ];
264

265 3
        foreach ($captchaList as $captcha) {
266 3
            $setting = (array) $this->getOption($captcha, 'captcha_modules');
267

268
            // Initialize messenger instances from the factory/
269 3
            if (CaptchaFactory::check($setting)) {
270

271 3
                $this->kernel->setCaptcha(
272 3
                    CaptchaFactory::getInstance(
273
                        // The ID of the captcha module in the configuration.
274 3
                        $captcha, 
275
                        // The settings of the captcha module in the configuration.
276 2
                        $setting    
277
                    )
278
                );
279
            }
280

281 3
            unset($setting);
282
        }
283
    }
284

285
    /**
286
     * Set up the action logger.
287
     *
288
     * @return void
289
     */
290 3
    protected function setupLogger(): void
291
    {
292 3
        $loggerSetting = $this->getOption('action', 'loggers');
293

294 3
        if ($loggerSetting['enable']) {
295 3
            if (!empty($loggerSetting['config']['directory_path'])) {
296 3
                $this->kernel->setLogger(
297 3
                    new ActionLogger($loggerSetting['config']['directory_path'])
298
                );
299
            }
300
        }
301
    }
302

303
    /**
304
     * Apply the denied list and the allowed list to Ip Component.
305
     * 
306
     * @return void
307
     */
308 3
    protected function setupAndApplyComponentIpManager(): void
309
    {
310 3
        $ipList = (array) $this->getOption('ip_manager');
311

312 3
        $allowedList = [];
313 3
        $deniedList = [];
314

315 3
        foreach ($ipList as $ip) {
316

317 3
            if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
318

319 3
                if ('allow' === $ip['rule']) {
320 3
                    $allowedList[] = $ip['ip'];
321
                }
322

323 3
                if ('deny' === $ip['rule']) {
324 3
                    $deniedList[] = $ip['ip'];
325
                }
326
            }
327
        }
328

329
        /** @scrutinizer ignore-call */ 
330 3
        $this->kernel->component['Ip']->setAllowedItems($allowedList);
331

332
        /** @scrutinizer ignore-call */ 
333 3
        $this->kernel->component['Ip']->setDeniedItems($deniedList);
334
    }
335

336
    /**
337
     * If you use CDN, please choose the real IP source.
338
     *
339
     * @return void
340
     */
341 3
    protected function setupIpSource(): void
342
    {
343 3
        $ipSourceType = $this->getOption('ip_variable_source');
344 3
        $serverParams = get_request()->getServerParams();
345

346
        /**
347
         * REMOTE_ADDR: general
348
         * HTTP_CF_CONNECTING_IP: Cloudflare
349
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
350
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
351
         */
352 3
        $key = array_search(true, $ipSourceType);
353 3
        $ip = $serverParams[$key];
354

355 3
        if (empty($ip)) {
356

357
            // @codeCoverageIgnoreStart
358
            throw new RuntimeException(
359
                'IP source is not set correctly.'
360
            );
361
            // @codeCoverageIgnoreEnd
362
        }
363

364 3
        $this->kernel->setIp($ip, true);
365
    }
366

367
    /**
368
     * Set deny attempts.
369
     *
370
     * @return void
371
     */
372 3
    protected function setupDenyTooManyAttempts(): void
373
    {
374 3
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
375

376 3
        $this->kernel->setProperty(
377 3
            'deny_attempt_enable',
378
            [
379 3
                'data_circle'     => $setting['data_circle']['enable'],     // false   
380 3
                'system_firewall' => $setting['system_firewall']['enable'], // false   
381
            ]
382
        );
383

384 3
        $this->kernel->setProperty(
385 3
            'deny_attempt_buffer',
386
            [
387 3
                'data_circle'     => $setting['data_circle']['buffer'],     // 10
388 3
                'system_firewall' => $setting['system_firewall']['buffer'], // 10
389
            ]
390
        );
391

392
        // Check the time of the last failed attempt.
393 3
        $recordAttempt = $this->getOption('record_attempt');
394

395 3
        $this->kernel->setProperty(
396 3
            'record_attempt_detection_period',
397 3
            $recordAttempt['detection_period'] // 5
398
        ); 
399

400 3
        $this->kernel->setProperty(
401 3
            'reset_attempt_counter',
402 3
            $recordAttempt['time_to_reset'] // 1800
403
        );  
404
    }
405

406
    /**
407
     * Set iptables working folder.
408
     *
409
     * @return void
410
     */
411 3
    protected function setupiptablesBridgeDirectory(): void
412
    {
413 3
        $iptablesSetting = $this->getOption('config', 'iptables');
414

415 3
        $this->kernel->setProperty(
416 3
            'iptables_watching_folder',
417 3
            $iptablesSetting['watching_folder']
418
        );
419
    }
420

421
    /**
422
     * Set the online session limit.
423
     *
424
     * @return void
425
     */
426 3
    protected function setupLimitSession(): void
427
    {
428 3
        $sessionLimitSetting = $this->getOption('online_session_limit');
429

430 3
        if ($sessionLimitSetting['enable']) {
431

432 3
            $onlineUsers = $sessionLimitSetting['config']['count'];       // default: 100
433 3
            $alivePeriod = $sessionLimitSetting['config']['period'];      // default: 300
434 3
            $isUniqueIp  = $sessionLimitSetting['config']['unique_only']; // false
435

436 3
            $this->kernel->limitSession($onlineUsers, $alivePeriod, $isUniqueIp);
437
        }
438
    }
439

440
    /**
441
     * Set the cron job.
442
     * This is triggered by the pageviews, not system cron job.
443
     *
444
     * @return void
445
     */
446 3
    protected function setupCronJob(): void 
447
    {
448 3
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
449

450 3
        if ($cronjobSetting['enable']) {
451

452 3
            $nowTime = time();
453

454 3
            $lastResetTime = $cronjobSetting['config']['last_update'];
455

456 3
            if (!empty($lastResetTime) ) {
457 3
                $lastResetTime = strtotime($lastResetTime);
458
            } else {
459
                // @codeCoverageIgnoreStart
460

461
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
462

463
                // @codeCoverageIgnoreEnd
464
            }
465

466 3
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
467

468 3
                $updateResetTime = date('Y-m-d 00:00:00');
469

470
                // Update new reset time.
471 3
                $this->setConfig(
472 3
                    'cronjob.reset_circle.config.last_update',
473 2
                    $updateResetTime
474
                );
475

476 3
                $this->updateConfig();
477

478
                // Remove all logs.
479
                /** @scrutinizer ignore-call */ 
480 3
                $this->kernel->driver->rebuild();
481
            }
482
        }
483
    }
484

485
    /**
486
     * Set the URLs that want to be excluded from Shieldon protection.
487
     *
488
     * @return void
489
     */
490 3
    protected function setupExcludedUrls(): void
491
    {
492 3
        $excludedUrls = $this->getOption('excluded_urls');
493

494 3
        if (!empty($excludedUrls)) {
495 3
            $list = array_column($excludedUrls, 'url');
496

497 3
            $this->kernel->setExcludedList($list);
498
        }
499
    }
500

501
    /**
502
     * WWW-Athentication.
503
     *
504
     * @return void
505
     */
506 3
    protected function setupPageAuthentication(): void
507
    {
508 3
        $authenticateList = $this->getOption('www_authenticate');
509

510 3
        if (is_array($authenticateList)) {
511 3
            $this->add(
512 3
                new HttpAuthentication($authenticateList)
513
            );
514
        }
515
    }
516

517
    /**
518
     * Set dialog UI.
519
     *
520
     * @return void
521
     */
522 3
    protected function setupDialogUserInterface()
523
    {
524
        Event::AddListener('session_init', function() {
525 3
            $ui = $this->getOption('dialog_ui');
526

527 3
            if (!empty($ui)) {
528 3
                get_session_instance()->set('shieldon_ui_lang', $ui['lang']);
529 3
                $this->kernel->setDialog($this->getOption('dialog_ui'));
530
            }
531 3
        });
532

533 3
        $dialogInfo = $this->getOption('dialog_info_disclosure');
534

535 3
        $this->kernel->setProperty('display_online_info', $dialogInfo['online_user_amount']);
536 3
        $this->kernel->setProperty('display_user_info',   $dialogInfo['user_inforamtion']);
537 3
        $this->kernel->setProperty('display_http_code',   $dialogInfo['http_status_code']);
538 3
        $this->kernel->setProperty('display_reason_code', $dialogInfo['reason_code']);
539 3
        $this->kernel->setProperty('display_reason_text', $dialogInfo['reason_text']);
540
    }
541
}

Read our documentation on viewing source code .

Loading