Firewall: add model for categories. work in progress for https://github.com/opnsense/core/issues/4587

o replace type ahead with tokenizer (mutli select) while here, since the type ahead needed fixes which we rather don't copy to the other firewall sections
o we might want to change the model serializeToConfig() and reload and send it to a separate function in config.inc later to ease migration later. Part of it could be separated with a callback, but it's not super needed
o hook categories into model for firewall rules
This commit is contained in:
Ad Schellevis 2021-01-12 18:10:26 +01:00
parent c6f4d40ae0
commit 416fc37ee9
3 changed files with 48 additions and 90 deletions

View File

@ -42,14 +42,21 @@ class Category extends BaseModel
{
public function getByName($name)
{
foreach ($this->categories->category->iterateItems() as $alias) {
if ((string)$alias->name == $name) {
return $alias;
foreach ($this->categories->category->iterateItems() as $category) {
if ((string)$category->name == $name) {
return $category;
}
}
return null;
}
public function iterateCategories()
{
foreach ($this->categories->category->iterateItems() as $category) {
yield ['name' => (string)$category->name];
}
}
/**
* 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
@ -71,8 +78,12 @@ class Category extends BaseModel
}
if ($cfgsection != null) {
foreach ($cfgsection as $node) {
if (!empty($node->category) && !in_array((string)$node->category, $used_categories)) {
$used_categories[] = (string)$node->category;
if (!empty($node->category)) {
foreach (explode(",", (string)$node->category) as $cat) {
if (!in_array($cat, $used_categories)) {
$used_categories[] = $cat;
}
}
}
}
}

View File

@ -512,7 +512,14 @@ $( document ).ready(function() {
}
});
$(".rule").each(function(){
if (selected_values.indexOf($(this).data('category')) == -1 && selected_values.length > 0) {
let rule_categories = $(this).data('category').split(',');
let is_selected = false;
rule_categories.forEach(function(item){
if (selected_values.indexOf(item) > -1) {
is_selected = true;
}
});
if (!is_selected && selected_values.length > 0) {
$(this).hide();
$(this).find("input").prop('disabled', true);
} else {
@ -624,17 +631,12 @@ $( document ).ready(function() {
<div id="category_block" style="z-index:-100;">
<select class="selectpicker hidden-xs hidden-sm hidden-md" data-live-search="true" data-size="5" multiple title="<?=gettext("Select category");?>" id="fw_category">
<?php
// collect unique list of categories and append to option list
$categories = array();
foreach ($a_filter as $tmp_rule) {
if (!empty($tmp_rule['category']) && !in_array($tmp_rule['category'], $categories)) {
$categories[] = $tmp_rule['category'];
}
}
foreach ($categories as $category):?>
<option value="<?=$category;?>" <?=in_array($category, $selected_category) ? "selected=\"selected\"" : "" ;?>><?=$category;?></option>
foreach ((new OPNsense\Firewall\Category())->iterateCategories() as $category):?>
<option value="<?=$category['name'];?>" <?=in_array($category['name'], $selected_category) ? "selected=\"selected\"" : "" ;?>>
<?=$category['name'];?>
</option>
<?php
endforeach;?>
endforeach;?>
</select>
<button id="btn_inspect" class="btn btn-default hidden-xs">
<i class="fa fa-eye" aria-hidden="true"></i>

View File

@ -139,6 +139,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
$pconfig[$fieldname] = $a_filter[$configId][$fieldname];
}
}
$pconfig['category'] = !empty($pconfig['category']) ? explode(",", $pconfig['category']) : [];
// process fields with some kind of logic
address_to_pconfig($a_filter[$configId]['source'], $pconfig['src'],
@ -534,6 +535,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// sort filter items per interface, not really necessary but leaves a bit nicer sorted config.xml behind.
filter_rules_sort();
// write to config
OPNsense\Core\Config::getInstance()->fromArray($config);
$catmdl = new OPNsense\Firewall\Category();
if ($catmdl->sync()) {
$catmdl->serializeToConfig();
$config = OPNsense\Core\Config::getInstance()->toArray(listtags());
}
write_config();
mark_subsystem_dirty('filter');
@ -552,6 +559,9 @@ $priorities = interfaces_vlan_priorities();
include("head.inc");
?>
<script src="<?= cache_safe('/ui/js/tokenize2.js') ?>"></script>
<link rel="stylesheet" type="text/css" href="<?= cache_safe(get_themed_filename('/css/tokenize2.css')) ?>">
<script src="<?= cache_safe('/ui/js/opnsense_ui.js') ?>"></script>
<body>
<script>
$( document ).ready(function() {
@ -672,67 +682,7 @@ include("head.inc");
$("#toggleAdvanced").click();
<?php endif;?>
// add typeahead for existing categories, all options are saves in the select option "existing_categories"
var categories = [];
$("#existing_categories > option").each(function(){
categories.push($(this).val());
});
$("#category").typeahead({
source: categories
});
/***************************************************************************************************************
* typeahead seems to be broken since jQuery 3.3.0
* temporary fix to make sure "position()" returns the expected values as suggested in:
* https://github.com/bassjobsen/Bootstrap-3-Typeahead/issues/384
*
* When being used outside position: relative items, the plugin still seems to be working
* (which is likely why the menu quick search isn't affected).
***************************************************************************************************************/
$.fn.position = function() {
if ( !this[ 0 ] ) {
return;
}
var offsetParent, offset, doc,
elem = this[ 0 ],
parentOffset = { top: 0, left: 0 };
// position:fixed elements are offset from the viewport, which itself always has zero offset
if ( jQuery.css( elem, "position" ) === "fixed" ) {
// Assume position:fixed implies availability of getBoundingClientRect
offset = elem.getBoundingClientRect();
} else {
offset = this.offset();
// Account for the *real* offset parent, which can be the document or its root element
// when a statically positioned element is identified
doc = elem.ownerDocument;
offsetParent = elem.offsetParent || doc.documentElement;
while ( offsetParent &&
offsetParent !== doc &&
( offsetParent !== doc.body && offsetParent !== doc.documentElement ) &&
jQuery.css( offsetParent, "position" ) === "static" ) {
offsetParent = offsetParent.parentNode;
}
if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {
// Incorporate borders into its offset, since they are outside its content origin
parentOffset = jQuery( offsetParent ).offset();
parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
}
}
// Subtract parent offsets and element margins
return {
top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
};
};
formatTokenizersUI();
});
</script>
@ -1265,23 +1215,18 @@ include("head.inc");
<tr>
<td><a id="help_for_category" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Category"); ?></td>
<td>
<input name="category" type="text" class="formfld unknown" id="category" size="40" value="<?=$pconfig['category'];?>" />
<div class="hidden" data-for="help_for_category">
<?=gettext("You may enter or select a category here to group firewall rules (not parsed)."); ?>
</div>
<select class="hidden" id="existing_categories">
<select name="category[]" id="category" multiple="multiple" class="tokenize" data-allownew="true" data-width="334px">
<?php
$categories = array();
foreach ($a_filter as $tmp_rule) {
if (!empty($tmp_rule['category']) && !in_array($tmp_rule['category'], $categories)) {
$categories[] = $tmp_rule['category'];
}
}
foreach ($categories as $category):?>
<option value="<?=$category;?>"></option>
foreach ((new OPNsense\Firewall\Category())->iterateCategories() as $category):?>
<option value="<?=$category['name'];?>" <?=in_array($category['name'], $pconfig['category']) ? 'selected="selected"' : '';?> >
<?=$category['name'];?>
</option>
<?php
endforeach;?>
</select>
<div class="hidden" data-for="help_for_category">
<?=gettext("You may enter or select a category here to group firewall rules (not parsed)."); ?>
</div>
</tr>
<tr>
<td><a id="help_for_descr" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("Description"); ?></td>