Passed
Push — 2.x ( f77509...6f7bcf )
by Terry
02:15
created

BaseController::fieldCsrf()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 3
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 5
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\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
    use ConfigMethodsTrait;
61
    use CsrfTrait;
62
    use DemoModeTrait;
63
    use FirewallTrait;
64
65
    /**
66
     * LogPaeser instance.
67
     *
68
     * @var object
69
     */
70
    protected $parser;
71
72
    /**
73
     * Messages.
74
     *
75
     * @var array
76
     */
77
    protected $messages = [];
78
79
    /**
80
     * Check page availability.
81
     *
82
     * @var array
83
     */
84
    protected $pageAvailability = [
85
86
        // Need to implement Action Logger to make it true.
87
        'logs' => false,
88
    ];
89
90
    /**
91
     * Language code.
92
     *
93
     * @var string
94
     */
95
    protected $locate = 'en';
96
97
    /**
98
     * Captcha modules.
99
     *
100
     * @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...
101
     */
102
    protected $captcha = [];
103
104
    /**
105
     * The base URL of the firewall panel.
106
     *
107
     * @var string
108
     */
109
    public $base = '';
110
111
    /**
112
     * Firewall panel base controller.                  
113
     */
114
    public function __construct() 
115
    {
116
        $firewall = Container::get('firewall');
117
118
        if (!($firewall instanceof Firewall)) {
119
            throw new RuntimeException(
120
                'The Firewall instance should be initialized first.'
121
            );
122
        }
123
124
        $this->mode          = 'managed';
125
        $this->kernel        = $firewall->getKernel();
126
        $this->configuration = $firewall->getConfiguration();
127
        $this->directory     = $firewall->getDirectory();
128
        $this->filename      = $firewall->getFilename();
129
        $this->base          = SHIELDON_PANEL_BASE;
130
131
        if (!empty($this->kernel->logger)) {
132
133
            // We need to know where the logs stored in.
134
            $logDirectory = $this->kernel->logger->getDirectory();
135
136
            // Load ActionLogParser for parsing log files.
137
            $this->parser = new ActionLogParser($logDirectory);
138
139
            $this->pageAvailability['logs'] = true;
140
        }
141
142
        $flashMessage = get_session()->get('flash_messages');
143
144
        // Flash message, use it when redirecting page.
145
        if (!empty($flashMessage)) {
146
            $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...
147
            get_session()->remove('flash_messages');
148
        }
149
150
        $this->locate = get_user_lang();
151
    }
152
153
    /**
154
     * Load view file.
155
     *
156
     * @param string $page The page type. (filename)
157
     * @param array  $data The variables passed to that page.
158
     *
159
     * @return string
160
     */
161
    protected function loadView(string $page, array $data = []): string
162
    {
163
        if (!defined('SHIELDON_VIEW')) {
164
            define('SHIELDON_VIEW', true);
165
        }
166
167
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
168
    
169
        if (!empty($data)) {
170
            extract($data);
171
        }
172
173
        $output = '';
174
    
175
        if (file_exists($viewFilePath)) {
176
            ob_start();
177
            include $viewFilePath;
178
            $output = ob_get_contents();
179
            ob_end_clean();
180
        }
181
182
        return $output;
183
    }
184
185
    /**
186
     * Render the web page with full layout.
187
     *
188
     * @param string $page The page type. (filename)
189
     * @param array  $data The variables passed to that page.
190
     *
191
     * @return ResponseInterface
192
     */
193
    protected function renderPage(string $page, array $data): ResponseInterface
194
    {
195
        $channelName = $this->kernel->driver->getChannel();
196
        $body = [];
197
198
        if (empty($channelName)) {
199
            $channelName = 'default';
200
        }
201
202
        $body['channel_name'] = $channelName;
203
        $body['mode_name'] = $this->mode;
204
        $body['page_url'] = $this->url();
205
        $body['content'] = $this->loadView($page, $data);
206
        $body['title'] = $data['title'] ?? '';
207
208
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
209
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
210
211
        $page = $this->loadView('panel/template', $body);
212
213
        return $this->respond($page);
214
    }
215
216
    /**
217
     * Return the response instance.
218
     *
219
     * @param string $body The content body.
220
     *
221
     * @return ResponseInterface
222
     */
223
    protected function respond(string $body): ResponseInterface
224
    {
225
        $response = get_response();
226
        $stream = $response->getBody();
227
        $stream->write($body);
228
        $stream->rewind();
229
230
        return $response->withBody($stream);
231
    }
232
233
    /**
234
     * Include a view file. This 
235
     * This method is used in a template loading other templates.
236
     *
237
     * @param string $page The page type. (filename)
238
     * @param array  $data The variables passed to that page.
239
     *
240
     * @return void
241
     */
242
    protected function loadViewPart(string $page, array $data = []): void
243
    {
244
        if (!defined('SHIELDON_VIEW')) {
245
            define('SHIELDON_VIEW', true);
246
        }
247
248
        foreach ($data as $k => $v) {
249
            ${$k} = $v;
250
        }
251
252
        include __DIR__ . '/../../../templates/' . $page . '.php';
253
    }
254
255
    /**
256
     * Response message to front.
257
     *
258
     * @param string $type The message status type. error|success
259
     * @param string $text The message body.
260
     *
261
     * @return void
262
     */
263
    protected function pushMessage(string $type, string $text): void
