Passed
Push — 2.x ( 1cf170...c97871 )
by Terry
01:57
created

BaseController   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 447
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 151
c 5
b 0
f 0
dl 0
loc 447
rs 9.36
wmc 38

12 Methods

Rating   Name   Duplication   Size   Complexity  
A respond() 0 8 1
A renderPage() 0 26 2
A __construct() 0 37 5
A url() 0 3 1
A saveConfig() 0 36 5
A loadView() 0 22 4
A loadViewPart() 0 11 3
A pushMessage() 0 14 2
A checked() 0 15 4
A checkPostParamsExist() 0 11 3
A messengerAjaxStatus() 0 13 3
A _() 0 43 5
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\Panel;
24
25
use Psr\Http\Message\ResponseInterface;
26
use Shieldon\Firewall\Firewall;
27
use Shieldon\Firewall\FirewallTrait;
28
use Shieldon\Firewall\Panel\DemoModeTrait;
29
use Shieldon\Firewall\Panel\ConfigMethodsTrait;
30
use Shieldon\Firewall\Panel\CsrfTrait;
31
use Shieldon\Firewall\Utils\Container;
32
use Shieldon\Firewall\Log\ActionLogParser;
33
use function Shieldon\Firewall\__;
34
use function Shieldon\Firewall\get_request;
35
use function Shieldon\Firewall\get_response;
36
use function Shieldon\Firewall\get_session;
37
use function Shieldon\Firewall\unset_superglobal;
38
use function Shieldon\Firewall\get_user_lang;
39
40
use RuntimeException;
41
use function array_push;
42
use function define;
43
use function defined;
44
use function extract;
45
use function file_exists;
46
use function file_put_contents;
47
use function in_array;
48
use function is_array;
49
use function json_encode;
50
use function ob_end_clean;
51
use function ob_get_contents;
52
use function ob_start;
53
use function trim;
54
55
/**
56
 * Base controller.
57
 */
