Skip to content

Commit dedba05

Browse files
Feature/saved search main (#103)
* Add saved search list and show commands (#95) * Add saved search feature * Added extraction commands * Added tests * Refactor: Format and privatize methods * Added Changelog * Added missing test for send-to * Added usage note * Refactor extraction commands at securitydata level * Apply begin and end timestamp filter to saved search * Apply begin and end timestamp filter to saved search * Refactor/mutual exclusion saved search with advanced-query (#98) * Fix: Mutual exclusion of saved search with advanced query * Fix tests * Refactor- Merge changes from master * Privatize method and follow proper naming convention * refactor- Use constants instead of strings * Use public methods instead of private in tests * Add constant * Improved documentation * Made saved-search mutually exclusive with other args alike advance-query
1 parent 8f17a80 commit dedba05

File tree

23 files changed

+518
-192
lines changed

23 files changed

+518
-192
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
4040

4141
### Added
4242

43+
- Extraction subcommands of `code42 security-data`, `print/write-to/send-to` accepts argument `--saved-search` to
44+
return saved search results.
45+
46+
- `code42 security-data saved-search` commands:
47+
- `list` prints out existing saved searches' id and name
48+
- `show` takes a search id
49+
4350
- `code42 high-risk-employee bulk` supports `add-risk-tags` and `remove-risk-tags`.
4451
- `code42 high-risk-employee bulk generate-template <cmd>` options `add-risk-tags` and `remove-risk-tags`.
4552
- `add-risk-tags` that takes a csv file with username and space separated risk tags.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ To see all your profiles, do:
6161
code42 profile list
6262
```
6363

64+
A separate profile would be needed in order to keep the incremental checkpoints separate for different queries.
65+
i.e User needs to maintain separate profiles for file event queries and saved search queries as only one checkpoint
66+
is supported per profile.
67+
6468
## Security Data and Alerts
6569

6670
Using the CLI, you can query for security events and alerts and send them to three possible destination types:

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424
"c42eventextractor==0.3.2",
2525
"keyring==18.0.1",
2626
"keyrings.alt==3.2.0",
27-
"py42>=1.2.0",
28-
"pexpect>=4.8"
2927
],
3028
license="MIT",
3129
include_package_data=True,
@@ -40,6 +38,7 @@
4038
"sphinx",
4139
"sphinx_rtd_theme",
4240
"tox==3.14.3",
41+
"pexpect>=4.8",
4342
]
4443
},
4544
classifiers=[

src/code42cli/cmds/alerts/extraction.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from code42cli.cmds.search_shared.extraction import (
1717
verify_begin_date_requirements,
1818
create_handlers,
19-
exit_if_advanced_query_used_with_other_search_args,
2019
create_time_range_filter,
2120
)
2221
from code42cli.logger import get_main_cli_logger
@@ -40,7 +39,6 @@ def extract(sdk, profile, output_logger, args):
4039
handlers = create_handlers(sdk, AlertExtractor, output_logger, store)
4140
extractor = AlertExtractor(sdk, handlers)
4241
if args.advanced_query:
43-
exit_if_advanced_query_used_with_other_search_args(args, enums.AlertFilterArguments())
4442
extractor.extract_advanced(args.advanced_query)
4543
else:
4644
verify_begin_date_requirements(args, store)

src/code42cli/cmds/alerts/main.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from code42cli.args import ArgConfig
22
from code42cli.commands import Command, SubcommandLoader
3+
from code42cli.parser import exit_if_mutually_exclusive_args_used_together
34
from code42cli.cmds.alerts.extraction import extract
45
from code42cli.cmds.search_shared import args, logger_factory
56
from code42cli.cmds.search_shared.enums import (
@@ -10,6 +11,9 @@
1011
RuleType,
1112
)
1213
from code42cli.cmds.search_shared.cursor_store import AlertCursorStore
14+
from code42cli.cmds.search_shared.args import (
15+
create_incompatible_search_args, SEARCH_FOR_ALERTS
16+
)
1317

1418

1519
class MainAlertsSubcommandLoader(SubcommandLoader):
@@ -67,20 +71,31 @@ def clear_checkpoint(sdk, profile):
6771
AlertCursorStore(profile.name).replace_stored_cursor_timestamp(None)
6872

6973

74+
def _validate_args(args):
75+
if args.advanced_query:
76+
incompatible_search_args_dict = create_incompatible_search_args(SEARCH_FOR_ALERTS)
77+
incompatible_search_args_list = list(incompatible_search_args_dict.keys())
78+
invalid_args = incompatible_search_args_list + list(AlertFilterArguments())
79+
exit_if_mutually_exclusive_args_used_together(args, invalid_args)
80+
81+
7082
def print_out(sdk, profile, args):
7183
"""Activates 'print' command. It gets alerts and prints them to stdout."""
84+
_validate_args(args)
7285
logger = logger_factory.get_logger_for_stdout(args.format)
7386
extract(sdk, profile, logger, args)
7487

7588

7689
def write_to(sdk, profile, args):
7790
"""Activates 'write-to' command. It gets alerts and writes them to the given file."""
91+
_validate_args(args)
7892
logger = logger_factory.get_logger_for_file(args.output_file, args.format)
7993
extract(sdk, profile, logger, args)
8094

8195

8296
def send_to(sdk, profile, args):
8397
"""Activates 'send-to' command. It getsalerts and logs them to the given server."""
98+
_validate_args(args)
8499
logger = logger_factory.get_logger_for_server(args.server, args.protocol, args.format)
85100
extract(sdk, profile, logger, args)
86101

@@ -191,5 +206,5 @@ def _load_search_args(arg_collection):
191206
help=u"Filter alerts by description. Does fuzzy search by default.",
192207
),
193208
}
194-
search_args = args.create_search_args(search_for=u"alerts", filter_args=filter_args)
209+
search_args = args.create_search_args(search_for=SEARCH_FOR_ALERTS, filter_args=filter_args)
195210
arg_collection.extend(search_args)

src/code42cli/cmds/search_shared/args.py

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
from code42cli.cmds.search_shared.enums import SearchArguments, OutputFormat, AlertOutputFormat
22
from code42cli.args import ArgConfig
33

4+
SEARCH_FOR_ALERTS = u"alerts"
5+
SEARCH_FOR_FILE_EVENTS = u"file events"
46

5-
def create_search_args(search_for, filter_args):
6-
advanced_query_incompatible_args = create_advanced_query_incompatible_search_args(search_for)
7-
filter_args.update(advanced_query_incompatible_args)
87

9-
format_enum = AlertOutputFormat() if search_for == "alerts" else OutputFormat()
8+
def create_search_args(search_for, filter_args):
9+
incompatible_args = create_incompatible_search_args(search_for)
10+
filter_args.update(incompatible_args)
11+
if search_for == SEARCH_FOR_FILE_EVENTS:
12+
filter_args.update(_saved_search_args())
13+
format_enum = AlertOutputFormat() if search_for == SEARCH_FOR_ALERTS else OutputFormat()
1014

1115
advanced_query_compatible_args = {
1216
SearchArguments.ADVANCED_QUERY: ArgConfig(
@@ -31,7 +35,16 @@ def create_search_args(search_for, filter_args):
3135
return filter_args
3236

3337

34-
def create_advanced_query_incompatible_search_args(search_for=None):
38+
def _saved_search_args():
39+
saved_search = ArgConfig(
40+
u"--saved-search",
41+
help=u"Limits events to those discoverable with the saved search "
42+
u"filters for the saved search with the given ID.\n"
43+
u"WARNING: Using saved search is incompatible with other query-building args.")
44+
return {u"saved_search": saved_search}
45+
46+
47+
def create_incompatible_search_args(search_for=None):
3548
"""Returns a dict of args that are incompatible with the --advanced-query flag. Any new
3649
incompatible args should go here as this is function is also used for arg validation."""
3750
args = {
@@ -60,3 +73,13 @@ def create_advanced_query_incompatible_search_args(search_for=None):
6073
),
6174
}
6275
return args
76+
77+
78+
def get_advanced_query_incompatible_search_args(search_for):
79+
incompatible_args = create_incompatible_search_args(search_for)
80+
incompatible_args.update(_saved_search_args())
81+
return incompatible_args
82+
83+
84+
def get_saved_search_incompatible_search_args(search_for):
85+
return create_incompatible_search_args(search_for)

src/code42cli/cmds/search_shared/extraction.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from code42cli.logger import get_main_cli_logger
99
from code42cli.cmds.alerts.util import get_alert_details
1010
from code42cli.util import warn_interrupt
11-
from code42cli.cmds.search_shared.args import create_advanced_query_incompatible_search_args
1211

1312
logger = get_main_cli_logger()
1413

@@ -74,18 +73,6 @@ def handle_response(response):
7473
return handlers
7574

7675

77-
def exit_if_advanced_query_used_with_other_search_args(args, search_arg_enum):
78-
incompatible_search_args_dict = create_advanced_query_incompatible_search_args()
79-
incompatible_search_args_list = list(incompatible_search_args_dict.keys())
80-
invalid_args = incompatible_search_args_list + list(search_arg_enum)
81-
for arg in invalid_args:
82-
if args.__dict__[arg]:
83-
logger.print_and_log_error(
84-
u"You cannot use --advanced-query with additional search args."
85-
)
86-
exit(1)
87-
88-
8976
def create_time_range_filter(filter_cls, begin_date=None, end_date=None):
9077
"""Creates a filter using the given filter class (must be a subclass of
9178
:class:`py42.sdk.queries.query_filter.QueryFilterTimestampField`) and date args. Returns

src/code42cli/cmds/securitydata/extraction.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33

44
from code42cli.cmds.search_shared.enums import (
55
ExposureType as ExposureTypeOptions,
6-
FileEventFilterArguments,
76
)
87
from code42cli.cmds.search_shared.cursor_store import FileEventCursorStore
98
from code42cli.cmds.search_shared.extraction import (
109
verify_begin_date_requirements,
1110
create_handlers,
12-
exit_if_advanced_query_used_with_other_search_args,
1311
create_time_range_filter,
1412
)
1513
import code42cli.errors as errors
@@ -18,7 +16,7 @@
1816
logger = get_main_cli_logger()
1917

2018

21-
def extract(sdk, profile, output_logger, args):
19+
def extract(sdk, profile, output_logger, args, query=None):
2220
"""Extracts file events using the given command-line arguments.
2321
2422
Args:
@@ -29,18 +27,21 @@ def extract(sdk, profile, output_logger, args):
2927
write-to: uses a logger that logs to a file.
3028
send-to: uses a logger that sends logs to a server.
3129
args: Command line args used to build up file event query filters.
30+
query: FileEventQuery instance created from search-id of saved search.
3231
"""
3332
store = FileEventCursorStore(profile.name) if args.incremental else None
3433
handlers = create_handlers(sdk, FileEventExtractor, output_logger, store)
3534
extractor = FileEventExtractor(sdk, handlers)
36-
if args.advanced_query:
37-
exit_if_advanced_query_used_with_other_search_args(args, FileEventFilterArguments())
38-
extractor.extract_advanced(args.advanced_query)
39-
else:
35+
if not args.advanced_query and not args.saved_search:
4036
verify_begin_date_requirements(args, store)
4137
if args.type:
4238
_verify_exposure_types(args.type)
39+
if args.advanced_query:
40+
extractor.extract_advanced(args.advanced_query)
41+
else:
4342
filters = _create_file_event_filters(args)
43+
if args.saved_search:
44+
filters.extend(query._filter_group_list)
4445
extractor.extract(*filters)
4546
if handlers.TOTAL_EVENTS == 0 and not errors.ERRORED:
4647
logger.print_info(u"No results found.")

src/code42cli/cmds/securitydata/main.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from code42cli.args import ArgConfig
2+
from code42cli.parser import exit_if_mutually_exclusive_args_used_together
23
from code42cli.cmds.search_shared import logger_factory, args
34
from code42cli.cmds.search_shared.enums import (
45
FileEventFilterArguments,
@@ -8,13 +9,20 @@
89
from code42cli.cmds.securitydata.extraction import extract
910
from code42cli.cmds.search_shared.cursor_store import FileEventCursorStore
1011
from code42cli.commands import Command, SubcommandLoader
12+
from code42cli.cmds.securitydata.savedsearch.commands import SavedSearchSubCommandLoader
13+
from code42cli.cmds.search_shared.args import (
14+
SEARCH_FOR_FILE_EVENTS,
15+
get_advanced_query_incompatible_search_args,
16+
get_saved_search_incompatible_search_args,
17+
)
1118

1219

1320
class SecurityDataSubcommandLoader(SubcommandLoader):
1421
PRINT = u"print"
1522
WRITE_TO = u"write-to"
1623
SEND_TO = u"send-to"
1724
CLEAR_CHECKPOINT = u"clear-checkpoint"
25+
SAVED_SEARCH = u"saved-search"
1826

1927
def load_commands(self):
2028
"""Sets up the `security-data` subcommand with all of its subcommands."""
@@ -54,7 +62,14 @@ def load_commands(self):
5462
handler=clear_checkpoint,
5563
)
5664

57-
return [print_func, write, send, clear]
65+
saved_search = Command(
66+
self.SAVED_SEARCH,
67+
u"Manage saved searches.",
68+
subcommand_loader=SavedSearchSubCommandLoader(self.SAVED_SEARCH)
69+
70+
)
71+
72+
return [print_func, write, send, clear, saved_search]
5873

5974

6075
def clear_checkpoint(sdk, profile):
@@ -65,22 +80,48 @@ def clear_checkpoint(sdk, profile):
6580
FileEventCursorStore(profile.name).replace_stored_cursor_timestamp(None)
6681

6782

83+
def _get_incompatible_search_args(incompatible_search_args_dict):
84+
incompatible_search_args_list = list(incompatible_search_args_dict.keys())
85+
return incompatible_search_args_list + list(FileEventFilterArguments())
86+
87+
88+
def _validate_args(args):
89+
90+
if args.advanced_query:
91+
incompatible_search_args_dict = get_advanced_query_incompatible_search_args(SEARCH_FOR_FILE_EVENTS)
92+
incompatible_search_args = _get_incompatible_search_args(incompatible_search_args_dict)
93+
exit_if_mutually_exclusive_args_used_together(args, incompatible_search_args)
94+
if args.saved_search:
95+
incompatible_search_args_dict = get_saved_search_incompatible_search_args(SEARCH_FOR_FILE_EVENTS)
96+
incompatible_search_args = _get_incompatible_search_args(incompatible_search_args_dict)
97+
exit_if_mutually_exclusive_args_used_together(args, incompatible_search_args, u"--saved-search")
98+
99+
100+
def _extract(sdk, profile, logger, args):
101+
query = sdk.securitydata.savedsearches.get_query(args.saved_search) \
102+
if args.saved_search else None
103+
extract(sdk, profile, logger, args, query)
104+
105+
68106
def print_out(sdk, profile, args):
69107
"""Activates 'print' command. It gets security events and prints them to stdout."""
108+
_validate_args(args)
70109
logger = logger_factory.get_logger_for_stdout(args.format)
71-
extract(sdk, profile, logger, args)
110+
_extract(sdk, profile, logger, args)
72111

73112

74113
def write_to(sdk, profile, args):
75114
"""Activates 'write-to' command. It gets security events and writes them to the given file."""
115+
_validate_args(args)
76116
logger = logger_factory.get_logger_for_file(args.output_file, args.format)
77-
extract(sdk, profile, logger, args)
117+
_extract(sdk, profile, logger, args)
78118

79119

80120
def send_to(sdk, profile, args):
81121
"""Activates 'send-to' command. It gets security events and logs them to the given server."""
122+
_validate_args(args)
82123
logger = logger_factory.get_logger_for_server(args.server, args.protocol, args.format)
83-
extract(sdk, profile, logger, args)
124+
_extract(sdk, profile, logger, args)
84125

85126

86127
def _load_write_to_args(arg_collection):
@@ -166,5 +207,5 @@ def _load_search_args(arg_collection):
166207
help=u"Get all events including non-exposure events.",
167208
),
168209
}
169-
search_args = args.create_search_args(search_for=u"file events", filter_args=filter_args)
210+
search_args = args.create_search_args(search_for=SEARCH_FOR_FILE_EVENTS, filter_args=filter_args)
170211
arg_collection.extend(search_args)

src/code42cli/cmds/securitydata/savedsearch/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)