From 3f028dcceb002aeca731fb8190e40b43c2f638ea Mon Sep 17 00:00:00 2001 From: Federico Foschini Date: Mon, 11 Mar 2024 16:27:20 +0100 Subject: [PATCH 1/4] Example test and update check_value --- routing_test.py | 7 +++++++ routingfilter/filters/filters.py | 6 ++++-- test_data/test_customer_1.json | 24 ++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 test_data/test_customer_1.json diff --git a/routing_test.py b/routing_test.py index d696629..2b6b388 100644 --- a/routing_test.py +++ b/routing_test.py @@ -124,6 +124,13 @@ def test_routing_history(self): self.assertTrue("Workshop" in self.test_event_1["certego"]["routing_history"]) self.assertTrue("TyreFit" in self.test_event_1["certego"]["routing_history"]) + def test_routing_history_no_customer(self): + self.routing.load_from_dicts([load_test_data("test_rule_24_routing_history"), load_test_data("test_customer_1")]) + self.routing.match(self.test_event_1) + self.routing.match(self.test_event_1, type_="customers") + self.assertTrue(self.test_event_1["certego"]["routing_history"]["Workshop"]) + self.assertFalse(self.test_event_1["certego"]["routing_history"]["customer"]) #TODO FIX Customer should never be on history + def test_routing_history_stream_none(self): self.routing.load_from_dicts([load_test_data("test_rule_1_equals")]) self.routing.match(self.test_event_1) diff --git a/routingfilter/filters/filters.py b/routingfilter/filters/filters.py index e2b22e1..8d37f3a 100644 --- a/routingfilter/filters/filters.py +++ b/routingfilter/filters/filters.py @@ -283,6 +283,7 @@ def _check_value(self) -> Optional[Exception]: :return: none or error generated :rtype: Optional[Exception] """ + tmp = [] for value in self._value: try: value = IP(value) @@ -292,7 +293,8 @@ def _check_value(self) -> Optional[Exception]: except TypeError as e: self.logger.error(f"IP address (type error) error, during check of value {value} in list {self._value}. Error was: {e}.") raise ValueError(f"IP address check failed: type error for value {value}.") - return None + tmp.append(value) + self._value = tmp def match(self, event: DictQuery) -> bool: """ @@ -323,7 +325,7 @@ def _check_network(self, ip_address: str) -> bool: try: ip_address = IP(ip_address) for value in self._value: - if ip_address in IP(value): + if ip_address in value: return True except ValueError as e: self.logger.debug(f"Error in parsing IP address (value error): {e}. ") diff --git a/test_data/test_customer_1.json b/test_data/test_customer_1.json new file mode 100644 index 0000000..1b5ef93 --- /dev/null +++ b/test_data/test_customer_1.json @@ -0,0 +1,24 @@ +{ + "customers": { + "rules": { + "all": [ + { + "filters": [ + { + "type": "EQUALS", + "key": "wheel_model", + "description": "Carbon fiber wheels needs manual truing", + "value": [ + "Superlight", + "RacePro" + ] + } + ], + "customers": { + "customer": "Pamcagnolo" + } + } + ] + } + } +} \ No newline at end of file From eebb0eb2a3110e91f2fadeb52e579c415d5ce101 Mon Sep 17 00:00:00 2001 From: giorgia Date: Tue, 12 Mar 2024 11:56:14 +0100 Subject: [PATCH 2/4] updated check value and fixed customer certego.routing_history --- routing_test.py | 2 +- routingfilter/filters/filters.py | 106 ++++++++++++++++++++----------- routingfilter/filters/rule.py | 3 +- 3 files changed, 71 insertions(+), 40 deletions(-) diff --git a/routing_test.py b/routing_test.py index 2b6b388..b24dc61 100644 --- a/routing_test.py +++ b/routing_test.py @@ -129,7 +129,7 @@ def test_routing_history_no_customer(self): self.routing.match(self.test_event_1) self.routing.match(self.test_event_1, type_="customers") self.assertTrue(self.test_event_1["certego"]["routing_history"]["Workshop"]) - self.assertFalse(self.test_event_1["certego"]["routing_history"]["customer"]) #TODO FIX Customer should never be on history + self.assertNotIn("customer", self.test_event_1["certego"]["routing_history"]) def test_routing_history_stream_none(self): self.routing.load_from_dicts([load_test_data("test_rule_1_equals")]) diff --git a/routingfilter/filters/filters.py b/routingfilter/filters/filters.py index 8d37f3a..7af08d7 100644 --- a/routingfilter/filters/filters.py +++ b/routingfilter/filters/filters.py @@ -1,7 +1,7 @@ import logging import re from abc import ABC, abstractmethod -from typing import Optional +from typing import NoReturn, Optional import macaddress from IPy import IP @@ -19,14 +19,15 @@ def __init__(self, key, value, **kwargs): def match(self, event: DictQuery) -> bool: return NotImplemented - def _check_value(self) -> Optional[Exception]: + @abstractmethod + def _check_value(self) -> Exception | NoReturn: """ - Check if values in self._value are correct and raise an exception if they are incorrect. + Check if values in self._value are correct and raise an exception if they are incorrect. If necessary, it converts value in lower case. :return: no value or raise an exception - :rtype: Optional[Exception] + :rtype: NoReturn | Exception """ - return None + return NotImplemented class AllFilter(AbstractFilter): @@ -34,6 +35,9 @@ def __init__(self): key = value = [] super().__init__(key, value) + def _check_value(self) -> Exception | NoReturn: + return + def match(self, event: DictQuery) -> bool: """ Return always true. @@ -51,6 +55,9 @@ def __init__(self, key): value = [] super().__init__(key, value) + def _check_value(self) -> Exception | NoReturn: + return + def match(self, event: DictQuery) -> bool: """ Return True if one of the key exists in the event. @@ -83,6 +90,13 @@ class EqualFilter(AbstractFilter): def __init__(self, key, value): super().__init__(key, value) + def _check_value(self) -> Exception | NoReturn: + tmp = [] + for value in self._value: + value = value.lower() if isinstance(value, str) else str(value) + tmp.append(value) + self._value = tmp + def match(self, event: DictQuery): """ Check if at least a key matches at least one value. @@ -92,16 +106,12 @@ def match(self, event: DictQuery): :return: true if event matches, false otherwise :rtype: bool """ - filter_value = [] - for value in self._value: - value = value.lower() if isinstance(value, str) else str(value) - filter_value.append(value) for key in self._key: event_value = event.get(key, []) event_value = event_value if isinstance(event_value, list) else [event_value] for value in event_value: value = value.lower() if isinstance(value, str) else str(value) - if value in filter_value: + if value in self._value: return True return False @@ -120,6 +130,13 @@ def match(self, event: DictQuery) -> bool: class StartswithFilter(AbstractFilter): + def _check_value(self) -> Exception | NoReturn: + tmp = [] + for prefix in self._value: + prefix = str(prefix).lower() + tmp.append(prefix) + self._value = tmp + def match(self, event: DictQuery) -> bool: """ Return True if at least one event value corresponding to a key starts with one of the value. @@ -148,13 +165,19 @@ def _check_startswith(self, value: str) -> bool: """ value = value.lower() for prefix in self._value: - prefix = str(prefix).lower() if value.startswith(prefix): return True return False class EndswithFilter(AbstractFilter): + def _check_value(self) -> Exception | NoReturn: + tmp = [] + for suffix in self._value: + suffix = str(suffix).lower() + tmp.append(suffix) + self._value = tmp + def match(self, event: DictQuery) -> bool: """ Return True if at least one event value corresponding to a key ends with one of the value. @@ -172,7 +195,7 @@ def match(self, event: DictQuery) -> bool: return True return False - def _check_endswith(self, value): + def _check_endswith(self, value: str) -> bool: """ Check if the value end with one of the suffix given. @@ -183,13 +206,19 @@ def _check_endswith(self, value): """ value = value.lower() for suffix in self._value: - suffix = str(suffix).lower() - if str(value).endswith(suffix): + if value.endswith(suffix): return True return False class KeywordFilter(AbstractFilter): + def _check_value(self) -> Exception | NoReturn: + tmp = [] + for keyword in self._value: + keyword = str(keyword).lower() + tmp.append(keyword) + self._value = tmp + def match(self, event: DictQuery) -> bool: """ Return True if at least one value is present in the event value of corresponding key. @@ -216,16 +245,14 @@ def _check_keyword(self, value: str) -> bool: :return: true or false :rtype: bool """ - value = value.lower() for keyword in self._value: - keyword = str(keyword).lower() - if keyword in value: + if keyword in value.lower(): return True return False class RegexpFilter(AbstractFilter): - def _check_value(self) -> Optional[Exception]: + def _check_value(self) -> Exception | NoReturn: """ Check if values in self._value are valid regexes. @@ -238,7 +265,6 @@ def _check_value(self) -> Optional[Exception]: except re.error as e: self.logger.error(f"Invalid regex {value}, during check of value list {self._value}. Error message: {e}") raise ValueError(f"Regex check failed: error for value {value}. Error message: {e}") - return None def match(self, event: DictQuery) -> bool: """ @@ -276,7 +302,7 @@ class NetworkFilter(AbstractFilter): def __init__(self, key, value): super().__init__(key, value) - def _check_value(self) -> Optional[Exception]: + def _check_value(self) -> Exception | NoReturn: """ Check if the values in self._value are valid IP addresses. @@ -351,17 +377,20 @@ class DomainFilter(AbstractFilter): def __init__(self, key, value): super().__init__(key, value) - def _check_value(self) -> Optional[Exception]: + def _check_value(self) -> Exception | NoReturn: """ Check if values in self._value are string. :return: none or error generated :rtype: bool """ - for value in self._value: - if not isinstance(value, str): - raise ValueError(f"Domain check failed: value {value} is not a string.") - return None + tmp = [] + for domain in self._value: + if not isinstance(domain, str): + raise ValueError(f"Domain check failed: value {domain} is not a string.") + domain = str(domain).lower() + tmp.append(domain) + self._value = tmp def match(self, event: DictQuery) -> bool: """ @@ -390,8 +419,7 @@ def _check_domain(self, value: str) -> bool: """ value = value.lower() for domain in self._value: - domain = str(domain).lower() - if value == domain or str(value).endswith(f".{domain}"): + if value == domain or value.endswith(f".{domain}"): return True return False @@ -402,32 +430,32 @@ def __init__(self, key, value, comparator_type): self._check_comparator_type() super().__init__(key, value) - def _check_value(self) -> Optional[Exception]: + def _check_value(self) -> Exception | NoReturn: """ Check if values in self._value are float. :return: none or error generated - :rtype: Optional[Exception] + :rtype: Exception | NoReturn """ + tmp = [] for value in self._value: try: - float(value) + tmp.append(float(value)) except ValueError: self.logger.error(f"Comparator check failed: value {value} of list {self._value} is not a float") raise ValueError(f"Comparator check failed: value {value} is not a float") - return None + self._value = tmp - def _check_comparator_type(self) -> Optional[Exception]: + def _check_comparator_type(self) -> Exception | NoReturn: """ Check if comparator is valid. :return: none or error generated - :rtype: Optional[Exception] + :rtype: Exception | NoReturn """ if self._comparator_type not in ["GREATER", "LESS", "GREATER_EQ", "LESS_EQ"]: self.logger.error(f"Comparator check failed: value {self._comparator_type} is not valid.") raise ValueError(f"Comparator type check failed. {self._comparator_type} is not a valid comparator.") - return None def match(self, event: DictQuery) -> bool: """ @@ -457,7 +485,6 @@ def _compare(self, value: float) -> bool: """ for term in self._value: try: - term = float(term) value = float(value) except ValueError as e: self.logger.debug(f"Error in parsing value to float in comparator filter: {e}. ") @@ -482,19 +509,22 @@ class TypeofFilter(AbstractFilter): def __init__(self, key, value): super().__init__(key, value) - def _check_value(self) -> Optional[Exception]: + def _check_value(self) -> Exception | NoReturn: """ Check if value is a correct type. :return: no value or raised an exception - :rtype: Optional[Exception] + :rtype: NoReturn | Exception """ valid_type = ["str", "int", "float", "bool", "list", "dict", "ip", "mac"] + tmp = [] for value in self._value: + value = str(value).lower() if value not in valid_type: self.logger.error(f"Type check failed: value {value} of list {self._value} is invalid.") raise ValueError(f"Type check failed: value {value} is invalid.") - return None + tmp.append(value) + self._value = tmp def match(self, event: DictQuery) -> bool: """ diff --git a/routingfilter/filters/rule.py b/routingfilter/filters/rule.py index 07e6542..eed15b1 100644 --- a/routingfilter/filters/rule.py +++ b/routingfilter/filters/rule.py @@ -48,7 +48,8 @@ def match(self, event: DictQuery) -> Results | None: if key in routing_history: output_copy.pop(key) else: - routing_history.update({key: now}) + if key != "customer": + routing_history.update({key: now}) results = Results(rules=self.uid, output=output_copy) return results From 0bf89eae23fc59bb6d1fdc956dd58731b62be559 Mon Sep 17 00:00:00 2001 From: giorgia Date: Tue, 12 Mar 2024 12:09:27 +0100 Subject: [PATCH 3/4] updated check value and fixed customer certego.routing_history --- routingfilter/filters/filters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/routingfilter/filters/filters.py b/routingfilter/filters/filters.py index 7af08d7..c0a0f25 100644 --- a/routingfilter/filters/filters.py +++ b/routingfilter/filters/filters.py @@ -259,12 +259,14 @@ def _check_value(self) -> Exception | NoReturn: :return: none or error generated: :rtype: Optional[Exception] """ + tmp = [] for value in self._value: try: - re.compile(value) + tmp.append(re.compile(value)) except re.error as e: self.logger.error(f"Invalid regex {value}, during check of value list {self._value}. Error message: {e}") raise ValueError(f"Regex check failed: error for value {value}. Error message: {e}") + self._value = tmp def match(self, event: DictQuery) -> bool: """ @@ -293,7 +295,7 @@ def _check_regex(self, value: str) -> bool: :rtype: bool """ for regex in self._value: - if re.search(regex, value): + if regex.search(value): return True return False From fc1d1f009b6fceb7dba3b4e227ec56ecd9c67b81 Mon Sep 17 00:00:00 2001 From: giorgia Date: Tue, 12 Mar 2024 12:16:56 +0100 Subject: [PATCH 4/4] updated check value and fixed customer certego.routing_history --- CHANGELOG.md | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75e77aa..de526a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ ## 2.2.x +### 2.2.9 +#### Bugfix +* Fixed customer routing history +#### Changes +* Updated _check_value() method in filters ### 2.2.8 #### Bugfix * Fixed a bug when an existing fields didn't match diff --git a/setup.py b/setup.py index 1ba2a04..df8715d 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="routingfilter", - version="2.2.8", + version="2.2.9", packages=find_packages(include=["routingfilter", "routingfilter.*"]), include_package_data=True, install_requires=["IPy~=1.1", "macaddress~=2.0.2"],