58
class BaseController
59
{
60
    /**
61
     *   Public methods       | Desctiotion
62
     *  ----------------------|---------------------------------------------
63
     *                        | No public methods.
64
     *  ----------------------|---------------------------------------------
65
     */
66
67
    /**
68
     *   Public methods       | Desctiotion
69
     *  ----------------------|---------------------------------------------
70
     *                        | No public methods.
71
     *  ----------------------|---------------------------------------------
72
     */
73
    use ConfigMethodsTrait;
74
75
    /**
76
     *   Public methods       | Desctiotion
77
     *  ----------------------|---------------------------------------------
78
     *   csrf                 | Receive the CSRF name and token from the App.
79
     *   setCsrfField         | Set CSRF input fields.
80
     *   fieldCsrf            | Output HTML input element with CSRF token.
81
     *  ----------------------|---------------------------------------------
82
     */
83
    use CsrfTrait;
84
85
    /**
86
     *   Public methods       | Desctiotion
87
     *  ----------------------|---------------------------------------------
88
     *   demo                 | Start a demo mode. Setting fields are hidden.
89
     *  ----------------------|---------------------------------------------
90
     */
91
    use DemoModeTrait;
92
93
    /**
94
     *   Public methods       | Desctiotion
95
     *  ----------------------|---------------------------------------------
96
     *   getKernel            | Get the Shieldon Kernel instance.
97
     *   getConfiguration     | Get the configuration data.
98
     *   getDirectory         | Get the dictionary where the data is stored.
99
     *   getFileName          | Get the path of the configuration file.
100
     *   getConfig            | Get the value by identification string.
101
     *   setConfig            | Set the value by identification string.
102
     *  ----------------------|---------------------------------------------
103
     */
104
    use FirewallTrait;
105
106
    /**
107
     * LogPaeser instance.
108
     *
109
     * @var object
110
     */
111
    protected $parser;
112
113
    /**
114
     * Messages.
115
     *
116
     * @var array
117
     */
118
    protected $messages = [];
119
120
    /**
121
     * Check page availability.
122
     *
123
     * @var array
124
     */
125
    protected $pageAvailability = [
126
127
        // Need to implement Action Logger to make it true.
128
        'logs' => false,
129
    ];
130
131
    /**
132
     * Language code.
133
     *
134
     * @var string
135
     */
136
    protected $locate = 'en';
137
138
    /**
139
     * Captcha modules.
140
     *
141
     * @var array
142
     */
143
    protected $captcha = [];
144
145
    /**
146
     * The base URL of the firewall panel.
147
     *
148
     * @var string
149
     */
150
    public $base = '';
151
152
    /**
153
     * Firewall panel base controller.                  
154
     */
155
    public function __construct() 
156
    {
157
        $firewall = Container::get('firewall');
158
159
        if (!($firewall instanceof Firewall)) {
160
            throw new RuntimeException(
161
                'The Firewall instance should be initialized first.'
162
            );
163
        }
164
165
        $this->mode          = 'managed';
166
        $this->kernel        = $firewall->getKernel();
167
        $this->configuration = $firewall->getConfiguration();
168
        $this->directory     = $firewall->getDirectory();
169
        $this->filename      = $firewall->getFilename();
170
        $this->base          = SHIELDON_PANEL_BASE;
171
172
        if (!empty($this->kernel->logger)) {
173
174
            // We need to know where the logs stored in.
175
            $logDirectory = $this->kernel->logger->getDirectory();
176
177
            // Load ActionLogParser for parsing log files.
178
            $this->parser = new ActionLogParser($logDirectory);
179
180
            $this->pageAvailability['logs'] = true;
181
        }
182
183
        $flashMessage = get_session()->get('flash_messages');
184
185
        // Flash message, use it when redirecting page.
186
        if (!empty($flashMessage) && is_array($flashMessage)) {
187
            $this->messages = $flashMessage;
188
            get_session()->remove('flash_messages');
189
        }
190
191
        $this->locate = get_user_lang();
192
    }
193
194
    /**
195
     * Load view file.
196
     *
197
     * @param string $page The page type. (filename)
198
     * @param array  $data The variables passed to that page.
199
     *
200
     * @return string
201
     */
202
    protected function loadView(string $page, array $data = []): string
203
    {
204
        if (!defined('SHIELDON_VIEW')) {
205
            define('SHIELDON_VIEW', true);
206
        }
207
208
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
209
    
210
        if (!empty($data)) {
211
            extract($data);
212
        }
213
214
        $output = '';
215
    
216
        if (file_exists($viewFilePath)) {
217
            ob_start();
218
            include $viewFilePath;
219
            $output = ob_get_contents();
220
            ob_end_clean();
221
        }
222
223
        return $output;
224
    }
225
226
    /**
227
     * Render the web page with full layout.
228
     *
229
     * @param string $page The page type. (filename)
230
     * @param array  $data The variables passed to that page.
231
     *
232
     * @return ResponseInterface
233
     */
234
    protected function renderPage(string $page, array $data): ResponseInterface
235
    {
236
        $channelName = $this->kernel->driver->getChannel();
237
        $body = [];
238
239
        if (empty($channelName)) {
240
            $channelName = 'default';
241
        }
242
243
        $body['title'] = $data['title'] ?? '';
244
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
245
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
246
247
        $body['channel_name'] = $channelName;
248
        $body['mode_name'] = $this->mode;
249
        $body['page_url'] = $this->url();
250
        $body['content'] = $this->loadView($page, $data);
251
252
        $body['js_url'] = $this->url('asset/js');
253
        $body['css_url'] = $this->url('asset/css');
254
        $body['favicon_url'] = $this->url('asset/favicon');
255
        $body['logo_url'] = $this->url('asset/logo');
256
257
        $page = $this->loadView('panel/template', $body);
258
259
        return $this->respond($page);
260
    }
261
262
    /**
263
     * Return the response instance.
264
     *
265
     * @param string $body The content body.
266
     *
267
     * @return ResponseInterface
268
     */
269
    protected function respond(string $body): ResponseInterface
270
    {
271
        $response = get_response();
272
        $stream = $response->getBody();
273
        $stream->write($body);
274
        $stream->rewind();
275
276
        return $response->withBody($stream);
277
    }
278
279
    /**
280
     * Include a view file. This 
281
     * This method is used in a template loading other templates.
282
     *
283
     * @param string $page The page type. (filename)
284
     * @param array  $data The variables passed to that page.
285
     *
286
     * @return void
287
     */
288
    protected function loadViewPart(string $page, array $data = []): void
289
    {
290
        if (!defined('SHIELDON_VIEW')) {
291
            define('SHIELDON_VIEW', true);
292
        }
293
294
        foreach ($data as $k => $v) {
295
            ${$k} = $v;
296
        }
297
298
        include __DIR__ . '/../../../templates/' . $page . '.php';
299
    }
300
301
    /**
302
     * Response message to front.
303
     *
304
     * @param string $type The message status type. error|success
305
     * @param string $text The message body.
306
     *
307
     * @return void
308
     */
309
    protected function pushMessage(string $type, string $text): void
310
    {
311
        $class = $type;
312
313
        if ($type == 'error') {
314
            $class = 'danger';
315
        }
316
317
        array_push(
318
            $this->messages,
319
            [
320
                'type' => $type,
321
                'text' => $text,
322
                'class' => $class,
323
            ]
324
        );
325
    }
326
327
    /**
328
     * Return the relative URL.
329
     *
330
     * @param string $path The page's path.
331
     *
332
     * @return string
333
     */
334
    protected function url(string $path = ''): string
335
    {
336
        return '/' . trim($this->base, '/') . '/' . $path . '/';
337
    }
338
339
    /**
340
     * Save the configuration settings to the JSON file.
341
     *
342
     * @return void
343
     */
344
    protected function saveConfig(): void
345
    {
346
        if ($this->mode !== 'managed') {
347
            return;
348
        }
349
350
        $postParams = (array) get_request()->getParsedBody();
351
352
        $configFilePath = $this->directory . '/' . $this->filename;
353
354
        foreach ($this->csrfField as $csrfInfo) {
355
            // @codeCoverageIgnoreStart
356
            if (!empty($csrfInfo['name'])) {
357
                unset_superglobal($csrfInfo['name'], 'post');
358
            }
359
            // @codeCoverageIgnoreEnd
360
        }
361
362
        $this->saveConfigPrepareSettings($postParams);
363
364
        //  Start checking the availibility of the data driver settings.
365
        $result = true;
366
        $result = $this->saveConfigCheckDataDriver($result);
367
        $result = $this->saveConfigCheckActionLogger($result);
368
        $result = $this->saveConfigCheckIptables($result);
369
370
        // Only update settings while data driver is correctly connected.
371
        if ($result) {
372
            file_put_contents($configFilePath, json_encode($this->configuration));
373
374
            $this->pushMessage(
375
                'success',
376
                __(
377
                    'panel',
378
                    'success_settings_saved',
379
                    'Settings saved.'
380
                )
381
            );
382
        }
383
    }
384
385
    /**
386
     * Echo the setting string to the template.
387
     *
388
     * @param string $field   Field.
389
     * @param mixed  $default Default value.
390
     *
391
     * @return void
392
     */
393
    protected function _(string $field, $default = ''): void
394
    {
395
        if ($this->mode === 'demo') {
396
397
            // Hide sensitive data because of security concerns.
398
            $hiddenForDemo = [
399
                'drivers.redis.auth',
400
                'drivers.file.directory_path',
401
                'drivers.sqlite.directory_path',
402
                'drivers.mysql.dbname',
403
                'drivers.mysql.user',
404
                'drivers.mysql.pass',
405
                'captcha_modules.recaptcha.config.site_key',
406
                'captcha_modules.recaptcha.config.secret_key',
407
                'loggers.action.config.directory_path',
408
                'admin.user',
409
                'admin.pass',
410
                'admin.last_modified',
411
                'messengers.telegram.config.api_key',
412
                'messengers.telegram.config.channel',
413
                'messengers.sendgrid.config.api_key',
414
                'messengers.sendgrid.config.sender',
415
                'messengers.sendgrid.config.recipients',
416
                'messengers.line_notify.config.access_token',
417
                'iptables.config.watching_folder',
418
                'ip6tables.config.watching_folder',
419
                'messengers.sendgrid.config.recipients', // array
420
            ];
421
422
            if (in_array($field, $hiddenForDemo)) {
423
                echo __('panel', 'field_not_visible', 'Cannot view this field in demonstration mode.');
424
                return;
425
            }
426
        }
427
428
        $fieldtype = gettype($this->getConfig($field));
429
430
        if ($fieldtype === 'array') {
431
            echo implode("\n", $this->getConfig($field));
432
            return;
433
        }
434
435
        echo $this->getConfig($field) ?: $default;
436
    }
437
438
    /**
439
     * Use on HTML checkbox and radio elements.
440
     *
441
     * @param string $value        The variable or configuation field.
442
     * @param mixed  $valueChecked The value.
443
     * @param bool   $isConfig     Is it a configuration field or not.
444
     *
445
     * @return void
446
     */
447
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
448
    {
449
        if ($isConfig) {
450
            if ($this->getConfig($value) === $valueChecked) {
451
                echo 'checked';
452
                return;
453
            }
454
        } else {
455
            if ($value === $valueChecked) {
456
                echo 'checked';
457
                return;
458
            }
459
        }
460
461
        echo '';
462
    }
463
464
    /**
465
     * Echo correspondence string on Messenger setting page.
466
     *
467
     * @param string $moduleName The messenger module's name.
468
     * @param string $echoType   Value: css | icon
469
     *
470
     * @return void
471
     */
472
    protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void
473
    {
474
        $echo = [];
475
476
        $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? 
477
            'success' :
478
            '';
479
        
480
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ?
481
            '<i class="fas fa-check"></i>' :
482
            '<i class="fas fa-exclamation"></i>';
483
484
        echo $echo[$echoType];
485
    }
486
487
    /**
488
     * Check the required fields.
489
     *
490
     * @param array $fields The fields from POST form.
491
     *
492
     * @return bool
493
     */
494
    protected function checkPostParamsExist(...$fields): bool
495
    {
496
        $postParams = (array) get_request()->getParsedBody();
497
498
        foreach ($fields as $field) {
499
            if (empty($postParams[$field])) {
500
                return false;
501
            }
502
        }
503
504
        return true;
505
    }
506
}
507