diff --git a/stix2/properties.py b/stix2/properties.py index 4eb5014..a217950 100644 --- a/stix2/properties.py +++ b/stix2/properties.py @@ -15,7 +15,10 @@ import stix2 from .base import _Observable, _STIXBase from .core import STIX2_OBJ_MAPS, parse, parse_observable -from .exceptions import CustomContentError, DictionaryKeyError +from .exceptions import ( + CustomContentError, DictionaryKeyError, MissingPropertiesError, + MutuallyExclusivePropertiesError, +) from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime ERROR_INVALID_ID = ( @@ -417,15 +420,27 @@ class HexProperty(Property): class ReferenceProperty(Property): - def __init__(self, valid_types=None, spec_version=stix2.DEFAULT_VERSION, **kwargs): + def __init__(self, valid_types=None, invalid_types=None, spec_version=stix2.DEFAULT_VERSION, **kwargs): """ references sometimes must be to a specific object type """ self.spec_version = spec_version + # These checks need to be done prior to the STIX object finishing construction + # and thus we can't use base.py's _check_mutually_exclusive_properties() + # in the typical location of _check_object_constraints() in sdo.py + if valid_types and invalid_types: + raise MutuallyExclusivePropertiesError(self.__class__, ['invalid_types', 'valid_types']) + elif valid_types is None and invalid_types is None: + raise MissingPropertiesError(self.__class__, ['invalid_types', 'valid_types']) + if valid_types and type(valid_types) is not list: valid_types = [valid_types] + elif invalid_types and type(invalid_types) is not list: + invalid_types = [invalid_types] + self.valid_types = valid_types + self.invalid_types = invalid_types super(ReferenceProperty, self).__init__(**kwargs) @@ -434,8 +449,18 @@ class ReferenceProperty(Property): value = value.id value = str(value) - if self.valid_types and value[:value.index('--')] in self.valid_types: - required_prefix = value[:value.index('--') + 2] + possible_prefix = value[:value.index('--') + 2] + + if self.valid_types: + if possible_prefix in self.valid_types: + required_prefix = possible_prefix + else: + raise ValueError("The type-specifying prefix for this identifier is invalid") + elif self.invalid_types: + if possible_prefix not in self.invalid_types: + required_prefix = possible_prefix + else: + raise ValueError("The type-specifying prefix for this identifier is invalid") _validate_id(value, self.spec_version, required_prefix) diff --git a/stix2/test/v20/test_properties.py b/stix2/test/v20/test_properties.py index 548e7fe..f71d829 100644 --- a/stix2/test/v20/test_properties.py +++ b/stix2/test/v20/test_properties.py @@ -276,7 +276,7 @@ def test_boolean_property_invalid(value): def test_reference_property(): - ref_prop = ReferenceProperty(valid_types=None, spec_version="2.0") + ref_prop = ReferenceProperty(valid_types="my-type", spec_version="2.0") assert ref_prop.clean("my-type--00000000-0000-4000-8000-000000000000") with pytest.raises(ValueError): diff --git a/stix2/test/v21/test_properties.py b/stix2/test/v21/test_properties.py index 41ca3e9..1fb3cc4 100644 --- a/stix2/test/v21/test_properties.py +++ b/stix2/test/v21/test_properties.py @@ -271,7 +271,7 @@ def test_boolean_property_invalid(value): def test_reference_property(): - ref_prop = ReferenceProperty(valid_types=None, spec_version="2.1") + ref_prop = ReferenceProperty(valid_types="my-type", spec_version="2.1") assert ref_prop.clean("my-type--00000000-0000-4000-8000-000000000000") with pytest.raises(ValueError): diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index 9288003..899ccd8 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -233,7 +233,7 @@ class Report(STIXDomainObject): ('name', StringProperty(required=True)), ('description', StringProperty()), ('published', TimestampProperty(required=True)), - ('object_refs', ListProperty(ReferenceProperty(valid_types=None, spec_version='2.0'), required=True)), + ('object_refs', ListProperty(ReferenceProperty(invalid_types="", spec_version='2.0'), required=True)), ('revoked', BooleanProperty(default=lambda: False)), ('labels', ListProperty(StringProperty, required=True)), ('external_references', ListProperty(ExternalReference)), diff --git a/stix2/v20/sro.py b/stix2/v20/sro.py index a21c871..3b1e582 100644 --- a/stix2/v20/sro.py +++ b/stix2/v20/sro.py @@ -16,6 +16,8 @@ class Relationship(STIXRelationshipObject): `the STIX 2.0 specification `__. """ + _invalid_source_target_types = ['bundle', 'language-content', 'marking-definition', 'relationship', 'sighting'] + _type = 'relationship' _properties = OrderedDict([ ('type', TypeProperty(_type)), @@ -25,8 +27,8 @@ class Relationship(STIXRelationshipObject): ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('relationship_type', StringProperty(required=True)), ('description', StringProperty()), - ('source_ref', ReferenceProperty(valid_types=None, spec_version='2.0', required=True)), - ('target_ref', ReferenceProperty(valid_types=None, spec_version='2.0', required=True)), + ('source_ref', ReferenceProperty(invalid_types=_invalid_source_target_types, spec_version='2.0', required=True)), + ('target_ref', ReferenceProperty(invalid_types=_invalid_source_target_types, spec_version='2.0', required=True)), ('revoked', BooleanProperty(default=lambda: False)), ('labels', ListProperty(StringProperty)), ('external_references', ListProperty(ExternalReference)), @@ -65,7 +67,7 @@ class Sighting(STIXRelationshipObject): ('first_seen', TimestampProperty()), ('last_seen', TimestampProperty()), ('count', IntegerProperty(min=0, max=999999999)), - ('sighting_of_ref', ReferenceProperty(valid_types=None, spec_version='2.0', required=True)), + ('sighting_of_ref', ReferenceProperty(valid_types="Left to user", spec_version='2.0', required=True)), ('observed_data_refs', ListProperty(ReferenceProperty(valid_types='observed-data', spec_version='2.0'))), ('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.0'))), ('summary', BooleanProperty(default=lambda: False)), diff --git a/stix2/v21/common.py b/stix2/v21/common.py index e085985..44d3675 100644 --- a/stix2/v21/common.py +++ b/stix2/v21/common.py @@ -77,7 +77,7 @@ class LanguageContent(_STIXBase): ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')), ('created', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), - ('object_ref', ReferenceProperty(valid_types=None, spec_version='2.1', required=True)), + ('object_ref', ReferenceProperty(invalid_types=[""], spec_version='2.1', required=True)), # TODO: 'object_modified' it MUST be an exact match for the modified time of the STIX Object (SRO or SDO) being referenced. ('object_modified', TimestampProperty(precision='millisecond')), # TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py index f19d8ad..fbce8fd 100644 --- a/stix2/v21/observables.py +++ b/stix2/v21/observables.py @@ -385,7 +385,7 @@ class File(_Observable): ('mtime', TimestampProperty()), ('atime', TimestampProperty()), ('parent_directory_ref', ReferenceProperty(valid_types='directory')), - ('contains_refs', ListProperty(ReferenceProperty())), + ('contains_refs', ListProperty(ReferenceProperty(invalid_types=[""]))), ('content_ref', ReferenceProperty(valid_types='artifact')), ('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)), ('spec_version', StringProperty(fixed='2.1')), diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py index e9714e7..cb7eff3 100644 --- a/stix2/v21/sdo.py +++ b/stix2/v21/sdo.py @@ -444,7 +444,7 @@ class Malware(STIXDomainObject): ('architecture_execution_envs', ListProperty(StringProperty)), ('implementation_languages', ListProperty(StringProperty)), ('capabilities', ListProperty(StringProperty)), - ('sample_refs', ListProperty(ReferenceProperty(valid_types=None, spec_version='2.1'))), + ('sample_refs', ListProperty(ReferenceProperty(valid_types=['artifact', 'file'], spec_version='2.1'))), ('revoked', BooleanProperty(default=lambda: False)), ('labels', ListProperty(StringProperty)), ('confidence', IntegerProperty()), diff --git a/stix2/v21/sro.py b/stix2/v21/sro.py index 37f0024..31f9c4c 100644 --- a/stix2/v21/sro.py +++ b/stix2/v21/sro.py @@ -17,6 +17,8 @@ class Relationship(STIXRelationshipObject): `the STIX 2.1 specification `__. """ + _invalid_source_target_types = ['bundle', 'language-content', 'marking-definition', 'relationship', 'sighting'] + _type = 'relationship' _properties = OrderedDict([ ('type', TypeProperty(_type)), @@ -27,8 +29,8 @@ class Relationship(STIXRelationshipObject): ('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')), ('relationship_type', StringProperty(required=True)), ('description', StringProperty()), - ('source_ref', ReferenceProperty(valid_types=None, spec_version='2.1', required=True)), - ('target_ref', ReferenceProperty(valid_types=None, spec_version='2.1', required=True)), + ('source_ref', ReferenceProperty(valid_types=_invalid_source_target_types, spec_version='2.1', required=True)), + ('target_ref', ReferenceProperty(valid_types=_invalid_source_target_types, spec_version='2.1', required=True)), ('start_time', TimestampProperty()), ('stop_time', TimestampProperty()), ('revoked', BooleanProperty(default=lambda: False)), @@ -84,7 +86,7 @@ class Sighting(STIXRelationshipObject): ('first_seen', TimestampProperty()), ('last_seen', TimestampProperty()), ('count', IntegerProperty(min=0, max=999999999)), - ('sighting_of_ref', ReferenceProperty(valid_types=None, spec_version='2.1', required=True)), + ('sighting_of_ref', ReferenceProperty(valid_types="Left to user", spec_version='2.1', required=True)), ('observed_data_refs', ListProperty(ReferenceProperty(valid_types='observed-data', spec_version='2.1'))), ('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.1'))), ('summary', BooleanProperty()),