Passed
Push — 2.x ( 4bb95a...adaf26 )
by Terry
01:49
created

Kernel::setDialog()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
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
 * 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;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Psr\Http\Message\ServerRequestInterface;
27
use Shieldon\Firewall\Captcha\Foundation;
28
use Shieldon\Firewall\Helpers;
29
use Shieldon\Firewall\HttpFactory;
30
use Shieldon\Firewall\IpTrait;
31
use Shieldon\Firewall\Kernel\CaptchaTrait;
32
use Shieldon\Firewall\Kernel\ComponentTrait;
33
use Shieldon\Firewall\Kernel\DriverTrait;
34
use Shieldon\Firewall\Kernel\FilterTrait;
35
use Shieldon\Firewall\Kernel\MessengerTrait;
36
use Shieldon\Firewall\Kernel\RuleTrait;
37
use Shieldon\Firewall\Kernel\SessionTrait;
38
use Shieldon\Firewall\Kernel\TemplateTrait;
39
use Shieldon\Firewall\Log\ActionLogger;
40
use Shieldon\Firewall\Utils\Container;
41
use function Shieldon\Firewall\get_default_properties;
42
use function Shieldon\Firewall\get_request;
43
use function Shieldon\Firewall\get_session;
44
45
use Closure;
46
use RuntimeException;
47
use function array_push;
48
use function file_exists;
49
use function get_class;
50
use function gethostbyaddr;
51
use function ltrim;
52
use function strpos;
53
use function strrpos;
54
use function substr;
55
use function time;
56
57
/**
58
 * The primary Shiendon class.
59
 */
