diff --git a/README.rst b/README.rst index b2a8c9c..03c7cc3 100644 --- a/README.rst +++ b/README.rst @@ -39,8 +39,8 @@ constructor: from stix2 import Indicator indicator = Indicator(name="File hash for malware variant", - labels=['malicious-activity'], - pattern='file:hashes.md5 = "d41d8cd98f00b204e9800998ecf8427e"') + labels=["malicious-activity"], + pattern="[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']") Certain required attributes of all objects will be set automatically if not provided as keyword arguments: diff --git a/setup.py b/setup.py index 29d98f6..2b8d544 100644 --- a/setup.py +++ b/setup.py @@ -53,6 +53,7 @@ setup( 'requests', 'simplejson', 'six', + 'stix2-patterns', 'taxii2-client', ], ) diff --git a/stix2/properties.py b/stix2/properties.py index f63ec8b..a04294e 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -7,6 +7,8 @@ import uuid from six import string_types, text_type +from stix2patterns.validator import run_validator + from .base import _STIXBase from .exceptions import DictionaryKeyError from .utils import get_dict, parse_into_datetime @@ -370,3 +372,17 @@ class EnumProperty(StringProperty): if value not in self.allowed: raise ValueError("value '%s' is not valid for this enumeration." % value) return self.string_type(value) + + +class PatternProperty(StringProperty): + + def __init__(self, **kwargs): + super(PatternProperty, self).__init__(**kwargs) + + def clean(self, value): + str_value = super(PatternProperty, self).clean(value) + errors = run_validator(str_value) + if errors: + raise ValueError(str(errors[0])) + + return self.string_type(value) diff --git a/stix2/sdo.py b/stix2/sdo.py index b2b0e7e..7eebf56 100644 --- a/stix2/sdo.py +++ b/stix2/sdo.py @@ -11,8 +11,8 @@ from .base import _STIXBase from .common import ExternalReference, GranularMarking, KillChainPhase from .observables import ObservableProperty from .properties import (BooleanProperty, IDProperty, IntegerProperty, - ListProperty, ReferenceProperty, StringProperty, - TimestampProperty, TypeProperty) + ListProperty, PatternProperty, ReferenceProperty, + StringProperty, TimestampProperty, TypeProperty) from .utils import NOW @@ -117,7 +117,7 @@ class Indicator(_STIXBase): ('labels', ListProperty(StringProperty, required=True)), ('name', StringProperty()), ('description', StringProperty()), - ('pattern', StringProperty(required=True)), + ('pattern', PatternProperty(required=True)), ('valid_from', TimestampProperty(default=lambda: NOW)), ('valid_until', TimestampProperty()), ('kill_chain_phases', ListProperty(KillChainPhase)), diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py index 5a7ae5a..1f8e3bd 100644 --- a/stix2/test/test_indicator.py +++ b/stix2/test/test_indicator.py @@ -174,3 +174,23 @@ def test_parse_indicator(data): assert idctr.valid_from == dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc) assert idctr.labels[0] == "malicious-activity" assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']" + + +def test_invalid_indicator_pattern(): + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + stix2.Indicator( + labels=['malicious-activity'], + pattern="file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e'", + ) + assert excinfo.value.cls == stix2.Indicator + assert excinfo.value.prop_name == 'pattern' + assert 'input is missing square brackets' in excinfo.value.reason + + with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo: + stix2.Indicator( + labels=['malicious-activity'], + pattern='[file:hashes.MD5 = "d41d8cd98f00b204e9800998ecf8427e"]', + ) + assert excinfo.value.cls == stix2.Indicator + assert excinfo.value.prop_name == 'pattern' + assert 'mismatched input' in excinfo.value.reason