Skip to content

Commit

Permalink
Adding set and sort filter functions (#294)
Browse files Browse the repository at this point in the history
* Initial failing implementation

* Remove sorted tests

* Add more tests

* Add a wrapper for single-arg methods

* Add new functions to docs

* Update changelog

---------

Co-authored-by: marcsantamaria-sky <[email protected]>
  • Loading branch information
jsoucheiron and marcsantamaria-sky committed Jul 17, 2024
1 parent 0e18dce commit e685807
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

## [1.16.0]
### Additions
- Added 2 new filter functions: `set` and `sorted`


## [1.15.7]
### Updates
- Bumped pycfmodel to use pydantic v2
Expand Down
53 changes: 35 additions & 18 deletions cfripper/config/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,25 @@

from cfripper.model.enums import RuleMode, RuleRisk

VALID_FUNCTIONS = [
VALID_FUNCTIONS = {
"and",
"empty",
"eq",
"ne",
"lt",
"exists",
"ge",
"gt",
"in",
"le",
"ge",
"lt",
"ne",
"not",
"or",
"and",
"in",
"ref",
"regex",
"regex:ignorecase",
"exists",
"empty",
"ref",
]
"set",
"sorted",
}

logger = logging.getLogger(__file__)

Expand All @@ -39,22 +41,37 @@ def wrap(*args, **kwargs):

return wrap

def single_param_resolver(f):
def wrap(*args, **kwargs):
calculated_parameters = [arg(kwargs) for arg in args]
if len(calculated_parameters) == 1 and isinstance(calculated_parameters[0], (dict, set)):
result = f(*calculated_parameters, **kwargs)
else:
result = f(calculated_parameters, **kwargs)
if debug:
logger.debug(f"{function_name}({', '.join(str(x) for x in calculated_parameters)}) -> {result}")
return result

return wrap

implemented_filter_functions = {
"and": lambda *args, **kwargs: all(arg(kwargs) for arg in args),
"empty": param_resolver(lambda *args, **kwargs: len(args) == 0),
"eq": param_resolver(lambda a, b, **kwargs: a == b),
"ne": param_resolver(lambda a, b, **kwargs: a != b),
"lt": param_resolver(lambda a, b, **kwargs: a < b),
"exists": param_resolver(lambda a, **kwargs: a is not None),
"ge": param_resolver(lambda a, b, **kwargs: a >= b),
"gt": param_resolver(lambda a, b, **kwargs: a > b),
"in": param_resolver(lambda a, b, **kwargs: a in b),
"le": param_resolver(lambda a, b, **kwargs: a <= b),
"ge": param_resolver(lambda a, b, **kwargs: a >= b),
"lt": param_resolver(lambda a, b, **kwargs: a < b),
"ne": param_resolver(lambda a, b, **kwargs: a != b),
"not": param_resolver(lambda a, **kwargs: not a),
"or": lambda *args, **kwargs: any(arg(kwargs) for arg in args),
"and": lambda *args, **kwargs: all(arg(kwargs) for arg in args),
"in": param_resolver(lambda a, b, **kwargs: a in b),
"ref": param_resolver(lambda param_name, **kwargs: get(kwargs, param_name)),
"regex": param_resolver(lambda *args, **kwargs: bool(re.match(*args))),
"regex:ignorecase": param_resolver(lambda *args, **kwargs: bool(re.match(*args, re.IGNORECASE))),
"exists": param_resolver(lambda a, **kwargs: a is not None),
"empty": param_resolver(lambda *args, **kwargs: len(args) == 0),
"ref": param_resolver(lambda param_name, **kwargs: get(kwargs, param_name)),
"set": single_param_resolver(lambda *args, **kwargs: set(*args)),
"sorted": single_param_resolver(lambda *args, **kwargs: sorted(*args)),
}
return implemented_filter_functions[function_name]

Expand Down
4 changes: 3 additions & 1 deletion docs/rule_config_and_filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ When loading filters from a folder the order is alphabetical.
### Implemented filter functions

| Function | Description | Example |
| :----------------: | :-------------------------------------------------------------------------: | :-------------------------------------: |
|:------------------:|:---------------------------------------------------------------------------:|:---------------------------------------:|
| `eq` | Same as a == b | `{"eq": ["string", "string"]}` |
| `ne` | Same as a != b | `{"ne": ["string", "not_that_string"]}` |
| `lt` | Same as a < b | `{"lt": [0, 1]}` |
Expand All @@ -45,6 +45,8 @@ When loading filters from a folder the order is alphabetical.
| `exists` | True if a is not None | `{"exists": None}` |
| `empty` | True if len(a) equals 0 | `{"empty": []}` |
| `ref` | Get the value at any depth of the context based on the path described by a. | `{"ref": "param_a.param_b"}` |
| `set` | Turns the input into a set | `{"set": [80, 443]}` |
| `sorted` | Return the sorted input as a list | `{"sorted": [80, 443]}` |

### Examples

Expand Down
27 changes: 23 additions & 4 deletions tests/config/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def template_security_group_firehose_ips():


@pytest.mark.parametrize(
"filter, args, expected_result",
"filter_name, args, expected_result",
[
(Filter(eval={"eq": ["string", "string"]}), {}, True),
(Filter(eval={"eq": [1, 1]}), {}, True),
Expand Down Expand Up @@ -219,7 +219,26 @@ def template_security_group_firehose_ips():
(Filter(eval={"ref": "param_a.param_b.param_c"}), {"param_a": {"param_b": {"param_c": [1]}}}, [1]),
(Filter(eval={"ref": "param_a.param_b.param_c"}), {"param_a": {"param_b": {"param_c": [-1]}}}, [-1]),
(Filter(eval={"ref": "param_a.param_b.param_c"}), {"param_a": {"param_b": {"param_c": [1.0]}}}, [1.0]),
(Filter(eval={"ref": "param_a.param_b.param_c"}), {"param_a": {"param_b": {"param_c": [-1.0]}}}, [-1.0]),
(Filter(eval={"set": []}), {}, set()),
(Filter(eval={"set": {}}), {}, set()),
(Filter(eval={"set": set()}), {}, set()),
(Filter(eval={"set": {"80"}}), {}, {"80"}),
(Filter(eval={"set": ["80"]}), {}, {"80"}),
(Filter(eval={"set": {"80": 100}}), {}, {"80"}),
(Filter(eval={"set": {"80": 100, "90": 100}}), {}, {"80", "90"}),
(Filter(eval={"set": ["80", "443"]}), {}, {"80", "443"}),
(Filter(eval={"set": {"80", "443"}}), {}, {"80", "443"}),
(Filter(eval={"set": ["80", "443", "8080"]}), {}, {"80", "443", "8080"}),
(Filter(eval={"sorted": []}), {}, []),
(Filter(eval={"sorted": {}}), {}, []),
(Filter(eval={"sorted": set()}), {}, []),
(Filter(eval={"sorted": {"80"}}), {}, ["80"]),
(Filter(eval={"sorted": ["80"]}), {}, ["80"]),
(Filter(eval={"sorted": {"80": 100}}), {}, ["80"]),
(Filter(eval={"sorted": {"80": 100, "90": 100}}), {}, ["80", "90"]),
(Filter(eval={"sorted": ["80", "443"]}), {}, ["443", "80"]),
(Filter(eval={"sorted": {"80", "443"}}), {}, ["443", "80"]),
(Filter(eval={"sorted": ["80", "443", "8080"]}), {}, ["443", "80", "8080"]),
# Composed
(Filter(eval={"eq": [{"ref": "param_a"}, "a"]}), {"param_a": "a"}, True),
(Filter(eval={"eq": ["a", {"ref": "param_a"}]}), {"param_a": "a"}, True),
Expand All @@ -242,8 +261,8 @@ def template_security_group_firehose_ips():
),
],
)
def test_filter(filter, args, expected_result):
assert filter(**args) == expected_result
def test_filter(filter_name, args, expected_result):
assert filter_name(**args) == expected_result


def test_exist_function_and_property_does_not_exist(template_cross_account_role_no_name):
Expand Down

0 comments on commit e685807

Please sign in to comment.