Skip to content

Commit

Permalink
Updates for API and fix for util:string operation (#329)
Browse files Browse the repository at this point in the history
APP-4522 - [API] Updated batch module to support external date fields
APP-4521 - [API] Updated support for IR endpoints
APP-4520 - [Util] Fixed TCEX string operations trim method
  • Loading branch information
bpurdy-tc authored Jun 12, 2024
1 parent 2014958 commit cb964be
Show file tree
Hide file tree
Showing 23 changed files with 453 additions and 79 deletions.
4 changes: 4 additions & 0 deletions release_notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Release Notes

## 4.0.6

- APP-4522 - [API] Updated batch module to support external date fields
- APP-4521 - [API] Updated support for IR endpoints
- APP-4520 - [Util] Fixed TCEX string operations trim method
- APP-4482 - [API] Updated transforms to support new static methods.
- APP-4472 - [NAICS] Added NAICS module for lookup by id or name.

Expand Down
74 changes: 73 additions & 1 deletion tcex/api/tc/v2/batch/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,16 @@ def add_key_value(self, key: str, value: str):
value: The field value to add to the JSON batch data.
"""
key = self._metadata_map.get(key, key)
if key in ['dateAdded', 'lastModified']:

if key in {
'dateAdded',
'lastModified',
'firstSeen',
'lastSeen',
'externalDateCreated',
'externalDateExpires',
'externalLastModified',
}:
self._indicator_data[key] = self.util.any_to_datetime(value).strftime(
'%Y-%m-%dT%H:%M:%SZ'
)
Expand Down Expand Up @@ -293,6 +302,69 @@ def last_modified(self, last_modified: str):
'%Y-%m-%dT%H:%M:%SZ'
)

@property
def first_seen(self) -> str:
"""Return Indicator firstSeen."""
return self._indicator_data.get('firstSeen') # type: ignore

@first_seen.setter
def first_seen(self, first_seen: str):
"""Set Indicator firstSeen."""
self._indicator_data['firstSeen'] = self.util.any_to_datetime(first_seen).strftime(
'%Y-%m-%dT%H:%M:%SZ'
)

@property
def last_seen(self) -> str:
"""Return Indicator lastSeen."""
return self._indicator_data.get('lastSeen') # type: ignore

@last_seen.setter
def last_seen(self, last_seen: str):
"""Set Indicator lastSeen."""
self._indicator_data['lastSeen'] = self.util.any_to_datetime(last_seen).strftime(
'%Y-%m-%dT%H:%M:%SZ'
)

@property
def external_date_created(self) -> str:
"""Return Indicator externalDateCreated."""
return self._indicator_data.get('externalDateCreated') # type: ignore

@external_date_created.setter
def external_date_created(self, external_date_created: str):
"""Set Indicator externalDateCreated."""
external_date_created = self.util.any_to_datetime(external_date_created).strftime(
'%Y-%m-%dT%H:%M:%SZ'
)
self._indicator_data['externalDateCreated'] = external_date_created

@property
def external_date_expires(self) -> str:
"""Return Indicator externalDateExpires."""
return self._indicator_data.get('externalDateExpires') # type: ignore

@external_date_expires.setter
def external_date_expires(self, external_date_expires: str):
"""Set Indicator externalDateExpires."""
external_date_expires = self.util.any_to_datetime(external_date_expires).strftime(
'%Y-%m-%dT%H:%M:%SZ'
)
self._indicator_data['externalDateExpires'] = external_date_expires

@property
def external_date_last_modified(self) -> str:
"""Return Indicator externalLastModified."""
return self._indicator_data.get('externalLastModified') # type: ignore

@external_date_expires.setter
def external_date_expires(self, external_date_last_modified: str):
"""Set Indicator externalLastModified."""
external_date_last_modified = self.util.any_to_datetime(
external_date_last_modified
).strftime('%Y-%m-%dT%H:%M:%SZ')
self._indicator_data['externalLastModified'] = external_date_last_modified

def occurrence(
self,
file_name: str | None = None,
Expand Down
1 change: 1 addition & 0 deletions tcex/api/tc/v3/_gen/_gen_args_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def gen_args(

# get properties from schema
schema = model.schema(by_alias=False)
properties = {}
if '$ref' in schema:
model_name = schema.get('$ref').split('/')[-1]
properties = schema.get('definitions').get(model_name).get('properties')
Expand Down
33 changes: 33 additions & 0 deletions tcex/api/tc/v3/_gen/_gen_filter_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,37 @@ def _gen_code_has_security_label_method(self) -> list:
'',
]

def _gen_code_has_intel_requirement_method(self) -> list:
self._add_tql_imports()
_code = [
f'{self.i1}@property',
f'{self.i1}def has_intel_requirement(self):',
f'{self.i2}"""Return **IntelRequirementFilter** for further filtering."""',
]
if self.type_ != 'intel_requirements':
_code.extend(
[
f'{self.i2}# first-party',
(
f'{self.i2}from tcex.api.tc.v3.intel_requirements.intel_requirement_filter '
'import IntelRequirementFilter'
),
'',
]
)
_code.extend(
[
f'{self.i2}intel_requirements = IntelRequirementFilter(Tql())',
(
f'''{self.i2}self._tql.add_filter('hasIntelRequirement', '''
'''TqlOperator.EQ, intel_requirements, TqlType.SUB_QUERY)'''
),
f'{self.i2}return intel_requirements',
'',
]
)
return _code

def _gen_code_has_victim_asset_method(self) -> list:
"""Return code for has_victim_asset TQL filter methods."""
self._add_tql_imports()
Expand Down Expand Up @@ -526,6 +557,8 @@ def gen_class_methods(self):
_filter_class.extend(self._gen_code_has_victim_method())
elif f.keyword.snake_case() == 'has_victim_asset':
_filter_class.extend(self._gen_code_has_victim_asset_method())
elif f.keyword.snake_case() == 'has_intel_requirement':
_filter_class.extend(self._gen_code_has_intel_requirement_method())
else:
_filter_class.extend(self._gen_code_generic_method(f))

Expand Down
30 changes: 30 additions & 0 deletions tcex/api/tc/v3/attribute_types/attribute_type_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,36 @@ def default(self, operator: Enum, default: bool):
"""
self._tql.add_filter('default', operator, default, TqlType.BOOLEAN)

def default_owner_id(self, operator: Enum, default_owner_id: int | list):
"""Filter Attribute settings owner id based on **defaultOwnerId** keyword.
Args:
operator: The operator enum for the filter.
default_owner_id: The owner id of the attribute type settings.
"""
if isinstance(default_owner_id, list) and operator not in self.list_types:
raise RuntimeError(
'Operator must be CONTAINS, NOT_CONTAINS, IN'
'or NOT_IN when filtering on a list of values.'
)

self._tql.add_filter('defaultOwnerId', operator, default_owner_id, TqlType.INTEGER)

def default_type(self, operator: Enum, default_type: list | str):
"""Filter Defaulted Type based on **defaultType** keyword.
Args:
operator: The operator enum for the filter.
default_type: The data type(s) that the attribute type is defaulted for.
"""
if isinstance(default_type, list) and operator not in self.list_types:
raise RuntimeError(
'Operator must be CONTAINS, NOT_CONTAINS, IN'
'or NOT_IN when filtering on a list of values.'
)

self._tql.add_filter('defaultType', operator, default_type, TqlType.STRING)

def description(self, operator: Enum, description: list | str):
"""Filter Description based on **description** keyword.
Expand Down
1 change: 1 addition & 0 deletions tcex/api/tc/v3/groups/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Group(ObjectABC):
due_date (str, kwargs): The date and time that the Task is due.
escalation_date (str, kwargs): The escalation date and time.
event_date (str, kwargs): The date and time that the incident or event was first created.
event_type (str, kwargs): The identification of an event type.
external_date_added (str, kwargs): The date and time that the item was first created
externally.
external_date_expires (str, kwargs): The date and time the item expires externally.
Expand Down
23 changes: 10 additions & 13 deletions tcex/api/tc/v3/groups/group_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,22 +409,19 @@ def has_intel_query(self, operator: Enum, has_intel_query: int | list):

self._tql.add_filter('hasIntelQuery', operator, has_intel_query, TqlType.INTEGER)

def has_intel_requirement(self, operator: Enum, has_intel_requirement: int | list):
"""Filter Associated Intel Requirement based on **hasIntelRequirement** keyword.
Args:
operator: The operator enum for the filter.
has_intel_requirement: A nested query for association to intel requirements.
"""
if isinstance(has_intel_requirement, list) and operator not in self.list_types:
raise RuntimeError(
'Operator must be CONTAINS, NOT_CONTAINS, IN'
'or NOT_IN when filtering on a list of values.'
)
@property
def has_intel_requirement(self):
"""Return **IntelRequirementFilter** for further filtering."""
# first-party
from tcex.api.tc.v3.intel_requirements.intel_requirement_filter import (
IntelRequirementFilter,
)

intel_requirements = IntelRequirementFilter(Tql())
self._tql.add_filter(
'hasIntelRequirement', operator, has_intel_requirement, TqlType.INTEGER
'hasIntelRequirement', TqlOperator.EQ, intel_requirements, TqlType.SUB_QUERY
)
return intel_requirements

@property
def has_security_label(self):
Expand Down
23 changes: 10 additions & 13 deletions tcex/api/tc/v3/indicators/indicator_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,22 +503,19 @@ def has_indicator(self):
self._tql.add_filter('hasIndicator', TqlOperator.EQ, indicators, TqlType.SUB_QUERY)
return indicators

def has_intel_requirement(self, operator: Enum, has_intel_requirement: int | list):
"""Filter Associated Intel Requirement based on **hasIntelRequirement** keyword.
Args:
operator: The operator enum for the filter.
has_intel_requirement: A nested query for association to intel requirements.
"""
if isinstance(has_intel_requirement, list) and operator not in self.list_types:
raise RuntimeError(
'Operator must be CONTAINS, NOT_CONTAINS, IN'
'or NOT_IN when filtering on a list of values.'
)
@property
def has_intel_requirement(self):
"""Return **IntelRequirementFilter** for further filtering."""
# first-party
from tcex.api.tc.v3.intel_requirements.intel_requirement_filter import (
IntelRequirementFilter,
)

intel_requirements = IntelRequirementFilter(Tql())
self._tql.add_filter(
'hasIntelRequirement', operator, has_intel_requirement, TqlType.INTEGER
'hasIntelRequirement', TqlOperator.EQ, intel_requirements, TqlType.SUB_QUERY
)
return intel_requirements

@property
def has_security_label(self):
Expand Down
35 changes: 31 additions & 4 deletions tcex/api/tc/v3/intel_requirements/intel_req_type_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,38 @@
# pylint: disable=no-member,no-self-argument,wrong-import-position

# third-party
from pydantic import BaseModel
from pydantic import Extra, Field

# first-party
from tcex.api.tc.v3.v3_model_abc import V3ModelABC
from tcex.util import Util

class IntelReqTypeModel(BaseModel):

class IntelReqTypeModel(
V3ModelABC,
alias_generator=Util().snake_to_camel,
extra=Extra.allow,
title='Intel Requirement Type Model',
validate_assignment=True,
):
"""Model Definition"""

name: str | None
description: str | None
id: int | None = Field( # pyright: ignore [reportGeneralTypeIssues]
None,
methods=['POST', 'PUT'],
description='The ID of the item.',
read_only=True,
title='id',
)
name: str | None = Field(
None,
description='Name of the Intel Requirement Type.',
read_only=True,
title='name',
)
description: str | None = Field(
None,
description='Description of the Intel Requirement Type.',
read_only=True,
title='description',
)
20 changes: 6 additions & 14 deletions tcex/api/tc/v3/intel_requirements/intel_requirement_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,22 +412,14 @@ def has_intel_query(self, operator: Enum, has_intel_query: int | list):

self._tql.add_filter('hasIntelQuery', operator, has_intel_query, TqlType.INTEGER)

def has_intel_requirement(self, operator: Enum, has_intel_requirement: int | list):
"""Filter Associated Intel Requirement based on **hasIntelRequirement** keyword.
Args:
operator: The operator enum for the filter.
has_intel_requirement: A nested query for association to intel requirements.
"""
if isinstance(has_intel_requirement, list) and operator not in self.list_types:
raise RuntimeError(
'Operator must be CONTAINS, NOT_CONTAINS, IN'
'or NOT_IN when filtering on a list of values.'
)

@property
def has_intel_requirement(self):
"""Return **IntelRequirementFilter** for further filtering."""
intel_requirements = IntelRequirementFilter(Tql())
self._tql.add_filter(
'hasIntelRequirement', operator, has_intel_requirement, TqlType.INTEGER
'hasIntelRequirement', TqlOperator.EQ, intel_requirements, TqlType.SUB_QUERY
)
return intel_requirements

@property
def has_security_label(self):
Expand Down
40 changes: 35 additions & 5 deletions tcex/api/tc/v3/intel_requirements/keyword_section_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,28 @@
# pylint: disable=no-member,no-self-argument,wrong-import-position

# third-party
from pydantic import BaseModel
from pydantic import Field

# first-party
from tcex.api.tc.v3.v3_model_abc import V3ModelABC
from tcex.util import Util


class KeywordModel(BaseModel):
class KeywordModel(
V3ModelABC,
title='Keyword Model',
alias_generator=Util().snake_to_camel,
validate_assignment=True,
):
"""Model Definition"""

value: str | None
value: str | None = Field(
None,
description='The value of the keyword.',
methods=['POST', 'PUT'],
read_only=False,
title='value',
)


class KeywordSectionModel(
Expand All @@ -33,5 +44,24 @@ class KeywordSectionModel(
}
"""

compareValue: str | None
keywords: list[KeywordModel] | None
section_number: int | None = Field(
None,
description='The section number of the keyword section.',
methods=['POST', 'PUT'],
read_only=False,
title='sectionNumber',
)
compareValue: str | None = Field(
None,
description='The compare value for the keyword section.',
methods=['POST', 'PUT'],
read_only=False,
title='compareValue',
)
keywords: list[KeywordModel] | None = Field(
None,
description='A list of keywords for the keyword section.',
methods=['POST', 'PUT'],
read_only=False,
title='keywords',
)
Loading

0 comments on commit cb964be

Please sign in to comment.