1
<?php
2
/**
3
 * This file is part of the Shieldon package.
4
 *
5
 * (c) Terry L. <contact@terryl.in>
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 <contact@terryl.in>
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\Container;
32
use Shieldon\Firewall\Log\ActionLogParser;
33
use RuntimeException;
34
use function Shieldon\Firewall\__;
35
use function Shieldon\Firewall\get_request;
36
use function Shieldon\Firewall\get_response;
37
use function Shieldon\Firewall\get_session_instance;
38
use function Shieldon\Firewall\unset_superglobal;
39
use function Shieldon\Firewall\get_user_lang;
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
use const JSON_PRETTY_PRINT;
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 3
    public function __construct() 
156
    {
157 3
        $firewall = Container::get('firewall');
158

159 3
        if (!($firewall instanceof Firewall)) {
160 3
            throw new RuntimeException(
161 3
                'The Firewall instance should be initialized first.'
162
            );
163
        }
164

165 3
        $this->mode          = 'managed';
166 3
        $this->kernel        = $firewall->getKernel();
167 3
        $this->configuration = $firewall->getConfiguration();
168 3
        $this->directory     = $firewall->getDirectory();
169 3
        $this->filename      = $firewall->getFilename();
170 3
        $this->base          = SHIELDON_PANEL_BASE;
171

172 3
        if (!empty($this->kernel->logger)) {
173

174
            // We need to know where the logs stored in.
175 3
            $logDirectory = $this->kernel->logger->getDirectory();
176

177
            // Load ActionLogParser for parsing log files.
178 3
            $this->parser = new ActionLogParser($logDirectory);
179

180 3
            $this->pageAvailability['logs'] = true;
181
        }
182

183 3
        $flashMessage = get_session_instance()->get('flash_messages');
184

185
        // Flash message, use it when redirecting page.
186 3
        if (!empty($flashMessage) && is_array($flashMessage)) {
187 3
            $this->messages = $flashMessage;
188 3
            get_session_instance()->remove('flash_messages');
189
        }
190

191 3
        $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 3
    protected function loadView(string $page, array $data = []): string
203
    {
204 3
        if (!defined('SHIELDON_VIEW')) {
205 3
            define('SHIELDON_VIEW', true);
206
        }
207

208 3
        $viewFilePath =  __DIR__ . '/../../../templates/' . $page . '.php';
209
    
210 3
        if (!empty($data)) {
211 3
            extract($data);
212
        }
213

214 3
        $output = '';
215
    
216 3
        if (file_exists($viewFilePath)) {
217 3
            ob_start();
218 3
            include $viewFilePath;
219 3
            $output = ob_get_contents();
220 3
            ob_end_clean();
221
        }
222

223 3
        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 3
    protected function renderPage(string $page, array $data): ResponseInterface
235
    {
236 3
        $channelName = $this->kernel->driver->getChannel();
237 3
        $body = [];
238

239 3
        if (empty($channelName)) {
240 3
            $channelName = 'default';
241
        }
242

243 3
        $body['title'] = $data['title'] ?? '';
244 3
        $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall');
245 3
        $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION;
246

247 3
        $body['channel_name'] = $channelName;
248 3
        $body['mode_name'] = $this->mode;
249 3
        $body['page_url'] = $this->url();
250 3
        $body['content'] = $this->loadView($page, $data);
251

252 3
        $body['js_url'] = $this->url('asset/js');
253 3
        $body['css_url'] = $this->url('asset/css');
254 3
        $body['favicon_url'] = $this->url('asset/favicon');
255 3
        $body['logo_url'] = $this->url('asset/logo');
256

257 3
        $page = $this->loadView('panel/template', $body);
258

259 3
        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 3
    protected function respond(string $body): ResponseInterface
270
    {
271 3
        $response = get_response();
272 3
        $stream = $response->getBody();
273 3
        $stream->write($body);
274 3
        $stream->rewind();
275

276 3
        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 3
    protected function loadViewPart(string $page, array $data = []): void
289
    {
290 3
        if (!defined('SHIELDON_VIEW')) {
291 3
            define('SHIELDON_VIEW', true);
292
        }
293

294 3
        foreach ($data as $k => $v) {
295 3
            ${$k} = $v;
296
        }
297

298 3
        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 3
    protected function pushMessage(string $type, string $text): void
310
    {
311 3
        $class = $type;
312

313 3
        if ($type == 'error') {
314 3
            $class = 'danger';
315
        }
316

317 3
        array_push(
318 3
            $this->messages,
319
            [
320 3
                'type' => $type,
321 3
                'text' => $text,
322 3
                '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 3
    protected function url(string $path = ''): string
335
    {
336 3
        return '/' . trim($this->base, '/') . '/' . $path . '/';
337
    }
338

339
    /**
340
     * Save the configuration settings to the JSON file.
341
     *
342
     * @return void
343
     */
