diff --git a/README.md b/README.md index 5eb72e3..0f9a7cf 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ provided as keyword arguments: ```python >>> indicator = Indicator(id="campaign--63ce9068-b5ab-47fa-a2cf-a602ea01f21a") - ValueError: Indicator id values must begin with 'indicator--' + ValueError: Invalid value for Indicator 'id': must start with 'indicator--'. ``` - If not provided, `created` and `modified` will be set to the (same) current diff --git a/stix2/base.py b/stix2/base.py index 00caa84..4d1572e 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -74,6 +74,19 @@ class _STIXBase(collections.Mapping): ) raise ValueError(msg) + def _check_property(self, prop_name, prop, kwargs): + if prop_name not in kwargs: + kwargs[prop_name] = prop.default() + # if default == NOW: + # kwargs[prop_name] = self.__now + try: + kwargs[prop_name] = prop.validate(kwargs[prop_name]) + except ValueError as exc: + msg = "Invalid value for {0} '{1}': {2}" + raise ValueError(msg.format(self.__class__.__name__, + prop_name, + exc)) + def __init__(self, **kwargs): cls = self.__class__ class_name = cls.__name__ @@ -98,8 +111,7 @@ class _STIXBase(collections.Mapping): if isinstance(prop_metadata, dict): self._handle_old_style_property(prop_name, prop_metadata, kwargs) else: # This is a Property Subclasses - # self.check_property(prop_name, prop_metadata, kwargs) - pass + self._check_property(prop_name, prop_metadata, kwargs) self._inner = kwargs diff --git a/stix2/bundle.py b/stix2/bundle.py index 614ce0d..8723640 100644 --- a/stix2/bundle.py +++ b/stix2/bundle.py @@ -1,7 +1,8 @@ """STIX 2 Bundle object""" from .base import _STIXBase -from .common import TYPE_PROPERTY, ID_PROPERTY +from .common import TYPE_PROPERTY +from .properties import IDProperty class Bundle(_STIXBase): @@ -9,7 +10,7 @@ class Bundle(_STIXBase): _type = 'bundle' _properties = { 'type': TYPE_PROPERTY, - 'id': ID_PROPERTY, + 'id': IDProperty(_type), 'spec_version': { 'fixed': "2.0", }, diff --git a/stix2/common.py b/stix2/common.py index 44ff90b..1198832 100644 --- a/stix2/common.py +++ b/stix2/common.py @@ -9,13 +9,6 @@ TYPE_PROPERTY = { 'validate': (lambda x, val: val == x._type) } -ID_PROPERTY = { - 'default': (lambda x: x._make_id()), - 'validate': (lambda x, val: val.startswith(x._type + "--")), - 'expected': (lambda x: x._type + "--"), - 'error_msg': "{type} {field} values must begin with '{expected}'." -} - ref_regex = ("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}" "-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") @@ -31,7 +24,7 @@ BOOL_PROPERTY = { COMMON_PROPERTIES = { 'type': TYPE_PROPERTY, - 'id': ID_PROPERTY, + # 'id' should be defined on each individual type 'created': { 'default': NOW, }, diff --git a/stix2/properties.py b/stix2/properties.py index 8ca92d0..0a2341e 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -100,11 +100,14 @@ class List(Property): class IDProperty(Property): def __init__(self, type): - self.type = type + self.required_prefix = type + "--" + super(IDProperty, self).__init__() def validate(self, value): # TODO: validate GUID as well - return value.startswith(self.type + "--") + if not value.startswith(self.required_prefix): + raise ValueError("must start with '{0}'.".format(self.required_prefix)) + return value def default(self): - return self.type + "--" + str(uuid.uuid4()) + return self.required_prefix + str(uuid.uuid4()) diff --git a/stix2/sdo.py b/stix2/sdo.py index fcc5b26..d6ac7de 100644 --- a/stix2/sdo.py +++ b/stix2/sdo.py @@ -2,6 +2,7 @@ from .base import _STIXBase from .common import COMMON_PROPERTIES +from .properties import IDProperty from .utils import NOW @@ -10,6 +11,7 @@ class AttackPattern(_STIXBase): _type = 'attack-pattern' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, @@ -35,6 +37,7 @@ class Campaign(_STIXBase): _type = 'campaign' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, @@ -66,6 +69,7 @@ class CourseOfAction(_STIXBase): _type = 'course-of-action' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, @@ -89,6 +93,7 @@ class Identity(_STIXBase): _type = 'identity' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, @@ -120,6 +125,7 @@ class Indicator(_STIXBase): _type = 'indicator' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'labels': { 'required': True, }, @@ -155,6 +161,7 @@ class IntrusionSet(_STIXBase): _type = 'intrusion-set' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, @@ -192,6 +199,7 @@ class Malware(_STIXBase): _type = 'malware' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'labels': { 'required': True, }, @@ -220,6 +228,7 @@ class ObservedData(_STIXBase): _type = 'observed-data' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'first_observed': {}, 'last_observed': {}, 'number_observed': {}, @@ -246,6 +255,7 @@ class Report(_STIXBase): _type = 'report' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'labels': { 'required': True, }, @@ -276,6 +286,7 @@ class ThreatActor(_STIXBase): _type = 'threat-actor' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'labels': { 'required': True, }, @@ -318,6 +329,7 @@ class Tool(_STIXBase): _type = 'tool' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'labels': { 'required': True, }, @@ -348,6 +360,7 @@ class Vulnerability(_STIXBase): _type = 'vulnerability' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'name': { 'required': True, }, diff --git a/stix2/sro.py b/stix2/sro.py index 856cf3b..0226011 100644 --- a/stix2/sro.py +++ b/stix2/sro.py @@ -2,6 +2,7 @@ from .base import _STIXBase from .common import COMMON_PROPERTIES +from .properties import IDProperty class Relationship(_STIXBase): @@ -9,6 +10,7 @@ class Relationship(_STIXBase): _type = 'relationship' _properties = COMMON_PROPERTIES.copy() _properties.update({ + 'id': IDProperty(_type), 'relationship_type': { 'required': True, }, diff --git a/stix2/test/test_properties.py b/stix2/test/test_properties.py index 85adc34..3a793a4 100644 --- a/stix2/test/test_properties.py +++ b/stix2/test/test_properties.py @@ -49,6 +49,7 @@ def test_fixed_property(): def test_id_property(): idprop = IDProperty('my-type') - assert idprop.validate('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') is True - assert idprop.validate('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') is False + assert idprop.validate('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') + with pytest.raises(ValueError): + idprop.validate('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') assert idprop.validate(idprop.default()) diff --git a/stix2/test/test_stix2.py b/stix2/test/test_stix2.py index c448048..40a8f9b 100644 --- a/stix2/test/test_stix2.py +++ b/stix2/test/test_stix2.py @@ -161,7 +161,7 @@ def test_indicator_id_must_start_with_indicator(): with pytest.raises(ValueError) as excinfo: indicator = stix2.Indicator(id='my-prefix--', **INDICATOR_KWARGS) - assert str(excinfo.value) == "Indicator id values must begin with 'indicator--'." + assert str(excinfo.value) == "Invalid value for Indicator 'id': must start with 'indicator--'." def test_indicator_required_fields(): @@ -262,7 +262,7 @@ def test_malware_id_must_start_with_malware(): with pytest.raises(ValueError) as excinfo: malware = stix2.Malware(id='my-prefix--', **MALWARE_KWARGS) - assert str(excinfo.value) == "Malware id values must begin with 'malware--'." + assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'." def test_malware_required_fields(): @@ -345,7 +345,7 @@ def test_relationship_id_must_start_with_relationship(): with pytest.raises(ValueError) as excinfo: relationship = stix2.Relationship(id='my-prefix--', **RELATIONSHIP_KWARGS) - assert str(excinfo.value) == "Relationship id values must begin with 'relationship--'." + assert str(excinfo.value) == "Invalid value for Relationship 'id': must start with 'relationship--'." def test_relationship_required_field_relationship_type(): @@ -464,7 +464,7 @@ def test_bundle_id_must_start_with_bundle(): with pytest.raises(ValueError) as excinfo: bundle = stix2.Bundle(id='my-prefix--') - assert str(excinfo.value) == "Bundle id values must begin with 'bundle--'." + assert str(excinfo.value) == "Invalid value for Bundle 'id': must start with 'bundle--'." def test_bundle_with_wrong_spec_version():