Passed
Push — 2.x ( 5178d8...5d0da9 )
by Terry
02:05
created

BaseController::messengerAjaxStatus()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 11
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
 * 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\Utils\Container;
31
use Shieldon\Firewall\Log\ActionLogParser;
32
use function Shieldon\Firewall\__;
33
use function Shieldon\Firewall\get_request;
34
use function Shieldon\Firewall\get_response;
35
use function Shieldon\Firewall\get_session;
36
use function Shieldon\Firewall\unset_superglobal;
37
use function Shieldon\Firewall\get_user_lang;
38
39
use RuntimeException;
40
use function array_push;
41
use function define;
42
use function defined;
43
use function extract;
44
use function file_exists;
45
use function file_put_contents;
46
use function in_array;
47
use function is_array;
48
use function json_encode;
49
use function ob_end_clean;
50
use function ob_get_contents;
51
use function ob_start;
52
use function trim;
53
54
/**
55
 * Base controller.
56
 */
57
class BaseController
58
{
59
    use FirewallTrait;
60
    use DemoModeTrait;
61
    use ConfigMethodsTrait;
62
63
    /**
64
     * LogPaeser instance.
65
     *
66
     * @var object
67
     */
68
    protected $parser;
69
70
    /**
71
     * Messages.
72
     *
73
     * @var array
74
     */
75
    protected $messages = [];
76
77
    /**
78
     * Check page availability.
79
     *
80
     * @var array
81
     */
82
    protected $pageAvailability = [
83
84
        // Need to implement Action Logger to make it true.
85
        'logs' => false,
86
    ];
87
88
    /**
89
     * See $this->csrf()
90
     *
91
     * @var array
92
     */
93
    protected $csrfField = [];
94
95
    /**
96
     * Language code.
97
     *
98
     * @var string
99
     */
100
    protected $locate = 'en';
101
102
    /**
103
     * Captcha modules.
104
     *
105
     * @var Interface
0 ignored issues
show
Bug introduced by
The type Shieldon\Firewall\Panel\Interface 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...
106
     */
107
    protected $captcha = [];
108
109
    /**
110
     * The base URL of the firewall panel.
111
     *
112
     * @var string
113
     */
114
    public $base = '';
115
116
    /**
117
     * Firewall panel base controller.                  
118
     */
119
    public function __construct() 
120
    {
121
        $firewall = Container::get('firewall');
122
123
        if (!($firewall instanceof Firewall)) {
124
            throw new RuntimeException(
125
                'The Firewall instance should be initialized first.'
126
            );
127
        }
128
129
        $this->mode          = 'managed';
130
        $this->kernel        = $firewall->getKernel();
131
        $this->configuration = $firewall->getConfiguration();
132
        $this->directory     = $firewall->getDirectory();
133
        $this->filename      = $firewall->getFilename();
134
        $this->base          = SHIELDON_PANEL_BASE;
135
136
        if (!empty($this->kernel->logger)) {
137
138
            // We need to know where the logs stored in.
139
            $logDirectory = $this->kernel->logger->getDirectory();
140
141
            // Load ActionLogParser for parsing log files.
142
            $this->parser = new ActionLogParser($logDirectory);
143
144
            $this->pageAvailability['logs'] = true;
145
        }
146
147
        $flashMessage = get_session()->get('flash_messages');
148
149
        // Flash message, use it when redirecting page.
150
        if (!empty($flashMessage)) {
151
            $this->messages = $flashMessage;
0 ignored issues
show
Documentation Bug introduced by
It seems like $flashMessage can also be of type string. However, the property $messages is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
152
            get_session()->remove('flash_messages');
153
        }
154
155
        $this->locate = get_user_lang();
156
    }
157
158
    /**
159
     * Load view file.
160
     *
161
     * @param string $page The page type. (filename)
162
     * @param array  $data The variables passed to that page.
163
     *
164
     * @return string
165
     */
166
    protected function loadView(string $page, array $data = []): string
167
    {
168
        if (!defined('SHIELDON_VIEW')) {
169
            define('SHIELDON_VIEW', true);
170
        }
171
172
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
173
    
174
        if (!empty($data)) {
175
            extract($data);
176
        }
177
178
        $output = '';
179
    
180
        if (file_exists($viewFilePath)) {
181
            ob_start();
182
            include $viewFilePath;
183
            $output = ob_get_contents();
184
            ob_end_clean();
185
        }
186
187
        return $output;
188
    }
189
190
    /**
191
     * Render the web page with full layout.
192
     *
193
     * @param string $page The page type. (filename)
194
     * @param array  $data The variables passed to that page.
195
     *
196
     * @return ResponseInterface
197
     */
198
    protected function renderPage(string $page, array $data): ResponseInterface
199
    {
200
        $channelName = $this->kernel->driver->getChannel();
201
        $body = [];
202
203
        if (empty($channelName)) {
204
            $channelName = 'default';
205
        }
206
207
        $body['channel_name'] = $channelName;
208
        $body['mode_name'] = $this->mode;
209
        $body['page_url'] = $this->url();
210
        $body['content'] = $this->loadView($page, $data);
211
        $body['title'] = $data['title'] ?? '';
212
213
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
214
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
215
216
        $page = $this->loadView('panel/template', $body);
217
218
        return $this->respond($page);
219
    }
220
221
    /**
222
     * Return the response instance.
223
     *
224
     * @param string $body The content body.
225
     *
226
     * @return ResponseInterface
227
     */
228
    protected function respond(string $body): ResponseInterface
229
    {
230
        $response = get_response();
231
        $stream = $response->getBody();
232
        $stream->write($body);
233
        $stream->rewind();
234
235
        return $response->withBody($stream);
236
    }
237
238
    /**
239
     * Include a view file. This 
240
     * This method is used in a template loading other templates.
241
     *
242
     * @param string $page The page type. (filename)
243
     * @param array  $data The variables passed to that page.
244
     *
245
     * @return void
246
     */
247
    protected function loadViewPart(string $page, array $data = []): void
248
    {
249
        if (!defined('SHIELDON_VIEW')) {
250
            define('SHIELDON_VIEW', true);
251
        }
252
253
        foreach ($data as $k => $v) {
254
            ${$k} = $v;
255
        }
256
257
        include __DIR__ . '/../../../templates/' . $page . '.php';
258
    }
259
260
    /**
261
     * Response message to front.
262
     *
263
     * @param string $type The message status type. error|success
264
     * @param string $text The message body.
265
     *
266
     * @return void
267
     */
268
    protected function pushMessage(string $type, string $text): void
269
    {
270
        $class = $type;
271
272
        if ($type == 'error') {
273
            $class = 'danger';
274
        }
275
276
        array_push(
277
            $this->messages,
278
            [
279
                'type' => $type,
280
                'text' => $text,
281
                'class' => $class,
282
            ]
283
        );
284
    }
285
286
    /**
287
     * Return the relative URL.
288
     *
289
     * @param string $path The page's path.
290
     *
291
     * @return string
292
     */
293
    protected function url(string $path = ''): string
294
    {
295
        return '/' . trim($this->base, '/') . '/' . $path . '/';
296
    }
297
298
    /**
299
     * Output HTML input element with CSRF token.
300
     *
301
     * @return void
302
     */
303
    public function fieldCsrf(): void
304
    {
305
        if (!empty($this->csrfField)) {
306
            foreach ($this->csrfField as $value) {
307
                echo '<input type="hidden" name="' . $value['name'] . '" value="' . $value['value'] . '" id="csrf-field">';
308
            }
309
        }
310
    }
311
312
    /**
313
     * Save the configuration settings to the JSON file.
314
     *
315
     * @return void
316
     */
317
    protected function saveConfig(): void
318
    {
319
        if ($this->mode !== 'managed') {
320
            return;
321
322
        }
323
        $postParams = get_request()->getParsedBody();
324
325
        if (!is_array($postParams)) {
326
            return;
327
        }
328
329
        $configFilePath = $this->directory . '/' . $this->filename;
330
331
        foreach ($this->csrfField as $csrfInfo) {
332
            if (!empty($csrfInfo['name'])) {
333
                unset_superglobal($csrfInfo['name'], 'post');
334
            }
335
        }
336
337
        $this->saveConfigPrepareSettings($postParams);
338
339
        //  Start checking the availibility of the data driver settings.
340
        $result = true;
341
        $result = $this->saveConfigCheckDataDriver($result);
342
        $result = $this->saveConfigCheckActionLogger($result);
343
        $result = $this->saveConfigCheckIptables($result);
344
345
        // Only update settings while data driver is correctly connected.
346
        if ($result) {
347
            file_put_contents($configFilePath, json_encode($this->configuration));
348
349
            $this->pushMessage(
350
                'success',
351
                __(
352
                    'panel',
353
                    'success_settings_saved',
354
                    'Settings saved.'
355
                )
356
            );
357
        }
358
    }
359
360
    /**
361
     * Echo the setting string to the template.
362
     *
363
     * @param string $field   Field.
364
     * @param mixed  $default Default value.
365
     *
366
     * @return void
367
     */
368
    protected function _(string $field, $default = ''): void
369
    {
370
        if ($this->mode === 'demo') {
371
372
            // Hide sensitive data because of security concerns.
373
            $hiddenForDemo = [
374
                'drivers.redis.auth',
375
                'drivers.file.directory_path',
376
                'drivers.sqlite.directory_path',
377
                'drivers.mysql.dbname',
378
                'drivers.mysql.user',
379
                'drivers.mysql.pass',
380
                'captcha_modules.recaptcha.config.site_key',
381
                'captcha_modules.recaptcha.config.secret_key',
382
                'loggers.action.config.directory_path',
383
                'admin.user',
384
                'admin.pass',
385
                'admin.last_modified',
386
                'messengers.telegram.config.api_key',
387
                'messengers.telegram.config.channel',
388
                'messengers.sendgrid.config.api_key',
389
                'messengers.sendgrid.config.sender',
390
                'messengers.sendgrid.config.recipients',
391
                'messengers.line_notify.config.access_token',
392
                'iptables.config.watching_folder',
393
                'ip6tables.config.watching_folder',
394
                'messengers.sendgrid.config.recipients', // array
395
            ];
396
397
            if (in_array($field, $hiddenForDemo)) {
398
                echo __('panel', 'field_not_visible', 'Cannot view this field in demo mode.');
399
                return;
400
            }
401
        }
402
403
        $fieldtype = gettype($this->getConfig($field));
404
405
        if (in_array($fieldtype, ['integer', 'string', 'double'])) {
406
            echo (!empty($this->getConfig($field))) ? $this->getConfig($field) : $default;
407
            return;
408
409
        } elseif (in_array($fieldtype, ['array'])) {
410
            echo implode("\n", $this->getConfig($field));
0 ignored issues
show
Bug introduced by
It seems like $this->getConfig($field) can also be of type string; however, parameter $pieces of implode() 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

410
            echo implode("\n", /** @scrutinizer ignore-type */ $this->getConfig($field));
Loading history...
411
            return;
412
        }
413
414
        echo '';
415
    }
416
417
    /**
418
     * Use on HTML checkbox and radio elements.
419
     *
420
     * @param string $value        The variable or configuation field.
421
     * @param mixed  $valueChecked The value.
422
     * @param bool   $isConfig     Is it a configuration field or not.
423
     *
424
     * @return void
425
     */
426
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
427
    {
428
        if ($isConfig) {
429
            if ($this->getConfig($value) === $valueChecked) {
430
                echo 'checked';
431
                return;
432
            }
433
        } else {
434
            if ($value === $valueChecked) {
435
                echo 'checked';
436
                return;
437
            }
438
        }
439
440
        echo '';
441
    }
442
443
    /**
444
     * Echo correspondence string on Messenger setting page.
445
     *
446
     * @param string $moduleName The messenger module's name.
447
     * @param string $echoType   Value: css | icon
448
     *
449
     * @return void
450
     */
451
    protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void
452
    {
453
        $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? 
0 ignored issues
show
Comprehensibility Best Practice introduced by
$echo was never initialized. Although not strictly required by PHP, it is generally a good practice to add $echo = array(); before regardless.
Loading history...
454
            'success' :
455
            '';
456
        
457
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ?
458
            '<i class="fas fa-check"></i>' :
459
            '<i class="fas fa-exclamation"></i>';
460
461
        echo $echo[$echoType];
462
    }
463
464
    /**
465
     * Use on HTML select elemets.
466
     *
467
     * @param string $value        The value.
468
     * @param mixed  $valueChecked The value to confirm.
469
     *
470
     * @return void
471
     */
472
    protected function selected(string $value, $valueChecked): void
473
    {
474
        if ($this->getConfig($value) === $valueChecked) {
475
            echo 'selected';
476
        } else {
477
            echo '';
478
        }
479
    }
480
}
481