terrylinooo /
shieldon
| 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
Loading history...
|
|||
| 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 |