Skip to content

Commit 4164ac4

Browse files
committed
Merge remote-tracking branch 'origin/pr/447'
* origin/pr/447: straight forward placeholder firewall: Add comment field to firewall rules
2 parents 940f4ae + d4db7b5 commit 4164ac4

5 files changed

Lines changed: 169 additions & 4 deletions

File tree

qubesmanager/firewall.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ def try_to_create_rule(self):
107107
"invalid.".format(parsed_service)))
108108
return False
109109

110+
comment_text = self.commentLineEdit.text().strip()
111+
if comment_text:
112+
rule.comment = comment_text
113+
110114
if self.model.current_row is not None:
111115
self.model.set_child(self.model.current_row, rule)
112116
else:
@@ -173,7 +177,12 @@ def __init__(self, parent=None):
173177
self.current_row = None
174178
self.current_dialog = None
175179

176-
self.__column_names = {0: "Address", 1: "Port/Service", 2: "Protocol", }
180+
self.__column_names = {
181+
0: "Address",
182+
1: "Port/Service",
183+
2: "Protocol",
184+
3: "Comment",
185+
}
177186
self.__services = []
178187

179188
self.port_range_pattern = re.compile(r'\d+-\d+')
@@ -243,6 +252,13 @@ def get_column_string(self, col, rule):
243252
if rule.proto is None:
244253
return "any"
245254
return str(rule.proto)
255+
256+
# Comment
257+
if col == 3:
258+
if rule.comment is None:
259+
return ""
260+
return str(rule.comment)
261+
246262
return "unknown"
247263

248264
def get_firewall_conf(self, vm):
@@ -392,6 +408,8 @@ def populate_edit_dialog(self, dialog, row):
392408
dialog.udp_radio.setChecked(True)
393409
else:
394410
dialog.any_radio.setChecked(True)
411+
comment = self.get_column_string(3, self.children[row])
412+
dialog.commentLineEdit.setText(comment)
395413

396414
def run_rule_dialog(self, dialog, row=None):
397415
self.current_row = row

qubesmanager/i18n/qubesmanager_en.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -793,6 +793,21 @@ p, li { white-space: pre-wrap; }
793793
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port/service can be provided as either port number (e.g. 122), port range (1024-1234) or service name (e.g. smtp) . For full list of services known, see /etc/services in dom0.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
794794
<translation type="unfinished"></translation>
795795
</message>
796+
<message>
797+
<location filename="../ui_newfwruledlg.py" line="105"/>
798+
<source>Comment</source>
799+
<translation type="unfinished"></translation>
800+
</message>
801+
<message>
802+
<location filename="../ui_newfwruledlg.py" line="106"/>
803+
<source>Optional note for your own reference.</source>
804+
<translation type="unfinished"></translation>
805+
</message>
806+
<message>
807+
<location filename="../ui_newfwruledlg.py" line="107"/>
808+
<source>Optional note (e.g. reason for this rule)</source>
809+
<translation type="unfinished"></translation>
810+
</message>
796811
</context>
797812
<context>
798813
<name>QubeManager</name>

qubesmanager/i18n/qubesmanager_es.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,21 @@ p, li { white-space: pre-wrap; }
815815
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Port/service can be provided as either port number (e.g. 122), port range (1024-1234) or service name (e.g. smtp) . For full list of services known, see /etc/services in dom0.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
816816
<translation type="unfinished"></translation>
817817
</message>
818+
<message>
819+
<location filename="../ui_newfwruledlg.py" line="105"/>
820+
<source>Comment</source>
821+
<translation type="unfinished"></translation>
822+
</message>
823+
<message>
824+
<location filename="../ui_newfwruledlg.py" line="106"/>
825+
<source>Optional note for your own reference.</source>
826+
<translation type="unfinished"></translation>
827+
</message>
828+
<message>
829+
<location filename="../ui_newfwruledlg.py" line="107"/>
830+
<source>Optional note (e.g. reason for this rule)</source>
831+
<translation type="unfinished"></translation>
832+
</message>
818833
</context>
819834
<context>
820835
<name>QubeManager</name>

qubesmanager/tests/test_vm_settings.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1459,6 +1459,42 @@ def test_305_firewall_edit_rule(settings_fixture):
14591459
assert expected_call in settings_window.qubesapp.actual_calls
14601460

14611461

1462+
@check_errors
1463+
@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True)
1464+
def test_305b_firewall_add_rule_with_comment(settings_fixture):
1465+
settings_window, page, vm_name = settings_fixture
1466+
1467+
assert settings_window.policy_deny_radio_button.isChecked()
1468+
1469+
settings_window.new_rule_button.click()
1470+
settings_window.fw_model.current_dialog.addressComboBox.setCurrentText(
1471+
"example.com"
1472+
)
1473+
settings_window.fw_model.current_dialog.commentLineEdit.setText(
1474+
"temporary access for testing"
1475+
)
1476+
settings_window.fw_model.current_dialog.buttonBox.button(
1477+
QtWidgets.QDialogButtonBox.StandardButton.Ok
1478+
).click()
1479+
1480+
expected_call = (
1481+
"test-vm-set",
1482+
"admin.vm.firewall.Set",
1483+
None,
1484+
b"action=accept dsthost=qubes-os.org\n"
1485+
b"action=accept dsthost=example.com"
1486+
b" comment=temporary access for testing\n"
1487+
b"action=accept specialtarget=dns\n"
1488+
b"action=accept proto=icmp\n"
1489+
b"action=drop\n",
1490+
)
1491+
assert expected_call not in settings_window.qubesapp.actual_calls
1492+
settings_window.qubesapp.expected_calls[expected_call] = b"0\x00"
1493+
1494+
settings_window.accept()
1495+
1496+
assert expected_call in settings_window.qubesapp.actual_calls
1497+
14621498
@check_errors
14631499
@pytest.mark.parametrize("settings_fixture", ["test-vm-set"], indirect=True)
14641500
def test_306_firewall_unlimit(settings_fixture):
@@ -1494,6 +1530,66 @@ def test_306_firewall_unlimit(settings_fixture):
14941530
assert expected_call in settings_window.qubesapp.actual_calls
14951531

