Passed
Push — 2.x ( 915907...d168c3 )
by Terry
02:15
created

MainTrait::setLogger()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
c 2
b 0
f 0
nc 3
nop 0
dl 0
loc 7
rs 10
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\Firewall;
14
15
use Shieldon\Firewall\Firewall\Driver\DriverFactory;
16
use Shieldon\Firewall\Firewall\Captcha\CaptchaFactory;
17
use Shieldon\Firewall\Log\ActionLogger;
18
use Shieldon\Firewall\Middleware as Middleware;
19
use function Shieldon\Firewall\get_request;
20
use function Shieldon\Firewall\get_session;
21
22
use RuntimeException;
23
use function strpos;
24
use function time;
25
26
/*
27
 * Main Trait for Firwall class.
28
 */
29
trait MainTrait
30
{
31
    /**
32
     * Fetch value from configuration.
33
     *
34
     * @param string $option
35
     * @param string $section
36
     *
37
     * @return mixed
38
     */
39
    abstract function getOption(string $option, string $section = '');
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
40
41
    /**
42
     * Set a data driver for the use of Shiedon Firewall.
43
     * Currently supports File, Redis, MySQL and SQLite.
44
     *
45
     * @return void
46
     */
47
    protected function setDriver(): void
48
    {
49
        $driverType = $this->getOption('driver_type');
50
        $driverSetting = $this->getOption($driverType, 'drivers');
51
52
        if (isset($driverSetting['directory_path'])) {
53
            $driverSetting['directory_path'] = $driverSetting['directory_path'] ?: $this->directory;
54
        }
55
56
        $driverInstance = DriverFactory::getInstance($driverType, $driverSetting);
57
58
        $this->status = false;
0 ignored issues
show
Bug Best Practice introduced by
The property status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
59
        if ($driverInstance !== null) {
60
            $this->kernel->setDriver($driverInstance);
61
            $this->status = true;
62
        }
63
    }
64
65
    /**
66
     * Filters
67
     *
68
     * (1) Session.
69
     * (2) Cookie generated by JavaScript code.
70
     * (3) HTTP referrer information.
71
     * (4) Pageview frequency.
72
     *
73
     * @return void
74
     */
75
    protected function setFilters(): void
76
    {
77
        $filters = [
78
            'session',
79
            'cookie',
80
            'referer',
81
            'frequency',
82
        ];
83
84
        foreach ($filters as $filter) {
85
            $setting = $this->getOption($filter, 'filters');
86
            $settings[$filter] = $setting;
87
            $filterConfig[$filter] = $setting['enable'];
88
            $filterLimit[$filter] = $setting['config']['quota']; // 5
89
            unset($setting);
90
        }
91
        unset($filterLimit['frequency']);
92
93
        $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 84. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
94
        $this->kernel->setProperty('limit_unusual_behavior', $filterLimit);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $filterLimit seems to be defined by a foreach iteration on line 84. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
95
96
        $frequencyQuota = [
97
            's' => $settings['frequency']['config']['quota_s'],
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $settings seems to be defined by a foreach iteration on line 84. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
98
            'm' => $settings['frequency']['config']['quota_m'],
99
            'h' => $settings['frequency']['config']['quota_h'],
100
            'd' => $settings['frequency']['config']['quota_d'],
101
        ];
102
103
        $this->kernel->setProperty('time_unit_quota', $frequencyQuota);
104
105
        $this->kernel->setProperty('cookie_name', $settings['cookie']['config']['cookie_name']);      // ssjd
106
        $this->kernel->setProperty('cookie_domain', $settings['cookie']['config']['cookie_domain']);  // ''
107
        $this->kernel->setProperty('cookie_value', $settings['cookie']['config']['cookie_value']);    // 1
108
109
        $this->kernel->setProperty('interval_check_referer', $settings['referer']['config']['time_buffer']);
110
        $this->kernel->setProperty('interval_check_session', $settings['referer']['config']['time_buffer']);
111
    }
112
113
    /**
114
     * Components
115
     * 
116
     * (1) Ip
117
     * (2) Rdns
118
     * (3) Header
119
     * (4) User-agent
120
     * (5) Trusted bot
121
     *
122
     * @return void
123
     */
124
    protected function setComponents(): void
125
    {
126
        $componentConfig = [
127
            'Ip'         => $this->getOption('ip', 'components'),
128
            'Rdns'       => $this->getOption('rdns', 'components'),
129
            'Header'     => $this->getOption('header', 'components'),
130
            'UserAgent'  => $this->getOption('user_agent', 'components'),
131
            'TrustedBot' => $this->getOption('trusted_bot', 'components'),
132
        ];
133
134
        foreach ($componentConfig as $className => $config) {
135
            $class = 'Shieldon\Firewall\Component\\' . $className;
136
137
            if ($config['enable']) {
138
                $componentInstance = new $class();
139
                $componentInstance->setStrict($config['strict_mode']);
140
141
                if ($className === 'Ip') {
142
                    // Need Ip component to be loaded before calling this method.
143
                    $this->applyComponentIpManager();
144
                }
145
146
                $this->kernel->setComponent($componentInstance);
147
            }
148
        }
149
    }
150
151
    /**
152
     * Captcha modules.
153
     * 
154
     * (1) Google ReCaptcha
155
     * (2) Simple image captcha.
156
     *
157
     * @return void
158
     */
159
    protected function setCaptchas(): void
160
    {
161
        $captchaList = [
162
            'recaptcha',
163
            'image',
164
        ];
165
166
        foreach ($captchaList as $captcha) {
167
            $setting = (array) $this->getOption($captcha, 'captcha_modules');
168
169
            // Initialize messenger instances from the factory/
170
            if (CaptchaFactory::check($captcha, $setting)) {
171
172
                $this->kernel->setCaptcha(
173
                    CaptchaFactory::getInstance(
174
                        // The ID of the captcha module in the configuration.
175
                        $captcha, 
176
                        // The settings of the captcha module in the configuration.
177
                        $setting    
178
                    )
179
                );
180
            }
181
182
            unset($setting);
183
        }
184
    }
185
186
    /**
187
     * Set up the action logger.
188
     *
189
     * @return void
190
     */
191
    protected function setLogger(): void
192
    {
193
        $loggerSetting = $this->getOption('action', 'loggers');
194
195
        if ($loggerSetting['enable']) {
196
            if (!empty($loggerSetting['config']['directory_path'])) {
197
                $this->kernel->setLogger(new ActionLogger($loggerSetting['config']['directory_path']));
198
            }
199
        }
200
    }
201
202
    /**
203
     * Apply the denied list and the allowed list to Ip Component.
204
     */
205
    protected function applyComponentIpManager()
206
    {
207
        $ipList = (array) $this->getOption('ip_manager');
208
209
        $allowedList = [];
210
        $deniedList = [];
211
212
        foreach ($ipList as $ip) {
213
214
            if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
215
216
                if ('allow' === $ip['rule']) {
217
                    $allowedList[] = $ip['ip'];
218
                }
219
220
                if ('deny' === $ip['rule']) {
221
                    $deniedList[] = $ip['ip'];
222
                }
223
            }
224
        }
225
226
        $this->kernel->component['Ip']->setAllowedItems($allowedList);
227
        $this->kernel->component['Ip']->setDeniedItems($deniedList);
228
    }
