diff --git a/.isort.cfg b/.isort.cfg index e141924..e8d95f8 100644 --- a/.isort.cfg +++ b/.isort.cfg @@ -1,7 +1,7 @@ [settings] check=1 diff=1 -known_third_party=dateutil,pytest,pytz,six,requests,taxii2_client +known_third_party=dateutil,pytest,pytz,six,requests,taxii2client known_first_party=stix2 not_skip=__init__.py force_sort_within_sections=1 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 c10da46..2b8d544 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,8 @@ setup( 'pytz', 'requests', 'simplejson', - 'six' + '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_data_sources.py b/stix2/test/test_data_sources.py index bbb7f31..0095802 100644 --- a/stix2/test/test_data_sources.py +++ b/stix2/test/test_data_sources.py @@ -1,5 +1,5 @@ import pytest -from taxii2_client import Collection +from taxii2client import Collection from stix2.sources import DataSource, Filter, taxii 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