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\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 | 85 | public function __construct() |
|
156 | { |
||
157 | 85 | $firewall = Container::get('firewall'); |
|
158 | |||
159 | 85 | if (!($firewall instanceof Firewall)) { |
|
160 | 1 | throw new RuntimeException( |
|
161 | 1 | 'The Firewall instance should be initialized first.' |
|
162 | 1 | ); |
|
163 | } |
||
164 | |||
165 | 84 | $this->mode = 'managed'; |
|
166 | 84 | $this->kernel = $firewall->getKernel(); |
|
167 | 84 | $this->configuration = $firewall->getConfiguration(); |
|
168 | 84 | $this->directory = $firewall->getDirectory(); |
|
169 | 84 | $this->filename = $firewall->getFilename(); |
|
170 | 84 | $this->base = SHIELDON_PANEL_BASE; |
|
171 | |||
172 | 84 | if (!empty($this->kernel->logger)) { |
|
173 | // We need to know where the logs stored in. |
||
174 | $logDirectory = $this->kernel->logger->getDirectory(); |
||
175 | 67 | ||
176 | // Load ActionLogParser for parsing log files. |
||
177 | $this->parser = new ActionLogParser($logDirectory); |
||
178 | 67 | ||
179 | $this->pageAvailability['logs'] = true; |
||
180 | 67 | } |
|
181 | |||
182 | $flashMessage = get_session_instance()->get('flash_messages'); |
||
183 | 84 | ||
184 | // Flash message, use it when redirecting page. |
||
185 | if (!empty($flashMessage) && is_array($flashMessage)) { |
||
186 | 84 | $this->messages = $flashMessage; |
|
187 | 17 | get_session_instance()->remove('flash_messages'); |
|
188 | 17 | } |
|
189 | |||
190 | $this->locate = get_user_lang(); |
||
191 | 84 | } |
|
192 | |||
193 | /** |
||
194 | * Load view file. |
||
195 | * |
||
196 | * @param string $page The page type. (filename) |
||
197 | * @param array $data The variables passed to that page. |
||
198 | * |
||
199 | * @return string |
||
200 | */ |
||
201 | protected function loadView(string $page, array $data = []): string |
||
202 | 45 | { |
|
203 | if (!defined('SHIELDON_VIEW')) { |
||
204 | 45 | define('SHIELDON_VIEW', true); |
|
205 | 37 | } |
|
206 | |||
207 | $viewFilePath = __DIR__ . '/../../../templates/' . $page . '.php'; |
||
208 | 45 | ||
209 | if (!empty($data)) { |
||
210 | 45 | extract($data); |
|
211 | 45 | } |
|
212 | |||
213 | $output = ''; |
||
214 | 45 | ||
215 | if (file_exists($viewFilePath)) { |
||
216 | 45 | ob_start(); |
|
217 | 45 | include $viewFilePath; |
|
218 | 45 | $output = ob_get_contents(); |
|
219 | 45 | ob_end_clean(); |
|
220 | 45 | } |
|
221 | |||
222 | return $output; |
||
223 | 45 | } |
|
224 | |||
225 | /** |
||
226 | * Render the web page with full layout. |
||
227 | * |
||
228 | * @param string $page The page type. (filename) |
||
229 | * @param array $data The variables passed to that page. |
||
230 | * |
||
231 | * @return ResponseInterface |
||
232 | */ |
||
233 | protected function renderPage(string $page, array $data): ResponseInterface |
||
234 | 39 | { |
|
235 | $channelName = $this->kernel->driver->getChannel(); |
||
236 | 39 | $body = []; |
|
237 | 39 | ||
238 | if (empty($channelName)) { |
||
239 | 39 | $channelName = 'default'; |
|
240 | 39 | } |
|
241 | |||
242 | $body['title'] = $data['title'] ?? ''; |
||
243 | 39 | $body['title'] .= ' - ' . __('panel', 'title_site_wide', 'Shieldon Firewall'); |
|
244 | 39 | $body['title'] .= ' v' . SHIELDON_FIREWALL_VERSION; |
|
245 | 39 | ||
246 | $body['channel_name'] = $channelName; |
||
247 | 39 | $body['mode_name'] = $this->mode; |
|
248 | 39 | $body['page_url'] = $this->url(); |
|
249 | 39 | $body['content'] = $this->loadView($page, $data); |
|
250 | 39 | ||
251 | $body['js_url'] = $this->url('asset/js'); |
||
252 | 39 | $body['css_url'] = $this->url('asset/css'); |
|
253 | 39 | $body['favicon_url'] = $this->url('asset/favicon'); |
|
254 | 39 | $body['logo_url'] = $this->url('asset/logo'); |
|
255 | 39 | ||
256 | if ($this->mode === 'demo') { |
||
257 | 39 | $body['title'] .= ' (DEMO)'; |
|
258 | } |
||
259 | 39 | ||
260 | $page = $this->loadView('panel/template', $body); |
||
261 | |||
262 | return $this->respond($page); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Return the response instance. |
||
267 | * |
||
268 | * @param string $body The content body. |
||
269 | 45 | * |
|
270 | * @return ResponseInterface |
||
271 | 45 | */ |
|
272 | 45 | protected function respond(string $body): ResponseInterface |
|
273 | 45 | { |
|
274 | 45 | $response = get_response(); |
|
275 | $stream = $response->getBody(); |
||
276 | 45 | $stream->write($body); |
|
277 | $stream->rewind(); |
||
278 | |||
279 | return $response->withBody($stream); |
||
0 ignored issues
–
show
Bug
Best Practice
introduced
by
![]() |
|||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Include a view file. |
||
284 | * This method is used in a template loading other templates. |
||
285 | * |
||
286 | * @param string $page The page type. (filename) |
||
287 | * @param array $data The variables passed to that page. |
||
288 | 7 | * |
|
289 | * @return void |
||
290 | 7 | */ |
|
291 | 1 | protected function loadViewPart(string $page, array $data = []): void |
|
292 | { |
||
293 | if (!defined('SHIELDON_VIEW')) { |
||
294 | 7 | define('SHIELDON_VIEW', true); |
|
295 | 3 | } |
|
296 | |||
297 | foreach ($data as $k => $v) { |
||
298 | 7 | ${$k} = $v; |
|
299 | } |
||
300 | |||
301 | include __DIR__ . '/../../../templates/' . $page . '.php'; |
||
302 | } |
||
303 | |||
304 | /** |
||
305 | * Response message to front. |
||
306 | * |
||
307 | * @param string $type The message status type. error|success |
||
308 | * @param string $text The message body. |
||
309 | 32 | * |
|
310 | * @return void |
||
311 | 32 | */ |
|
312 | protected function pushMessage(string $type, string $text): void |
||
313 | 32 | { |
|
314 | 5 | $class = $type; |
|
315 | |||
316 | if ($type == 'error') { |
||
317 | 32 | $class = 'danger'; |
|
318 | 32 | } |
|
319 | 32 | ||
320 | 32 | array_push( |
|
321 | 32 | $this->messages, |
|
322 | 32 | [ |
|
323 | 32 | 'type' => $type, |
|
324 | 32 | 'text' => $text, |
|
325 | 'class' => $class, |
||
326 | ] |
||
327 | ); |
||
328 | } |
||
329 | |||
330 | /** |
||
331 | * Return the relative URL. |
||
332 | * |
||
333 | * @param string $path The page's path. |
||
334 | 45 | * |
|
335 | * @return string |
||
336 | 45 | */ |
|
337 | protected function url(string $path = ''): string |
||
338 | { |
||
339 | return '/' . trim($this->base, '/') . '/' . $path . '/'; |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Save the configuration settings to the JSON file. |
||
344 | 26 | * |
|
345 | * @return void |
||
346 | 26 | */ |
|
347 | 1 | protected function saveConfig(): void |
|
348 | { |
||
349 | if ($this->mode !== 'managed') { |
||
350 | 25 | return; |
|
351 | } |
||
352 | 25 | ||
353 | $postParams = (array) get_request()->getParsedBody(); |
||
354 | 25 | ||
355 | $configFilePath = $this->directory . '/' . $this->filename; |
||
356 | |||
357 | foreach ($this->csrfField as $csrfInfo) { |
||
358 | // @codeCoverageIgnoreStart |
||
359 | if (!empty($csrfInfo['name'])) { |
||
360 | unset_superglobal($csrfInfo['name'], 'post'); |
||
361 | } |
||
362 | 25 | // @codeCoverageIgnoreEnd |
|
363 | } |
||
364 | |||
365 | 25 | $this->saveConfigPrepareSettings($postParams); |
|
366 | 25 | ||
367 | 25 | // Start checking the availibility of the data driver settings. |
|
368 | 25 | $result = true; |
|
369 | $result = $this->saveConfigCheckDataDriver($result); |
||
370 | $result = $this->saveConfigCheckActionLogger($result); |
||
371 | 25 | $result = $this->saveConfigCheckIptables($result); |
|
372 | 25 | ||
373 | // Only update settings while data driver is correctly connected. |
||
374 | 25 | if ($result) { |
|
375 | 25 | file_put_contents($configFilePath, json_encode($this->configuration, JSON_PRETTY_PRINT)); |
|
376 | 25 | ||
377 | 25 | $this->pushMessage( |
|
378 | 25 | 'success', |
|
379 | 25 | __( |
|
380 | 25 | 'panel', |
|
381 | 25 | 'success_settings_saved', |
|
382 | 'Settings saved.' |
||
383 | ) |
||
384 | ); |
||
385 | } |
||
386 | } |
||
387 | |||
388 | /** |
||
389 | * Echo the setting string to the template. |
||
390 | * |
||
391 | * @param string $field Field. |
||
392 | * @param mixed $default Default value. |
||
393 | 7 | * |
|
394 | * @return void |
||
395 | 7 | */ |
|
396 | protected function _(string $field, $default = ''): void |
||
397 | { |
||
398 | 2 | if ($this->mode === 'demo') { |
|
399 | 2 | // Hide sensitive data because of security concerns. |
|
400 | 2 | $hiddenForDemo = [ |
|
401 | 2 | 'drivers.redis.auth', |
|
402 | 2 | 'drivers.file.directory_path', |
|
403 | 2 | 'drivers.sqlite.directory_path', |
|
404 | 2 | 'drivers.mysql.dbname', |
|
405 | 2 | 'drivers.mysql.user', |
|
406 | 2 | 'drivers.mysql.pass', |
|
407 | 2 | 'captcha_modules.recaptcha.config.site_key', |
|
408 | 2 | 'captcha_modules.recaptcha.config.secret_key', |
|
409 | 2 | 'loggers.action.config.directory_path', |
|
410 | 2 | 'admin.user', |
|
411 | 2 | 'admin.pass', |
|
412 | 2 | 'admin.last_modified', |
|
413 | 2 | 'messengers.telegram.config.api_key', |
|
414 | 2 | 'messengers.telegram.config.channel', |
|
415 | 2 | 'messengers.sendgrid.config.api_key', |
|
416 | 2 | 'messengers.sendgrid.config.sender', |
|
417 | 2 | 'messengers.sendgrid.config.recipients', |
|
418 | 2 | 'messengers.line_notify.config.access_token', |
|
419 | 2 | 'iptables.config.watching_folder', |
|
420 | 2 | 'ip6tables.config.watching_folder', |
|
421 | 'messengers.sendgrid.config.recipients', // array |
||
422 | 2 | ]; |
|
423 | 1 | ||
424 | 1 | if (in_array($field, $hiddenForDemo)) { |
|
425 | echo __('panel', 'field_not_visible', 'This field cannot be viewed in demonstration mode.'); |
||
426 | return; |
||
427 | } |
||
428 | 6 | } |
|
429 | |||
430 | 6 | $fieldtype = gettype($this->getConfig($field)); |
|
431 | 2 | ||
432 | 2 | if ($fieldtype === 'array') { |
|
433 | echo implode("\n", $this->getConfig($field)); |
||
434 | return; |
||
435 | 6 | } |
|
436 | |||
437 | echo $this->getConfig($field) ?: $default; |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * Use on HTML checkbox and radio elements. |
||
442 | * |
||
443 | * @param string $value The variable or configuation field. |
||
444 | * @param mixed $valueChecked The value. |
||
445 | * @param bool $isConfig Is it a configuration field or not. |
||
446 | * |
||
447 | 9 | * @return void |
|
448 | */ |
||
449 | 9 | protected function checked(string $value, $valueChecked, bool $isConfig = true): void |
|
450 | 8 | { |
|
451 | 8 | if ($isConfig) { |
|
452 | 8 | if ($this->getConfig($value) === $valueChecked) { |
|
453 | echo 'checked'; |
||
454 | return; |
||
455 | 5 | } |
|
456 | 5 | } else { |
|
457 | 5 | if ($value === $valueChecked) { |
|
458 | echo 'checked'; |
||
459 | return; |
||
460 | } |
||
461 | 8 | } |
|
462 | |||
463 | echo ''; |
||
464 | } |
||
465 | |||
466 | /** |
||
467 | * Echo correspondence string on Messenger setting page. |
||
468 | * |
||
469 | * @param string $moduleName The messenger module's name. |
||
470 | * @param string $echoType Value: css | icon |
||
471 | * |
||
472 | 2 | * @return void |
|
473 | */ |
||
474 | 2 | protected function messengerAjaxStatus(string $moduleName, string $echoType = 'css'): void |
|
475 | { |
||
476 | 2 | $echo = []; |
|
477 | 2 | ||
478 | 2 | $echo['css'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? |
|
479 | 'success' : |
||
480 | 2 | ''; |
|
481 | 2 | ||
482 | 2 | $echo['icon'] = $this->getConfig('messengers.' . $moduleName . '.confirm_test') ? |
|
483 | '<i class="fas fa-check"></i>' : |
||
484 | 2 | '<i class="fas fa-exclamation"></i>'; |
|
485 | |||
486 | echo $echo[$echoType]; |
||
487 | } |
||
488 | |||
489 | /** |
||
490 | * Check the required fields. |
||
491 | * |
||
492 | * @param array $fields The fields from POST form. |
||
493 | * |
||
494 | 8 | * @return bool |
|
495 | */ |
||
496 | 8 | protected function checkPostParamsExist(...$fields): bool |
|
497 | { |
||
498 | 8 | $postParams = (array) get_request()->getParsedBody(); |
|
499 | 8 | ||
500 | 8 | foreach ($fields as $field) { |
|
501 | if (empty($postParams[$field])) { |
||
502 | return false; |
||
503 | } |
||
504 | 6 | } |
|
505 | |||
506 | return true; |
||
507 | } |
||
508 | } |
||
509 |