diff --git a/ui/opensnitch/dialogs/prompt/utils.py b/ui/opensnitch/dialogs/prompt/utils.py index a362748cf9..98f1f45d8a 100644 --- a/ui/opensnitch/dialogs/prompt/utils.py +++ b/ui/opensnitch/dialogs/prompt/utils.py @@ -1,5 +1,6 @@ from slugify import slugify import os +import re import ipaddress from PyQt6.QtCore import QCoreApplication as QC @@ -23,7 +24,20 @@ def get_rule_name(rule, is_list): rule_temp_name = "%s-list" % rule_temp_name else: rule_temp_name = "%s-simple" % rule_temp_name - rule_temp_name = slugify("%s %s" % (rule_temp_name, rule.operator.data)) + + rule_data = rule.operator.data + if rule.operator.type == Config.RULE_TYPE_REGEXP: + # Alnum character classes (e.g. [0-9A-Za-z]) don't carry any + # identifying info for a rule name, and slugify() lowercases + # everything, so a mixed-case range like [0-9A-Za-z] turns into + # "0-9a-za-z", which looks like a duplicated a-z class. Strip them + # out instead of slugifying them (#1587). + # Only match classes made up of digits/letters/hyphens, so we don't + # touch escaped brackets or other regexp syntax a user may have + # typed by hand (e.g. r'\[test\]' or r'[a-z\]]'). + rule_data = re.sub(r'\[[0-9A-Za-z\-]+\]', '', rule_data) + + rule_temp_name = slugify("%s %s" % (rule_temp_name, rule_data)) return rule_temp_name[:128] diff --git a/ui/tests/dialogs/test_prompt_utils.py b/ui/tests/dialogs/test_prompt_utils.py new file mode 100644 index 0000000000..f166bee3e6 --- /dev/null +++ b/ui/tests/dialogs/test_prompt_utils.py @@ -0,0 +1,52 @@ +# +# pytest -v tests/dialogs/test_prompt_utils.py +# + +import opensnitch.proto as proto +ui_pb2, ui_pb2_grpc = proto.import_() + +from opensnitch.config import Config +from opensnitch.dialogs.prompt import utils + + +def _make_rule(data, rule_type=Config.RULE_TYPE_REGEXP): + rule = ui_pb2.Rule(name="user.choice") + rule.action = Config.ACTION_ALLOW + rule.duration = Config.DURATION_ONCE + rule.operator.type = rule_type + rule.operator.operand = Config.OPERAND_PROCESS_PATH + rule.operator.data = data + return rule + + +class TestPromptUtils(): + + def test_get_rule_name_appimage_regexp_no_duplicated_class(self): + """ Regexps built for appimage/snap paths use a mixed-case character + class ([0-9A-Za-z]). slugify() lowercases everything, which used to + turn it into "0-9a-za-z" in the rule name, looking like a duplicated + a-z class (#1587). + """ + rule = _make_rule(r'^/tmp/\.mount_handy_[0-9A-Za-z]+\/.*handy$') + name = utils.get_rule_name(rule, False) + + assert "0-9a-za-z" not in name + assert "handy" in name + + def test_get_rule_name_simple_rule_unaffected(self): + """ Non-regexp rules aren't touched by the character-class stripping. + """ + rule = _make_rule("www.google.com", rule_type=Config.RULE_TYPE_SIMPLE) + name = utils.get_rule_name(rule, False) + + assert name == "allow-once-simple-www-google-com" + + def test_get_rule_name_escaped_brackets_not_mangled(self): + """ A hand-typed regexp matching a literal bracket must not be + treated as a character class and stripped. + """ + rule = _make_rule(r'^/opt/\[legacy\]/app$') + name = utils.get_rule_name(rule, False) + + assert "legacy" in name + assert "app" in name