From 1dce13d61bc92afa5d9cca9b2a7b0c20007fcf01 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 30 Oct 2024 15:10:53 +0100 Subject: [PATCH 1/9] fix: [AnalystData] Avoiding issues with analyst data objects --- pymisp/mispevent.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 362fd1e0..20ad33b1 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -60,28 +60,35 @@ def relationships(self) -> list[MISPRelationship]: 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) + the_note.from_dict( + note=note, language=language, object_uuid=self.uuid, + object_type=self.analyst_data_object_type, contained=True, + **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) + the_opinion.from_dict( + opinion=opinion, comment=comment, object_uuid=self.uuid, + object_type=self.analyst_data_object_type, contained=True, + **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, + **kwargs + ) self.relationships.append(the_relationship) self.edited = True return the_relationship @@ -2591,8 +2598,8 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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 conainted and 'Note' in kwargs: kwargs = kwargs['Note'] self.note = kwargs.pop('note', None) if self.note is None: @@ -2616,8 +2623,8 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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: @@ -2651,8 +2658,8 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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: From 8e288a43231d35a9852a1383f64ec36c9a3771e2 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 30 Oct 2024 17:00:22 +0100 Subject: [PATCH 2/9] fix: [AnalystData] Typo... --- pymisp/mispevent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 20ad33b1..3b31087f 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -2599,7 +2599,7 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) def from_dict(self, contained=False, **kwargs) -> None: # type: ignore[no-untyped-def] - if not conainted and 'Note' in kwargs: + if not contained and 'Note' in kwargs: kwargs = kwargs['Note'] self.note = kwargs.pop('note', None) if self.note is None: From df39554208739586bf25b6803c9011f85e7aefd9 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Tue, 19 Nov 2024 13:49:53 +0100 Subject: [PATCH 3/9] chg: [AnalystData] Flattening analyst data based on the recent changes on MISP standard format - Adding a note or an opinion will always add the new analyst data object to the list of notes or opinions at the parent data layer level - `from_dict` on a JSON blob is also able to parse properly analyst data and generate flat lists regardless of whether the given data described in the new flat or previously nested format --- pymisp/mispevent.py | 86 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 3b31087f..86e8f700 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -38,9 +38,6 @@ def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.uuid: str # Created in the child class self._analyst_data_object_type: str # Must be defined in the child class - self.Note: list[MISPNote] = [] - self.Opinion: list[MISPOpinion] = [] - self.Relationship: list[MISPRelationship] = [] @property def analyst_data_object_type(self) -> str: @@ -60,10 +57,11 @@ def relationships(self) -> list[MISPRelationship]: def add_note(self, note: str, language: str | None = None, **kwargs) -> MISPNote: # type: ignore[no-untyped-def] the_note = MISPNote() + 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=self.uuid, - object_type=self.analyst_data_object_type, contained=True, - **kwargs + 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 @@ -71,10 +69,11 @@ def add_note(self, note: str, language: str | None = None, **kwargs) -> MISPNote def add_opinion(self, opinion: int, comment: str | None = None, **kwargs) -> MISPOpinion: # type: ignore[no-untyped-def] the_opinion = MISPOpinion() + 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=self.uuid, - object_type=self.analyst_data_object_type, contained=True, - **kwargs + 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 @@ -87,7 +86,7 @@ def add_relationship(self, related_object_type: AbstractMISP | str, related_obje related_object_uuid=related_object_uuid, relationship_type=relationship_type, object_uuid=self.uuid, object_type=self.analyst_data_object_type, contained=True, - **kwargs + parent=self, **kwargs ) self.relationships.append(the_relationship) self.edited = True @@ -100,12 +99,8 @@ def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] 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) @@ -338,6 +333,9 @@ def __init__(self, describe_types: dict[str, Any] | None = None, strict: bool = self.Sighting: list[MISPSighting] = [] self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] + self.Note: list[MISPNote] = [] + self.Opinion: list[MISPOpinion] = [] + self.Relationship: list[MISPRelationship] = [] self.expand: str self.timestamp: float | int | datetime @@ -794,6 +792,9 @@ def __init__(self, name: str, strict: bool = False, standalone: bool = True, # self.ObjectReference: list[MISPObjectReference] = [] self._standalone: bool = False self.Attribute: list[MISPObjectAttribute] = [] + self.Note: list[MISPNote] = [] + self.Opinion: list[MISPOpinion] = [] + self.Relationship: list[MISPRelationship] = [] self.SharingGroup: MISPSharingGroup self._default_attributes_parameters: dict[str, Any] if isinstance(default_attributes_parameters, MISPAttribute): @@ -1180,6 +1181,9 @@ class MISPEventReport(AnalystDataBehaviorMixin): def __init__(self, **kwargs): super().__init__(**kwargs) self.uuid: str = str(uuid.uuid4()) + self.Note: list[MISPNote] = [] + self.Opinion: list[MISPOpinion] = [] + self.Relationship: list[MISPRelationship] = [] def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventReport' in kwargs: @@ -1588,6 +1592,9 @@ def __init__(self, describe_types: dict[str, Any] | None = None, strict_validati self.EventReport: list[MISPEventReport] = [] self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] + self.Note: list[MISPNote] = [] + self.Opinion: list[MISPOpinion] = [] + self.Relationship: list[MISPRelationship] = [] self.publish_timestamp: float | int | datetime self.timestamp: float | int | datetime @@ -2496,6 +2503,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 @@ -2511,6 +2522,10 @@ def orgc(self, orgc: MISPOrganisation) -> None: 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") @@ -2525,8 +2540,38 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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, **kwargs) -> MISPNote: + misp_note = MISPNote() + object_uuid = kwargs.pop('object_uuid', self.uuid) + object_type = kwargs.pop('object_type', self.analyst_data_object_type) + misp_note.from_dict( + note=note, language=language, object_uuid=object_uuid, + object_type=object_type, parent=kwargs.pop('parent', self.parent), + contained=True, **kwargs + ) + self.parent.notes.append(misp_note) + self.edited = True + return misp_note + + def add_opinion(self, opinion: int, comment: str | None = None, **kwargs) -> MISPOpinion: + misp_opinion = MISPOpinion() + object_uuid = kwargs.pop('object_uuid', self.uuid) + object_type = kwargs.pop('object_type', self.analyst_data_object_type) + misp_opinion.from_dict( + opinion=opinion, comment=comment, object_uuid=object_uuid, + object_type=object_type, parent=kwargs.pop('parent', self.parent), + contained=True, **kwargs + ) + 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') self.distribution = kwargs.pop('distribution', None) if self.distribution is not None: self.distribution = int(self.distribution) @@ -2580,6 +2625,13 @@ def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().from_dict(**kwargs) + for note in notes: + note_value = note.pop('note') + self.add_note(note_value, parent=self.parent, **note) + for opinion in opinions: + opinion_value = opinion.pop('opinion') + self.add_opinion(opinion_value, parent=self.parent, **opinion) + def _set_default(self) -> None: if not hasattr(self, 'created'): self.created = datetime.timestamp(datetime.now()) @@ -2587,7 +2639,7 @@ def _set_default(self) -> None: self.modified = self.created -class MISPNote(AnalystDataBehaviorMixin, MISPAnalystData): +class MISPNote(MISPAnalystData): _fields_for_feed: set[str] = MISPAnalystData._fields_for_feed.union({'note', 'language'}) @@ -2612,7 +2664,7 @@ def __repr__(self) -> str: 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'}) @@ -2646,7 +2698,7 @@ def __repr__(self) -> str: 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'}) From ea6ff20103f58730729f4f9c0a8e3c4c6654b5b9 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 20 Nov 2024 11:47:41 +0100 Subject: [PATCH 4/9] fix: [MISPAnalystData] Better handling of the different use cases - Additional checks for parent to support both the standalone and attached analyst data objects - Standalone Analyst data objects with nested notes or opinions are defined with the nesting as they have no parent. When they are added to a parent data layer, the nested objects are then flattened --- pymisp/mispevent.py | 48 +++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 86e8f700..7ecfaba8 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -2542,36 +2542,52 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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, **kwargs) -> MISPNote: + 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) -> MISPNote: misp_note = MISPNote() - object_uuid = kwargs.pop('object_uuid', self.uuid) - object_type = kwargs.pop('object_type', self.analyst_data_object_type) + 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=kwargs.pop('parent', self.parent), - contained=True, **kwargs + object_type=object_type, parent=parent, contained=True, **kwargs ) - self.parent.notes.append(misp_note) + 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, **kwargs) -> MISPOpinion: + 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) -> MISPOpinion: misp_opinion = MISPOpinion() - object_uuid = kwargs.pop('object_uuid', self.uuid) - object_type = kwargs.pop('object_type', self.analyst_data_object_type) + 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=kwargs.pop('parent', self.parent), - contained=True, **kwargs + object_type=object_type, parent=parent, contained=True, **kwargs ) - self.parent.opinions.append(misp_opinion) + 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') + self.__parent = kwargs.pop('parent', None) self.distribution = kwargs.pop('distribution', None) if self.distribution is not None: self.distribution = int(self.distribution) @@ -2626,11 +2642,9 @@ def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().from_dict(**kwargs) for note in notes: - note_value = note.pop('note') - self.add_note(note_value, parent=self.parent, **note) + self.add_note(**note) for opinion in opinions: - opinion_value = opinion.pop('opinion') - self.add_opinion(opinion_value, parent=self.parent, **opinion) + self.add_opinion(**opinion) def _set_default(self) -> None: if not hasattr(self, 'created'): From 6ca367dc2514140bc946f698e094cc5051ade3c3 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 20 Nov 2024 13:21:35 +0100 Subject: [PATCH 5/9] fix: [MISPAnalystData] Reverted the declaration of Analyst data objects lists back to the mixin parent class --- pymisp/mispevent.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 7ecfaba8..2cf15b2f 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -38,6 +38,9 @@ def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.uuid: str # Created in the child class self._analyst_data_object_type: str # Must be defined in the child class + self.Note: list[MISPNote] = [] + self.Opinion: list[MISPOpinion] = [] + self.Relationship: list[MISPRelationship] = [] @property def analyst_data_object_type(self) -> str: @@ -333,9 +336,6 @@ def __init__(self, describe_types: dict[str, Any] | None = None, strict: bool = self.Sighting: list[MISPSighting] = [] self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] - self.Note: list[MISPNote] = [] - self.Opinion: list[MISPOpinion] = [] - self.Relationship: list[MISPRelationship] = [] self.expand: str self.timestamp: float | int | datetime @@ -792,9 +792,6 @@ def __init__(self, name: str, strict: bool = False, standalone: bool = True, # self.ObjectReference: list[MISPObjectReference] = [] self._standalone: bool = False self.Attribute: list[MISPObjectAttribute] = [] - self.Note: list[MISPNote] = [] - self.Opinion: list[MISPOpinion] = [] - self.Relationship: list[MISPRelationship] = [] self.SharingGroup: MISPSharingGroup self._default_attributes_parameters: dict[str, Any] if isinstance(default_attributes_parameters, MISPAttribute): @@ -1181,9 +1178,6 @@ class MISPEventReport(AnalystDataBehaviorMixin): def __init__(self, **kwargs): super().__init__(**kwargs) self.uuid: str = str(uuid.uuid4()) - self.Note: list[MISPNote] = [] - self.Opinion: list[MISPOpinion] = [] - self.Relationship: list[MISPRelationship] = [] def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventReport' in kwargs: @@ -1592,9 +1586,6 @@ def __init__(self, describe_types: dict[str, Any] | None = None, strict_validati self.EventReport: list[MISPEventReport] = [] self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] - self.Note: list[MISPNote] = [] - self.Opinion: list[MISPOpinion] = [] - self.Relationship: list[MISPRelationship] = [] self.publish_timestamp: float | int | datetime self.timestamp: float | int | datetime From 90b8f5883a359fd93eaba7d51a10e2c356dcd625 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 20 Nov 2024 13:36:19 +0100 Subject: [PATCH 6/9] fix: [MISPAnalystData] Added missing typing --- pymisp/mispevent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 2cf15b2f..bd466eda 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -2533,7 +2533,7 @@ def __init__(self, **kwargs: dict[str, Any]) -> None: 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) -> MISPNote: + 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 @@ -2554,7 +2554,7 @@ def add_note(self, note: str, language: str | None = None, object_uuid: str | No 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) -> MISPOpinion: + 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 From 1e9258eb4adc354c041d29c0b1227a800d7968fa Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 18 Dec 2024 18:17:00 +0100 Subject: [PATCH 7/9] add: [tests] Testing Analyst Data in different scenarios - Testing different ways to attach analyst data - Testing that no matter what object type the analyst data is attached to, the `object_type` & `object_uuid` are correct, and the parent container does contain every analyst data object in flat lists with no nesting --- tests/test_analyst_data.py | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 tests/test_analyst_data.py diff --git a/tests/test_analyst_data.py b/tests/test_analyst_data.py new file mode 100644 index 00000000..46729607 --- /dev/null +++ b/tests/test_analyst_data.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +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') + From a7cf9dc3f03ffda5ee6c1b614792455fe43d8704 Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 18 Dec 2024 18:24:06 +0100 Subject: [PATCH 8/9] fix: [tests] Removed typing --- tests/test_analyst_data.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_analyst_data.py b/tests/test_analyst_data.py index 46729607..d594c0fe 100644 --- a/tests/test_analyst_data.py +++ b/tests/test_analyst_data.py @@ -68,8 +68,7 @@ def test_analyst_data_object_object_attribute_alternative(self) -> None: 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: + def _attach_analyst_data(self, container) -> None: object_type = container._analyst_data_object_type note1 = container.add_note(note='note1') opinion1 = note1.add_opinion(opinion=25, comment='Disagree') From 23b5d3a328f056209253f0eaedba01e3b90f507a Mon Sep 17 00:00:00 2001 From: Christian Studer Date: Wed, 18 Dec 2024 18:28:37 +0100 Subject: [PATCH 9/9] Revert "fix: [tests] Removed typing" This reverts commit a7cf9dc3f03ffda5ee6c1b614792455fe43d8704. --- tests/test_analyst_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_analyst_data.py b/tests/test_analyst_data.py index d594c0fe..46729607 100644 --- a/tests/test_analyst_data.py +++ b/tests/test_analyst_data.py @@ -68,7 +68,8 @@ def test_analyst_data_object_object_attribute_alternative(self) -> None: misp_object.add_attribute('filename', 'foo.exe') self._attach_analyst_data(misp_object.attributes[0]) - def _attach_analyst_data(self, container) -> None: + 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')