%PDF- %PDF-
| Direktori : /backups/router/usr/local/opnsense/mvc/app/controllers/OPNsense/Base/ |
| Current File : //backups/router/usr/local/opnsense/mvc/app/controllers/OPNsense/Base/ControllerBase.php |
<?php
/*
* Copyright (C) 2015 Deciso B.V.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
namespace OPNsense\Base;
use Phalcon\Di\FactoryDefault;
use Phalcon\Mvc\View;
use Phalcon\Mvc\View\Engine\Volt as VoltEngine;
use OPNsense\Core\AppConfig;
use OPNsense\Core\Config;
use OPNsense\Mvc\Dispatcher;
/**
* Class ControllerBase implements core controller for OPNsense framework
* @package OPNsense\Base
*/
class ControllerBase extends ControllerRoot
{
public View $view;
/**
* @var array Content-Security-Policy extensions, when set they will be merged with the defaults
*/
protected array $content_security_policy = [];
private array $volt_functions = [
'theme_file_or_default' => 'view_fetch_themed_filename',
'file_exists' => 'view_file_exists',
'cache_safe' => 'view_cache_safe'
];
/**
* @return array list of javascript files to be included in default.volt
*/
protected function templateJSIncludes()
{
return [
// legacy browser functions
'/ui/js/polyfills.js',
// JQuery
'/ui/js/jquery-3.5.1.min.js',
// JQuery Tokenize2 (https://zellerda.github.io/Tokenize2/)
'/ui/js/tokenize2.js',
// Bootgrid (grid system from http://www.jquery-bootgrid.com/ )
'/ui/js/jquery.bootgrid.js',
// Bootstrap type ahead
'/ui/js/bootstrap3-typeahead.min.js',
// OPNsense standard toolkit
'/ui/js/opnsense.js',
'/ui/js/opnsense_theme.js',
'/ui/js/opnsense_ui.js',
'/ui/js/opnsense_bootgrid_plugin.js',
'/ui/js/opnsense_status.js',
// bootstrap script
'/ui/js/bootstrap.min.js',
'/ui/js/bootstrap-select.min.js',
// bootstrap dialog
'/ui/js/bootstrap-dialog.min.js'
];
}
/**
* @return array list of css files to be included in default.volt (used themed versions if available)
*/
protected function templateCSSIncludes()
{
return [
// default theme
'css/main.css',
// Stylesheet for fancy select/dropdown
'/css/bootstrap-select.css',
// bootstrap dialog
'/css/bootstrap-dialog.css',
// Font awesome
'/ui/assets/fontawesome/css/fontawesome.min.css',
'/ui/assets/fontawesome/css/solid.min.css',
'/ui/assets/fontawesome/css/brands.min.css',
'/ui/assets/fontawesome/css/v4-shims.min.css',
// JQuery Tokenize2 (https://zellerda.github.io/Tokenize2/)
'/css/tokenize2.css',
// Bootgrid (grid system from http://www.jquery-bootgrid.com/ )
'/css/jquery.bootgrid.css'
];
}
/**
* Construct a view to render Volt templates
*/
public function __construct()
{
$volt_functions = $this->volt_functions;
$appcfg = new AppConfig();
$this->view = new View();
$viewDirs = [];
foreach ((array)$appcfg->application->viewsDir as $viewDir) {
$viewDirs[] = $viewDir;
}
$this->view->setViewsDir($viewDirs);
$this->view->setDI(new FactoryDefault());
$this->view->registerEngines([
'.volt' => function ($view) use ($appcfg, $volt_functions) {
$volt = new VoltEngine($view);
$volt->setOptions([
'path' => $appcfg->application->cacheDir,
'separator' => '_'
]);
foreach ($volt_functions as $func_name => $function) {
$volt->getCompiler()->addFunction($func_name, $function);
}
$volt->getCompiler()->addFilter('safe', 'view_html_safe');
return $volt;
}]);
}
/**
* @param Dispatcher $dispatcher
* @return void
*/
public function afterExecuteRoute(Dispatcher $dispatcher)
{
$this->view->start();
$this->view->processRender('', '');
$this->view->finish();
$this->response->setContent($this->view->getContent());
}
/**
* convert xml form definition to simple data structure to use in our Volt templates
*
* @param $xmlNode
* @return array
*/
private function parseFormNode($xmlNode)
{
$result = [];
foreach ($xmlNode as $key => $node) {
switch ($key) {
case "tab":
if (!array_key_exists("tabs", $result)) {
$result['tabs'] = [];
}
$tab = [];
$tab[] = (string)$node->attributes()->id;
$tab[] = gettext((string)$node->attributes()->description);
if (isset($node->subtab)) {
$tab["subtabs"] = $this->parseFormNode($node);
} else {
$tab[] = $this->parseFormNode($node);
}
$result['tabs'][] = $tab;
break;
case "subtab":
$subtab = [];
$subtab[] = $node->attributes()->id;
$subtab[] = gettext((string)$node->attributes()->description);
$subtab[] = $this->parseFormNode($node);
$result[] = $subtab;
break;
case "field":
// field type, containing attributes
$result[] = $this->parseFormNode($node);
break;
case "help":
case "hint":
case "label":
$result[$key] = gettext((string)$node);
break;
default:
// default behavior, copy in value as key/value data
$result[$key] = (string)$node;
break;
}
}
return $result;
}
/**
* fetch form xml
* @return SimpleXMLElement
*/
private function getFormXML($formname)
{
$class_info = new \ReflectionClass($this);
$filename = dirname($class_info->getFileName()) . "/forms/" . $formname . ".xml";
if (!file_exists($filename)) {
throw new \Exception('form xml ' . $filename . ' missing');
}
$formXml = simplexml_load_file($filename);
if ($formXml === false) {
throw new \Exception('form xml ' . $filename . ' not valid');
}
return $formXml;
}
/**
* parse an xml type form
* @param $formname
* @return array
* @throws \Exception
*/
public function getForm($formname)
{
return $this->parseFormNode($this->getFormXML($formname));
}
/**
* Extract grid fields from form definition
* @return array
*/
public function getFormGrid($formname, $grid_id = null, $edit_alert_id = null)
{
/* collect all fields, sort by sequence */
$all_data = [];
$idx = 0;
foreach ($this->getFormXML($formname) as $rootkey => $rootnode) {
if ($rootkey == 'field' && !empty((string)$rootnode->id)) {
$record = [
'column-id' => '',
'label' => '',
'visible' => 'true',
'sortable' => 'true',
'identifier' => 'false',
'type' => 'string' /* XXX: convert to type + formatter using source type? */
];
foreach ($rootnode as $key => $item) {
switch ($key) {
case 'label':
$record['label'] = gettext((string)$item);
break;
case 'id':
$tmp = explode('.', (string)$item);
$record['column-id'] = end($tmp);
break;
}
}
/* iterate field->grid_view items */
$this_sequence = '9999999';
if (isset($rootnode->grid_view)) {
foreach ($rootnode->grid_view->children() as $key => $item) {
if ($key == 'ignore' && $item != 'false') {
/* ignore field as requested */
continue 2;
} elseif ($key == 'sequence') {
$this_sequence = (string)$item;
} else {
$record[$key] = (string)$item;
}
}
}
$all_data[sprintf("%010d.%03d", $this_sequence, $idx++)] = $record;
}
}
/* prepend identifier */
$all_data[sprintf("%010d.%03d", 0, 0)] = [
'column-id' => 'uuid',
'label' => gettext('ID'),
'type' => 'string',
'identifier' => 'true',
'visible' => 'false'
];
ksort($all_data);
$basename = $grid_id ?? $formname;
return [
'table_id' => $basename,
'edit_dialog_id' => 'dialog_' . $basename,
'edit_alert_id' => $edit_alert_id == null ? 'change_message_' . $basename : $edit_alert_id,
'fields' => array_values($all_data)
];
}
/**
* Default action. Set the standard layout.
*/
public function initialize()
{
// set base template
$this->view->setTemplateBefore('default');
$this->view->session = $this->session;
}
/**
* shared functionality for all components
* @param $dispatcher
* @return bool
* @throws \Exception
*/
public function beforeExecuteRoute(Dispatcher $dispatcher)
{
// only handle input validation on first request.
if (!$dispatcher->wasForwarded()) {
// Authentication
// - use authentication of legacy OPNsense.
if (!$this->doAuth()) {
return false;
}
// check for valid csrf on post requests
if ($this->request->isPost() && !$this->security->checkToken(null, null, false)) {
// post without csrf, exit.
$this->response->setStatusCode(403, "Forbidden");
return false;
}
// REST type calls should be implemented by inheriting ApiControllerBase.
// because we don't check for csrf on these methods, we want to make sure these aren't used.
if (
$this->request->isHead() ||
$this->request->isPut() ||
$this->request->isDelete() ||
$this->request->isPatch() ||
$this->request->isOptions()
) {
throw new \Exception('request type not supported');
}
}
// link username on successful login
$this->logged_in_user = $this->session->get("Username");
// include csrf for volt view rendering.
$csrf_token = $this->session->get('$PHALCON/CSRF$');
$csrf_tokenKey = $this->session->get('$PHALCON/CSRF/KEY$');
if (empty($csrf_token) || empty($csrf_tokenKey)) {
// when there's no token in our session, request a new one
$csrf_token = $this->security->getToken();
$csrf_tokenKey = $this->security->getTokenKey();
}
$this->view->setVars(['csrf_tokenKey' => $csrf_tokenKey, 'csrf_token' => $csrf_token]);
// link menu system to view
$menu = new Menu\MenuSystem();
// add interfaces to "Interfaces" menu tab... kind of a hack, may need some improvement.
$cnf = Config::getInstance();
$this->view->setVar('lang', $this->translator);
$this->view->setVar('langcode', str_replace('_', '-', $this->langcode));
$rewrite_uri = explode("?", $_SERVER["REQUEST_URI"])[0];
$this->view->menuSystem = $menu->getItems($rewrite_uri);
/* XXX generating breadcrumbs requires getItems() call */
$this->view->menuBreadcrumbs = $menu->getBreadcrumbs();
// set theme in ui_theme template var, let template handle its defaults (if there is no theme).
if (
$cnf->object()->theme->count() > 0 && !empty($cnf->object()->theme) &&
(
is_dir('/usr/local/opnsense/www/themes/' . (string)$cnf->object()->theme) ||
!is_dir('/usr/local/opnsense/www/themes')
)
) {
$this->view->ui_theme = $cnf->object()->theme;
}
// parse product properties, use template (.in) when not found
$firmware_product_fn = __DIR__ . '/../../../../../version/core';
$firmware_product_fn = !is_file($firmware_product_fn) ? $firmware_product_fn . ".in" : $firmware_product_fn;
$product_vars = json_decode(file_get_contents($firmware_product_fn), true);
foreach ($product_vars as $product_key => $product_var) {
$this->view->$product_key = $product_var;
}
// info about the current user and box
$this->view->session_username = !empty($_SESSION['Username']) ? $_SESSION['Username'] : '(unknown)';
$this->view->system_hostname = $cnf->object()->system->hostname;
$this->view->system_domain = $cnf->object()->system->domain;
if (isset($this->view->menuBreadcrumbs[0]['name'])) {
$output = [];
foreach ($this->view->menuBreadcrumbs as $crumb) {
$output[] = gettext($crumb['name']);
}
$this->view->title = join(': ', $output);
$output = [];
foreach (array_reverse($this->view->menuBreadcrumbs) as $crumb) {
$output[] = gettext($crumb['name']);
}
$this->view->headTitle = join(' | ', $output);
}
// append ACL object to view
$this->view->acl = new \OPNsense\Core\ACL();
// Javascript includes
$this->view->javascript_files = $this->templateJSIncludes();
$this->view->css_files = $this->templateCSSIncludes();
// set security policies
$policies = array(
"default-src" => "'self'",
"img-src" => "'self' data: blob:",
"script-src" => "'self' 'unsafe-inline' 'unsafe-eval'",
"style-src" => "'self' 'unsafe-inline' 'unsafe-eval'");
foreach ($this->content_security_policy as $policy_name => $policy_content) {
if (empty($policies[$policy_name])) {
$policies[$policy_name] = "";
}
$policies[$policy_name] .= " {$policy_content}";
}
$csp = "";
foreach ($policies as $policy_name => $policy) {
$csp .= $policy_name . " " . $policy . " ;";
}
$this->response->setHeader('Content-Security-Policy', $csp);
$this->response->setHeader('X-Frame-Options', "SAMEORIGIN");
$this->response->setHeader('X-Content-Type-Options', "nosniff");
$this->response->setHeader('X-XSS-Protection', "1; mode=block");
$this->response->setHeader('Referrer-Policy', "same-origin");
}
}