QRadarCreateAQLQuery

Build QRadar AQL Query.

python · IBM QRadar

Details

IDQRadarCreateAQLQuery
Languagepython
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
TagsUtility

README

Build QRadar AQL Query. (Available from Cortex XSOAR 6.0.0).

Script Data


Name Description
Script Type python3
Tags Utility
Cortex XSOAR Version 6.0.0

Inputs


Argument Name Description
base_values_to_search The values of the first field to search. This can be a single value or a comma-separated list of values. For example admin1,admin2.
base_fields_to_search The field names of the first field to search. This can be a single value or a comma-separated list of values. For example admin1,admin2.
base_field_state The state of the second field to search, meaning whether the values in the field should be included or excluded. Valid options are include or exclude.
base_field_match Whether the values of the second field should be an exact match or a partial match. Valid options are exact or partial.
select_fields The list of fields to select within the AQL query. The default fields are DATEFORMAT(devicetime,’dd-MM-yyyy hh:mm’),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username
time_frame Time frame as used in AQL examples can be LAST 7 DAYS START ‘2019-09-25 15:51’ STOP ‘2019-09-25 17:51’. For more examples, view IBM’s AQL documentation.
first_additional_values The values of the second field to search. This can be a single value or a comma-separated list of values. For example admin1,admin2.
first_additional_fields The field names of the second field to search. This can be a single value or a comma-separated list of values. For example admin1,admin2.
first_additional_field_state The state of the second field to search, meaning whether the values in the field should be included or excluded. Valid options are include or exclude.
first_additional_field_match Whether the values of the second field should be an exact match or a partial match. Valid options are exact or partial.
second_additional_values The values of the third field to search. This can be a single value or a comma-separated list of values. For example admin1,admin2
second_additional_fields The field names of the third field to search. This can be a single value or a comma-separated list of values. For example username,user
second_additional_field_state The state of the third field to search, meaning whether the values in the field should be included or excluded. Valid options are include or exclude.
second_additional_field_match Whether the values of the third field should be an exact match or a partial match. Valid options are exact or partial. When choosing exact, the AQL query will use the = operator. When choosing partial, the AQL query will use ILIKE and add ‘%%’ to the values.

Outputs


Path Description Type
QRadarQuery The resultant AQL query based on the inputs. string

Script Example Search for a hash where we dont know the field

!QRadarCreateAQLQuery base_field_match=partial base_values_to_search=2367666DB8DFF58982A74695760E3EF0ACEBD050

Context Example

{
    "QRadarQuery": "select DATEFORMAT(devicetime,'dd-MM-yyyy hh:mm'),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (UTF8(payload) ILIKE '%2367666DB8DFF58982A74695760E3EF0ACEBD050%') LAST 1 HOURS"
}

Human Readable Output

select DATEFORMAT(devicetime,’dd-MM-yyyy hh:mm’),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (UTF8(payload) ILIKE ‘%2367666DB8DFF58982A74695760E3EF0ACEBD050%’) LAST 1 HOURS

Script Example Search for a hash in specific fields

!QRadarCreateAQLQuery base_field_match=exact base_values_to_search=2367666DB8DFF58982A74695760E3EF0ACEBD050 base_fields_to_search=sha1,sha1-hash

Context Example

{
    "QRadarQuery": "select DATEFORMAT(devicetime,'dd-MM-yyyy hh:mm'),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = '2367666DB8DFF58982A74695760E3EF0ACEBD050' OR sha1-hash = '2367666DB8DFF58982A74695760E3EF0ACEBD050') LAST 1 HOURS"
}

Human Readable Output

select DATEFORMAT(devicetime,’dd-MM-yyyy hh:mm’),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = ‘2367666DB8DFF58982A74695760E3EF0ACEBD050’ OR sha1-hash = ‘2367666DB8DFF58982A74695760E3EF0ACEBD050’) LAST 1 HOURS

Script Example Search for user and hash

!QRadarCreateAQLQuery base_field_match=exact base_values_to_search=2367666DB8DFF58982A74695760E3EF0ACEBD050 base_fields_to_search=sha1,sha1-hash first_additional_fields=username first_additional_values=admin

Context Example

{
    "QRadarQuery": "select DATEFORMAT(devicetime,'dd-MM-yyyy hh:mm'),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = '2367666DB8DFF58982A74695760E3EF0ACEBD050' OR sha1-hash = '2367666DB8DFF58982A74695760E3EF0ACEBD050') AND (username = 'admin') LAST 1 HOURS"
}

