This commit is contained in:
Ad Schellevis
2024-01-01 17:14:09 +01:00
parent 9ff64a3e41
commit c6e16e54f3
12 changed files with 327 additions and 9 deletions
@@ -42,4 +42,7 @@ function pfplugin_firewall($fw)
foreach ($mdlFilter->snatrules->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerSNatRule(50, $rule->serialize());
}
foreach ($mdlFilter->npt->rule->sortedBy(["sequence"]) as $key => $rule) {
$fw->registerNptRule(50, $rule->serialize());
}
}
@@ -30,6 +30,7 @@ namespace OPNsense\Firewall\Api;
use OPNsense\Base\ApiMutableModelControllerBase;
use OPNsense\Core\Backend;
use OPNsense\Core\Config;
use OPNsense\Firewall\Category;
/**
* Class FilterBaseController implements actions for various types
@@ -39,6 +40,45 @@ abstract class FilterBaseController extends ApiMutableModelControllerBase
{
protected static $internalModelName = 'filter';
protected static $internalModelClass = 'OPNsense\Firewall\Filter';
protected static $categorysource = null;
/**
* list categories and usage
* @return array
*/
public function listCategoriesAction()
{
$response = ['rows' => []];
$catcount = [];
if (!empty(static::$categorysource)) {
$node = $this->getModel();
foreach (explode('.', static::$categorysource) as $ref) {
$node = $node->$ref;
}
foreach ($node->iterateItems() as $item) {
if (!empty((string)$item->categories)) {
foreach (explode(',', (string)$item->categories) as $cat) {
if (!isset($catcount[$cat])) {
$catcount[$cat] = 0;
}
$catcount[$cat] += 1;
}
}
}
}
foreach ((new Category())->categories->category->iterateItems() as $key => $category) {
$response['rows'][] = [
"uuid" => $key,
"name" => (string)$category->name,
"color" => (string)$category->color,
"used" => isset($catcount[$key]) ? $catcount[$key] : 0
];
}
array_multisort(array_column($response['rows'], "name"), SORT_ASC, SORT_NATURAL, $response['rows']);
return $response;
}
public function applyAction($rollback_revision = null)
{
@@ -29,9 +29,15 @@ namespace OPNsense\Firewall\Api;
class FilterController extends FilterBaseController
{
protected static $categorysource = "rules.rule";
public function searchRuleAction()
{
return $this->searchBase("rules.rule", array('enabled', 'sequence', 'description'), "sequence");
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("rules.rule", ['enabled', 'sequence', 'description'], "sequence", $filter_funct);
}
public function setRuleAction($uuid)
@@ -0,0 +1,67 @@
<?php
/*
* Copyright (C) 2023 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\Firewall\Api;
class NptController extends FilterBaseController
{
protected static $categorysource = "npt.rule";
public function searchRuleAction()
{
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("npt.rule", ['enabled', 'sequence', 'description'], "sequence", $filter_funct);
}
public function setRuleAction($uuid)
{
return $this->setBase("rule", "npt.rule", $uuid);
}
public function addRuleAction()
{
return $this->addBase("rule", "npt.rule");
}
public function getRuleAction($uuid = null)
{
return $this->getBase("rule", "npt.rule", $uuid);
}
public function delRuleAction($uuid)
{
return $this->delBase("npt.rule", $uuid);
}
public function toggleRuleAction($uuid, $enabled = null)
{
return $this->toggleBase("npt.rule", $uuid, $enabled);
}
}
@@ -29,9 +29,15 @@ namespace OPNsense\Firewall\Api;
class SourceNatController extends FilterBaseController
{
protected static $categorysource = "snatrules.rule";
public function searchRuleAction()
{
return $this->searchBase("snatrules.rule", array('enabled', 'sequence', 'description'), "sequence");
$category = $this->request->get('category');
$filter_funct = function ($record) use ($category) {
return empty($category) || array_intersect(explode(',', $record->categories), $category);
};
return $this->searchBase("snatrules.rule", ['enabled', 'sequence', 'description'], "sequence", $filter_funct);
}
public function setRuleAction($uuid)
@@ -0,0 +1,38 @@
<?php
/*
* Copyright (C) 2023 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\Firewall;
class NptController extends \OPNsense\Base\IndexController
{
public function indexAction()
{
$this->view->pick('OPNsense/Firewall/filter');
$this->view->ruleController = "npt";
$this->view->formDialogFilterRule = $this->getForm("dialogNptRule");
}
}
@@ -99,7 +99,13 @@
<type>checkbox</type>
<help>Log packets that are handled by this rule</help>
</field>
<field>
<id>rule.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
@@ -0,0 +1,40 @@
<form>
<field>
<id>rule.enabled</id>
<label>enabled</label>
<type>checkbox</type>
<help>Enable this rule</help>
</field>
<field>
<id>rule.sequence</id>
<label>Sequence</label>
<type>text</type>
</field>
<field>
<id>rule.interface</id>
<label>Interface</label>
<type>dropdown</type>
</field>
<field>
<id>rule.source_net</id>
<label>Source</label>
<type>text</type>
</field>
<field>
<id>rule.destination_net</id>
<label>Destination</label>
<type>text</type>
</field>
<field>
<id>rule.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
<type>text</type>
</field>
</form>
@@ -86,6 +86,13 @@
<type>checkbox</type>
<help>Log packets that are handled by this rule</help>
</field>
<field>
<id>rule.categories</id>
<label>Categories</label>
<type>select_multiple</type>
<style>tokenize</style>
<help>For grouping purposes you may select multiple groups here to organize items.</help>
</field>
<field>
<id>rule.description</id>
<label>Description</label>
@@ -100,6 +100,17 @@
<Default>0</Default>
<Required>Y</Required>
</log>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.\-,_\x{00A0}-\x{FFFF}]){0,255}$/u</mask>
@@ -184,6 +195,17 @@
<Default>0</Default>
<Required>Y</Required>
</log>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.\-,_\x{00A0}-\x{FFFF}]){0,255}$/u</mask>
@@ -191,5 +213,38 @@
</description>
</rule>
</snatrules>
<npt>
<rule type=".\SourceNatRuleField">
<enabled type="BooleanField">
<Default>1</Default>
<Required>Y</Required>
</enabled>
<interface type="InterfaceField">
<Required>Y</Required>
<Default>lan</Default>
<AllowDynamic>Y</AllowDynamic>
</interface>
<source_net type="NetworkAliasField">
<Required>Y</Required>
</source_net>
<destination_net type="NetworkAliasField"/>
<categories type="ModelRelationField">
<Model>
<rulesets>
<source>OPNsense.Firewall.Category</source>
<items>categories.category</items>
<display>name</display>
</rulesets>
</Model>
<Multiple>Y</Multiple>
<ValidationMessage>Related category not found.</ValidationMessage>
</categories>
<description type="TextField">
<Required>N</Required>
<mask>/^([\t\n\v\f\r 0-9a-zA-Z.\-,_\x{00A0}-\x{FFFF}]){0,255}$/u</mask>
<ValidationMessage>Description should be a string between 1 and 255 characters</ValidationMessage>
</description>
</rule>
</npt>
</items>
</model>
@@ -7,6 +7,9 @@
<SourceNat order="100" VisibleName="Source NAT" url="/ui/firewall/source_nat/">
<FilterRef url="/ui/firewall/source_nat#*" visibility="hidden"/>
</SourceNat>
<Npt order="100" VisibleName="NPTv6" url="/ui/firewall/npt/">
<FilterRef url="/ui/firewall/npt#*" visibility="hidden"/>
</Npt>
</Automation>
</Firewall>
</menu>
@@ -2,12 +2,47 @@
$( document ).ready(function() {
let initial_load = true;
let grid = $("#grid-rules").UIBootgrid({
search:'/api/firewall/{{ruleController}}/searchRule/',
get:'/api/firewall/{{ruleController}}/getRule/',
set:'/api/firewall/{{ruleController}}/setRule/',
add:'/api/firewall/{{ruleController}}/addRule/',
del:'/api/firewall/{{ruleController}}/delRule/',
toggle:'/api/firewall/{{ruleController}}/toggleRule/'
search:'/api/firewall/{{ruleController}}/search_rule/',
get:'/api/firewall/{{ruleController}}/get_rule/',
set:'/api/firewall/{{ruleController}}/set_rule/',
add:'/api/firewall/{{ruleController}}/add_rule/',
del:'/api/firewall/{{ruleController}}/del_rule/',
toggle:'/api/firewall/{{ruleController}}/toggle_rule/',
options:{
requestHandler: function(request){
if ( $('#category_filter').val().length > 0) {
request['category'] = $('#category_filter').val();
}
return request;
}
}
});
grid.on("loaded.rs.jquery.bootgrid", function (e){
// reload categories before grid load
ajaxCall('/api/firewall/{{ruleController}}/list_categories', {}, function(data, status){
if (data.rows !== undefined) {
let current_selection = $("#category_filter").val();
$("#category_filter").empty();
for (i=0; i < data.rows.length ; ++i) {
let row = data.rows[i];
let opt_val = $('<div/>').html(row.name).text();
let bgcolor = row.color != "" ? row.color : '31708f;'; // set category color
let option = $("<option/>").val(row.uuid).html(row.name);
if (row.used > 0) {
option.attr(
'data-content',
"<span>"+opt_val + "</span>"+
"<span style='background:#"+bgcolor+";' class='badge pull-right'>" + row.used + "</span>"
);
option.attr('id', row.uuid);
}
$("#category_filter").append(option);
}
$("#category_filter").val(current_selection);
$("#category_filter").selectpicker('refresh');
}
});
});
// open edit dialog when opened with a uuid reference
@@ -62,6 +97,11 @@
}
});
});
// move filter into action header
$("#type_filter_container").detach().prependTo('#grid-rules-header > .row > .actionBar > .actions');
$("#category_filter").change(function(){
$('#grid-rules').bootgrid('reload');
});
});
</script>
@@ -71,6 +111,13 @@
</ul>
<div class="tab-content content-box">
<div id="rules" class="tab-pane fade in active">
<div class="hidden">
<!-- filter per type container -->
<div id="type_filter_container" class="btn-group">
<select id="category_filter" data-title="{{ lang._('Categories') }}" class="selectpicker" data-live-search="true" data-size="5" multiple data-width="200px">
</select>
</div>
</div>
<!-- tab page "rules" -->
<table id="grid-rules" class="table table-condensed table-hover table-striped" data-editDialog="DialogFilterRule" data-editAlert="FilterRuleChangeMessage">
<thead>