Skip to content

Commit 8984400

Browse files
committed
remove validation for event query filters, update file event v2 model
1 parent d211097 commit 8984400

5 files changed

Lines changed: 165 additions & 19 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ dependencies = [
101101
debug = "pytest ./tests/test_file_events.py -s -k saved_search"
102102
cov = "pytest --cov-report=term-missing --cov-config=pyproject.toml --cov=incydr"
103103
cov-report = "pytest --cov-report=xml:coverage.xml --cov-config=pyproject.toml --cov=incydr"
104-
no-cov = "cov --no-cov"
104+
no-cov = "pytest --disable-warnings"
105105

106106
[tool.coverage.run]
107107
branch = true

src/_incydr_sdk/file_events/client.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,29 @@
11
from typing import List
22

33
from pydantic import parse_obj_as
4+
from requests import HTTPError
45
from requests.adapters import HTTPAdapter
56
from urllib3 import Retry
67

8+
from ..exceptions import IncydrException
79
from .models.response import FileEventsPage
810
from .models.response import SavedSearch
911
from _incydr_sdk.queries.file_events import EventQuery
1012

1113

14+
class InvalidQueryException(IncydrException):
15+
"""Raised when the file events search endpoint returns a 400."""
16+
17+
def __init__(self, query=None):
18+
self.query = query
19+
self.message = (
20+
"400 Response Error: Invalid query. Please double check your query filters are valid. "
21+
"\nTip: Make sure you're specifying your filter fields in dot notation. "
22+
"\nFor example, filter by 'file.archiveId' to filter by the archiveId field within the file object.)"
23+
)
24+
super().__init__(self.message)
25+
26+
1227
class FileEventsV2:
1328
"""
1429
Client for `/v2/file-events` endpoints.
@@ -46,7 +61,12 @@ def search(self, query: EventQuery) -> FileEventsPage:
4661
"""
4762
self._mount_retry_adapter()
4863

49-
response = self._parent.session.post("/v2/file-events", json=query.dict())
64+
try:
65+
response = self._parent.session.post("/v2/file-events", json=query.dict())
66+
except HTTPError as err:
67+
if err.response.status_code == 400:
68+
raise InvalidQueryException(query)
69+
raise err
5070
page = FileEventsPage.parse_response(response)
5171
query.page_token = page.next_pg_token
5272
return page

src/_incydr_sdk/file_events/models/event.py

Lines changed: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@
99
from _incydr_sdk.enums.file_events import ReportType
1010

1111

12+
class UserJustification(Model):
13+
reason: Optional[str] = Field(
14+
None, title="User-select justification for temporarily allowing this action."
15+
)
16+
text: Optional[str] = Field(
17+
None,
18+
title="User-select justification for temporarily allowing this action. Only applies when reason is 'Other'.",
19+
)
20+
21+
22+
class ResponseControls(Model):
23+
preventative_control: Optional[str] = Field(
24+
None,
25+
alias="preventativeControl",
26+
example="BLOCKED",
27+
title="The preventative action applied to this event",
28+
)
29+
user_justification: Optional[UserJustification] = Field(
30+
None, alias="userJustification"
31+
)
32+
33+
1234
class AcquiredFromGit(Model):
1335
repository_email: Optional[str] = Field(
1436
None,
@@ -108,15 +130,33 @@ class Hash(Model):
108130
)
109131

110132

133+
class Extension(Model):
134+
browser: Optional[str] = Field(
135+
None, title="The web browser in which the event occurred."
136+
)
137+
version: Optional[str] = Field(
138+
None,
139+
title="The version of the Code42 Incydr extension installed when the event occurred.",
140+
)
141+
logged_in_user: Optional[str] = Field(
142+
None,
143+
alias="loggedInUser",
144+
title="The user signed in to the active tab where the event occurred. For example, the user signed in to Gmail. This may differ from the user account signed in to the browser itself.",
145+
)
146+
147+
111148
class Process(Model):
112149
executable: Optional[str] = Field(
150+
None,
113151
description="The name of the process that accessed the file, as reported by the device’s operating system. Depending on your Code42 product plan, this value may be null for some event types.",
114152
example="bash",
115153
)
116154
owner: Optional[str] = Field(
155+
None,
117156
description="The username of the process owner, as reported by the device’s operating system. Depending on your Code42 product plan, this value may be null for some event types.",
118157
example="root",
119158
)
159+
extension: Optional[Extension]
120160

121161

122162
class RemovableMedia(Model):
@@ -454,6 +494,11 @@ class File(Model):
454494
description="Unique identifier reported by the cloud provider for the file associated with the event.",
455495
example="PUL5zWLRrdudiJZ1OCWw",
456496
)
497+
mime_type: Optional[str] = Field(
498+
None,
499+
alias="mimeType",
500+
title="The MIME type of the file. For endpoint events, if the mimeTypeByBytes differs from mimeTypeByExtension, this indicates the most likely MIME type for the file. For activity observed by a web browser, this is the only MIME type reported.",
501+
)
457502
mime_type_by_bytes: Optional[str] = Field(
458503
alias="mimeTypeByBytes",
459504
description="The MIME type of the file based on its contents.",
@@ -489,6 +534,21 @@ class File(Model):
489534
description="URL reported by the cloud provider at the time the event occurred.",
490535
example="https://example.com",
491536
)
537+
archive_id: Optional[str] = Field(
538+
None,
539+
alias="archiveId",
540+
title="Unique identifier for files identified as an archive, such as .zip files.",
541+
)
542+
parent_archive_id: Optional[str] = Field(
543+
None,
544+
alias="parentArchiveId",
545+
title="For files contained within an archive (such as a .zip file), the unique identifier for that archive; searching on parentArchiveID returns events for all files contained within that archive",
546+
)
547+
password_protected: Optional[bool] = Field(
548+
None,
549+
alias="passwordProtected",
550+
title="Indicates if this file is password protected.",
551+
)
492552