Human Readable Output

select DATEFORMAT(devicetime,’dd-MM-yyyy hh:mm’),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = ‘2367666DB8DFF58982A74695760E3EF0ACEBD050’ OR sha1-hash = ‘2367666DB8DFF58982A74695760E3EF0ACEBD050’) AND (username = ‘admin’) LAST 1 HOURS

Script Example Search for a hash in payload that doesnt contain admin

!QRadarCreateAQLQuery base_field_match=exact base_values_to_search=2367666DB8DFF58982A74695760E3EF0ACEBD050 base_fields_to_search=sha1 first_additional_field_state=exclude first_additional_field_match=partial first_additional_values=admin

Context Example

{
    "QRadarQuery": "select DATEFORMAT(devicetime,'dd-MM-yyyy hh:mm'),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = '2367666DB8DFF58982A74695760E3EF0ACEBD050') AND (UTF8(payload) NOT ILIKE '%admin%') LAST 1 HOURS"
}

Human Readable Output

select DATEFORMAT(devicetime,’dd-MM-yyyy hh:mm’),LOGSOURCENAME(logsourceid),CATEGORYNAME(category),QIDNAME(qid),sourceip,destinationip,username from events where (sha1 = ‘2367666DB8DFF58982A74695760E3EF0ACEBD050’) AND (UTF8(payload) NOT ILIKE ‘%admin%’) LAST 1 HOURS

import pytest
from CommonServerPython import *
from QRadarCreateAQLQuery import *


class TestFieldsSection:
    def test_sanity(self):
        assert fields_section(["field"], ["value"]) == "(field = 'value')"

    def test_with_space(self):
        assert fields_section(["field name"], ["value"]) == "('field name' = 'value')"

    def test_multiple_fields(self):
        assert fields_section(["field1", "field2"], ["value"]) == "(field1 = 'value' OR field2 = 'value')"

    def test_multiple_values(self):
        assert fields_section(["field"], ["value1", "value2"]) == "(field = 'value1' OR field = 'value2')"

    def test_multiple_fields_and_values(self):
        assert (
            fields_section(["field1", "field2"], ["value1", "value2"])
            == "(field1 = 'value1' OR field1 = 'value2' OR field2 = 'value1' OR field2 = 'value2')"
        )

    def test_not_equal(self):
        assert fields_section(["field"], ["value"], match_rule=MatchRule.NOT_EQUAL) == "(field != 'value')"

    def test_ilike(self):
        assert fields_section(["field"], ["value"], match_rule=MatchRule.ILIKE) == "(field ILIKE '%value%')"

    def test_not_not_ilike(self):
        assert fields_section(["field"], ["value"], match_rule=MatchRule.NOT_ILIKE) == "(field NOT ILIKE '%value%')"


def test_complete_query():
    assert (
        complete_query("select_fields", "combined_sections", "time_frame")
        == "select select_fields from events where combined_sections time_frame"
    )


class TestPrepareArgs:
    def test_with_empty(self):
        assert prepare_args({"some emty ergument": ""}) == {}

    def test_with_base_args(self):
        test = "test"
        original_args = {
            "base_values_to_search": test,
            "base_fields_to_search": test,
            "base_field_state": test,
            "base_field_match": test,
        }
        expected_args = {
            "base_additional_values": test,
            "base_additional_fields": test,
            "base_additional_field_state": test,
            "base_additional_field_match": test,
        }
        assert prepare_args(original_args) == expected_args

    def test_base_kyas_not_found(self):
        assert prepare_args({}) == {}


def test_original_key_name():
    assert original_key_name("non_existing_key") == "non_existing_key"
    assert original_key_name("base_additional_values") == "base_values_to_search"
    assert original_key_name("base_additional_fields") == "base_fields_to_search"
    assert original_key_name("base_additional_field_state") == "base_field_state"
    assert original_key_name("base_additional_field_match") == "base_field_match"


class TestCreateSectionsStr:
    def test_failure_with_non_existing_base(self):
        with pytest.raises(DemistoException, match="base arguments not given correctly"):
            create_sections_str({})

    def test_go_over_all_3_arg_groups(self, mocker):
        mocker.patch("QRadarCreateAQLQuery.prepare_section", return_value={})
        fields_section_mocker = mocker.patch("QRadarCreateAQLQuery.fields_section", return_value="")
        create_sections_str({})
        assert fields_section_mocker.call_count == 3

    def test_operator_concat(self, mocker):
        mocker.patch("QRadarCreateAQLQuery.prepare_section", return_value={})
        mocker.patch("QRadarCreateAQLQuery.fields_section", return_value="test")
        assert create_sections_str({}) == "test AND test AND test"


