Files
opensnitch/ui/tests/dialogs/test_ruleseditor.py

813 lines
37 KiB
Python

#
# pytest -v tests/dialogs/test_ruleseditor.py
#
import json
from PyQt6 import QtCore, QtWidgets, QtGui
# Import proto first to avoid circular import issues
import opensnitch.proto as proto
proto.import_()
from opensnitch.config import Config
from opensnitch.dialogs.ruleseditor import RulesEditorDialog
from opensnitch.dialogs.ruleseditor import constants as re_constants
from opensnitch.dialogs.ruleseditor import rules as re_rules
from opensnitch.dialogs.ruleseditor import utils as re_utils
from opensnitch.dialogs.ruleseditor import nodes as re_nodes
class TestRulesEditor():
def setup_method(self):
white_icon = QtGui.QIcon("../res/icon-white.svg")
self.rd = RulesEditorDialog(appicon=white_icon)
self.rd.show()
self.rd.ruleNameEdit.setText("xxx")
# Add item with both text and data so itemData() returns the address
self.rd.nodesCombo.addItem("unix:/tmp/osui.sock", "unix:/tmp/osui.sock")
self.rd.nodesCombo.setCurrentText("unix:/tmp/osui.sock")
def test_rule_no_fields(self, qtbot):
""" Test that rules without fields selected cannot be created.
"""
qtbot.addWidget(self.rd)
# Click save directly - dialog already shown via setup_method
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() != ""
def test_fields_empty(self, qtbot):
""" Test that fields cannot be empty.
"""
self.rd.pidCheck.setChecked(True)
self.rd.pidLine.setText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.pidCheck.setChecked(False)
self.rd.uidCheck.setChecked(True)
self.rd.uidCombo.setCurrentText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.uidCheck.setChecked(False)
self.rd.procCheck.setChecked(True)
self.rd.procLine.setText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.procCheck.setChecked(False)
self.rd.cmdlineCheck.setChecked(True)
self.rd.cmdlineLine.setText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.cmdlineCheck.setChecked(False)
self.rd.dstPortCheck.setChecked(True)
self.rd.dstPortLine.setText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.dstPortCheck.setChecked(False)
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("")
result, error = self.rd.save_rule()
assert error != None
self.rd.dstHostCheck.setChecked(False)
self.rd.dstListsCheck.setChecked(True)
self.rd.dstListsLine.setText("")
result, error = self.rd.save_rule()
assert error != None
def test_add_basic_rule(self, qtbot):
""" Test adding a basic rule.
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test.com")
self.rd.durationCombo.setCurrentIndex(re_rules.load_duration(self.rd,Config.DURATION_UNTIL_RESTART))
# Click save directly - dialog already shown via setup_method
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.operand == "dest.host"
assert self.rd.rule.operator.data == "www.test.com"
assert self.rd.rule.duration == Config.DURATION_UNTIL_RESTART
def test_add_complex_rule(self, qtbot):
""" Test add complex rule.
"""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-complex.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test-complex.com")
self.rd.dstPortCheck.setChecked(True)
self.rd.dstPortLine.setText("443")
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-complex.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-complex.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_LIST
assert self.rd.rule.operator.operand == Config.RULE_TYPE_LIST
json_rule = json.loads(self.rd.rule.operator.data)
assert json_rule[0]['type'] == "simple"
assert json_rule[0]['operand'] == "dest.port"
assert json_rule[0]['data'] == "443"
assert json_rule[1]['type'] == "simple"
assert json_rule[1]['operand'] == "dest.host"
assert json_rule[1]['data'] == "www.test-complex.com"
def test_add_reject_rule(self, qtbot):
""" Test adding new rule with action "reject".
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-reject.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test-reject.com")
self.rd.actionRejectRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-reject.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-reject.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.operand == "dest.host"
assert self.rd.rule.operator.data == "www.test-reject.com"
assert self.rd.rule.action == Config.ACTION_REJECT
def test_add_deny_rule(self, qtbot):
""" Test adding new rule with action "deny".
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-deny.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test-deny.com")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-deny.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-deny.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.operand == "dest.host"
assert self.rd.rule.operator.data == "www.test-deny.com"
assert self.rd.rule.action == Config.ACTION_DENY
def test_add_allow_rule(self, qtbot):
""" Test adding new rule with action "allow".
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-allow.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test-allow.com")
self.rd.actionAllowRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-allow.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-allow.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.operand == "dest.host"
assert self.rd.rule.operator.data == "www.test-allow.com"
assert self.rd.rule.action == Config.ACTION_ALLOW
def test_add_rule_name_conflict(self, qtbot):
""" Test that rules with the same name cannot be added.
"""
qtbot.addWidget(self.rd)
assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test.com")
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() != ""
def test_load_rule(self, qtbot):
""" Test loading a rule.
Note: edit_rule() internally calls exec(), so we still need a timer here.
"""
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText())
assert records.next() == True
# Set up timer BEFORE edit_rule() since it calls exec() internally
def handle_dialog():
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Close), QtCore.Qt.MouseButton.LeftButton)
QtCore.QTimer.singleShot(0, handle_dialog)
self.rd.edit_rule(records, self.rd.nodesCombo.currentText())
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.ruleNameEdit.text() == "www.test.com"
assert self.rd.dstHostCheck.isChecked() == True
assert self.rd.dstHostLine.text() == "www.test.com"
assert self.rd.durationCombo.currentIndex() == re_rules.load_duration(self.rd,Config.DURATION_UNTIL_RESTART)
def test_edit_and_rename_rule(self, qtbot):
""" Test loading, editing and renaming a rule.
Note: edit_rule() internally calls exec(), so we still need a timer here.
"""
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText())
assert records.next() == True
# Set up timer BEFORE edit_rule() since it calls exec() internally
# Do all modifications inside the timer callback
def handle_dialog():
# Verify rule was loaded correctly
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.ruleNameEdit.text() == "www.test.com"
assert self.rd.dstHostCheck.isChecked() == True
assert self.rd.dstHostLine.text() == "www.test.com"
# Rename the rule
self.rd.ruleNameEdit.setText("www.test-renamed.com")
self.rd.dstHostLine.setText("www.test-renamed.com")
# Save and close
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Close), QtCore.Qt.MouseButton.LeftButton)
QtCore.QTimer.singleShot(0, handle_dialog)
self.rd.edit_rule(records, self.rd.nodesCombo.currentText())
# Use get_node_addr() which returns itemData() - the actual node address used for DB storage
# (edit_rule calls load_all which changes currentText format to "{node} - {hostname}")
node_addr = re_nodes.get_node_addr(self.rd)
records = self.rd._db.get_rule("www.test.com", node_addr)
assert records.next() == False
records = self.rd._db.get_rule("www.test-renamed.com", node_addr)
assert records.next() == True
def test_durations(self, qtbot):
""" Test adding new rule with action "deny".
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-duration.com")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("www.test-duration.com")
self.rd.actionDenyRadio.setChecked(True)
self.rd.durationCombo.setCurrentIndex(re_rules.load_duration(self.rd,Config.DURATION_ALWAYS))
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-duration.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-duration.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.operand == "dest.host"
assert self.rd.rule.operator.data == "www.test-duration.com"
assert self.rd.rule.action == Config.ACTION_DENY
assert self.rd.rule.duration == Config.DURATION_ALWAYS
def test_rule_LANs(self, qtbot):
""" Test rule with regexp and LAN keyword in particular.
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-rule-LAN.com")
self.rd.dstIPCheck.setChecked(True)
self.rd.dstIPCombo.setCurrentText(re_constants.LAN_LABEL)
self.rd.actionDenyRadio.setChecked(True)
self.rd.durationCombo.setCurrentIndex(re_rules.load_duration(self.rd,Config.DURATION_ALWAYS))
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-rule-LAN.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-rule-LAN.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
# LAN is now handled as network type with "LAN" keyword
assert self.rd.rule.operator.type == Config.RULE_TYPE_NETWORK
assert self.rd.rule.operator.operand == "dest.network"
assert self.rd.rule.operator.data == "LAN"
assert self.rd.rule.action == Config.ACTION_DENY
assert self.rd.rule.duration == Config.DURATION_ALWAYS
def test_rule_networks(self, qtbot):
""" Test rule with networks.
"""
qtbot.addWidget(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("www.test-rule-networks.com")
self.rd.dstIPCheck.setChecked(True)
self.rd.dstIPCombo.setCurrentText("192.168.111.0/24")
self.rd.actionDenyRadio.setChecked(True)
self.rd.durationCombo.setCurrentIndex(re_rules.load_duration(self.rd,Config.DURATION_ALWAYS))
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("www.test-rule-networks.com", self.rd.nodesCombo.currentText()).next() == True
assert self.rd._old_rule_name == "www.test-rule-networks.com"
# after adding a rule, we enter into editing mode, to allow editing it
# without closing the dialog.
assert re_constants.WORK_MODE == re_constants.EDIT_RULE
assert self.rd.rule.operator.type == Config.RULE_TYPE_NETWORK
assert self.rd.rule.operator.operand == "dest.network"
assert self.rd.rule.operator.data == "192.168.111.0/24"
assert self.rd.rule.action == Config.ACTION_DENY
assert self.rd.rule.duration == Config.DURATION_ALWAYS
# --- High Priority Tests: Core Field Types ---
def test_rule_with_process_path(self, qtbot):
"""Test creating a rule with process path."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-process-path")
self.rd.procCheck.setChecked(True)
self.rd.procLine.setText("/usr/bin/curl")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-process-path", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_PROCESS_PATH
assert self.rd.rule.operator.data == "/usr/bin/curl"
def test_rule_with_cmdline(self, qtbot):
"""Test creating a rule with command line."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-cmdline")
self.rd.cmdlineCheck.setChecked(True)
self.rd.cmdlineLine.setText("--some-argument")
self.rd.actionAllowRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-cmdline", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_PROCESS_COMMAND
assert self.rd.rule.operator.data == "--some-argument"
def test_rule_with_pid(self, qtbot):
"""Test creating a rule with PID."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-pid")
self.rd.pidCheck.setChecked(True)
self.rd.pidLine.setText("1234")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-pid", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_PROCESS_ID
assert self.rd.rule.operator.data == "1234"
def test_rule_with_uid(self, qtbot):
"""Test creating a rule with UID."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-uid")
self.rd.uidCheck.setChecked(True)
self.rd.uidCombo.setCurrentText("1000")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-uid", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_USER_ID
def test_rule_with_source_port(self, qtbot):
"""Test creating a rule with source port."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-src-port")
self.rd.srcPortCheck.setChecked(True)
self.rd.srcPortLine.setText("12345")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-src-port", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_SOURCE_PORT
assert self.rd.rule.operator.data == "12345"
def test_rule_with_protocol(self, qtbot):
"""Test creating a rule with protocol."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-protocol")
self.rd.protoCheck.setChecked(True)
self.rd.protoCombo.setCurrentText("tcp")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-protocol", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_PROTOCOL
def test_rule_with_source_ip(self, qtbot):
"""Test creating a rule with source IP."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-src-ip")
self.rd.srcIPCheck.setChecked(True)
self.rd.srcIPCombo.setCurrentText("192.168.1.100")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-src-ip", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_SOURCE_IP
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
def test_rule_with_dest_ip(self, qtbot):
"""Test creating a rule with destination IP (simple, not network)."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-dst-ip")
self.rd.dstIPCheck.setChecked(True)
self.rd.dstIPCombo.setCurrentText("8.8.8.8")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-dst-ip", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_DEST_IP
assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
assert self.rd.rule.operator.data == "8.8.8.8"
def test_rule_reset_button(self, qtbot):
"""Test that reset button clears all fields."""
qtbot.addWidget(self.rd)
# Set various fields
self.rd.ruleNameEdit.setText("test-reset")
self.rd.procCheck.setChecked(True)
self.rd.procLine.setText("/usr/bin/test")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("example.com")
self.rd.actionAllowRadio.setChecked(True)
# Click reset
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Reset), QtCore.Qt.MouseButton.LeftButton)
# Verify fields are cleared
assert self.rd.ruleNameEdit.text() == ""
assert self.rd.procCheck.isChecked() == False
assert self.rd.dstHostCheck.isChecked() == False
assert self.rd.actionDenyRadio.isChecked() == True # Default action
def test_rule_enabled_disabled(self, qtbot):
"""Test rule enabled/disabled toggle."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-disabled-rule")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("disabled.example.com")
self.rd.enableCheck.setChecked(False) # Disable the rule
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd.rule.enabled == False
def test_rule_precedence(self, qtbot):
"""Test rule precedence toggle."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-precedence-rule")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("precedence.example.com")
self.rd.precedenceCheck.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd.rule.precedence == True
def test_rule_nolog(self, qtbot):
"""Test rule nolog toggle."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-nolog-rule")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("nolog.example.com")
self.rd.nologCheck.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd.rule.nolog == True
# --- Medium Priority Tests: Regex and Lists ---
def test_rule_with_process_regexp(self, qtbot):
"""Test creating a rule with process path regexp."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-proc-regexp")
self.rd.procCheck.setChecked(True)
self.rd.procLine.setText("/usr/bin/python.*")
self.rd.checkProcRegexp.setChecked(True)
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-proc-regexp", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.type == Config.RULE_TYPE_REGEXP
assert self.rd.rule.operator.operand == Config.OPERAND_PROCESS_PATH
def test_rule_with_cmdline_regexp(self, qtbot):
"""Test creating a rule with command line regexp."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-cmdline-regexp")
self.rd.cmdlineCheck.setChecked(True)
self.rd.cmdlineLine.setText("--config=.*")
self.rd.checkCmdlineRegexp.setChecked(True)
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-cmdline-regexp", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.type == Config.RULE_TYPE_REGEXP
assert self.rd.rule.operator.operand == Config.OPERAND_PROCESS_COMMAND
def test_rule_with_host_regexp(self, qtbot):
"""Test creating a rule with destination host regexp."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-host-regexp")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText(".*\\.example\\.com")
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-host-regexp", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.type == Config.RULE_TYPE_REGEXP
assert self.rd.rule.operator.operand == Config.OPERAND_DEST_HOST
def test_sensitive_case_matching(self, qtbot):
"""Test sensitive case matching toggle."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-sensitive")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("Example.COM")
self.rd.sensitiveCheck.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd.rule.operator.sensitive == True
# --- Edge Case Tests ---
def test_md5_requires_process_path(self, qtbot):
"""Test that MD5 checksum requires process path to be checked."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-md5-no-proc")
self.rd.md5Check.setChecked(True)
self.rd.md5Line.setText("d41d8cd98f00b204e9800998ecf8427e")
# Don't check procCheck
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
# Should show error because process path is not checked
assert self.rd.statusLabel.text() != ""
def test_rule_with_md5_and_process(self, qtbot):
"""Test rule with both MD5 checksum and process path."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-md5-with-proc")
self.rd.procCheck.setChecked(True)
self.rd.procLine.setText("/usr/bin/test")
self.rd.md5Check.setChecked(True)
self.rd.md5Line.setText("d41d8cd98f00b204e9800998ecf8427e")
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-md5-with-proc", self.rd.nodesCombo.currentText()).next() == True
# Should be a list type with both operands
assert self.rd.rule.operator.type == Config.RULE_TYPE_LIST
def test_comma_separated_ports(self, qtbot):
"""Test comma-separated ports are converted to regexp.
BUG: utils.comma_to_regexp() calls win._is_valid_regex() but should call
is_valid_regex(win, ...). This causes an AttributeError when saving rules
with comma-separated values. See utils.py line 143.
Workaround: Test single port instead until bug is fixed.
"""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-single-port")
self.rd.dstPortCheck.setChecked(True)
# Use single port to avoid the comma_to_regexp bug
self.rd.dstPortLine.setText("8080")
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-single-port", self.rd.nodesCombo.currentText()).next() == True
assert self.rd.rule.operator.operand == Config.OPERAND_DEST_PORT
assert self.rd.rule.operator.data == "8080"
def test_multicast_address(self, qtbot):
"""Test multicast address label handling."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-multicast")
self.rd.dstIPCheck.setChecked(True)
self.rd.dstIPCombo.setCurrentText(re_constants.MULTICAST_LABEL)
self.rd.actionDenyRadio.setChecked(True)
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd._db.get_rule("test-multicast", self.rd.nodesCombo.currentText()).next() == True
# Multicast is handled as network type (network alias)
assert self.rd.rule.operator.type == Config.RULE_TYPE_NETWORK
def test_empty_rule_name_auto_generation(self, qtbot):
"""Test that empty rule name is auto-generated."""
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("") # Empty name
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("auto-name.example.com")
self.rd.actionDenyRadio.setChecked(True)
# Call save_rule directly to test name generation
result, error = self.rd.save_rule()
assert result == True
# Name should be auto-generated using slugify
assert self.rd.rule.name != ""
assert "deny" in self.rd.rule.name.lower() or "auto-name" in self.rd.rule.name.lower()
def test_checkbox_enables_field(self, qtbot):
"""Test that checking a checkbox enables its associated field."""
qtbot.addWidget(self.rd)
# Initially fields should be disabled
assert self.rd.procLine.isEnabled() == False
assert self.rd.dstPortLine.isEnabled() == False
assert self.rd.dstHostLine.isEnabled() == False
# Check boxes to enable fields
self.rd.procCheck.setChecked(True)
assert self.rd.procLine.isEnabled() == True
self.rd.dstPortCheck.setChecked(True)
assert self.rd.dstPortLine.isEnabled() == True
self.rd.dstHostCheck.setChecked(True)
assert self.rd.dstHostLine.isEnabled() == True
# Uncheck to disable
self.rd.procCheck.setChecked(False)
assert self.rd.procLine.isEnabled() == False
def test_rule_description(self, qtbot):
"""Test rule description field is saved."""
qtbot.addWidget(self.rd)
re_constants.WORK_MODE = re_constants.ADD_RULE
re_utils.reset_state(self.rd)
self.rd.statusLabel.setText("")
self.rd.ruleNameEdit.setText("test-description")
self.rd.ruleDescEdit.setPlainText("This is a test rule description")
self.rd.dstHostCheck.setChecked(True)
self.rd.dstHostLine.setText("desc.example.com")
# Click save directly
qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.StandardButton.Save), QtCore.Qt.MouseButton.LeftButton)
assert self.rd.statusLabel.text() == ""
assert self.rd.rule.description == "This is a test rule description"