Skip to content

Commit 15f0378

Browse files
authored
Feature/Option to use OR queries on security-data/alerts search (#117)
* add --password option to `profile create` and `profile update` commands * remove unused `_reset_pw()` * remove unused `_reset_pw()` * clarify reset-pw help * fix bad merge and test * add --or-query option to `alerts search` and `security-data search` * bump required extractor version * fixes * fix style * tests for OR expected query * style fix * remove unused result var * change or_query check * change or_query check on security-data
1 parent 23e5c8e commit 15f0378

6 files changed

Lines changed: 138 additions & 14 deletions

File tree

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
install_requires=[
3434
"click>=7.1.1",
3535
"colorama>=0.4.3",
36-
"c42eventextractor==0.3.2",
36+
"c42eventextractor==0.4.0",
3737
"keyring==18.0.1",
3838
"keyrings.alt==3.2.0",
39-
"py42>=1.5.1",
39+
"py42>=1.7.0",
4040
],
4141
extras_require={
4242
"dev": [

src/code42cli/cmds/alerts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,21 @@ def clear_checkpoint(state, checkpoint_name):
155155
@alerts.command()
156156
@alert_options
157157
@search_options
158+
@click.option(
159+
"--or-query", is_flag=True, cls=searchopt.AdvancedQueryAndSavedSearchIncompatible
160+
)
158161
@opt.sdk_options
159-
def search(cli_state, format, begin, end, advanced_query, use_checkpoint, **kwargs):
162+
def search(
163+
cli_state, format, begin, end, advanced_query, use_checkpoint, or_query, **kwargs
164+
):
160165
"""Search for alerts."""
161166
output_logger = logger_factory.get_logger_for_stdout(format)
162167
cursor = _get_alert_cursor_store(cli_state.profile.name) if use_checkpoint else None
163168
handlers = ext.create_handlers(
164169
cli_state.sdk, AlertExtractor, output_logger, cursor, use_checkpoint
165170
)
166171
extractor = _get_alert_extractor(cli_state.sdk, handlers)
172+
extractor.use_or_query = or_query
167173
if advanced_query:
168174
extractor.extract_advanced(advanced_query)
169175
else:

src/code42cli/cmds/profile.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,11 @@ def profile():
2626
help="The name of the Code42 CLI profile to use when executing this command.",
2727
)
2828
server_option = click.option(
29-
"-s",
30-
"--server",
31-
required=True,
32-
type=str,
33-
help="The url and port of the Code42 server.",
29+
"-s", "--server", required=True, help="The url and port of the Code42 server.",
3430
)
3531

3632
username_option = click.option(
37-
"-u",
38-
"--username",
39-
required=True,
40-
type=str,
41-
help="The username of the Code42 API user.",
33+
"-u", "--username", required=True, help="The username of the Code42 API user.",
4234
)
4335

4436
password_option = click.option(

src/code42cli/cmds/securitydata.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,20 @@ def clear_checkpoint(state, checkpoint_name):
163163
@security_data.command()
164164
@file_event_options
165165
@search_options
166+
@click.option(
167+
"--or-query", is_flag=True, cls=searchopt.AdvancedQueryAndSavedSearchIncompatible
168+
)
166169
@sdk_options
167170
def search(
168-
state, format, begin, end, advanced_query, use_checkpoint, saved_search, **kwargs
171+
state,
172+
format,
173+
begin,
174+
end,
175+
advanced_query,
176+
use_checkpoint,
177+
saved_search,
178+
or_query,
179+
**kwargs
169180
):
170181
"""Search for file events."""
171182
output_logger = logger_factory.get_logger_for_stdout(format)
@@ -176,6 +187,8 @@ def search(
176187
state.sdk, FileEventExtractor, output_logger, cursor, use_checkpoint
177188
)
178189
extractor = _get_file_event_extractor(state.sdk, handlers)
190+
extractor.use_or_query = or_query
191+
extractor.or_query_exempt_filters.append(f.ExposureType.exists())
179192
if advanced_query:
180193
extractor.extract_advanced(advanced_query)
181194
elif saved_search:

tests/cmds/test_alerts.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import json
2+
13
import py42.sdk.queries.alerts.filters as f
24
import pytest
35
from c42eventextractor.extractors import AlertExtractor
@@ -576,3 +578,57 @@ def test_search_when_given_multiple_search_args_uses_expected_filters(
576578
assert str(f.Actor.is_in([actor])) in filter_strings
577579
assert str(f.Actor.not_in([exclude_actor])) in filter_strings
578580
assert str(f.RuleName.is_in([rule_name])) in filter_strings
581+
582+
583+
def test_search_with_or_query_flag_produces_expected_query(runner, cli_state):
584+
begin_date = get_test_date_str(days_ago=10)
585+
test_actor = "test@example.com"
586+
test_rule_type = "FedEndpointExfiltration"
587+
runner.invoke(
588+
cli,
589+
[
590+
"alerts",
591+
"search",
592+
"--or-query",
593+
"--begin",
594+
begin_date,
595+
"--actor",
596+
test_actor,
597+
"--rule-type",
598+
test_rule_type,
599+
],
600+
obj=cli_state,
601+
)
602+
expected_query = {
603+
"tenantId": None,
604+
"groupClause": "AND",
605+
"groups": [
606+
{
607+
"filterClause": "AND",
608+
"filters": [
609+
{
610+
"operator": "ON_OR_AFTER",
611+
"term": "createdAt",
612+
"value": "{}T00:00:00.000Z".format(begin_date),
613+
}
614+
],
615+
},
616+
{
617+
"filterClause": "OR",
618+
"filters": [
619+
{"operator": "IS", "term": "actor", "value": "test@example.com"},
620+
{
621+
"operator": "IS",
622+
"term": "type",
623+
"value": "FedEndpointExfiltration",
624+
},
625+
],
626+
},
627+
],
628+
"pgNum": 0,
629+
"pgSize": 500,
630+
"srtDirection": "asc",
631+
"srtKey": "CreatedAt",
632+
}
633+
actual_query = json.loads(str(cli_state.sdk.alerts.search.call_args[0][0]))
634+
assert actual_query == expected_query

tests/cmds/test_securitydata.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import logging
23

34
import py42.sdk.queries.fileevents.filters as f
@@ -634,3 +635,59 @@ def test_show_detail_calls_get_by_id_method(runner, cli_state):
634635
cli, ["security-data", "saved-search", "show", test_id], obj=cli_state
635636
)
636637
cli_state.sdk.securitydata.savedsearches.get_by_id.assert_called_once_with(test_id)
638+
639+
640+
def test_search_with_or_query_flag_produces_expected_query(runner, cli_state):
641+
begin_date = get_test_date_str(days_ago=10)
642+
test_username = "test@example.com"
643+
test_filename = "test.txt"
644+
runner.invoke(
645+
cli,
646+
[
647+
"security-data",
648+
"search",
649+
"--or-query",
650+
"--begin",
651+
begin_date,
652+
"--c42-username",
653+
test_username,
654+
"--file-name",
655+
test_filename,
656+
],
657+
obj=cli_state,
658+
)
659+
expected_query = {
660+
"groupClause": "AND",
661+
"groups": [
662+
{
663+
"filterClause": "AND",
664+
"filters": [
665+
{"operator": "EXISTS", "term": "exposure", "value": None},
666+
{
667+
"operator": "ON_OR_AFTER",
668+
"term": "eventTimestamp",
669+
"value": "{}T00:00:00.000Z".format(begin_date),
670+
},
671+
],
672+
},
673+
{
674+
"filterClause": "OR",
675+
"filters": [
676+
{
677+
"operator": "IS",
678+
"term": "deviceUserName",
679+
"value": "test@example.com",
680+
},
681+
{"operator": "IS", "term": "fileName", "value": "test.txt"},
682+
],
683+
},
684+
],
685+
"pgNum": 1,
686+
"pgSize": 10000,
687+
"srtDir": "asc",
688+
"srtKey": "insertionTimestamp",
689+
}
690+
actual_query = json.loads(
691+
str(cli_state.sdk.securitydata.search_file_events.call_args[0][0])
692+
)
693+
assert actual_query == expected_query

0 commit comments

Comments
 (0)