493553

494554
class Risk(Model):
@@ -556,6 +616,11 @@ class Source(Model):
556616
description="The IP address of the user's device on your internal network, including Network interfaces, Virtual Network Interface controllers (NICs), and Loopback/non-routable addresses.",
557617
example=["127.0.0.1", "127.0.0.2"],
558618
)
619+
remote_hostname: Optional[str] = Field(
620+
None,
621+
alias="remoteHostname",
622+
title="For events where a file transfer tool was used, the source hostname.",
623+
)
559624
removable_media: Optional[RemovableMedia] = Field(
560625
alias="removableMedia",
561626
description="Metadata about the removable media source.",
@@ -638,18 +703,44 @@ class Event(Model):
638703
description="Sharing types added by this event.",
639704
example=["SharedViaLink"],
640705
)
641-
vector: Optional[str]
706+
vector: Optional[str] = Field(
707+
None,
708+
example="GIT_PUSH",
709+
title="The method of file movement. For example: UPLOADED, DOWNLOADED, EMAILED.",
710+
)
642711

643712

644713
class Git(Model):
645-
event_id: Optional[str] = Field(None, alias="eventId")
646-
last_commit_hash: Optional[str] = Field(None, alias="lastCommitHash")
647-
repository_email: Optional[str] = Field(None, alias="repositoryEmail")
714+
event_id: Optional[str] = Field(
715+
None,
716+
alias="eventId",
717+
title="A global unique identifier (GUID) generated by Incydr for this Git event. All files associated with this event have the same Git event ID. A single Git event can be associated with multiple file events.",
718+
)
719+
last_commit_hash: Optional[str] = Field(
720+
None,
721+
alias="lastCommitHash",
722+
title="Hash value from the most recent commit in this Git event.",
723+
)
724+
repository_email: Optional[str] = Field(
725+
None,
726+
alias="repositoryEmail",
727+
title="The email address specified by the user who performed the Git event. This is a user-defined value and may differ from the credentials used to sign in to Git.",
728+
)
648729
repository_endpoint_path: Optional[str] = Field(
649-
None, alias="repositoryEndpointPath"
730+
None,
731+
alias="repositoryEndpointPath",
732+
title="File path of the local Git repository on the user's endpoint.",
733+
)
734+
repository_uri: Optional[str] = Field(
735+
None,
736+
alias="repositoryUri",
737+
title="Uniform Resource Identifier (URI) for the Git repository.",
738+
)
739+
repository_user: Optional[str] = Field(
740+
None,
741+
alias="repositoryUser",
742+
title="The username specified by the user who performed the Git event. This is a user-defined value and may differ from the credentials used to sign in to Git.",
650743
)
651-
repository_uri: Optional[str] = Field(None, alias="repositoryUri")
652-
repository_user: Optional[str] = Field(None, alias="repositoryUser")
653744