344 3
    protected function saveConfig(): void
345
    {
346 3
        if ($this->mode !== 'managed') {
347 3
            return;
348
        }
349

350 3
        $postParams = (array) get_request()->getParsedBody();
351

352 3
        $configFilePath = $this->directory . '/' . $this->filename;
353

354 3
        foreach ($this->csrfField as $csrfInfo) {
355
            // @codeCoverageIgnoreStart
356
            if (!empty($csrfInfo['name'])) {
357
                unset_superglobal($csrfInfo['name'], 'post');
358
            }
359
            // @codeCoverageIgnoreEnd
360
        }
361

362 3
        $this->saveConfigPrepareSettings($postParams);
363

364
        //  Start checking the availibility of the data driver settings.
365 3
        $result = true;
366 3
        $result = $this->saveConfigCheckDataDriver($result);
367 3
        $result = $this->saveConfigCheckActionLogger($result);
368 3
        $result = $this->saveConfigCheckIptables($result);
369

370
        // Only update settings while data driver is correctly connected.
371 3
        if ($result) {
372 3
            file_put_contents($configFilePath, json_encode($this->configuration, JSON_PRETTY_PRINT));
373

374 3
            $this->pushMessage(
375 3
                'success',
376 3
                __(
377 3
                    'panel',
378 3
                    'success_settings_saved',
379 3
                    '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 3
    protected function _(string $field, $default = ''): void
394
    {
395 3
        if ($this->mode === 'demo') {
396

397
            // Hide sensitive data because of security concerns.
398
            $hiddenForDemo = [
399 3
                '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 3
            if (in_array($field, $hiddenForDemo)) {
423 3
                echo __('panel', 'field_not_visible', 'Cannot view this field in demonstration mode.');
424 3
                return;
425
            }
426
        }
427

428 3
        $fieldtype = gettype($this->getConfig($field));
429

430 3
        if ($fieldtype === 'array') {
431 3
            echo implode("\n", $this->getConfig($field));
432 3
            return;
433
        }
434

435 3
        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 3
    protected function checked(string $value, $valueChecked, bool $isConfig = true): void
448
    {
449 3
        if ($isConfig) {
450 3
            if ($this->getConfig($value) === $valueChecked) {
451 3
                echo 'checked';
452 3
                return;
453
            }
454
        } else {
455 3
            if ($value === $valueChecked) {
456 3
                echo 'checked';
457 3
                return;
458
            }
459
        }
460

461 3
        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 3
    protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void
473
    {
474 3
        $echo = [];
475

476 3
        $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? 
477 3
            'success' :
478 3
            '';
479
        
480 3
        $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ?
481 3
            '<i class="fas fa-check"></i>' :
482 3
            '<i class="fas fa-exclamation"></i>';
483

484 3
        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 3
    protected function checkPostParamsExist(...$fields): bool
495
    {
496 3
        $postParams = (array) get_request()->getParsedBody();
497

498 3
        foreach ($fields as $field) {
499 3
            if (empty($postParams[$field])) {
500 3
                return false;
501
            }
502
        }
503

504 3
        return true;
505
    }
506
}

Read our documentation on viewing source code .

Loading