14961532

1533+
@check_errors
1534+
@mock.patch("subprocess.check_output")
1535+
def test_306b_firewall_preserve_cli_comment(mock_subprocess, qapp, test_qubes_app):
1536+
"""Rules created via CLI with comments should survive GUI round-trip."""
1537+
mock_subprocess.return_value = b""
1538+
1539+
fw_rules = [
1540+
{"action": "accept", "dsthost": "gitlab.com",
1541+
"comment": "IP for gitlab at the time"},
1542+
{"action": "accept", "specialtarget": "dns"},
1543+
{"action": "accept", "proto": "icmp"},
1544+
{"action": "drop"},
1545+
]
1546+
1547+
test_qubes_app._qubes["test-vm-comment"] = MockQube(
1548+
name="test-vm-comment",
1549+
qapp=test_qubes_app,
1550+
label="green",
1551+
firewall_rules=fw_rules,
1552+
)
1553+
test_qubes_app.expected_calls[
1554+
("test-vm-comment", "admin.vm.notes.Get", None, None)
1555+
] = b"0\x00"
1556+
test_qubes_app.update_vm_calls()
1557+
1558+
settings_window = vm_settings.VMSettingsWindow(
1559+
"test-vm-comment", "firewall", qapp, test_qubes_app
1560+
)
1561+
1562+
# Comment should be visible in the model
1563+
assert settings_window.fw_model.get_column_string(
1564+
3, settings_window.fw_model.children[0]
1565+
) == "IP for gitlab at the time"
1566+
1567+
# Edit the rule (opens dialog, accept it unchanged) to trigger fw_changed
1568+
settings_window.rulesTreeView.setCurrentIndex(
1569+
settings_window.fw_model.index(0, 0))
1570+
settings_window.edit_rule_button.click()
1571+
settings_window.fw_model.current_dialog.buttonBox.button(
1572+
QtWidgets.QDialogButtonBox.StandardButton.Ok
1573+
).click()
1574+
1575+
# The comment must survive the edit round-trip
1576+
expected_call = (
1577+
"test-vm-comment",
1578+
"admin.vm.firewall.Set",
1579+
None,
1580+
b"action=accept dsthost=gitlab.com"
1581+
b" comment=IP for gitlab at the time\n"
1582+
b"action=accept specialtarget=dns\n"
1583+
b"action=accept proto=icmp\n"
1584+
b"action=drop\n",
1585+
)
1586+
settings_window.qubesapp.expected_calls[expected_call] = b"0\x00"
1587+
1588+
settings_window.accept()
1589+
1590+
assert expected_call in settings_window.qubesapp.actual_calls
1591+
1592+
14971593
@check_errors
14981594
@mock.patch("subprocess.check_output")
14991595
def test_307_open_with_limit(mock_subprocess, qapp, test_qubes_app):

ui/newfwruledlg.ui

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<x>0</x>
1111
<y>0</y>
1212
<width>381</width>
13-
<height>193</height>
13+
<height>225</height>
1414
</rect>
1515
</property>
1616
<property name="windowTitle">
@@ -105,8 +105,28 @@
105105
<bool>true</bool>
106106
</property>
107107
</widget>
108-
</item>
109-
<item row="1" column="1">
108+
</item>
109+
<item row="3" column="0">
110+
<widget class="QLabel" name="label_5">
111+
<property name="text">
112+
<string>Comment</string>
113+
</property>
114+
</widget>
115+
</item>
116+
<item row="3" column="1" colspan="3">
117+
<widget class="QLineEdit" name="commentLineEdit">
118+
<property name="toolTip">
119+
<string>Comments are for user reference only</string>
120+
</property>
121+
<property name="placeholderText">
122+
<string>Comment (optional)</string>
123+
</property>
124+
<property name="maxLength">
125+
<number>512</number>
126+
</property>
127+
</widget>
128+
</item>
129+
<item row="1" column="1">
110130
<widget class="QRadioButton" name="tcp_radio">
111131
<property name="sizePolicy">
112132
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
@@ -152,6 +172,7 @@
152172
<tabstop>udp_radio</tabstop>
153173
<tabstop>any_radio</tabstop>
154174
<tabstop>serviceComboBox</tabstop>
175+
<tabstop>commentLineEdit</tabstop>
155176
<tabstop>buttonBox</tabstop>
156177
</tabstops>
157178
<resources/>

0 commit comments

Comments
 (0)