class TestPrepareSection:
    def test_without_the_section_args(self):
        with pytest.raises(SectionNotFound, match="section name"):
            prepare_section({}, "section name")

    def test_singel_value_and_field(self):
        input_args = {
            "_additional_values": "test_val",
            "_additional_fields": "test_field",
            "_additional_field_state": "include",
            "_additional_field_match": "exact",
        }
        output_args = prepare_section(input_args, "")
        assert output_args["values_list"] == ["test_val"]
        assert output_args["fields_list"] == ["test_field"]

    def test_values_list(self):
        input_args = {
            "_additional_values": "test_val1,test_val2",
            "_additional_fields": "test_field1,test_field2",
            "_additional_field_state": "include",
            "_additional_field_match": "exact",
        }
        output_args = prepare_section(input_args, "")
        assert output_args["values_list"] == ["test_val1", "test_val2"]
        assert output_args["fields_list"] == ["test_field1", "test_field2"]

    class TestWithoutFields:
        def test_with_exact_field_match(self):
            args = {"_additional_values": "test", "_additional_field_state": "include", "_additional_field_match": "exact"}
            with pytest.raises(KeyError, match="_additional_fields"):
                prepare_section(args, "")

        def test_with_partial_field_match(self):
            input_args = {
                "_additional_values": "test_val1,test_val2",
                "_additional_field_state": "include",
                "_additional_field_match": "partial",
            }
            output_args = prepare_section(input_args, "")
            assert output_args["fields_list"] == ["UTF8(payload)"]

    class TestMachRules:
        @classmethod
        def setup_method(cls):
            cls._args = {"_additional_values": "test_val", "_additional_fields": "test_field"}

        def test_match_rule_equal(self):
            self._args.update({"_additional_field_state": "include", "_additional_field_match": "exact"})
            assert prepare_section(self._args, "")["match_rule"] == MatchRule.EQUAL

        def test_match_rule_not_equal(self):
            self._args.update({"_additional_field_state": "exclude", "_additional_field_match": "exact"})
            assert prepare_section(self._args, "")["match_rule"] == MatchRule.NOT_EQUAL

        def test_match_rule_ilike(self):
            self._args.update({"_additional_field_state": "include", "_additional_field_match": "partial"})
            assert prepare_section(self._args, "")["match_rule"] == MatchRule.ILIKE

        def test_match_rule_not_ilike(self):
            self._args.update({"_additional_field_state": "exclude", "_additional_field_match": "partial"})
            assert prepare_section(self._args, "")["match_rule"] == MatchRule.NOT_ILIKE


class TestKeyErrors:
    @staticmethod
    def mocks(args, mocker):
        mocker.patch.object(demisto, "args", return_value=args)
        return mocker.patch("QRadarCreateAQLQuery.return_error")

    def test_with_required_key(self, mocker):
        mock_return_error = self.mocks({}, mocker)
        main()
        mock_return_error.assert_called_once_with("Missing time_frame.")

    def test_with_base_key(self, mocker):
        args = {
            "time_frame": "test",
            "select_fields": "test",
            "base_values_to_search": "test",
            "base_field_state": "exclude",
            "base_field_match": "exact",
        }
        mock_return_error = self.mocks(args, mocker)
        main()
        mock_return_error.assert_called_once_with("Missing base_fields_to_search.")

    def test_with_other_key(self, mocker):
        args = {
            "time_frame": "test",
            "select_fields": "test",
            "base_values_to_search": "test",
            "base_field_state": "exclude",
            "base_field_match": "partial",
            "first_additional_values": "test",
            "first_additional_field_state": "exclude",
            "first_additional_field_match": "exact",
        }
        mock_return_error = self.mocks(args, mocker)
        main()
        mock_return_error.assert_called_once_with("Missing first_additional_fields.")


def test_main_general_exception(mocker):
    error = Exception("test")

    def raising():
        raise error

    mocker.patch.object(demisto, "args", new=raising)
    mock_return_error = mocker.patch("QRadarCreateAQLQuery.return_error")
    main()
    mock_return_error.assert_called_once_with("test", error)