Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 35cfd9f

Browse files
authored
add content based filtering in Events API (#3258)
1 parent 40352db commit 35cfd9f

File tree

2 files changed

+308
-52
lines changed

2 files changed

+308
-52
lines changed

‎localstack/services/events/events_starter.py

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import re
12
import datetime
23
import json
34
import uuid
45
import logging
6+
import ipaddress
57
from moto.events.models import Rule as rule_model
68
from moto.events.responses import EventsHandler as events_handler
79
from localstack import config
@@ -24,6 +26,10 @@
2426
DEFAULT_EVENT_BUS_NAME: set()
2527
}
2628

29+
CONTENT_BASE_FILTER_KEYWORDS = [
30+
'prefix', 'anything-but', 'numeric', 'cidr', 'exists'
31+
]
32+
2733

2834
def send_event_to_sqs(event, arn):
2935
region = arn.split(':')[3]
@@ -52,12 +58,39 @@ def filter_event_with_target_input_path(target, event):
5258

5359

5460
def filter_event_based_on_event_format(self, rule, event):
61+
def filter_event(event_pattern, event):
62+
for key, value in event_pattern.items():
63+
event_value = event.get(key.lower())
64+
if not event_value:
65+
return False
66+
67+
if isinstance(value, list) and not identify_content_base_parameter_in_pattern(value):
68+
if isinstance(event_value, list) and \
69+
get_two_lists_intersection(value, event_value) == []:
70+
return False
71+
elif not isinstance(event_value, list) and \
72+
isinstance(event_value, (str, int)) and \
73+
event_value not in value:
74+
return False
75+
76+
elif isinstance(value, list) and identify_content_base_parameter_in_pattern(value):
77+
if not filter_event_with_content_base_parameter(value, event_value):
78+
return False
79+
80+
elif isinstance(value, (str, int)):
81+
try:
82+
if isinstance(json.loads(value), dict) and \
83+
not filter_event(json.loads(value), event_value):
84+
return False
85+
except json.decoder.JSONDecodeError:
86+
return False
87+
return True
88+
5589
rule_information = self.events_backend.describe_rule(rule)
5690
if rule_information.event_pattern:
5791
event_pattern = json.loads(rule_information.event_pattern)
58-
for key, value in event_pattern.items():
59-
if event.get(key.lower()) and event.get(key.lower()) not in value and event.get(key) != value:
60-
return False
92+
if not filter_event(event_pattern, event):
93+
return False
6194
return True
6295

6396

@@ -198,3 +231,100 @@ def start_events(port=None, asynchronous=None, update_listener=None):
198231
asynchronous=asynchronous,
199232
update_listener=update_listener
200233
)
234+
235+
236+
# ---------------
237+
# HELPER METHODS
238+
# ---------------
239+
240+
241+
def get_two_lists_intersection(lst1, lst2):
242+
lst3 = [value for value in lst1 if value in lst2]
243+
return lst3
244+
245+
246+
def identify_content_base_parameter_in_pattern(parameters):
247+
if any([list(param.keys())[0] in CONTENT_BASE_FILTER_KEYWORDS for param in parameters if isinstance(param, dict)]):
248+
return True
249+
250+
251+
def filter_event_with_content_base_parameter(pattern_value, event_value):
252+
for element in pattern_value:
253+
if (isinstance(element, (str, int))) \
254+
and (event_value == element or element in event_value):
255+
return True
256+
elif isinstance(element, dict):
257+
element_key = list(element.keys())[0]
258+
element_value = element.get(element_key)
259+
if element_key.lower() == 'prefix':
260+
if re.match(r'^{}'.format(element_value), event_value):
261+
return True
262+
elif element_key.lower() == 'exists':
263+
if element_value and event_value:
264+
return True
265+
elif not element_value and not event_value:
266+
return True
267+
elif element_key.lower() == 'cidr':
268+
ips = [str(ip) for ip in ipaddress.IPv4Network(element_value)]
269+
if event_value in ips:
270+
return True
271+
elif element_key.lower() == 'numeric':
272+
if check_valid_numeric_content_base_rule(element_value):
273+
for index in range(len(element_value)):
274+
if isinstance(element_value[index], int):
275+
continue
276+
if element_value[index] == '>' and \
277+
isinstance(element_value[index + 1], int) and \
278+
event_value <= element_value[index + 1]:
279+
break
280+
elif element_value[index] == '>=' and \
281+
isinstance(element_value[index + 1], int) and \
282+
event_value < element_value[index + 1]:
283+
break
284+
elif element_value[index] == '<' and \
285+
isinstance(element_value[index + 1], int) and \
286+
event_value >= element_value[index + 1]:
287+
break
288+
elif element_value[index] == '<=' and \
289+
isinstance(element_value[index + 1], int) and \
290+
event_value > element_value[index + 1]:
291+
break
292+
else:
293+
return True
294+
295+
elif element_key.lower() == 'anything-but':
296+
if isinstance(element_value, list) and \
297+
event_value not in element_value:
298+
return True
299+
elif (isinstance(element_value, (str, int))) and \
300+
event_value != element_value:
301+
return True
302+
elif isinstance(element_value, dict):
303+
nested_key = list(element_value)[0]
304+
if nested_key == 'prefix' and \
305+
not re.match(r'^{}'.format(element_value.get(nested_key)), event_value):
306+
return True
307+
return False
308+
309+
310+
def check_valid_numeric_content_base_rule(list_of_operators):
311+
if len(list_of_operators) > 4:
312+
return False
313+
314+
if '=' in list_of_operators:
315+
return False
316+
317+
if len(list_of_operators) > 2:
318+
upper_limit = None
319+
lower_limit = None
320+
for index in range(len(list_of_operators)):
321+
if not isinstance(list_of_operators[index], int) and \
322+
'<' in list_of_operators[index]:
323+
upper_limit = list_of_operators[index + 1]
324+
if not isinstance(list_of_operators[index], int) and \
325+
'>' in list_of_operators[index]:
326+
lower_limit = list_of_operators[index + 1]
327+
if upper_limit and lower_limit and upper_limit < lower_limit:
328+
return False
329+
index = index + 1
330+
return True

0 commit comments

Comments
 (0)