60
class Kernel
61
{
62
    /**
63
     *   Public methods       | Desctiotion
64
     *  ----------------------|---------------------------------------------
65
     *   ban                  | Ban an IP.
66
     *   getCurrentUrl        | Get current user's browsing path.
67
     *   managedBy            | Used on testing purpose.
68
     *   run                  | Run the checking process.
69
     *   setClosure           | Set a closure function.
70
     *   exclude              | Set a URL you want them excluded them from protection.
71
     *   setExcludedList      | Set the URLs you want them excluded them from protection.
72
     *   setLogger            | Set the action log logger.
73
     *   setProperties        | Set the property settings.
74
     *   setProperty          | Set a property setting.
75
     *   setStrict            | Strict mode apply to all components.
76
     *   unban                | Unban an IP.
77
     *  ----------------------|---------------------------------------------
78
     */
79
80
    /**
81
     *   Public methods       | Desctiotion
82
     *  ----------------------|---------------------------------------------
83
     *   setIp                | Ban an IP.
84
     *   getIp                | Get current user's browsing path.
85
     *   setRdns              | Print a JavaScript snippet in the pages.
86
     *   getRdns              | Used on testing purpose.
87
     *  ----------------------|---------------------------------------------
88
     */
89
    use CaptchaTrait;
90
91
    /**
92
     *   Public methods       | Desctiotion
93
     *  ----------------------|---------------------------------------------
94
     *   setComponent         | Set a commponent.
95
     *   getComponent         | Get a component instance from component's container.
96
     *   disableComponents    | Disable all components.
97
     *  ----------------------|---------------------------------------------
98
     */
99
    use ComponentTrait;
100
101
    /**
102
     *   Public methods       | Desctiotion
103
     *  ----------------------|---------------------------------------------
104
     *   setDriver            | Set a data driver.
105
     *   setChannel           | Set a data channel.
106
     *   disableDbBuilder     | disable creating data tables.
107
     *  ----------------------|---------------------------------------------
108
     */
109
    use DriverTrait;
110
111
    /**
112
     *   Public methods       | Desctiotion
113
     *  ----------------------|---------------------------------------------
114
     *   setFilters           | Set the filters.
115
     *   setFilter            | Set a filter.
116
     *   disableFilters       | Disable all filters.
117
     *  ----------------------|---------------------------------------------
118
     */
119
    use FilterTrait;
120
121
    /**
122
     *   Public methods       | Desctiotion
123
     *  ----------------------|---------------------------------------------
124
     *   setIp                | Set an IP address.
125
     *   getIp                | Get current set IP.
126
     *   setRdns              | Set a RDNS record for the check.
127
     *   getRdns              | Get IP resolved hostname.
128
     *  ----------------------|---------------------------------------------
129
     */
130
    use IpTrait;
131
132
    /**
133
     *   Public methods       | Desctiotion
134
     *  ----------------------|---------------------------------------------
135
     *   setMessenger         | Set a messenger
136
     *  ----------------------|---------------------------------------------
137
     */
138
    use MessengerTrait;
139
140
    /**
141
     *   Public methods       | Desctiotion
142
     *  ----------------------|---------------------------------------------
143
     *                        | No public methods.
144
     *  ----------------------|---------------------------------------------
145
     */
146
    use RuleTrait;
147
148
    /**
149
     *   Public methods       | Desctiotion
150
     *  ----------------------|---------------------------------------------
151
     *   limitSession         | Limit the amount of the online users.
152
     *   getSessionCount      | Get the amount of the sessions.
153
     *  ----------------------|---------------------------------------------
154
     */
155
    use SessionTrait;
156
157
    /**
158
     *   Public methods       | Desctiotion
159
     *  ----------------------|---------------------------------------------
160
     *   respond              | Respond the result.
161
     *   setTemplateDirectory | Set the frontend template directory.
162
     *   getJavascript        | Print a JavaScript snippet in the pages.
163
     *  ----------------------|---------------------------------------------
164
     */
165
    use TemplateTrait;
166
167
    /**
168
     * HTTP Status Codes
169
     */
170
    const HTTP_STATUS_OK                 = 200;
171
    const HTTP_STATUS_SEE_OTHER          = 303;
172
    const HTTP_STATUS_BAD_REQUEST        = 400;
173
    const HTTP_STATUS_FORBIDDEN          = 403;
174
    const HTTP_STATUS_TOO_MANY_REQUESTS  = 429;
175
176
    /**
177
     * Reason Codes (ALLOW)
178
     */
179
    const REASON_IS_SEARCH_ENGINE        = 100;
180
    const REASON_IS_GOOGLE               = 101;
181
    const REASON_IS_BING                 = 102;
182
    const REASON_IS_YAHOO                = 103;
183
    const REASON_IS_SOCIAL_NETWORK       = 110;
184
    const REASON_IS_FACEBOOK             = 111;
185
    const REASON_IS_TWITTER              = 112;
186
187
    /**
188
     * Reason Codes (DENY)
189
     */
190
    const REASON_TOO_MANY_SESSIONS       = 1;
191
    const REASON_TOO_MANY_ACCESSES       = 2; // (not used)
192
    const REASON_EMPTY_JS_COOKIE         = 3;
193
    const REASON_EMPTY_REFERER           = 4;
194
    const REASON_REACHED_LIMIT_DAY       = 11;
195
    const REASON_REACHED_LIMIT_HOUR      = 12;
196
    const REASON_REACHED_LIMIT_MINUTE    = 13;
197
    const REASON_REACHED_LIMIT_SECOND    = 14;
198
    const REASON_INVALID_IP              = 40;
199
    const REASON_DENY_IP                 = 41;
200
    const REASON_ALLOW_IP                = 42;
201
    const REASON_COMPONENT_IP            = 81;
202
    const REASON_COMPONENT_RDNS          = 82;
203
    const REASON_COMPONENT_HEADER        = 83;
204
    const REASON_COMPONENT_USERAGENT     = 84;
205
    const REASON_COMPONENT_TRUSTED_ROBOT = 85;
206
    const REASON_MANUAL_BAN              = 99;
207
208
    /**
209
     * Action Codes
210
     */
211
    const ACTION_DENY                    = 0;
212
    const ACTION_ALLOW                   = 1;
213
    const ACTION_TEMPORARILY_DENY        = 2;
214
    const ACTION_UNBAN                   = 9;
215
216
    /**
217
     * Result Codes
218
     */
219
    const RESPONSE_DENY                  = 0;
220
    const RESPONSE_ALLOW                 = 1;
221
    const RESPONSE_TEMPORARILY_DENY      = 2;
222
    const RESPONSE_LIMIT_SESSION         = 3;
223
224
    /**
225
     * Logger Codes
226
     */
227
    const LOG_LIMIT                      = 3;
228
    const LOG_PAGEVIEW                   = 11;
229
    const LOG_BLACKLIST                  = 98;
230
    const LOG_CAPTCHA                    = 99;
231
232
    const KERNEL_DIR = __DIR__;
233
234
    /**
235
     * The result passed from filters, compoents, etc.
236
     * 
237
     * DENY    : 0
238
     * ALLOW   : 1
239
     * CAPTCHA : 2
240
     *
241
     * @var int
242
     */
243
    protected $result = 1;
244
245
    /**
246
     * Default settings
247
     *
248
     * @var array
249
     */
250
    protected $properties = [];
251
252
    /**
253
     * Logger instance.
254
     *
255
     * @var ActionLogger
256
     */
257
    public $logger;
258
259
    /**
260
     * The closure functions that will be executed in this->run()
261
     *
262
     * @var array
263
     */
264
    protected $closures = [];
265
266
    /**
267
     * URLs that are excluded from Shieldon's protection.
268
     *
269
     * @var array
270
     */
271
    protected $excludedUrls = [];
272
273
    /**
274
     * Strict mode.
275
     * 
276
     * Set by `strictMode()` only. The default value of this propertry is undefined.
277
     *
278
     * @var bool|null
279
     */
280
    protected $strictMode;
281
282
    /**
283
     * The directory in where the frontend template files are placed.
284
     *
285
     * @var string
286
     */
287
    protected $templateDirectory = '';
288
289
    /**
290
     * Which type of configuration source that Shieldon firewall managed?
291
     * value: managed | config | self | demo
292
     *
293
     * @var string
294
     */
295
    protected $firewallType = 'self'; 
296
297
    /**
298
     * Shieldon constructor.
299
     *
300
     * @param ServerRequestInterface|null $request  A PSR-7 server request.
301
     * @param ResponseInterface|null      $response A PSR-7 server response.
302
     *
303
     * @return void
304
     */
305
    public function __construct(?ServerRequestInterface $request = null, ?ResponseInterface $response = null)
306
    {
307
        // Load helper functions. This is the must.
308
        new Helpers();
309
310
        $request = $request ?? HttpFactory::createRequest();
311
        $response = $response ?? HttpFactory::createResponse();
312
        $session = HttpFactory::createSession();
313
314
        $this->properties = get_default_properties();
315
        $this->setCaptcha(new Foundation());
316
317
        Container::set('request', $request);
318
        Container::set('response', $response);
319
        Container::set('session', $session);
320
        Container::set('shieldon', $this);
321
    }
322
323
    /**
324
     * Run, run, run!
325
     *
326
     * Check the rule tables first, if an IP address has been listed.
327
     * Call function filter() if an IP address is not listed in rule tables.
328
     *
329
     * @return int
330
     */
331
    public function run(): int
332
    {
333
        $this->assertDriver();
334
335
        // Ignore the excluded urls.
336
        foreach ($this->excludedUrls as $url) {
337
            if (strpos($this->getCurrentUrl(), $url) === 0) {
338
                return $this->result = self::RESPONSE_ALLOW;
339
            }
340
        }
341
342
        // Execute closure functions.
343
        foreach ($this->closures as $closure) {
344
            $closure();
345
        }
346
347
        $result = $this->process();
348
349
        if ($result !== self::RESPONSE_ALLOW) {
350
351
            // Current session did not pass the CAPTCHA, it is still stuck in 
352
            // CAPTCHA page.
353
            $actionCode = self::LOG_CAPTCHA;
354
355
            // If current session's respone code is RESPONSE_DENY, record it as 
356
            // `blacklist_count` in our logs.
357
            // It is stuck in warning page, not CAPTCHA.
358
            if ($result === self::RESPONSE_DENY) {
359
                $actionCode = self::LOG_BLACKLIST;
360
            }
361
362
            if ($result === self::RESPONSE_LIMIT_SESSION) {
363
                $actionCode = self::LOG_LIMIT;
364
            }
365
366
            $this->log($actionCode);
367
368
        } else {
369
370
            $this->log(self::LOG_PAGEVIEW);
371
        }
372
373
        // @ MessengerTrait
374
        $this->triggerMessengers();
375
376
        return $result;
377
    }
378
379
    /**
380
     * Ban an IP.
381
     *
382
     * @param string $ip A valid IP address.
383
     *
384
     * @return void
385
     */
386
    public function ban(string $ip = ''): void
387
    {
388
        if ('' === $ip) {
389
            $ip = $this->ip;
390
        }
391
 
392
        $this->action(
393
            self::ACTION_DENY,
394
            self::REASON_MANUAL_BAN,
395
            $ip
396
        );
397
    }
398
399
    /**
400
     * Unban an IP.
401
     *
402
     * @param string $ip A valid IP address.
403
     *
404
     * @return void
405
     */
406
    public function unban(string $ip = ''): void
407
    {
408
        if ($ip === '') {
409
            $ip = $this->ip;
410
        }
411
412
        $this->action(
413
            self::ACTION_UNBAN,
414
            self::REASON_MANUAL_BAN,
415
            $ip
416
        );
417
        $this->log(self::ACTION_UNBAN);
418
419
        $this->result = self::RESPONSE_ALLOW;
420
    }
421
422
    /**
423
     * Set a property setting.
424
     *
425
     * @param string $key   The key of a property setting.
426
     * @param mixed  $value The value of a property setting.
427
     *
428
     * @return void
429
     */
430
    public function setProperty(string $key = '', $value = '')
431
    {
432
        if (isset($this->properties[$key])) {
433
            $this->properties[$key] = $value;
434
        }
435
    }
436
437
    /**
438
     * Set the property settings.
439
     * 
440
     * @param array $settings The settings.
441
     *
442
     * @return void
443
     */
444
    public function setProperties(array $settings): void
445
    {
446
        foreach (array_keys($this->properties) as $k) {
447
            if (isset($settings[$k])) {
448
                $this->properties[$k] = $settings[$k];
449
            }
450
        }
451
    }
452
453
    /**
454
     * Strict mode.
455
     * This option will take effects to all components.
456
     * 
457
     * @param bool $bool Set true to enble strict mode, false to disable it overwise.
458
     *
459
     * @return void
460
     */
461
    public function setStrict(bool $bool)
462
    {
463
        $this->strictMode = $bool;
464
    }
465
466
    /**
467
     * Set an action log logger.
468
     *
469
     * @param ActionLogger $logger Record action logs for users.
470
     *
471
     * @return void
472
     */
473
    public function setLogger(ActionLogger $logger): void
474
    {
475
        $this->logger = $logger;
476
    }
477
478
    /**
479
     * Add a path into the excluded list.
480
     *
481
     * @param string $uriPath The path component of a URI.
482
     * 
483
     * @return void
484
     */
485
    public function exclude(string $uriPath): void
486
    {
487
        $uriPath = '/' . ltrim($uriPath, '/');
488
489
        array_push($this->excludedUrls, $uriPath);
490
    }
491
492
    /**
493
     * Set the URLs you want them excluded them from protection.
494
     *
495
     * @param array $urls The list of URL want to be excluded.
496
     *
497
     * @return void
498
     */
499
    public function setExcludedList(array $urls = []): void
500
    {
501
        $this->excludedUrls = $urls;
502
    }
503
504
    /**
505
     * Set a closure function.
506
     *
507
     * @param string  $key     The name for the closure class.
508
     * @param Closure $closure An instance will be later called.
509
     *
510
     * @return void
511
     */
512
    public function setClosure(string $key, Closure $closure): void
513
    {
514
        $this->closures[$key] = $closure;
515
    }
516
517
    /**
518
     * Get current visior's path.
519
     *
520
     * @return string
521
     */
522
    public function getCurrentUrl(): string
523
    {
524
        return get_request()->getUri()->getPath();
525
    }
526
527
    /**
528
     * Displayed on Firewall Panel, telling you current what type of 
529
     * configuration is used.
530
     * 
531
     * @param string $type The type of configuration.
532
     *                     accepted value: demo | managed | config
533
     *
534
     * @return void
535
     */
536
    public function managedBy(string $type = ''): void
537
    {
538
        if (in_array($type, ['managed', 'config', 'demo'])) {
539
            $this->firewallType = $type;
540
        }
541
    }
542
543
    /*
544
    |-------------------------------------------------------------------
545
    | Non-public methids.
546
    |-------------------------------------------------------------------
547
    */
548
549
    /**
550
     * Run, run, run!
551
     *
552
     * Check the rule tables first, if an IP address has been listed.
553
     * Call function filter() if an IP address is not listed in rule tables.
554
     *
555
     * @return int The response code.
556
     */
557
    protected function process(): int
558
    {
559
        $this->driver->init($this->isCreateDatabase);
560
561
        $this->initComponents();
562
563
        $processMethods = [
564
            'isRuleExist',   // Stage 1 - Looking for rule table.
565
            'isTrustedBot',  // Stage 2 - Detect popular search engine.
566
            'isFakeRobot',   // Stage 3 - Reject fake search engine crawlers.
567
            'isIpComponent', // Stage 4 - IP manager.
568
            'isComponents'   // Stage 5 - Check other components.
569
        ];
570
571
        foreach ($processMethods as $method) {
572
            if ($this->{$method}()) {
573
                return $this->result;
574
            }
575
        }
576
577
        // Stage 6 - Check filters if set.
578
        if (array_search(true, $this->filterStatus)) {
579
            return $this->result = $this->sessionHandler($this->filter());
580
        }
581
582
        // Stage 7 - Go into session limit check.
583
        return $this->result = $this->sessionHandler(self::RESPONSE_ALLOW);
584
    }
585
586
    /**
587
     * Start an action for this IP address, allow or deny, and give a reason for it.
588
     *
589
     * @param int    $actionCode The action code. - 0: deny, 1: allow, 9: unban.
590
     * @param string $reasonCode The response code.
591
     * @param string $assignIp   The IP address.
592
     * 
593
     * @return void
594
     */
595
    protected function action(
596
        int    $actionCode,
597
        int    $reasonCode,
598
        string $assignIp = ''
599
    ): void {
600
601
        $ip = $this->ip;
602
        $rdns = $this->rdns;
603
        $now = time();
604
        $logData = [];
605
    
606
        if ('' !== $assignIp) {
607
            $ip = $assignIp;
608
            $rdns = gethostbyaddr($ip);
609
        }
610
611
        if ($actionCode === self::ACTION_UNBAN) {
612
            $this->driver->delete($ip, 'rule');
613
        } else {
614
            $logData['log_ip']     = $ip;
615
            $logData['ip_resolve'] = $rdns;
616
            $logData['time']       = $now;
617
            $logData['type']       = $actionCode;
618
            $logData['reason']     = $reasonCode;
619
            $logData['attempts']   = 0;
620
621
            $this->driver->save($ip, $logData, 'rule');
622
        }
623
624
        // Remove logs for this IP address because It already has it's own rule on system.
625
        // No need to count for it anymore.
626
        $this->driver->delete($ip, 'filter');
627
628
        // Log this action.
629
        $this->log($actionCode, $ip);
630
    }
631
632
    /**
633
     * Log actions.
634
     *
635
     * @param int    $actionCode The code number of the action.
636
     * @param string $ip         The IP address.
637
     *
638
     * @return void
639
     */
640
    protected function log(int $actionCode, $ip = ''): void
641
    {
642
        if (!$this->logger) {
643
            return;
644
        }
645
646
        $logData = [];
647
 
648
        $logData['ip'] = $ip ?: $this->getIp();
649
        $logData['session_id'] = get_session()->get('id');
650
        $logData['action_code'] = $actionCode;
651
        $logData['timesamp'] = time();
652
653
        $this->logger->add($logData);
654
    }
655
656
    /**
657
     * Get a class name without namespace string.
658
     *
659
     * @param object $instance Class
660
     * 
661
     * @return string
662
     */
663
    protected function getClassName($instance): string
664
    {
665
        $class = get_class($instance);
666
        return substr($class, strrpos($class, '\\') + 1); 
667
    }
668
669
    /**
670
     * Save and return the result identifier.
671
     * This method is for passing value from traits.
672
     *
673
     * @param int $resultCode The result identifier.
674
     *
675
     * @return int
676
     */
677
    protected function setResultCode(int $resultCode): int
678
    {
679
        return $this->result = $resultCode;
680
    }
681
}
682