diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c9ec75b --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +include LICENSE +include CHANGELOG +recursive-exclude stix2\test * diff --git a/README.rst b/README.rst index 2753440..faacc53 100644 --- a/README.rst +++ b/README.rst @@ -62,6 +62,16 @@ To parse a STIX JSON string into a Python STIX object, use ``parse()``: For more in-depth documentation, please see `https://stix2.readthedocs.io/ `__. +STIX 2.X Technical Specification Support +---------------------------------------- + +This version of python-stix2 supports STIX 2.0 by default. Although, the +`stix2` Python library is built to support multiple versions of the STIX +Technical Specification. With every major release of stix2 the ``import stix2`` +statement will automatically load the SDO/SROs equivalent to the most recent +supported 2.X Technical Specification. Please see the library documentation +for more details. + Governance ---------- diff --git a/docs/guide/ts_support.ipynb b/docs/guide/ts_support.ipynb new file mode 100644 index 0000000..f98d7b5 --- /dev/null +++ b/docs/guide/ts_support.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Technical Specification Support\n", + "\n", + "### How imports will work\n", + "\n", + "Imports can be used in different ways depending on the use case and support levels.\n", + "\n", + "People who want to (in general) support the latest version of STIX 2.X without making changes, implicitly using the latest version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "stix2.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import Indicator\n", + "\n", + "Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "People who want to use an explicit version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2.v20\n", + "\n", + "stix2.v20.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2.v20 import Indicator\n", + "\n", + "Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or even," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2.v20 as stix2\n", + "\n", + "stix2.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The last option makes it easy to update to a new version in one place per file, once you've made the deliberate action to do this.\n", + "\n", + "People who want to use multiple versions in a single file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "stix2.v20.Indicator()\n", + "\n", + "stix2.v21.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import v20, v21\n", + "\n", + "v20.Indicator()\n", + "v21.Indicator()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "or (less preferred):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2.v20 import Indicator as Indicator_v20\n", + "from stix2.v21 import Indicator as Indicator_v21\n", + "\n", + "Indicator_v20()\n", + "Indicator_v21()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How parsing will work\n", + "If the ``version`` positional argument is not provided. The data will be parsed using the latest version of STIX 2.X supported by the `stix2` library.\n", + "\n", + "You can lock your `parse()` method to a specific STIX version by" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from stix2 import parse\n", + "\n", + "indicator = parse(\"\"\"{\n", + " \"type\": \"indicator\",\n", + " \"id\": \"indicator--dbcbd659-c927-4f9a-994f-0a2632274394\",\n", + " \"created\": \"2017-09-26T23:33:39.829Z\",\n", + " \"modified\": \"2017-09-26T23:33:39.829Z\",\n", + " \"labels\": [\n", + " \"malicious-activity\"\n", + " ],\n", + " \"name\": \"File hash for malware variant\",\n", + " \"pattern\": \"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\",\n", + " \"valid_from\": \"2017-09-26T23:33:39.829952Z\"\n", + "}\"\"\", version=\"2.0\")\n", + "print(indicator)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Keep in mind that if a 2.1 or higher object is parsed, the operation will fail." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### How will custom work\n", + "\n", + "CustomObject, CustomObservable, CustomMarking and CustomExtension must be registered explicitly by STIX version. This is a design decision since properties or requirements may change as the STIX Technical Specification advances.\n", + "\n", + "You can perform this by," + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import stix2\n", + "\n", + "# Make my custom observable available in STIX 2.0\n", + "@stix2.v20.CustomObservable('x-new-object-type',\n", + " ((\"prop\", stix2.properties.BooleanProperty())))\n", + "class NewObject2(object):\n", + " pass\n", + "\n", + "\n", + "# Make my custom observable available in STIX 2.1\n", + "@stix2.v21.CustomObservable('x-new-object-type',\n", + " ((\"prop\", stix2.properties.BooleanProperty())))\n", + "class NewObject2(object):\n", + " pass" + ] + } + ], + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/setup.py b/setup.py index 75b5a43..72bc5d7 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ setup( 'Programming Language :: Python :: 3.6', ], keywords="stix stix2 json cti cyber threat intelligence", - packages=find_packages(), + packages=find_packages(exclude=['*.test']), install_requires=[ 'python-dateutil', 'pytz', diff --git a/stix2/__init__.py b/stix2/__init__.py index 55911a4..7ba3e99 100644 --- a/stix2/__init__.py +++ b/stix2/__init__.py @@ -19,27 +19,10 @@ # flake8: noqa -from . import exceptions -from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, - ExternalReference, GranularMarking, KillChainPhase, - MarkingDefinition, StatementMarking, TLPMarking) -from .core import Bundle, _register_type, parse +from .core import Bundle, _collect_stix2_obj_maps, _register_type, parse from .environment import Environment, ObjectFactory from .markings import (add_markings, clear_markings, get_markings, is_marked, remove_markings, set_markings) -from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact, - AutonomousSystem, CustomExtension, CustomObservable, - Directory, DomainName, EmailAddress, EmailMessage, - EmailMIMEComponent, File, HTTPRequestExt, ICMPExt, - IPv4Address, IPv6Address, MACAddress, Mutex, - NetworkTraffic, NTFSExt, PDFExt, Process, - RasterImageExt, SocketExt, Software, TCPExt, - UNIXAccountExt, UserAccount, WindowsPEBinaryExt, - WindowsPEOptionalHeaderType, WindowsPESection, - WindowsProcessExt, WindowsRegistryKey, - WindowsRegistryValueType, WindowsServiceExt, - X509Certificate, X509V3ExtenstionsType, - parse_observable) from .patterns import (AndBooleanExpression, AndObservationExpression, BasicObjectPathComponent, EqualityComparisonExpression, FloatConstant, FollowedByObservationExpression, @@ -58,9 +41,6 @@ from .patterns import (AndBooleanExpression, AndObservationExpression, ReferenceObjectPathComponent, RepeatQualifier, StartStopQualifier, StringConstant, TimestampConstant, WithinQualifier) -from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject, - Identity, Indicator, IntrusionSet, Malware, ObservedData, - Report, ThreatActor, Tool, Vulnerability) from .sources import CompositeDataSource from .sources.filesystem import (FileSystemSink, FileSystemSource, FileSystemStore) @@ -68,6 +48,10 @@ from .sources.filters import Filter from .sources.memory import MemorySink, MemorySource, MemoryStore from .sources.taxii import (TAXIICollectionSink, TAXIICollectionSource, TAXIICollectionStore) -from .sro import Relationship, Sighting from .utils import get_dict, new_version, revoke +from .v20 import * # This import will always be the latest STIX 2.X version from .version import __version__ + +_collect_stix2_obj_maps() + +DEFAULT_VERSION = "2.0" # Default version will always be the latest STIX 2.X version diff --git a/stix2/core.py b/stix2/core.py index 20bd187..658bb47 100644 --- a/stix2/core.py +++ b/stix2/core.py @@ -1,16 +1,15 @@ """STIX 2.0 Objects that are neither SDOs nor SROs.""" from collections import OrderedDict +import importlib +import pkgutil + +import stix2 from . import exceptions from .base import _STIXBase -from .common import MarkingDefinition from .properties import IDProperty, ListProperty, Property, TypeProperty -from .sdo import (AttackPattern, Campaign, CourseOfAction, Identity, Indicator, - IntrusionSet, Malware, ObservedData, Report, - STIXDomainObject, ThreatActor, Tool, Vulnerability) -from .sro import Relationship, Sighting, STIXRelationshipObject -from .utils import get_dict +from .utils import get_class_hierarchy_names, get_dict class STIXObjectProperty(Property): @@ -22,8 +21,8 @@ class STIXObjectProperty(Property): def clean(self, value): # Any STIX Object (SDO, SRO, or Marking Definition) can be added to # a bundle with no further checks. - if isinstance(value, (STIXDomainObject, STIXRelationshipObject, - MarkingDefinition)): + if any(x in ('STIXDomainObject', 'STIXRelationshipObject', 'MarkingDefinition') + for x in get_class_hierarchy_names(value)): return value try: dictified = get_dict(value) @@ -67,37 +66,30 @@ class Bundle(_STIXBase): super(Bundle, self).__init__(**kwargs) -OBJ_MAP = { - 'attack-pattern': AttackPattern, - 'bundle': Bundle, - 'campaign': Campaign, - 'course-of-action': CourseOfAction, - 'identity': Identity, - 'indicator': Indicator, - 'intrusion-set': IntrusionSet, - 'malware': Malware, - 'marking-definition': MarkingDefinition, - 'observed-data': ObservedData, - 'report': Report, - 'relationship': Relationship, - 'threat-actor': ThreatActor, - 'tool': Tool, - 'sighting': Sighting, - 'vulnerability': Vulnerability, -} +STIX2_OBJ_MAPS = {} -def parse(data, allow_custom=False): +def parse(data, allow_custom=False, version=None): """Deserialize a string or file-like object into a STIX object. Args: data (str, dict, file-like object): The STIX 2 content to be parsed. - allow_custom (bool): Whether to allow custom properties or not. Default: False. + allow_custom (bool): Whether to allow custom properties or not. + Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: An instantiated Python STIX object. """ + if not version: + # Use latest version + v = 'v' + stix2.DEFAULT_VERSION.replace('.', '') + else: + v = 'v' + version.replace('.', '') + + OBJ_MAP = STIX2_OBJ_MAPS[v] obj = get_dict(data) if 'type' not in obj: @@ -110,8 +102,34 @@ def parse(data, allow_custom=False): return obj_class(allow_custom=allow_custom, **obj) -def _register_type(new_type): +def _register_type(new_type, version=None): """Register a custom STIX Object type. + Args: + new_type (class): A class to register in the Object map. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. """ + if not version: + # Use latest version + v = 'v' + stix2.DEFAULT_VERSION.replace('.', '') + else: + v = 'v' + version.replace('.', '') + + OBJ_MAP = STIX2_OBJ_MAPS[v] OBJ_MAP[new_type._type] = new_type + + +def _collect_stix2_obj_maps(): + """Navigate the package once and retrieve all OBJ_MAP dicts for each v2X + package.""" + if not STIX2_OBJ_MAPS: + top_level_module = importlib.import_module('stix2') + path = top_level_module.__path__ + prefix = str(top_level_module.__name__) + '.' + + for module_loader, name, is_pkg in pkgutil.walk_packages(path=path, + prefix=prefix): + if name.startswith('stix2.v2') and is_pkg: + mod = importlib.import_module(name, str(top_level_module.__name__)) + STIX2_OBJ_MAPS[name.split('.')[-1]] = mod.OBJ_MAP diff --git a/stix2/sources/filesystem.py b/stix2/sources/filesystem.py index 576ba1b..eb83d8c 100644 --- a/stix2/sources/filesystem.py +++ b/stix2/sources/filesystem.py @@ -8,13 +8,10 @@ TODO: import json import os -from stix2.common import MarkingDefinition from stix2.core import Bundle, parse -from stix2.sdo import STIXDomainObject from stix2.sources import DataSink, DataSource, DataStore from stix2.sources.filters import Filter, apply_common_filters -from stix2.sro import STIXRelationshipObject -from stix2.utils import deduplicate +from stix2.utils import deduplicate, get_class_hierarchy_names class FileSystemStore(DataStore): @@ -78,7 +75,7 @@ class FileSystemSink(DataSink): with open(path, "w") as f: f.write(str(stix_obj)) - def add(self, stix_data=None, allow_custom=False): + def add(self, stix_data=None, allow_custom=False, version=None): """Add STIX objects to file directory. Args: @@ -87,6 +84,8 @@ class FileSystemSink(DataSink): json encoded string. allow_custom (bool): whether to allow custom objects/properties or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Note: ``stix_data`` can be a Bundle object, but each object in it will be @@ -94,12 +93,13 @@ class FileSystemSink(DataSink): the Bundle contained, but not the Bundle itself. """ - if isinstance(stix_data, (STIXDomainObject, STIXRelationshipObject, MarkingDefinition)): + if any(x in ('STIXDomainObject', 'STIXRelationshipObject', 'MarkingDefinition') + for x in get_class_hierarchy_names(stix_data)): # adding python STIX object self._check_path_and_write(stix_data) elif isinstance(stix_data, (str, dict)): - stix_data = parse(stix_data, allow_custom) + stix_data = parse(stix_data, allow_custom, version) if stix_data["type"] == "bundle": # extract STIX objects for stix_obj in stix_data.get("objects", []): @@ -146,15 +146,17 @@ class FileSystemSource(DataSource): def stix_dir(self): return self._stix_dir - def get(self, stix_id, _composite_filters=None, allow_custom=False): + def get(self, stix_id, _composite_filters=None, allow_custom=False, version=None): """Retrieve STIX object from file directory via STIX ID. Args: stix_id (str): The STIX ID of the STIX object to be retrieved. - composite_filters (set): set of filters passed from the parent + _composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied allow_custom (bool): whether to retrieve custom objects/properties or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (STIX object): STIX object that has the supplied STIX ID. @@ -164,7 +166,8 @@ class FileSystemSource(DataSource): """ query = [Filter("id", "=", stix_id)] - all_data = self.query(query=query, _composite_filters=_composite_filters, allow_custom=allow_custom) + all_data = self.query(query=query, _composite_filters=_composite_filters, + allow_custom=allow_custom, version=version) if all_data: stix_obj = sorted(all_data, key=lambda k: k['modified'])[0] @@ -173,7 +176,7 @@ class FileSystemSource(DataSource): return stix_obj - def all_versions(self, stix_id, _composite_filters=None, allow_custom=False): + def all_versions(self, stix_id, _composite_filters=None, allow_custom=False, version=None): """Retrieve STIX object from file directory via STIX ID, all versions. Note: Since FileSystem sources/sinks don't handle multiple versions @@ -181,10 +184,12 @@ class FileSystemSource(DataSource): Args: stix_id (str): The STIX ID of the STIX objects to be retrieved. - composite_filters (set): set of filters passed from the parent + _composite_filters (set): set of filters passed from the parent CompositeDataSource, not user supplied allow_custom (bool): whether to retrieve custom objects/properties or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (list): of STIX objects that has the supplied STIX ID. @@ -192,9 +197,10 @@ class FileSystemSource(DataSource): a python STIX objects and then returned """ - return [self.get(stix_id=stix_id, _composite_filters=_composite_filters, allow_custom=allow_custom)] + return [self.get(stix_id=stix_id, _composite_filters=_composite_filters, + allow_custom=allow_custom, version=version)] - def query(self, query=None, _composite_filters=None, allow_custom=False): + def query(self, query=None, _composite_filters=None, allow_custom=False, version=None): """Search and retrieve STIX objects based on the complete query. A "complete query" includes the filters from the query, the filters @@ -203,10 +209,12 @@ class FileSystemSource(DataSource): Args: query (list): list of filters to search on - composite_filters (set): set of filters passed from the + _composite_filters (set): set of filters passed from the CompositeDataSource, not user supplied allow_custom (bool): whether to retrieve custom objects/properties or not. Default: False. + version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If + None, use latest version. Returns: (list): list of STIX objects that matches the supplied @@ -297,7 +305,7 @@ class FileSystemSource(DataSource): all_data = deduplicate(all_data) # parse python STIX objects from the STIX object dicts - stix_objs = [parse(stix_obj_dict, allow_custom) for stix_obj_dict in all_data] + stix_objs = [parse(stix_obj_dict, allow_custom, version) for stix_obj_dict in all_data] return stix_objs diff --git a/stix2/test/test_bundle.py b/stix2/test/test_bundle.py index 24bbd43..b1cffd0 100644 --- a/stix2/test/test_bundle.py +++ b/stix2/test/test_bundle.py @@ -132,8 +132,9 @@ def test_create_bundle_invalid(indicator, malware, relationship): assert excinfo.value.reason == 'This property may not contain a Bundle object' -def test_parse_bundle(): - bundle = stix2.parse(EXPECTED_BUNDLE) +@pytest.mark.parametrize("version", ["2.0"]) +def test_parse_bundle(version): + bundle = stix2.parse(EXPECTED_BUNDLE, version=version) assert bundle.type == "bundle" assert bundle.id.startswith("bundle--") diff --git a/stix2/test/test_custom.py b/stix2/test/test_custom.py index c5726b8..92d5d4c 100644 --- a/stix2/test/test_custom.py +++ b/stix2/test/test_custom.py @@ -484,3 +484,13 @@ def test_parse_observable_with_unregistered_custom_extension(): with pytest.raises(ValueError) as excinfo: stix2.parse_observable(input_str) assert "Can't parse Unknown extension type" in str(excinfo.value) + + +def test_register_custom_object(): + # Not the way to register custom object. + class CustomObject2(object): + _type = 'awesome-object' + + stix2._register_type(CustomObject2) + # Note that we will always check against newest OBJ_MAP. + assert (CustomObject2._type, CustomObject2) in stix2.OBJ_MAP.items() diff --git a/stix2/test/test_properties.py b/stix2/test/test_properties.py index 7d03b9e..6bd1888 100644 --- a/stix2/test/test_properties.py +++ b/stix2/test/test_properties.py @@ -1,8 +1,7 @@ import pytest -from stix2 import TCPExt +from stix2 import EmailMIMEComponent, ExtensionsProperty, TCPExt from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError -from stix2.observables import EmailMIMEComponent, ExtensionsProperty from stix2.properties import (BinaryProperty, BooleanProperty, DictionaryProperty, EmbeddedObjectProperty, EnumProperty, FloatProperty, HashesProperty, diff --git a/stix2/utils.py b/stix2/utils.py index 4623f28..f23dbe2 100644 --- a/stix2/utils.py +++ b/stix2/utils.py @@ -247,3 +247,11 @@ def revoke(data): if data.get("revoked"): raise RevokeError("revoke") return new_version(data, revoked=True) + + +def get_class_hierarchy_names(obj): + """Given an object, return the names of the class hierarchy.""" + names = [] + for cls in obj.__class__.__mro__: + names.append(cls.__name__) + return names diff --git a/stix2/v20/__init__.py b/stix2/v20/__init__.py new file mode 100644 index 0000000..888e1ca --- /dev/null +++ b/stix2/v20/__init__.py @@ -0,0 +1,43 @@ + +# flake8: noqa + +from ..core import Bundle +from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE, CustomMarking, + ExternalReference, GranularMarking, KillChainPhase, + MarkingDefinition, StatementMarking, TLPMarking) +from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact, + AutonomousSystem, CustomExtension, CustomObservable, + Directory, DomainName, EmailAddress, EmailMessage, + EmailMIMEComponent, ExtensionsProperty, File, + HTTPRequestExt, ICMPExt, IPv4Address, IPv6Address, + MACAddress, Mutex, NetworkTraffic, NTFSExt, PDFExt, + Process, RasterImageExt, SocketExt, Software, TCPExt, + UNIXAccountExt, UserAccount, WindowsPEBinaryExt, + WindowsPEOptionalHeaderType, WindowsPESection, + WindowsProcessExt, WindowsRegistryKey, + WindowsRegistryValueType, WindowsServiceExt, + X509Certificate, X509V3ExtenstionsType, + parse_observable) +from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject, + Identity, Indicator, IntrusionSet, Malware, ObservedData, + Report, ThreatActor, Tool, Vulnerability) +from .sro import Relationship, Sighting + +OBJ_MAP = { + 'attack-pattern': AttackPattern, + 'bundle': Bundle, + 'campaign': Campaign, + 'course-of-action': CourseOfAction, + 'identity': Identity, + 'indicator': Indicator, + 'intrusion-set': IntrusionSet, + 'malware': Malware, + 'marking-definition': MarkingDefinition, + 'observed-data': ObservedData, + 'report': Report, + 'relationship': Relationship, + 'threat-actor': ThreatActor, + 'tool': Tool, + 'sighting': Sighting, + 'vulnerability': Vulnerability, +} diff --git a/stix2/common.py b/stix2/v20/common.py similarity index 94% rename from stix2/common.py rename to stix2/v20/common.py index 449cd54..2d15529 100644 --- a/stix2/common.py +++ b/stix2/v20/common.py @@ -2,12 +2,12 @@ from collections import OrderedDict -from .base import _STIXBase -from .markings import _MarkingsMixin -from .properties import (HashesProperty, IDProperty, ListProperty, Property, - ReferenceProperty, SelectorProperty, StringProperty, - TimestampProperty, TypeProperty) -from .utils import NOW, get_dict +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (HashesProperty, IDProperty, ListProperty, Property, + ReferenceProperty, SelectorProperty, StringProperty, + TimestampProperty, TypeProperty) +from ..utils import NOW, get_dict class ExternalReference(_STIXBase): diff --git a/stix2/observables.py b/stix2/v20/observables.py similarity index 98% rename from stix2/observables.py rename to stix2/v20/observables.py index aaec2d7..a874df9 100644 --- a/stix2/observables.py +++ b/stix2/v20/observables.py @@ -7,15 +7,15 @@ Observable and do not have a ``_type`` attribute. from collections import OrderedDict -from .base import _Extension, _Observable, _STIXBase -from .exceptions import (AtLeastOnePropertyError, DependentPropertiesError, - ParseError) -from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty, - EmbeddedObjectProperty, EnumProperty, FloatProperty, - HashesProperty, HexProperty, IntegerProperty, - ListProperty, ObjectReferenceProperty, Property, - StringProperty, TimestampProperty, TypeProperty) -from .utils import get_dict +from ..base import _Extension, _Observable, _STIXBase +from ..exceptions import (AtLeastOnePropertyError, DependentPropertiesError, + ParseError) +from ..properties import (BinaryProperty, BooleanProperty, DictionaryProperty, + EmbeddedObjectProperty, EnumProperty, FloatProperty, + HashesProperty, HexProperty, IntegerProperty, + ListProperty, ObjectReferenceProperty, Property, + StringProperty, TimestampProperty, TypeProperty) +from ..utils import get_dict class ObservableProperty(Property): diff --git a/stix2/sdo.py b/stix2/v20/sdo.py similarity index 97% rename from stix2/sdo.py rename to stix2/v20/sdo.py index 8dad686..1af0777 100644 --- a/stix2/sdo.py +++ b/stix2/v20/sdo.py @@ -4,14 +4,14 @@ from collections import OrderedDict import stix2 -from .base import _STIXBase +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (BooleanProperty, IDProperty, IntegerProperty, + ListProperty, PatternProperty, ReferenceProperty, + StringProperty, TimestampProperty, TypeProperty) +from ..utils import NOW from .common import ExternalReference, GranularMarking, KillChainPhase -from .markings import _MarkingsMixin from .observables import ObservableProperty -from .properties import (BooleanProperty, IDProperty, IntegerProperty, - ListProperty, PatternProperty, ReferenceProperty, - StringProperty, TimestampProperty, TypeProperty) -from .utils import NOW class STIXDomainObject(_STIXBase, _MarkingsMixin): @@ -358,7 +358,7 @@ def CustomObject(type='x-custom-type', properties=None): return raise e - stix2._register_type(_Custom) + stix2._register_type(_Custom, version="2.0") return _Custom return custom_builder diff --git a/stix2/sro.py b/stix2/v20/sro.py similarity index 91% rename from stix2/sro.py rename to stix2/v20/sro.py index 60f99f5..7f05f5e 100644 --- a/stix2/sro.py +++ b/stix2/v20/sro.py @@ -2,13 +2,13 @@ from collections import OrderedDict -from .base import _STIXBase +from ..base import _STIXBase +from ..markings import _MarkingsMixin +from ..properties import (BooleanProperty, IDProperty, IntegerProperty, + ListProperty, ReferenceProperty, StringProperty, + TimestampProperty, TypeProperty) +from ..utils import NOW from .common import ExternalReference, GranularMarking -from .markings import _MarkingsMixin -from .properties import (BooleanProperty, IDProperty, IntegerProperty, - ListProperty, ReferenceProperty, StringProperty, - TimestampProperty, TypeProperty) -from .utils import NOW class STIXRelationshipObject(_STIXBase, _MarkingsMixin):