264
    {
265
        $class = $type;
266
267
        if ($type == 'error') {
268
            $class = 'danger';
269
        }
270
271
        array_push(
272
            $this->messages,
273
            [
274
                'type' => $type,
275
                'text' => $text,
276
                'class' => $class,
277
            ]
278
        );
279
    }
280
281
    /**
282
     * Return the relative URL.
283
     *
284
     * @param string $path The page's path.
285
     *
286
     * @return string
287
     */
288
    protected function url(string $path = ''): string
289
    {
290
        return '/' . trim($this->base, '/') . '/' . $path . '/';
291
    }
292
293
    /**
294
     * Save the configuration settings to the JSON file.
295
     *
296
     * @return void
297
     */
298
    protected function saveConfig(): void
299
    {
300
        if ($this->mode !== 'managed') {
301
            return;
302
        }
303
304
        $postParams = (array) get_request()->getParsedBody();
305
        $configFilePath = $this->directory . '/' . $this->filename;
306
307
        foreach ($this->csrfField as $csrfInfo) {
308
            if (!empty($csrfInfo['name'])) {
309
                unset_superglobal($csrfInfo['name'], 'post');
310
            }
311
        }
312
313
        $this->saveConfigPrepareSettings($postParams);
314
315
        //  Start checking the availibility of the data driver settings.
316
        $result = true;
317
        $result = $this->saveConfigCheckDataDriver($result);
318
        $result = $this->saveConfigCheckActionLogger($result);
319
        $result = $this->saveConfigCheckIptables($result);
320
321
        // Only update settings while data driver is correctly connected.
322
        if ($result) {
323
            file_put_contents($configFilePath, json_encode($this->configuration));
324
325
            $this->pushMessage(
326
                'success',
327
                __(
328
                    'panel',
329
                    'success_settings_saved',
330
                    'Settings saved.'
331
                )
332
            );
333
        }
334
    }
335
336
    /**
337
     * Echo the setting string to the template.
338
     *
339
     * @param string $field   Field.
340
     * @param mixed  $default Default value.
341
     *
342
     * @return void
343
     */
344
    protected function _(string $field, $default = ''): void
345
    {
346
        if ($this->mode === 'demo') {
347
348
            // Hide sensitive data because of security concerns.
349
            $hiddenForDemo = [
350
                'drivers.redis.auth',
351
                'drivers.file.directory_path',
352
                'drivers.sqlite.directory_path',
353
                'drivers.mysql.dbname',
354
                'drivers.mysql.user',
355
                'drivers.mysql.pass',
356
                'captcha_modules.recaptcha.config.site_key',
357
                'captcha_modules.recaptcha.config.secret_key',
358
                'loggers.action.config.directory_path',
359
                'admin.user',
360
                'admin.pass',
361
                'admin.last_modified',
362
                'messengers.telegram.config.api_key',
363
                'messengers.telegram.config.channel',
364
                'messengers.sendgrid.config.api_key',
365
                'messengers.sendgrid.config.sender',
366
                'messengers.sendgrid.config.recipients',
367
                'messengers.line_notify.config.access_token',
368
                'iptables.config.watching_folder',
369
                'ip6tables.config.watching_folder',
370
                'messengers.sendgrid.config.recipients', // array
371
            ];
372
373
            if (in_array($field, $hiddenForDemo)) {
374
                echo __('panel', 'field_not_visible', 'Cannot view this field in demo mode.');
375
                return;
376
            }
377
        }
378
379
        $fieldtype = gettype($this->getConfig($field));
380
381
        if (in_array($fieldtype, ['integer', 'string', 'double'])) {
382
            echo (!empty($this->getConfig($field))) ? $this->getConfig($field) : $default;
383
            return;
384
385
        } elseif (in_array($fieldtype, ['array'])) {
386
            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

386
            echo implode("\n", /** @scrutinizer ignore-type */ $this->getConfig($field));
Loading history...
387
            return;
388
        }
389
390
        echo '';
391
    }
392
393
    /**
394
     * Use on HTML checkbox and radio elements.
395
     *
396
     * @param string $value        The variable or configuation field.
397
     * @param mixed  $valueChecked The value.
398
     * @param bool   $isConfig     Is it a configuration field or not.
399
     *
400
     * @return void
401
     */
402
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
403
    {
404
        if ($isConfig) {
405
            if ($this->getConfig($value) === $valueChecked) {
406
                echo 'checked';
407
                return;
408
            }
409
        } else {
410
            if ($value === $valueChecked) {
411
                echo 'checked';
412
                return;
413
            }
414
        }
415
416
        echo '';
417
    }
418
419
    /**
420
     * Echo correspondence string on Messenger setting page.
421
     *
422
     * @param string $moduleName The messenger module's name.
423
     * @param string $echoType   Value: css | icon
424
     *
425
     * @return void
426
     */
427
    protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void
428
    {
429
        $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...
430
            'success' :
431
            '';
432
        
433
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ?
434
            '<i class="fas fa-check"></i>' :
435
            '<i class="fas fa-exclamation"></i>';
436
437
        echo $echo[$echoType];
438
    }
439
440
    /**
441
     * Use on HTML select elemets.
442
     *
443
     * @param string $value        The value.
444
     * @param mixed  $valueChecked The value to confirm.
445
     *
446
     * @return void
447
     */
448
    protected function selected(string $value, $valueChecked): void
449
    {
450
        if ($this->getConfig($value) === $valueChecked) {
451
            echo 'selected';
452
        } else {
453
            echo '';
454
        }
455
    }
456
}
457