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 @@
+
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.
+ #}
+
+
+
+
+