diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/CategoryController.php b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/CategoryController.php index a174b5747..6fa5333bd 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/CategoryController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/Api/CategoryController.php @@ -51,7 +51,7 @@ class CategoryController extends ApiMutableModelControllerBase */ public function searchItemAction() { - return $this->searchBase("categories.category", array('name'), "name"); + return $this->searchBase("categories.category", array('name', 'auto'), "name"); } /** @@ -63,7 +63,15 @@ class CategoryController extends ApiMutableModelControllerBase */ public function setItemAction($uuid) { - // XXX: handle renames? + $node = $this->getModel()->getNodeByReference('categories.category.' . $uuid); + $old_name = $node != null ? (string)$node->name : null; + if ($old_name !== null && $this->request->isPost() && $this->request->hasPost("category")) { + $new_name = $this->request->getPost("category")['name']; + if ($new_name != $old_name) { + // replace categories, setBase() will synchronise the changes to disk + $this->getModel()->refactor($old_name, $new_name); + } + } return $this->setBase("category", "categories.category", $uuid); } @@ -101,7 +109,12 @@ class CategoryController extends ApiMutableModelControllerBase public function delItemAction($uuid) { Config::getInstance()->lock(); - // XXX : check if used and throw error + $node = $this->getModel()->getNodeByReference('categories.category.' . $uuid); + $node_name = $node != null ? (string)$node->name : null; + if ($node_name != null && $this->getModel()->isUsed($node_name)) { + $message = gettext("Cannot delete a category which is still in use."); + throw new \OPNsense\Base\UserException($message, gettext("Category in use")); + } return $this->delBase("categories.category", $uuid); } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/CategoryController.php b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/CategoryController.php new file mode 100644 index 000000000..06b7e166d --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/CategoryController.php @@ -0,0 +1,43 @@ +view->formDialogEdit = $this->getForm("categoryEdit"); + $this->view->pick('OPNsense/Firewall/category'); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/categoryEdit.xml b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/categoryEdit.xml new file mode 100644 index 000000000..8b18fa004 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Firewall/forms/categoryEdit.xml @@ -0,0 +1,14 @@ +
+ + category.auto + + checkbox + Automatically added, will be removed when used + + + category.name + + text + Enter a name for this category. + +
diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml index a8eda9fb4..b521ddef4 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/ACL/ACL.xml @@ -211,6 +211,13 @@ api/firewall/alias/get* + + Firewall: Categories + + ui/firewall/category/* + api/firewall/category/* + + Firewall: NAT: 1:1 diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index 0b40907fd..a0ffa8356 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -154,6 +154,7 @@ + diff --git a/src/opnsense/mvc/app/models/OPNsense/Firewall/Category.php b/src/opnsense/mvc/app/models/OPNsense/Firewall/Category.php index 356201a9a..963ef97d9 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Firewall/Category.php +++ b/src/opnsense/mvc/app/models/OPNsense/Firewall/Category.php @@ -57,15 +57,8 @@ class Category extends BaseModel } } - /** - * collect unique categories from rules and updates the model with auto generated items. - * XXX: if this operation turns out to be a bottleneck, we should move the maintance responsibiliy to the caller - * for the item in question (rule update) - * @return bool true if changed - */ - public function sync() + private function ruleIterator() { - $has_changed = false; $cfgObj = Config::getInstance()->object(); $source = [ ['filter', 'rule'], @@ -73,8 +66,7 @@ class Category extends BaseModel ['nat', 'onetoone'], ['nat', 'outbound', 'rule'], ['nat', 'npt'], - ]; - $used_categories = []; + ]; foreach ($source as $aliasref) { $cfgsection = $cfgObj; foreach ($aliasref as $cfgName) { @@ -85,15 +77,62 @@ class Category extends BaseModel if ($cfgsection != null) { foreach ($cfgsection as $node) { if (!empty($node->category)) { - foreach (explode(",", (string)$node->category) as $cat) { - if (!in_array($cat, $used_categories)) { - $used_categories[] = $cat; - } - } + yield $node; } } } } + } + + /** + * refactor category usage (replace category in rules) + */ + public function refactor($oldname, $newname) + { + $has_changed = false; + foreach ($this->ruleIterator() as $node) { + $cats = explode(",", (string)$node->category); + if (in_array($oldname, $cats)) { + unset($cats[array_search((string)$oldname, $cats)]); + $cats[] = $newname; + $node->category = implode(",", $cats); + $has_changed = true; + } + } + return $has_changed; + } + + /** + * refactor category usage (replace category in rules) + */ + public function isUsed($name) + { + foreach ($this->ruleIterator() as $node) { + if (in_array($name, explode(",", (string)$node->category))) { + return true; + } + } + return false; + } + + + /** + * collect unique categories from rules and updates the model with auto generated items. + * XXX: if this operation turns out to be a bottleneck, we should move the maintance responsibiliy to the caller + * for the item in question (rule update) + * @return bool true if changed + */ + public function sync() + { + $has_changed = false; + $used_categories = []; + foreach ($this->ruleIterator() as $node) { + foreach (explode(",", (string)$node->category) as $cat) { + if (!in_array($cat, $used_categories)) { + $used_categories[] = $cat; + } + } + } foreach ($this->categories->category->iterateItems() as $key => $category) { if (!empty((string)$category->auto) && !in_array((string)$category->name, $used_categories)) { $this->categories->category->del($key); @@ -102,7 +141,6 @@ class Category extends BaseModel unset($used_categories[array_search((string)$category->name, $used_categories)]); } } - foreach ($used_categories as $name) { $node = $this->categories->category->add(); $node->name = $name; diff --git a/src/opnsense/mvc/app/views/OPNsense/Firewall/category.volt b/src/opnsense/mvc/app/views/OPNsense/Firewall/category.volt new file mode 100644 index 000000000..3eaafde47 --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Firewall/category.volt @@ -0,0 +1,76 @@ +{# + # Copyright (c) 2020 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. + #} + + + + + +
+
+ + + + + + + + + + + + + + + + + +
{{ lang._('Name') }}{{ lang._('Auto') }}{{ lang._('Commands') }}{{ lang._('ID') }}
+ + +
+
+
+ +{# include dialog #} +{{ partial("layout_partials/base_dialog",['fields':formDialogEdit,'id':'DialogEdit','label':lang._('Edit category')])}}