diff --git a/mula/scheduler/storage/filters/casting.py b/mula/scheduler/storage/filters/casting.py index e3eb2c85b18..cf3b1a98a54 100644 --- a/mula/scheduler/storage/filters/casting.py +++ b/mula/scheduler/storage/filters/casting.py @@ -43,9 +43,9 @@ def cast_expression(expression: BinaryExpression, filter_: Filter) -> BinaryExpr # if the value can be decoded. try: decoded_value = json.loads(filter_.value) - if isinstance(decoded_value, dict): - # If it's a JSON object, return the expression as is. We don't - # need to cast it. + # If the string is a JSON object, return the expression as is. + # We don't need to cast it. + if isinstance(decoded_value, dict | list): return expression expression = expression.astext except json.JSONDecodeError: diff --git a/mula/tests/integration/test_api.py b/mula/tests/integration/test_api.py index 85e14b5ec37..e1adde1f496 100644 --- a/mula/tests/integration/test_api.py +++ b/mula/tests/integration/test_api.py @@ -481,6 +481,34 @@ def test_pop_queue_filters_nested(self): self.assertEqual(second_item_id, response.json().get("id")) self.assertEqual(0, self.scheduler.queue.qsize()) + def test_pop_queue_filters_nested_contained_by(self): + # Add one task to the queue + first_item = create_task_in(1, data=functions.TestModel(id="123", name="test", categories=["foo", "bar"])) + response = self.client.post(f"/queues/{self.scheduler.scheduler_id}/push", data=first_item) + self.assertEqual(response.status_code, 201) + self.assertEqual(1, self.scheduler.queue.qsize()) + + # Add second item to the queue + second_item = create_task_in(2, data=functions.TestModel(id="456", name="test", categories=["baz", "bat"])) + response = self.client.post(f"/queues/{self.scheduler.scheduler_id}/push", data=second_item) + second_item_id = response.json().get("id") + self.assertEqual(response.status_code, 201) + self.assertEqual(2, self.scheduler.queue.qsize()) + + # Test contained by + response = self.client.post( + f"/queues/{self.scheduler.scheduler_id}/pop", + json={ + "filters": [ + {"column": "data", "operator": "<@", "field": "categories", "value": json.dumps(["baz", "bat"])} + ] + }, + ) + + self.assertEqual(200, response.status_code) + self.assertEqual(second_item_id, response.json().get("id")) + self.assertEqual(1, self.scheduler.queue.qsize()) + def test_pop_empty(self): """When queue is empty it should return an empty response""" response = self.client.post(f"/queues/{self.scheduler.scheduler_id}/pop") diff --git a/mula/tests/unit/test_filter.py b/mula/tests/unit/test_filter.py index 5e1882f94bc..4b804511bb0 100644 --- a/mula/tests/unit/test_filter.py +++ b/mula/tests/unit/test_filter.py @@ -38,21 +38,31 @@ def setUp(self): age=25, height=1.8, is_active=True, - data={"foo": "bar", "score": 15, "nested": {"bar": "baz"}, "list": ["foo", "bar"]}, + data={"foo": "bar", "score": 15, "nested": {"bar": "baz"}, "list": ["ipv4", "network/local"]}, ), TestModel( name="Bob", age=30, height=1.7, is_active=False, - data={"foo": "baz", "score": 25, "nested": {"bar": "baz"}, "list": ["bar", "baz"]}, + data={ + "foo": "baz", + "score": 25, + "nested": {"bar": "baz"}, + "list": ["ipv4", "ipv6", "network/local"], + }, ), TestModel( name="Charlie", age=28, height=1.6, is_active=True, - data={"foo": "bar", "score": 35, "nested": {"bar": "baz"}, "list": ["foo", "bar"]}, + data={ + "foo": "bar", + "score": 35, + "nested": {"bar": "baz"}, + "list": ["ipv4", "ipv6", "network/internet"], + }, ), ] ) @@ -705,9 +715,25 @@ def test_apply_filter_jsonb_contains(self): self.assertEqual(results[0].name, "Alice") self.assertEqual(results[1].name, "Charlie") - def test_apply_filter_jsonb_in_list(self): + def test_apply_filter_jsonb_contains_list(self): filter_request = FilterRequest( - filters=[Filter(column="data", operator="@>", value=json.dumps({"list": ["foo"]}))] + filters=[Filter(column="data", field="list", operator="@>", value=json.dumps(["ipv4"]))] + ) + + query = session.query(TestModel) + filtered_query = apply_filter(TestModel, query, filter_request) + + results = filtered_query.order_by(TestModel.name).all() + self.assertEqual(len(results), 3) + self.assertEqual(results[0].name, "Alice") + self.assertEqual(results[1].name, "Bob") + self.assertEqual(results[2].name, "Charlie") + + def test_apply_filter_jsonb_contained_by_list(self): + filter_request = FilterRequest( + filters=[ + Filter(column="data", field="list", operator="<@", value=json.dumps(["ipv4", "ipv6", "network/local"])) + ] ) query = session.query(TestModel) @@ -716,4 +742,4 @@ def test_apply_filter_jsonb_in_list(self): results = filtered_query.order_by(TestModel.name).all() self.assertEqual(len(results), 2) self.assertEqual(results[0].name, "Alice") - self.assertEqual(results[1].name, "Charlie") + self.assertEqual(results[1].name, "Bob") diff --git a/rocky/reports/report_types/findings_report/report.py b/rocky/reports/report_types/findings_report/report.py index 91fb63e2533..481e95785a3 100644 --- a/rocky/reports/report_types/findings_report/report.py +++ b/rocky/reports/report_types/findings_report/report.py @@ -23,7 +23,18 @@ class FindingsReport(Report): id = "findings-report" name = _("Findings Report") description = _("Shows all the finding types and their occurrences.") - plugins: ReportPlugins = {"required": set(), "optional": set()} + plugins: ReportPlugins = { + "required": { + "dns-records", + "nmap", + "nmap-udp", + "webpage-analysis", + "ssl-version", + "ssl-certificates", + "testssl-sh-ciphers", + }, + "optional": {"snyk", "service_banner", "shodan", "leakix"}, + } input_ooi_types = ALL_TYPES template_path = "findings_report/report.html" label_style = "3-light" diff --git a/rocky/reports/report_types/vulnerability_report/report.html b/rocky/reports/report_types/vulnerability_report/report.html index 7f52510b3ec..09eed1c928e 100644 --- a/rocky/reports/report_types/vulnerability_report/report.html +++ b/rocky/reports/report_types/vulnerability_report/report.html @@ -1,6 +1,9 @@ {% load i18n %} +{% load report_extra %} -{% if data %} +{% if data|sum_findings == 0 %} +
{% translate "No vulnerabilities have been found on this system." %}
+{% else %} {% for ip, vulnerability_data in data.items %} {% if vulnerability_data.summary.total_findings > 0 %} {% if show_heading %} @@ -91,10 +94,6 @@{% translate "No vulnerabilities have been found on this system." %}
- {% endif %} {% endif %} {% endfor %} {% endif %} diff --git a/rocky/reports/templatetags/report_extra.py b/rocky/reports/templatetags/report_extra.py index c0c4076b618..a417120407c 100644 --- a/rocky/reports/templatetags/report_extra.py +++ b/rocky/reports/templatetags/report_extra.py @@ -1,3 +1,5 @@ +from typing import Any + from django import template from reports.report_types.helpers import get_report_by_id @@ -10,6 +12,11 @@ def sum_attribute(checks, attribute): return sum(int(check[attribute]) for check in checks) +@register.filter +def sum_findings(data: dict[str, Any]) -> int: + return sum(int(ip["summary"]["total_findings"]) for ip in data.values()) + + @register.filter def get_report_type_name(report_type_id: str): return get_report_by_id(report_type_id).name diff --git a/rocky/rocky/locale/django.pot b/rocky/rocky/locale/django.pot index 0aa3be3149d..4af966792b8 100644 --- a/rocky/rocky/locale/django.pot +++ b/rocky/rocky/locale/django.pot @@ -3568,6 +3568,10 @@ msgid "" "protocols." msgstr "" +#: reports/report_types/vulnerability_report/report.html +msgid "No vulnerabilities have been found on this system." +msgstr "" + #: reports/report_types/vulnerability_report/report.html msgid "" "The Vulnerability Report provides an overview of all identified CVE " @@ -3580,10 +3584,6 @@ msgstr "" msgid "Advice" msgstr "" -#: reports/report_types/vulnerability_report/report.html -msgid "No vulnerabilities have been found on this system." -msgstr "" - #: reports/report_types/vulnerability_report/report.py msgid "Vulnerability Report" msgstr ""