mirror of https://github.com/MISP/PyMISP
Merge branch 'main' of github.com:misp/pymisp
commit
36145fcd84
|
@ -140,7 +140,7 @@ subject_process = (strip |
|
|||
##
|
||||
## Tags that will be used for the changelog must match this regexp.
|
||||
##
|
||||
tag_filter_regexp = r'^v[0-9]+\.[0-9]+\.[0-9]+(\.[0-9])*$'
|
||||
tag_filter_regexp = r'^v2.5.[0-9]+(\.[0-9])*$|^v2.4.(1)?[0-9]{1,2}$'
|
||||
|
||||
|
||||
## ``unreleased_version_label`` is a string
|
||||
|
@ -189,4 +189,3 @@ output_engine = rest_py
|
|||
## This option tells git-log whether to include merge commits in the log.
|
||||
## The default is to include them.
|
||||
include_merge = False
|
||||
|
||||
|
|
1137
CHANGELOG.txt
1137
CHANGELOG.txt
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
@ -11,6 +10,11 @@ try:
|
|||
except ImportError:
|
||||
with_distribution = False
|
||||
|
||||
try:
|
||||
from settings import with_signatures
|
||||
except ImportError:
|
||||
with_signatures = False
|
||||
|
||||
try:
|
||||
from settings import with_local_tags
|
||||
except ImportError:
|
||||
|
@ -39,20 +43,39 @@ def init():
|
|||
return ExpandedPyMISP(url, key, ssl)
|
||||
|
||||
|
||||
def saveEvent(event):
|
||||
def saveEvent(event, misp):
|
||||
stringified_event = json.dumps(event, indent=2)
|
||||
if with_signatures and event['Event'].get('protected'):
|
||||
signature = getSignature(stringified_event, misp)
|
||||
try:
|
||||
with open(os.path.join(outputdir, f'{event["Event"]["uuid"]}.asc'), 'w') as f:
|
||||
f.write(signature)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit('Could not create the event signature dump.')
|
||||
|
||||
try:
|
||||
with open(os.path.join(outputdir, f'{event["Event"]["uuid"]}.json'), 'w') as f:
|
||||
json.dump(event, f, indent=2)
|
||||
f.write(stringified_event)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit('Could not create the event dump.')
|
||||
|
||||
|
||||
def getSignature(stringified_event, misp):
|
||||
try:
|
||||
signature = misp.sign_blob(stringified_event)
|
||||
return signature
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit('Could not get the signature for the event from the MISP instance. Perhaps the user does not have the necessary permissions.')
|
||||
|
||||
|
||||
def saveHashes(hashes):
|
||||
try:
|
||||
with open(os.path.join(outputdir, 'hashes.csv'), 'w') as hashFile:
|
||||
for element in hashes:
|
||||
hashFile.write('{},{}\n'.format(element[0], element[1]))
|
||||
hashFile.write(f'{element[0]},{element[1]}\n')
|
||||
except Exception as e:
|
||||
print(e)
|
||||
sys.exit('Could not create the quick hash lookup file.')
|
||||
|
@ -79,6 +102,7 @@ if __name__ == '__main__':
|
|||
sys.exit("No events returned.")
|
||||
manifest = {}
|
||||
hashes = []
|
||||
signatures = []
|
||||
counter = 1
|
||||
total = len(events)
|
||||
for event in events:
|
||||
|
@ -97,7 +121,7 @@ if __name__ == '__main__':
|
|||
continue
|
||||
hashes += [[h, e.uuid] for h in e_feed['Event'].pop('_hashes')]
|
||||
manifest.update(e_feed['Event'].pop('_manifest'))
|
||||
saveEvent(e_feed)
|
||||
saveEvent(e_feed, misp)
|
||||
print("Event " + str(counter) + "/" + str(total) + " exported.")
|
||||
counter += 1
|
||||
saveManifest(manifest)
|
||||
|
|
|
@ -54,3 +54,6 @@ with_distribution = False
|
|||
|
||||
# Include the exportable local tags along with the global tags. The default is True.
|
||||
with_local_tags = True
|
||||
|
||||
# Include signatures for protected events. This will allow MISP to ingest and update protected events from the feed. It requires perm_server_sign to be set to true in the user's role on MISP's side.
|
||||
with_signatures = False
|
File diff suppressed because it is too large
Load Diff
|
@ -3541,6 +3541,14 @@ class PyMISP:
|
|||
response = self._prepare_request('POST', url, data=to_post)
|
||||
return response
|
||||
|
||||
def sign_blob(self, blob: str) -> str:
|
||||
"""Sign a blob
|
||||
|
||||
:param blob: blob to sign
|
||||
"""
|
||||
response = self._prepare_request('POST', '/cryptographicKeys/serverSign', data=blob)
|
||||
return self._check_response(response, lenient_response_type=True)
|
||||
|
||||
# ## END Others ###
|
||||
|
||||
# ## BEGIN Statistics ###
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit dcf0c3febcacc3b6dd8d0d390b7ec59254cd46ba
|
||||
Subproject commit a2e4bbc9615ba0e1ddda3322dcc6c72d5a0e76e5
|
|
@ -60,28 +60,37 @@ class AnalystDataBehaviorMixin(AbstractMISP):
|
|||
|
||||
def add_note(self, note: str, language: str | None = None, **kwargs) -> MISPNote: # type: ignore[no-untyped-def]
|
||||
the_note = MISPNote()
|
||||
the_note.from_dict(note=note, language=language,
|
||||
object_uuid=self.uuid, object_type=self.analyst_data_object_type,
|
||||
**kwargs)
|
||||
object_uuid = kwargs.pop('object_uuid', self.uuid)
|
||||
object_type = kwargs.pop('object_type', self.analyst_data_object_type)
|
||||
the_note.from_dict(
|
||||
note=note, language=language, object_uuid=object_uuid,
|
||||
object_type=object_type, contained=True, parent=self, **kwargs
|
||||
)
|
||||
self.notes.append(the_note)
|
||||
self.edited = True
|
||||
return the_note
|
||||
|
||||
def add_opinion(self, opinion: int, comment: str | None = None, **kwargs) -> MISPOpinion: # type: ignore[no-untyped-def]
|
||||
the_opinion = MISPOpinion()
|
||||
the_opinion.from_dict(opinion=opinion, comment=comment,
|
||||
object_uuid=self.uuid, object_type=self.analyst_data_object_type,
|
||||
**kwargs)
|
||||
object_uuid = kwargs.pop('object_uuid', self.uuid)
|
||||
object_type = kwargs.pop('object_type', self.analyst_data_object_type)
|
||||
the_opinion.from_dict(
|
||||
opinion=opinion, comment=comment, object_uuid=object_uuid,
|
||||
object_type=object_type, contained=True, parent=self, **kwargs
|
||||
)
|
||||
self.opinions.append(the_opinion)
|
||||
self.edited = True
|
||||
return the_opinion
|
||||
|
||||
def add_relationship(self, related_object_type: AbstractMISP | str, related_object_uuid: str | None, relationship_type: str, **kwargs) -> MISPRelationship: # type: ignore[no-untyped-def]
|
||||
the_relationship = MISPRelationship()
|
||||
the_relationship.from_dict(related_object_type=related_object_type, related_object_uuid=related_object_uuid,
|
||||
relationship_type=relationship_type,
|
||||
object_uuid=self.uuid, object_type=self.analyst_data_object_type,
|
||||
**kwargs)
|
||||
the_relationship.from_dict(
|
||||
related_object_type=related_object_type,
|
||||
related_object_uuid=related_object_uuid,
|
||||
relationship_type=relationship_type, object_uuid=self.uuid,
|
||||
object_type=self.analyst_data_object_type, contained=True,
|
||||
parent=self, **kwargs
|
||||
)
|
||||
self.relationships.append(the_relationship)
|
||||
self.edited = True
|
||||
return the_relationship
|
||||
|
@ -93,12 +102,8 @@ class AnalystDataBehaviorMixin(AbstractMISP):
|
|||
relationships = kwargs.pop('Relationship', [])
|
||||
super().from_dict(**kwargs)
|
||||
for note in notes:
|
||||
note.pop('object_uuid', None)
|
||||
note.pop('object_type', None)
|
||||
self.add_note(**note)
|
||||
for opinion in opinions:
|
||||
opinion.pop('object_uuid', None)
|
||||
opinion.pop('object_type', None)
|
||||
self.add_opinion(**opinion)
|
||||
for relationship in relationships:
|
||||
relationship.pop('object_uuid', None)
|
||||
|
@ -1559,7 +1564,8 @@ class MISPGalaxy(AbstractMISP):
|
|||
class MISPEvent(AnalystDataBehaviorMixin):
|
||||
|
||||
_fields_for_feed: set[str] = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp',
|
||||
'publish_timestamp', 'published', 'date', 'extends_uuid'}
|
||||
'publish_timestamp', 'published', 'date', 'extends_uuid',
|
||||
'protected'}
|
||||
|
||||
_analyst_data_object_type = 'Event'
|
||||
|
||||
|
@ -1581,6 +1587,7 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
self.EventReport: list[MISPEventReport] = []
|
||||
self.Tag: list[MISPTag] = []
|
||||
self.Galaxy: list[MISPGalaxy] = []
|
||||
self.CryptographicKey: list[MISPCryptographicKey] = []
|
||||
|
||||
self.publish_timestamp: float | int | datetime
|
||||
self.timestamp: float | int | datetime
|
||||
|
@ -1600,6 +1607,8 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
|
||||
def _set_default(self) -> None:
|
||||
"""There are a few keys that could, or need to be set by default for the feed generator"""
|
||||
if not hasattr(self, 'protected'):
|
||||
self.protected = False
|
||||
if not hasattr(self, 'published'):
|
||||
self.published = True
|
||||
if not hasattr(self, 'uuid'):
|
||||
|
@ -1649,13 +1658,14 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
to_return += attribute.hash_values(algorithm)
|
||||
return to_return
|
||||
|
||||
def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True) -> dict[str, Any]:
|
||||
def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True, with_cryptographic_keys: bool = True) -> dict[str, Any]:
|
||||
""" Generate a json output for MISP Feed.
|
||||
|
||||
:param valid_distributions: only makes sense if the distribution key is set; i.e., the event is exported from a MISP instance.
|
||||
:param with_distribution: exports distribution and Sharing Group info; otherwise all SharingGroup information is discarded (protecting privacy)
|
||||
:param with_local_tags: tag export includes local exportable tags along with global exportable tags
|
||||
:param with_event_reports: include event reports in the returned MISP event
|
||||
:param with_cryptographic_keys: include the associated cryptographic keys in the returned protected MISP event
|
||||
"""
|
||||
required = ['info', 'Orgc']
|
||||
for r in required:
|
||||
|
@ -1720,6 +1730,13 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
event_report.pop('sharing_group_id', None)
|
||||
to_return['EventReport'].append(event_report.to_dict())
|
||||
|
||||
if with_cryptographic_keys and self.cryptographic_keys:
|
||||
to_return['CryptographicKey'] = []
|
||||
for cryptographic_key in self.cryptographic_keys:
|
||||
cryptographic_key.pop('parent_id', None)
|
||||
cryptographic_key.pop('id', None)
|
||||
to_return['CryptographicKey'].append(cryptographic_key.to_dict())
|
||||
|
||||
return {'Event': to_return}
|
||||
|
||||
@property
|
||||
|
@ -1756,6 +1773,10 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
def event_reports(self) -> list[MISPEventReport]:
|
||||
return self.EventReport
|
||||
|
||||
@property
|
||||
def cryptographic_keys(self) -> list[MISPCryptographicKey]:
|
||||
return self.CryptographicKey
|
||||
|
||||
@property
|
||||
def shadow_attributes(self) -> list[MISPShadowAttribute]:
|
||||
return self.ShadowAttribute
|
||||
|
@ -1891,6 +1912,8 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
[self.add_galaxy(**e) for e in kwargs.pop('Galaxy')]
|
||||
if kwargs.get('EventReport'):
|
||||
[self.add_event_report(**e) for e in kwargs.pop('EventReport')]
|
||||
if kwargs.get('CryptographicKey'):
|
||||
[self.add_cryprographic_key(**e) for e in kwargs.pop('CryptographicKey')]
|
||||
|
||||
# All other keys
|
||||
if kwargs.get('id'):
|
||||
|
@ -2041,6 +2064,15 @@ class MISPEvent(AnalystDataBehaviorMixin):
|
|||
self.edited = True
|
||||
return event_report
|
||||
|
||||
def add_cryprographic_key(self, parent_type: str, key_data: str, type: str, uuid: str, fingerprint: str, timestamp: str, **kwargs) -> MISPCryptographicKey: # type: ignore[no-untyped-def]
|
||||
"""Add a Cryptographic Key. parent_type, key_data, type, uuid, fingerprint, timestamp are required but you can pass all
|
||||
other parameters supported by MISPEventReport"""
|
||||
cryptographic_key = MISPCryptographicKey()
|
||||
cryptographic_key.from_dict(parent_type=parent_type, key_data=key_data, type=type, uuid=uuid, fingerprint=fingerprint, timestamp=timestamp, **kwargs)
|
||||
self.cryptographic_keys.append(cryptographic_key)
|
||||
self.edited = True
|
||||
return cryptographic_key
|
||||
|
||||
def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def]
|
||||
"""Add a galaxy and sub-clusters into an event, either by passing
|
||||
a MISPGalaxy or a dictionary.
|
||||
|
@ -2226,6 +2258,13 @@ class MISPWarninglist(AbstractMISP):
|
|||
super().from_dict(**kwargs)
|
||||
|
||||
|
||||
class MISPCryptographicKey(AbstractMISP):
|
||||
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if 'CryptographicKey' in kwargs:
|
||||
kwargs = kwargs['CryptographicKey']
|
||||
super().from_dict(**kwargs)
|
||||
|
||||
|
||||
class MISPTaxonomy(AbstractMISP):
|
||||
|
||||
enabled: bool
|
||||
|
@ -2489,6 +2528,10 @@ class MISPAnalystData(AbstractMISP):
|
|||
'Object', 'Note', 'Opinion', 'Relationship', 'Organisation',
|
||||
'SharingGroup'}
|
||||
|
||||
@property
|
||||
def analyst_data_object_type(self) -> str:
|
||||
return self._analyst_data_object_type
|
||||
|
||||
@property
|
||||
def org(self) -> MISPOrganisation:
|
||||
return self.Org
|
||||
|
@ -2504,6 +2547,10 @@ class MISPAnalystData(AbstractMISP):
|
|||
else:
|
||||
raise PyMISPError('Orgc must be of type MISPOrganisation.')
|
||||
|
||||
@property
|
||||
def parent(self) -> MISPAttribute | MISPEvent | MISPEventReport | MISPObject:
|
||||
return self.__parent
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is MISPAnalystData:
|
||||
raise TypeError(f"only children of '{cls.__name__}' may be instantiated")
|
||||
|
@ -2518,8 +2565,54 @@ class MISPAnalystData(AbstractMISP):
|
|||
self.created: float | int | datetime
|
||||
self.modified: float | int | datetime
|
||||
self.SharingGroup: MISPSharingGroup
|
||||
self._analyst_data_object_type: str # Must be defined in the child class
|
||||
|
||||
def add_note(self, note: str, language: str | None = None, object_uuid: str | None = None, object_type: str | None = None, parent: MISPEvent | MISPAttribute | MISPObject | MISPEventReport | None = None, **kwargs: dict[str, Any]) -> MISPNote:
|
||||
misp_note = MISPNote()
|
||||
if object_uuid is None:
|
||||
object_uuid = self.uuid
|
||||
if object_type is None:
|
||||
object_type = self.analyst_data_object_type
|
||||
if parent is None and hasattr(self, 'parent'):
|
||||
parent = self.parent
|
||||
misp_note.from_dict(
|
||||
note=note, language=language, object_uuid=object_uuid,
|
||||
object_type=object_type, parent=parent, contained=True, **kwargs
|
||||
)
|
||||
if parent is None:
|
||||
if not hasattr(self, 'Note'):
|
||||
self.Note: list[MISPNote] = []
|
||||
self.Note.append(misp_note)
|
||||
else:
|
||||
self.parent.notes.append(misp_note)
|
||||
self.edited = True
|
||||
return misp_note
|
||||
|
||||
def add_opinion(self, opinion: int, comment: str | None = None, object_uuid: str | None = None, object_type: str | None = None, parent: MISPEvent | MISPAttribute | MISPObject | MISPEventReport | None = None, **kwargs: dict[str, Any]) -> MISPOpinion:
|
||||
misp_opinion = MISPOpinion()
|
||||
if object_uuid is None:
|
||||
object_uuid = self.uuid
|
||||
if object_type is None:
|
||||
object_type = self.analyst_data_object_type
|
||||
if parent is None and hasattr(self, 'parent'):
|
||||
parent = self.parent
|
||||
misp_opinion.from_dict(
|
||||
opinion=opinion, comment=comment, object_uuid=object_uuid,
|
||||
object_type=object_type, parent=parent, contained=True, **kwargs
|
||||
)
|
||||
if parent is None:
|
||||
if not hasattr(self, 'Opinion'):
|
||||
self.Opinion: list[MISPOpinion] = []
|
||||
self.Opinion.append(misp_opinion)
|
||||
else:
|
||||
self.parent.opinions.append(misp_opinion)
|
||||
self.edited = True
|
||||
return misp_opinion
|
||||
|
||||
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
notes = kwargs.pop('Note', [])
|
||||
opinions = kwargs.pop('Opinion', [])
|
||||
self.__parent = kwargs.pop('parent', None)
|
||||
self.distribution = kwargs.pop('distribution', None)
|
||||
if self.distribution is not None:
|
||||
self.distribution = int(self.distribution)
|
||||
|
@ -2573,6 +2666,11 @@ class MISPAnalystData(AbstractMISP):
|
|||
|
||||
super().from_dict(**kwargs)
|
||||
|
||||
for note in notes:
|
||||
self.add_note(**note)
|
||||
for opinion in opinions:
|
||||
self.add_opinion(**opinion)
|
||||
|
||||
def _set_default(self) -> None:
|
||||
if not hasattr(self, 'created'):
|
||||
self.created = datetime.timestamp(datetime.now())
|
||||
|
@ -2580,7 +2678,7 @@ class MISPAnalystData(AbstractMISP):
|
|||
self.modified = self.created
|
||||
|
||||
|
||||
class MISPNote(AnalystDataBehaviorMixin, MISPAnalystData):
|
||||
class MISPNote(MISPAnalystData):
|
||||
|
||||
_fields_for_feed: set[str] = MISPAnalystData._fields_for_feed.union({'note', 'language'})
|
||||
|
||||
|
@ -2591,8 +2689,8 @@ class MISPNote(AnalystDataBehaviorMixin, MISPAnalystData):
|
|||
self.language: str
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if 'Note' in kwargs:
|
||||
def from_dict(self, contained=False, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if not contained and 'Note' in kwargs:
|
||||
kwargs = kwargs['Note']
|
||||
self.note = kwargs.pop('note', None)
|
||||
if self.note is None:
|
||||
|
@ -2605,7 +2703,7 @@ class MISPNote(AnalystDataBehaviorMixin, MISPAnalystData):
|
|||
return f'<{self.__class__.__name__}(NotInitialized)'
|
||||
|
||||
|
||||
class MISPOpinion(AnalystDataBehaviorMixin, MISPAnalystData):
|
||||
class MISPOpinion(MISPAnalystData):
|
||||
|
||||
_fields_for_feed: set[str] = MISPAnalystData._fields_for_feed.union({'opinion', 'comment'})
|
||||
|
||||
|
@ -2616,8 +2714,8 @@ class MISPOpinion(AnalystDataBehaviorMixin, MISPAnalystData):
|
|||
self.comment: str
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if 'Opinion' in kwargs:
|
||||
def from_dict(self, contained=False, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if not contained and 'Opinion' in kwargs:
|
||||
kwargs = kwargs['Opinion']
|
||||
self.opinion = kwargs.pop('opinion', None)
|
||||
if self.opinion is not None:
|
||||
|
@ -2639,7 +2737,7 @@ class MISPOpinion(AnalystDataBehaviorMixin, MISPAnalystData):
|
|||
return f'<{self.__class__.__name__}(NotInitialized)'
|
||||
|
||||
|
||||
class MISPRelationship(AnalystDataBehaviorMixin, MISPAnalystData):
|
||||
class MISPRelationship(MISPAnalystData):
|
||||
|
||||
_fields_for_feed: set[str] = MISPAnalystData._fields_for_feed.union({'related_object_uuid', 'related_object_type', 'relationship_type'})
|
||||
|
||||
|
@ -2651,8 +2749,8 @@ class MISPRelationship(AnalystDataBehaviorMixin, MISPAnalystData):
|
|||
self.relationship_type: str
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if 'Relationship' in kwargs:
|
||||
def from_dict(self, contained=False, **kwargs) -> None: # type: ignore[no-untyped-def]
|
||||
if not contained and 'Relationship' in kwargs:
|
||||
kwargs = kwargs['Relationship']
|
||||
self.related_object_type = kwargs.pop('related_object_type', None)
|
||||
if self.related_object_type is None:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "pymisp"
|
||||
version = "2.5.2"
|
||||
version = "2.5.3"
|
||||
description = "Python API for MISP."
|
||||
authors = ["Raphaël Vinot <raphael.vinot@circl.lu>"]
|
||||
license = "BSD-2-Clause"
|
||||
|
@ -53,7 +53,7 @@ RTFDE = {version = "^0.1.1", optional = true}
|
|||
oletools = {version = "^0.60.1", optional = true}
|
||||
python-magic = {version = "^0.4.27", optional = true}
|
||||
pydeep2 = {version = "^0.5.1", optional = true}
|
||||
lief = {version = "^0.15.0", optional = true}
|
||||
lief = {version = "^0.16.0", optional = true}
|
||||
beautifulsoup4 = {version = "^4.12.3", optional = true}
|
||||
validators = {version = "^0.34.0", optional = true}
|
||||
sphinx-autodoc-typehints = {version = "^2.5.0", optional = true, python = ">=3.10"}
|
||||
|
@ -61,7 +61,7 @@ docutils = {version = "^0.21.1", optional = true, python = ">=3.10"}
|
|||
recommonmark = {version = "^0.7.1", optional = true, python = ">=3.10"}
|
||||
reportlab = {version = "^4.2.5", optional = true}
|
||||
pyfaup = {version = "^1.2", optional = true}
|
||||
publicsuffixlist = {version = "^1.0.2.20241113", optional = true}
|
||||
publicsuffixlist = {version = "^1.0.2.20241218", optional = true}
|
||||
urllib3 = {extras = ["brotli"], version = "*", optional = true}
|
||||
Sphinx = [
|
||||
{version = "^8", python = ">=3.10", optional = true}
|
||||
|
@ -84,9 +84,9 @@ ipython = [
|
|||
{version = "^8.18.0", python = "<3.10"},
|
||||
{version = "^8.19.0", python = ">=3.10"}
|
||||
]
|
||||
jupyterlab = "^4.3.1"
|
||||
jupyterlab = "^4.3.4"
|
||||
types-requests = "^2.32.0.20241016"
|
||||
types-python-dateutil = "^2.9.0.20241003"
|
||||
types-python-dateutil = "^2.9.0.20241206"
|
||||
types-redis = "^4.6.0.20241004"
|
||||
types-Flask = "^1.1.6"
|
||||
pytest-cov = "^6.0.0"
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from pymisp import (MISPAttribute, MISPEvent, MISPEventReport, MISPNote,
|
||||
MISPObject, MISPOpinion)
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
class TestAnalystData(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.note_dict = {
|
||||
"uuid": uuid4(),
|
||||
"note": "note3"
|
||||
}
|
||||
self.opinion_dict = {
|
||||
"uuid": uuid4(),
|
||||
"opinion": 75,
|
||||
"comment": "Agree"
|
||||
}
|
||||
|
||||
def test_analyst_data_on_attribute(self) -> None:
|
||||
attribute = MISPAttribute()
|
||||
attribute.from_dict(type='filename', value='foo.exe')
|
||||
self._attach_analyst_data(attribute)
|
||||
|
||||
def test_analyst_data_on_attribute_alternative(self) -> None:
|
||||
event = MISPEvent()
|
||||
event.info = 'Test on Attribute'
|
||||
event.add_attribute('domain', 'foo.bar')
|
||||
self._attach_analyst_data(event.attributes[0])
|
||||
|
||||
def test_analyst_data_on_event(self) -> None:
|
||||
event = MISPEvent()
|
||||
event.info = 'Test Event'
|
||||
self._attach_analyst_data(event)
|
||||
|
||||
def test_analyst_data_on_event_report(self) -> None:
|
||||
event_report = MISPEventReport()
|
||||
event_report.from_dict(name='Test Report', content='This is a report')
|
||||
self._attach_analyst_data(event_report)
|
||||
|
||||
def test_analyst_data_on_event_report_alternative(self) -> None:
|
||||
event = MISPEvent()
|
||||
event.info = 'Test on Event Report'
|
||||
event.add_event_report('Test Report', 'This is a report')
|
||||
self._attach_analyst_data(event.event_reports[0])
|
||||
|
||||
def test_analyst_data_on_object(self) -> None:
|
||||
misp_object = MISPObject('file')
|
||||
misp_object.add_attribute('filename', 'foo.exe')
|
||||
self._attach_analyst_data(misp_object)
|
||||
|
||||
def test_analyst_data_on_object_alternative(self) -> None:
|
||||
event = MISPEvent()
|
||||
event.info = 'Test on Object'
|
||||
misp_object = MISPObject('file')
|
||||
misp_object.add_attribute('filename', 'foo.exe')
|
||||
event.add_object(misp_object)
|
||||
self._attach_analyst_data(event.objects[0])
|
||||
|
||||
def test_analyst_data_on_object_attribute(self) -> None:
|
||||
misp_object = MISPObject('file')
|
||||
object_attribute = misp_object.add_attribute('filename', 'foo.exe')
|
||||
self._attach_analyst_data(object_attribute)
|
||||
|
||||
def test_analyst_data_object_object_attribute_alternative(self) -> None:
|
||||
misp_object = MISPObject('file')
|
||||
misp_object.add_attribute('filename', 'foo.exe')
|
||||
self._attach_analyst_data(misp_object.attributes[0])
|
||||
|
||||
def _attach_analyst_data(
|
||||
self, container: MISPAttribute | MISPEvent | MISPEventReport | MISPObject) -> None:
|
||||
object_type = container._analyst_data_object_type
|
||||
note1 = container.add_note(note='note1')
|
||||
opinion1 = note1.add_opinion(opinion=25, comment='Disagree')
|
||||
opinion2 = container.add_opinion(opinion=50, comment='Neutral')
|
||||
note2 = opinion2.add_note(note='note2')
|
||||
|
||||
dict_note = MISPNote()
|
||||
dict_note.from_dict(
|
||||
object_type=object_type, object_uuid=container.uuid, **self.note_dict
|
||||
)
|
||||
note3 = container.add_note(**dict_note)
|
||||
dict_opinion = MISPOpinion()
|
||||
dict_opinion.from_dict(
|
||||
object_type='Note', object_uuid=note3.uuid, **self.opinion_dict
|
||||
)
|
||||
container.add_opinion(**dict_opinion)
|
||||
|
||||
self.assertEqual(len(container.notes), 3)
|
||||
self.assertEqual(len(container.opinions), 3)
|
||||
|
||||
misp_note1, misp_note2, misp_note3 = container.notes
|
||||
misp_opinion1, misp_opinion2, misp_opinion3 = container.opinions
|
||||
|
||||
self.assertEqual(misp_note1.object_type, object_type)
|
||||
self.assertEqual(misp_note1.object_uuid, container.uuid)
|
||||
self.assertEqual(misp_note1.note, 'note1')
|
||||
|
||||
self.assertEqual(misp_note2.object_type, 'Opinion')
|
||||
self.assertEqual(misp_note2.object_uuid, opinion2.uuid)
|
||||
self.assertEqual(misp_note2.note, 'note2')
|
||||
|
||||
self.assertEqual(misp_note3.object_type, object_type)
|
||||
self.assertEqual(misp_note3.object_uuid, container.uuid)
|
||||
self.assertEqual(misp_note3.note, 'note3')
|
||||
|
||||
self.assertEqual(misp_opinion1.object_type, 'Note')
|
||||
self.assertEqual(misp_opinion1.object_uuid, note1.uuid)
|
||||
self.assertEqual(misp_opinion1.opinion, 25)
|
||||
self.assertEqual(misp_opinion1.comment, 'Disagree')
|
||||
|
||||
self.assertEqual(misp_opinion2.object_type, object_type)
|
||||
self.assertEqual(misp_opinion2.object_uuid, container.uuid)
|
||||
self.assertEqual(misp_opinion2.opinion, 50)
|
||||
self.assertEqual(misp_opinion2.comment, 'Neutral')
|
||||
|
||||
self.assertEqual(misp_opinion3.object_type, 'Note')
|
||||
self.assertEqual(misp_opinion3.object_uuid, note3.uuid)
|
||||
self.assertEqual(misp_opinion3.opinion, 75)
|
||||
self.assertEqual(misp_opinion3.comment, 'Agree')
|
Loading…
Reference in New Issue