commit
388d95d456
|
@ -4,7 +4,9 @@ not_skip = __init__.py
|
|||
known_third_party =
|
||||
antlr4,
|
||||
dateutil,
|
||||
haversine,
|
||||
medallion,
|
||||
pyjarowinkler,
|
||||
pytest,
|
||||
pytz,
|
||||
requests,
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
sudo: false
|
||||
language: python
|
||||
cache: pip
|
||||
dist: xenial
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.4"
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
matrix:
|
||||
include:
|
||||
- python: 3.7 # https://github.com/travis-ci/travis-ci/issues/9069#issuecomment-425720905
|
||||
dist: xenial
|
||||
sudo: true
|
||||
- "3.7"
|
||||
install:
|
||||
- pip install -U pip setuptools
|
||||
- pip install tox-travis pre-commit
|
||||
|
|
15
CHANGELOG
15
CHANGELOG
|
@ -1,6 +1,21 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
1.2.1 - 2019-10-16
|
||||
|
||||
* #301 Adds more detailed debugging semantic equivalence output
|
||||
* #301 Updates semantic equivalence errors
|
||||
* #300 Fixes bug with deterministic IDs for SCOs containing unicode
|
||||
|
||||
1.2.0 - 2019-09-25
|
||||
|
||||
* #268, #271, #273, #275, #283, #285, #290 Changes support of STIX 2.1 to WD05 (CSD02), for all object types
|
||||
* #269 Updates id properties to take a spec_version parameter
|
||||
* #283 Changes the exception class hierarchy
|
||||
* #289 Adds functions for calculating semantic equivalence of two objects
|
||||
* #286 Fixes handling of custom observable extensions
|
||||
* #287 Fixes bug with timestamp precision preservation in MarkingDefinition objects
|
||||
|
||||
1.1.3 - 2019-08-12
|
||||
|
||||
* #258 Ignores empty values for optional fields
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
|Build_Status| |Coverage| |Version| |Downloads_Badge|
|
||||
|Build_Status| |Coverage| |Version| |Downloads_Badge| |Documentation_Status|
|
||||
|
||||
cti-python-stix2
|
||||
================
|
||||
|
@ -170,3 +170,6 @@ to repository-cla@oasis-open.org.
|
|||
.. |Downloads_Badge| image:: https://img.shields.io/pypi/dm/stix2.svg?maxAge=3600
|
||||
:target: https://pypi.python.org/pypi/stix2/
|
||||
:alt: Downloads
|
||||
.. |Documentation_Status| image:: https://readthedocs.org/projects/stix2/badge/?version=latest
|
||||
:target: https://stix2.readthedocs.io/en/latest/?badge=latest
|
||||
:alt: Documentation Status
|
||||
|
|
|
@ -537,7 +537,7 @@
|
|||
"source": [
|
||||
"### Custom STIX Object Types\n",
|
||||
"\n",
|
||||
"To create a custom STIX object type, define a class with the @[CustomObject](../api/stix2.v20.sdo.rst#stix2.v20.sdo.CustomObject) decorator. It takes the type name and a list of property tuples, each tuple consisting of the property name and a property instance. Any special validation of the properties can be added by supplying an ``__init__`` function.\n",
|
||||
"To create a custom STIX object type, define a class with the @[CustomObject](../api/v20/stix2.v20.sdo.rst#stix2.v20.sdo.CustomObject) decorator. It takes the type name and a list of property tuples, each tuple consisting of the property name and a property instance. Any special validation of the properties can be added by supplying an ``__init__`` function.\n",
|
||||
"\n",
|
||||
"Let's say zoo animals have become a serious cyber threat and we want to model them in STIX using a custom object type. Let's use a ``species`` property to store the kind of animal, and make that property required. We also want a property to store the class of animal, such as \"mammal\" or \"bird\" but only want to allow specific values in it. We can add some logic to validate this property in ``__init__``."
|
||||
]
|
||||
|
@ -841,7 +841,7 @@
|
|||
"source": [
|
||||
"### Custom Cyber Observable Types\n",
|
||||
"\n",
|
||||
"Similar to custom STIX object types, use a decorator to create [custom Cyber Observable](../api/stix2.v20.observables.rst#stix2.v20.observables.CustomObservable) types. Just as before, ``__init__()`` can hold additional validation, but it is not necessary."
|
||||
"Similar to custom STIX object types, use a decorator to create [custom Cyber Observable](../api/v20/stix2.v20.observables.rst#stix2.v20.observables.CustomObservable) types. Just as before, ``__init__()`` can hold additional validation, but it is not necessary."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1163,7 +1163,7 @@
|
|||
"source": [
|
||||
"### Custom Cyber Observable Extensions\n",
|
||||
"\n",
|
||||
"Finally, custom extensions to existing Cyber Observable types can also be created. Just use the @[CustomExtension](../api/stix2.v20.observables.rst#stix2.v20.observables.CustomExtension) decorator. Note that you must provide the Cyber Observable class to which the extension applies. Again, any extra validation of the properties can be implemented by providing an ``__init__()`` but it is not required. Let's say we want to make an extension to the ``File`` Cyber Observable Object:"
|
||||
"Finally, custom extensions to existing Cyber Observable types can also be created. Just use the @[CustomExtension](../api/v20/stix2.v20.observables.rst#stix2.v20.observables.CustomExtension) decorator. Note that you must provide the Cyber Observable class to which the extension applies. Again, any extra validation of the properties can be implemented by providing an ``__init__()`` but it is not required. Let's say we want to make an extension to the ``File`` Cyber Observable Object:"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -450,6 +450,14 @@
|
|||
"mem.source.filters.add([f1,f2])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Note: The `defanged` property is now always included (implicitly) for STIX 2.1 Cyber Observable Objects (SCOs)**\n\n",
|
||||
"This is important to remember if you are writing a filter that involves checking the `objects` property of a STIX 2.1 `ObservedData` object. If any of the objects associated with the `objects` property are STIX 2.1 SCOs, then your filter must include the `defanged` property. For an example, refer to `filters[14]` & `filters[15]` in stix2/test/v21/test_datastore_filters.py "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
|
@ -484,7 +492,7 @@
|
|||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"If a STIX object has a `created_by_ref` property, you can use the [creator_of()](../api/stix2.datastore.rst#stix2.datastore.DataSource.creator_of) method to retrieve the [Identity](../api/stix2.v20.sdo.rst#stix2.v20.sdo.Identity) object that created it."
|
||||
"If a STIX object has a `created_by_ref` property, you can use the [creator_of()](../api/stix2.datastore.rst#stix2.datastore.DataSource.creator_of) method to retrieve the [Identity](../api/v20/stix2.v20.sdo.rst#stix2.v20.sdo.Identity) object that created it."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -726,21 +734,21 @@
|
|||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "cti-python-stix2",
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "cti-python-stix2"
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.12"
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -365,7 +365,7 @@
|
|||
"source": [
|
||||
"### How custom content works\n",
|
||||
"\n",
|
||||
"[CustomObject](../api/stix2.v20.sdo.rst#stix2.v20.sdo.CustomObject), [CustomObservable](../api/stix2.v20.observables.rst#stix2.v20.observables.CustomObservable), [CustomMarking](../api/stix2.v20.common.rst#stix2.v20.common.CustomMarking) and [CustomExtension](../api/stix2.v20.observables.rst#stix2.v20.observables.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",
|
||||
"[CustomObject](../api/v20/stix2.v20.sdo.rst#stix2.v20.sdo.CustomObject), [CustomObservable](../api/v20/stix2.v20.observables.rst#stix2.v20.observables.CustomObservable), [CustomMarking](../api/v20/stix2.v20.common.rst#stix2.v20.common.CustomMarking) and [CustomExtension](../api/v20/stix2.v20.observables.rst#stix2.v20.observables.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:"
|
||||
]
|
||||
|
|
|
@ -624,7 +624,7 @@
|
|||
"source": [
|
||||
"### Creating STIX Data\n",
|
||||
"\n",
|
||||
"To create a STIX object, just use that object's class constructor. Once it's created, add it to the workbench with [save()](../api/datastore/stix2.workbench.rst#stix2.workbench.save)."
|
||||
"To create a STIX object, just use that object's class constructor. Once it's created, add it to the workbench with [save()](../api/stix2.workbench.rst#stix2.workbench.save)."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -760,7 +760,7 @@
|
|||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Defaults can also be set for the [created timestamp](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_created), [external references](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_external_refs) and [object marking references](../api/datastore/stix2.workbench.rst#stix2.workbench.set_default_object_marking_refs)."
|
||||
"Defaults can also be set for the [created timestamp](../api/stix2.workbench.rst#stix2.workbench.set_default_created), [external references](../api/stix2.workbench.rst#stix2.workbench.set_default_external_refs) and [object marking references](../api/stix2.workbench.rst#stix2.workbench.set_default_object_marking_refs)."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
bumpversion
|
||||
ipython
|
||||
nbsphinx==0.3.2
|
||||
nbsphinx==0.4.3
|
||||
pre-commit
|
||||
pytest
|
||||
pytest-cov
|
||||
sphinx<1.6
|
||||
sphinx<2
|
||||
sphinx-prompt
|
||||
tox
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 1.1.3
|
||||
current_version = 1.2.1
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
|
|
1
setup.py
1
setup.py
|
@ -64,5 +64,6 @@ setup(
|
|||
},
|
||||
extras_require={
|
||||
'taxii': ['taxii2-client'],
|
||||
'semantic': ['haversine', 'pyjarowinkler'],
|
||||
},
|
||||
)
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
|
||||
# flake8: noqa
|
||||
|
||||
DEFAULT_VERSION = '2.0' # Default version will always be the latest STIX 2.X version
|
||||
|
||||
from .confidence import scales
|
||||
from .core import _collect_stix2_mappings, parse, parse_observable
|
||||
from .datastore import CompositeDataSource
|
||||
|
@ -56,5 +58,3 @@ from .v20 import * # This import will always be the latest STIX 2.X version
|
|||
from .version import __version__
|
||||
|
||||
_collect_stix2_mappings()
|
||||
|
||||
DEFAULT_VERSION = '2.0' # Default version will always be the latest STIX 2.X version
|
||||
|
|
131
stix2/base.py
131
stix2/base.py
|
@ -3,14 +3,17 @@
|
|||
import collections
|
||||
import copy
|
||||
import datetime as dt
|
||||
import uuid
|
||||
|
||||
import simplejson as json
|
||||
import six
|
||||
|
||||
from stix2.canonicalization.Canonicalize import canonicalize
|
||||
|
||||
from .exceptions import (
|
||||
AtLeastOnePropertyError, CustomContentError, DependentPropertiesError,
|
||||
ExtraPropertiesError, ImmutableError, InvalidObjRefError,
|
||||
InvalidValueError, MissingPropertiesError,
|
||||
MutuallyExclusivePropertiesError,
|
||||
AtLeastOnePropertyError, DependentPropertiesError, ExtraPropertiesError,
|
||||
ImmutableError, InvalidObjRefError, InvalidValueError,
|
||||
MissingPropertiesError, MutuallyExclusivePropertiesError,
|
||||
)
|
||||
from .markings.utils import validate
|
||||
from .utils import NOW, find_property_index, format_datetime, get_timestamp
|
||||
|
@ -20,6 +23,7 @@ from .utils import revoke as _revoke
|
|||
__all__ = ['STIXJSONEncoder', '_STIXBase']
|
||||
|
||||
DEFAULT_ERROR = "{type} must have {property}='{expected}'."
|
||||
SCO_DET_ID_NAMESPACE = uuid.UUID("00abedb4-aa42-466c-9c01-fed23315a9b7")
|
||||
|
||||
|
||||
class STIXJSONEncoder(json.JSONEncoder):
|
||||
|
@ -88,10 +92,17 @@ class _STIXBase(collections.Mapping):
|
|||
if prop_name in kwargs:
|
||||
try:
|
||||
kwargs[prop_name] = prop.clean(kwargs[prop_name])
|
||||
except ValueError as exc:
|
||||
if self.__allow_custom and isinstance(exc, CustomContentError):
|
||||
return
|
||||
raise InvalidValueError(self.__class__, prop_name, reason=str(exc))
|
||||
except InvalidValueError:
|
||||
# No point in wrapping InvalidValueError in another
|
||||
# InvalidValueError... so let those propagate.
|
||||
raise
|
||||
except Exception as exc:
|
||||
six.raise_from(
|
||||
InvalidValueError(
|
||||
self.__class__, prop_name, reason=str(exc),
|
||||
),
|
||||
exc,
|
||||
)
|
||||
|
||||
# interproperty constraint methods
|
||||
|
||||
|
@ -105,10 +116,15 @@ class _STIXBase(collections.Mapping):
|
|||
def _check_at_least_one_property(self, list_of_properties=None):
|
||||
if not list_of_properties:
|
||||
list_of_properties = sorted(list(self.__class__._properties.keys()))
|
||||
if 'type' in list_of_properties:
|
||||
list_of_properties.remove('type')
|
||||
if isinstance(self, _Observable):
|
||||
props_to_remove = ["type", "id", "defanged", "spec_version"]
|
||||
else:
|
||||
props_to_remove = ["type"]
|
||||
|
||||
list_of_properties = [prop for prop in list_of_properties if prop not in props_to_remove]
|
||||
current_properties = self.properties_populated()
|
||||
list_of_properties_populated = set(list_of_properties).intersection(current_properties)
|
||||
|
||||
if list_of_properties and (not list_of_properties_populated or list_of_properties_populated == set(['extensions'])):
|
||||
raise AtLeastOnePropertyError(self.__class__, list_of_properties)
|
||||
|
||||
|
@ -293,9 +309,26 @@ class _Observable(_STIXBase):
|
|||
self.__allow_custom = kwargs.get('allow_custom', False)
|
||||
self._properties['extensions'].allow_custom = kwargs.get('allow_custom', False)
|
||||
|
||||
try:
|
||||
# Since `spec_version` is optional, this is how we check for a 2.1 SCO
|
||||
self._id_contributing_properties
|
||||
|
||||
if 'id' not in kwargs:
|
||||
possible_id = self._generate_id(kwargs)
|
||||
if possible_id is not None:
|
||||
kwargs['id'] = possible_id
|
||||
except AttributeError:
|
||||
# End up here if handling a 2.0 SCO, and don't need to do anything further
|
||||
pass
|
||||
|
||||
super(_Observable, self).__init__(**kwargs)
|
||||
|
||||
def _check_ref(self, ref, prop, prop_name):
|
||||
"""
|
||||
Only for checking `*_ref` or `*_refs` properties in spec_version 2.0
|
||||
STIX Cyber Observables (SCOs)
|
||||
"""
|
||||
|
||||
if '*' in self._STIXBase__valid_refs:
|
||||
return # don't check if refs are valid
|
||||
|
||||
|
@ -324,12 +357,54 @@ class _Observable(_STIXBase):
|
|||
if prop_name not in kwargs:
|
||||
return
|
||||
|
||||
from .properties import ObjectReferenceProperty
|
||||
if prop_name.endswith('_ref'):
|
||||
ref = kwargs[prop_name]
|
||||
self._check_ref(ref, prop, prop_name)
|
||||
elif prop_name.endswith('_refs'):
|
||||
for ref in kwargs[prop_name]:
|
||||
if isinstance(prop, ObjectReferenceProperty):
|
||||
ref = kwargs[prop_name]
|
||||
self._check_ref(ref, prop, prop_name)
|
||||
elif prop_name.endswith('_refs'):
|
||||
if isinstance(prop.contained, ObjectReferenceProperty):
|
||||
for ref in kwargs[prop_name]:
|
||||
self._check_ref(ref, prop, prop_name)
|
||||
|
||||
def _generate_id(self, kwargs):
|
||||
required_prefix = self._type + "--"
|
||||
|
||||
properties_to_use = self._id_contributing_properties
|
||||
if properties_to_use:
|
||||
streamlined_obj_vals = []
|
||||
if "hashes" in kwargs and "hashes" in properties_to_use:
|
||||
possible_hash = _choose_one_hash(kwargs["hashes"])
|
||||
if possible_hash:
|
||||
streamlined_obj_vals.append(possible_hash)
|
||||
for key in properties_to_use:
|
||||
if key != "hashes" and key in kwargs:
|
||||
if isinstance(kwargs[key], dict) or isinstance(kwargs[key], _STIXBase):
|
||||
temp_deep_copy = copy.deepcopy(dict(kwargs[key]))
|
||||
_recursive_stix_to_dict(temp_deep_copy)
|
||||
streamlined_obj_vals.append(temp_deep_copy)
|
||||
elif isinstance(kwargs[key], list) and isinstance(kwargs[key][0], _STIXBase):
|
||||
for obj in kwargs[key]:
|
||||
temp_deep_copy = copy.deepcopy(dict(obj))
|
||||
_recursive_stix_to_dict(temp_deep_copy)
|
||||
streamlined_obj_vals.append(temp_deep_copy)
|
||||
else:
|
||||
streamlined_obj_vals.append(kwargs[key])
|
||||
|
||||
if streamlined_obj_vals:
|
||||
data = canonicalize(streamlined_obj_vals, utf8=False)
|
||||
|
||||
# The situation is complicated w.r.t. python 2/3 behavior, so
|
||||
# I'd rather not rely on particular exceptions being raised to
|
||||
# determine what to do. Better to just check the python version
|
||||
# directly.
|
||||
if six.PY3:
|
||||
return required_prefix + six.text_type(uuid.uuid5(SCO_DET_ID_NAMESPACE, data))
|
||||
else:
|
||||
return required_prefix + six.text_type(uuid.uuid5(SCO_DET_ID_NAMESPACE, data.encode("utf-8")))
|
||||
|
||||
# We return None if there are no values specified for any of the id-contributing-properties
|
||||
return None
|
||||
|
||||
|
||||
class _Extension(_STIXBase):
|
||||
|
@ -339,6 +414,34 @@ class _Extension(_STIXBase):
|
|||
self._check_at_least_one_property()
|
||||
|
||||
|
||||
def _choose_one_hash(hash_dict):
|
||||
if "MD5" in hash_dict:
|
||||
return {"MD5": hash_dict["MD5"]}
|
||||
elif "SHA-1" in hash_dict:
|
||||
return {"SHA-1": hash_dict["SHA-1"]}
|
||||
elif "SHA-256" in hash_dict:
|
||||
return {"SHA-256": hash_dict["SHA-256"]}
|
||||
elif "SHA-512" in hash_dict:
|
||||
return {"SHA-512": hash_dict["SHA-512"]}
|
||||
else:
|
||||
k = next(iter(hash_dict), None)
|
||||
if k is not None:
|
||||
return {k: hash_dict[k]}
|
||||
|
||||
|
||||
def _cls_init(cls, obj, kwargs):
|
||||
if getattr(cls, '__init__', object.__init__) is not object.__init__:
|
||||
cls.__init__(obj, **kwargs)
|
||||
|
||||
|
||||
def _recursive_stix_to_dict(input_dict):
|
||||
for key in input_dict:
|
||||
if isinstance(input_dict[key], dict):
|
||||
_recursive_stix_to_dict(input_dict[key])
|
||||
elif isinstance(input_dict[key], _STIXBase):
|
||||
input_dict[key] = dict(input_dict[key])
|
||||
|
||||
# There may stil be nested _STIXBase objects
|
||||
_recursive_stix_to_dict(input_dict[key])
|
||||
else:
|
||||
return
|
||||
|
|
|
@ -0,0 +1,512 @@
|
|||
##############################################################################
|
||||
# #
|
||||
# Copyright 2006-2019 WebPKI.org (http://webpki.org). #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); #
|
||||
# you may not use this file except in compliance with the License. #
|
||||
# You may obtain a copy of the License at #
|
||||
# #
|
||||
# https://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# #
|
||||
##############################################################################
|
||||
|
||||
#################################################
|
||||
# JCS compatible JSON serializer for Python 3.x #
|
||||
#################################################
|
||||
|
||||
# This file has been modified to be compatible with Python 2.x as well
|
||||
|
||||
import re
|
||||
|
||||
import six
|
||||
|
||||
from stix2.canonicalization.NumberToJson import convert2Es6Format
|
||||
|
||||
try:
|
||||
from _json import encode_basestring_ascii as c_encode_basestring_ascii
|
||||
except ImportError:
|
||||
c_encode_basestring_ascii = None
|
||||
try:
|
||||
from _json import encode_basestring as c_encode_basestring
|
||||
except ImportError:
|
||||
c_encode_basestring = None
|
||||
try:
|
||||
from _json import make_encoder as c_make_encoder
|
||||
except ImportError:
|
||||
c_make_encoder = None
|
||||
|
||||
ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
|
||||
ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
|
||||
HAS_UTF8 = re.compile(b'[\x80-\xff]')
|
||||
ESCAPE_DCT = {
|
||||
'\\': '\\\\',
|
||||
'"': '\\"',
|
||||
'\b': '\\b',
|
||||
'\f': '\\f',
|
||||
'\n': '\\n',
|
||||
'\r': '\\r',
|
||||
'\t': '\\t',
|
||||
}
|
||||
for i in range(0x20):
|
||||
ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
|
||||
|
||||
INFINITY = float('inf')
|
||||
|
||||
|
||||
def py_encode_basestring(s):
|
||||
"""Return a JSON representation of a Python string
|
||||
|
||||
"""
|
||||
def replace(match):
|
||||
return ESCAPE_DCT[match.group(0)]
|
||||
return '"' + ESCAPE.sub(replace, s) + '"'
|
||||
|
||||
|
||||
encode_basestring = (c_encode_basestring or py_encode_basestring)
|
||||
|
||||
|
||||
def py_encode_basestring_ascii(s):
|
||||
"""Return an ASCII-only JSON representation of a Python string
|
||||
|
||||
"""
|
||||
def replace(match):
|
||||
s = match.group(0)
|
||||
try:
|
||||
return ESCAPE_DCT[s]
|
||||
except KeyError:
|
||||
n = ord(s)
|
||||
if n < 0x10000:
|
||||
return '\\u{0:04x}'.format(n)
|
||||
else:
|
||||
# surrogate pair
|
||||
n -= 0x10000
|
||||
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
||||
s2 = 0xdc00 | (n & 0x3ff)
|
||||
return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
|
||||
return '"' + ESCAPE_ASCII.sub(replace, s) + '"'
|
||||
|
||||
|
||||
encode_basestring_ascii = (
|
||||
c_encode_basestring_ascii or py_encode_basestring_ascii
|
||||
)
|
||||
|
||||
|
||||
class JSONEncoder(object):
|
||||
"""Extensible JSON <http://json.org> encoder for Python data structures.
|
||||
|
||||
Supports the following objects and types by default:
|
||||
|
||||
+-------------------+---------------+
|
||||
| Python | JSON |
|
||||
+===================+===============+
|
||||
| dict | object |
|
||||
+-------------------+---------------+
|
||||
| list, tuple | array |
|
||||
+-------------------+---------------+
|
||||
| str | string |
|
||||
+-------------------+---------------+
|
||||
| int, float | number |
|
||||
+-------------------+---------------+
|
||||
| True | true |
|
||||
+-------------------+---------------+
|
||||
| False | false |
|
||||
+-------------------+---------------+
|
||||
| None | null |
|
||||
+-------------------+---------------+
|
||||
|
||||
To extend this to recognize other objects, subclass and implement a
|
||||
``.default()`` method with another method that returns a serializable
|
||||
object for ``o`` if possible, otherwise it should call the superclass
|
||||
implementation (to raise ``TypeError``).
|
||||
|
||||
"""
|
||||
item_separator = ', '
|
||||
key_separator = ': '
|
||||
|
||||
def __init__(
|
||||
self, skipkeys=False, ensure_ascii=False,
|
||||
check_circular=True, allow_nan=True, sort_keys=True,
|
||||
indent=None, separators=(',', ':'), default=None,
|
||||
):
|
||||
"""Constructor for JSONEncoder, with sensible defaults.
|
||||
|
||||
If skipkeys is false, then it is a TypeError to attempt
|
||||
encoding of keys that are not str, int, float or None. If
|
||||
skipkeys is True, such items are simply skipped.
|
||||
|
||||
If ensure_ascii is true, the output is guaranteed to be str
|
||||
objects with all incoming non-ASCII characters escaped. If
|
||||
ensure_ascii is false, the output can contain non-ASCII characters.
|
||||
|
||||
If check_circular is true, then lists, dicts, and custom encoded
|
||||
objects will be checked for circular references during encoding to
|
||||
prevent an infinite recursion (which would cause an OverflowError).
|
||||
Otherwise, no such check takes place.
|
||||
|
||||
If allow_nan is true, then NaN, Infinity, and -Infinity will be
|
||||
encoded as such. This behavior is not JSON specification compliant,
|
||||
but is consistent with most JavaScript based encoders and decoders.
|
||||
Otherwise, it will be a ValueError to encode such floats.
|
||||
|
||||
If sort_keys is true, then the output of dictionaries will be
|
||||
sorted by key; this is useful for regression tests to ensure
|
||||
that JSON serializations can be compared on a day-to-day basis.
|
||||
|
||||
If indent is a non-negative integer, then JSON array
|
||||
elements and object members will be pretty-printed with that
|
||||
indent level. An indent level of 0 will only insert newlines.
|
||||
None is the most compact representation.
|
||||
|
||||
If specified, separators should be an (item_separator, key_separator)
|
||||
tuple. The default is (', ', ': ') if *indent* is ``None`` and
|
||||
(',', ': ') otherwise. To get the most compact JSON representation,
|
||||
you should specify (',', ':') to eliminate whitespace.
|
||||
|
||||
If specified, default is a function that gets called for objects
|
||||
that can't otherwise be serialized. It should return a JSON encodable
|
||||
version of the object or raise a ``TypeError``.
|
||||
|
||||
"""
|
||||
|
||||
self.skipkeys = skipkeys
|
||||
self.ensure_ascii = ensure_ascii
|
||||
self.check_circular = check_circular
|
||||
self.allow_nan = allow_nan
|
||||
self.sort_keys = sort_keys
|
||||
self.indent = indent
|
||||
if separators is not None:
|
||||
self.item_separator, self.key_separator = separators
|
||||
elif indent is not None:
|
||||
self.item_separator = ','
|
||||
if default is not None:
|
||||
self.default = default
|
||||
|
||||
def default(self, o):
|
||||
"""Implement this method in a subclass such that it returns
|
||||
a serializable object for ``o``, or calls the base implementation
|
||||
(to raise a ``TypeError``).
|
||||
|
||||
For example, to support arbitrary iterators, you could
|
||||
implement default like this::
|
||||
|
||||
def default(self, o):
|
||||
try:
|
||||
iterable = iter(o)
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
return list(iterable)
|
||||
# Let the base class default method raise the TypeError
|
||||
return JSONEncoder.default(self, o)
|
||||
|
||||
"""
|
||||
raise TypeError(
|
||||
"Object of type '%s' is not JSON serializable" %
|
||||
o.__class__.__name__,
|
||||
)
|
||||
|
||||
def encode(self, o):
|
||||
"""Return a JSON string representation of a Python data structure.
|
||||
|
||||
>>> from json.encoder import JSONEncoder
|
||||
>>> JSONEncoder().encode({"foo": ["bar", "baz"]})
|
||||
'{"foo": ["bar", "baz"]}'
|
||||
|
||||
"""
|
||||
# This is for extremely simple cases and benchmarks.
|
||||
if isinstance(o, str):
|
||||
if self.ensure_ascii:
|
||||
return encode_basestring_ascii(o)
|
||||
else:
|
||||
return encode_basestring(o)
|
||||
# This doesn't pass the iterator directly to ''.join() because the
|
||||
# exceptions aren't as detailed. The list call should be roughly
|
||||
# equivalent to the PySequence_Fast that ''.join() would do.
|
||||
chunks = self.iterencode(o, _one_shot=False)
|
||||
if not isinstance(chunks, (list, tuple)):
|
||||
chunks = list(chunks)
|
||||
return ''.join(chunks)
|
||||
|
||||
def iterencode(self, o, _one_shot=False):
|
||||
"""Encode the given object and yield each string
|
||||
representation as available.
|
||||
|
||||
For example::
|
||||
|
||||
for chunk in JSONEncoder().iterencode(bigobject):
|
||||
mysocket.write(chunk)
|
||||
|
||||
"""
|
||||
if self.check_circular:
|
||||
markers = {}
|
||||
else:
|
||||
markers = None
|
||||
if self.ensure_ascii:
|
||||
_encoder = encode_basestring_ascii
|
||||
else:
|
||||
_encoder = encode_basestring
|
||||
|
||||
def floatstr(
|
||||
o, allow_nan=self.allow_nan,
|
||||
_repr=float.__repr__, _inf=INFINITY, _neginf=-INFINITY,
|
||||
):
|
||||
# Check for specials. Note that this type of test is processor
|
||||
# and/or platform-specific, so do tests which don't depend on the
|
||||
# internals.
|
||||
|
||||
if o != o:
|
||||
text = 'NaN'
|
||||
elif o == _inf:
|
||||
text = 'Infinity'
|
||||
elif o == _neginf:
|
||||
text = '-Infinity'
|
||||
else:
|
||||
return _repr(o)
|
||||
|
||||
if not allow_nan:
|
||||
raise ValueError(
|
||||
"Out of range float values are not JSON compliant: " +
|
||||
repr(o),
|
||||
)
|
||||
|
||||
return text
|
||||
|
||||
if (
|
||||
_one_shot and c_make_encoder is not None
|
||||
and self.indent is None
|
||||
):
|
||||
_iterencode = c_make_encoder(
|
||||
markers, self.default, _encoder, self.indent,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, self.allow_nan,
|
||||
)
|
||||
else:
|
||||
_iterencode = _make_iterencode(
|
||||
markers, self.default, _encoder, self.indent, floatstr,
|
||||
self.key_separator, self.item_separator, self.sort_keys,
|
||||
self.skipkeys, _one_shot,
|
||||
)
|
||||
return _iterencode(o, 0)
|
||||
|
||||
|
||||
def _make_iterencode(
|
||||
markers, _default, _encoder, _indent, _floatstr,
|
||||
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
|
||||
# HACK: hand-optimized bytecode; turn globals into locals
|
||||
ValueError=ValueError,
|
||||
dict=dict,
|
||||
float=float,
|
||||
id=id,
|
||||
int=int,
|
||||
isinstance=isinstance,
|
||||
list=list,
|
||||
str=str,
|
||||
tuple=tuple,
|
||||
_intstr=int.__str__,
|
||||
):
|
||||
|
||||
if _indent is not None and not isinstance(_indent, str):
|
||||
_indent = ' ' * _indent
|
||||
|
||||
def _iterencode_list(lst, _current_indent_level):
|
||||
if not lst:
|
||||
yield '[]'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(lst)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = lst
|
||||
buf = '['
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + _indent * _current_indent_level
|
||||
separator = _item_separator + newline_indent
|
||||
buf += newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
separator = _item_separator
|
||||
first = True
|
||||
for value in lst:
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
buf = separator
|
||||
if isinstance(value, str):
|
||||
yield buf + _encoder(value)
|
||||
elif value is None:
|
||||
yield buf + 'null'
|
||||
elif value is True:
|
||||
yield buf + 'true'
|
||||
elif value is False:
|
||||
yield buf + 'false'
|
||||
elif isinstance(value, int):
|
||||
# Subclasses of int/float may override __str__, but we still
|
||||
# want to encode them as integers/floats in JSON. One example
|
||||
# within the standard library is IntEnum.
|
||||
yield buf + convert2Es6Format(value)
|
||||
elif isinstance(value, float):
|
||||
# see comment above for int
|
||||
yield buf + convert2Es6Format(value)
|
||||
else:
|
||||
yield buf
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
# Below line commented-out for python2 compatibility
|
||||
# yield from chunks
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + _indent * _current_indent_level
|
||||
yield ']'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode_dict(dct, _current_indent_level):
|
||||
if not dct:
|
||||
yield '{}'
|
||||
return
|
||||
if markers is not None:
|
||||
markerid = id(dct)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = dct
|
||||
yield '{'
|
||||
if _indent is not None:
|
||||
_current_indent_level += 1
|
||||
newline_indent = '\n' + _indent * _current_indent_level
|
||||
item_separator = _item_separator + newline_indent
|
||||
yield newline_indent
|
||||
else:
|
||||
newline_indent = None
|
||||
item_separator = _item_separator
|
||||
first = True
|
||||
if _sort_keys:
|
||||
items = sorted(dct.items(), key=lambda kv: kv[0].encode('utf-16_be'))
|
||||
else:
|
||||
items = dct.items()
|
||||
for key, value in items:
|
||||
# Replaced isinstance(key, str) with below to enable simultaneous python 2 & 3 compatibility
|
||||
if isinstance(key, six.string_types) or isinstance(key, six.binary_type):
|
||||
pass
|
||||
# JavaScript is weakly typed for these, so it makes sense to
|
||||
# also allow them. Many encoders seem to do something like this.
|
||||
elif isinstance(key, float):
|
||||
# see comment for int/float in _make_iterencode
|
||||
key = convert2Es6Format(key)
|
||||
elif key is True:
|
||||
key = 'true'
|
||||
elif key is False:
|
||||
key = 'false'
|
||||
elif key is None:
|
||||
key = 'null'
|
||||
elif isinstance(key, int):
|
||||
# see comment for int/float in _make_iterencode
|
||||
key = convert2Es6Format(key)
|
||||
elif _skipkeys:
|
||||
continue
|
||||
else:
|
||||
raise TypeError("key " + repr(key) + " is not a string")
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
yield item_separator
|
||||
yield _encoder(key)
|
||||
yield _key_separator
|
||||
if isinstance(value, str):
|
||||
yield _encoder(value)
|
||||
elif value is None:
|
||||
yield 'null'
|
||||
elif value is True:
|
||||
yield 'true'
|
||||
elif value is False:
|
||||
yield 'false'
|
||||
elif isinstance(value, int):
|
||||
# see comment for int/float in _make_iterencode
|
||||
yield convert2Es6Format(value)
|
||||
elif isinstance(value, float):
|
||||
# see comment for int/float in _make_iterencode
|
||||
yield convert2Es6Format(value)
|
||||
else:
|
||||
if isinstance(value, (list, tuple)):
|
||||
chunks = _iterencode_list(value, _current_indent_level)
|
||||
elif isinstance(value, dict):
|
||||
chunks = _iterencode_dict(value, _current_indent_level)
|
||||
else:
|
||||
chunks = _iterencode(value, _current_indent_level)
|
||||
# Below line commented-out for python2 compatibility
|
||||
# yield from chunks
|
||||
for chunk in chunks:
|
||||
yield chunk
|
||||
if newline_indent is not None:
|
||||
_current_indent_level -= 1
|
||||
yield '\n' + _indent * _current_indent_level
|
||||
yield '}'
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
|
||||
def _iterencode(o, _current_indent_level):
|
||||
# Replaced isinstance(o, str) with below to enable simultaneous python 2 & 3 compatibility
|
||||
if isinstance(o, six.string_types) or isinstance(o, six.binary_type):
|
||||
yield _encoder(o)
|
||||
elif o is None:
|
||||
yield 'null'
|
||||
elif o is True:
|
||||
yield 'true'
|
||||
elif o is False:
|
||||
yield 'false'
|
||||
elif isinstance(o, int):
|
||||
# see comment for int/float in _make_iterencode
|
||||
yield convert2Es6Format(o)
|
||||
elif isinstance(o, float):
|
||||
# see comment for int/float in _make_iterencode
|
||||
yield convert2Es6Format(o)
|
||||
elif isinstance(o, (list, tuple)):
|
||||
# Below line commented-out for python2 compatibility
|
||||
# yield from _iterencode_list(o, _current_indent_level)
|
||||
for thing in _iterencode_list(o, _current_indent_level):
|
||||
yield thing
|
||||
elif isinstance(o, dict):
|
||||
# Below line commented-out for python2 compatibility
|
||||
# yield from _iterencode_dict(o, _current_indent_level)
|
||||
for thing in _iterencode_dict(o, _current_indent_level):
|
||||
yield thing
|
||||
else:
|
||||
if markers is not None:
|
||||
markerid = id(o)
|
||||
if markerid in markers:
|
||||
raise ValueError("Circular reference detected")
|
||||
markers[markerid] = o
|
||||
o = _default(o)
|
||||
# Below line commented-out for python2 compatibility
|
||||
# yield from _iterencode(o, _current_indent_level)
|
||||
for thing in _iterencode(o, _current_indent_level):
|
||||
yield thing
|
||||
if markers is not None:
|
||||
del markers[markerid]
|
||||
return _iterencode
|
||||
|
||||
|
||||
def canonicalize(obj, utf8=True):
|
||||
textVal = JSONEncoder(sort_keys=True).encode(obj)
|
||||
if utf8:
|
||||
return textVal.encode()
|
||||
return textVal
|
||||
|
||||
|
||||
def serialize(obj, utf8=True):
|
||||
textVal = JSONEncoder(sort_keys=False).encode(obj)
|
||||
if utf8:
|
||||
return textVal.encode()
|
||||
return textVal
|
|
@ -0,0 +1,95 @@
|
|||
##############################################################################
|
||||
# #
|
||||
# Copyright 2006-2019 WebPKI.org (http://webpki.org). #
|
||||
# #
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); #
|
||||
# you may not use this file except in compliance with the License. #
|
||||
# You may obtain a copy of the License at #
|
||||
# #
|
||||
# https://www.apache.org/licenses/LICENSE-2.0 #
|
||||
# #
|
||||
# Unless required by applicable law or agreed to in writing, software #
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, #
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
|
||||
# See the License for the specific language governing permissions and #
|
||||
# limitations under the License. #
|
||||
# #
|
||||
##############################################################################
|
||||
|
||||
|
||||
##################################################################
|
||||
# Convert a Python double/float into an ES6/V8 compatible string #
|
||||
##################################################################
|
||||
def convert2Es6Format(value):
|
||||
# Convert double/float to str using the native Python formatter
|
||||
fvalue = float(value)
|
||||
|
||||
# Zero is a special case. The following line takes "-0" case as well
|
||||
if fvalue == 0:
|
||||
return '0'
|
||||
|
||||
# The rest of the algorithm works on the textual representation only
|
||||
pyDouble = str(fvalue)
|
||||
|
||||
# The following line catches the "inf" and "nan" values returned by str(fvalue)
|
||||
if pyDouble.find('n') >= 0:
|
||||
raise ValueError("Invalid JSON number: " + pyDouble)
|
||||
|
||||
# Save sign separately, it doesn't have any role in the algorithm
|
||||
pySign = ''
|
||||
if pyDouble.find('-') == 0:
|
||||
pySign = '-'
|
||||
pyDouble = pyDouble[1:]
|
||||
|
||||
# Now we should only have valid non-zero values
|
||||
pyExpStr = ''
|
||||
pyExpVal = 0
|
||||
q = pyDouble.find('e')
|
||||
if q > 0:
|
||||
# Grab the exponent and remove it from the number
|
||||
pyExpStr = pyDouble[q:]
|
||||
if pyExpStr[2:3] == '0':
|
||||
# Supress leading zero on exponents
|
||||
pyExpStr = pyExpStr[:2] + pyExpStr[3:]
|
||||
pyDouble = pyDouble[0:q]
|
||||
pyExpVal = int(pyExpStr[1:])
|
||||
|
||||
# Split number in pyFirst + pyDot + pyLast
|
||||
pyFirst = pyDouble
|
||||
pyDot = ''
|
||||
pyLast = ''
|
||||
q = pyDouble.find('.')
|
||||
if q > 0:
|
||||
pyDot = '.'
|
||||
pyFirst = pyDouble[:q]
|
||||
pyLast = pyDouble[q + 1:]
|
||||
|
||||
# Now the string is split into: pySign + pyFirst + pyDot + pyLast + pyExpStr
|
||||
if pyLast == '0':
|
||||
# Always remove trailing .0
|
||||
pyDot = ''
|
||||
pyLast = ''
|
||||
|
||||
if pyExpVal > 0 and pyExpVal < 21:
|
||||
# Integers are shown as is with up to 21 digits
|
||||
pyFirst += pyLast
|
||||
pyLast = ''
|
||||
pyDot = ''
|
||||
pyExpStr = ''
|
||||
q = pyExpVal - len(pyFirst)
|
||||
while q >= 0:
|
||||
q -= 1
|
||||
pyFirst += '0'
|
||||
elif pyExpVal < 0 and pyExpVal > -7:
|
||||
# Small numbers are shown as 0.etc with e-6 as lower limit
|
||||
pyLast = pyFirst + pyLast
|
||||
pyFirst = '0'
|
||||
pyDot = '.'
|
||||
pyExpStr = ''
|
||||
q = pyExpVal
|
||||
while q < -1:
|
||||
q += 1
|
||||
pyLast = '0' + pyLast
|
||||
|
||||
# The resulting sub-strings are concatenated
|
||||
return pySign + pyFirst + pyDot + pyLast + pyExpStr
|
|
@ -169,19 +169,6 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
|
|||
raise CustomContentError("Can't parse unknown observable type '%s'! For custom observables, "
|
||||
"use the CustomObservable decorator." % obj['type'])
|
||||
|
||||
EXT_MAP = STIX2_OBJ_MAPS[v]['observable-extensions']
|
||||
|
||||
if 'extensions' in obj and obj['type'] in EXT_MAP:
|
||||
for name, ext in obj['extensions'].items():
|
||||
try:
|
||||
ext_class = EXT_MAP[obj['type']][name]
|
||||
except KeyError:
|
||||
if not allow_custom:
|
||||
raise CustomContentError("Can't parse unknown extension type '%s'"
|
||||
"for observable type '%s'!" % (name, obj['type']))
|
||||
else: # extension was found
|
||||
obj['extensions'][name] = ext_class(allow_custom=allow_custom, **obj['extensions'][name])
|
||||
|
||||
return obj_class(allow_custom=allow_custom, **obj)
|
||||
|
||||
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
"""Python STIX2 Environment API."""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import time
|
||||
|
||||
from .core import parse as _parse
|
||||
from .datastore import CompositeDataSource, DataStoreMixin
|
||||
from .utils import STIXdatetime, parse_into_datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ObjectFactory(object):
|
||||
|
@ -186,3 +191,501 @@ class Environment(DataStoreMixin):
|
|||
return self.get(creator_id)
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def semantically_equivalent(obj1, obj2, **weight_dict):
|
||||
"""This method is meant to verify if two objects of the same type are
|
||||
semantically equivalent.
|
||||
|
||||
Args:
|
||||
obj1: A stix2 object instance
|
||||
obj2: A stix2 object instance
|
||||
weight_dict: A dictionary that can be used to override settings
|
||||
in the semantic equivalence process
|
||||
|
||||
Returns:
|
||||
float: A number between 0.0 and 100.0 as a measurement of equivalence.
|
||||
|
||||
Warning:
|
||||
Course of Action, Intrusion-Set, Observed-Data, Report are not supported
|
||||
by this implementation. Indicator pattern check is also limited.
|
||||
|
||||
Note:
|
||||
This implementation follows the Committee Note on semantic equivalence.
|
||||
see `the Committee Note <link here>`__.
|
||||
|
||||
"""
|
||||
# default weights used for the semantic equivalence process
|
||||
weights = {
|
||||
"attack-pattern": {
|
||||
"name": 30,
|
||||
"external_references": 70,
|
||||
"method": _attack_pattern_checks,
|
||||
},
|
||||
"campaign": {
|
||||
"name": 60,
|
||||
"aliases": 40,
|
||||
"method": _campaign_checks,
|
||||
},
|
||||
"identity": {
|
||||
"name": 60,
|
||||
"identity_class": 20,
|
||||
"sectors": 20,
|
||||
"method": _identity_checks,
|
||||
},
|
||||
"indicator": {
|
||||
"indicator_types": 15,
|
||||
"pattern": 80,
|
||||
"valid_from": 5,
|
||||
"tdelta": 1, # One day interval
|
||||
"method": _indicator_checks,
|
||||
},
|
||||
"location": {
|
||||
"longitude_latitude": 34,
|
||||
"region": 33,
|
||||
"country": 33,
|
||||
"threshold": 1000.0,
|
||||
"method": _location_checks,
|
||||
},
|
||||
"malware": {
|
||||
"malware_types": 20,
|
||||
"name": 80,
|
||||
"method": _malware_checks,
|
||||
},
|
||||
"threat-actor": {
|
||||
"name": 60,
|
||||
"threat_actor_types": 20,
|
||||
"aliases": 20,
|
||||
"method": _threat_actor_checks,
|
||||
},
|
||||
"tool": {
|
||||
"tool_types": 20,
|
||||
"name": 80,
|
||||
"method": _tool_checks,
|
||||
},
|
||||
"vulnerability": {
|
||||
"name": 30,
|
||||
"external_references": 70,
|
||||
"method": _vulnerability_checks,
|
||||
},
|
||||
"_internal": {
|
||||
"ignore_spec_version": False,
|
||||
},
|
||||
}
|
||||
|
||||
if weight_dict:
|
||||
weights.update(weight_dict)
|
||||
|
||||
type1, type2 = obj1["type"], obj2["type"]
|
||||
ignore_spec_version = weights["_internal"]["ignore_spec_version"]
|
||||
|
||||
if type1 != type2:
|
||||
raise ValueError('The objects to compare must be of the same type!')
|
||||
|
||||
if ignore_spec_version is False and obj1.get("spec_version", "2.0") != obj2.get("spec_version", "2.0"):
|
||||
raise ValueError('The objects to compare must be of the same spec version!')
|
||||
|
||||
try:
|
||||
method = weights[type1]["method"]
|
||||
except KeyError:
|
||||
logger.warning("'%s' type has no semantic equivalence method to call!", type1)
|
||||
sum_weights = matching_score = 0
|
||||
else:
|
||||
logger.debug("Starting semantic equivalence process between: '%s' and '%s'", obj1["id"], obj2["id"])
|
||||
matching_score, sum_weights = method(obj1, obj2, **weights[type1])
|
||||
|
||||
if sum_weights <= 0:
|
||||
return 0
|
||||
|
||||
equivalence_score = (matching_score / sum_weights) * 100.0
|
||||
return equivalence_score
|
||||
|
||||
|
||||
def check_property_present(prop, obj1, obj2):
|
||||
"""Helper method checks if a property is present on both objects."""
|
||||
if prop in obj1 and prop in obj2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def partial_timestamp_based(t1, t2, tdelta):
|
||||
"""Performs a timestamp-based matching via checking how close one timestamp is to another.
|
||||
|
||||
Args:
|
||||
t1: A datetime string or STIXdatetime object.
|
||||
t2: A datetime string or STIXdatetime object.
|
||||
tdelta (float): A given time delta. This number is multiplied by 86400 (1 day) to
|
||||
extend or shrink your time change tolerance.
|
||||
|
||||
Returns:
|
||||
float: Number between 0.0 and 1.0 depending on match criteria.
|
||||
|
||||
"""
|
||||
if not isinstance(t1, STIXdatetime):
|
||||
t1 = parse_into_datetime(t1)
|
||||
if not isinstance(t2, STIXdatetime):
|
||||
t2 = parse_into_datetime(t2)
|
||||
t1, t2 = time.mktime(t1.timetuple()), time.mktime(t2.timetuple())
|
||||
result = 1 - min(abs(t1 - t2) / (86400 * tdelta), 1)
|
||||
logger.debug("--\t\tpartial_timestamp_based '%s' '%s' tdelta: '%s'\tresult: '%s'", t1, t2, tdelta, result)
|
||||
return result
|
||||
|
||||
|
||||
def partial_list_based(l1, l2):
|
||||
"""Performs a partial list matching via finding the intersection between common values.
|
||||
|
||||
Args:
|
||||
l1: A list of values.
|
||||
l2: A list of values.
|
||||
|
||||
Returns:
|
||||
float: 1.0 if the value matches exactly, 0.0 otherwise.
|
||||
|
||||
"""
|
||||
l1_set, l2_set = set(l1), set(l2)
|
||||
result = len(l1_set.intersection(l2_set)) / max(len(l1), len(l2))
|
||||
logger.debug("--\t\tpartial_list_based '%s' '%s'\tresult: '%s'", l1, l2, result)
|
||||
return result
|
||||
|
||||
|
||||
def exact_match(val1, val2):
|
||||
"""Performs an exact value match based on two values
|
||||
|
||||
Args:
|
||||
val1: A value suitable for an equality test.
|
||||
val2: A value suitable for an equality test.
|
||||
|
||||
Returns:
|
||||
float: 1.0 if the value matches exactly, 0.0 otherwise.
|
||||
|
||||
"""
|
||||
result = 0.0
|
||||
if val1 == val2:
|
||||
result = 1.0
|
||||
logger.debug("--\t\texact_match '%s' '%s'\tresult: '%s'", val1, val2, result)
|
||||
return result
|
||||
|
||||
|
||||
def partial_string_based(str1, str2):
|
||||
"""Performs a partial string match using the Jaro-Winkler distance algorithm.
|
||||
|
||||
Args:
|
||||
str1: A string value to check.
|
||||
str2: A string value to check.
|
||||
|
||||
Returns:
|
||||
float: Number between 0.0 and 1.0 depending on match criteria.
|
||||
|
||||
"""
|
||||
from pyjarowinkler import distance
|
||||
result = distance.get_jaro_distance(str1, str2)
|
||||
logger.debug("--\t\tpartial_string_based '%s' '%s'\tresult: '%s'", str1, str2, result)
|
||||
return result
|
||||
|
||||
|
||||
def custom_pattern_based(pattern1, pattern2):
|
||||
"""Performs a matching on Indicator Patterns.
|
||||
|
||||
Args:
|
||||
pattern1: An Indicator pattern
|
||||
pattern2: An Indicator pattern
|
||||
|
||||
Returns:
|
||||
float: Number between 0.0 and 1.0 depending on match criteria.
|
||||
|
||||
"""
|
||||
logger.warning("Indicator pattern equivalence is not fully defined; will default to zero if not completely identical")
|
||||
return exact_match(pattern1, pattern2) # TODO: Implement pattern based equivalence
|
||||
|
||||
|
||||
def partial_external_reference_based(refs1, refs2):
|
||||
"""Performs a matching on External References.
|
||||
|
||||
Args:
|
||||
refs1: A list of external references.
|
||||
refs2: A list of external references.
|
||||
|
||||
Returns:
|
||||
float: Number between 0.0 and 1.0 depending on matches.
|
||||
|
||||
"""
|
||||
allowed = set(("veris", "cve", "capec", "mitre-attack"))
|
||||
matches = 0
|
||||
|
||||
if len(refs1) >= len(refs2):
|
||||
l1 = refs1
|
||||
l2 = refs2
|
||||
else:
|
||||
l1 = refs2
|
||||
l2 = refs1
|
||||
|
||||
for ext_ref1 in l1:
|
||||
for ext_ref2 in l2:
|
||||
sn_match = False
|
||||
ei_match = False
|
||||
url_match = False
|
||||
source_name = None
|
||||
|
||||
if check_property_present("source_name", ext_ref1, ext_ref2):
|
||||
if ext_ref1["source_name"] == ext_ref2["source_name"]:
|
||||
source_name = ext_ref1["source_name"]
|
||||
sn_match = True
|
||||
if check_property_present("external_id", ext_ref1, ext_ref2):
|
||||
if ext_ref1["external_id"] == ext_ref2["external_id"]:
|
||||
ei_match = True
|
||||
if check_property_present("url", ext_ref1, ext_ref2):
|
||||
if ext_ref1["url"] == ext_ref2["url"]:
|
||||
url_match = True
|
||||
|
||||
# Special case: if source_name is a STIX defined name and either
|
||||
# external_id or url match then its a perfect match and other entries
|
||||
# can be ignored.
|
||||
if sn_match and (ei_match or url_match) and source_name in allowed:
|
||||
result = 1.0
|
||||
logger.debug(
|
||||
"--\t\tpartial_external_reference_based '%s' '%s'\tresult: '%s'",
|
||||
refs1, refs2, result,
|
||||
)
|
||||
return result
|
||||
|
||||
# Regular check. If the source_name (not STIX-defined) or external_id or
|
||||
# url matches then we consider the entry a match.
|
||||
if (sn_match or ei_match or url_match) and source_name not in allowed:
|
||||
matches += 1
|
||||
|
||||
result = matches / max(len(refs1), len(refs2))
|
||||
logger.debug(
|
||||
"--\t\tpartial_external_reference_based '%s' '%s'\tresult: '%s'",
|
||||
refs1, refs2, result,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def partial_location_distance(lat1, long1, lat2, long2, threshold):
|
||||
"""Given two coordinates perform a matching based on its distance using the Haversine Formula.
|
||||
|
||||
Args:
|
||||
lat1: Latitude value for first coordinate point.
|
||||
lat2: Latitude value for second coordinate point.
|
||||
long1: Longitude value for first coordinate point.
|
||||
long2: Longitude value for second coordinate point.
|
||||
threshold (float): A kilometer measurement for the threshold distance between these two points.
|
||||
|
||||
Returns:
|
||||
float: Number between 0.0 and 1.0 depending on match.
|
||||
|
||||
"""
|
||||
from haversine import haversine, Unit
|
||||
distance = haversine((lat1, long1), (lat2, long2), unit=Unit.KILOMETERS)
|
||||
result = 1 - (distance / threshold)
|
||||
logger.debug(
|
||||
"--\t\tpartial_location_distance '%s' '%s' threshold: '%s'\tresult: '%s'",
|
||||
(lat1, long1), (lat2, long2), threshold, result,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _attack_pattern_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("external_references", obj1, obj2):
|
||||
w = weights["external_references"]
|
||||
contributing_score = (
|
||||
w * partial_external_reference_based(obj1["external_references"], obj2["external_references"])
|
||||
)
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'external_references' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _campaign_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("aliases", obj1, obj2):
|
||||
w = weights["aliases"]
|
||||
contributing_score = w * partial_list_based(obj1["aliases"], obj2["aliases"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'aliases' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _identity_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * exact_match(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("identity_class", obj1, obj2):
|
||||
w = weights["identity_class"]
|
||||
contributing_score = w * exact_match(obj1["identity_class"], obj2["identity_class"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'identity_class' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("sectors", obj1, obj2):
|
||||
w = weights["sectors"]
|
||||
contributing_score = w * partial_list_based(obj1["sectors"], obj2["sectors"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'sectors' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _indicator_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("indicator_types", obj1, obj2):
|
||||
w = weights["indicator_types"]
|
||||
contributing_score = w * partial_list_based(obj1["indicator_types"], obj2["indicator_types"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'indicator_types' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("pattern", obj1, obj2):
|
||||
w = weights["pattern"]
|
||||
contributing_score = w * custom_pattern_based(obj1["pattern"], obj2["pattern"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'pattern' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("valid_from", obj1, obj2):
|
||||
w = weights["valid_from"]
|
||||
contributing_score = (
|
||||
w *
|
||||
partial_timestamp_based(obj1["valid_from"], obj2["valid_from"], weights["tdelta"])
|
||||
)
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'valid_from' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _location_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("latitude", obj1, obj2) and check_property_present("longitude", obj1, obj2):
|
||||
w = weights["longitude_latitude"]
|
||||
contributing_score = (
|
||||
w *
|
||||
partial_location_distance(obj1["latitude"], obj1["longitude"], obj2["latitude"], obj2["longitude"], weights["threshold"])
|
||||
)
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'longitude_latitude' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("region", obj1, obj2):
|
||||
w = weights["region"]
|
||||
contributing_score = w * exact_match(obj1["region"], obj2["region"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'region' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("country", obj1, obj2):
|
||||
w = weights["country"]
|
||||
contributing_score = w * exact_match(obj1["country"], obj2["country"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'country' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _malware_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("malware_types", obj1, obj2):
|
||||
w = weights["malware_types"]
|
||||
contributing_score = w * partial_list_based(obj1["malware_types"], obj2["malware_types"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'malware_types' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _threat_actor_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("threat_actor_types", obj1, obj2):
|
||||
w = weights["threat_actor_types"]
|
||||
contributing_score = w * partial_list_based(obj1["threat_actor_types"], obj2["threat_actor_types"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'threat_actor_types' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("aliases", obj1, obj2):
|
||||
w = weights["aliases"]
|
||||
contributing_score = w * partial_list_based(obj1["aliases"], obj2["aliases"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'aliases' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _tool_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("tool_types", obj1, obj2):
|
||||
w = weights["tool_types"]
|
||||
contributing_score = w * partial_list_based(obj1["tool_types"], obj2["tool_types"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'tool_types' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
||||
|
||||
def _vulnerability_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
contributing_score = w * partial_string_based(obj1["name"], obj2["name"])
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'name' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
if check_property_present("external_references", obj1, obj2):
|
||||
w = weights["external_references"]
|
||||
contributing_score = w * partial_external_reference_based(
|
||||
obj1["external_references"],
|
||||
obj2["external_references"],
|
||||
)
|
||||
sum_weights += w
|
||||
matching_score += contributing_score
|
||||
logger.debug("'external_references' check -- weight: %s, contributing score: %s", w, contributing_score)
|
||||
logger.debug("Matching Score: %s, Sum of Weights: %s", matching_score, sum_weights)
|
||||
return matching_score, sum_weights
|
||||
|
|
|
@ -5,7 +5,15 @@ class STIXError(Exception):
|
|||
"""Base class for errors generated in the stix2 library."""
|
||||
|
||||
|
||||
class InvalidValueError(STIXError, ValueError):
|
||||
class ObjectConfigurationError(STIXError):
|
||||
"""
|
||||
Represents specification violations regarding the composition of STIX
|
||||
objects.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidValueError(ObjectConfigurationError):
|
||||
"""An invalid value was provided to a STIX object's ``__init__``."""
|
||||
|
||||
def __init__(self, cls, prop_name, reason):
|
||||
|
@ -19,52 +27,89 @@ class InvalidValueError(STIXError, ValueError):
|
|||
return msg.format(self)
|
||||
|
||||
|
||||
class MissingPropertiesError(STIXError, ValueError):
|
||||
class PropertyPresenceError(ObjectConfigurationError):
|
||||
"""
|
||||
Represents an invalid combination of properties on a STIX object. This
|
||||
class can be used directly when the object requirements are more
|
||||
complicated and none of the more specific exception subclasses apply.
|
||||
"""
|
||||
def __init__(self, message, cls):
|
||||
super(PropertyPresenceError, self).__init__(message)
|
||||
self.cls = cls
|
||||
|
||||
|
||||
class MissingPropertiesError(PropertyPresenceError):
|
||||
"""Missing one or more required properties when constructing STIX object."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
super(MissingPropertiesError, self).__init__()
|
||||
self.cls = cls
|
||||
self.properties = sorted(list(properties))
|
||||
self.properties = sorted(properties)
|
||||
|
||||
def __str__(self):
|
||||
msg = "No values for required properties for {0}: ({1})."
|
||||
return msg.format(
|
||||
self.cls.__name__,
|
||||
msg = "No values for required properties for {0}: ({1}).".format(
|
||||
cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
super(MissingPropertiesError, self).__init__(msg, cls)
|
||||
|
||||
class ExtraPropertiesError(STIXError, TypeError):
|
||||
|
||||
class ExtraPropertiesError(PropertyPresenceError):
|
||||
"""One or more extra properties were provided when constructing STIX object."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
super(ExtraPropertiesError, self).__init__()
|
||||
self.cls = cls
|
||||
self.properties = sorted(list(properties))
|
||||
self.properties = sorted(properties)
|
||||
|
||||
def __str__(self):
|
||||
msg = "Unexpected properties for {0}: ({1})."
|
||||
return msg.format(
|
||||
self.cls.__name__,
|
||||
msg = "Unexpected properties for {0}: ({1}).".format(
|
||||
cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
|
||||
class ImmutableError(STIXError, ValueError):
|
||||
"""Attempted to modify an object after creation."""
|
||||
|
||||
def __init__(self, cls, key):
|
||||
super(ImmutableError, self).__init__()
|
||||
self.cls = cls
|
||||
self.key = key
|
||||
|
||||
def __str__(self):
|
||||
msg = "Cannot modify '{0.key}' property in '{0.cls.__name__}' after creation."
|
||||
return msg.format(self)
|
||||
super(ExtraPropertiesError, self).__init__(msg, cls)
|
||||
|
||||
|
||||
class DictionaryKeyError(STIXError, ValueError):
|
||||
class MutuallyExclusivePropertiesError(PropertyPresenceError):
|
||||
"""Violating interproperty mutually exclusive constraint of a STIX object type."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
self.properties = sorted(properties)
|
||||
|
||||
msg = "The ({1}) properties for {0} are mutually exclusive.".format(
|
||||
cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
super(MutuallyExclusivePropertiesError, self).__init__(msg, cls)
|
||||
|
||||
|
||||
class DependentPropertiesError(PropertyPresenceError):
|
||||
"""Violating interproperty dependency constraint of a STIX object type."""
|
||||
|
||||
def __init__(self, cls, dependencies):
|
||||
self.dependencies = dependencies
|
||||
|
||||
msg = "The property dependencies for {0}: ({1}) are not met.".format(
|
||||
cls.__name__,
|
||||
", ".join(name for x in self.dependencies for name in x),
|
||||
)
|
||||
|
||||
super(DependentPropertiesError, self).__init__(msg, cls)
|
||||
|
||||
|
||||
class AtLeastOnePropertyError(PropertyPresenceError):
|
||||
"""Violating a constraint of a STIX object type that at least one of the given properties must be populated."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
self.properties = sorted(properties)
|
||||
|
||||
msg = "At least one of the ({1}) properties for {0} must be " \
|
||||
"populated.".format(
|
||||
cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
super(AtLeastOnePropertyError, self).__init__(msg, cls)
|
||||
|
||||
|
||||
class DictionaryKeyError(ObjectConfigurationError):
|
||||
"""Dictionary key does not conform to the correct format."""
|
||||
|
||||
def __init__(self, key, reason):
|
||||
|
@ -77,7 +122,7 @@ class DictionaryKeyError(STIXError, ValueError):
|
|||
return msg.format(self)
|
||||
|
||||
|
||||
class InvalidObjRefError(STIXError, ValueError):
|
||||
class InvalidObjRefError(ObjectConfigurationError):
|
||||
"""A STIX Cyber Observable Object contains an invalid object reference."""
|
||||
|
||||
def __init__(self, cls, prop_name, reason):
|
||||
|
@ -91,95 +136,7 @@ class InvalidObjRefError(STIXError, ValueError):
|
|||
return msg.format(self)
|
||||
|
||||
|
||||
class UnmodifiablePropertyError(STIXError, ValueError):
|
||||
"""Attempted to modify an unmodifiable property of object when creating a new version."""
|
||||
|
||||
def __init__(self, unchangable_properties):
|
||||
super(UnmodifiablePropertyError, self).__init__()
|
||||
self.unchangable_properties = unchangable_properties
|
||||
|
||||
def __str__(self):
|
||||
msg = "These properties cannot be changed when making a new version: {0}."
|
||||
return msg.format(", ".join(self.unchangable_properties))
|
||||
|
||||
|
||||
class MutuallyExclusivePropertiesError(STIXError, TypeError):
|
||||
"""Violating interproperty mutually exclusive constraint of a STIX object type."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
super(MutuallyExclusivePropertiesError, self).__init__()
|
||||
self.cls = cls
|
||||
self.properties = sorted(list(properties))
|
||||
|
||||
def __str__(self):
|
||||
msg = "The ({1}) properties for {0} are mutually exclusive."
|
||||
return msg.format(
|
||||
self.cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
|
||||
class DependentPropertiesError(STIXError, TypeError):
|
||||
"""Violating interproperty dependency constraint of a STIX object type."""
|
||||
|
||||
def __init__(self, cls, dependencies):
|
||||
super(DependentPropertiesError, self).__init__()
|
||||
self.cls = cls
|
||||
self.dependencies = dependencies
|
||||
|
||||
def __str__(self):
|
||||
msg = "The property dependencies for {0}: ({1}) are not met."
|
||||
return msg.format(
|
||||
self.cls.__name__,
|
||||
", ".join(name for x in self.dependencies for name in x),
|
||||
)
|
||||
|
||||
|
||||
class AtLeastOnePropertyError(STIXError, TypeError):
|
||||
"""Violating a constraint of a STIX object type that at least one of the given properties must be populated."""
|
||||
|
||||
def __init__(self, cls, properties):
|
||||
super(AtLeastOnePropertyError, self).__init__()
|
||||
self.cls = cls
|
||||
self.properties = sorted(list(properties))
|
||||
|
||||
def __str__(self):
|
||||
msg = "At least one of the ({1}) properties for {0} must be populated."
|
||||
return msg.format(
|
||||
self.cls.__name__,
|
||||
", ".join(x for x in self.properties),
|
||||
)
|
||||
|
||||
|
||||
class RevokeError(STIXError, ValueError):
|
||||
"""Attempted to an operation on a revoked object."""
|
||||
|
||||
def __init__(self, called_by):
|
||||
super(RevokeError, self).__init__()
|
||||
self.called_by = called_by
|
||||
|
||||
def __str__(self):
|
||||
if self.called_by == "revoke":
|
||||
return "Cannot revoke an already revoked object."
|
||||
else:
|
||||
return "Cannot create a new version of a revoked object."
|
||||
|
||||
|
||||
class ParseError(STIXError, ValueError):
|
||||
"""Could not parse object."""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(ParseError, self).__init__(msg)
|
||||
|
||||
|
||||
class CustomContentError(STIXError, ValueError):
|
||||
"""Custom STIX Content (SDO, Observable, Extension, etc.) detected."""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(CustomContentError, self).__init__(msg)
|
||||
|
||||
|
||||
class InvalidSelectorError(STIXError, AssertionError):
|
||||
class InvalidSelectorError(ObjectConfigurationError):
|
||||
"""Granular Marking selector violation. The selector must resolve into an existing STIX object property."""
|
||||
|
||||
def __init__(self, cls, key):
|
||||
|
@ -192,7 +149,73 @@ class InvalidSelectorError(STIXError, AssertionError):
|
|||
return msg.format(self.key, self.cls.__class__.__name__)
|
||||
|
||||
|
||||
class MarkingNotFoundError(STIXError, AssertionError):
|
||||
class TLPMarkingDefinitionError(ObjectConfigurationError):
|
||||
"""Marking violation. The marking-definition for TLP MUST follow the mandated instances from the spec."""
|
||||
|
||||
def __init__(self, user_obj, spec_obj):
|
||||
super(TLPMarkingDefinitionError, self).__init__()
|
||||
self.user_obj = user_obj
|
||||
self.spec_obj = spec_obj
|
||||
|
||||
def __str__(self):
|
||||
msg = "Marking {0} does not match spec marking {1}!"
|
||||
return msg.format(self.user_obj, self.spec_obj)
|
||||
|
||||
|
||||
class ImmutableError(STIXError):
|
||||
"""Attempted to modify an object after creation."""
|
||||
|
||||
def __init__(self, cls, key):
|
||||
super(ImmutableError, self).__init__()
|
||||
self.cls = cls
|
||||
self.key = key
|
||||
|
||||
def __str__(self):
|
||||
msg = "Cannot modify '{0.key}' property in '{0.cls.__name__}' after creation."
|
||||
return msg.format(self)
|
||||
|
||||
|
||||
class UnmodifiablePropertyError(STIXError):
|
||||
"""Attempted to modify an unmodifiable property of object when creating a new version."""
|
||||
|
||||
def __init__(self, unchangable_properties):
|
||||
super(UnmodifiablePropertyError, self).__init__()
|
||||
self.unchangable_properties = unchangable_properties
|
||||
|
||||
def __str__(self):
|
||||
msg = "These properties cannot be changed when making a new version: {0}."
|
||||
return msg.format(", ".join(self.unchangable_properties))
|
||||
|
||||
|
||||
class RevokeError(STIXError):
|
||||
"""Attempted an operation on a revoked object."""
|
||||
|
||||
def __init__(self, called_by):
|
||||
super(RevokeError, self).__init__()
|
||||
self.called_by = called_by
|
||||
|
||||
def __str__(self):
|
||||
if self.called_by == "revoke":
|
||||
return "Cannot revoke an already revoked object."
|
||||
else:
|
||||
return "Cannot create a new version of a revoked object."
|
||||
|
||||
|
||||
class ParseError(STIXError):
|
||||
"""Could not parse object."""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(ParseError, self).__init__(msg)
|
||||
|
||||
|
||||
class CustomContentError(STIXError):
|
||||
"""Custom STIX Content (SDO, Observable, Extension, etc.) detected."""
|
||||
|
||||
def __init__(self, msg):
|
||||
super(CustomContentError, self).__init__(msg)
|
||||
|
||||
|
||||
class MarkingNotFoundError(STIXError):
|
||||
"""Marking violation. The marking reference must be present in SDO or SRO."""
|
||||
|
||||
def __init__(self, cls, key):
|
||||
|
@ -205,14 +228,8 @@ class MarkingNotFoundError(STIXError, AssertionError):
|
|||
return msg.format(self.key, self.cls.__class__.__name__)
|
||||
|
||||
|
||||
class TLPMarkingDefinitionError(STIXError, AssertionError):
|
||||
"""Marking violation. The marking-definition for TLP MUST follow the mandated instances from the spec."""
|
||||
|
||||
def __init__(self, user_obj, spec_obj):
|
||||
super(TLPMarkingDefinitionError, self).__init__()
|
||||
self.user_obj = user_obj
|
||||
self.spec_obj = spec_obj
|
||||
|
||||
def __str__(self):
|
||||
msg = "Marking {0} does not match spec marking {1}!"
|
||||
return msg.format(self.user_obj, self.spec_obj)
|
||||
class STIXDeprecationWarning(DeprecationWarning):
|
||||
"""
|
||||
Represents usage of a deprecated component of a STIX specification.
|
||||
"""
|
||||
pass
|
||||
|
|
|
@ -11,27 +11,71 @@ import uuid
|
|||
from six import string_types, text_type
|
||||
from stix2patterns.validator import run_validator
|
||||
|
||||
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
|
||||
|
||||
# This uses the regular expression for a RFC 4122, Version 4 UUID. In the
|
||||
# 8-4-4-4-12 hexadecimal representation, the first hex digit of the third
|
||||
# component must be a 4, and the first hex digit of the fourth component
|
||||
# must be 8, 9, a, or b (10xx bit pattern).
|
||||
ID_REGEX = re.compile(
|
||||
r"^[a-z0-9][a-z0-9-]+[a-z0-9]--" # object type
|
||||
"[0-9a-fA-F]{8}-"
|
||||
"[0-9a-fA-F]{4}-"
|
||||
"4[0-9a-fA-F]{3}-"
|
||||
"[89abAB][0-9a-fA-F]{3}-"
|
||||
"[0-9a-fA-F]{12}$",
|
||||
ERROR_INVALID_ID = (
|
||||
"not a valid STIX identifier, must match <object-type>--<UUID>: {}"
|
||||
)
|
||||
|
||||
ERROR_INVALID_ID = (
|
||||
"not a valid STIX identifier, must match <object-type>--<UUIDv4>"
|
||||
)
|
||||
|
||||
def _check_uuid(uuid_str, spec_version):
|
||||
"""
|
||||
Check whether the given UUID string is valid with respect to the given STIX
|
||||
spec version. STIX 2.0 requires UUIDv4; 2.1 only requires the RFC 4122
|
||||
variant.
|
||||
|
||||
:param uuid_str: A UUID as a string
|
||||
:param spec_version: The STIX spec version
|
||||
:return: True if the UUID is valid, False if not
|
||||
:raises ValueError: If uuid_str is malformed
|
||||
"""
|
||||
uuid_obj = uuid.UUID(uuid_str)
|
||||
|
||||
ok = uuid_obj.variant == uuid.RFC_4122
|
||||
if ok and spec_version == "2.0":
|
||||
ok = uuid_obj.version == 4
|
||||
|
||||
return ok
|
||||
|
||||
|
||||
def _validate_id(id_, spec_version, required_prefix):
|
||||
"""
|
||||
Check the STIX identifier for correctness, raise an exception if there are
|
||||
errors.
|
||||
|
||||
:param id_: The STIX identifier
|
||||
:param spec_version: The STIX specification version to use
|
||||
:param required_prefix: The required prefix on the identifier, if any.
|
||||
This function doesn't add a "--" suffix to the prefix, so callers must
|
||||
add it if it is important. Pass None to skip the prefix check.
|
||||
:raises ValueError: If there are any errors with the identifier
|
||||
"""
|
||||
if required_prefix:
|
||||
if not id_.startswith(required_prefix):
|
||||
raise ValueError("must start with '{}'.".format(required_prefix))
|
||||
|
||||
try:
|
||||
if required_prefix:
|
||||
uuid_part = id_[len(required_prefix):]
|
||||
else:
|
||||
idx = id_.index("--")
|
||||
uuid_part = id_[idx+2:]
|
||||
|
||||
result = _check_uuid(uuid_part, spec_version)
|
||||
except ValueError:
|
||||
# replace their ValueError with ours
|
||||
raise ValueError(ERROR_INVALID_ID.format(id_))
|
||||
|
||||
if not result:
|
||||
raise ValueError(ERROR_INVALID_ID.format(id_))
|
||||
|
||||
|
||||
class Property(object):
|
||||
|
@ -61,7 +105,7 @@ class Property(object):
|
|||
- Return a value that is valid for this property. If ``value`` is not
|
||||
valid for this property, this will attempt to transform it first. If
|
||||
``value`` is not valid and no such transformation is possible, it
|
||||
should raise a ValueError.
|
||||
should raise an exception.
|
||||
- ``def default(self):``
|
||||
- provide a default value for this property.
|
||||
- ``default()`` can return the special value ``NOW`` to use the current
|
||||
|
@ -199,15 +243,13 @@ class TypeProperty(Property):
|
|||
|
||||
class IDProperty(Property):
|
||||
|
||||
def __init__(self, type):
|
||||
def __init__(self, type, spec_version=stix2.DEFAULT_VERSION):
|
||||
self.required_prefix = type + "--"
|
||||
self.spec_version = spec_version
|
||||
super(IDProperty, self).__init__()
|
||||
|
||||
def clean(self, value):
|
||||
if not value.startswith(self.required_prefix):
|
||||
raise ValueError("must start with '{}'.".format(self.required_prefix))
|
||||
if not ID_REGEX.match(value):
|
||||
raise ValueError(ERROR_INVALID_ID)
|
||||
_validate_id(value, self.spec_version, self.required_prefix)
|
||||
return value
|
||||
|
||||
def default(self):
|
||||
|
@ -296,7 +338,7 @@ class TimestampProperty(Property):
|
|||
|
||||
class DictionaryProperty(Property):
|
||||
|
||||
def __init__(self, spec_version='2.0', **kwargs):
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, **kwargs):
|
||||
self.spec_version = spec_version
|
||||
super(DictionaryProperty, self).__init__(**kwargs)
|
||||
|
||||
|
@ -378,22 +420,57 @@ class HexProperty(Property):
|
|||
|
||||
class ReferenceProperty(Property):
|
||||
|
||||
def __init__(self, type=None, **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.type = 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)
|
||||
|
||||
def clean(self, value):
|
||||
if isinstance(value, _STIXBase):
|
||||
value = value.id
|
||||
value = str(value)
|
||||
if self.type:
|
||||
if not value.startswith(self.type):
|
||||
raise ValueError("must start with '{}'.".format(self.type))
|
||||
if not ID_REGEX.match(value):
|
||||
raise ValueError(ERROR_INVALID_ID)
|
||||
|
||||
possible_prefix = value[:value.index('--') + 2]
|
||||
|
||||
if self.valid_types:
|
||||
if self.valid_types == ["only_SDO"]:
|
||||
self.valid_types = STIX2_OBJ_MAPS['v21']['objects'].keys()
|
||||
elif self.valid_types == ["only_SCO"]:
|
||||
self.valid_types = STIX2_OBJ_MAPS['v21']['observables'].keys()
|
||||
elif self.valid_types == ["only_SCO_&_SRO"]:
|
||||
self.valid_types = list(STIX2_OBJ_MAPS['v21']['observables'].keys()) + ['relationship', 'sighting']
|
||||
|
||||
if possible_prefix[:-2] in self.valid_types:
|
||||
required_prefix = possible_prefix
|
||||
else:
|
||||
raise ValueError("The type-specifying prefix '%s' for this property is not valid" % (possible_prefix))
|
||||
elif self.invalid_types:
|
||||
if possible_prefix[:-2] not in self.invalid_types:
|
||||
required_prefix = possible_prefix
|
||||
else:
|
||||
raise ValueError("An invalid type-specifying prefix '%s' was specified for this property" % (possible_prefix, value))
|
||||
|
||||
_validate_id(value, self.spec_version, required_prefix)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
|
@ -462,7 +539,7 @@ class ObservableProperty(Property):
|
|||
"""Property for holding Cyber Observable Objects.
|
||||
"""
|
||||
|
||||
def __init__(self, spec_version='2.0', allow_custom=False, *args, **kwargs):
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, *args, **kwargs):
|
||||
self.allow_custom = allow_custom
|
||||
self.spec_version = spec_version
|
||||
super(ObservableProperty, self).__init__(*args, **kwargs)
|
||||
|
@ -497,7 +574,7 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
"""Property for representing extensions on Observable objects.
|
||||
"""
|
||||
|
||||
def __init__(self, spec_version='2.0', allow_custom=False, enclosing_type=None, required=False):
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, enclosing_type=None, required=False):
|
||||
self.allow_custom = allow_custom
|
||||
self.enclosing_type = enclosing_type
|
||||
super(ExtensionsProperty, self).__init__(spec_version=spec_version, required=required)
|
||||
|
@ -530,13 +607,16 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
else:
|
||||
raise ValueError("Cannot determine extension type.")
|
||||
else:
|
||||
raise CustomContentError("Can't parse unknown extension type: {}".format(key))
|
||||
if self.allow_custom:
|
||||
dictified[key] = subvalue
|
||||
else:
|
||||
raise CustomContentError("Can't parse unknown extension type: {}".format(key))
|
||||
return dictified
|
||||
|
||||
|
||||
class STIXObjectProperty(Property):
|
||||
|
||||
def __init__(self, spec_version='2.0', allow_custom=False, *args, **kwargs):
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, *args, **kwargs):
|
||||
self.allow_custom = allow_custom
|
||||
self.spec_version = spec_version
|
||||
super(STIXObjectProperty, self).__init__(*args, **kwargs)
|
||||
|
|
|
@ -1,37 +1,43 @@
|
|||
import importlib
|
||||
import os
|
||||
|
||||
import stix2
|
||||
from stix2.workbench import (
|
||||
AttackPattern, Campaign, CourseOfAction, ExternalReference,
|
||||
FileSystemSource, Filter, Identity, Indicator, IntrusionSet, Malware,
|
||||
MarkingDefinition, ObservedData, Relationship, Report, StatementMarking,
|
||||
ThreatActor, Tool, Vulnerability, add_data_source, all_versions,
|
||||
attack_patterns, campaigns, courses_of_action, create, get, identities,
|
||||
indicators, intrusion_sets, malware, observed_data, query, reports, save,
|
||||
set_default_created, set_default_creator, set_default_external_refs,
|
||||
_STIX_VID, AttackPattern, Bundle, Campaign, CourseOfAction,
|
||||
ExternalReference, File, FileSystemSource, Filter, Identity, Indicator,
|
||||
IntrusionSet, Malware, MarkingDefinition, NTFSExt, ObservedData,
|
||||
Relationship, Report, StatementMarking, ThreatActor, Tool, Vulnerability,
|
||||
add_data_source, all_versions, attack_patterns, campaigns,
|
||||
courses_of_action, create, get, identities, indicators, intrusion_sets,
|
||||
malware, observed_data, query, reports, save, set_default_created,
|
||||
set_default_creator, set_default_external_refs,
|
||||
set_default_object_marking_refs, threat_actors, tools, vulnerabilities,
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
|
||||
COURSE_OF_ACTION_ID, COURSE_OF_ACTION_KWARGS, IDENTITY_ID, IDENTITY_KWARGS,
|
||||
INDICATOR_ID, INDICATOR_KWARGS, INTRUSION_SET_ID, INTRUSION_SET_KWARGS,
|
||||
MALWARE_ID, MALWARE_KWARGS, OBSERVED_DATA_ID, OBSERVED_DATA_KWARGS,
|
||||
REPORT_ID, REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID,
|
||||
TOOL_KWARGS, VULNERABILITY_ID, VULNERABILITY_KWARGS,
|
||||
# Auto-detect some settings based on the current default STIX version
|
||||
_STIX_DATA_PATH = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
_STIX_VID,
|
||||
"stix2_data",
|
||||
)
|
||||
_STIX_CONSTANTS_MODULE = "stix2.test." + _STIX_VID + ".constants"
|
||||
|
||||
|
||||
constants = importlib.import_module(_STIX_CONSTANTS_MODULE)
|
||||
|
||||
|
||||
def test_workbench_environment():
|
||||
|
||||
# Create a STIX object
|
||||
ind = create(Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
ind = create(
|
||||
Indicator, id=constants.INDICATOR_ID, **constants.INDICATOR_KWARGS
|
||||
)
|
||||
save(ind)
|
||||
|
||||
resp = get(INDICATOR_ID)
|
||||
resp = get(constants.INDICATOR_ID)
|
||||
assert resp['labels'][0] == 'malicious-activity'
|
||||
|
||||
resp = all_versions(INDICATOR_ID)
|
||||
resp = all_versions(constants.INDICATOR_ID)
|
||||
assert len(resp) == 1
|
||||
|
||||
# Search on something other than id
|
||||
|
@ -41,176 +47,193 @@ def test_workbench_environment():
|
|||
|
||||
|
||||
def test_workbench_get_all_attack_patterns():
|
||||
mal = AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS)
|
||||
mal = AttackPattern(
|
||||
id=constants.ATTACK_PATTERN_ID, **constants.ATTACK_PATTERN_KWARGS
|
||||
)
|
||||
save(mal)
|
||||
|
||||
resp = attack_patterns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == ATTACK_PATTERN_ID
|
||||
assert resp[0].id == constants.ATTACK_PATTERN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_campaigns():
|
||||
cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
cam = Campaign(id=constants.CAMPAIGN_ID, **constants.CAMPAIGN_KWARGS)
|
||||
save(cam)
|
||||
|
||||
resp = campaigns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == CAMPAIGN_ID
|
||||
assert resp[0].id == constants.CAMPAIGN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_courses_of_action():
|
||||
coa = CourseOfAction(id=COURSE_OF_ACTION_ID, **COURSE_OF_ACTION_KWARGS)
|
||||
coa = CourseOfAction(
|
||||
id=constants.COURSE_OF_ACTION_ID, **constants.COURSE_OF_ACTION_KWARGS
|
||||
)
|
||||
save(coa)
|
||||
|
||||
resp = courses_of_action()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == COURSE_OF_ACTION_ID
|
||||
assert resp[0].id == constants.COURSE_OF_ACTION_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_identities():
|
||||
idty = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
idty = Identity(id=constants.IDENTITY_ID, **constants.IDENTITY_KWARGS)
|
||||
save(idty)
|
||||
|
||||
resp = identities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == IDENTITY_ID
|
||||
assert resp[0].id == constants.IDENTITY_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_indicators():
|
||||
resp = indicators()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INDICATOR_ID
|
||||
assert resp[0].id == constants.INDICATOR_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_intrusion_sets():
|
||||
ins = IntrusionSet(id=INTRUSION_SET_ID, **INTRUSION_SET_KWARGS)
|
||||
ins = IntrusionSet(
|
||||
id=constants.INTRUSION_SET_ID, **constants.INTRUSION_SET_KWARGS
|
||||
)
|
||||
save(ins)
|
||||
|
||||
resp = intrusion_sets()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INTRUSION_SET_ID
|
||||
assert resp[0].id == constants.INTRUSION_SET_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_malware():
|
||||
mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
mal = Malware(id=constants.MALWARE_ID, **constants.MALWARE_KWARGS)
|
||||
save(mal)
|
||||
|
||||
resp = malware()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == MALWARE_ID
|
||||
assert resp[0].id == constants.MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_observed_data():
|
||||
od = ObservedData(id=OBSERVED_DATA_ID, **OBSERVED_DATA_KWARGS)
|
||||
od = ObservedData(
|
||||
id=constants.OBSERVED_DATA_ID, **constants.OBSERVED_DATA_KWARGS
|
||||
)
|
||||
save(od)
|
||||
|
||||
resp = observed_data()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == OBSERVED_DATA_ID
|
||||
assert resp[0].id == constants.OBSERVED_DATA_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_reports():
|
||||
rep = Report(id=REPORT_ID, **REPORT_KWARGS)
|
||||
rep = Report(id=constants.REPORT_ID, **constants.REPORT_KWARGS)
|
||||
save(rep)
|
||||
|
||||
resp = reports()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == REPORT_ID
|
||||
assert resp[0].id == constants.REPORT_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_threat_actors():
|
||||
thr = ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS)
|
||||
thr = ThreatActor(
|
||||
id=constants.THREAT_ACTOR_ID, **constants.THREAT_ACTOR_KWARGS
|
||||
)
|
||||
save(thr)
|
||||
|
||||
resp = threat_actors()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == THREAT_ACTOR_ID
|
||||
assert resp[0].id == constants.THREAT_ACTOR_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_tools():
|
||||
tool = Tool(id=TOOL_ID, **TOOL_KWARGS)
|
||||
tool = Tool(id=constants.TOOL_ID, **constants.TOOL_KWARGS)
|
||||
save(tool)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == TOOL_ID
|
||||
assert resp[0].id == constants.TOOL_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_vulnerabilities():
|
||||
vuln = Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
vuln = Vulnerability(
|
||||
id=constants.VULNERABILITY_ID, **constants.VULNERABILITY_KWARGS
|
||||
)
|
||||
save(vuln)
|
||||
|
||||
resp = vulnerabilities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == VULNERABILITY_ID
|
||||
assert resp[0].id == constants.VULNERABILITY_ID
|
||||
|
||||
|
||||
def test_workbench_add_to_bundle():
|
||||
vuln = Vulnerability(**VULNERABILITY_KWARGS)
|
||||
bundle = stix2.v20.Bundle(vuln)
|
||||
vuln = Vulnerability(**constants.VULNERABILITY_KWARGS)
|
||||
bundle = Bundle(vuln)
|
||||
assert bundle.objects[0].name == 'Heartbleed'
|
||||
|
||||
|
||||
def test_workbench_relationships():
|
||||
rel = Relationship(INDICATOR_ID, 'indicates', MALWARE_ID)
|
||||
rel = Relationship(
|
||||
constants.INDICATOR_ID, 'indicates', constants.MALWARE_ID,
|
||||
)
|
||||
save(rel)
|
||||
|
||||
ind = get(INDICATOR_ID)
|
||||
ind = get(constants.INDICATOR_ID)
|
||||
resp = ind.relationships()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].relationship_type == 'indicates'
|
||||
assert resp[0].source_ref == INDICATOR_ID
|
||||
assert resp[0].target_ref == MALWARE_ID
|
||||
assert resp[0].source_ref == constants.INDICATOR_ID
|
||||
assert resp[0].target_ref == constants.MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_created_by():
|
||||
intset = IntrusionSet(name="Breach 123", created_by_ref=IDENTITY_ID)
|
||||
intset = IntrusionSet(
|
||||
name="Breach 123", created_by_ref=constants.IDENTITY_ID,
|
||||
)
|
||||
save(intset)
|
||||
creator = intset.created_by()
|
||||
assert creator.id == IDENTITY_ID
|
||||
assert creator.id == constants.IDENTITY_ID
|
||||
|
||||
|
||||
def test_workbench_related():
|
||||
rel1 = Relationship(MALWARE_ID, 'targets', IDENTITY_ID)
|
||||
rel2 = Relationship(CAMPAIGN_ID, 'uses', MALWARE_ID)
|
||||
rel1 = Relationship(constants.MALWARE_ID, 'targets', constants.IDENTITY_ID)
|
||||
rel2 = Relationship(constants.CAMPAIGN_ID, 'uses', constants.MALWARE_ID)
|
||||
save([rel1, rel2])
|
||||
|
||||
resp = get(MALWARE_ID).related()
|
||||
resp = get(constants.MALWARE_ID).related()
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
assert any(x['id'] == constants.CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == constants.INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == constants.IDENTITY_ID for x in resp)
|
||||
|
||||
resp = get(MALWARE_ID).related(relationship_type='indicates')
|
||||
resp = get(constants.MALWARE_ID).related(relationship_type='indicates')
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_workbench_related_with_filters():
|
||||
malware = Malware(labels=["ransomware"], name="CryptorBit", created_by_ref=IDENTITY_ID)
|
||||
rel = Relationship(malware.id, 'variant-of', MALWARE_ID)
|
||||
malware = Malware(
|
||||
labels=["ransomware"], name="CryptorBit", created_by_ref=constants.IDENTITY_ID,
|
||||
)
|
||||
rel = Relationship(malware.id, 'variant-of', constants.MALWARE_ID)
|
||||
save([malware, rel])
|
||||
|
||||
filters = [Filter('created_by_ref', '=', IDENTITY_ID)]
|
||||
resp = get(MALWARE_ID).related(filters=filters)
|
||||
filters = [Filter('created_by_ref', '=', constants.IDENTITY_ID)]
|
||||
resp = get(constants.MALWARE_ID).related(filters=filters)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0].name == malware.name
|
||||
assert resp[0].created_by_ref == IDENTITY_ID
|
||||
assert resp[0].created_by_ref == constants.IDENTITY_ID
|
||||
|
||||
# filters arg can also be single filter
|
||||
resp = get(MALWARE_ID).related(filters=filters[0])
|
||||
resp = get(constants.MALWARE_ID).related(filters=filters[0])
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_add_data_source():
|
||||
fs_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
|
||||
fs = FileSystemSource(fs_path)
|
||||
fs = FileSystemSource(_STIX_DATA_PATH)
|
||||
add_data_source(fs)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 3
|
||||
resp_ids = [tool.id for tool in resp]
|
||||
assert TOOL_ID in resp_ids
|
||||
assert constants.TOOL_ID in resp_ids
|
||||
assert 'tool--03342581-f790-4f03-ba41-e82e67392e23' in resp_ids
|
||||
assert 'tool--242f3da3-4425-4d11-8f5c-b842886da966' in resp_ids
|
||||
|
||||
|
@ -229,22 +252,28 @@ def test_additional_filters_list():
|
|||
|
||||
|
||||
def test_default_creator():
|
||||
set_default_creator(IDENTITY_ID)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
set_default_creator(constants.IDENTITY_ID)
|
||||
campaign = Campaign(**constants.CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created_by_ref' not in CAMPAIGN_KWARGS
|
||||
assert campaign.created_by_ref == IDENTITY_ID
|
||||
assert 'created_by_ref' not in constants.CAMPAIGN_KWARGS
|
||||
assert campaign.created_by_ref == constants.IDENTITY_ID
|
||||
|
||||
# turn off side-effects to avoid affecting future tests
|
||||
set_default_creator(None)
|
||||
|
||||
|
||||
def test_default_created_timestamp():
|
||||
timestamp = "2018-03-19T01:02:03.000Z"
|
||||
set_default_created(timestamp)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
campaign = Campaign(**constants.CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created' not in CAMPAIGN_KWARGS
|
||||
assert 'created' not in constants.CAMPAIGN_KWARGS
|
||||
assert stix2.utils.format_datetime(campaign.created) == timestamp
|
||||
assert stix2.utils.format_datetime(campaign.modified) == timestamp
|
||||
|
||||
# turn off side-effects to avoid affecting future tests
|
||||
set_default_created(None)
|
||||
|
||||
|
||||
def test_default_external_refs():
|
||||
ext_ref = ExternalReference(
|
||||
|
@ -252,11 +281,14 @@ def test_default_external_refs():
|
|||
description="Threat report",
|
||||
)
|
||||
set_default_external_refs(ext_ref)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
campaign = Campaign(**constants.CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.external_references[0].source_name == "ACME Threat Intel"
|
||||
assert campaign.external_references[0].description == "Threat report"
|
||||
|
||||
# turn off side-effects to avoid affecting future tests
|
||||
set_default_external_refs([])
|
||||
|
||||
|
||||
def test_default_object_marking_refs():
|
||||
stmt_marking = StatementMarking("Copyright 2016, Example Corp")
|
||||
|
@ -265,18 +297,21 @@ def test_default_object_marking_refs():
|
|||
definition=stmt_marking,
|
||||
)
|
||||
set_default_object_marking_refs(mark_def)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
campaign = Campaign(**constants.CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.object_marking_refs[0] == mark_def.id
|
||||
|
||||
# turn off side-effects to avoid affecting future tests
|
||||
set_default_object_marking_refs([])
|
||||
|
||||
|
||||
def test_workbench_custom_property_object_in_observable_extension():
|
||||
ntfs = stix2.v20.NTFSExt(
|
||||
ntfs = NTFSExt(
|
||||
allow_custom=True,
|
||||
sid=1,
|
||||
x_foo='bar',
|
||||
)
|
||||
artifact = stix2.v20.File(
|
||||
artifact = File(
|
||||
name='test',
|
||||
extensions={'ntfs-ext': ntfs},
|
||||
)
|
||||
|
@ -293,7 +328,7 @@ def test_workbench_custom_property_object_in_observable_extension():
|
|||
|
||||
|
||||
def test_workbench_custom_property_dict_in_observable_extension():
|
||||
artifact = stix2.v20.File(
|
||||
artifact = File(
|
||||
allow_custom=True,
|
||||
name='test',
|
||||
extensions={
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
|
||||
import stix2
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import IDENTITY_ID
|
||||
|
||||
EXPECTED_BUNDLE = """{
|
||||
|
@ -156,15 +157,15 @@ def test_create_bundle_with_arg_listarg_and_kwarg(indicator, malware, relationsh
|
|||
|
||||
|
||||
def test_create_bundle_invalid(indicator, malware, relationship):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Bundle(objects=[1])
|
||||
assert excinfo.value.reason == "This property may only contain a dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Bundle(objects=[{}])
|
||||
assert excinfo.value.reason == "This property may only contain a non-empty dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Bundle(objects=[{'type': 'bundle'}])
|
||||
assert excinfo.value.reason == 'This property may not contain a Bundle object'
|
||||
|
||||
|
@ -175,7 +176,7 @@ def test_parse_bundle(version):
|
|||
|
||||
assert bundle.type == "bundle"
|
||||
assert bundle.id.startswith("bundle--")
|
||||
assert type(bundle.objects[0]) is stix2.v20.Indicator
|
||||
assert isinstance(bundle.objects[0], stix2.v20.Indicator)
|
||||
assert bundle.objects[0].type == 'indicator'
|
||||
assert bundle.objects[1].type == 'malware'
|
||||
assert bundle.objects[2].type == 'relationship'
|
||||
|
@ -232,7 +233,7 @@ def test_bundle_with_different_spec_objects():
|
|||
},
|
||||
]
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Bundle(objects=data)
|
||||
|
||||
assert "Spec version 2.0 bundles don't yet support containing objects of a different spec version." in str(excinfo.value)
|
||||
|
|
|
@ -74,7 +74,9 @@ def test_register_object_with_version():
|
|||
v = 'v20'
|
||||
|
||||
assert bundle.objects[0].type in core.STIX2_OBJ_MAPS[v]['objects']
|
||||
assert v in str(bundle.objects[0].__class__)
|
||||
# spec_version is not in STIX 2.0, and is required in 2.1, so this
|
||||
# suffices as a test for a STIX 2.0 object.
|
||||
assert "spec_version" not in bundle.objects[0]
|
||||
|
||||
|
||||
def test_register_marking_with_version():
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
import stix2.v20
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID
|
||||
|
||||
IDENTITY_CUSTOM_PROP = stix2.v20.Identity(
|
||||
|
@ -95,7 +97,7 @@ def test_identity_custom_property_allowed():
|
|||
def test_parse_identity_custom_property(data):
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.parse(data, version="2.0")
|
||||
assert excinfo.value.cls == stix2.v20.Identity
|
||||
assert issubclass(excinfo.value.cls, stix2.v20.Identity)
|
||||
assert excinfo.value.properties == ['foo']
|
||||
assert "Unexpected properties for" in str(excinfo.value)
|
||||
|
||||
|
@ -133,7 +135,7 @@ def test_custom_property_dict_in_bundled_object():
|
|||
'identity_class': 'individual',
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.v20.Bundle(custom_identity)
|
||||
|
||||
bundle = stix2.v20.Bundle(custom_identity, allow_custom=True)
|
||||
|
@ -199,7 +201,7 @@ def test_custom_property_object_in_observable_extension():
|
|||
|
||||
|
||||
def test_custom_property_dict_in_observable_extension():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.v20.File(
|
||||
name='test',
|
||||
extensions={
|
||||
|
@ -718,7 +720,7 @@ def test_custom_extension():
|
|||
def test_custom_extension_wrong_observable_type():
|
||||
# NewExtension is an extension of DomainName, not File
|
||||
ext = NewExtension(property1='something')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.File(
|
||||
name="abc.txt",
|
||||
extensions={
|
||||
|
@ -884,6 +886,49 @@ def test_parse_observable_with_custom_extension():
|
|||
assert parsed.extensions['x-new-ext'].property2 == 12
|
||||
|
||||
|
||||
def test_custom_and_spec_extension_mix():
|
||||
"""
|
||||
Try to make sure that when allow_custom=True, encountering a custom
|
||||
extension doesn't result in a completely uncleaned extensions property.
|
||||
"""
|
||||
|
||||
file_obs = stix2.v20.File(
|
||||
name="my_file.dat",
|
||||
extensions={
|
||||
"x-custom1": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
"ntfs-ext": {
|
||||
"sid": "S-1-whatever",
|
||||
},
|
||||
"x-custom2": {
|
||||
"z": 99.9,
|
||||
"y": False,
|
||||
},
|
||||
"raster-image-ext": {
|
||||
"image_height": 1024,
|
||||
"image_width": 768,
|
||||
"bits_per_pixel": 32,
|
||||
},
|
||||
},
|
||||
allow_custom=True,
|
||||
)
|
||||
|
||||
assert file_obs.extensions["x-custom1"] == {"a": 1, "b": 2}
|
||||
assert file_obs.extensions["x-custom2"] == {"y": False, "z": 99.9}
|
||||
assert file_obs.extensions["ntfs-ext"].sid == "S-1-whatever"
|
||||
assert file_obs.extensions["raster-image-ext"].image_height == 1024
|
||||
|
||||
# Both of these should have been converted to objects, not left as dicts.
|
||||
assert isinstance(
|
||||
file_obs.extensions["raster-image-ext"], stix2.v20.RasterImageExt,
|
||||
)
|
||||
assert isinstance(
|
||||
file_obs.extensions["ntfs-ext"], stix2.v20.NTFSExt,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
# URL is not in EXT_MAP
|
||||
|
@ -911,7 +956,7 @@ def test_parse_observable_with_custom_extension():
|
|||
],
|
||||
)
|
||||
def test_parse_observable_with_unregistered_custom_extension(data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.0')
|
||||
assert "Can't parse unknown extension type" in str(excinfo.value)
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import pytest
|
||||
|
||||
from stix2 import markings
|
||||
from stix2.exceptions import MarkingNotFoundError
|
||||
from stix2.exceptions import InvalidSelectorError, MarkingNotFoundError
|
||||
from stix2.v20 import TLP_RED, Malware
|
||||
|
||||
from .constants import MALWARE_MORE_KWARGS as MALWARE_KWARGS_CONST
|
||||
|
@ -179,7 +179,7 @@ def test_add_marking_mark_same_property_same_marking():
|
|||
],
|
||||
)
|
||||
def test_add_marking_bad_selector(data, marking):
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.add_markings(data, marking[0], marking[1])
|
||||
|
||||
|
||||
|
@ -299,7 +299,7 @@ def test_get_markings_multiple_selectors(data):
|
|||
)
|
||||
def test_get_markings_bad_selector(data, selector):
|
||||
"""Test bad selectors raise exception"""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.get_markings(data, selector)
|
||||
|
||||
|
||||
|
@ -560,7 +560,7 @@ def test_remove_marking_bad_selector():
|
|||
before = {
|
||||
"description": "test description",
|
||||
}
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.remove_markings(before, ["marking-definition--1", "marking-definition--2"], ["title"])
|
||||
|
||||
|
||||
|
@ -642,7 +642,7 @@ def test_is_marked_smoke(data):
|
|||
)
|
||||
def test_is_marked_invalid_selector(data, selector):
|
||||
"""Test invalid selector raises an error."""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.is_marked(data, selectors=selector)
|
||||
|
||||
|
||||
|
@ -836,7 +836,7 @@ def test_is_marked_positional_arguments_combinations():
|
|||
|
||||
|
||||
def test_create_sdo_with_invalid_marking():
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(InvalidSelectorError) as excinfo:
|
||||
Malware(
|
||||
granular_markings=[
|
||||
{
|
||||
|
@ -974,7 +974,7 @@ def test_set_marking_bad_selector(marking):
|
|||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
before = markings.set_markings(before, marking[0], marking[1])
|
||||
|
||||
assert before == after
|
||||
|
@ -1080,7 +1080,7 @@ def test_clear_marking_all_selectors(data):
|
|||
)
|
||||
def test_clear_marking_bad_selector(data, selector):
|
||||
"""Test bad selector raises exception."""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.clear_markings(data, selector)
|
||||
|
||||
|
||||
|
|
|
@ -112,8 +112,6 @@ def test_indicator_created_ref_invalid_format():
|
|||
|
||||
assert excinfo.value.cls == stix2.v20.Indicator
|
||||
assert excinfo.value.prop_name == "created_by_ref"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Indicator 'created_by_ref': must start with 'identity'."
|
||||
|
||||
|
||||
def test_indicator_revoked_invalid():
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytz
|
|||
|
||||
import stix2
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
||||
|
||||
EXPECTED_MALWARE = """{
|
||||
|
@ -145,7 +146,7 @@ def test_parse_malware(data):
|
|||
|
||||
def test_parse_malware_invalid_labels():
|
||||
data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse(data, version="2.0")
|
||||
assert "Invalid value for Malware 'labels'" in str(excinfo.value)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
from stix2 import exceptions, markings
|
||||
from stix2.v20 import TLP_AMBER, Malware
|
||||
|
||||
from ...exceptions import MarkingNotFoundError
|
||||
from .constants import FAKE_TIME, MALWARE_ID
|
||||
from .constants import MALWARE_KWARGS as MALWARE_KWARGS_CONST
|
||||
from .constants import MARKING_IDS
|
||||
|
@ -350,7 +351,7 @@ def test_remove_markings_bad_markings():
|
|||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(MarkingNotFoundError) as excinfo:
|
||||
markings.remove_markings(before, [MARKING_IDS[4]], None)
|
||||
assert str(excinfo.value) == "Marking ['%s'] was not found in Malware!" % MARKING_IDS[4]
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytz
|
|||
|
||||
import stix2
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import IDENTITY_ID, OBSERVED_DATA_ID
|
||||
|
||||
OBJECTS_REGEX = re.compile('\"objects\": {(?:.*?)(?:(?:[^{]*?)|(?:{[^{]*?}))*}', re.DOTALL)
|
||||
|
@ -239,7 +240,7 @@ def test_parse_artifact_valid(data):
|
|||
)
|
||||
def test_parse_artifact_invalid(data):
|
||||
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.parse(odata_str, version="2.0")
|
||||
|
||||
|
||||
|
@ -468,11 +469,10 @@ def test_parse_email_message_with_at_least_one_error(data):
|
|||
"4": "artifact",
|
||||
"5": "file",
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, valid_refs, version='2.0')
|
||||
|
||||
assert excinfo.value.cls == stix2.v20.EmailMIMEComponent
|
||||
assert excinfo.value.properties == ["body", "body_raw_ref"]
|
||||
assert excinfo.value.cls == stix2.v20.EmailMessage
|
||||
assert "At least one of the" in str(excinfo.value)
|
||||
assert "must be populated" in str(excinfo.value)
|
||||
|
||||
|
@ -734,7 +734,7 @@ def test_file_example_with_NTFSExt():
|
|||
|
||||
|
||||
def test_file_example_with_empty_NTFSExt():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.File(
|
||||
name="abc.txt",
|
||||
extensions={
|
||||
|
@ -742,8 +742,7 @@ def test_file_example_with_empty_NTFSExt():
|
|||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v20.NTFSExt
|
||||
assert excinfo.value.properties == sorted(list(stix2.NTFSExt._properties.keys()))
|
||||
assert excinfo.value.cls == stix2.v20.File
|
||||
|
||||
|
||||
def test_file_example_with_PDFExt():
|
||||
|
@ -1112,16 +1111,14 @@ def test_process_example_empty_error():
|
|||
|
||||
|
||||
def test_process_example_empty_with_extensions():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Process(
|
||||
extensions={
|
||||
"windows-process-ext": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v20.WindowsProcessExt
|
||||
properties_of_extension = list(stix2.v20.WindowsProcessExt._properties.keys())
|
||||
assert excinfo.value.properties == sorted(properties_of_extension)
|
||||
assert excinfo.value.cls == stix2.v20.Process
|
||||
|
||||
|
||||
def test_process_example_windows_process_ext():
|
||||
|
@ -1144,7 +1141,7 @@ def test_process_example_windows_process_ext():
|
|||
|
||||
|
||||
def test_process_example_windows_process_ext_empty():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v20.Process(
|
||||
pid=1221,
|
||||
name="gedit-bin",
|
||||
|
@ -1153,9 +1150,7 @@ def test_process_example_windows_process_ext_empty():
|
|||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v20.WindowsProcessExt
|
||||
properties_of_extension = list(stix2.v20.WindowsProcessExt._properties.keys())
|
||||
assert excinfo.value.properties == sorted(properties_of_extension)
|
||||
assert excinfo.value.cls == stix2.v20.Process
|
||||
|
||||
|
||||
def test_process_example_extensions_empty():
|
||||
|
@ -1289,7 +1284,7 @@ def test_user_account_unix_account_ext_example():
|
|||
|
||||
|
||||
def test_windows_registry_key_example():
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.v20.WindowsRegistryValueType(
|
||||
name="Foo",
|
||||
data="qwerty",
|
||||
|
|
|
@ -3,9 +3,11 @@ import uuid
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
|
||||
from stix2.exceptions import (
|
||||
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
|
||||
)
|
||||
from stix2.properties import (
|
||||
ERROR_INVALID_ID, BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
|
||||
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
Property, ReferenceProperty, STIXObjectProperty, StringProperty,
|
||||
|
@ -89,7 +91,7 @@ def test_type_property():
|
|||
assert prop.clean(prop.default())
|
||||
|
||||
|
||||
ID_PROP = IDProperty('my-type')
|
||||
ID_PROP = IDProperty('my-type', spec_version="2.0")
|
||||
MY_ID = 'my-type--232c9d3f-49fc-4440-bb01-607f638778e7'
|
||||
|
||||
|
||||
|
@ -127,7 +129,7 @@ CONSTANT_IDS.extend(constants.RELATIONSHIP_IDS)
|
|||
@pytest.mark.parametrize("value", CONSTANT_IDS)
|
||||
def test_id_property_valid_for_type(value):
|
||||
type = value.split('--', 1)[0]
|
||||
assert IDProperty(type=type).clean(value) == value
|
||||
assert IDProperty(type=type, spec_version="2.0").clean(value) == value
|
||||
|
||||
|
||||
def test_id_property_wrong_type():
|
||||
|
@ -147,9 +149,8 @@ def test_id_property_wrong_type():
|
|||
],
|
||||
)
|
||||
def test_id_property_not_a_valid_hex_uuid(value):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
ID_PROP.clean(value)
|
||||
assert str(excinfo.value) == ERROR_INVALID_ID
|
||||
|
||||
|
||||
def test_id_property_default():
|
||||
|
@ -275,7 +276,7 @@ def test_boolean_property_invalid(value):
|
|||
|
||||
|
||||
def test_reference_property():
|
||||
ref_prop = ReferenceProperty()
|
||||
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):
|
||||
|
@ -286,6 +287,16 @@ def test_reference_property():
|
|||
ref_prop.clean("my-type--00000000-0000-0000-0000-000000000000")
|
||||
|
||||
|
||||
def test_reference_property_specific_type():
|
||||
ref_prop = ReferenceProperty(valid_types="my-type", spec_version="2.0")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("not-my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf")
|
||||
|
||||
assert ref_prop.clean("my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf") == \
|
||||
"my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'2017-01-01T12:34:56Z',
|
||||
|
@ -329,7 +340,7 @@ def test_hex_property():
|
|||
],
|
||||
)
|
||||
def test_dictionary_property_valid(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
dict_prop = DictionaryProperty(spec_version="2.0")
|
||||
assert dict_prop.clean(d)
|
||||
|
||||
|
||||
|
@ -350,7 +361,7 @@ def test_dictionary_property_valid(d):
|
|||
],
|
||||
)
|
||||
def test_dictionary_property_invalid_key(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
dict_prop = DictionaryProperty(spec_version="2.0")
|
||||
|
||||
with pytest.raises(DictionaryKeyError) as excinfo:
|
||||
dict_prop.clean(d[0])
|
||||
|
@ -372,7 +383,7 @@ def test_dictionary_property_invalid_key(d):
|
|||
],
|
||||
)
|
||||
def test_dictionary_property_invalid(d):
|
||||
dict_prop = DictionaryProperty()
|
||||
dict_prop = DictionaryProperty(spec_version="2.0")
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
dict_prop.clean(d[0])
|
||||
|
@ -382,7 +393,7 @@ def test_dictionary_property_invalid(d):
|
|||
def test_property_list_of_dictionary():
|
||||
@stix2.v20.CustomObject(
|
||||
'x-new-obj', [
|
||||
('property1', ListProperty(DictionaryProperty(), required=True)),
|
||||
('property1', ListProperty(DictionaryProperty(spec_version="2.0"), required=True)),
|
||||
],
|
||||
)
|
||||
class NewObj():
|
||||
|
@ -448,7 +459,7 @@ def test_enum_property_invalid():
|
|||
|
||||
|
||||
def test_extension_property_valid():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
ext_prop = ExtensionsProperty(spec_version="2.0", enclosing_type='file')
|
||||
assert ext_prop({
|
||||
'windows-pebinary-ext': {
|
||||
'pe_type': 'exe',
|
||||
|
@ -456,23 +467,27 @@ def test_extension_property_valid():
|
|||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
1,
|
||||
{'foobar-ext': {
|
||||
'pe_type': 'exe',
|
||||
}},
|
||||
],
|
||||
)
|
||||
def test_extension_property_invalid(data):
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
def test_extension_property_invalid1():
|
||||
ext_prop = ExtensionsProperty(spec_version="2.0", enclosing_type='file')
|
||||
with pytest.raises(ValueError):
|
||||
ext_prop.clean(data)
|
||||
ext_prop.clean(1)
|
||||
|
||||
|
||||
def test_extension_property_invalid2():
|
||||
ext_prop = ExtensionsProperty(spec_version="2.0", enclosing_type='file')
|
||||
with pytest.raises(CustomContentError):
|
||||
ext_prop.clean(
|
||||
{
|
||||
'foobar-ext': {
|
||||
'pe_type': 'exe',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_extension_property_invalid_type():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='indicator')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
ext_prop = ExtensionsProperty(spec_version="2.0", enclosing_type='indicator')
|
||||
with pytest.raises(CustomContentError) as excinfo:
|
||||
ext_prop.clean(
|
||||
{
|
||||
'windows-pebinary-ext': {
|
||||
|
|
|
@ -90,8 +90,6 @@ def test_report_example_objects_in_object_refs_with_bad_id():
|
|||
|
||||
assert excinfo.value.cls == stix2.v20.Report
|
||||
assert excinfo.value.prop_name == "object_refs"
|
||||
assert excinfo.value.reason == stix2.properties.ERROR_INVALID_ID
|
||||
assert str(excinfo.value) == "Invalid value for Report 'object_refs': " + stix2.properties.ERROR_INVALID_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -59,8 +59,6 @@ def test_sighting_bad_where_sighted_refs():
|
|||
|
||||
assert excinfo.value.cls == stix2.v20.Sighting
|
||||
assert excinfo.value.prop_name == "where_sighted_refs"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Sighting 'where_sighted_refs': must start with 'identity'."
|
||||
|
||||
|
||||
def test_sighting_type_must_be_sightings():
|
||||
|
@ -69,8 +67,6 @@ def test_sighting_type_must_be_sightings():
|
|||
|
||||
assert excinfo.value.cls == stix2.v20.Sighting
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'sighting'."
|
||||
assert str(excinfo.value) == "Invalid value for Sighting 'type': must equal 'sighting'."
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_sighting():
|
||||
|
|
|
@ -144,8 +144,8 @@ def test_deduplicate(stix_objs1):
|
|||
"type": "network-traffic",
|
||||
"src_ref": "1",
|
||||
"protocols": [
|
||||
"tcp",
|
||||
"http",
|
||||
"tcp",
|
||||
"http",
|
||||
],
|
||||
"extensions": {
|
||||
"http-request-ext": {
|
||||
|
|
|
@ -5,7 +5,8 @@ import pytest
|
|||
import stix2
|
||||
|
||||
from .constants import (
|
||||
FAKE_TIME, INDICATOR_KWARGS, MALWARE_KWARGS, RELATIONSHIP_KWARGS,
|
||||
FAKE_TIME, GROUPING_KWARGS, INDICATOR_KWARGS, INFRASTRUCTURE_KWARGS,
|
||||
MALWARE_KWARGS, RELATIONSHIP_KWARGS,
|
||||
)
|
||||
|
||||
|
||||
|
@ -39,6 +40,16 @@ def indicator(uuid4, clock):
|
|||
return stix2.v21.Indicator(**INDICATOR_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def infrastructure(uuid4, clock):
|
||||
return stix2.v21.Infrastructure(**INFRASTRUCTURE_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def grouping(uuid4, clock):
|
||||
return stix2.v21.Grouping(**GROUPING_KWARGS)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def malware(uuid4, clock):
|
||||
return stix2.v21.Malware(**MALWARE_KWARGS)
|
||||
|
@ -60,6 +71,7 @@ def stix_objs1():
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -73,6 +85,7 @@ def stix_objs1():
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -86,6 +99,7 @@ def stix_objs1():
|
|||
"modified": "2017-01-27T13:49:53.936Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -99,6 +113,7 @@ def stix_objs1():
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -112,6 +127,7 @@ def stix_objs1():
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -129,6 +145,7 @@ def stix_objs2():
|
|||
],
|
||||
"modified": "2017-01-31T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern_type": "stix",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
|
@ -142,6 +159,7 @@ def stix_objs2():
|
|||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern_type": "stix",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
|
@ -155,6 +173,7 @@ def stix_objs2():
|
|||
],
|
||||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern_type": "stix",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
|
|
|
@ -7,8 +7,10 @@ FAKE_TIME = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
|||
ATTACK_PATTERN_ID = "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"
|
||||
CAMPAIGN_ID = "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
COURSE_OF_ACTION_ID = "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"
|
||||
GROUPING_ID = "grouping--753abcde-3141-5926-ace5-0a810b1ff996"
|
||||
IDENTITY_ID = "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
||||
INDICATOR_ID = "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7"
|
||||
INFRASTRUCTURE_ID = "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018"
|
||||
INTRUSION_SET_ID = "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29"
|
||||
LOCATION_ID = "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64"
|
||||
MALWARE_ID = "malware--9c4638ec-f1de-4ddb-abf4-1b760417654e"
|
||||
|
@ -70,6 +72,15 @@ COURSE_OF_ACTION_KWARGS = dict(
|
|||
name="Block",
|
||||
)
|
||||
|
||||
GROUPING_KWARGS = dict(
|
||||
name="Harry Potter and the Leet Hackers",
|
||||
context="suspicious-activity",
|
||||
object_refs=[
|
||||
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
|
||||
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
|
||||
],
|
||||
)
|
||||
|
||||
IDENTITY_KWARGS = dict(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
|
@ -77,7 +88,14 @@ IDENTITY_KWARGS = dict(
|
|||
|
||||
INDICATOR_KWARGS = dict(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern_type="stix",
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
|
||||
INFRASTRUCTURE_KWARGS = dict(
|
||||
name="Poison Ivy C2",
|
||||
infrastructure_types=["command-and-control"],
|
||||
)
|
||||
|
||||
INTRUSION_SET_KWARGS = dict(
|
||||
|
@ -87,6 +105,7 @@ INTRUSION_SET_KWARGS = dict(
|
|||
MALWARE_KWARGS = dict(
|
||||
malware_types=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
MALWARE_MORE_KWARGS = dict(
|
||||
|
@ -97,6 +116,7 @@ MALWARE_MORE_KWARGS = dict(
|
|||
malware_types=['ransomware'],
|
||||
name="Cryptolocker",
|
||||
description="A ransomware related to ...",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
OBSERVED_DATA_KWARGS = dict(
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
],
|
||||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"spec_version": "2.1",
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"type": "bundle"
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
"object_marking_refs": [
|
||||
"marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"
|
||||
],
|
||||
"type": "malware"
|
||||
"type": "malware",
|
||||
"is_family": false
|
||||
}
|
||||
],
|
||||
"spec_version": "2.0",
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import datetime as dt
|
||||
import json
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
from stix2.base import STIXJSONEncoder
|
||||
|
||||
|
||||
|
@ -23,3 +25,14 @@ def test_encode_json_object():
|
|||
json.dumps(test_dict, cls=STIXJSONEncoder)
|
||||
|
||||
assert " is not JSON serializable" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_deterministic_id_unicode():
|
||||
mutex = {'name': u'D*Fl#Ed*\u00a3\u00a8', 'type': 'mutex'}
|
||||
obs = stix2.parse_observable(mutex, version="2.1")
|
||||
|
||||
dd_idx = obs.id.index("--")
|
||||
id_uuid = uuid.UUID(obs.id[dd_idx+2:])
|
||||
|
||||
assert id_uuid.variant == uuid.RFC_4122
|
||||
assert id_uuid.version == 5
|
||||
|
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
|
||||
import stix2
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import IDENTITY_ID
|
||||
|
||||
EXPECTED_BUNDLE = """{
|
||||
|
@ -20,6 +21,7 @@ EXPECTED_BUNDLE = """{
|
|||
"malicious-activity"
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "2017-01-01T12:34:56Z"
|
||||
},
|
||||
{
|
||||
|
@ -31,7 +33,8 @@ EXPECTED_BUNDLE = """{
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
@ -57,6 +60,7 @@ EXPECTED_BUNDLE_DICT = {
|
|||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
"indicator_types": [
|
||||
"malicious-activity",
|
||||
|
@ -72,6 +76,7 @@ EXPECTED_BUNDLE_DICT = {
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
@ -160,15 +165,15 @@ def test_create_bundle_with_arg_listarg_and_kwarg(indicator, malware, relationsh
|
|||
|
||||
|
||||
def test_create_bundle_invalid(indicator, malware, relationship):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v21.Bundle(objects=[1])
|
||||
assert excinfo.value.reason == "This property may only contain a dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v21.Bundle(objects=[{}])
|
||||
assert excinfo.value.reason == "This property may only contain a non-empty dictionary or object"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v21.Bundle(objects=[{'type': 'bundle'}])
|
||||
assert excinfo.value.reason == 'This property may not contain a Bundle object'
|
||||
|
||||
|
@ -179,7 +184,7 @@ def test_parse_bundle(version):
|
|||
|
||||
assert bundle.type == "bundle"
|
||||
assert bundle.id.startswith("bundle--")
|
||||
assert type(bundle.objects[0]) is stix2.v21.Indicator
|
||||
assert isinstance(bundle.objects[0], stix2.v21.Indicator)
|
||||
assert bundle.objects[0].type == 'indicator'
|
||||
assert bundle.objects[1].type == 'malware'
|
||||
assert bundle.objects[2].type == 'relationship'
|
||||
|
@ -232,6 +237,7 @@ def test_bundle_obj_id_found():
|
|||
"malicious-activity",
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
},
|
||||
{
|
||||
|
@ -244,6 +250,7 @@ def test_bundle_obj_id_found():
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "malware",
|
||||
|
@ -255,6 +262,7 @@ def test_bundle_obj_id_found():
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
|
|
@ -16,6 +16,7 @@ BUNDLE = {
|
|||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "2017-01-01T12:34:56Z",
|
||||
"indicator_types": [
|
||||
"malicious-activity",
|
||||
|
@ -31,6 +32,7 @@ BUNDLE = {
|
|||
"malware_types": [
|
||||
"ransomware",
|
||||
],
|
||||
"is_family": False,
|
||||
},
|
||||
{
|
||||
"type": "relationship",
|
||||
|
@ -77,7 +79,7 @@ def test_register_object_with_version():
|
|||
v = 'v21'
|
||||
|
||||
assert bundle.objects[0].type in core.STIX2_OBJ_MAPS[v]['objects']
|
||||
assert v in str(bundle.objects[0].__class__)
|
||||
assert bundle.objects[0].spec_version == "2.1"
|
||||
|
||||
|
||||
def test_register_marking_with_version():
|
||||
|
@ -125,7 +127,7 @@ def test_register_observable_with_default_version():
|
|||
"1": {
|
||||
"type": "directory",
|
||||
"path": "/usr/home",
|
||||
"contains_refs": ["0"],
|
||||
"contains_refs": ["file--420bc087-8b53-5ae9-8210-20d27d5e96c8"],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -163,7 +165,7 @@ def test_register_observable_extension_with_default_version():
|
|||
"1": {
|
||||
"type": "directory",
|
||||
"path": "/usr/home",
|
||||
"contains_refs": ["0"],
|
||||
"contains_refs": ["file--420bc087-8b53-5ae9-8210-20d27d5e96c8"],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import datetime as dt
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
import stix2.exceptions
|
||||
import stix2.utils
|
||||
|
||||
from .constants import COURSE_OF_ACTION_ID, IDENTITY_ID
|
||||
|
||||
EXPECTED = """{
|
||||
COA_WITH_BIN_JSON = """{
|
||||
"type": "course-of-action",
|
||||
"spec_version": "2.1",
|
||||
"id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
|
@ -15,48 +14,85 @@ EXPECTED = """{
|
|||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"action_type": "textual:text/plain",
|
||||
"os_execution_envs": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"action_bin": "aGVsbG8gd29ybGQ="
|
||||
}"""
|
||||
|
||||
|
||||
def test_course_of_action_example():
|
||||
coa = stix2.v21.CourseOfAction(
|
||||
id=COURSE_OF_ACTION_ID,
|
||||
created_by_ref=IDENTITY_ID,
|
||||
created="2016-04-06T20:03:48.000Z",
|
||||
modified="2016-04-06T20:03:48.000Z",
|
||||
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
description="This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
)
|
||||
COA_WITH_REF_JSON = """{
|
||||
"type": "course-of-action",
|
||||
"spec_version": "2.1",
|
||||
"id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||
"created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"action_type": "textual:text/plain",
|
||||
"os_execution_envs": [
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
],
|
||||
"action_reference": {
|
||||
"source_name": "a source",
|
||||
"description": "description of a source"
|
||||
}
|
||||
}"""
|
||||
|
||||
assert str(coa) == EXPECTED
|
||||
|
||||
COA_WITH_BIN_DICT = json.loads(COA_WITH_BIN_JSON)
|
||||
COA_WITH_REF_DICT = json.loads(COA_WITH_REF_JSON)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
EXPECTED,
|
||||
{
|
||||
"created": "2016-04-06T20:03:48.000Z",
|
||||
"created_by_ref": IDENTITY_ID,
|
||||
"description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
|
||||
"id": COURSE_OF_ACTION_ID,
|
||||
"modified": "2016-04-06T20:03:48.000Z",
|
||||
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
"spec_version": "2.1",
|
||||
"type": "course-of-action",
|
||||
},
|
||||
"sdo_json,sdo_dict", [
|
||||
(COA_WITH_BIN_JSON, COA_WITH_BIN_DICT),
|
||||
(COA_WITH_REF_JSON, COA_WITH_REF_DICT),
|
||||
],
|
||||
)
|
||||
def test_parse_course_of_action(data):
|
||||
coa = stix2.parse(data, version="2.1")
|
||||
def test_course_of_action_example(sdo_json, sdo_dict):
|
||||
coa = stix2.v21.CourseOfAction(**sdo_dict)
|
||||
assert str(coa) == sdo_json
|
||||
|
||||
assert coa.type == 'course-of-action'
|
||||
assert coa.spec_version == '2.1'
|
||||
assert coa.id == COURSE_OF_ACTION_ID
|
||||
assert coa.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert coa.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
|
||||
assert coa.created_by_ref == IDENTITY_ID
|
||||
assert coa.description == "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
|
||||
assert coa.name == "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"sdo_json,sdo_dict", [
|
||||
(COA_WITH_BIN_JSON, COA_WITH_BIN_DICT),
|
||||
(COA_WITH_REF_JSON, COA_WITH_REF_DICT),
|
||||
],
|
||||
)
|
||||
def test_parse_course_of_action(sdo_json, sdo_dict):
|
||||
|
||||
# Names of timestamp-valued attributes
|
||||
ts_attrs = {"created", "modified"}
|
||||
|
||||
for data in (sdo_json, sdo_dict):
|
||||
coa = stix2.parse(data, version="2.1")
|
||||
|
||||
# sdo_dict is handy as a source of attribute names/values to check
|
||||
for attr_name, attr_value in sdo_dict.items():
|
||||
cmp_value = stix2.utils.parse_into_datetime(attr_value) \
|
||||
if attr_name in ts_attrs else attr_value
|
||||
|
||||
assert getattr(coa, attr_name) == cmp_value
|
||||
|
||||
|
||||
def test_course_of_action_constraint():
|
||||
with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError):
|
||||
stix2.v21.CourseOfAction(
|
||||
name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
|
||||
action_bin="aGVsbG8gd29ybGQ=",
|
||||
action_reference=stix2.v21.ExternalReference(
|
||||
source_name="a source",
|
||||
description="description of a source",
|
||||
),
|
||||
)
|
||||
|
||||
# TODO: Add other examples
|
||||
|
|
|
@ -2,7 +2,9 @@ import pytest
|
|||
|
||||
import stix2
|
||||
import stix2.base
|
||||
import stix2.v21
|
||||
|
||||
from ...exceptions import InvalidValueError
|
||||
from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID
|
||||
|
||||
IDENTITY_CUSTOM_PROP = stix2.v21.Identity(
|
||||
|
@ -97,7 +99,7 @@ def test_identity_custom_property_allowed():
|
|||
def test_parse_identity_custom_property(data):
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.parse(data, version="2.1")
|
||||
assert excinfo.value.cls == stix2.v21.Identity
|
||||
assert issubclass(excinfo.value.cls, stix2.v21.Identity)
|
||||
assert excinfo.value.properties == ['foo']
|
||||
assert "Unexpected properties for" in str(excinfo.value)
|
||||
|
||||
|
@ -136,7 +138,7 @@ def test_custom_property_dict_in_bundled_object():
|
|||
'identity_class': 'individual',
|
||||
'x_foo': 'bar',
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.v21.Bundle(custom_identity)
|
||||
|
||||
bundle = stix2.v21.Bundle(custom_identity, allow_custom=True)
|
||||
|
@ -203,7 +205,7 @@ def test_custom_property_object_in_observable_extension():
|
|||
|
||||
|
||||
def test_custom_property_dict_in_observable_extension():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||
with pytest.raises(InvalidValueError):
|
||||
stix2.v21.File(
|
||||
name='test',
|
||||
extensions={
|
||||
|
@ -722,7 +724,7 @@ def test_custom_extension():
|
|||
def test_custom_extension_wrong_observable_type():
|
||||
# NewExtension is an extension of DomainName, not File
|
||||
ext = NewExtension(property1='something')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.v21.File(
|
||||
name="abc.txt",
|
||||
extensions={
|
||||
|
@ -888,6 +890,49 @@ def test_parse_observable_with_custom_extension():
|
|||
assert parsed.extensions['x-new-ext'].property2 == 12
|
||||
|
||||
|
||||
def test_custom_and_spec_extension_mix():
|
||||
"""
|
||||
Try to make sure that when allow_custom=True, encountering a custom
|
||||
extension doesn't result in a completely uncleaned extensions property.
|
||||
"""
|
||||
|
||||
file_obs = stix2.v21.File(
|
||||
name="my_file.dat",
|
||||
extensions={
|
||||
"x-custom1": {
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
},
|
||||
"ntfs-ext": {
|
||||
"sid": "S-1-whatever",
|
||||
},
|
||||
"x-custom2": {
|
||||
"z": 99.9,
|
||||
"y": False,
|
||||
},
|
||||
"raster-image-ext": {
|
||||
"image_height": 1024,
|
||||
"image_width": 768,
|
||||
"bits_per_pixel": 32,
|
||||
},
|
||||
},
|
||||
allow_custom=True,
|
||||
)
|
||||
|
||||
assert file_obs.extensions["x-custom1"] == {"a": 1, "b": 2}
|
||||
assert file_obs.extensions["x-custom2"] == {"y": False, "z": 99.9}
|
||||
assert file_obs.extensions["ntfs-ext"].sid == "S-1-whatever"
|
||||
assert file_obs.extensions["raster-image-ext"].image_height == 1024
|
||||
|
||||
# Both of these should have been converted to objects, not left as dicts.
|
||||
assert isinstance(
|
||||
file_obs.extensions["raster-image-ext"], stix2.v21.RasterImageExt,
|
||||
)
|
||||
assert isinstance(
|
||||
file_obs.extensions["ntfs-ext"], stix2.v21.NTFSExt,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
# URL is not in EXT_MAP
|
||||
|
@ -915,7 +960,7 @@ def test_parse_observable_with_custom_extension():
|
|||
],
|
||||
)
|
||||
def test_parse_observable_with_unregistered_custom_extension(data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
assert "Can't parse unknown extension type" in str(excinfo.value)
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ stix_objs = [
|
|||
"remote-access-trojan",
|
||||
],
|
||||
"modified": "2017-01-27T13:49:53.997Z",
|
||||
"is_family": False,
|
||||
"name": "Poison Ivy",
|
||||
"type": "malware",
|
||||
},
|
||||
|
@ -28,6 +29,7 @@ stix_objs = [
|
|||
"modified": "2014-05-08T09:00:00.000Z",
|
||||
"name": "File hash for Poison Ivy variant",
|
||||
"pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2014-05-08T09:00:00.000000Z",
|
||||
|
@ -84,7 +86,10 @@ stix_objs = [
|
|||
"objects": {
|
||||
"0": {
|
||||
"type": "file",
|
||||
"spec_version": "2.1",
|
||||
"id": "file--42a7175a-42cc-508f-8fa7-23b330aff876",
|
||||
"name": "HAL 9000.exe",
|
||||
"defanged": False,
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -107,8 +112,14 @@ filters = [
|
|||
Filter("object_marking_refs", "=", "marking-definition--613f2e26-0000-4000-8000-b8e91df99dc9"),
|
||||
Filter("granular_markings.selectors", "in", "description"),
|
||||
Filter("external_references.source_name", "=", "CVE"),
|
||||
Filter("objects", "=", {"0": {"type": "file", "name": "HAL 9000.exe"}}),
|
||||
Filter("objects", "contains", {"type": "file", "name": "HAL 9000.exe"}),
|
||||
Filter(
|
||||
"objects", "=",
|
||||
{"0": {"type": "file", "id": "file--42a7175a-42cc-508f-8fa7-23b330aff876", "name": "HAL 9000.exe", "spec_version": "2.1", "defanged": False}},
|
||||
),
|
||||
Filter(
|
||||
"objects", "contains",
|
||||
{"type": "file", "id": "file--42a7175a-42cc-508f-8fa7-23b330aff876", "name": "HAL 9000.exe", "spec_version": "2.1", "defanged": False},
|
||||
),
|
||||
Filter("labels", "contains", "heartbleed"),
|
||||
]
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ IND1 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -37,6 +38,7 @@ IND2 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -50,6 +52,7 @@ IND3 = {
|
|||
"modified": "2017-01-27T13:49:53.936Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -63,6 +66,7 @@ IND4 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -76,6 +80,7 @@ IND5 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -89,6 +94,7 @@ IND6 = {
|
|||
"modified": "2017-01-31T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -102,6 +108,7 @@ IND7 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
@ -115,6 +122,7 @@ IND8 = {
|
|||
"modified": "2017-01-27T13:49:53.935Z",
|
||||
"name": "Malicious site hosting downloader",
|
||||
"pattern": "[url:value = 'http://x4z9arb.cn/4712']",
|
||||
"pattern_type": "stix",
|
||||
"spec_version": "2.1",
|
||||
"type": "indicator",
|
||||
"valid_from": "2017-01-27T13:49:53.935382Z",
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
import stix2.environment
|
||||
import stix2.exceptions
|
||||
|
||||
from .constants import (
|
||||
CAMPAIGN_ID, CAMPAIGN_KWARGS, FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS,
|
||||
INDICATOR_ID, INDICATOR_KWARGS, MALWARE_ID, MALWARE_KWARGS,
|
||||
RELATIONSHIP_IDS,
|
||||
ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
|
||||
FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
|
||||
LOCATION_ID, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS, REPORT_ID,
|
||||
REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID, TOOL_KWARGS,
|
||||
VULNERABILITY_ID, VULNERABILITY_KWARGS,
|
||||
)
|
||||
|
||||
|
||||
|
@ -219,7 +223,8 @@ def test_parse_malware():
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
mal = env.parse(data, version="2.1")
|
||||
|
||||
|
@ -230,6 +235,7 @@ def test_parse_malware():
|
|||
assert mal.modified == FAKE_TIME
|
||||
assert mal.malware_types == ['ransomware']
|
||||
assert mal.name == "Cryptolocker"
|
||||
assert not mal.is_family
|
||||
|
||||
|
||||
def test_creator_of():
|
||||
|
@ -351,6 +357,7 @@ def test_related_to_no_id(ds):
|
|||
mal = {
|
||||
"type": "malware",
|
||||
"name": "some variant",
|
||||
"is_family": False,
|
||||
}
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
env.related_to(mal)
|
||||
|
@ -372,3 +379,374 @@ def test_related_to_by_target(ds):
|
|||
assert len(resp) == 2
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_attack_pattern1():
|
||||
ap1 = stix2.v21.AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS)
|
||||
ap2 = stix2.v21.AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ap1, ap2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_attack_pattern2():
|
||||
ATTACK_KWARGS = dict(
|
||||
name="Phishing",
|
||||
external_references=[
|
||||
{
|
||||
"url": "https://example2",
|
||||
"source_name": "some-source2",
|
||||
},
|
||||
],
|
||||
)
|
||||
ap1 = stix2.v21.AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_KWARGS)
|
||||
ap2 = stix2.v21.AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ap1, ap2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_campaign1():
|
||||
camp1 = stix2.v21.Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
camp2 = stix2.v21.Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(camp1, camp2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_campaign2():
|
||||
CAMP_KWARGS = dict(
|
||||
name="Green Group Attacks Against Finance",
|
||||
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||
aliases=["super-green", "some-green"],
|
||||
)
|
||||
camp1 = stix2.v21.Campaign(id=CAMPAIGN_ID, **CAMP_KWARGS)
|
||||
camp2 = stix2.v21.Campaign(id=CAMPAIGN_ID, **CAMP_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(camp1, camp2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_identity1():
|
||||
iden1 = stix2.v21.Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
iden2 = stix2.v21.Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(iden1, iden2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_identity2():
|
||||
IDEN_KWARGS = dict(
|
||||
name="John Smith",
|
||||
identity_class="individual",
|
||||
sectors=["government", "critical-infrastructure"],
|
||||
)
|
||||
iden1 = stix2.v21.Identity(id=IDENTITY_ID, **IDEN_KWARGS)
|
||||
iden2 = stix2.v21.Identity(id=IDENTITY_ID, **IDEN_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(iden1, iden2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_indicator():
|
||||
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
ind2 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ind1, ind2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_location1():
|
||||
LOCATION_KWARGS = dict(latitude=45, longitude=179)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(loc1, loc2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_location2():
|
||||
LOCATION_KWARGS = dict(
|
||||
latitude=38.889,
|
||||
longitude=-77.023,
|
||||
region="northern-america",
|
||||
country="us",
|
||||
)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(loc1, loc2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_malware():
|
||||
malw1 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
malw2 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(malw1, malw2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_threat_actor1():
|
||||
ta1 = stix2.v21.ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS)
|
||||
ta2 = stix2.v21.ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ta1, ta2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_threat_actor2():
|
||||
THREAT_KWARGS = dict(
|
||||
threat_actor_types=["crime-syndicate"],
|
||||
aliases=["super-evil"],
|
||||
name="Evil Org",
|
||||
)
|
||||
ta1 = stix2.v21.ThreatActor(id=THREAT_ACTOR_ID, **THREAT_KWARGS)
|
||||
ta2 = stix2.v21.ThreatActor(id=THREAT_ACTOR_ID, **THREAT_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ta1, ta2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_tool():
|
||||
tool1 = stix2.v21.Tool(id=TOOL_ID, **TOOL_KWARGS)
|
||||
tool2 = stix2.v21.Tool(id=TOOL_ID, **TOOL_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(tool1, tool2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_vulnerability1():
|
||||
vul1 = stix2.v21.Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
vul2 = stix2.v21.Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(vul1, vul2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_vulnerability2():
|
||||
VULN_KWARGS1 = dict(
|
||||
name="Heartbleed",
|
||||
external_references=[
|
||||
{
|
||||
"url": "https://example",
|
||||
"source_name": "some-source",
|
||||
},
|
||||
],
|
||||
)
|
||||
VULN_KWARGS2 = dict(
|
||||
name="Zot",
|
||||
external_references=[
|
||||
{
|
||||
"url": "https://example2",
|
||||
"source_name": "some-source2",
|
||||
},
|
||||
],
|
||||
)
|
||||
vul1 = stix2.v21.Vulnerability(id=VULNERABILITY_ID, **VULN_KWARGS1)
|
||||
vul2 = stix2.v21.Vulnerability(id=VULNERABILITY_ID, **VULN_KWARGS2)
|
||||
env = stix2.Environment().semantically_equivalent(vul1, vul2)
|
||||
assert round(env) == 0.0
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_unknown_object():
|
||||
CUSTOM_KWARGS1 = dict(
|
||||
type="x-foobar",
|
||||
id="x-foobar--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
name="Heartbleed",
|
||||
external_references=[
|
||||
{
|
||||
"url": "https://example",
|
||||
"source_name": "some-source",
|
||||
},
|
||||
],
|
||||
)
|
||||
CUSTOM_KWARGS2 = dict(
|
||||
type="x-foobar",
|
||||
id="x-foobar--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061",
|
||||
name="Zot",
|
||||
external_references=[
|
||||
{
|
||||
"url": "https://example2",
|
||||
"source_name": "some-source2",
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
def _x_foobar_checks(obj1, obj2, **weights):
|
||||
matching_score = 0.0
|
||||
sum_weights = 0.0
|
||||
if stix2.environment.check_property_present("external_references", obj1, obj2):
|
||||
w = weights["external_references"]
|
||||
sum_weights += w
|
||||
matching_score += w * stix2.environment.partial_external_reference_based(
|
||||
obj1["external_references"],
|
||||
obj2["external_references"],
|
||||
)
|
||||
if stix2.environment.check_property_present("name", obj1, obj2):
|
||||
w = weights["name"]
|
||||
sum_weights += w
|
||||
matching_score += w * stix2.environment.partial_string_based(obj1["name"], obj2["name"])
|
||||
return matching_score, sum_weights
|
||||
|
||||
weights = {
|
||||
"x-foobar": {
|
||||
"external_references": 40,
|
||||
"name": 60,
|
||||
"method": _x_foobar_checks,
|
||||
},
|
||||
"_internal": {
|
||||
"ignore_spec_version": False,
|
||||
},
|
||||
}
|
||||
cust1 = stix2.parse(CUSTOM_KWARGS1, allow_custom=True)
|
||||
cust2 = stix2.parse(CUSTOM_KWARGS2, allow_custom=True)
|
||||
env = stix2.Environment().semantically_equivalent(cust1, cust2, **weights)
|
||||
assert round(env) == 0
|
||||
|
||||
|
||||
def test_semantic_equivalence_different_type_raises():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
vul1 = stix2.v21.Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
stix2.Environment().semantically_equivalent(vul1, ind1)
|
||||
|
||||
assert str(excinfo.value) == "The objects to compare must be of the same type!"
|
||||
|
||||
|
||||
def test_semantic_equivalence_different_spec_version_raises():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
V20_KWARGS = dict(
|
||||
labels=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
)
|
||||
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
ind2 = stix2.v20.Indicator(id=INDICATOR_ID, **V20_KWARGS)
|
||||
stix2.Environment().semantically_equivalent(ind1, ind2)
|
||||
|
||||
assert str(excinfo.value) == "The objects to compare must be of the same spec version!"
|
||||
|
||||
|
||||
def test_semantic_equivalence_zero_match():
|
||||
IND_KWARGS = dict(
|
||||
indicator_types=["APTX"],
|
||||
pattern="[ipv4-addr:value = '192.168.1.1']",
|
||||
pattern_type="stix",
|
||||
valid_from="2019-01-01T12:34:56Z",
|
||||
)
|
||||
weights = {
|
||||
"indicator": {
|
||||
"indicator_types": 15,
|
||||
"pattern": 80,
|
||||
"valid_from": 0,
|
||||
"tdelta": 1, # One day interval
|
||||
"method": stix2.environment._indicator_checks,
|
||||
},
|
||||
"_internal": {
|
||||
"ignore_spec_version": False,
|
||||
},
|
||||
}
|
||||
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
ind2 = stix2.v21.Indicator(id=INDICATOR_ID, **IND_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ind1, ind2, **weights)
|
||||
assert round(env) == 0
|
||||
|
||||
|
||||
def test_semantic_equivalence_different_spec_version():
|
||||
IND_KWARGS = dict(
|
||||
labels=["APTX"],
|
||||
pattern="[ipv4-addr:value = '192.168.1.1']",
|
||||
)
|
||||
weights = {
|
||||
"indicator": {
|
||||
"indicator_types": 15,
|
||||
"pattern": 80,
|
||||
"valid_from": 0,
|
||||
"tdelta": 1, # One day interval
|
||||
"method": stix2.environment._indicator_checks,
|
||||
},
|
||||
"_internal": {
|
||||
"ignore_spec_version": True, # Disables spec_version check.
|
||||
},
|
||||
}
|
||||
ind1 = stix2.v21.Indicator(id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
ind2 = stix2.v20.Indicator(id=INDICATOR_ID, **IND_KWARGS)
|
||||
env = stix2.Environment().semantically_equivalent(ind1, ind2, **weights)
|
||||
assert round(env) == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"refs1,refs2,ret_val", [
|
||||
(
|
||||
[
|
||||
{
|
||||
"url": "https://attack.mitre.org/techniques/T1150",
|
||||
"source_name": "mitre-attack",
|
||||
"external_id": "T1150",
|
||||
},
|
||||
{
|
||||
"url": "https://researchcenter.paloaltonetworks.com/2016/09/unit42-sofacys-komplex-os-x-trojan/",
|
||||
"source_name": "Sofacy Komplex Trojan",
|
||||
"description": "Dani Creus, Tyler Halfpop, Robert Falcone. (2016, September 26). Sofacy's 'Komplex' OS X Trojan. Retrieved July 8, 2017.",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://attack.mitre.org/techniques/T1129",
|
||||
"source_name": "mitre-attack",
|
||||
"external_id": "T1129",
|
||||
},
|
||||
{
|
||||
"url": "https://en.wikipedia.org/wiki/Microsoft_Windows_library_files",
|
||||
"source_name": "Wikipedia Windows Library Files",
|
||||
"description": "Wikipedia. (2017, January 31). Microsoft Windows library files. Retrieved February 13, 2017.",
|
||||
},
|
||||
],
|
||||
0.0,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"url": "https://attack.mitre.org/techniques/T1129",
|
||||
"source_name": "mitre-attack",
|
||||
"external_id": "T1129",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://attack.mitre.org/techniques/T1129",
|
||||
"source_name": "mitre-attack",
|
||||
"external_id": "T1129",
|
||||
},
|
||||
{
|
||||
"url": "https://en.wikipedia.org/wiki/Microsoft_Windows_library_files",
|
||||
"source_name": "Wikipedia Windows Library Files",
|
||||
"description": "Wikipedia. (2017, January 31). Microsoft Windows library files. Retrieved February 13, 2017.",
|
||||
},
|
||||
],
|
||||
1.0,
|
||||
),
|
||||
(
|
||||
[
|
||||
{
|
||||
"url": "https://example",
|
||||
"source_name": "some-source",
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
"url": "https://example",
|
||||
"source_name": "some-source",
|
||||
},
|
||||
],
|
||||
1.0,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_semantic_equivalence_external_references(refs1, refs2, ret_val):
|
||||
value = stix2.environment.partial_external_reference_based(refs1, refs2)
|
||||
assert value == ret_val
|
||||
|
||||
|
||||
def test_semantic_equivalence_timestamp():
|
||||
t1 = "2018-10-17T00:14:20.652Z"
|
||||
t2 = "2018-10-17T12:14:20.652Z"
|
||||
assert stix2.environment.partial_timestamp_based(t1, t2, 1) == 0.5
|
||||
|
||||
|
||||
def test_semantic_equivalence_exact_match():
|
||||
t1 = "2018-10-17T00:14:20.652Z"
|
||||
t2 = "2018-10-17T12:14:20.652Z"
|
||||
assert stix2.environment.exact_match(t1, t2) == 0.0
|
||||
|
||||
|
||||
def test_non_existent_config_for_object():
|
||||
r1 = stix2.v21.Report(id=REPORT_ID, **REPORT_KWARGS)
|
||||
r2 = stix2.v21.Report(id=REPORT_ID, **REPORT_KWARGS)
|
||||
assert stix2.Environment().semantically_equivalent(r1, r2) == 0.0
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from stix2 import markings
|
||||
from stix2.exceptions import MarkingNotFoundError
|
||||
from stix2.exceptions import InvalidSelectorError, MarkingNotFoundError
|
||||
from stix2.v21 import TLP_RED, Malware
|
||||
|
||||
from .constants import MALWARE_MORE_KWARGS as MALWARE_KWARGS_CONST
|
||||
|
@ -209,7 +209,7 @@ def test_add_marking_mark_same_property_same_marking():
|
|||
],
|
||||
)
|
||||
def test_add_marking_bad_selector(data, marking):
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.add_markings(data, marking[0], marking[1])
|
||||
|
||||
|
||||
|
@ -329,7 +329,7 @@ def test_get_markings_multiple_selectors(data):
|
|||
)
|
||||
def test_get_markings_bad_selector(data, selector):
|
||||
"""Test bad selectors raise exception"""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.get_markings(data, selector)
|
||||
|
||||
|
||||
|
@ -714,7 +714,7 @@ def test_remove_marking_bad_selector():
|
|||
before = {
|
||||
"description": "test description",
|
||||
}
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.remove_markings(before, ["marking-definition--1", "marking-definition--2"], ["title"])
|
||||
|
||||
|
||||
|
@ -805,7 +805,7 @@ def test_is_marked_smoke(data):
|
|||
)
|
||||
def test_is_marked_invalid_selector(data, selector):
|
||||
"""Test invalid selector raises an error."""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.is_marked(data, selectors=selector)
|
||||
|
||||
|
||||
|
@ -1000,7 +1000,7 @@ def test_is_marked_positional_arguments_combinations():
|
|||
|
||||
|
||||
def test_create_sdo_with_invalid_marking():
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(InvalidSelectorError) as excinfo:
|
||||
Malware(
|
||||
granular_markings=[
|
||||
{
|
||||
|
@ -1192,7 +1192,7 @@ def test_set_marking_bad_selector(marking):
|
|||
**MALWARE_KWARGS
|
||||
)
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
before = markings.set_markings(before, marking[0], marking[1])
|
||||
|
||||
assert before == after
|
||||
|
@ -1298,7 +1298,7 @@ def test_clear_marking_all_selectors(data):
|
|||
)
|
||||
def test_clear_marking_bad_selector(data, selector):
|
||||
"""Test bad selector raises exception."""
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(InvalidSelectorError):
|
||||
markings.clear_markings(data, selector)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, GROUPING_ID, GROUPING_KWARGS
|
||||
|
||||
EXPECTED_GROUPING = """{
|
||||
"type": "grouping",
|
||||
"spec_version": "2.1",
|
||||
"id": "grouping--753abcde-3141-5926-ace5-0a810b1ff996",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Harry Potter and the Leet Hackers",
|
||||
"context": "suspicious-activity",
|
||||
"object_refs": [
|
||||
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
|
||||
"identity--988145ed-a3b4-4421-b7a7-273376be67ce"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_grouping_with_all_required_properties():
|
||||
now = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
|
||||
grp = stix2.v21.Grouping(
|
||||
type="grouping",
|
||||
id=GROUPING_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
name="Harry Potter and the Leet Hackers",
|
||||
context="suspicious-activity",
|
||||
object_refs=[
|
||||
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
|
||||
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
|
||||
],
|
||||
)
|
||||
|
||||
assert str(grp) == EXPECTED_GROUPING
|
||||
|
||||
|
||||
def test_grouping_autogenerated_properties(grouping):
|
||||
assert grouping.type == 'grouping'
|
||||
assert grouping.id == 'grouping--00000000-0000-4000-8000-000000000001'
|
||||
assert grouping.created == FAKE_TIME
|
||||
assert grouping.modified == FAKE_TIME
|
||||
assert grouping.name == "Harry Potter and the Leet Hackers"
|
||||
assert grouping.context == "suspicious-activity"
|
||||
|
||||
assert grouping['type'] == 'grouping'
|
||||
assert grouping['id'] == 'grouping--00000000-0000-4000-8000-000000000001'
|
||||
assert grouping['created'] == FAKE_TIME
|
||||
assert grouping['modified'] == FAKE_TIME
|
||||
assert grouping['name'] == "Harry Potter and the Leet Hackers"
|
||||
assert grouping['context'] == "suspicious-activity"
|
||||
|
||||
|
||||
def test_grouping_type_must_be_grouping():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Grouping(type='xxx', **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'grouping'."
|
||||
assert str(excinfo.value) == "Invalid value for Grouping 'type': must equal 'grouping'."
|
||||
|
||||
|
||||
def test_grouping_id_must_start_with_grouping():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Grouping(id='my-prefix--', **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'grouping--'."
|
||||
assert str(excinfo.value) == "Invalid value for Grouping 'id': must start with 'grouping--'."
|
||||
|
||||
|
||||
def test_grouping_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Grouping()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.properties == ["context", "object_refs"]
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_grouping():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.v21.Grouping(my_custom_property="foo", **GROUPING_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Grouping
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Grouping: (my_custom_property)."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
EXPECTED_GROUPING,
|
||||
{
|
||||
"type": "grouping",
|
||||
"spec_version": "2.1",
|
||||
"id": GROUPING_ID,
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Harry Potter and the Leet Hackers",
|
||||
"context": "suspicious-activity",
|
||||
"object_refs": [
|
||||
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
|
||||
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_parse_grouping(data):
|
||||
grp = stix2.parse(data)
|
||||
|
||||
assert grp.type == 'grouping'
|
||||
assert grp.spec_version == '2.1'
|
||||
assert grp.id == GROUPING_ID
|
||||
assert grp.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert grp.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert grp.name == "Harry Potter and the Leet Hackers"
|
||||
assert grp.context == "suspicious-activity"
|
||||
assert grp.object_refs == [
|
||||
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
|
||||
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
|
||||
]
|
|
@ -18,6 +18,7 @@ EXPECTED_INDICATOR = """{
|
|||
"malicious-activity"
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "1970-01-01T00:00:01Z"
|
||||
}"""
|
||||
|
||||
|
@ -29,6 +30,7 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
|||
modified='2017-01-01T00:00:01.000Z',
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
pattern_type='stix',
|
||||
valid_from='1970-01-01T00:00:01Z'
|
||||
""".split()) + ")"
|
||||
|
||||
|
@ -43,6 +45,7 @@ def test_indicator_with_all_required_properties():
|
|||
created=now,
|
||||
modified=now,
|
||||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
pattern_type="stix",
|
||||
valid_from=epoch,
|
||||
indicator_types=['malicious-activity'],
|
||||
)
|
||||
|
@ -98,8 +101,8 @@ def test_indicator_required_properties():
|
|||
stix2.v21.Indicator()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.properties == ["indicator_types", "pattern"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern)."
|
||||
assert excinfo.value.properties == ["indicator_types", "pattern", "pattern_type", "valid_from"]
|
||||
assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern, pattern_type, valid_from)."
|
||||
|
||||
|
||||
def test_indicator_required_property_pattern():
|
||||
|
@ -107,7 +110,7 @@ def test_indicator_required_property_pattern():
|
|||
stix2.v21.Indicator(indicator_types=['malicious-activity'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.properties == ["pattern"]
|
||||
assert excinfo.value.properties == ["pattern", "pattern_type", "valid_from"]
|
||||
|
||||
|
||||
def test_indicator_created_ref_invalid_format():
|
||||
|
@ -116,8 +119,6 @@ def test_indicator_created_ref_invalid_format():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.prop_name == "created_by_ref"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Indicator 'created_by_ref': must start with 'identity'."
|
||||
|
||||
|
||||
def test_indicator_revoked_invalid():
|
||||
|
@ -164,6 +165,7 @@ def test_created_modified_time_are_identical_by_default():
|
|||
"malicious-activity",
|
||||
],
|
||||
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
"pattern_type": "stix",
|
||||
"valid_from": "1970-01-01T00:00:01Z",
|
||||
},
|
||||
],
|
||||
|
@ -186,6 +188,8 @@ def test_invalid_indicator_pattern():
|
|||
stix2.v21.Indicator(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern="file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e'",
|
||||
pattern_type="stix",
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
|
@ -195,6 +199,8 @@ def test_invalid_indicator_pattern():
|
|||
stix2.v21.Indicator(
|
||||
indicator_types=['malicious-activity'],
|
||||
pattern='[file:hashes.MD5 = "d41d8cd98f00b204e9800998ecf8427e"]',
|
||||
pattern_type="stix",
|
||||
valid_from="2017-01-01T12:34:56Z",
|
||||
)
|
||||
assert excinfo.value.cls == stix2.v21.Indicator
|
||||
assert excinfo.value.prop_name == 'pattern'
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
import datetime as dt
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
|
||||
from .constants import FAKE_TIME, INFRASTRUCTURE_ID, INFRASTRUCTURE_KWARGS
|
||||
|
||||
EXPECTED_INFRASTRUCTURE = """{
|
||||
"type": "infrastructure",
|
||||
"spec_version": "2.1",
|
||||
"id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018",
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"name": "Poison Ivy C2",
|
||||
"infrastructure_types": [
|
||||
"command-and-control"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_infrastructure_with_all_required_properties():
|
||||
now = dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
|
||||
infra = stix2.v21.Infrastructure(
|
||||
type="infrastructure",
|
||||
id=INFRASTRUCTURE_ID,
|
||||
created=now,
|
||||
modified=now,
|
||||
name="Poison Ivy C2",
|
||||
infrastructure_types=["command-and-control"],
|
||||
)
|
||||
|
||||
assert str(infra) == EXPECTED_INFRASTRUCTURE
|
||||
|
||||
|
||||
def test_infrastructure_autogenerated_properties(infrastructure):
|
||||
assert infrastructure.type == 'infrastructure'
|
||||
assert infrastructure.id == 'infrastructure--00000000-0000-4000-8000-000000000001'
|
||||
assert infrastructure.created == FAKE_TIME
|
||||
assert infrastructure.modified == FAKE_TIME
|
||||
assert infrastructure.infrastructure_types == ['command-and-control']
|
||||
assert infrastructure.name == "Poison Ivy C2"
|
||||
|
||||
assert infrastructure['type'] == 'infrastructure'
|
||||
assert infrastructure['id'] == 'infrastructure--00000000-0000-4000-8000-000000000001'
|
||||
assert infrastructure['created'] == FAKE_TIME
|
||||
assert infrastructure['modified'] == FAKE_TIME
|
||||
assert infrastructure['infrastructure_types'] == ['command-and-control']
|
||||
assert infrastructure['name'] == "Poison Ivy C2"
|
||||
|
||||
|
||||
def test_infrastructure_type_must_be_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(type='xxx', **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.prop_name == "type"
|
||||
assert excinfo.value.reason == "must equal 'infrastructure'."
|
||||
assert str(excinfo.value) == "Invalid value for Infrastructure 'type': must equal 'infrastructure'."
|
||||
|
||||
|
||||
def test_infrastructure_id_must_start_with_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(id='my-prefix--', **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.prop_name == "id"
|
||||
assert excinfo.value.reason == "must start with 'infrastructure--'."
|
||||
assert str(excinfo.value) == "Invalid value for Infrastructure 'id': must start with 'infrastructure--'."
|
||||
|
||||
|
||||
def test_infrastructure_required_properties():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ["infrastructure_types", "name"]
|
||||
|
||||
|
||||
def test_infrastructure_required_property_name():
|
||||
with pytest.raises(stix2.exceptions.MissingPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure(infrastructure_types=['command-and-control'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ["name"]
|
||||
|
||||
|
||||
def test_invalid_kwarg_to_infrastructure():
|
||||
with pytest.raises(stix2.exceptions.ExtraPropertiesError) as excinfo:
|
||||
stix2.v21.Infrastructure(my_custom_property="foo", **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Infrastructure
|
||||
assert excinfo.value.properties == ['my_custom_property']
|
||||
assert str(excinfo.value) == "Unexpected properties for Infrastructure: (my_custom_property)."
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
EXPECTED_INFRASTRUCTURE,
|
||||
{
|
||||
"type": "infrastructure",
|
||||
"spec_version": "2.1",
|
||||
"id": INFRASTRUCTURE_ID,
|
||||
"created": "2017-01-01T12:34:56.000Z",
|
||||
"modified": "2017-01-01T12:34:56.000Z",
|
||||
"infrastructure_types": ["command-and-control"],
|
||||
"name": "Poison Ivy C2",
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_parse_infrastructure(data):
|
||||
infra = stix2.parse(data)
|
||||
|
||||
assert infra.type == 'infrastructure'
|
||||
assert infra.spec_version == '2.1'
|
||||
assert infra.id == INFRASTRUCTURE_ID
|
||||
assert infra.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
|
||||
assert infra.infrastructure_types == ['command-and-control']
|
||||
assert infra.name == 'Poison Ivy C2'
|
||||
|
||||
|
||||
def test_parse_infrastructure_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": "reconnaissance"
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_INFRASTRUCTURE.replace('infrastructure"', 'infrastructure",%s' % kill_chain)
|
||||
infra = stix2.parse(data, version="2.1")
|
||||
assert infra.kill_chain_phases[0].kill_chain_name == "lockheed-martin-cyber-kill-chain"
|
||||
assert infra.kill_chain_phases[0].phase_name == "reconnaissance"
|
||||
assert infra['kill_chain_phases'][0]['kill_chain_name'] == "lockheed-martin-cyber-kill-chain"
|
||||
assert infra['kill_chain_phases'][0]['phase_name'] == "reconnaissance"
|
||||
|
||||
|
||||
def test_parse_infrastructure_clean_kill_chain_phases():
|
||||
kill_chain = """
|
||||
"kill_chain_phases": [
|
||||
{
|
||||
"kill_chain_name": "lockheed-martin-cyber-kill-chain",
|
||||
"phase_name": 1
|
||||
}
|
||||
]"""
|
||||
data = EXPECTED_INFRASTRUCTURE.replace('2.1"', '2.1",%s' % kill_chain)
|
||||
infra = stix2.parse(data, version="2.1")
|
||||
assert infra['kill_chain_phases'][0]['phase_name'] == "1"
|
||||
|
||||
|
||||
def test_infrastructure_invalid_last_before_first():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.Infrastructure(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **INFRASTRUCTURE_KWARGS)
|
||||
|
||||
assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value)
|
|
@ -71,3 +71,18 @@ def test_language_content_campaign():
|
|||
# or https://docs.python.org/2/library/json.html#json.dumps
|
||||
assert lc.serialize(pretty=True, ensure_ascii=False) == TEST_LANGUAGE_CONTENT
|
||||
assert lc.modified == camp.modified
|
||||
|
||||
|
||||
def test_object_modified_optional():
|
||||
"""
|
||||
object_modified is now optional in STIX 2.1.
|
||||
"""
|
||||
|
||||
stix2.v21.LanguageContent(
|
||||
object_ref=CAMPAIGN_ID,
|
||||
contents={
|
||||
"en": {
|
||||
"name": "the english text",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import pytest
|
|||
import pytz
|
||||
|
||||
import stix2
|
||||
import stix2.exceptions
|
||||
|
||||
from .constants import LOCATION_ID
|
||||
|
||||
|
@ -111,7 +112,7 @@ def test_parse_location(data):
|
|||
],
|
||||
)
|
||||
def test_location_bad_latitude(data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
|
||||
assert "Invalid value for Location 'latitude'" in str(excinfo.value)
|
||||
|
@ -140,7 +141,7 @@ def test_location_bad_latitude(data):
|
|||
],
|
||||
)
|
||||
def test_location_bad_longitude(data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
|
||||
assert "Invalid value for Location 'longitude'" in str(excinfo.value)
|
||||
|
@ -190,7 +191,7 @@ def test_location_properties_missing_when_precision_is_present(data):
|
|||
],
|
||||
)
|
||||
def test_location_negative_precision(data):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
|
||||
assert "Invalid value for Location 'precision'" in str(excinfo.value)
|
||||
|
@ -264,6 +265,15 @@ def test_location_lat_or_lon_dependency_missing(data, msg):
|
|||
assert msg in str(excinfo.value)
|
||||
|
||||
|
||||
def test_location_complex_presence_constraint():
|
||||
with pytest.raises(stix2.exceptions.PropertyPresenceError):
|
||||
stix2.parse({
|
||||
"type": "location",
|
||||
"spec_version": "2.1",
|
||||
"id": LOCATION_ID,
|
||||
})
|
||||
|
||||
|
||||
def test_google_map_url_long_lat_provided():
|
||||
expected_url = "https://www.google.com/maps/search/?api=1&query=41.862401%2C-87.616001"
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytz
|
|||
|
||||
import stix2
|
||||
|
||||
from ...exceptions import InvalidValueError, PropertyPresenceError
|
||||
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
||||
|
||||
EXPECTED_MALWARE = """{
|
||||
|
@ -17,7 +18,8 @@ EXPECTED_MALWARE = """{
|
|||
"name": "Cryptolocker",
|
||||
"malware_types": [
|
||||
"ransomware"
|
||||
]
|
||||
],
|
||||
"is_family": false
|
||||
}"""
|
||||
|
||||
|
||||
|
@ -31,6 +33,7 @@ def test_malware_with_all_required_properties():
|
|||
modified=now,
|
||||
malware_types=["ransomware"],
|
||||
name="Cryptolocker",
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
assert str(mal) == EXPECTED_MALWARE
|
||||
|
@ -77,7 +80,7 @@ def test_malware_required_properties():
|
|||
stix2.v21.Malware()
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Malware
|
||||
assert excinfo.value.properties == ["malware_types", "name"]
|
||||
assert excinfo.value.properties == ["is_family", "malware_types"]
|
||||
|
||||
|
||||
def test_malware_required_property_name():
|
||||
|
@ -85,7 +88,7 @@ def test_malware_required_property_name():
|
|||
stix2.v21.Malware(malware_types=['ransomware'])
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Malware
|
||||
assert excinfo.value.properties == ["name"]
|
||||
assert excinfo.value.properties == ["is_family"]
|
||||
|
||||
|
||||
def test_cannot_assign_to_malware_attributes(malware):
|
||||
|
@ -115,6 +118,7 @@ def test_invalid_kwarg_to_malware():
|
|||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"malware_types": ["ransomware"],
|
||||
"name": "Cryptolocker",
|
||||
"is_family": False,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -128,11 +132,12 @@ def test_parse_malware(data):
|
|||
assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
|
||||
assert mal.malware_types == ['ransomware']
|
||||
assert mal.name == 'Cryptolocker'
|
||||
assert not mal.is_family
|
||||
|
||||
|
||||
def test_parse_malware_invalid_labels():
|
||||
data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(InvalidValueError) as excinfo:
|
||||
stix2.parse(data)
|
||||
assert "Invalid value for Malware 'malware_types'" in str(excinfo.value)
|
||||
|
||||
|
@ -164,3 +169,31 @@ def test_parse_malware_clean_kill_chain_phases():
|
|||
data = EXPECTED_MALWARE.replace('2.1"', '2.1",%s' % kill_chain)
|
||||
mal = stix2.parse(data, version="2.1")
|
||||
assert mal['kill_chain_phases'][0]['phase_name'] == "1"
|
||||
|
||||
|
||||
def test_malware_invalid_last_before_first():
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.v21.Malware(first_seen="2017-01-01T12:34:56.000Z", last_seen="2017-01-01T12:33:56.000Z", **MALWARE_KWARGS)
|
||||
|
||||
assert "'last_seen' must be greater than or equal to 'first_seen'" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_malware_family_no_name():
|
||||
with pytest.raises(PropertyPresenceError):
|
||||
stix2.parse({
|
||||
"type": "malware",
|
||||
"id": MALWARE_ID,
|
||||
"spec_version": "2.1",
|
||||
"is_family": True,
|
||||
"malware_types": ["a type"],
|
||||
})
|
||||
|
||||
|
||||
def test_malware_non_family_no_name():
|
||||
stix2.parse({
|
||||
"type": "malware",
|
||||
"id": MALWARE_ID,
|
||||
"spec_version": "2.1",
|
||||
"is_family": False,
|
||||
"malware_types": ["something"],
|
||||
})
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2.exceptions
|
||||
import stix2.utils
|
||||
import stix2.v21
|
||||
|
||||
MALWARE_ANALYSIS_JSON = """{
|
||||
"type": "malware-analysis",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware-analysis--f8afc020-f92f-4906-a971-88ee5882eb46",
|
||||
"created": "2017-11-28T09:44:58.418Z",
|
||||
"modified": "2017-12-31T21:27:49.754Z",
|
||||
"created_by_ref": "identity--e0353ed3-991e-4f71-a332-114c2f10b84f",
|
||||
"labels": [
|
||||
"label1",
|
||||
"label2"
|
||||
],
|
||||
"product": "Acme Malware Analyzer",
|
||||
"version": "2.5",
|
||||
"host_vm_ref": "software--1bda7336-fe67-469f-a8ca-ab6268b0449b",
|
||||
"operating_system_ref": "software--c96bfaef-861b-408b-b0f1-b685881725ef",
|
||||
"installed_software_refs": [
|
||||
"software--7325bf2d-de9e-441e-b3b3-63df43149897",
|
||||
"software--46a6a91d-1160-4867-a4d1-b14e080e4e5b"
|
||||
],
|
||||
"configuration_version": "1.7",
|
||||
"modules": [
|
||||
"Super Analyzer"
|
||||
],
|
||||
"analysis_engine_version": "1.2",
|
||||
"analysis_definition_version": "3.4",
|
||||
"submitted": "2018-11-23T06:45:55.747Z",
|
||||
"analysis_started": "2018-11-29T07:30:03.895Z",
|
||||
"analysis_ended": "2018-11-29T08:30:03.895Z",
|
||||
"av_result": "malicious",
|
||||
"analysis_sco_refs": [
|
||||
"file--fc27e371-6c88-4c5c-868a-4dda0e60b167",
|
||||
"url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
MALWARE_ANALYSIS_DICT = json.loads(MALWARE_ANALYSIS_JSON)
|
||||
|
||||
|
||||
def test_malware_analysis_example():
|
||||
ma = stix2.v21.MalwareAnalysis(**MALWARE_ANALYSIS_DICT)
|
||||
|
||||
assert str(ma) == MALWARE_ANALYSIS_JSON
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
MALWARE_ANALYSIS_JSON,
|
||||
MALWARE_ANALYSIS_DICT,
|
||||
],
|
||||
)
|
||||
def test_parse_malware_analysis(data):
|
||||
ma = stix2.parse(data, version="2.1")
|
||||
|
||||
# timestamp-valued attributes whose values (from JSON) can't be compared
|
||||
# directly, since stix2 internally converts them to datetime objects.
|
||||
ts_attrs = {
|
||||
"created",
|
||||
"modified",
|
||||
"submitted",
|
||||
"analysis_started",
|
||||
"analysis_ended",
|
||||
}
|
||||
|
||||
for attr_name, attr_value in MALWARE_ANALYSIS_DICT.items():
|
||||
cmp_value = stix2.utils.parse_into_datetime(attr_value) \
|
||||
if attr_name in ts_attrs else attr_value
|
||||
|
||||
assert getattr(ma, attr_name) == cmp_value
|
||||
|
||||
|
||||
def test_malware_analysis_constraint():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError):
|
||||
stix2.v21.MalwareAnalysis(
|
||||
product="Acme Malware Analyzer",
|
||||
)
|
|
@ -3,6 +3,7 @@ import pytest
|
|||
from stix2 import exceptions, markings
|
||||
from stix2.v21 import TLP_AMBER, Malware
|
||||
|
||||
from ...exceptions import MarkingNotFoundError
|
||||
from .constants import FAKE_TIME, MALWARE_ID
|
||||
from .constants import MALWARE_KWARGS as MALWARE_KWARGS_CONST
|
||||
from .constants import MARKING_IDS
|
||||
|
@ -349,7 +350,7 @@ def test_remove_markings_bad_markings():
|
|||
object_marking_refs=[MARKING_IDS[0], MARKING_IDS[1], MARKING_IDS[2]],
|
||||
**MALWARE_KWARGS
|
||||
)
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(MarkingNotFoundError) as excinfo:
|
||||
markings.remove_markings(before, [MARKING_IDS[4]], None)
|
||||
assert str(excinfo.value) == "Marking ['%s'] was not found in Malware!" % MARKING_IDS[4]
|
||||
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import datetime as dt
|
||||
import re
|
||||
import uuid
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
||||
import stix2
|
||||
import stix2.exceptions
|
||||
|
||||
from .constants import IDENTITY_ID, OBSERVED_DATA_ID
|
||||
|
||||
|
@ -24,6 +26,8 @@ EXPECTED = """{
|
|||
"objects": {
|
||||
"0": {
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
"spec_version": "2.1",
|
||||
"name": "foo.exe"
|
||||
}
|
||||
}
|
||||
|
@ -41,13 +45,19 @@ def test_observed_data_example():
|
|||
number_observed=50,
|
||||
objects={
|
||||
"0": {
|
||||
"name": "foo.exe",
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
"name": "foo.exe",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert str(observed_data) == EXPECTED
|
||||
assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"
|
||||
assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
||||
assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
||||
assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc)
|
||||
assert observed_data.number_observed == 50
|
||||
assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe")
|
||||
|
||||
|
||||
EXPECTED_WITH_REF = """{
|
||||
|
@ -63,13 +73,17 @@ EXPECTED_WITH_REF = """{
|
|||
"objects": {
|
||||
"0": {
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
"spec_version": "2.1",
|
||||
"name": "foo.exe"
|
||||
},
|
||||
"1": {
|
||||
"type": "directory",
|
||||
"id": "directory--536a61a4-0934-516b-9aad-fcbb75e0583a",
|
||||
"spec_version": "2.1",
|
||||
"path": "/usr/home",
|
||||
"contains_refs": [
|
||||
"0"
|
||||
"file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -87,18 +101,86 @@ def test_observed_data_example_with_refs():
|
|||
number_observed=50,
|
||||
objects={
|
||||
"0": {
|
||||
"name": "foo.exe",
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
"name": "foo.exe",
|
||||
},
|
||||
"1": {
|
||||
"type": "directory",
|
||||
"id": "directory--536a61a4-0934-516b-9aad-fcbb75e0583a",
|
||||
"path": "/usr/home",
|
||||
"contains_refs": ["0"],
|
||||
"contains_refs": ["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"],
|
||||
},
|
||||
},
|
||||
)
|
||||
assert observed_data.id == "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"
|
||||
assert observed_data.created_by_ref == "identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
||||
assert observed_data.created == observed_data.modified == dt.datetime(2016, 4, 6, 19, 58, 16, tzinfo=pytz.utc)
|
||||
assert observed_data.first_observed == observed_data.last_observed == dt.datetime(2015, 12, 21, 19, 00, 00, tzinfo=pytz.utc)
|
||||
assert observed_data.number_observed == 50
|
||||
assert observed_data.objects['0'] == stix2.v21.File(name="foo.exe")
|
||||
assert observed_data.objects['1'] == stix2.v21.Directory(path="/usr/home", contains_refs=["file--5956efbb-a7b0-566d-a7f9-a202eb05c70f"])
|
||||
|
||||
assert str(observed_data) == EXPECTED_WITH_REF
|
||||
|
||||
EXPECTED_OBJECT_REFS = """{
|
||||
"type": "observed-data",
|
||||
"spec_version": "2.1",
|
||||
"id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
"created": "2016-04-06T19:58:16.000Z",
|
||||
"modified": "2016-04-06T19:58:16.000Z",
|
||||
"first_observed": "2015-12-21T19:00:00Z",
|
||||
"last_observed": "2015-12-21T19:00:00Z",
|
||||
"number_observed": 50,
|
||||
"object_refs": [
|
||||
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
||||
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
||||
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
def test_observed_data_example_with_object_refs():
|
||||
observed_data = stix2.v21.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16.000Z",
|
||||
modified="2016-04-06T19:58:16.000Z",
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=50,
|
||||
object_refs=[
|
||||
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
||||
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
||||
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c",
|
||||
],
|
||||
)
|
||||
|
||||
assert str(observed_data) == EXPECTED_OBJECT_REFS
|
||||
|
||||
|
||||
def test_observed_data_object_constraint():
|
||||
with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError):
|
||||
stix2.v21.ObservedData(
|
||||
id="observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf",
|
||||
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||
created="2016-04-06T19:58:16.000Z",
|
||||
modified="2016-04-06T19:58:16.000Z",
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=50,
|
||||
objects={
|
||||
"0": {
|
||||
"name": "foo.exe",
|
||||
"type": "file",
|
||||
},
|
||||
},
|
||||
object_refs=[
|
||||
"file--758bf2c0-a6f1-56d1-872e-6b727467739a",
|
||||
"url--d97ed5c4-3f33-46d9-b25b-c3d7b94d1457",
|
||||
"mutex--eca0b3ba-8d76-11e9-a1fd-34415dabec0c",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_observed_data_example_with_bad_refs():
|
||||
|
@ -114,19 +196,20 @@ def test_observed_data_example_with_bad_refs():
|
|||
objects={
|
||||
"0": {
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
"name": "foo.exe",
|
||||
},
|
||||
"1": {
|
||||
"type": "directory",
|
||||
"path": "/usr/home",
|
||||
"contains_refs": ["2"],
|
||||
"contains_refs": ["monkey--5956efbb-a7b0-566d-a7f9-a202eb05c70f"],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.ObservedData
|
||||
assert excinfo.value.prop_name == "objects"
|
||||
assert excinfo.value.reason == "Invalid object reference for 'Directory:contains_refs': '2' is not a valid object in local scope"
|
||||
assert excinfo.value.cls == stix2.v21.Directory
|
||||
assert excinfo.value.prop_name == "contains_refs"
|
||||
assert "The type-specifying prefix 'monkey--' for this property is not valid" in excinfo.value.reason
|
||||
|
||||
|
||||
def test_observed_data_example_with_non_dictionary():
|
||||
|
@ -182,6 +265,7 @@ def test_observed_data_example_with_empty_dictionary():
|
|||
"0": {
|
||||
"name": "foo.exe",
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -243,7 +327,7 @@ def test_parse_artifact_valid(data):
|
|||
)
|
||||
def test_parse_artifact_invalid(data):
|
||||
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.parse(odata_str, version="2.1")
|
||||
|
||||
|
||||
|
@ -280,17 +364,21 @@ def test_parse_autonomous_system_valid(data):
|
|||
"type": "email-addr",
|
||||
"value": "john@example.com",
|
||||
"display_name": "John Doe",
|
||||
"belongs_to_ref": "0"
|
||||
"belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91"
|
||||
}""",
|
||||
],
|
||||
)
|
||||
def test_parse_email_address(data):
|
||||
odata = stix2.parse_observable(data, {"0": "user-account"}, version='2.1')
|
||||
odata = stix2.parse_observable(data, version='2.1')
|
||||
assert odata.type == "email-addr"
|
||||
|
||||
odata_str = re.compile('"belongs_to_ref": "0"', re.DOTALL).sub('"belongs_to_ref": "3"', data)
|
||||
with pytest.raises(stix2.exceptions.InvalidObjRefError):
|
||||
stix2.parse_observable(odata_str, {"0": "user-account"}, version='2.1')
|
||||
odata_str = re.compile(
|
||||
'"belongs_to_ref": "user-account--fc07c1af-6b11-41f8-97a4-47920d866a91"', re.DOTALL,
|
||||
).sub(
|
||||
'"belongs_to_ref": "mutex--9be6365f-b89c-48c0-9340-6953f6595718"', data,
|
||||
)
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.parse_observable(odata_str, version='2.1')
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -301,12 +389,12 @@ def test_parse_email_address(data):
|
|||
"is_multipart": true,
|
||||
"content_type": "multipart/mixed",
|
||||
"date": "2016-06-19T14:20:40.000Z",
|
||||
"from_ref": "1",
|
||||
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
||||
"to_refs": [
|
||||
"2"
|
||||
"email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"
|
||||
],
|
||||
"cc_refs": [
|
||||
"3"
|
||||
"email-addr--1766f860-5cf3-5697-8789-35f1242663d5"
|
||||
],
|
||||
"subject": "Check out this picture of a cat!",
|
||||
"additional_header_fields": {
|
||||
|
@ -323,12 +411,12 @@ def test_parse_email_address(data):
|
|||
{
|
||||
"content_type": "image/png",
|
||||
"content_disposition": "attachment; filename=\\"tabby.png\\"",
|
||||
"body_raw_ref": "4"
|
||||
"body_raw_ref": "artifact--80b04ad8-db52-464b-a85a-a44a5f3a60c5"
|
||||
},
|
||||
{
|
||||
"content_type": "application/zip",
|
||||
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
|
||||
"body_raw_ref": "5"
|
||||
"body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -336,15 +424,7 @@ def test_parse_email_address(data):
|
|||
],
|
||||
)
|
||||
def test_parse_email_message(data):
|
||||
valid_refs = {
|
||||
"0": "email-message",
|
||||
"1": "email-addr",
|
||||
"2": "email-addr",
|
||||
"3": "email-addr",
|
||||
"4": "artifact",
|
||||
"5": "file",
|
||||
}
|
||||
odata = stix2.parse_observable(data, valid_refs, version='2.1')
|
||||
odata = stix2.parse_observable(data, version='2.1')
|
||||
assert odata.type == "email-message"
|
||||
assert odata.body_multipart[0].content_disposition == "inline"
|
||||
|
||||
|
@ -354,8 +434,8 @@ def test_parse_email_message(data):
|
|||
"""
|
||||
{
|
||||
"type": "email-message",
|
||||
"from_ref": "0",
|
||||
"to_refs": ["1"],
|
||||
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
||||
"to_refs": ["email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"],
|
||||
"is_multipart": true,
|
||||
"date": "1997-11-21T15:55:06.000Z",
|
||||
"subject": "Saying Hello",
|
||||
|
@ -365,12 +445,8 @@ def test_parse_email_message(data):
|
|||
],
|
||||
)
|
||||
def test_parse_email_message_not_multipart(data):
|
||||
valid_refs = {
|
||||
"0": "email-addr",
|
||||
"1": "email-addr",
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.DependentPropertiesError) as excinfo:
|
||||
stix2.parse_observable(data, valid_refs, version='2.1')
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.EmailMessage
|
||||
assert excinfo.value.dependencies == [("is_multipart", "body")]
|
||||
|
@ -380,35 +456,38 @@ def test_parse_email_message_not_multipart(data):
|
|||
"data", [
|
||||
""""0": {
|
||||
"type": "file",
|
||||
"id": "file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
||||
"hashes": {
|
||||
"SHA-256": "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
|
||||
}
|
||||
},
|
||||
"1": {
|
||||
"type": "file",
|
||||
"id": "file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
||||
"hashes": {
|
||||
"SHA-256": "19c549ec2628b989382f6b280cbd7bb836a0b461332c0fe53511ce7d584b89d3"
|
||||
"SHA-1": "6e71b3cac15d32fe2d36c270887df9479c25c640"
|
||||
}
|
||||
},
|
||||
"2": {
|
||||
"type": "file",
|
||||
"id": "file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
||||
"hashes": {
|
||||
"SHA-256": "0969de02ecf8a5f003e3f6d063d848c8a193aada092623f8ce408c15bcb5f038"
|
||||
"SHA-512": "b7e98c78c24fb4c2c7b175e90474b21eae0ccf1b5ea4708b4e0f2d2940004419edc7161c18a1e71b2565df099ba017bcaa67a248e2989b6268ce078b88f2e210"
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"type": "file",
|
||||
"name": "foo.zip",
|
||||
"hashes": {
|
||||
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
||||
"SHA3-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
||||
},
|
||||
"mime_type": "application/zip",
|
||||
"extensions": {
|
||||
"archive-ext": {
|
||||
"contains_refs": [
|
||||
"0",
|
||||
"1",
|
||||
"2"
|
||||
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
||||
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
||||
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -419,7 +498,11 @@ def test_parse_file_archive(data):
|
|||
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
||||
odata = stix2.parse(odata_str, version="2.1")
|
||||
assert all(x in odata.objects["3"].extensions['archive-ext'].contains_refs
|
||||
for x in ["0", "1", "2"])
|
||||
for x in [
|
||||
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
||||
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
||||
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -430,12 +513,12 @@ def test_parse_file_archive(data):
|
|||
"is_multipart": true,
|
||||
"content_type": "multipart/mixed",
|
||||
"date": "2016-06-19T14:20:40.000Z",
|
||||
"from_ref": "1",
|
||||
"from_ref": "email-addr--d4ef7e1f-086d-5ff4-bce4-312ddc3eae76",
|
||||
"to_refs": [
|
||||
"2"
|
||||
"email-addr--8b0eb924-208c-5efd-80e5-84e2d610e54b"
|
||||
],
|
||||
"cc_refs": [
|
||||
"3"
|
||||
"email-addr--1766f860-5cf3-5697-8789-35f1242663d5"
|
||||
],
|
||||
"subject": "Check out this picture of a cat!",
|
||||
"additional_header_fields": {
|
||||
|
@ -456,7 +539,7 @@ def test_parse_file_archive(data):
|
|||
{
|
||||
"content_type": "application/zip",
|
||||
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
|
||||
"body_raw_ref": "5"
|
||||
"body_raw_ref": "file--e63474fc-b386-5630-a003-1b555e22f99b"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -464,19 +547,10 @@ def test_parse_file_archive(data):
|
|||
],
|
||||
)
|
||||
def test_parse_email_message_with_at_least_one_error(data):
|
||||
valid_refs = {
|
||||
"0": "email-message",
|
||||
"1": "email-addr",
|
||||
"2": "email-addr",
|
||||
"3": "email-addr",
|
||||
"4": "artifact",
|
||||
"5": "file",
|
||||
}
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
stix2.parse_observable(data, valid_refs, version='2.1')
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.EmailMIMEComponent
|
||||
assert excinfo.value.properties == ["body", "body_raw_ref"]
|
||||
assert excinfo.value.cls == stix2.v21.EmailMessage
|
||||
assert "At least one of the" in str(excinfo.value)
|
||||
assert "must be populated" in str(excinfo.value)
|
||||
|
||||
|
@ -486,8 +560,8 @@ def test_parse_email_message_with_at_least_one_error(data):
|
|||
"""
|
||||
{
|
||||
"type": "network-traffic",
|
||||
"src_ref": "0",
|
||||
"dst_ref": "1",
|
||||
"src_ref": "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c",
|
||||
"dst_ref": "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483",
|
||||
"protocols": [
|
||||
"tcp"
|
||||
]
|
||||
|
@ -497,13 +571,12 @@ def test_parse_email_message_with_at_least_one_error(data):
|
|||
)
|
||||
def test_parse_basic_tcp_traffic(data):
|
||||
odata = stix2.parse_observable(
|
||||
data, {"0": "ipv4-addr", "1": "ipv4-addr"},
|
||||
version='2.1',
|
||||
data, version='2.1',
|
||||
)
|
||||
|
||||
assert odata.type == "network-traffic"
|
||||
assert odata.src_ref == "0"
|
||||
assert odata.dst_ref == "1"
|
||||
assert odata.src_ref == "ipv4-addr--e535b017-cc1c-566b-a3e2-f69f92ed9c4c"
|
||||
assert odata.dst_ref == "ipv4-addr--78327430-9ad9-5632-ae3d-8e2fce8f5483"
|
||||
assert odata.protocols == ["tcp"]
|
||||
|
||||
|
||||
|
@ -521,7 +594,7 @@ def test_parse_basic_tcp_traffic(data):
|
|||
"src_byte_count": 35779,
|
||||
"dst_byte_count": 935750,
|
||||
"encapsulates_refs": [
|
||||
"4"
|
||||
"network-traffic--016914c3-b680-5df2-81c4-bb9ccf8dc8b0"
|
||||
]
|
||||
}
|
||||
""",
|
||||
|
@ -529,7 +602,7 @@ def test_parse_basic_tcp_traffic(data):
|
|||
)
|
||||
def test_parse_basic_tcp_traffic_with_error(data):
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
stix2.parse_observable(data, {"4": "network-traffic"}, version='2.1')
|
||||
stix2.parse_observable(data, version='2.1')
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.NetworkTraffic
|
||||
assert excinfo.value.properties == ["dst_ref", "src_ref"]
|
||||
|
@ -575,16 +648,18 @@ def test_observed_data_with_process_example():
|
|||
objects={
|
||||
"0": {
|
||||
"type": "file",
|
||||
"id": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea",
|
||||
"hashes": {
|
||||
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f",
|
||||
},
|
||||
},
|
||||
"1": {
|
||||
"type": "process",
|
||||
"id": "process--f6c4a02c-23e1-4a6d-a0d7-d862e893817a",
|
||||
"pid": 1221,
|
||||
"created": "2016-01-20T14:11:25.55Z",
|
||||
"created_time": "2016-01-20T14:11:25.55Z",
|
||||
"command_line": "./gedit-bin --new-window",
|
||||
"image_ref": "0",
|
||||
"image_ref": "file--0d16c8d3-c177-5f5d-a022-b1bdac329bea",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -628,31 +703,33 @@ def test_artifact_mutual_exclusion_error():
|
|||
|
||||
|
||||
def test_directory_example():
|
||||
dir = stix2.v21.Directory(
|
||||
_valid_refs={"1": "file"},
|
||||
path='/usr/lib',
|
||||
created="2015-12-21T19:00:00Z",
|
||||
modified="2015-12-24T19:00:00Z",
|
||||
accessed="2015-12-21T20:00:00Z",
|
||||
contains_refs=["1"],
|
||||
f = stix2.v21.File(
|
||||
name="penguin.exe",
|
||||
)
|
||||
|
||||
assert dir.path == '/usr/lib'
|
||||
assert dir.created == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir.modified == dt.datetime(2015, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir.accessed == dt.datetime(2015, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir.contains_refs == ["1"]
|
||||
dir1 = stix2.v21.Directory(
|
||||
path='/usr/lib',
|
||||
ctime="2015-12-21T19:00:00Z",
|
||||
mtime="2015-12-24T19:00:00Z",
|
||||
atime="2015-12-21T20:00:00Z",
|
||||
contains_refs=[str(f.id)],
|
||||
)
|
||||
|
||||
assert dir1.path == '/usr/lib'
|
||||
assert dir1.ctime == dt.datetime(2015, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir1.mtime == dt.datetime(2015, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir1.atime == dt.datetime(2015, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
||||
assert dir1.contains_refs == ["file--9d050a3b-72cd-5b57-bf18-024e74e1e5eb"]
|
||||
|
||||
|
||||
def test_directory_example_ref_error():
|
||||
with pytest.raises(stix2.exceptions.InvalidObjRefError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Directory(
|
||||
_valid_refs=[],
|
||||
path='/usr/lib',
|
||||
created="2015-12-21T19:00:00Z",
|
||||
modified="2015-12-24T19:00:00Z",
|
||||
accessed="2015-12-21T20:00:00Z",
|
||||
contains_refs=["1"],
|
||||
ctime="2015-12-21T19:00:00Z",
|
||||
mtime="2015-12-24T19:00:00Z",
|
||||
atime="2015-12-21T20:00:00Z",
|
||||
contains_refs=["domain-name--02af94ea-7e38-5718-87c3-5cc023e3d49d"],
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Directory
|
||||
|
@ -660,22 +737,24 @@ def test_directory_example_ref_error():
|
|||
|
||||
|
||||
def test_domain_name_example():
|
||||
dn = stix2.v21.DomainName(
|
||||
_valid_refs={"1": 'domain-name'},
|
||||
value="example.com",
|
||||
resolves_to_refs=["1"],
|
||||
dn1 = stix2.v21.DomainName(
|
||||
value="mitre.org",
|
||||
)
|
||||
|
||||
assert dn.value == "example.com"
|
||||
assert dn.resolves_to_refs == ["1"]
|
||||
dn2 = stix2.v21.DomainName(
|
||||
value="example.com",
|
||||
resolves_to_refs=[str(dn1.id)],
|
||||
)
|
||||
|
||||
assert dn2.value == "example.com"
|
||||
assert dn2.resolves_to_refs == ["domain-name--02af94ea-7e38-5718-87c3-5cc023e3d49d"]
|
||||
|
||||
|
||||
def test_domain_name_example_invalid_ref_type():
|
||||
with pytest.raises(stix2.exceptions.InvalidObjRefError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.DomainName(
|
||||
_valid_refs={"1": "file"},
|
||||
value="example.com",
|
||||
resolves_to_refs=["1"],
|
||||
resolves_to_refs=["file--44a431e6-764b-5556-a3f5-bf655930a581"],
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.DomainName
|
||||
|
@ -691,9 +770,9 @@ def test_file_example():
|
|||
size=100,
|
||||
magic_number_hex="1C",
|
||||
mime_type="application/msword",
|
||||
created="2016-12-21T19:00:00Z",
|
||||
modified="2016-12-24T19:00:00Z",
|
||||
accessed="2016-12-21T20:00:00Z",
|
||||
ctime="2016-12-21T19:00:00Z",
|
||||
mtime="2016-12-24T19:00:00Z",
|
||||
atime="2016-12-21T20:00:00Z",
|
||||
)
|
||||
|
||||
assert f.name == "qwerty.dll"
|
||||
|
@ -701,9 +780,9 @@ def test_file_example():
|
|||
assert f.magic_number_hex == "1C"
|
||||
assert f.hashes["SHA-256"] == "ceafbfd424be2ca4a5f0402cae090dda2fb0526cf521b60b60077c0f622b285a"
|
||||
assert f.mime_type == "application/msword"
|
||||
assert f.created == dt.datetime(2016, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert f.modified == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert f.accessed == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
||||
assert f.ctime == dt.datetime(2016, 12, 21, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert f.mtime == dt.datetime(2016, 12, 24, 19, 0, 0, tzinfo=pytz.utc)
|
||||
assert f.atime == dt.datetime(2016, 12, 21, 20, 0, 0, tzinfo=pytz.utc)
|
||||
|
||||
|
||||
def test_file_example_with_NTFSExt():
|
||||
|
@ -726,7 +805,7 @@ def test_file_example_with_NTFSExt():
|
|||
|
||||
|
||||
def test_file_example_with_empty_NTFSExt():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.File(
|
||||
name="abc.txt",
|
||||
extensions={
|
||||
|
@ -734,8 +813,7 @@ def test_file_example_with_empty_NTFSExt():
|
|||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.NTFSExt
|
||||
assert excinfo.value.properties == sorted(list(stix2.NTFSExt._properties.keys()))
|
||||
assert excinfo.value.cls == stix2.v21.File
|
||||
|
||||
|
||||
def test_file_example_with_PDFExt():
|
||||
|
@ -818,6 +896,7 @@ RASTER_IMAGE_EXT = """{
|
|||
"objects": {
|
||||
"0": {
|
||||
"type": "file",
|
||||
"id": "file--44a431e6-764b-5556-a3f5-bf655930a581",
|
||||
"name": "picture.jpg",
|
||||
"hashes": {
|
||||
"SHA-256": "35a01331e9ad96f751278b891b6ea09699806faedfa237d40513d92ad1b7100f"
|
||||
|
@ -929,18 +1008,17 @@ def test_file_example_encryption_error():
|
|||
assert "At least one of the (hashes, name)" in str(excinfo.value)
|
||||
|
||||
|
||||
def test_ip4_address_example():
|
||||
def test_ipv4_address_example():
|
||||
ip4 = stix2.v21.IPv4Address(
|
||||
_valid_refs={"4": "mac-addr", "5": "mac-addr"},
|
||||
value="198.51.100.3",
|
||||
resolves_to_refs=["4", "5"],
|
||||
resolves_to_refs=["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"],
|
||||
)
|
||||
|
||||
assert ip4.value == "198.51.100.3"
|
||||
assert ip4.resolves_to_refs == ["4", "5"]
|
||||
assert ip4.resolves_to_refs == ["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"]
|
||||
|
||||
|
||||
def test_ip4_address_valid_refs():
|
||||
def test_ipv4_address_valid_refs():
|
||||
mac1 = stix2.v21.MACAddress(
|
||||
value="a1:b2:c3:d4:e5:f6",
|
||||
)
|
||||
|
@ -949,22 +1027,21 @@ def test_ip4_address_valid_refs():
|
|||
)
|
||||
|
||||
ip4 = stix2.v21.IPv4Address(
|
||||
_valid_refs={"1": mac1, "2": mac2},
|
||||
value="177.60.40.7",
|
||||
resolves_to_refs=["1", "2"],
|
||||
resolves_to_refs=[str(mac1.id), str(mac2.id)],
|
||||
)
|
||||
|
||||
assert ip4.value == "177.60.40.7"
|
||||
assert ip4.resolves_to_refs == ["1", "2"]
|
||||
assert ip4.resolves_to_refs == ["mac-addr--a85820f7-d9b7-567a-a3a6-dedc34139342", "mac-addr--9a59b496-fdeb-510f-97b5-7137210bc699"]
|
||||
|
||||
|
||||
def test_ip4_address_example_cidr():
|
||||
def test_ipv4_address_example_cidr():
|
||||
ip4 = stix2.v21.IPv4Address(value="198.51.100.0/24")
|
||||
|
||||
assert ip4.value == "198.51.100.0/24"
|
||||
|
||||
|
||||
def test_ip6_address_example():
|
||||
def test_ipv6_address_example():
|
||||
ip6 = stix2.v21.IPv6Address(value="2001:0db8:85a3:0000:0000:8a2e:0370:7334")
|
||||
|
||||
assert ip6.value == "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
|
||||
|
@ -978,14 +1055,13 @@ def test_mac_address_example():
|
|||
|
||||
def test_network_traffic_example():
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
_valid_refs={"0": "ipv4-addr", "1": "ipv4-addr"},
|
||||
protocols="tcp",
|
||||
src_ref="0",
|
||||
dst_ref="1",
|
||||
protocols=["tcp"],
|
||||
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
||||
dst_ref="ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb",
|
||||
)
|
||||
assert nt.protocols == ["tcp"]
|
||||
assert nt.src_ref == "0"
|
||||
assert nt.dst_ref == "1"
|
||||
assert nt.src_ref == "ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88"
|
||||
assert nt.dst_ref == "ipv4-addr--6d39dd0b-1f74-5faf-8d76-d8762c2a57cb"
|
||||
|
||||
|
||||
def test_network_traffic_http_request_example():
|
||||
|
@ -1000,9 +1076,8 @@ def test_network_traffic_http_request_example():
|
|||
},
|
||||
)
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
_valid_refs={"0": "ipv4-addr"},
|
||||
protocols="tcp",
|
||||
src_ref="0",
|
||||
protocols=["tcp"],
|
||||
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
||||
extensions={'http-request-ext': h},
|
||||
)
|
||||
assert nt.extensions['http-request-ext'].request_method == "get"
|
||||
|
@ -1016,9 +1091,8 @@ def test_network_traffic_http_request_example():
|
|||
def test_network_traffic_icmp_example():
|
||||
h = stix2.v21.ICMPExt(icmp_type_hex="08", icmp_code_hex="00")
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
_valid_refs={"0": "ipv4-addr"},
|
||||
protocols="tcp",
|
||||
src_ref="0",
|
||||
protocols=["tcp"],
|
||||
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
||||
extensions={'icmp-ext': h},
|
||||
)
|
||||
assert nt.extensions['icmp-ext'].icmp_type_hex == "08"
|
||||
|
@ -1033,9 +1107,8 @@ def test_network_traffic_socket_example():
|
|||
socket_type="SOCK_STREAM",
|
||||
)
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
_valid_refs={"0": "ipv4-addr"},
|
||||
protocols="tcp",
|
||||
src_ref="0",
|
||||
protocols=["tcp"],
|
||||
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
||||
extensions={'socket-ext': h},
|
||||
)
|
||||
assert nt.extensions['socket-ext'].is_listening
|
||||
|
@ -1047,9 +1120,8 @@ def test_network_traffic_socket_example():
|
|||
def test_network_traffic_tcp_example():
|
||||
h = stix2.v21.TCPExt(src_flags_hex="00000002")
|
||||
nt = stix2.v21.NetworkTraffic(
|
||||
_valid_refs={"0": "ipv4-addr"},
|
||||
protocols="tcp",
|
||||
src_ref="0",
|
||||
protocols=["tcp"],
|
||||
src_ref="ipv4-addr--29a591d9-533a-5ecd-a5a1-cadee4411e88",
|
||||
extensions={'tcp-ext': h},
|
||||
)
|
||||
assert nt.extensions['tcp-ext'].src_flags_hex == "00000002"
|
||||
|
@ -1063,11 +1135,10 @@ def test_mutex_example():
|
|||
|
||||
def test_process_example():
|
||||
p = stix2.v21.Process(
|
||||
_valid_refs={"0": "file"},
|
||||
pid=1221,
|
||||
created="2016-01-20T14:11:25.55Z",
|
||||
created_time="2016-01-20T14:11:25.55Z",
|
||||
command_line="./gedit-bin --new-window",
|
||||
image_ref="0",
|
||||
image_ref="file--ea587d87-5ed2-5625-a9ac-01fd64161fd8",
|
||||
)
|
||||
|
||||
assert p.command_line == "./gedit-bin --new-window"
|
||||
|
@ -1079,7 +1150,7 @@ def test_process_example_empty_error():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.Process
|
||||
properties_of_process = list(stix2.v21.Process._properties.keys())
|
||||
properties_of_process.remove("type")
|
||||
properties_of_process = [prop for prop in properties_of_process if prop not in ["type", "id", "defanged", "spec_version"]]
|
||||
assert excinfo.value.properties == sorted(properties_of_process)
|
||||
msg = "At least one of the ({1}) properties for {0} must be populated."
|
||||
msg = msg.format(
|
||||
|
@ -1090,14 +1161,12 @@ def test_process_example_empty_error():
|
|||
|
||||
|
||||
def test_process_example_empty_with_extensions():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Process(extensions={
|
||||
"windows-process-ext": {},
|
||||
})
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.WindowsProcessExt
|
||||
properties_of_extension = list(stix2.v21.WindowsProcessExt._properties.keys())
|
||||
assert excinfo.value.properties == sorted(properties_of_extension)
|
||||
assert excinfo.value.cls == stix2.v21.Process
|
||||
|
||||
|
||||
def test_process_example_windows_process_ext():
|
||||
|
@ -1119,7 +1188,7 @@ def test_process_example_windows_process_ext():
|
|||
|
||||
|
||||
def test_process_example_windows_process_ext_empty():
|
||||
with pytest.raises(stix2.exceptions.AtLeastOnePropertyError) as excinfo:
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Process(
|
||||
pid=1221,
|
||||
extensions={
|
||||
|
@ -1127,9 +1196,7 @@ def test_process_example_windows_process_ext_empty():
|
|||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.WindowsProcessExt
|
||||
properties_of_extension = list(stix2.v21.WindowsProcessExt._properties.keys())
|
||||
assert excinfo.value.properties == sorted(properties_of_extension)
|
||||
assert excinfo.value.cls == stix2.v21.Process
|
||||
|
||||
|
||||
def test_process_example_extensions_empty():
|
||||
|
@ -1262,7 +1329,7 @@ def test_user_account_unix_account_ext_example():
|
|||
|
||||
|
||||
def test_windows_registry_key_example():
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError):
|
||||
stix2.v21.WindowsRegistryValueType(
|
||||
name="Foo",
|
||||
data="qwerty",
|
||||
|
@ -1307,15 +1374,155 @@ def test_new_version_with_related_objects():
|
|||
objects={
|
||||
'src_ip': {
|
||||
'type': 'ipv4-addr',
|
||||
'id': 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f',
|
||||
'value': '127.0.0.1/32',
|
||||
},
|
||||
'domain': {
|
||||
'type': 'domain-name',
|
||||
'id': 'domain-name--220a2699-5ebf-5b57-bf02-424964bb19c0',
|
||||
'value': 'example.com',
|
||||
'resolves_to_refs': ['src_ip'],
|
||||
'resolves_to_refs': ['ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f'],
|
||||
},
|
||||
},
|
||||
)
|
||||
new_version = data.new_version(last_observed="2017-12-12T12:00:00Z")
|
||||
assert new_version.last_observed.year == 2017
|
||||
assert new_version.objects['domain'].resolves_to_refs[0] == 'src_ip'
|
||||
assert new_version.objects['domain'].resolves_to_refs[0] == 'ipv4-addr--2b94bc65-17d4-54f6-9ffe-7d103551bb9f'
|
||||
|
||||
|
||||
def test_objects_deprecation():
|
||||
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
||||
|
||||
stix2.v21.ObservedData(
|
||||
first_observed="2016-03-12T12:00:00Z",
|
||||
last_observed="2016-03-12T12:00:00Z",
|
||||
number_observed=1,
|
||||
objects={
|
||||
"0": {
|
||||
"type": "file",
|
||||
"name": "foo",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_deterministic_id_same_extra_prop_vals():
|
||||
email_addr_1 = stix2.v21.EmailAddress(
|
||||
value="john@example.com",
|
||||
display_name="Johnny Doe",
|
||||
)
|
||||
|
||||
email_addr_2 = stix2.v21.EmailAddress(
|
||||
value="john@example.com",
|
||||
display_name="Johnny Doe",
|
||||
)
|
||||
|
||||
assert email_addr_1.id == email_addr_2.id
|
||||
|
||||
uuid_obj_1 = uuid.UUID(email_addr_1.id[-36:])
|
||||
assert uuid_obj_1.variant == uuid.RFC_4122
|
||||
assert uuid_obj_1.version == 5
|
||||
|
||||
uuid_obj_2 = uuid.UUID(email_addr_2.id[-36:])
|
||||
assert uuid_obj_2.variant == uuid.RFC_4122
|
||||
assert uuid_obj_2.version == 5
|
||||
|
||||
|
||||
def test_deterministic_id_diff_extra_prop_vals():
|
||||
email_addr_1 = stix2.v21.EmailAddress(
|
||||
value="john@example.com",
|
||||
display_name="Johnny Doe",
|
||||
)
|
||||
|
||||
email_addr_2 = stix2.v21.EmailAddress(
|
||||
value="john@example.com",
|
||||
display_name="Janey Doe",
|
||||
)
|
||||
|
||||
assert email_addr_1.id == email_addr_2.id
|
||||
|
||||
uuid_obj_1 = uuid.UUID(email_addr_1.id[-36:])
|
||||
assert uuid_obj_1.variant == uuid.RFC_4122
|
||||
assert uuid_obj_1.version == 5
|
||||
|
||||
uuid_obj_2 = uuid.UUID(email_addr_2.id[-36:])
|
||||
assert uuid_obj_2.variant == uuid.RFC_4122
|
||||
assert uuid_obj_2.version == 5
|
||||
|
||||
|
||||
def test_deterministic_id_diff_contributing_prop_vals():
|
||||
email_addr_1 = stix2.v21.EmailAddress(
|
||||
value="john@example.com",
|
||||
display_name="Johnny Doe",
|
||||
)
|
||||
|
||||
email_addr_2 = stix2.v21.EmailAddress(
|
||||
value="jane@example.com",
|
||||
display_name="Janey Doe",
|
||||
)
|
||||
|
||||
assert email_addr_1.id != email_addr_2.id
|
||||
|
||||
uuid_obj_1 = uuid.UUID(email_addr_1.id[-36:])
|
||||
assert uuid_obj_1.variant == uuid.RFC_4122
|
||||
assert uuid_obj_1.version == 5
|
||||
|
||||
uuid_obj_2 = uuid.UUID(email_addr_2.id[-36:])
|
||||
assert uuid_obj_2.variant == uuid.RFC_4122
|
||||
assert uuid_obj_2.version == 5
|
||||
|
||||
|
||||
def test_deterministic_id_no_contributing_props():
|
||||
email_msg_1 = stix2.v21.EmailMessage(
|
||||
is_multipart=False,
|
||||
)
|
||||
|
||||
email_msg_2 = stix2.v21.EmailMessage(
|
||||
is_multipart=False,
|
||||
)
|
||||
|
||||
assert email_msg_1.id != email_msg_2.id
|
||||
|
||||
uuid_obj_1 = uuid.UUID(email_msg_1.id[-36:])
|
||||
assert uuid_obj_1.variant == uuid.RFC_4122
|
||||
assert uuid_obj_1.version == 4
|
||||
|
||||
uuid_obj_2 = uuid.UUID(email_msg_2.id[-36:])
|
||||
assert uuid_obj_2.variant == uuid.RFC_4122
|
||||
assert uuid_obj_2.version == 4
|
||||
|
||||
|
||||
def test_ipv4_resolves_to_refs_deprecation():
|
||||
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
||||
|
||||
stix2.v21.IPv4Address(
|
||||
value="26.09.19.70",
|
||||
resolves_to_refs=["mac-addr--08900593-0265-52fc-93c0-5b4a942f5887"],
|
||||
)
|
||||
|
||||
|
||||
def test_ipv4_belongs_to_refs_deprecation():
|
||||
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
||||
|
||||
stix2.v21.IPv4Address(
|
||||
value="21.12.19.64",
|
||||
belongs_to_refs=["autonomous-system--52e0a49d-d683-5801-a7b8-145765a1e116"],
|
||||
)
|
||||
|
||||
|
||||
def test_ipv6_resolves_to_refs_deprecation():
|
||||
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
||||
|
||||
stix2.v21.IPv6Address(
|
||||
value="2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||
resolves_to_refs=["mac-addr--08900593-0265-52fc-93c0-5b4a942f5887"],
|
||||
)
|
||||
|
||||
|
||||
def test_ipv6_belongs_to_refs_deprecation():
|
||||
with pytest.warns(stix2.exceptions.STIXDeprecationWarning):
|
||||
|
||||
stix2.v21.IPv6Address(
|
||||
value="2001:0db8:85a3:0000:0000:8a2e:0370:7334",
|
||||
belongs_to_refs=["autonomous-system--52e0a49d-d683-5801-a7b8-145765a1e116"],
|
||||
)
|
||||
|
|
|
@ -23,10 +23,10 @@ EXPECTED_OPINION = """{
|
|||
"created": "2016-05-12T08:17:27.000Z",
|
||||
"modified": "2016-05-12T08:17:27.000Z",
|
||||
"explanation": "%s",
|
||||
"opinion": "strongly-disagree",
|
||||
"object_refs": [
|
||||
"relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471"
|
||||
],
|
||||
"opinion": "strongly-disagree"
|
||||
]
|
||||
}""" % EXPLANATION
|
||||
|
||||
EXPECTED_OPINION_REPR = "Opinion(" + " ".join((
|
||||
|
@ -37,8 +37,9 @@ EXPECTED_OPINION_REPR = "Opinion(" + " ".join((
|
|||
created='2016-05-12T08:17:27.000Z',
|
||||
modified='2016-05-12T08:17:27.000Z',
|
||||
explanation="%s",
|
||||
object_refs=['relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471'],
|
||||
opinion='strongly-disagree'""" % EXPLANATION
|
||||
opinion='strongly-disagree',
|
||||
object_refs=['relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471']
|
||||
""" % EXPLANATION
|
||||
).split()) + ")"
|
||||
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import uuid
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
|
||||
from stix2.exceptions import (
|
||||
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
|
||||
)
|
||||
from stix2.properties import (
|
||||
ERROR_INVALID_ID, BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
|
||||
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
Property, ReferenceProperty, StringProperty, TimestampProperty,
|
||||
|
@ -89,7 +89,7 @@ def test_type_property():
|
|||
assert prop.clean(prop.default())
|
||||
|
||||
|
||||
ID_PROP = IDProperty('my-type')
|
||||
ID_PROP = IDProperty('my-type', spec_version="2.1")
|
||||
MY_ID = 'my-type--232c9d3f-49fc-4440-bb01-607f638778e7'
|
||||
|
||||
|
||||
|
@ -127,7 +127,7 @@ CONSTANT_IDS.extend(constants.RELATIONSHIP_IDS)
|
|||
@pytest.mark.parametrize("value", CONSTANT_IDS)
|
||||
def test_id_property_valid_for_type(value):
|
||||
type = value.split('--', 1)[0]
|
||||
assert IDProperty(type=type).clean(value) == value
|
||||
assert IDProperty(type=type, spec_version="2.1").clean(value) == value
|
||||
|
||||
|
||||
def test_id_property_wrong_type():
|
||||
|
@ -139,17 +139,13 @@ def test_id_property_wrong_type():
|
|||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'my-type--foo',
|
||||
# Not a v4 UUID
|
||||
# Not a RFC 4122 UUID
|
||||
'my-type--00000000-0000-0000-0000-000000000000',
|
||||
'my-type--' + str(uuid.uuid1()),
|
||||
'my-type--' + str(uuid.uuid3(uuid.NAMESPACE_DNS, "example.org")),
|
||||
'my-type--' + str(uuid.uuid5(uuid.NAMESPACE_DNS, "example.org")),
|
||||
],
|
||||
)
|
||||
def test_id_property_not_a_valid_hex_uuid(value):
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(ValueError):
|
||||
ID_PROP.clean(value)
|
||||
assert str(excinfo.value) == ERROR_INVALID_ID
|
||||
|
||||
|
||||
def test_id_property_default():
|
||||
|
@ -275,17 +271,27 @@ def test_boolean_property_invalid(value):
|
|||
|
||||
|
||||
def test_reference_property():
|
||||
ref_prop = ReferenceProperty()
|
||||
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):
|
||||
ref_prop.clean("foo")
|
||||
|
||||
# This is not a valid V4 UUID
|
||||
# This is not a valid RFC 4122 UUID
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("my-type--00000000-0000-0000-0000-000000000000")
|
||||
|
||||
|
||||
def test_reference_property_specific_type():
|
||||
ref_prop = ReferenceProperty(valid_types="my-type", spec_version="2.1")
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
ref_prop.clean("not-my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf")
|
||||
|
||||
assert ref_prop.clean("my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf") == \
|
||||
"my-type--8a8e8758-f92c-4058-ba38-f061cd42a0cf"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value", [
|
||||
'2017-01-01T12:34:56Z',
|
||||
|
@ -470,23 +476,27 @@ def test_extension_property_valid():
|
|||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data", [
|
||||
1,
|
||||
{'foobar-ext': {
|
||||
'pe_type': 'exe',
|
||||
}},
|
||||
],
|
||||
)
|
||||
def test_extension_property_invalid(data):
|
||||
def test_extension_property_invalid1():
|
||||
ext_prop = ExtensionsProperty(spec_version='2.1', enclosing_type='file')
|
||||
with pytest.raises(ValueError):
|
||||
ext_prop.clean(data)
|
||||
ext_prop.clean(1)
|
||||
|
||||
|
||||
def test_extension_property_invalid2():
|
||||
ext_prop = ExtensionsProperty(spec_version='2.1', enclosing_type='file')
|
||||
with pytest.raises(CustomContentError):
|
||||
ext_prop.clean(
|
||||
{
|
||||
'foobar-ext': {
|
||||
'pe_type': 'exe',
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_extension_property_invalid_type():
|
||||
ext_prop = ExtensionsProperty(spec_version='2.1', enclosing_type='indicator')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
with pytest.raises(CustomContentError) as excinfo:
|
||||
ext_prop.clean(
|
||||
{
|
||||
'windows-pebinary-ext': {
|
||||
|
|
|
@ -91,8 +91,6 @@ def test_report_example_objects_in_object_refs_with_bad_id():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.Report
|
||||
assert excinfo.value.prop_name == "object_refs"
|
||||
assert excinfo.value.reason == stix2.properties.ERROR_INVALID_ID
|
||||
assert str(excinfo.value) == "Invalid value for Report 'object_refs': " + stix2.properties.ERROR_INVALID_ID
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
@ -61,8 +61,6 @@ def test_sighting_bad_where_sighted_refs():
|
|||
|
||||
assert excinfo.value.cls == stix2.v21.Sighting
|
||||
assert excinfo.value.prop_name == "where_sighted_refs"
|
||||
assert excinfo.value.reason == "must start with 'identity'."
|
||||
assert str(excinfo.value) == "Invalid value for Sighting 'where_sighted_refs': must start with 'identity'."
|
||||
|
||||
|
||||
def test_sighting_type_must_be_sightings():
|
||||
|
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
import pytz
|
||||
|
||||
import stix2
|
||||
import stix2.v21
|
||||
|
||||
from .constants import IDENTITY_ID, THREAT_ACTOR_ID
|
||||
|
||||
|
@ -67,4 +68,26 @@ def test_parse_threat_actor(data):
|
|||
assert actor.name == "Evil Org"
|
||||
assert actor.threat_actor_types == ["crime-syndicate"]
|
||||
|
||||
|
||||
def test_seen_ordering_constraint():
|
||||
"""
|
||||
Test first_seen/last_seen value co-constraint.
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
stix2.v21.ThreatActor(
|
||||
name="Bad Person",
|
||||
threat_actor_types=["bad person", "evil person"],
|
||||
first_seen="2010-04-21T09:31:11Z",
|
||||
last_seen="2009-02-06T03:39:31Z",
|
||||
)
|
||||
|
||||
# equal timestamps is okay.
|
||||
stix2.v21.ThreatActor(
|
||||
name="Bad Person",
|
||||
threat_actor_types=["bad person", "evil person"],
|
||||
first_seen="2010-04-21T09:31:11Z",
|
||||
last_seen="2010-04-21T09:31:11Z",
|
||||
)
|
||||
|
||||
|
||||
# TODO: Add other examples
|
||||
|
|
|
@ -135,14 +135,16 @@ def test_deduplicate(stix_objs1):
|
|||
"0": {
|
||||
"name": "foo.exe",
|
||||
"type": "file",
|
||||
"id": "file--5956efbb-a7b0-566d-a7f9-a202eb05c70f",
|
||||
},
|
||||
"1": {
|
||||
"type": "ipv4-addr",
|
||||
"value": "198.51.100.3",
|
||||
"id": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5",
|
||||
},
|
||||
"2": {
|
||||
"type": "network-traffic",
|
||||
"src_ref": "1",
|
||||
"src_ref": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5",
|
||||
"protocols": [
|
||||
"tcp",
|
||||
"http",
|
||||
|
@ -161,7 +163,7 @@ def test_deduplicate(stix_objs1):
|
|||
},
|
||||
},
|
||||
},
|
||||
), ('1', {"type": "ipv4-addr", "value": "198.51.100.3"}), 1,
|
||||
), ('1', {"type": "ipv4-addr", "value": "198.51.100.3", "id": "ipv4-addr--1f8f4d63-9f33-5353-a3e3-e1b84c83a7b5"}), 1,
|
||||
),
|
||||
(
|
||||
{
|
||||
|
|
|
@ -230,6 +230,7 @@ def test_remove_custom_stix_property():
|
|||
malware_types=["rootkit"],
|
||||
x_custom="armada",
|
||||
allow_custom=True,
|
||||
is_family=False,
|
||||
)
|
||||
|
||||
mal_nc = stix2.utils.remove_custom_stix(mal)
|
||||
|
|
|
@ -1,331 +0,0 @@
|
|||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2.workbench import (
|
||||
AttackPattern, Campaign, CourseOfAction, ExternalReference,
|
||||
FileSystemSource, Filter, Identity, Indicator, IntrusionSet, Malware,
|
||||
MarkingDefinition, ObservedData, Relationship, Report, StatementMarking,
|
||||
ThreatActor, Tool, Vulnerability, add_data_source, all_versions,
|
||||
attack_patterns, campaigns, courses_of_action, create, get, identities,
|
||||
indicators, intrusion_sets, malware, observed_data, query, reports, save,
|
||||
set_default_created, set_default_creator, set_default_external_refs,
|
||||
set_default_object_marking_refs, threat_actors, tools, vulnerabilities,
|
||||
)
|
||||
|
||||
from .constants import (
|
||||
ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
|
||||
COURSE_OF_ACTION_ID, COURSE_OF_ACTION_KWARGS, IDENTITY_ID, IDENTITY_KWARGS,
|
||||
INDICATOR_ID, INDICATOR_KWARGS, INTRUSION_SET_ID, INTRUSION_SET_KWARGS,
|
||||
MALWARE_ID, MALWARE_KWARGS, OBSERVED_DATA_ID, OBSERVED_DATA_KWARGS,
|
||||
REPORT_ID, REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID,
|
||||
TOOL_KWARGS, VULNERABILITY_ID, VULNERABILITY_KWARGS,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_environment():
|
||||
|
||||
# Create a STIX object
|
||||
ind = create(Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
|
||||
save(ind)
|
||||
|
||||
resp = get(INDICATOR_ID)
|
||||
assert resp['indicator_types'][0] == 'malicious-activity'
|
||||
|
||||
resp = all_versions(INDICATOR_ID)
|
||||
assert len(resp) == 1
|
||||
|
||||
# Search on something other than id
|
||||
q = [Filter('type', '=', 'vulnerability')]
|
||||
resp = query(q)
|
||||
assert len(resp) == 0
|
||||
|
||||
|
||||
def test_workbench_get_all_attack_patterns():
|
||||
mal = AttackPattern(id=ATTACK_PATTERN_ID, **ATTACK_PATTERN_KWARGS)
|
||||
save(mal)
|
||||
|
||||
resp = attack_patterns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == ATTACK_PATTERN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_campaigns():
|
||||
cam = Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
save(cam)
|
||||
|
||||
resp = campaigns()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == CAMPAIGN_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_courses_of_action():
|
||||
coa = CourseOfAction(id=COURSE_OF_ACTION_ID, **COURSE_OF_ACTION_KWARGS)
|
||||
save(coa)
|
||||
|
||||
resp = courses_of_action()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == COURSE_OF_ACTION_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_identities():
|
||||
idty = Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
save(idty)
|
||||
|
||||
resp = identities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == IDENTITY_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_get_all_indicators():
|
||||
resp = indicators()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INDICATOR_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_intrusion_sets():
|
||||
ins = IntrusionSet(id=INTRUSION_SET_ID, **INTRUSION_SET_KWARGS)
|
||||
save(ins)
|
||||
|
||||
resp = intrusion_sets()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == INTRUSION_SET_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_get_all_malware():
|
||||
mal = Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
save(mal)
|
||||
|
||||
resp = malware()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_observed_data():
|
||||
od = ObservedData(id=OBSERVED_DATA_ID, **OBSERVED_DATA_KWARGS)
|
||||
save(od)
|
||||
|
||||
resp = observed_data()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == OBSERVED_DATA_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_get_all_reports():
|
||||
rep = Report(id=REPORT_ID, **REPORT_KWARGS)
|
||||
save(rep)
|
||||
|
||||
resp = reports()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == REPORT_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_get_all_threat_actors():
|
||||
thr = ThreatActor(id=THREAT_ACTOR_ID, **THREAT_ACTOR_KWARGS)
|
||||
save(thr)
|
||||
|
||||
resp = threat_actors()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == THREAT_ACTOR_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_get_all_tools():
|
||||
tool = Tool(id=TOOL_ID, **TOOL_KWARGS)
|
||||
save(tool)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == TOOL_ID
|
||||
|
||||
|
||||
def test_workbench_get_all_vulnerabilities():
|
||||
vuln = Vulnerability(id=VULNERABILITY_ID, **VULNERABILITY_KWARGS)
|
||||
save(vuln)
|
||||
|
||||
resp = vulnerabilities()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].id == VULNERABILITY_ID
|
||||
|
||||
|
||||
def test_workbench_add_to_bundle():
|
||||
vuln = Vulnerability(**VULNERABILITY_KWARGS)
|
||||
bundle = stix2.v21.Bundle(vuln)
|
||||
assert bundle.objects[0].name == 'Heartbleed'
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_relationships():
|
||||
rel = Relationship(INDICATOR_ID, 'indicates', MALWARE_ID)
|
||||
save(rel)
|
||||
|
||||
ind = get(INDICATOR_ID)
|
||||
resp = ind.relationships()
|
||||
assert len(resp) == 1
|
||||
assert resp[0].relationship_type == 'indicates'
|
||||
assert resp[0].source_ref == INDICATOR_ID
|
||||
assert resp[0].target_ref == MALWARE_ID
|
||||
|
||||
|
||||
def test_workbench_created_by():
|
||||
intset = IntrusionSet(name="Breach 123", created_by_ref=IDENTITY_ID)
|
||||
save(intset)
|
||||
creator = intset.created_by()
|
||||
assert creator.id == IDENTITY_ID
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_related():
|
||||
rel1 = Relationship(MALWARE_ID, 'targets', IDENTITY_ID)
|
||||
rel2 = Relationship(CAMPAIGN_ID, 'uses', MALWARE_ID)
|
||||
save([rel1, rel2])
|
||||
|
||||
resp = get(MALWARE_ID).related()
|
||||
assert len(resp) == 3
|
||||
assert any(x['id'] == CAMPAIGN_ID for x in resp)
|
||||
assert any(x['id'] == INDICATOR_ID for x in resp)
|
||||
assert any(x['id'] == IDENTITY_ID for x in resp)
|
||||
|
||||
resp = get(MALWARE_ID).related(relationship_type='indicates')
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_workbench_related_with_filters():
|
||||
malware = Malware(
|
||||
malware_types=["ransomware"], name="CryptorBit",
|
||||
created_by_ref=IDENTITY_ID,
|
||||
)
|
||||
rel = Relationship(malware.id, 'variant-of', MALWARE_ID)
|
||||
save([malware, rel])
|
||||
|
||||
filters = [Filter('created_by_ref', '=', IDENTITY_ID)]
|
||||
resp = get(MALWARE_ID).related(filters=filters)
|
||||
|
||||
assert len(resp) == 1
|
||||
assert resp[0].name == malware.name
|
||||
assert resp[0].created_by_ref == IDENTITY_ID
|
||||
|
||||
# filters arg can also be single filter
|
||||
resp = get(MALWARE_ID).related(filters=filters[0])
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
|
||||
def test_add_data_source():
|
||||
fs_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
|
||||
fs = FileSystemSource(fs_path)
|
||||
add_data_source(fs)
|
||||
|
||||
resp = tools()
|
||||
assert len(resp) == 3
|
||||
resp_ids = [tool.id for tool in resp]
|
||||
assert TOOL_ID in resp_ids
|
||||
assert 'tool--03342581-f790-4f03-ba41-e82e67392e23' in resp_ids
|
||||
assert 'tool--242f3da3-4425-4d11-8f5c-b842886da966' in resp_ids
|
||||
|
||||
|
||||
def test_additional_filter():
|
||||
resp = tools(Filter('created_by_ref', '=', 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5'))
|
||||
assert len(resp) == 2
|
||||
|
||||
|
||||
def test_additional_filters_list():
|
||||
resp = tools([
|
||||
Filter('created_by_ref', '=', 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5'),
|
||||
Filter('name', '=', 'Windows Credential Editor'),
|
||||
])
|
||||
assert len(resp) == 1
|
||||
|
||||
|
||||
def test_default_creator():
|
||||
set_default_creator(IDENTITY_ID)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created_by_ref' not in CAMPAIGN_KWARGS
|
||||
assert campaign.created_by_ref == IDENTITY_ID
|
||||
|
||||
|
||||
def test_default_created_timestamp():
|
||||
timestamp = "2018-03-19T01:02:03.000Z"
|
||||
set_default_created(timestamp)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert 'created' not in CAMPAIGN_KWARGS
|
||||
assert stix2.utils.format_datetime(campaign.created) == timestamp
|
||||
assert stix2.utils.format_datetime(campaign.modified) == timestamp
|
||||
|
||||
|
||||
def test_default_external_refs():
|
||||
ext_ref = ExternalReference(
|
||||
source_name="ACME Threat Intel",
|
||||
description="Threat report",
|
||||
)
|
||||
set_default_external_refs(ext_ref)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.external_references[0].source_name == "ACME Threat Intel"
|
||||
assert campaign.external_references[0].description == "Threat report"
|
||||
|
||||
|
||||
def test_default_object_marking_refs():
|
||||
stmt_marking = StatementMarking("Copyright 2016, Example Corp")
|
||||
mark_def = MarkingDefinition(
|
||||
definition_type="statement",
|
||||
definition=stmt_marking,
|
||||
)
|
||||
set_default_object_marking_refs(mark_def)
|
||||
campaign = Campaign(**CAMPAIGN_KWARGS)
|
||||
|
||||
assert campaign.object_marking_refs[0] == mark_def.id
|
||||
|
||||
|
||||
def test_workbench_custom_property_object_in_observable_extension():
|
||||
ntfs = stix2.v21.NTFSExt(
|
||||
allow_custom=True,
|
||||
sid=1,
|
||||
x_foo='bar',
|
||||
)
|
||||
artifact = stix2.v21.File(
|
||||
name='test',
|
||||
extensions={'ntfs-ext': ntfs},
|
||||
)
|
||||
observed_data = ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=1,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
||||
|
||||
|
||||
def test_workbench_custom_property_dict_in_observable_extension():
|
||||
artifact = stix2.v21.File(
|
||||
allow_custom=True,
|
||||
name='test',
|
||||
extensions={
|
||||
'ntfs-ext': {
|
||||
'allow_custom': True,
|
||||
'sid': 1,
|
||||
'x_foo': 'bar',
|
||||
},
|
||||
},
|
||||
)
|
||||
observed_data = ObservedData(
|
||||
allow_custom=True,
|
||||
first_observed="2015-12-21T19:00:00Z",
|
||||
last_observed="2015-12-21T19:00:00Z",
|
||||
number_observed=1,
|
||||
objects={"0": artifact},
|
||||
)
|
||||
|
||||
assert observed_data.objects['0'].extensions['ntfs-ext'].x_foo == "bar"
|
||||
assert '"x_foo": "bar"' in str(observed_data)
|
|
@ -16,11 +16,11 @@ class Bundle(_STIXBase):
|
|||
_type = 'bundle'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
# Not technically correct: STIX 2.0 spec doesn't say spec_version must
|
||||
# have this value, but it's all we support for now.
|
||||
('spec_version', StringProperty(fixed='2.0')),
|
||||
('objects', ListProperty(STIXObjectProperty)),
|
||||
('objects', ListProperty(STIXObjectProperty(spec_version="2.0"))),
|
||||
])
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -66,7 +66,7 @@ class GranularMarking(_STIXBase):
|
|||
"""
|
||||
|
||||
_properties = OrderedDict([
|
||||
('marking_ref', ReferenceProperty(required=True, type='marking-definition')),
|
||||
('marking_ref', ReferenceProperty(valid_types='marking-definition', spec_version='2.0', required=True)),
|
||||
('selectors', ListProperty(SelectorProperty, required=True)),
|
||||
])
|
||||
|
||||
|
@ -121,11 +121,11 @@ class MarkingDefinition(_STIXBase, _MarkingsMixin):
|
|||
_type = 'marking-definition'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('definition_type', StringProperty(required=True)),
|
||||
('definition', MarkingProperty(required=True)),
|
||||
|
|
|
@ -31,7 +31,7 @@ class Artifact(_Observable):
|
|||
('payload_bin', BinaryProperty()),
|
||||
('url', StringProperty()),
|
||||
('hashes', HashesProperty()),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -51,7 +51,7 @@ class AutonomousSystem(_Observable):
|
|||
('number', IntegerProperty(required=True)),
|
||||
('name', StringProperty()),
|
||||
('rir', StringProperty()),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -70,7 +70,7 @@ class Directory(_Observable):
|
|||
('modified', TimestampProperty()),
|
||||
('accessed', TimestampProperty()),
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty(valid_types=['file', 'directory']))),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -84,7 +84,7 @@ class DomainName(_Observable):
|
|||
('type', TypeProperty(_type)),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name']))),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -99,7 +99,7 @@ class EmailAddress(_Observable):
|
|||
('value', StringProperty(required=True)),
|
||||
('display_name', StringProperty()),
|
||||
('belongs_to_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -138,11 +138,11 @@ class EmailMessage(_Observable):
|
|||
('bcc_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))),
|
||||
('subject', StringProperty()),
|
||||
('received_lines', ListProperty(StringProperty)),
|
||||
('additional_header_fields', DictionaryProperty()),
|
||||
('additional_header_fields', DictionaryProperty(spec_version="2.0")),
|
||||
('body', StringProperty()),
|
||||
('body_multipart', ListProperty(EmbeddedObjectProperty(type=EmailMIMEComponent))),
|
||||
('raw_email_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -199,7 +199,7 @@ class PDFExt(_Extension):
|
|||
_properties = OrderedDict([
|
||||
('version', StringProperty()),
|
||||
('is_optimized', BooleanProperty()),
|
||||
('document_info_dict', DictionaryProperty()),
|
||||
('document_info_dict', DictionaryProperty(spec_version="2.0")),
|
||||
('pdfid0', StringProperty()),
|
||||
('pdfid1', StringProperty()),
|
||||
])
|
||||
|
@ -216,7 +216,7 @@ class RasterImageExt(_Extension):
|
|||
('image_width', IntegerProperty()),
|
||||
('bits_per_pixel', IntegerProperty()),
|
||||
('image_compression_algorithm', StringProperty()),
|
||||
('exif_tags', DictionaryProperty()),
|
||||
('exif_tags', DictionaryProperty(spec_version="2.0")),
|
||||
])
|
||||
|
||||
|
||||
|
@ -323,7 +323,7 @@ class File(_Observable):
|
|||
('decryption_key', StringProperty()),
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty)),
|
||||
('content_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -343,7 +343,7 @@ class IPv4Address(_Observable):
|
|||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))),
|
||||
('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -358,7 +358,7 @@ class IPv6Address(_Observable):
|
|||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))),
|
||||
('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -371,7 +371,7 @@ class MACAddress(_Observable):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -384,7 +384,7 @@ class Mutex(_Observable):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('name', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -398,7 +398,7 @@ class HTTPRequestExt(_Extension):
|
|||
('request_method', StringProperty(required=True)),
|
||||
('request_value', StringProperty(required=True)),
|
||||
('request_version', StringProperty()),
|
||||
('request_header', DictionaryProperty()),
|
||||
('request_header', DictionaryProperty(spec_version="2.0")),
|
||||
('message_body_length', IntegerProperty()),
|
||||
('message_body_data_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
])
|
||||
|
@ -449,7 +449,7 @@ class SocketExt(_Extension):
|
|||
"PF_NETROM",
|
||||
]),
|
||||
),
|
||||
('options', DictionaryProperty()),
|
||||
('options', DictionaryProperty(spec_version="2.0")),
|
||||
(
|
||||
'socket_type', EnumProperty(allowed=[
|
||||
"SOCK_STREAM",
|
||||
|
@ -496,12 +496,12 @@ class NetworkTraffic(_Observable):
|
|||
('dst_byte_count', IntegerProperty()),
|
||||
('src_packets', IntegerProperty()),
|
||||
('dst_packets', IntegerProperty()),
|
||||
('ipfix', DictionaryProperty()),
|
||||
('ipfix', DictionaryProperty(spec_version="2.0")),
|
||||
('src_payload_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('dst_payload_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('encapsulates_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))),
|
||||
('encapsulates_by_ref', ObjectReferenceProperty(valid_types='network-traffic')),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -521,7 +521,7 @@ class WindowsProcessExt(_Extension):
|
|||
('priority', StringProperty()),
|
||||
('owner_sid', StringProperty()),
|
||||
('window_title', StringProperty()),
|
||||
('startup_info', DictionaryProperty()),
|
||||
('startup_info', DictionaryProperty(spec_version="2.0")),
|
||||
])
|
||||
|
||||
|
||||
|
@ -584,13 +584,13 @@ class Process(_Observable):
|
|||
('cwd', StringProperty()),
|
||||
('arguments', ListProperty(StringProperty)),
|
||||
('command_line', StringProperty()),
|
||||
('environment_variables', DictionaryProperty()),
|
||||
('environment_variables', DictionaryProperty(spec_version="2.0")),
|
||||
('opened_connection_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))),
|
||||
('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('binary_ref', ObjectReferenceProperty(valid_types='file')),
|
||||
('parent_ref', ObjectReferenceProperty(valid_types='process')),
|
||||
('child_refs', ListProperty(ObjectReferenceProperty('process'))),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -621,7 +621,7 @@ class Software(_Observable):
|
|||
('languages', ListProperty(StringProperty)),
|
||||
('vendor', StringProperty()),
|
||||
('version', StringProperty()),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -634,7 +634,7 @@ class URL(_Observable):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -673,7 +673,7 @@ class UserAccount(_Observable):
|
|||
('password_last_changed', TimestampProperty()),
|
||||
('account_first_login', TimestampProperty()),
|
||||
('account_last_login', TimestampProperty()),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -720,7 +720,7 @@ class WindowsRegistryKey(_Observable):
|
|||
('modified', TimestampProperty()),
|
||||
('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('number_of_subkeys', IntegerProperty()),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
@property
|
||||
|
@ -776,7 +776,7 @@ class X509Certificate(_Observable):
|
|||
('subject_public_key_modulus', StringProperty()),
|
||||
('subject_public_key_exponent', IntegerProperty()),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtenstionsType)),
|
||||
('extensions', ExtensionsProperty(enclosing_type=_type)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -798,7 +798,7 @@ def CustomObservable(type='x-custom-observable', properties=None):
|
|||
_properties = list(itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type))],
|
||||
properties,
|
||||
[('extensions', ExtensionsProperty(enclosing_type=type))],
|
||||
[('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=type))],
|
||||
]))
|
||||
return _custom_observable_builder(cls, type, _properties, '2.0')
|
||||
return wrapper
|
||||
|
|
|
@ -22,8 +22,8 @@ class AttackPattern(STIXDomainObject):
|
|||
_type = 'attack-pattern'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -32,7 +32,7 @@ class AttackPattern(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -45,8 +45,8 @@ class Campaign(STIXDomainObject):
|
|||
_type = 'campaign'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -58,7 +58,7 @@ class Campaign(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -71,8 +71,8 @@ class CourseOfAction(STIXDomainObject):
|
|||
_type = 'course-of-action'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -80,7 +80,7 @@ class CourseOfAction(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -93,8 +93,8 @@ class Identity(STIXDomainObject):
|
|||
_type = 'identity'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -105,7 +105,7 @@ class Identity(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -118,8 +118,8 @@ class Indicator(STIXDomainObject):
|
|||
_type = 'indicator'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty()),
|
||||
|
@ -131,7 +131,7 @@ class Indicator(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty, required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -144,8 +144,8 @@ class IntrusionSet(STIXDomainObject):
|
|||
_type = 'intrusion-set'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -160,7 +160,7 @@ class IntrusionSet(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -173,8 +173,8 @@ class Malware(STIXDomainObject):
|
|||
_type = 'malware'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -183,7 +183,7 @@ class Malware(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty, required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -196,18 +196,18 @@ class ObservedData(STIXDomainObject):
|
|||
_type = 'observed-data'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('first_observed', TimestampProperty(required=True)),
|
||||
('last_observed', TimestampProperty(required=True)),
|
||||
('number_observed', IntegerProperty(min=1, max=999999999, required=True)),
|
||||
('objects', ObservableProperty(required=True)),
|
||||
('objects', ObservableProperty(spec_version="2.0", required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -226,18 +226,18 @@ class Report(STIXDomainObject):
|
|||
_type = 'report'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('published', TimestampProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty, 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)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -250,8 +250,8 @@ class ThreatActor(STIXDomainObject):
|
|||
_type = 'threat-actor'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -267,7 +267,7 @@ class ThreatActor(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty, required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -280,8 +280,8 @@ class Tool(STIXDomainObject):
|
|||
_type = 'tool'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -291,7 +291,7 @@ class Tool(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty, required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -304,8 +304,8 @@ class Vulnerability(STIXDomainObject):
|
|||
_type = 'vulnerability'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -313,7 +313,7 @@ class Vulnerability(STIXDomainObject):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -351,8 +351,8 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
_properties = list(itertools.chain.from_iterable([
|
||||
[
|
||||
('type', TypeProperty(type)),
|
||||
('id', IDProperty(type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
],
|
||||
|
@ -361,7 +361,7 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
|
|
|
@ -16,21 +16,23 @@ class Relationship(STIXRelationshipObject):
|
|||
`the STIX 2.0 specification <http://docs.oasis-open.org/cti/stix/v2.0/cs01/part2-stix-objects/stix-v2.0-cs01-part2-stix-objects.html#_Toc496714340>`__.
|
||||
"""
|
||||
|
||||
_invalid_source_target_types = ['bundle', 'language-content', 'marking-definition', 'relationship', 'sighting']
|
||||
|
||||
_type = 'relationship'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('relationship_type', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('source_ref', ReferenceProperty(required=True)),
|
||||
('target_ref', ReferenceProperty(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)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -58,21 +60,21 @@ class Sighting(STIXRelationshipObject):
|
|||
_type = 'sighting'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('count', IntegerProperty(min=0, max=999999999)),
|
||||
('sighting_of_ref', ReferenceProperty(required=True)),
|
||||
('observed_data_refs', ListProperty(ReferenceProperty(type='observed-data'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(type='identity'))),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="only_SDO", 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)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
|
|
@ -32,9 +32,10 @@ from .observables import (
|
|||
X509Certificate, X509V3ExtenstionsType,
|
||||
)
|
||||
from .sdo import (
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Identity, Indicator,
|
||||
IntrusionSet, Location, Malware, Note, ObservedData, Opinion, Report,
|
||||
ThreatActor, Tool, Vulnerability,
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Grouping, Identity,
|
||||
Indicator, Infrastructure, IntrusionSet, Location, Malware,
|
||||
MalwareAnalysis, Note, ObservedData, Opinion, Report, ThreatActor, Tool,
|
||||
Vulnerability,
|
||||
)
|
||||
from .sro import Relationship, Sighting
|
||||
|
||||
|
@ -43,12 +44,15 @@ OBJ_MAP = {
|
|||
'bundle': Bundle,
|
||||
'campaign': Campaign,
|
||||
'course-of-action': CourseOfAction,
|
||||
'grouping': Grouping,
|
||||
'identity': Identity,
|
||||
'indicator': Indicator,
|
||||
'infrastructure': Infrastructure,
|
||||
'intrusion-set': IntrusionSet,
|
||||
'language-content': LanguageContent,
|
||||
'location': Location,
|
||||
'malware': Malware,
|
||||
'malware-analysis': MalwareAnalysis,
|
||||
'note': Note,
|
||||
'marking-definition': MarkingDefinition,
|
||||
'observed-data': ObservedData,
|
||||
|
|
|
@ -17,7 +17,7 @@ class Bundle(_STIXBase):
|
|||
_type = 'bundle'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('objects', ListProperty(STIXObjectProperty(spec_version='2.1'))),
|
||||
])
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ class GranularMarking(_STIXBase):
|
|||
|
||||
_properties = OrderedDict([
|
||||
('lang', StringProperty()),
|
||||
('marking_ref', ReferenceProperty(type='marking-definition')),
|
||||
('marking_ref', ReferenceProperty(valid_types='marking-definition', spec_version='2.1')),
|
||||
('selectors', ListProperty(SelectorProperty, required=True)),
|
||||
])
|
||||
|
||||
|
@ -72,20 +72,20 @@ class LanguageContent(_STIXBase):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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(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(required=True, precision='millisecond')),
|
||||
('object_modified', TimestampProperty(precision='millisecond')),
|
||||
# TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx
|
||||
('contents', DictionaryProperty(spec_version='2.1', required=True)),
|
||||
('revoked', BooleanProperty()),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -144,10 +144,10 @@ class MarkingDefinition(_STIXBase, _MarkingsMixin):
|
|||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('definition_type', StringProperty(required=True)),
|
||||
('definition', MarkingProperty(required=True)),
|
||||
|
|
|
@ -7,16 +7,21 @@ Observable and do not have a ``_type`` attribute.
|
|||
|
||||
from collections import OrderedDict
|
||||
import itertools
|
||||
import warnings
|
||||
|
||||
from ..base import _Extension, _Observable, _STIXBase
|
||||
from ..custom import _custom_extension_builder, _custom_observable_builder
|
||||
from ..exceptions import AtLeastOnePropertyError, DependentPropertiesError
|
||||
from ..exceptions import (
|
||||
AtLeastOnePropertyError, DependentPropertiesError, STIXDeprecationWarning,
|
||||
)
|
||||
from ..properties import (
|
||||
BinaryProperty, BooleanProperty, CallableValues, DictionaryProperty,
|
||||
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
|
||||
HashesProperty, HexProperty, IntegerProperty, ListProperty,
|
||||
ObjectReferenceProperty, StringProperty, TimestampProperty, TypeProperty,
|
||||
HashesProperty, HexProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
ObjectReferenceProperty, ReferenceProperty, StringProperty,
|
||||
TimestampProperty, TypeProperty,
|
||||
)
|
||||
from .common import GranularMarking
|
||||
|
||||
|
||||
class Artifact(_Observable):
|
||||
|
@ -28,6 +33,7 @@ class Artifact(_Observable):
|
|||
_type = 'artifact'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('mime_type', StringProperty()),
|
||||
('payload_bin', BinaryProperty()),
|
||||
('url', StringProperty()),
|
||||
|
@ -35,7 +41,12 @@ class Artifact(_Observable):
|
|||
('encryption_algorithm', StringProperty()),
|
||||
('decryption_key', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "payload_bin"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(Artifact, self)._check_object_constraints()
|
||||
|
@ -52,11 +63,17 @@ class AutonomousSystem(_Observable):
|
|||
_type = 'autonomous-system'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('number', IntegerProperty(required=True)),
|
||||
('name', StringProperty()),
|
||||
('rir', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["number"]
|
||||
|
||||
|
||||
class Directory(_Observable):
|
||||
|
@ -68,15 +85,21 @@ class Directory(_Observable):
|
|||
_type = 'directory'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('path', StringProperty(required=True)),
|
||||
('path_enc', StringProperty()),
|
||||
# these are not the created/modified timestamps of the object itself
|
||||
('created', TimestampProperty()),
|
||||
('modified', TimestampProperty()),
|
||||
('accessed', TimestampProperty()),
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty(valid_types=['file', 'directory']))),
|
||||
('ctime', TimestampProperty()),
|
||||
('mtime', TimestampProperty()),
|
||||
('atime', TimestampProperty()),
|
||||
('contains_refs', ListProperty(ReferenceProperty(valid_types=['file', 'directory'], spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["path"]
|
||||
|
||||
|
||||
class DomainName(_Observable):
|
||||
|
@ -88,10 +111,24 @@ class DomainName(_Observable):
|
|||
_type = 'domain-name'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name']))),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name'], spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
if self.get('resolves_to_refs'):
|
||||
warnings.warn(
|
||||
"The 'resolves_to_refs' property of domain-name is deprecated in "
|
||||
"STIX 2.1. Use the 'resolves-to' relationship type instead",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class EmailAddress(_Observable):
|
||||
|
@ -103,11 +140,17 @@ class EmailAddress(_Observable):
|
|||
_type = 'email-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('display_name', StringProperty()),
|
||||
('belongs_to_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('belongs_to_ref', ReferenceProperty(valid_types='user-account', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
||||
class EmailMIMEComponent(_STIXBase):
|
||||
|
@ -137,22 +180,29 @@ class EmailMessage(_Observable):
|
|||
_type = 'email-message'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_multipart', BooleanProperty(required=True)),
|
||||
('date', TimestampProperty()),
|
||||
('content_type', StringProperty()),
|
||||
('from_ref', ObjectReferenceProperty(valid_types='email-addr')),
|
||||
('sender_ref', ObjectReferenceProperty(valid_types='email-addr')),
|
||||
('to_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))),
|
||||
('cc_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))),
|
||||
('bcc_refs', ListProperty(ObjectReferenceProperty(valid_types='email-addr'))),
|
||||
('from_ref', ReferenceProperty(valid_types='email-addr', spec_version='2.1')),
|
||||
('sender_ref', ReferenceProperty(valid_types='email-addr', spec_version='2.1')),
|
||||
('to_refs', ListProperty(ReferenceProperty(valid_types='email-addr', spec_version='2.1'))),
|
||||
('cc_refs', ListProperty(ReferenceProperty(valid_types='email-addr', spec_version='2.1'))),
|
||||
('bcc_refs', ListProperty(ReferenceProperty(valid_types='email-addr', spec_version='2.1'))),
|
||||
('message_id', StringProperty()),
|
||||
('subject', StringProperty()),
|
||||
('received_lines', ListProperty(StringProperty)),
|
||||
('additional_header_fields', DictionaryProperty(spec_version='2.1')),
|
||||
('body', StringProperty()),
|
||||
('body_multipart', ListProperty(EmbeddedObjectProperty(type=EmailMIMEComponent))),
|
||||
('raw_email_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('raw_email_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["from_ref", "subject", "body"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(EmailMessage, self)._check_object_constraints()
|
||||
|
@ -170,7 +220,7 @@ class ArchiveExt(_Extension):
|
|||
|
||||
_type = 'archive-ext'
|
||||
_properties = OrderedDict([
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty(valid_types='file'), required=True)),
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty(valid_types=['file', 'directory']), required=True)),
|
||||
('comment', StringProperty()),
|
||||
])
|
||||
|
||||
|
@ -323,6 +373,7 @@ class File(_Observable):
|
|||
_type = 'file'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('hashes', HashesProperty(spec_version='2.1')),
|
||||
('size', IntegerProperty(min=0)),
|
||||
('name', StringProperty()),
|
||||
|
@ -330,14 +381,19 @@ class File(_Observable):
|
|||
('magic_number_hex', HexProperty()),
|
||||
('mime_type', StringProperty()),
|
||||
# these are not the created/modified timestamps of the object itself
|
||||
('created', TimestampProperty()),
|
||||
('modified', TimestampProperty()),
|
||||
('accessed', TimestampProperty()),
|
||||
('parent_directory_ref', ObjectReferenceProperty(valid_types='directory')),
|
||||
('contains_refs', ListProperty(ObjectReferenceProperty)),
|
||||
('content_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('ctime', TimestampProperty()),
|
||||
('mtime', TimestampProperty()),
|
||||
('atime', TimestampProperty()),
|
||||
('parent_directory_ref', ReferenceProperty(valid_types='directory', spec_version='2.1')),
|
||||
('contains_refs', ListProperty(ReferenceProperty(invalid_types="", spec_version='2.1'))),
|
||||
('content_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "name", "extensions"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(File, self)._check_object_constraints()
|
||||
|
@ -353,11 +409,32 @@ class IPv4Address(_Observable):
|
|||
_type = 'ipv4-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))),
|
||||
('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))),
|
||||
('belongs_to_refs', ListProperty(ReferenceProperty(valid_types='autonomous-system', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
if self.get('resolves_to_refs'):
|
||||
warnings.warn(
|
||||
"The 'resolves_to_refs' property of ipv4-addr is deprecated in "
|
||||
"STIX 2.1. Use the 'resolves-to' relationship type instead",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
if self.get('belongs_to_refs'):
|
||||
warnings.warn(
|
||||
"The 'belongs_to_refs' property of ipv4-addr is deprecated in "
|
||||
"STIX 2.1. Use the 'belongs-to' relationship type instead",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class IPv6Address(_Observable):
|
||||
|
@ -369,11 +446,32 @@ class IPv6Address(_Observable):
|
|||
_type = 'ipv6-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ObjectReferenceProperty(valid_types='mac-addr'))),
|
||||
('belongs_to_refs', ListProperty(ObjectReferenceProperty(valid_types='autonomous-system'))),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))),
|
||||
('belongs_to_refs', ListProperty(ReferenceProperty(valid_types='autonomous-system', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
if self.get('resolves_to_refs'):
|
||||
warnings.warn(
|
||||
"The 'resolves_to_refs' property of ipv6-addr is deprecated in "
|
||||
"STIX 2.1. Use the 'resolves-to' relationship type instead",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
if self.get('belongs_to_refs'):
|
||||
warnings.warn(
|
||||
"The 'belongs_to_refs' property of ipv6-addr is deprecated in "
|
||||
"STIX 2.1. Use the 'belongs-to' relationship type instead",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
|
||||
class MACAddress(_Observable):
|
||||
|
@ -385,9 +483,15 @@ class MACAddress(_Observable):
|
|||
_type = 'mac-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
||||
class Mutex(_Observable):
|
||||
|
@ -399,9 +503,15 @@ class Mutex(_Observable):
|
|||
_type = 'mutex'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('name', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["name"]
|
||||
|
||||
|
||||
class HTTPRequestExt(_Extension):
|
||||
|
@ -505,11 +615,12 @@ class NetworkTraffic(_Observable):
|
|||
_type = 'network-traffic'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('start', TimestampProperty()),
|
||||
('end', TimestampProperty()),
|
||||
('is_active', BooleanProperty()),
|
||||
('src_ref', ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'])),
|
||||
('dst_ref', ObjectReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'])),
|
||||
('src_ref', ReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'], spec_version='2.1')),
|
||||
('dst_ref', ReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'mac-addr', 'domain-name'], spec_version='2.1')),
|
||||
('src_port', IntegerProperty(min=0, max=65535)),
|
||||
('dst_port', IntegerProperty(min=0, max=65535)),
|
||||
('protocols', ListProperty(StringProperty, required=True)),
|
||||
|
@ -518,12 +629,17 @@ class NetworkTraffic(_Observable):
|
|||
('src_packets', IntegerProperty(min=0)),
|
||||
('dst_packets', IntegerProperty(min=0)),
|
||||
('ipfix', DictionaryProperty(spec_version='2.1')),
|
||||
('src_payload_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('dst_payload_ref', ObjectReferenceProperty(valid_types='artifact')),
|
||||
('encapsulates_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))),
|
||||
('encapsulates_by_ref', ObjectReferenceProperty(valid_types='network-traffic')),
|
||||
('src_payload_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('dst_payload_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('encapsulates_refs', ListProperty(ReferenceProperty(valid_types='network-traffic', spec_version='2.1'))),
|
||||
('encapsulated_by_ref', ReferenceProperty(valid_types='network-traffic', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["start", "src_ref", "dst_ref", "src_port", "dst_port", "protocols"]
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(NetworkTraffic, self)._check_object_constraints()
|
||||
|
@ -624,20 +740,26 @@ class Process(_Observable):
|
|||
_type = 'process'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_hidden', BooleanProperty()),
|
||||
('pid', IntegerProperty()),
|
||||
# this is not the created timestamps of the object itself
|
||||
('created', TimestampProperty()),
|
||||
('created_time', TimestampProperty()),
|
||||
('cwd', StringProperty()),
|
||||
('command_line', StringProperty()),
|
||||
('environment_variables', DictionaryProperty(spec_version='2.1')),
|
||||
('opened_connection_refs', ListProperty(ObjectReferenceProperty(valid_types='network-traffic'))),
|
||||
('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('image_ref', ObjectReferenceProperty(valid_types='file')),
|
||||
('parent_ref', ObjectReferenceProperty(valid_types='process')),
|
||||
('child_refs', ListProperty(ObjectReferenceProperty('process'))),
|
||||
('opened_connection_refs', ListProperty(ReferenceProperty(valid_types='network-traffic', spec_version='2.1'))),
|
||||
('creator_user_ref', ReferenceProperty(valid_types='user-account', spec_version='2.1')),
|
||||
('image_ref', ReferenceProperty(valid_types='file', spec_version='2.1')),
|
||||
('parent_ref', ReferenceProperty(valid_types='process', spec_version='2.1')),
|
||||
('child_refs', ListProperty(ReferenceProperty(valid_types='process', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = []
|
||||
|
||||
def _check_object_constraints(self):
|
||||
# no need to check windows-service-ext, since it has a required property
|
||||
|
@ -663,13 +785,19 @@ class Software(_Observable):
|
|||
_type = 'software'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('name', StringProperty(required=True)),
|
||||
('cpe', StringProperty()),
|
||||
('languages', ListProperty(StringProperty)),
|
||||
('vendor', StringProperty()),
|
||||
('version', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["name", "cpe", "vendor", "version"]
|
||||
|
||||
|
||||
class URL(_Observable):
|
||||
|
@ -681,9 +809,15 @@ class URL(_Observable):
|
|||
_type = 'url'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
||||
class UNIXAccountExt(_Extension):
|
||||
|
@ -710,6 +844,7 @@ class UserAccount(_Observable):
|
|||
_type = 'user-account'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('user_id', StringProperty()),
|
||||
('credential', StringProperty()),
|
||||
('account_login', StringProperty()),
|
||||
|
@ -725,7 +860,12 @@ class UserAccount(_Observable):
|
|||
('account_first_login', TimestampProperty()),
|
||||
('account_last_login', TimestampProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["account_type", "user_id", "account_login"]
|
||||
|
||||
|
||||
class WindowsRegistryValueType(_STIXBase):
|
||||
|
@ -767,14 +907,20 @@ class WindowsRegistryKey(_Observable):
|
|||
_type = 'windows-registry-key'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('key', StringProperty()),
|
||||
('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))),
|
||||
# this is not the modified timestamps of the object itself
|
||||
('modified', TimestampProperty()),
|
||||
('creator_user_ref', ObjectReferenceProperty(valid_types='user-account')),
|
||||
('modified_time', TimestampProperty()),
|
||||
('creator_user_ref', ReferenceProperty(valid_types='user-account', spec_version='2.1')),
|
||||
('number_of_subkeys', IntegerProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["key", "values"]
|
||||
|
||||
@property
|
||||
def values(self):
|
||||
|
@ -818,6 +964,7 @@ class X509Certificate(_Observable):
|
|||
_type = 'x509-certificate'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_self_signed', BooleanProperty()),
|
||||
('hashes', HashesProperty(spec_version='2.1')),
|
||||
('version', StringProperty()),
|
||||
|
@ -832,7 +979,12 @@ class X509Certificate(_Observable):
|
|||
('subject_public_key_exponent', IntegerProperty()),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtenstionsType)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "serial_number"]
|
||||
|
||||
|
||||
def CustomObservable(type='x-custom-observable', properties=None):
|
||||
|
|
323
stix2/v21/sdo.py
323
stix2/v21/sdo.py
|
@ -2,15 +2,18 @@
|
|||
|
||||
from collections import OrderedDict
|
||||
import itertools
|
||||
import warnings
|
||||
|
||||
from six.moves.urllib.parse import quote_plus
|
||||
|
||||
from ..core import STIXDomainObject
|
||||
from ..custom import _custom_object_builder
|
||||
from ..exceptions import PropertyPresenceError, STIXDeprecationWarning
|
||||
from ..properties import (
|
||||
BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
|
||||
ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
|
||||
StringProperty, TimestampProperty, TypeProperty,
|
||||
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
|
||||
FloatProperty, IDProperty, IntegerProperty, ListProperty,
|
||||
ObservableProperty, PatternProperty, ReferenceProperty, StringProperty,
|
||||
TimestampProperty, TypeProperty,
|
||||
)
|
||||
from ..utils import NOW
|
||||
from .common import ExternalReference, GranularMarking, KillChainPhase
|
||||
|
@ -26,19 +29,20 @@ class AttackPattern(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -53,8 +57,8 @@ class Campaign(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -68,12 +72,12 @@ class Campaign(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
super(Campaign, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
@ -93,21 +97,61 @@ class CourseOfAction(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('action_type', StringProperty()),
|
||||
('os_execution_envs', ListProperty(StringProperty)),
|
||||
('action_bin', BinaryProperty()),
|
||||
('action_reference', EmbeddedObjectProperty(ExternalReference)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(CourseOfAction, self)._check_object_constraints()
|
||||
|
||||
self._check_mutually_exclusive_properties(
|
||||
["action_bin", "action_reference"],
|
||||
at_least_one=False,
|
||||
)
|
||||
|
||||
|
||||
class Grouping(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_type = 'grouping'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('context', StringProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
])
|
||||
|
||||
|
||||
class Identity(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -119,8 +163,8 @@ class Identity(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -134,7 +178,7 @@ class Identity(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -149,15 +193,17 @@ class Indicator(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('indicator_types', ListProperty(StringProperty, required=True)),
|
||||
('pattern', PatternProperty(required=True)),
|
||||
('valid_from', TimestampProperty(default=lambda: NOW)),
|
||||
('pattern_type', StringProperty(required=True)),
|
||||
('pattern_version', StringProperty()),
|
||||
('valid_from', TimestampProperty(default=lambda: NOW, required=True)),
|
||||
('valid_until', TimestampProperty()),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -165,12 +211,12 @@ class Indicator(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
super(Indicator, self)._check_object_constraints()
|
||||
|
||||
valid_from = self.get('valid_from')
|
||||
valid_until = self.get('valid_until')
|
||||
|
@ -180,6 +226,47 @@ class Indicator(STIXDomainObject):
|
|||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class Infrastructure(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_type = 'infrastructure'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('infrastructure_types', ListProperty(StringProperty, required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(Infrastructure, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
||||
if first_seen and last_seen and last_seen < first_seen:
|
||||
msg = "{0.id} 'last_seen' must be greater than or equal to 'first_seen'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class IntrusionSet(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
|
@ -190,8 +277,8 @@ class IntrusionSet(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -208,12 +295,12 @@ class IntrusionSet(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
super(IntrusionSet, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
@ -233,10 +320,11 @@ class Location(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('latitude', FloatProperty(min=-90.0, max=90.0)),
|
||||
('longitude', FloatProperty(min=-180.0, max=180.0)),
|
||||
|
@ -252,12 +340,12 @@ class Location(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
super(Location, self)._check_object_constraints()
|
||||
|
||||
if self.get('precision') is not None:
|
||||
self._check_properties_dependency(['longitude', 'latitude'], ['precision'])
|
||||
|
@ -265,6 +353,20 @@ class Location(STIXDomainObject):
|
|||
self._check_properties_dependency(['latitude'], ['longitude'])
|
||||
self._check_properties_dependency(['longitude'], ['latitude'])
|
||||
|
||||
if not (
|
||||
'region' in self
|
||||
or 'country' in self
|
||||
or (
|
||||
'latitude' in self
|
||||
and 'longitude' in self
|
||||
)
|
||||
):
|
||||
raise PropertyPresenceError(
|
||||
"Location objects must have the properties 'region', "
|
||||
"'country', or 'latitude' and 'longitude'",
|
||||
Location,
|
||||
)
|
||||
|
||||
def to_maps_url(self, map_engine="Google Maps"):
|
||||
"""Return URL to this location in an online map engine.
|
||||
|
||||
|
@ -326,23 +428,91 @@ class Malware(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('malware_types', ListProperty(StringProperty, required=True)),
|
||||
('is_family', BooleanProperty(required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('os_execution_envs', ListProperty(StringProperty)),
|
||||
('architecture_execution_envs', ListProperty(StringProperty)),
|
||||
('implementation_languages', ListProperty(StringProperty)),
|
||||
('capabilities', ListProperty(StringProperty)),
|
||||
('sample_refs', ListProperty(ReferenceProperty(valid_types=['artifact', 'file'], spec_version='2.1'))),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(Malware, self)._check_object_constraints()
|
||||
|
||||
first_seen = self.get('first_seen')
|
||||
last_seen = self.get('last_seen')
|
||||
|
||||
if first_seen and last_seen and last_seen < first_seen:
|
||||
msg = "{0.id} 'last_seen' must be greater than or equal to 'first_seen'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
if self.is_family and "name" not in self:
|
||||
raise PropertyPresenceError(
|
||||
"'name' is a required property for malware families",
|
||||
Malware,
|
||||
)
|
||||
|
||||
|
||||
class MalwareAnalysis(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_type = 'malware-analysis'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('product', StringProperty(required=True)),
|
||||
('version', StringProperty()),
|
||||
('host_vm_ref', ReferenceProperty(valid_types='software', spec_version='2.1')),
|
||||
('operating_system_ref', ReferenceProperty(valid_types='software', spec_version='2.1')),
|
||||
('installed_software_refs', ListProperty(ReferenceProperty(valid_types='software', spec_version='2.1'))),
|
||||
('configuration_version', StringProperty()),
|
||||
('modules', ListProperty(StringProperty)),
|
||||
('analysis_engine_version', StringProperty()),
|
||||
('analysis_definition_version', StringProperty()),
|
||||
('submitted', TimestampProperty()),
|
||||
('analysis_started', TimestampProperty()),
|
||||
('analysis_ended', TimestampProperty()),
|
||||
('av_result', StringProperty()),
|
||||
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="only_SCO", spec_version='2.1'))),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(MalwareAnalysis, self)._check_object_constraints()
|
||||
|
||||
self._check_at_least_one_property(["av_result", "analysis_sco_refs"])
|
||||
|
||||
|
||||
class Note(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -354,20 +524,20 @@ class Note(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('abstract', StringProperty()),
|
||||
('content', StringProperty(required=True)),
|
||||
('authors', ListProperty(StringProperty)),
|
||||
('object_refs', ListProperty(ReferenceProperty, required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -382,20 +552,21 @@ class ObservedData(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('first_observed', TimestampProperty(required=True)),
|
||||
('last_observed', TimestampProperty(required=True)),
|
||||
('number_observed', IntegerProperty(min=1, max=999999999, required=True)),
|
||||
('objects', ObservableProperty(spec_version='2.1', required=True)),
|
||||
('objects', ObservableProperty(spec_version='2.1')),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types="only_SCO_&_SRO", spec_version="2.1"))),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -403,14 +574,17 @@ class ObservedData(STIXDomainObject):
|
|||
self.__allow_custom = kwargs.get('allow_custom', False)
|
||||
self._properties['objects'].allow_custom = kwargs.get('allow_custom', False)
|
||||
|
||||
if "objects" in kwargs:
|
||||
warnings.warn(
|
||||
"The 'objects' property of observed-data is deprecated in "
|
||||
"STIX 2.1.",
|
||||
STIXDeprecationWarning,
|
||||
)
|
||||
|
||||
super(ObservedData, self).__init__(*args, **kwargs)
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(self.__class__, self)._check_object_constraints()
|
||||
|
||||
if self.get('number_observed', 1) == 1:
|
||||
self._check_properties_dependency(['first_observed'], ['last_observed'])
|
||||
self._check_properties_dependency(['last_observed'], ['first_observed'])
|
||||
super(ObservedData, self)._check_object_constraints()
|
||||
|
||||
first_observed = self.get('first_observed')
|
||||
last_observed = self.get('last_observed')
|
||||
|
@ -419,6 +593,10 @@ class ObservedData(STIXDomainObject):
|
|||
msg = "{0.id} 'last_observed' must be greater than or equal to 'first_observed'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
self._check_mutually_exclusive_properties(
|
||||
["objects", "object_refs"],
|
||||
)
|
||||
|
||||
|
||||
class Opinion(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -430,13 +608,12 @@ class Opinion(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('explanation', StringProperty()),
|
||||
('authors', ListProperty(StringProperty)),
|
||||
('object_refs', ListProperty(ReferenceProperty, required=True)),
|
||||
(
|
||||
'opinion', EnumProperty(
|
||||
allowed=[
|
||||
|
@ -448,12 +625,13 @@ class Opinion(STIXDomainObject):
|
|||
], required=True,
|
||||
),
|
||||
),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -468,21 +646,21 @@ class Report(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('report_types', ListProperty(StringProperty, required=True)),
|
||||
('published', TimestampProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty, required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(invalid_types=[""], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -497,14 +675,16 @@ class ThreatActor(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('threat_actor_types', ListProperty(StringProperty, required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('roles', ListProperty(StringProperty)),
|
||||
('goals', ListProperty(StringProperty)),
|
||||
('sophistication', StringProperty()),
|
||||
|
@ -517,10 +697,20 @@ class ThreatActor(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
super(ThreatActor, self)._check_object_constraints()
|
||||
|
||||
first_observed = self.get('first_seen')
|
||||
last_observed = self.get('last_seen')
|
||||
|
||||
if first_observed and last_observed and last_observed < first_observed:
|
||||
msg = "{0.id} 'last_seen' must be greater than or equal to 'first_seen'"
|
||||
raise ValueError(msg.format(self))
|
||||
|
||||
|
||||
class Tool(STIXDomainObject):
|
||||
# TODO: Add link
|
||||
|
@ -532,13 +722,14 @@ class Tool(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('tool_types', ListProperty(StringProperty, required=True)),
|
||||
('aliases', ListProperty(StringProperty)),
|
||||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('tool_version', StringProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -546,7 +737,7 @@ class Tool(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -561,8 +752,8 @@ class Vulnerability(STIXDomainObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('name', StringProperty(required=True)),
|
||||
|
@ -572,7 +763,7 @@ class Vulnerability(STIXDomainObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -611,8 +802,8 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
[
|
||||
('type', TypeProperty(type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(type, spec_version='2.1')),
|
||||
('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')),
|
||||
],
|
||||
|
@ -623,7 +814,7 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
|
|
|
@ -17,18 +17,20 @@ class Relationship(STIXRelationshipObject):
|
|||
`the STIX 2.1 specification <link here>`__.
|
||||
"""
|
||||
|
||||
_invalid_source_target_types = ['bundle', 'language-content', 'marking-definition', 'relationship', 'sighting']
|
||||
|
||||
_type = 'relationship'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('relationship_type', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('source_ref', ReferenceProperty(required=True)),
|
||||
('target_ref', ReferenceProperty(required=True)),
|
||||
('source_ref', ReferenceProperty(invalid_types=_invalid_source_target_types, spec_version='2.1', required=True)),
|
||||
('target_ref', ReferenceProperty(invalid_types=_invalid_source_target_types, spec_version='2.1', required=True)),
|
||||
('start_time', TimestampProperty()),
|
||||
('stop_time', TimestampProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
|
@ -36,7 +38,7 @@ class Relationship(STIXRelationshipObject):
|
|||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
@ -76,23 +78,24 @@ class Sighting(STIXRelationshipObject):
|
|||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(type='identity')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('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')),
|
||||
('description', StringProperty()),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('count', IntegerProperty(min=0, max=999999999)),
|
||||
('sighting_of_ref', ReferenceProperty(required=True)),
|
||||
('observed_data_refs', ListProperty(ReferenceProperty(type='observed-data'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(type='identity'))),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="only_SDO", 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()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(type='marking-definition'))),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = "1.1.3"
|
||||
__version__ = "1.2.1"
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
"""
|
||||
|
||||
import functools
|
||||
import stix2
|
||||
from . import AttackPattern as _AttackPattern
|
||||
from . import Campaign as _Campaign
|
||||
|
@ -52,6 +53,11 @@ from . import ( # noqa: F401
|
|||
)
|
||||
from .datastore.filters import FilterSet
|
||||
|
||||
|
||||
# Enable some adaptation to the current default supported STIX version.
|
||||
_STIX_VID = "v" + stix2.DEFAULT_VERSION.replace(".", "")
|
||||
|
||||
|
||||
# Use an implicit MemoryStore
|
||||
_environ = Environment(store=MemoryStore())
|
||||
|
||||
|
@ -116,48 +122,39 @@ def _related_wrapper(self, *args, **kwargs):
|
|||
return _environ.related_to(self, *args, **kwargs)
|
||||
|
||||
|
||||
def _observed_data_init(self, *args, **kwargs):
|
||||
self.__allow_custom = kwargs.get('allow_custom', False)
|
||||
self._properties['objects'].allow_custom = kwargs.get('allow_custom', False)
|
||||
super(self.__class__, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def _constructor_wrapper(obj_type):
|
||||
# Use an intermediate wrapper class so the implicit environment will create objects that have our wrapper functions
|
||||
class_dict = dict(
|
||||
created_by=_created_by_wrapper,
|
||||
relationships=_relationships_wrapper,
|
||||
related=_related_wrapper,
|
||||
**obj_type.__dict__
|
||||
)
|
||||
|
||||
# Avoid TypeError about super() in ObservedData
|
||||
if 'ObservedData' in obj_type.__name__:
|
||||
class_dict['__init__'] = _observed_data_init
|
||||
|
||||
wrapped_type = type(obj_type.__name__, obj_type.__bases__, class_dict)
|
||||
|
||||
@staticmethod
|
||||
def new_constructor(cls, *args, **kwargs):
|
||||
x = _environ.create(wrapped_type, *args, **kwargs)
|
||||
return x
|
||||
return new_constructor
|
||||
|
||||
|
||||
def _setup_workbench():
|
||||
# Create wrapper classes whose constructors call the implicit environment's create()
|
||||
for obj_type in STIX_OBJS:
|
||||
new_class_dict = {
|
||||
'__new__': _constructor_wrapper(obj_type),
|
||||
'__doc__': 'Workbench wrapper around the `{0} <stix2.v20.sdo.rst#stix2.v20.sdo.{0}>`__ object. {1}'.format(obj_type.__name__, STIX_OBJ_DOCS),
|
||||
}
|
||||
new_class = type(obj_type.__name__, (), new_class_dict)
|
||||
|
||||
# Add our new class to this module's globals and to the library-wide mapping.
|
||||
# This allows parse() to use the wrapped classes.
|
||||
globals()[obj_type.__name__] = new_class
|
||||
stix2.OBJ_MAP[obj_type._type] = new_class
|
||||
new_class = None
|
||||
# The idea here was originally to dynamically create subclasses which
|
||||
# were cleverly customized such that instantiating them would actually
|
||||
# invoke _environ.create(). This turns out to be impossible, since
|
||||
# __new__ can never create the class in the normal way, since that
|
||||
# invokes __new__ again, resulting in infinite recursion. And
|
||||
# _environ.create() does exactly that.
|
||||
#
|
||||
# So instead, we create something "class-like", in that calling it
|
||||
# produces an instance of the desired class. But these things will
|
||||
# be functions instead of classes. One might think this trickery will
|
||||
# have undesirable side-effects, but actually it seems to work.
|
||||
# So far...
|
||||
new_class_dict = {
|
||||
'__doc__': 'Workbench wrapper around the `{0} <stix2.{1}.sdo.rst#stix2.{1}.sdo.{0}>`__ object. {2}'.format(
|
||||
obj_type.__name__,
|
||||
_STIX_VID,
|
||||
STIX_OBJ_DOCS,
|
||||
),
|
||||
'created_by': _created_by_wrapper,
|
||||
'relationships': _relationships_wrapper,
|
||||
'related': _related_wrapper,
|
||||
}
|
||||
|
||||
new_class = type(obj_type.__name__, (obj_type,), new_class_dict)
|
||||
factory_func = functools.partial(_environ.create, new_class)
|
||||
|
||||
# Add our new "class" to this module's globals and to the library-wide
|
||||
# mapping. This allows parse() to use the wrapped classes.
|
||||
globals()[obj_type.__name__] = factory_func
|
||||
stix2.OBJ_MAP[obj_type._type] = factory_func
|
||||
|
||||
|
||||
_setup_workbench()
|
||||
|
|
6
tox.ini
6
tox.ini
|
@ -9,11 +9,11 @@ deps =
|
|||
pytest-cov
|
||||
coverage
|
||||
taxii2-client
|
||||
pyjarowinkler
|
||||
haversine
|
||||
medallion
|
||||
commands =
|
||||
pytest --ignore=stix2/test/v20/test_workbench.py --ignore=stix2/test/v21/test_workbench.py --cov=stix2 stix2/test/ --cov-report term-missing
|
||||
pytest stix2/test/v20/test_workbench.py --cov=stix2 --cov-report term-missing --cov-append
|
||||
pytest stix2/test/v21/test_workbench.py --cov=stix2 --cov-report term-missing --cov-append
|
||||
python -m pytest --cov=stix2 stix2/test/ --cov-report term-missing -W ignore::stix2.exceptions.STIXDeprecationWarning
|
||||
|
||||
passenv = CI TRAVIS TRAVIS_*
|
||||
|
||||
|
|
Loading…
Reference in New Issue