Passed
Push — 2.x ( 008340...958536 )
by Terry
02:17
created

Firewall::setExcludedUrls()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
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
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
20
21
use Shieldon\Firewall\Middleware as Middleware;
22
23
use Shieldon\Firewall\Utils\Container;
24
use Shieldon\Firewall\Log\ActionLogger;
25
use Shieldon\Firewall\FirewallTrait;
26
use Shieldon\Firewall\MessengerTrait;
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\MessengerTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
use Shieldon\Firewall\Firewall\XssProtectionTrait;
28
use Shieldon\Psr15\RequestHandler;
29
use function Shieldon\Firewall\get_request;
30
31
use RuntimeException;
32
33
use function array_column;
34
use function defined;
35
use function file_exists;
36
use function file_get_contents;
37
use function file_put_contents;
38
use function is_dir;
39
use function json_decode;
40
use function json_encode;
41
use function mkdir;
42
use function rtrim;
43
use function strpos;
44
use function umask;
45
use function time;
46
use function strtotime;
47
use function date;
48
49
/**
50
 * Managed Firewall.
51
 */
52
class Firewall
53
{
54
    use FirewallTrait;
55
    use MainTrait;
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\MainTrait was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
56
    use XssProtectionTrait;
57
    use MessengerTrait;
58
59
    /**
60
     * Collection of PSR-7 or PSR-15 middlewares.
61
     *
62
     * @var array
63
     */
64
    protected $middlewares = [];
65
66
    /**
67
     * Constructor.
68
     */
69
    public function __construct(?ServerRequestInterface $request = null, ?ResponseInterface $response = null)
70
    {
71
        Container::set('firewall', $this);
72
73
        $this->kernel = new Kernel($request, $response);
74
    }
75
76
    /**
77
     * Set up the path of the configuration file.
78
     *
79
     * @param string $source The path.
80
     * @param string $type   The type.
81
     * 
82
     * @return void
83
     */
84
    public function configure(string $source, string $type = 'json')
85
    {
86
        if ($type === 'json') {
87
            $this->directory = rtrim($source, '\\/');
88
            $configFilePath = $this->directory . '/' . $this->filename;
89
90
            if (file_exists($configFilePath)) {
91
                $jsonString = file_get_contents($configFilePath);
92
93
            } else {
94
                $jsonString = file_get_contents(__DIR__ . '/../../config.json');
95
96
                if (defined('PHP_UNIT_TEST')) {
97
                    $jsonString = file_get_contents(__DIR__ . '/../../tests/config.json');
98
                }
99
            }
100
101
            $this->configuration = json_decode($jsonString, true);
102
            $this->kernel->managedBy('managed');
103
104
        } elseif ($type === 'php') {
105
            $this->configuration = require $source;
106
            $this->kernel->managedBy('config');
107
        }
108
109
        $this->setup();
110
    }
111
112
    /**
113
     * Add middlewares and use them before going into Shieldon kernal.
114
     *
115
     * @param MiddlewareInterface $middleware A PSR-15 middlewares.
116
     *
117
     * @return void
118
     */
119
    public function add(MiddlewareInterface $middleware)
120
    {
121
        $this->middlewares[] = $middleware;
122
    }
123
124
    /**
125
     * Setup everything we need.
126
     *
127
     * @return void
128
     */
129
    public function setup(): void
130
    {
131
        $this->status = $this->getOption('daemon');
132
133
        $this->setDriver();
134
135
        $this->setChannel();
136
137
        $this->setIpSource();
138
139
        $this->setLogger();
140
141
        $this->setSessionLimit();
142
143
        $this->setCronJob();
144
145
        $this->setExcludedUrls();
146
147
        $this->setXssProtection();
148
149
        $this->setAuthentication();
150
151
        $this->setDialogUI();
152
153
        $this->setMessengers();
154
155
        $this->setMessageEvents();
156
157
        $this->setDenyAttempts();
158
159
        $this->setIptablesWatchingFolder();
160
    }
161
162
    /**
163
     * Just, run!
164
     *
165
     * @return ResponseInterface
166
     */
167
    public function run(): ResponseInterface
168
    {
169
        // If settings are ready, let's start monitoring requests.
170
        if ($this->status) {
171
172
            $response = get_request();
173
174
            // PSR-15 request handler.
175
            $requestHandler = new RequestHandler();
176
177
            foreach ($this->middlewares as $middleware) {
178
                $requestHandler->add($middleware);
179
            }
180
181
            $response = $requestHandler->handle($response);
182
183
            // Something is detected by Middlewares, return.
184
            if ($response->getStatusCode() !== 200) {
185
                return $response;
186
            }
187
188
            $result = $this->kernel->run();
189
190
            if ($result !== $this->kernel::RESPONSE_ALLOW) {
191
192
                if ($this->kernel->captchaResponse()) {
193
                    $this->kernel->unban();
194
195
                    $response = $response->withHeader('Location', $this->kernel->getCurrentUrl());
196
                    $response = $response->withStatus(303);
197
198
                    return $response;
199
                }
200
            }
201
        }
202
203
        return $this->kernel->respond();
204
    }
205
206
    /**
207
     * Set the channel ID.
208
     *
209
     * @return void
210
     */
211
    protected function setChannel(): void
212
    {
213
        $channelId = $this->getOption('channel_id');
214
215
        if ($channelId) {
216
            $this->kernel->setChannel($channelId);
217
        }
218
    }
219
220
    /**
221
     * If you use CDN, please choose the real IP source.
222
     *
223
     * @return void
224
     */
225
    protected function setIpSource(): void
226
    {
227
        $ipSourceType = $this->getOption('ip_variable_source');
228
        $serverParams = get_request()->getServerParams();
229
230
        /**
231
         * REMOTE_ADDR: general
232
         * HTTP_CF_CONNECTING_IP: Cloudflare
233
         * HTTP_X_FORWARDED_FOR: Google Cloud CDN, Google Load-balancer, AWS.
234
         * HTTP_X_FORWARDED_HOST: KeyCDN, or other CDN providers not listed here.
235
         * 
236
         */
237
        $key = array_search(true, $ipSourceType);
0 ignored issues
show
Bug introduced by
It seems like $ipSourceType can also be of type false; however, parameter $haystack of array_search() 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

237
        $key = array_search(true, /** @scrutinizer ignore-type */ $ipSourceType);
Loading history...
238
        $ip = $serverParams[$key];
239
240
        if (empty($ip)) {
241
            // @codeCoverageIgnoreStart
242
            throw new RuntimeException('IP source is not set correctly.');
243
            // @codeCoverageIgnoreEnd
244
        }
245
246
        $this->kernel->setIp($ip);
247
    }
248
249
    /**
250
     * Set deny attempts.
251
     *
252
     * @return void
253
     */
254
    protected function setDenyAttempts(): void
255
    {
256
        $setting = $this->getOption('failed_attempts_in_a_row', 'events');
257
258
        $enableDataCircle     = $setting['data_circle']['enable']     ?: false;
259
        $enableSystemFirewall = $setting['system_firewall']['enable'] ?: false;
260
261
        $this->kernel->setProperty('deny_attempt_enable', [
262
            'data_circle'     => $enableDataCircle,
263
            'system_firewall' => $enableSystemFirewall,
264
        ]);
265
266
        $this->kernel->setProperty('deny_attempt_buffer', [
267
            'data_circle'     => $setting['data_circle']['buffer'] ?? 10,
268
            'system_firewall' => $setting['data_circle']['buffer'] ?? 10,
269
        ]);
270
271
        // Check the time of the last failed attempt. @since 0.2.0
272
        $recordAttempt = $this->getOption('record_attempt');
273
274
        $detectionPeriod = $recordAttempt['detection_period'] ?? 5;
275
        $timeToReset     = $recordAttempt['time_to_reset']    ?? 1800;
276
277
        $this->kernel->setProperty('record_attempt_detection_period', $detectionPeriod);
278
        $this->kernel->setProperty('reset_attempt_counter', $timeToReset);
279
    }
280
281
    /**
282
     * Set iptables working folder.
283
     *
284
     * @return void
285
     */
286
    protected function setIptablesWatchingFolder(): void
287
    {
288
        $iptablesSetting = $this->getOption('config', 'iptables');
289
        $this->kernel->setProperty('iptables_watching_folder',  $iptablesSetting['watching_folder']);
290
    }
291
292
    /**
293
     * Set the online session limit.
294
     *
295
     * @return void
296
     */
297
    protected function setSessionLimit(): void
298
    {
299
        $sessionLimitSetting = $this->getOption('online_session_limit');
300
301
        if ($sessionLimitSetting['enable']) {
302
303
            $onlineUsers = $sessionLimitSetting['config']['count']  ?? 100;
304
            $alivePeriod = $sessionLimitSetting['config']['period'] ?? 300;
305
306
            $this->kernel->limitSession($onlineUsers, $alivePeriod);
307
        }
308
    }
309
310
    /**
311
     * Set the cron job.
312
     * This is triggered by the pageviews, not system cron job.
313
     *
314
     * @return void
315
     */
316
    protected function setCronJob(): void 
317
    {
318
        if (!$this->status) {
319
            return;
320
        }
321
322
        $cronjobSetting = $this->getOption('reset_circle', 'cronjob');
323
324
        if ($cronjobSetting['enable']) {
325
326
            $nowTime = time();
327
328
            $lastResetTime = $cronjobSetting['config']['last_update'];
329
330
            if (!empty($lastResetTime) ) {
331
                $lastResetTime = strtotime($lastResetTime);
332
            } else {
333
                // @codeCoverageIgnoreStart
334
                $lastResetTime = strtotime(date('Y-m-d 00:00:00'));
335
                // @codeCoverageIgnoreEnd
336
            }
337
338
            if (($nowTime - $lastResetTime) > $cronjobSetting['config']['period']) {
339
340
                $updateResetTime = date('Y-m-d 00:00:00');
341
342
                // Update new reset time.
343
                $this->setConfig('cronjob.reset_circle.config.last_update', $updateResetTime);
344
                $this->updateConfig();
345
346
                // Remove all logs.
347
                $this->kernel->driver->rebuild();
348
            }
349
        }
350
    }
351
352
    /**
353
     * Set the URLs that want to be excluded from Shieldon protection.
354
     *
355
     * @return void
356
     */
357
    protected function setExcludedUrls(): void
358
    {
359
        $excludedUrls = $this->getOption('excluded_urls');
360
361
        if (!empty($excludedUrls)) {
362
            $list = array_column($excludedUrls, 'url');
363
364
            $this->kernel->setExcludedUrls($list);
365
        }
366
    }
367
368
    /**
369
     * WWW-Athentication.
370
     *
371
     * @return void
372
     */
373
    protected function setAuthentication(): void
374
    {
375
        $authenticateList = $this->getOption('www_authenticate');
376
377
        if (is_array($authenticateList)) {
378
            $this->add(new Middleware\httpAuthentication($authenticateList));
379
        }
380
    }
381
382
    /**
383
     * Apply the denied list and the allowed list to Ip Component.
384
     */
385
    protected function applyComponentIpManager()
386
    {
387
        $ipList = $this->getOption('ip_manager');
388
389
        $allowedList = [];
390
        $deniedList = [];
391
392
        if (is_array($ipList)) {
393
            foreach ($ipList as $ip) {
394
395
                if (0 === strpos($this->kernel->getCurrentUrl(), $ip['url']) ) {
396
    
397
                    if ('allow' === $ip['rule']) {
398
                        $allowedList[] = $ip['ip'];
399
                    }
400
    
401
                    if ('deny' === $ip['rule']) {
402
                        $deniedList[] = $ip['ip'];
403
                    }
404
                }
405
            }
406
        }
407
408
        $this->kernel->component['Ip']->setAllowedItems($allowedList);
409
        $this->kernel->component['Ip']->setDeniedItems($deniedList);
410
    }
411
412
    /**
413
     * Set dialog UI.
414
     *
415
     * @return void
416
     */
417
    protected function setDialogUI()
418
    {
419
        $ui = $this->getOption('dialog_ui');
420
421
        if (!empty($ui)) {
422
            get_session()->set('shieldon_ui_lang', $ui['lang']);
423
            $this->kernel->setDialogUI($this->getOption('dialog_ui'));
424
        }
425
    }
426
}
427