229
230
    /**
231
     * Set the channel ID.
232
     *
233
     * @return void
234
     */
235
    protected function setChannel(): void
236
    {
237
        $channelId = $this->getOption('channel_id');
238
239
        if ($channelId) {
240
            $this->kernel->setChannel($channelId);
241
        }
242
    }
243
244
    /**
245
     * If you use CDN, please choose the real IP source.
246
     *
247
     * @return void
248
     */
249
    protected function setIpSource(): void
250
    {
251
        $ipSourceType = $this->getOption('ip_variable_source');
252
        $serverParams = get_request()->getServerParams();
253
254
        /**
255
         * REMOTE_ADDR: general
256
         * HTTP_CF_CONNECTING_IP: Cloudflare
257
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
258
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
259
         * 
260
         */
261
        $key = array_search(true, $ipSourceType);
262
        $ip = $serverParams[$key];
263
264
        if (empty($ip)) {
265
            // @codeCoverageIgnoreStart
266
            throw new RuntimeException('IP source is not set correctly.');
267
            // @codeCoverageIgnoreEnd
268
        }
269
270
        $this->kernel->setIp($ip);
271
    }
272
273
    /**
274
     * Set deny attempts.
275
     *
276
     * @return void
277
     */
278
    protected function setDenyTooManyAttempts(): void
