diff --git a/stix2/base.py b/stix2/base.py index a44d879..878154d 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -58,6 +58,7 @@ class _STIXBase(collections.Mapping): if extra_kwargs: raise TypeError("unexpected keyword arguments: " + str(extra_kwargs)) + # Detect any missing required fields required_fields = get_required_properties(cls._properties) missing_kwargs = set(required_fields) - set(kwargs) if missing_kwargs: diff --git a/stix2/common.py b/stix2/common.py index 7b4e3ea..3a77d0b 100644 --- a/stix2/common.py +++ b/stix2/common.py @@ -1,7 +1,8 @@ """STIX 2 Common Data Types and Properties""" from .base import _STIXBase -from .properties import Property, BooleanProperty, ReferenceProperty +from .properties import (Property, BooleanProperty, ReferenceProperty, + StringProperty) from .utils import NOW COMMON_PROPERTIES = { @@ -28,6 +29,6 @@ class ExternalReference(_STIXBase): class KillChainPhase(_STIXBase): _properties = { - 'kill_chain_name': Property(required=True), - 'phase_name': Property(required=True), + 'kill_chain_name': StringProperty(required=True), + 'phase_name': StringProperty(required=True), } diff --git a/stix2/properties.py b/stix2/properties.py index e62302a..5a57ffb 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -110,7 +110,7 @@ class ListProperty(Property): def clean(self, value): try: - return [self.contained(x) for x in value] + return [self.contained(**x) if type(x) is dict else self.contained(x) for x in value] except TypeError: raise ValueError("must be an iterable over a type whose constructor creates an object from the value.") diff --git a/stix2/sdo.py b/stix2/sdo.py index 8a45ff4..d996277 100644 --- a/stix2/sdo.py +++ b/stix2/sdo.py @@ -1,7 +1,7 @@ """STIX 2.0 Domain Objects""" from .base import _STIXBase -from .common import COMMON_PROPERTIES +from .common import COMMON_PROPERTIES, KillChainPhase from .properties import (StringProperty, IDProperty, ListProperty, TypeProperty, Property) from .utils import NOW @@ -109,7 +109,7 @@ class Malware(_STIXBase): 'labels': ListProperty(StringProperty, required=True), 'name': StringProperty(required=True), 'description': StringProperty(), - 'kill_chain_phases': Property(), + 'kill_chain_phases': ListProperty(KillChainPhase), }) diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py index 7668ffa..5206e60 100644 --- a/stix2/test/test_malware.py +++ b/stix2/test/test_malware.py @@ -107,3 +107,19 @@ def test_parse_malware_invalid_labels(): with pytest.raises(ValueError) as excinfo: stix2.parse(data) assert "Invalid value for Malware 'labels'" in str(excinfo.value) + + +def test_parse_malware_kill_chain_phases(): + kill_chain = """ + "kill_chain_phases": [ + { + "kill_chain_name": "lockheed-martin-cyber-kill-chain", + "phase_name": "reconnaissance" + } + ]""" + data = EXPECTED_MALWARE.replace('malware"', 'malware",%s' % kill_chain) + mal = stix2.parse(data) + assert mal.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain" + assert mal.kill_chain_phases[0].phase_name == "reconnaissance" + assert mal['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain" + assert mal['kill_chain_phases'][0]['phase_name'] == "reconnaissance"