654745

655746
class FileEventV2(ResponseModel):
@@ -688,6 +779,10 @@ class FileEventV2(ResponseModel):
688779
report: Optional[Report] = Field(
689780
description="Metadata for reports from 3rd party sources, such Salesforce downloads.",
690781
)
782+
response_controls: Optional[ResponseControls] = Field(
783+
alias="responseControls",
784+
description="Metadata about preventative actions applied to file activity. Only applies to events for users on a preventative watchlist.",
785+
)
691786
risk: Optional[Risk] = Field(
692787
description="Risk factor metadata.",
693788
)

src/_incydr_sdk/queries/file_events.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141

4242
class Filter(BaseModel):
43-
term: EventSearchTerm
43+
term: str
4444
operator: Operator
4545
value: Optional[Union[int, str]]
4646

@@ -53,8 +53,9 @@ def _validate_enums(cls, values: dict): # noqa `root_validator` is a classmetho
5353
operator = values.get("operator")
5454
value = values.get("value")
5555

56+
# 11-13-2024 - Removing strict filter term requirements to avoid breaking on new fields
5657
# make sure `term` is valid enum value
57-
EventSearchTerm(term)
58+
# EventSearchTerm(term)
5859

5960
if operator in (Operator.EXISTS, Operator.DOES_NOT_EXIST):
6061
values["value"] = None
@@ -66,15 +67,16 @@ def _validate_enums(cls, values: dict): # noqa `root_validator` is a classmetho
6667
f"`IS` and `IS_NOT` filters require a `str | int` value, got term={term}, operator={operator}, value={value}."
6768
)
6869

70+
# 11-13-2024 - Removing strict filter term requirements to avoid breaking on new fields
6971
# check that value is a valid enum for that search term
70-
enum = _term_enum_map.get(term)
71-
if enum:
72-
try:
73-
values.update(
74-
{"value": enum[value.upper()]}
75-
) # check if enum name is passed as a value
76-
except KeyError:
77-
enum(value)
72+
# enum = _term_enum_map.get(term)
73+
# if enum:
74+
# try:
75+
# values.update(
76+
# {"value": enum[value.upper()]}
77+
# ) # check if enum name is passed as a value
78+
# except KeyError:
79+
# enum(value)
7880

7981
return values
8082

tests/queries/test_event_query.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ def test_event_query_is_when_no_values_raises_error():
132132
assert e.value.args[0] == "equals() requires at least one value."
133133

134134

135+
# TODO
136+
@pytest.mark.skip(
137+
reason="11-13-2024 - Removing strict filter term requirements to avoid breaking on new fields"
138+
)
135139
def test_event_query_is_when_invalid_value_for_term_raises_type_error():
136140
with pytest.raises(ValueError) as e:
137141

@@ -144,6 +148,31 @@ def test_event_query_is_when_invalid_value_for_term_raises_type_error():
144148
)
145149

146150

151+
def test_event_query_allows_any_search_term_and_creates_expected_filter_group():
152+
q = EventQuery(TEST_START_DATE).equals("file-event-field", "value")
153+
expected = FilterGroup(
154+
filterClause="AND",
155+
filters=[
156+
Filter(term="file-event-field", operator="IS", value="value"),
157+
],
158+
)
159+
assert q.groups.pop() == expected
160+
161+
162+
def test_event_query_allows_any_string_values_and_creates_expected_filter_group():
163+
q = EventQuery(TEST_START_DATE).equals(
164+
"file.category", ["Document", "string-value"]
165+
)
166+
expected = FilterGroup(
167+
filterClause="OR",
168+
filters=[
169+
Filter(term="file.category", operator="IS", value="Document"),
170+
Filter(term="file.category", operator="IS", value="string-value"),
171+
],
172+
)
173+
assert q.groups.pop() == expected
174+
175+
147176
def test_event_query_exists_creates_expected_filter_group():
148177
q = EventQuery(start_date=TEST_START_DATE).exists("event.action")
149178
expected = FilterGroup(

0 commit comments

Comments
 (0)