279
    {
280
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
281
282
        $enableDataCircle     = $setting['data_circle']['enable']     ?: false;
283
        $enableSystemFirewall = $setting['system_firewall']['enable'] ?: false;
284
285
        $this->kernel->setProperty('deny_attempt_enable', [
286
            'data_circle'     => $enableDataCircle,
287
            'system_firewall' => $enableSystemFirewall,
288
        ]);
289
290
        $this->kernel->setProperty('deny_attempt_buffer', [
291
            'data_circle'     => $setting['data_circle']['buffer'] ?? 10,
292
            'system_firewall' => $setting['data_circle']['buffer'] ?? 10,
293
        ]);
294
295
        // Check the time of the last failed attempt. @since 0.2.0
296
        $recordAttempt = $this->getOption('record_attempt');
297
298
        $detectionPeriod = $recordAttempt['detection_period'] ?? 5;
299
        $timeToReset     = $recordAttempt['time_to_reset']    ?? 1800;
300
301
        $this->kernel->setProperty('record_attempt_detection_period', $detectionPeriod);
302
        $this->kernel->setProperty('reset_attempt_counter', $timeToReset);
303
    }
304
305
    /**
306
     * Set iptables working folder.
307
     *
308
     * @return void
309
     */
310
    protected function setIptablesBridgeDirectory(): void
311
    {
312
        $iptablesSetting = $this->getOption('config', 'iptables');
313
        $this->kernel->setProperty('iptables_watching_folder',  $iptablesSetting['watching_folder']);
314
    }
315
316
    /**
317
     * Set the online session limit.
318
     *
319
     * @return void
320
     */
321
    protected function setSessionLimit(): void
322
    {
323
        $sessionLimitSetting = $this->getOption('online_session_limit');
324
325
        if ($sessionLimitSetting['enable']) {
326
327
            $onlineUsers = $sessionLimitSetting['config']['count']  ?? 100;
328
            $alivePeriod = $sessionLimitSetting['config']['period'] ?? 300;
329
330
            $this->kernel->limitSession($onlineUsers, $alivePeriod);
331
        }
332
    }
333
334
    /**
335
     * Set the cron job.
336
     * This is triggered by the pageviews, not system cron job.
337
     *
338
     * @return void
339
     */
340
    protected function setCronJob(): void 
341
    {
342
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
343
344
        if ($cronjobSetting['enable']) {
345
346
            $nowTime = time();
347
348
            $lastResetTime = $cronjobSetting['config']['last_update'];
349
350
            if (!empty($lastResetTime) ) {
351
                $lastResetTime = strtotime($lastResetTime);
352
            } else {
353
                // @codeCoverageIgnoreStart
354
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
355
                // @codeCoverageIgnoreEnd
356
            }
357
358
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
359
360
                $updateResetTime = date('Y-m-d 00:00:00');
361
362
                // Update new reset time.
363
                $this->setConfig('cronjob.reset_circle.config.last_update', $updateResetTime);
0 ignored issues
show
Bug introduced by
It seems like setConfig() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

363
                $this->/** @scrutinizer ignore-call */ 
364
                       setConfig('cronjob.reset_circle.config.last_update', $updateResetTime);
Loading history...
364
                $this->updateConfig();
0 ignored issues
show
Bug introduced by
It seems like updateConfig() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

364
                $this->/** @scrutinizer ignore-call */ 
365
                       updateConfig();
Loading history...
365
366
                // Remove all logs.
367
                $this->kernel->driver->rebuild();
368
            }
369
        }
370
    }
371
372
    /**
373
     * Set the URLs that want to be excluded from Shieldon protection.
374
     *
375
     * @return void
376
     */
377
    protected function setExcludedUrls(): void
378
    {
379
        $excludedUrls = $this->getOption('excluded_urls');
380
381
        if (!empty($excludedUrls)) {
382
            $list = array_column($excludedUrls, 'url');
383
384
            $this->kernel->setExcludedUrls($list);
385
        }
386
    }
387
388
    /**
389
     * WWW-Athentication.
390
     *
391
     * @return void
392
     */
393
    protected function setPageAuthentication(): void
394
    {
395
        $authenticateList = $this->getOption('www_authenticate');
396
397
        if (is_array($authenticateList)) {
398
            $this->add(new Middleware\httpAuthentication($authenticateList));
0 ignored issues
show
Bug introduced by
It seems like add() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

398
            $this->/** @scrutinizer ignore-call */ 
399
                   add(new Middleware\httpAuthentication($authenticateList));
Loading history...
399
        }
400
    }
401
402
    /**
403
     * Set dialog UI.
404
     *
405
     * @return void
406
     */
407
    protected function setDialogUserInterface()
408
    {
409
        $ui = $this->getOption('dialog_ui');
410
411
        if (!empty($ui)) {
412
            get_session()->set('shieldon_ui_lang', $ui['lang']);
413
            $this->kernel->setDialog($this->getOption('dialog_ui'));
414
        }
415
    }
416
}
417