From ce9cfdfb1875bd9213b565c879060f8aa94de48d Mon Sep 17 00:00:00 2001 From: "nazar.gesyk" Date: Fri, 8 Dec 2023 15:46:45 +0200 Subject: [PATCH 1/2] Add new operators support --- translator/app/translator/const.py | 3 ++ translator/app/translator/core/render.py | 33 ++++++++++--- translator/app/translator/core/tokenizer.py | 7 ++- .../platforms/athena/renders/athena.py | 31 +++++++++--- .../translator/platforms/athena/tokenizer.py | 4 +- .../translator/platforms/base/lucene/const.py | 11 +++++ .../platforms/base/lucene/renders/lucene.py | 31 +++++++++--- .../platforms/base/lucene/tokenizer.py | 20 ++++++-- .../platforms/base/spl/renders/spl.py | 31 +++++++++--- .../platforms/chronicle/renders/chronicle.py | 34 +++++++++---- .../chronicle/renders/chronicle_rule.py | 31 +++++++++--- .../platforms/logscale/renders/logscale.py | 32 ++++++++++--- .../platforms/logscale/tokenizer.py | 2 +- .../microsoft/renders/microsoft_sentinel.py | 48 +++++++++++++++---- .../platforms/microsoft/tokenizer.py | 6 ++- .../opensearch/renders/opensearch.py | 33 ++++++++++--- .../platforms/qradar/renders/qradar.py | 41 +++++++++++++--- .../translator/platforms/qradar/tokenizer.py | 6 +-- .../platforms/sigma/renders/sigma.py | 3 +- 19 files changed, 325 insertions(+), 82 deletions(-) create mode 100644 translator/app/translator/platforms/base/lucene/const.py diff --git a/translator/app/translator/const.py b/translator/app/translator/const.py index 03d94491..c4da7cad 100644 --- a/translator/app/translator/const.py +++ b/translator/app/translator/const.py @@ -1,5 +1,8 @@ from os.path import abspath, dirname +from typing import Union, List APP_PATH = dirname(abspath(__file__)) CTI_MIN_LIMIT_QUERY = 10000 + +DEFAULT_VALUE_TYPE = Union[Union[int, str, List[int], List[str]]] diff --git a/translator/app/translator/core/render.py b/translator/app/translator/core/render.py index ac910521..9162c8e5 100644 --- a/translator/app/translator/core/render.py +++ b/translator/app/translator/core/render.py @@ -20,6 +20,7 @@ from abc import ABC from typing import Union, List, Dict +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.core.exceptions.core import NotImplementedException, StrictPlatformException from app.translator.core.exceptions.parser import UnsupportedOperatorException from app.translator.core.functions import PlatformFunctions @@ -37,6 +38,11 @@ class BaseQueryFieldValue(ABC): def __init__(self, or_token): self.field_value = { OperatorType.EQ: self.equal_modifier, + OperatorType.LT: self.less_modifier, + OperatorType.LTE: self.less_or_equal_modifier, + OperatorType.GT: self.greater_modifier, + OperatorType.GTE: self.greater_or_equal_modifier, + OperatorType.NEQ: self.not_equal_modifier, OperatorType.CONTAINS: self.contains_modifier, OperatorType.ENDSWITH: self.endswith_modifier, OperatorType.STARTSWITH: self.startswith_modifier, @@ -45,22 +51,37 @@ def __init__(self, or_token): } self.or_token = f" {or_token} " - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: raise NotImplementedException - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: raise NotImplementedException - def endswith_modifier(self, field, value): + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: raise NotImplementedException - def startswith_modifier(self, field, value): + def greater_modifier(self, field: str, value: Union[int, str]) -> str: raise NotImplementedException - def regex_modifier(self, field, value): + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: raise NotImplementedException - def keywords(self, field, value): + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + raise NotImplementedException + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + raise NotImplementedException + + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + raise NotImplementedException + + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + raise NotImplementedException + + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + raise NotImplementedException + + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: raise NotImplementedException def apply_field_value(self, field, operator, value): diff --git a/translator/app/translator/core/tokenizer.py b/translator/app/translator/core/tokenizer.py index 5d72fe38..54002f96 100644 --- a/translator/app/translator/core/tokenizer.py +++ b/translator/app/translator/core/tokenizer.py @@ -46,7 +46,7 @@ class QueryTokenizer(BaseTokenizer): field_pattern = r"(?P[a-zA-Z\._\-]+)" operator_pattern = r"\s?(?Pand|or|not|AND|OR|NOT)\s?" field_value_pattern = r"""^___field___\s*___match_operator___\s*___value___""" - match_operator_pattern = r"""(?:___field___\s?(?Pilike|contains|endswith|startswith|in|==|=|=~|!=|:|\:))\s?""" + match_operator_pattern = r"""(?:___field___\s?(?Pilike|contains|endswith|startswith|in|>=|<=|==|>|<|=~|!=|=|:|\:))\s?""" base_value_pattern = r"(?:___value_pattern___)" _value_pattern = r"""(?:\"|\')*(?P[:a-zA-Z\*0-9=+%#\-_\/\\'\,.&^@!\(\s]*)(?:\*|\'|\"|\s|\$)*""" value_pattern = base_value_pattern.replace('___value_pattern___', _value_pattern) @@ -60,6 +60,11 @@ class QueryTokenizer(BaseTokenizer): operators_map = { "=": OperatorType.EQ, "in": OperatorType.EQ, + "<": OperatorType.LT, + "<=": OperatorType.LTE, + ">": OperatorType.GT, + ">=": OperatorType.GTE, + "!=": OperatorType.NEQ, "contains": OperatorType.CONTAINS, "startswith": OperatorType.STARTSWITH, "endswith": OperatorType.ENDSWITH diff --git a/translator/app/translator/platforms/athena/renders/athena.py b/translator/app/translator/platforms/athena/renders/athena.py index add893cb..c11fc0af 100644 --- a/translator/app/translator/platforms/athena/renders/athena.py +++ b/translator/app/translator/platforms/athena/renders/athena.py @@ -16,7 +16,9 @@ limitations under the License. ----------------------------------------------------------------- """ +from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.platforms.athena.const import athena_details from app.translator.platforms.athena.mapping import AthenaMappings, athena_mappings from app.translator.core.exceptions.render import UnsupportedRenderMethod @@ -28,32 +30,49 @@ class AthenaFieldValue(BaseQueryFieldValue): details: PlatformDetails = athena_details - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.equal_modifier(field=field, value=v) for v in value])})" return f"{field} = '{value}'" - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f"{field} < '{value}'" + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f"{field} <= '{value}'" + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f"{field} > '{value}'" + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f"{field} >= '{value}'" + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + return f"{field} != '{value}'" + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f"{field} ILIKE '%{value}%' ESCAPE '\\'" - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f"{field} ILIKE '%{value}' ESCAPE '\\'" - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f"{field} ILIKE '{value}%' ESCAPE '\\'" - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f"{field} ILIKE '{value}' ESCAPE '\\'" - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: raise UnsupportedRenderMethod(platform_name=self.details.name, method="Keywords") diff --git a/translator/app/translator/platforms/athena/tokenizer.py b/translator/app/translator/platforms/athena/tokenizer.py index 52c28a2d..e8b7095f 100644 --- a/translator/app/translator/platforms/athena/tokenizer.py +++ b/translator/app/translator/platforms/athena/tokenizer.py @@ -27,7 +27,7 @@ class AthenaTokenizer(QueryTokenizer): field_pattern = r'(?P"[a-zA-Z\._\-\s]+"|[a-zA-Z\._\-]+)' - match_operator_pattern = r"""(?:___field___\s?(?Plike|in|=|>|<|>=|<=|<>|!=))\s?""" + match_operator_pattern = r"""(?:___field___\s?(?Plike|in|<=|>=|==|>|<|<>|!=|=))\s?""" num_value_pattern = r"(?P\d+(?:\.\d+)*)\s*" bool_value_pattern = r"(?Ptrue|false)\s*" single_quotes_value_pattern = r"""'(?P(?:[:a-zA-Z\*0-9=+%#\-\/\\,_".$&^@!\(\)\{\}\s]|'')*)'""" @@ -66,7 +66,7 @@ def search_field_value(self, query): should_process_value_wildcard_symbols = self.should_process_value_wildcard_symbols(operator) query, operator, value = self.search_value(query=query, operator=operator, field_name=field_name) - operator_token = Identifier(token_type=OperatorType.EQ) + operator_token = Identifier(token_type=operator) if should_process_value_wildcard_symbols: value, operator_token = self.process_value_wildcard_symbols( value=value, diff --git a/translator/app/translator/platforms/base/lucene/const.py b/translator/app/translator/platforms/base/lucene/const.py new file mode 100644 index 00000000..4a10486e --- /dev/null +++ b/translator/app/translator/platforms/base/lucene/const.py @@ -0,0 +1,11 @@ + +COMPARISON_OPERATORS_MAP = { + ":[* TO": { + "replace": [":\[\*\sTO"], + "default_op": "<=" + }, + ":[": { + "replace": [":\[", "TO\s\*"], + "default_op": ">=" + }, +} diff --git a/translator/app/translator/platforms/base/lucene/renders/lucene.py b/translator/app/translator/platforms/base/lucene/renders/lucene.py index 558c57dc..ef81bf37 100644 --- a/translator/app/translator/platforms/base/lucene/renders/lucene.py +++ b/translator/app/translator/platforms/base/lucene/renders/lucene.py @@ -18,6 +18,7 @@ """ from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.core.render import BaseQueryRender from app.translator.core.render import BaseQueryFieldValue @@ -27,39 +28,57 @@ class LuceneFieldValue(BaseQueryFieldValue): def apply_value(self, value: Union[str, int]): return value - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(self.apply_value(f'{v}') for v in value) return f"{field}:({values})" return f'{field}:{self.apply_value(value)}' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:<{self.apply_value(value)}' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:[* TO {self.apply_value(value)}]' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:>{self.apply_value(value)}' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:[{self.apply_value(value)} TO *]' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + values = self.or_token.join(self.apply_value(f'{v}') for v in value) + return f"NOT ({field} = ({values})" + return f'NOT ({field} = {self.apply_value(value)})' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(self.apply_value(f'*{v}*') for v in value) return f"{field}:({values})" prepared_value = self.apply_value(f"*{value}*") return f'{field}:{prepared_value}' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(self.apply_value(f'*{v}') for v in value) return f"{field}:({values})" prepared_value = self.apply_value(f"*{value}") return f'{field}:{prepared_value}' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(self.apply_value(f'{v}*') for v in value) return f"{field}:({values})" prepared_value = self.apply_value(f"{value}*") return f'{field}:{prepared_value}' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f'{field}:/{value}/' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return self.apply_value(f"*{value}*") diff --git a/translator/app/translator/platforms/base/lucene/tokenizer.py b/translator/app/translator/platforms/base/lucene/tokenizer.py index 5497ab8b..bb924fa6 100644 --- a/translator/app/translator/platforms/base/lucene/tokenizer.py +++ b/translator/app/translator/platforms/base/lucene/tokenizer.py @@ -26,12 +26,12 @@ from app.translator.core.tokenizer import QueryTokenizer from app.translator.core.custom_types.tokens import OperatorType from app.translator.tools.utils import get_match_group +from app.translator.platforms.base.lucene.const import COMPARISON_OPERATORS_MAP class LuceneTokenizer(QueryTokenizer, ANDLogicOperatorMixin): field_pattern = r"(?P[a-zA-Z\.\-_]+)" - match_operator_pattern = r"(?:___field___\s*(?P:))\s*" - + match_operator_pattern = r"(?:___field___\s*(?P:\[\*\sTO|:\[|:<|:>|:))\s*" num_value_pattern = r"(?P\d+(?:\.\d+)*)\s*" double_quotes_value_pattern = r'"(?P(?:[:a-zA-Z\*0-9=+%#\-_/,\'\.$&^@!\(\)\{\}\s]|\\\"|\\)*)"\s*' no_quotes_value_pattern = r"(?P(?:[a-zA-Z\*0-9=%#_/,\'\.$@]|\\\"|\\\\)+)\s*" @@ -46,6 +46,8 @@ class LuceneTokenizer(QueryTokenizer, ANDLogicOperatorMixin): operators_map = { ":": OperatorType.EQ, + ":>": OperatorType.GT, + ":<": OperatorType.LT } def __init__(self): @@ -77,9 +79,11 @@ def get_operator_and_value(self, match: re.Match, operator: str = OperatorType.E elif (d_q_value := get_match_group(match, group_name='d_q_value')) is not None: return operator, d_q_value - return super().get_operator_and_value(match) + return super().get_operator_and_value(match, operator) def search_value(self, query: str, operator: str, field_name: str) -> Tuple[str, str, Union[str, List[str]]]: + if operator in COMPARISON_OPERATORS_MAP.keys(): + return self.search_value_gte_lte(query, operator, field_name) check_pattern = self.multi_value_check_pattern check_regex = check_pattern.replace('___field___', field_name).replace('___operator___', operator) if re.match(check_regex, query): @@ -96,11 +100,19 @@ def search_value(self, query: str, operator: str, field_name: str) -> Tuple[str, if field_value_search is None: raise TokenizerGeneralException(error=f"Value couldn't be found in query part: {query}") - operator, value = self.get_operator_and_value(field_value_search) + operator, value = self.get_operator_and_value(field_value_search, self.map_operator(operator)) value = [self.clean_quotes(v) for v in re.split(r"\s+OR\s+", value)] if is_multi else value pos = field_value_search.end() return query[pos:], operator, value + def search_value_gte_lte(self, query: str, operator: str, field_name: str) -> Tuple[str, str, Union[str, List[str]]]: + query_list = query.split("]") + to_replace = [v for val in COMPARISON_OPERATORS_MAP.values() for v in val["replace"]] + to_replace.append(field_name) + regex = re.compile('|'.join(to_replace)) + value = re.sub(regex, '', query_list.pop(0)) + return "".join(query_list), COMPARISON_OPERATORS_MAP.get(operator, {}).get("default_op"), value.strip() + def search_keyword(self, query: str) -> Tuple[Keyword, str]: keyword_search = re.search(self.keyword_pattern, query) _, value = self.get_operator_and_value(keyword_search) diff --git a/translator/app/translator/platforms/base/spl/renders/spl.py b/translator/app/translator/platforms/base/spl/renders/spl.py index cfcb7732..f68636c0 100644 --- a/translator/app/translator/platforms/base/spl/renders/spl.py +++ b/translator/app/translator/platforms/base/spl/renders/spl.py @@ -16,39 +16,58 @@ limitations under the License. ----------------------------------------------------------------- """ +from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.core.exceptions.render import UnsupportedRenderMethod from app.translator.core.render import BaseQueryRender, BaseQueryFieldValue class SplFieldValue(BaseQueryFieldValue): - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.equal_modifier(field=field, value=v) for v in value])})" return f'{field}="{value}"' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}<"{value}"' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}<="{value}"' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}>"{value}"' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}>="{value}"' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + return f'{field}!="{value}"' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.contains_modifier(field=field, value=v) for v in value])})" return f'{field}="*{value}*"' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.endswith_modifier(field=field, value=v) for v in value])})" return f'{field}="*{value}"' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.startswith_modifier(field=field, value=v) for v in value])})" return f'{field}="{value}*"' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return f'"{value}"' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: raise UnsupportedRenderMethod(platform_name=self.details.name, method="Regex Expression") diff --git a/translator/app/translator/platforms/chronicle/renders/chronicle.py b/translator/app/translator/platforms/chronicle/renders/chronicle.py index 609bb076..281134dc 100644 --- a/translator/app/translator/platforms/chronicle/renders/chronicle.py +++ b/translator/app/translator/platforms/chronicle/renders/chronicle.py @@ -16,8 +16,9 @@ limitations under the License. ----------------------------------------------------------------- """ -from typing import List +from typing import List, Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.core.mapping import SourceMapping from app.translator.core.models.functions.base import Function from app.translator.platforms.chronicle.const import chronicle_query_details @@ -34,32 +35,49 @@ class ChronicleFieldValue(BaseQueryFieldValue): def apply_field(field): return field - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): - return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" + return f"({self.or_token.join(self.equal_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = "{value}" nocase' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} < "{value}" nocase' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} <= "{value}" nocase' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} > "{value}" nocase' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} >= "{value}" nocase' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + return f'{self.apply_field(field)} != "{value}" nocase' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = /.*{value}.*/ nocase' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = /.*{value}$/ nocase' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = /^{value}.*/ nocase' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = /{value}/ nocase' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: raise UnsupportedRenderMethod(platform_name=self.details.name, method="Keywords") diff --git a/translator/app/translator/platforms/chronicle/renders/chronicle_rule.py b/translator/app/translator/platforms/chronicle/renders/chronicle_rule.py index 8bd3256b..06e912c7 100644 --- a/translator/app/translator/platforms/chronicle/renders/chronicle_rule.py +++ b/translator/app/translator/platforms/chronicle/renders/chronicle_rule.py @@ -18,7 +18,9 @@ """ import re +from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.platforms.chronicle.renders.chronicle import ChronicleFieldValue, ChronicleQueryRender from app.translator.platforms.chronicle.const import DEFAULT_CHRONICLE_SECURITY_RULE, chronicle_rule_details from app.translator.core.mapping import SourceMapping @@ -33,22 +35,39 @@ class ChronicleRuleFieldValue(ChronicleFieldValue): details: PlatformDetails = chronicle_rule_details - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): - return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" + return f"({self.or_token.join(self.equal_modifier(field=field, value=v) for v in value)})" return f'{self.apply_field(field)} = "{value}"' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} < "{value}"' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} <= "{value}"' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} > "{value}"' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{self.apply_field(field)} >= "{value}"' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + return f'{self.apply_field(field)} != "{value}"' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f're.regex({self.apply_field(field)}, `.*{value}.*`)' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f're.regex({self.apply_field(field)}, `.*{value}`)' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f're.regex({self.apply_field(field)}, `{value}.*`)' @@ -57,7 +76,7 @@ def startswith_modifier(self, field, value): def apply_field(field): return f"$e.{field}" - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f're.regex({self.apply_field(field)}, `{value}`)' diff --git a/translator/app/translator/platforms/logscale/renders/logscale.py b/translator/app/translator/platforms/logscale/renders/logscale.py index 207c4c64..55f6b559 100644 --- a/translator/app/translator/platforms/logscale/renders/logscale.py +++ b/translator/app/translator/platforms/logscale/renders/logscale.py @@ -18,6 +18,7 @@ """ from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.platforms.logscale.const import logscale_query_details from app.translator.platforms.logscale.functions import LogScaleFunctions, log_scale_functions from app.translator.platforms.logscale.mapping import LogScaleMappings, logscale_mappings @@ -30,37 +31,54 @@ class LogScaleFieldValue(BaseQueryFieldValue): details: PlatformDetails = logscale_query_details - def apply_value(self, value: Union[str, int]): + def apply_value(self, value: Union[str, int]) -> str: if isinstance(value, str) and '"' in value: value = value.translate(str.maketrans({'"': r'\"'})) return value - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.equal_modifier(field=field, value=v) for v in value)})" return f'{field}="{self.apply_value(value)}"' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}<"{self.apply_value(value)}"' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}<="{self.apply_value(value)}"' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}>"{self.apply_value(value)}"' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}>="{self.apply_value(value)}"' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + return f'{field}!="{self.apply_value(value)}"' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f'{field}="*{self.apply_value(value)}*"' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f'{field}="*{self.apply_value(value)}"' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f'{field}="{self.apply_value(value)}*"' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f'{field}="/{self.apply_value(value)}/"' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return f'"{self.apply_value(value)}"' diff --git a/translator/app/translator/platforms/logscale/tokenizer.py b/translator/app/translator/platforms/logscale/tokenizer.py index 255bc0c5..79cbcc21 100644 --- a/translator/app/translator/platforms/logscale/tokenizer.py +++ b/translator/app/translator/platforms/logscale/tokenizer.py @@ -28,7 +28,7 @@ class LogScaleTokenizer(QueryTokenizer, ANDLogicOperatorMixin): - match_operator_pattern = r"""(?:___field___\s?(?P=|!=))\s?""" + match_operator_pattern = r"""(?:___field___\s?(?P=|!=|>=|>|<=|<))\s?""" num_value_pattern = r"(?P\d+(?:\.\d+)*)\s*" double_quotes_value_pattern = r'"(?P(?:[:a-zA-Z\*0-9=+%#\-_/,\'\.$&^@!\(\)\{\}\s]|\\\"|\\)*)"\s*' re_value_pattern = r"/(?P[:a-zA-Z\*0-9=+%#\\\-_\,\"\'\.$&^@!\(\)\{\}\s?]+)/i?\s*" diff --git a/translator/app/translator/platforms/microsoft/renders/microsoft_sentinel.py b/translator/app/translator/platforms/microsoft/renders/microsoft_sentinel.py index 3b430aa4..7982504b 100644 --- a/translator/app/translator/platforms/microsoft/renders/microsoft_sentinel.py +++ b/translator/app/translator/platforms/microsoft/renders/microsoft_sentinel.py @@ -18,6 +18,7 @@ """ from typing import Union +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.platforms.microsoft.const import microsoft_sentinel_query_details from app.translator.platforms.microsoft.functions import MicrosoftFunctions, microsoft_sentinel_functions from app.translator.platforms.microsoft.mapping import MicrosoftSentinelMappings, microsoft_sentinel_mappings @@ -33,39 +34,66 @@ class MicrosoftSentinelFieldValue(BaseQueryFieldValue): def __escape_value(value: Union[int, str]) -> Union[int, str]: return value.replace("'", "''") if isinstance(value, str) else value - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, str): return f"{field} =~ @'{self.__escape_value(value)}'" elif isinstance(value, list): prepared_values = ", ".join(f"@'{self.__escape_value(v)}'" for v in value) operator = "in~" if all(isinstance(v, str) for v in value) else "in" - return f'{field} {operator} ({prepared_values})' - return f'{field} == {value}' + return f"{field} {operator} ({prepared_values})" + return f"{field} == {value}" + + def less_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f"{field} < {value}" + return f"{field} < '{value}'" + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f"{field} <= {value}" + return f"{field} <= '{value}'" + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f"{field} > {value}" + return f"{field} > '{value}'" + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f"{field} >= {value}" + return f"{field} >= '{value}'" + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + if isinstance(value, int): + return f"{field} !~ {value}" + return f"{field} !~ '{value}'" - def contains_modifier(self, field, value): + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f"{field} contains @'{self.__escape_value(value)}'" - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f"{field} endswith @'{self.__escape_value(value)}'" - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f"{field} startswith @'{self.__escape_value(value)}'" - def __regex_modifier(self, field, value): + def __regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: return f"{field} matches regex @'(?i){self.__escape_value(value)}'" - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.__regex_modifier(field=field, value=v) for v in value)})" - return f'({self.__regex_modifier(field=field, value=value)})' + return f"({self.__regex_modifier(field=field, value=value)})" - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return f"* contains @'{self.__escape_value(value)}'" diff --git a/translator/app/translator/platforms/microsoft/tokenizer.py b/translator/app/translator/platforms/microsoft/tokenizer.py index d41a1484..d44d130d 100644 --- a/translator/app/translator/platforms/microsoft/tokenizer.py +++ b/translator/app/translator/platforms/microsoft/tokenizer.py @@ -27,7 +27,7 @@ class MicrosoftSentinelTokenizer(QueryTokenizer, OperatorBasedMixin): field_pattern = r"(?P[a-zA-Z\.\-_]+)" - match_operator_pattern = r"""(?:___field___\s?(?Pcontains|endswith|startswith|in~|in|==|=~|!=|=))\s?""" + match_operator_pattern = r"""(?:___field___\s?(?Pcontains|endswith|startswith|in~|in|==|=~|!~|!=|>=|>|<=|<|=|<>))\s?""" bool_value_pattern = r"(?Ptrue|false)\s*" num_value_pattern = r"(?P\d+(?:\.\d+)*)\s*" double_quotes_value_pattern = r'@?"(?P(?:[:a-zA-Z\*0-9=+%#\-_/,\'\.$&^@!\(\)\{\}\s]|\\\"|\\\\)*)"\s*' @@ -42,7 +42,9 @@ class MicrosoftSentinelTokenizer(QueryTokenizer, OperatorBasedMixin): operators_map = { "==": OperatorType.EQ, "in~": OperatorType.EQ, - "=~": OperatorType.EQ + "=~": OperatorType.EQ, + "<>": OperatorType.NEQ, + "!~": OperatorType.NEQ } def __init__(self, *args, **kwargs): diff --git a/translator/app/translator/platforms/opensearch/renders/opensearch.py b/translator/app/translator/platforms/opensearch/renders/opensearch.py index 3f265044..823e0ecb 100644 --- a/translator/app/translator/platforms/opensearch/renders/opensearch.py +++ b/translator/app/translator/platforms/opensearch/renders/opensearch.py @@ -16,6 +16,9 @@ limitations under the License. ----------------------------------------------------------------- """ +from typing import Union + +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.platforms.base.lucene.renders.lucene import LuceneQueryRender, LuceneFieldValue from app.translator.platforms.opensearch.const import opensearch_query_details from app.translator.platforms.opensearch.mapping import OpenSearchMappings, opensearch_mappings @@ -25,36 +28,54 @@ class OpenSearchFieldValue(LuceneFieldValue): details: PlatformDetails = opensearch_query_details - def equal_modifier(self, field, value): + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(f'"{v}"' for v in value) return f"{field}:({values})" return f'{field}:"{value}"' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:<"{self.apply_value(value)}"' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:[* TO "{self.apply_value(value)}"]' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:>"{self.apply_value(value)}"' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + return f'{field}:["{self.apply_value(value)}" TO *]' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + values = self.or_token.join(self.apply_value(f'"{v}"') for v in value) + return f"NOT ({field} = ({values})" + return f'NOT ({field} = "{self.apply_value(value)}")' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(f'"*{v}*"' for v in value) return f"{field}:({values})" return f'{field}:"*{value}*"' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(f'"*{v}"' for v in value) return f"{field}:({values})" return f'{field}:"*{value}"' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): values = self.or_token.join(f'"{v}*"' for v in value) return f"{field}:({values})" return f'{field}:"{value}*"' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f'{field}:/{value}/"' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return f'"*{value}*"' diff --git a/translator/app/translator/platforms/qradar/renders/qradar.py b/translator/app/translator/platforms/qradar/renders/qradar.py index 13548e32..26367002 100644 --- a/translator/app/translator/platforms/qradar/renders/qradar.py +++ b/translator/app/translator/platforms/qradar/renders/qradar.py @@ -16,9 +16,9 @@ limitations under the License. ----------------------------------------------------------------- """ - from typing import Union, List +from app.translator.const import DEFAULT_VALUE_TYPE from app.translator.core.mapping import SourceMapping from app.translator.core.models.functions.base import Function from app.translator.platforms.qradar.const import qradar_query_details @@ -30,7 +30,7 @@ class QradarFieldValue(BaseQueryFieldValue): details: PlatformDetails = qradar_query_details - def equal_modifier(self, field: str, value: Union[Union[int, str, List[int], List[str]]]) -> str: + def equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join([self.equal_modifier(field=field, value=v) for v in value])})" if field == "UTF8(payload)": @@ -40,27 +40,54 @@ def equal_modifier(self, field: str, value: Union[Union[int, str, List[int], Lis return f'"{field}"=\'{value}\'' - def contains_modifier(self, field, value): + def less_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f'"{field}"<{value}' + return f'"{field}"<\'{value}\'' + + def less_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f'"{field}"<={value}' + return f'"{field}"<=\'{value}\'' + + def greater_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f'"{field}">{value}' + return f'"{field}">\'{value}\'' + + def greater_or_equal_modifier(self, field: str, value: Union[int, str]) -> str: + if isinstance(value, int): + return f'"{field}">={value}' + return f'"{field}">=\'{value}\'' + + def not_equal_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: + if isinstance(value, list): + return f"({self.or_token.join([self.not_equal_modifier(field=field, value=v) for v in value])})" + if isinstance(value, int): + return f'"{field}"!={value}' + return f'"{field}"!=\'{value}\'' + + def contains_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.contains_modifier(field=field, value=v) for v in value)})" return f'"{field}" ILIKE \'%{value}%\'' - def endswith_modifier(self, field, value): + def endswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.endswith_modifier(field=field, value=v) for v in value)})" return f'"{field}" ILIKE \'%{value}\'' - def startswith_modifier(self, field, value): + def startswith_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.startswith_modifier(field=field, value=v) for v in value)})" return f'"{field}" ILIKE \'{value}%\'' - def regex_modifier(self, field, value): + def regex_modifier(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.regex_modifier(field=field, value=v) for v in value)})" return f'"{field}" IMATCHES \'{value}\'' - def keywords(self, field, value): + def keywords(self, field: str, value: DEFAULT_VALUE_TYPE) -> str: if isinstance(value, list): return f"({self.or_token.join(self.keywords(field=field, value=v) for v in value)})" return f'UTF8(payload) ILIKE "%{value}%"' diff --git a/translator/app/translator/platforms/qradar/tokenizer.py b/translator/app/translator/platforms/qradar/tokenizer.py index 284dd87c..5b8fceb5 100644 --- a/translator/app/translator/platforms/qradar/tokenizer.py +++ b/translator/app/translator/platforms/qradar/tokenizer.py @@ -29,7 +29,7 @@ class QradarTokenizer(QueryTokenizer): field_pattern = r'(?P"[a-zA-Z\._\-\s]+"|[a-zA-Z\._\-]+)' - match_operator_pattern = r"""(?:___field___\s?(?Plike|ilike|matches|imatches|in|=|!=))\s?""" + match_operator_pattern = r"""(?:___field___\s?(?Plike|ilike|matches|imatches|in|!=|>=|>|<=|<|=))\s?""" bool_value_pattern = r"(?Ptrue|false)\s*" _value_pattern = fr"{NUM_VALUE_PATTERN}|{bool_value_pattern}|{SINGLE_QUOTES_VALUE_PATTERN}" keyword_pattern = fr"{UTF8_PAYLOAD_PATTERN}\s+(?:like|LIKE|ilike|ILIKE)\s+{SINGLE_QUOTES_VALUE_PATTERN}" @@ -42,7 +42,7 @@ class QradarTokenizer(QueryTokenizer): "matches": OperatorType.REGEX, "imatches": OperatorType.REGEX } - + def __init__(self): super().__init__() self.operators_map.update(super().operators_map) @@ -74,7 +74,7 @@ def search_field_value(self, query): should_process_value_wildcard_symbols = self.should_process_value_wildcard_symbols(operator) query, operator, value = self.search_value(query=query, operator=operator, field_name=field_name) - operator_token = Identifier(token_type=OperatorType.EQ) + operator_token = Identifier(token_type=operator) if should_process_value_wildcard_symbols: value, operator_token = self.process_value_wildcard_symbols( value=value, diff --git a/translator/app/translator/platforms/sigma/renders/sigma.py b/translator/app/translator/platforms/sigma/renders/sigma.py index 176946e6..fca841dc 100644 --- a/translator/app/translator/platforms/sigma/renders/sigma.py +++ b/translator/app/translator/platforms/sigma/renders/sigma.py @@ -181,7 +181,8 @@ def generate_field(self, data: Field, source_mapping: SourceMapping): source_id = source_mapping.source_id generic_field_name = data.generic_names_map.get(source_id) or data.source_name field_name = self.map_field(source_mapping, generic_field_name) - if data.operator.token_type != OperatorType.EQ: + if data.operator.token_type not in (OperatorType.EQ, OperatorType.LT, OperatorType.LTE, OperatorType.GT, + OperatorType.GTE, OperatorType.EQ, OperatorType.NEQ): field_name = f"{field_name}|{data.operator.token_type}" if isinstance(data.values, list) and len(data.values) == 1 or isinstance(data.values, (str, int)): return {field_name: data.values[0]} From 68bce33136cadaa8aac5c2f43240b2b22e0e6e88 Mon Sep 17 00:00:00 2001 From: "nazar.gesyk" Date: Fri, 8 Dec 2023 16:23:57 +0200 Subject: [PATCH 2/2] Add new operators support --- translator/app/translator/platforms/sigma/renders/sigma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translator/app/translator/platforms/sigma/renders/sigma.py b/translator/app/translator/platforms/sigma/renders/sigma.py index fca841dc..2244e32f 100644 --- a/translator/app/translator/platforms/sigma/renders/sigma.py +++ b/translator/app/translator/platforms/sigma/renders/sigma.py @@ -182,7 +182,7 @@ def generate_field(self, data: Field, source_mapping: SourceMapping): generic_field_name = data.generic_names_map.get(source_id) or data.source_name field_name = self.map_field(source_mapping, generic_field_name) if data.operator.token_type not in (OperatorType.EQ, OperatorType.LT, OperatorType.LTE, OperatorType.GT, - OperatorType.GTE, OperatorType.EQ, OperatorType.NEQ): + OperatorType.GTE, OperatorType.NEQ): field_name = f"{field_name}|{data.operator.token_type}" if isinstance(data.values, list) and len(data.values) == 1 or isinstance(data.values, (str, int)): return {field_name: data.values[0]} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy