Merge branch 'master' into parsing
commit
bc8bdccece
|
@ -59,3 +59,7 @@ target/
|
||||||
|
|
||||||
# Vim
|
# Vim
|
||||||
*.swp
|
*.swp
|
||||||
|
#
|
||||||
|
# PyCharm
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
|
|
@ -9,5 +9,7 @@ python:
|
||||||
- "3.6"
|
- "3.6"
|
||||||
install:
|
install:
|
||||||
- pip install -U pip setuptools
|
- pip install -U pip setuptools
|
||||||
- pip install tox-travis
|
- pip install tox-travis pre-commit
|
||||||
script: tox
|
script:
|
||||||
|
- tox
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION != 2.6 ]]; then pre-commit run --all-files; fi
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
extensions = []
|
extensions = [
|
||||||
|
'sphinx-prompt',
|
||||||
|
]
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
source_suffix = '.rst'
|
source_suffix = '.rst'
|
||||||
master_doc = 'index'
|
master_doc = 'index'
|
||||||
|
|
|
@ -21,14 +21,24 @@ Setting up a development environment
|
||||||
We recommend using a `virtualenv <https://virtualenv.pypa.io/en/stable/>`_.
|
We recommend using a `virtualenv <https://virtualenv.pypa.io/en/stable/>`_.
|
||||||
|
|
||||||
1. Clone the repository. If you're planning to make pull request, you should fork
|
1. Clone the repository. If you're planning to make pull request, you should fork
|
||||||
the repository on GitHub and clone your fork instead of the main repo::
|
the repository on GitHub and clone your fork instead of the main repo:
|
||||||
|
|
||||||
$ git clone https://github.com/yourusername/cti-python-stix2.git
|
.. prompt:: bash
|
||||||
|
|
||||||
2. Install develoment-related dependencies::
|
git clone https://github.com/yourusername/cti-python-stix2.git
|
||||||
|
|
||||||
$ cd cti-python-stix2
|
2. Install develoment-related dependencies:
|
||||||
$ pip install -r requirements.txt
|
|
||||||
|
.. prompt:: bash
|
||||||
|
|
||||||
|
cd cti-python-stix2
|
||||||
|
pip install -r requirements.txt
|
||||||
|
|
||||||
|
3. Install `pre-commit <http://pre-commit.com/#usage>`_ git hooks:
|
||||||
|
|
||||||
|
.. prompt:: bash
|
||||||
|
|
||||||
|
pre-commit install
|
||||||
|
|
||||||
At this point you should be able to make changes to the code.
|
At this point you should be able to make changes to the code.
|
||||||
|
|
||||||
|
@ -55,15 +65,19 @@ or implements the features. Any code contributions to python-stix2 should come
|
||||||
with new or updated tests.
|
with new or updated tests.
|
||||||
|
|
||||||
To run the tests in your current Python environment, use the ``pytest`` command
|
To run the tests in your current Python environment, use the ``pytest`` command
|
||||||
from the root project directory::
|
from the root project directory:
|
||||||
|
|
||||||
$ pytest
|
.. prompt:: bash
|
||||||
|
|
||||||
|
pytest
|
||||||
|
|
||||||
This should show all of the tests that ran, along with their status.
|
This should show all of the tests that ran, along with their status.
|
||||||
|
|
||||||
You can run a specific test file by passing it on the command line::
|
You can run a specific test file by passing it on the command line:
|
||||||
|
|
||||||
$ pytest stix2/test/test_<xxx>.py
|
.. prompt:: bash
|
||||||
|
|
||||||
|
pytest stix2/test/test_<xxx>.py
|
||||||
|
|
||||||
To ensure that the test you wrote is running, you can deliberately add an
|
To ensure that the test you wrote is running, you can deliberately add an
|
||||||
``assert False`` statement at the beginning of the test. This is another benefit
|
``assert False`` statement at the beginning of the test. This is another benefit
|
||||||
|
@ -73,18 +87,22 @@ run) before making it pass.
|
||||||
`tox <https://tox.readthedocs.io/en/latest/>`_ allows you to test a package
|
`tox <https://tox.readthedocs.io/en/latest/>`_ allows you to test a package
|
||||||
across multiple versions of Python. Setting up multiple Python environments is
|
across multiple versions of Python. Setting up multiple Python environments is
|
||||||
beyond the scope of this guide, but feel free to ask for help setting them up.
|
beyond the scope of this guide, but feel free to ask for help setting them up.
|
||||||
Tox should be run from the root directory of the project:::
|
Tox should be run from the root directory of the project:
|
||||||
|
|
||||||
$ tox
|
.. prompt:: bash
|
||||||
|
|
||||||
|
tox
|
||||||
|
|
||||||
We aim for high test coverage, using the `coverage.py
|
We aim for high test coverage, using the `coverage.py
|
||||||
<http://coverage.readthedocs.io/en/latest/>`_ library. Though it's not an
|
<http://coverage.readthedocs.io/en/latest/>`_ library. Though it's not an
|
||||||
absolute requirement to maintain 100% coverage, all code contributions must
|
absolute requirement to maintain 100% coverage, all code contributions must
|
||||||
be accompanied by tests. To run coverage and look for untested lines of code,
|
be accompanied by tests. To run coverage and look for untested lines of code,
|
||||||
run::
|
run:
|
||||||
|
|
||||||
$ pytest --cov=stix2
|
.. prompt:: bash
|
||||||
$ coverage html
|
|
||||||
|
pytest --cov=stix2
|
||||||
|
coverage html
|
||||||
|
|
||||||
then look at the resulting report in ``htmlcov/index.html``.
|
then look at the resulting report in ``htmlcov/index.html``.
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ pre-commit
|
||||||
pytest
|
pytest
|
||||||
pytest-cov
|
pytest-cov
|
||||||
sphinx
|
sphinx
|
||||||
|
sphinx-prompt
|
||||||
tox
|
tox
|
||||||
|
|
||||||
-e .
|
-e .
|
||||||
|
|
|
@ -9,7 +9,8 @@ from .common import ExternalReference, KillChainPhase
|
||||||
from .sdo import AttackPattern, Campaign, CourseOfAction, Identity, Indicator, \
|
from .sdo import AttackPattern, Campaign, CourseOfAction, Identity, Indicator, \
|
||||||
IntrusionSet, Malware, ObservedData, Report, ThreatActor, Tool, \
|
IntrusionSet, Malware, ObservedData, Report, ThreatActor, Tool, \
|
||||||
Vulnerability
|
Vulnerability
|
||||||
from .sro import Relationship
|
from .sro import Relationship, Sighting
|
||||||
|
from .markings import MarkingDefinition, GranularMarking, StatementMarking, TLPMarking
|
||||||
|
|
||||||
|
|
||||||
def parse(data):
|
def parse(data):
|
||||||
|
|
|
@ -71,6 +71,11 @@ class _STIXBase(collections.Mapping):
|
||||||
|
|
||||||
self._inner = kwargs
|
self._inner = kwargs
|
||||||
|
|
||||||
|
if self.granular_markings:
|
||||||
|
for m in self.granular_markings:
|
||||||
|
# TODO: check selectors
|
||||||
|
pass
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self._inner[key]
|
return self._inner[key]
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""STIX 2 Common Data Types and Properties"""
|
"""STIX 2 Common Data Types and Properties"""
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .properties import (Property, BooleanProperty, ReferenceProperty,
|
from .properties import (Property, ListProperty, StringProperty, BooleanProperty,
|
||||||
StringProperty, TimestampProperty)
|
ReferenceProperty, TimestampProperty)
|
||||||
from .utils import NOW
|
from .utils import NOW
|
||||||
|
|
||||||
COMMON_PROPERTIES = {
|
COMMON_PROPERTIES = {
|
||||||
|
@ -11,10 +11,9 @@ COMMON_PROPERTIES = {
|
||||||
'modified': TimestampProperty(default=lambda: NOW),
|
'modified': TimestampProperty(default=lambda: NOW),
|
||||||
'external_references': Property(),
|
'external_references': Property(),
|
||||||
'revoked': BooleanProperty(),
|
'revoked': BooleanProperty(),
|
||||||
'created_by_ref': ReferenceProperty(),
|
'created_by_ref': ReferenceProperty(type="identity"),
|
||||||
# TODO:
|
'object_marking_refs': ListProperty(ReferenceProperty, element_type="marking-definition"),
|
||||||
# - object_marking_refs
|
'granular_markings': ListProperty(Property)
|
||||||
# - granular_markings
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
"""STIX 2.0 Marking Objects"""
|
||||||
|
|
||||||
|
from .base import _STIXBase
|
||||||
|
from .properties import IDProperty, TypeProperty, ListProperty, ReferenceProperty, Property, SelectorProperty
|
||||||
|
from .utils import NOW
|
||||||
|
|
||||||
|
|
||||||
|
class MarkingDefinition(_STIXBase):
|
||||||
|
_type = 'marking-definition'
|
||||||
|
_properties = {
|
||||||
|
'created': Property(default=lambda: NOW),
|
||||||
|
'external_references': Property(),
|
||||||
|
'created_by_ref': ReferenceProperty(type="identity"),
|
||||||
|
'object_marking_refs': ListProperty(ReferenceProperty, element_type="marking-definition"),
|
||||||
|
'granular_marking': ListProperty(Property, element_type="granular-marking"),
|
||||||
|
'type': TypeProperty(_type),
|
||||||
|
'id': IDProperty(_type),
|
||||||
|
'definition_type': Property(),
|
||||||
|
'definition': Property(),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class GranularMarking(_STIXBase):
|
||||||
|
_properties = {
|
||||||
|
'marking_ref': ReferenceProperty(required=True, type="marking-definition"),
|
||||||
|
'selectors': ListProperty(SelectorProperty, required=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TLPMarking(_STIXBase):
|
||||||
|
# TODO: don't allow the creation of any other TLPMarkings than the ones below
|
||||||
|
_properties = {
|
||||||
|
'tlp': Property(required=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StatementMarking(_STIXBase):
|
||||||
|
_properties = {
|
||||||
|
'statement': Property(required=True)
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, statement=None, **kwargs):
|
||||||
|
# Allow statement as positional args.
|
||||||
|
if statement and not kwargs.get('statement'):
|
||||||
|
kwargs['statement'] = statement
|
||||||
|
|
||||||
|
super(StatementMarking, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
TLP_WHITE = MarkingDefinition(
|
||||||
|
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="white")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_GREEN = MarkingDefinition(
|
||||||
|
id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="green")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_AMBER = MarkingDefinition(
|
||||||
|
id="marking-definition--f88d31f6-486f-44da-b317-01333bde0b82",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="amber")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_RED = MarkingDefinition(
|
||||||
|
id="marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="red")
|
||||||
|
)
|
|
@ -4,6 +4,7 @@ from six import PY2
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import pytz
|
import pytz
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
|
from .base import _STIXBase
|
||||||
|
|
||||||
|
|
||||||
class Property(object):
|
class Property(object):
|
||||||
|
@ -54,8 +55,9 @@ class Property(object):
|
||||||
raise ValueError("must equal '{0}'.".format(self._fixed_value))
|
raise ValueError("must equal '{0}'.".format(self._fixed_value))
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __init__(self, required=False, fixed=None, clean=None, default=None):
|
def __init__(self, required=False, fixed=None, clean=None, default=None, type=None):
|
||||||
self.required = required
|
self.required = required
|
||||||
|
self.type = type
|
||||||
if fixed:
|
if fixed:
|
||||||
self._fixed_value = fixed
|
self._fixed_value = fixed
|
||||||
self.validate = self._default_validate
|
self.validate = self._default_validate
|
||||||
|
@ -78,7 +80,7 @@ class Property(object):
|
||||||
|
|
||||||
class ListProperty(Property):
|
class ListProperty(Property):
|
||||||
|
|
||||||
def __init__(self, contained, **kwargs):
|
def __init__(self, contained, element_type=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
contained should be a type whose constructor creates an object from the value
|
contained should be a type whose constructor creates an object from the value
|
||||||
"""
|
"""
|
||||||
|
@ -88,6 +90,7 @@ class ListProperty(Property):
|
||||||
self.contained = bool
|
self.contained = bool
|
||||||
else:
|
else:
|
||||||
self.contained = contained
|
self.contained = contained
|
||||||
|
self.element_type = element_type
|
||||||
super(ListProperty, self).__init__(**kwargs)
|
super(ListProperty, self).__init__(**kwargs)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
|
@ -118,10 +121,18 @@ class ListProperty(Property):
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ValueError("must be an iterable.")
|
raise ValueError("must be an iterable.")
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for item in value:
|
||||||
try:
|
try:
|
||||||
return [self.contained(**x) if type(x) is dict else self.contained(x) for x in value]
|
if type(item) is dict:
|
||||||
|
result.append(self.contained(**item))
|
||||||
|
elif isinstance(item, ReferenceProperty):
|
||||||
|
result.append(self.contained(type=self.element_type))
|
||||||
|
else:
|
||||||
|
result.append(self.contained(item))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ValueError("the type of objects in the list must have a constructor that creates an object from the value.")
|
raise ValueError("the type of objects in the list must have a constructor that creates an object from the value.")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class StringProperty(Property):
|
class StringProperty(Property):
|
||||||
|
@ -145,7 +156,6 @@ class StringProperty(Property):
|
||||||
|
|
||||||
|
|
||||||
class TypeProperty(Property):
|
class TypeProperty(Property):
|
||||||
|
|
||||||
def __init__(self, type):
|
def __init__(self, type):
|
||||||
super(TypeProperty, self).__init__(fixed=type)
|
super(TypeProperty, self).__init__(fixed=type)
|
||||||
|
|
||||||
|
@ -226,9 +236,33 @@ REF_REGEX = re.compile("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}"
|
||||||
|
|
||||||
|
|
||||||
class ReferenceProperty(Property):
|
class ReferenceProperty(Property):
|
||||||
# TODO: support references that must be to a specific object type
|
def __init__(self, required=False, type=None):
|
||||||
|
"""
|
||||||
|
references sometimes must be to a specific object type
|
||||||
|
"""
|
||||||
|
self.type = type
|
||||||
|
super(ReferenceProperty, self).__init__(required, type=type)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
|
if isinstance(value, _STIXBase):
|
||||||
|
value = value.id
|
||||||
|
if self.type:
|
||||||
|
if not value.startswith(self.type):
|
||||||
|
raise ValueError("must start with '{0}'.".format(self.type))
|
||||||
if not REF_REGEX.match(value):
|
if not REF_REGEX.match(value):
|
||||||
raise ValueError("must match <object-type>--<guid>.")
|
raise ValueError("must match <object-type>--<guid>.")
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
SELECTOR_REGEX = re.compile("^[a-z0-9_-]{3,250}(\\.(\\[\\d+\\]|[a-z0-9_-]{1,250}))*$")
|
||||||
|
|
||||||
|
|
||||||
|
class SelectorProperty(Property):
|
||||||
|
def __init__(self, type=None):
|
||||||
|
# ignore type
|
||||||
|
super(SelectorProperty, self).__init__()
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
if not SELECTOR_REGEX.match(value):
|
||||||
|
raise ValueError("values must adhere to selector syntax")
|
||||||
|
return value
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .common import COMMON_PROPERTIES, KillChainPhase
|
from .common import COMMON_PROPERTIES, KillChainPhase
|
||||||
from .properties import (StringProperty, IDProperty, ListProperty,
|
from .properties import (Property, ListProperty, StringProperty, TypeProperty,
|
||||||
TypeProperty, Property)
|
IDProperty, ReferenceProperty)
|
||||||
from .utils import NOW
|
from .utils import NOW
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class Report(_STIXBase):
|
||||||
'name': Property(required=True),
|
'name': Property(required=True),
|
||||||
'description': Property(),
|
'description': Property(),
|
||||||
'published': Property(),
|
'published': Property(),
|
||||||
'object_refs': Property(),
|
'object_refs': ListProperty(ReferenceProperty),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
42
stix2/sro.py
42
stix2/sro.py
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .common import COMMON_PROPERTIES
|
from .common import COMMON_PROPERTIES
|
||||||
from .properties import IDProperty, TypeProperty, Property
|
from .properties import IDProperty, TypeProperty, ReferenceProperty, ListProperty, Property
|
||||||
|
|
||||||
|
|
||||||
class Relationship(_STIXBase):
|
class Relationship(_STIXBase):
|
||||||
|
@ -13,8 +13,9 @@ class Relationship(_STIXBase):
|
||||||
'id': IDProperty(_type),
|
'id': IDProperty(_type),
|
||||||
'type': TypeProperty(_type),
|
'type': TypeProperty(_type),
|
||||||
'relationship_type': Property(required=True),
|
'relationship_type': Property(required=True),
|
||||||
'source_ref': Property(required=True),
|
'description': Property(),
|
||||||
'target_ref': Property(required=True),
|
'source_ref': ReferenceProperty(required=True),
|
||||||
|
'target_ref': ReferenceProperty(required=True),
|
||||||
})
|
})
|
||||||
|
|
||||||
# Explicitly define the first three kwargs to make readable Relationship declarations.
|
# Explicitly define the first three kwargs to make readable Relationship declarations.
|
||||||
|
@ -31,12 +32,31 @@ class Relationship(_STIXBase):
|
||||||
if target_ref and not kwargs.get('target_ref'):
|
if target_ref and not kwargs.get('target_ref'):
|
||||||
kwargs['target_ref'] = target_ref
|
kwargs['target_ref'] = target_ref
|
||||||
|
|
||||||
# If actual STIX objects (vs. just the IDs) are passed in, extract the
|
|
||||||
# ID values to use in the Relationship object.
|
|
||||||
if kwargs.get('source_ref') and isinstance(kwargs['source_ref'], _STIXBase):
|
|
||||||
kwargs['source_ref'] = kwargs['source_ref'].id
|
|
||||||
|
|
||||||
if kwargs.get('target_ref') and isinstance(kwargs['target_ref'], _STIXBase):
|
|
||||||
kwargs['target_ref'] = kwargs['target_ref'].id
|
|
||||||
|
|
||||||
super(Relationship, self).__init__(**kwargs)
|
super(Relationship, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class Sighting(_STIXBase):
|
||||||
|
_type = 'sighting'
|
||||||
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
|
'type': TypeProperty(_type),
|
||||||
|
'first_seen': Property(),
|
||||||
|
'last_seen': Property(),
|
||||||
|
'count': Property(),
|
||||||
|
'sighting_of_ref': ReferenceProperty(required=True),
|
||||||
|
'observed_data_refs': ListProperty(ReferenceProperty, element_type="observed-data"),
|
||||||
|
'where_sighted_refs': ListProperty(ReferenceProperty, element_type="identity"),
|
||||||
|
'summary': Property(),
|
||||||
|
})
|
||||||
|
|
||||||
|
# Explicitly define the first kwargs to make readable Sighting declarations.
|
||||||
|
def __init__(self, sighting_of_ref=None, **kwargs):
|
||||||
|
# TODO:
|
||||||
|
# - description
|
||||||
|
|
||||||
|
# Allow sighting_of_ref as a positional arg.
|
||||||
|
if sighting_of_ref and not kwargs.get('sighting_of_ref'):
|
||||||
|
kwargs['sighting_of_ref'] = sighting_of_ref
|
||||||
|
|
||||||
|
super(Sighting, self).__init__(**kwargs)
|
||||||
|
|
|
@ -8,6 +8,7 @@ INDICATOR_ID = "indicator--01234567-89ab-cdef-0123-456789abcdef"
|
||||||
MALWARE_ID = "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
MALWARE_ID = "malware--fedcba98-7654-3210-fedc-ba9876543210"
|
||||||
RELATIONSHIP_ID = "relationship--00000000-1111-2222-3333-444444444444"
|
RELATIONSHIP_ID = "relationship--00000000-1111-2222-3333-444444444444"
|
||||||
IDENTITY_ID = "identity--d4d765ce-cff7-40e8-b7a6-e205d005ac2c"
|
IDENTITY_ID = "identity--d4d765ce-cff7-40e8-b7a6-e205d005ac2c"
|
||||||
|
SIGHTING_ID = "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb"
|
||||||
|
|
||||||
# Minimum required args for an Indicator instance
|
# Minimum required args for an Indicator instance
|
||||||
INDICATOR_KWARGS = dict(
|
INDICATOR_KWARGS = dict(
|
||||||
|
@ -27,3 +28,7 @@ RELATIONSHIP_KWARGS = dict(
|
||||||
source_ref=INDICATOR_ID,
|
source_ref=INDICATOR_ID,
|
||||||
target_ref=MALWARE_ID,
|
target_ref=MALWARE_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SIGHTING_KWARGS = dict(
|
||||||
|
sighting_of_ref=INDICATOR_ID,
|
||||||
|
)
|
||||||
|
|
|
@ -2,8 +2,6 @@ import pytest
|
||||||
|
|
||||||
import stix2
|
import stix2
|
||||||
|
|
||||||
from .fixtures import clock, uuid4, indicator, malware, relationship # noqa: F401
|
|
||||||
|
|
||||||
EXPECTED_BUNDLE = """{
|
EXPECTED_BUNDLE = """{
|
||||||
"id": "bundle--00000000-0000-0000-0000-000000000004",
|
"id": "bundle--00000000-0000-0000-0000-000000000004",
|
||||||
"objects": [
|
"objects": [
|
||||||
|
@ -73,13 +71,13 @@ def test_bundle_with_wrong_spec_version():
|
||||||
assert str(excinfo.value) == "Invalid value for Bundle 'spec_version': must equal '2.0'."
|
assert str(excinfo.value) == "Invalid value for Bundle 'spec_version': must equal '2.0'."
|
||||||
|
|
||||||
|
|
||||||
def test_create_bundle(indicator, malware, relationship): # noqa: F811
|
def test_create_bundle(indicator, malware, relationship):
|
||||||
bundle = stix2.Bundle(objects=[indicator, malware, relationship])
|
bundle = stix2.Bundle(objects=[indicator, malware, relationship])
|
||||||
|
|
||||||
assert str(bundle) == EXPECTED_BUNDLE
|
assert str(bundle) == EXPECTED_BUNDLE
|
||||||
|
|
||||||
|
|
||||||
def test_create_bundle_with_positional_args(indicator, malware, relationship): # noqa: F811
|
def test_create_bundle_with_positional_args(indicator, malware, relationship):
|
||||||
bundle = stix2.Bundle(indicator, malware, relationship)
|
bundle = stix2.Bundle(indicator, malware, relationship)
|
||||||
|
|
||||||
assert str(bundle) == EXPECTED_BUNDLE
|
assert str(bundle) == EXPECTED_BUNDLE
|
||||||
|
|
|
@ -2,14 +2,13 @@ import datetime as dt
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from .constants import FAKE_TIME
|
from .constants import FAKE_TIME
|
||||||
from .fixtures import clock, uuid4 # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
def test_clock(clock): # noqa: F811
|
def test_clock(clock):
|
||||||
assert dt.datetime.now() == FAKE_TIME
|
assert dt.datetime.now() == FAKE_TIME
|
||||||
|
|
||||||
|
|
||||||
def test_my_uuid4_fixture(uuid4): # noqa: F811
|
def test_my_uuid4_fixture(uuid4):
|
||||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000001"
|
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000001"
|
||||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000002"
|
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000002"
|
||||||
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000003"
|
assert uuid.uuid4() == "00000000-0000-0000-0000-000000000003"
|
||||||
|
|
|
@ -6,7 +6,6 @@ import pytz
|
||||||
import stix2
|
import stix2
|
||||||
|
|
||||||
from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS
|
from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS
|
||||||
from .fixtures import clock, uuid4, indicator # noqa: F401
|
|
||||||
|
|
||||||
EXPECTED_INDICATOR = """{
|
EXPECTED_INDICATOR = """{
|
||||||
"created": "2017-01-01T00:00:01Z",
|
"created": "2017-01-01T00:00:01Z",
|
||||||
|
@ -49,7 +48,7 @@ def test_indicator_with_all_required_fields():
|
||||||
assert repr(ind) == EXPECTED_INDICATOR_REPR
|
assert repr(ind) == EXPECTED_INDICATOR_REPR
|
||||||
|
|
||||||
|
|
||||||
def test_indicator_autogenerated_fields(indicator): # noqa: F811
|
def test_indicator_autogenerated_fields(indicator):
|
||||||
assert indicator.type == 'indicator'
|
assert indicator.type == 'indicator'
|
||||||
assert indicator.id == 'indicator--00000000-0000-0000-0000-000000000001'
|
assert indicator.id == 'indicator--00000000-0000-0000-0000-000000000001'
|
||||||
assert indicator.created == FAKE_TIME
|
assert indicator.created == FAKE_TIME
|
||||||
|
@ -96,7 +95,7 @@ def test_indicator_required_field_pattern():
|
||||||
def test_indicator_created_ref_invalid_format():
|
def test_indicator_created_ref_invalid_format():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
stix2.Indicator(created_by_ref='myprefix--12345678', **INDICATOR_KWARGS)
|
stix2.Indicator(created_by_ref='myprefix--12345678', **INDICATOR_KWARGS)
|
||||||
assert str(excinfo.value) == "Invalid value for Indicator 'created_by_ref': must match <object-type>--<guid>."
|
assert str(excinfo.value) == "Invalid value for Indicator 'created_by_ref': must start with 'identity'."
|
||||||
|
|
||||||
|
|
||||||
def test_indicator_revoked_invalid():
|
def test_indicator_revoked_invalid():
|
||||||
|
@ -105,7 +104,7 @@ def test_indicator_revoked_invalid():
|
||||||
assert str(excinfo.value) == "Invalid value for Indicator 'revoked': must be a boolean value."
|
assert str(excinfo.value) == "Invalid value for Indicator 'revoked': must be a boolean value."
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_assign_to_indicator_attributes(indicator): # noqa: F811
|
def test_cannot_assign_to_indicator_attributes(indicator):
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
indicator.valid_from = dt.datetime.now()
|
indicator.valid_from = dt.datetime.now()
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import re
|
||||||
import stix2
|
import stix2
|
||||||
|
|
||||||
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
|
||||||
from .fixtures import clock, uuid4, malware # noqa: F401
|
|
||||||
|
|
||||||
EXPECTED_MALWARE = """{
|
EXPECTED_MALWARE = """{
|
||||||
"created": "2016-05-12T08:17:27Z",
|
"created": "2016-05-12T08:17:27Z",
|
||||||
|
@ -36,7 +35,7 @@ def test_malware_with_all_required_fields():
|
||||||
assert str(mal) == EXPECTED_MALWARE
|
assert str(mal) == EXPECTED_MALWARE
|
||||||
|
|
||||||
|
|
||||||
def test_malware_autogenerated_fields(malware): # noqa: F811
|
def test_malware_autogenerated_fields(malware):
|
||||||
assert malware.type == 'malware'
|
assert malware.type == 'malware'
|
||||||
assert malware.id == 'malware--00000000-0000-0000-0000-000000000001'
|
assert malware.id == 'malware--00000000-0000-0000-0000-000000000001'
|
||||||
assert malware.created == FAKE_TIME
|
assert malware.created == FAKE_TIME
|
||||||
|
@ -78,7 +77,7 @@ def test_malware_required_field_name():
|
||||||
assert str(excinfo.value) == "Missing required field(s) for Malware: (name)."
|
assert str(excinfo.value) == "Missing required field(s) for Malware: (name)."
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_assign_to_malware_attributes(malware): # noqa: F811
|
def test_cannot_assign_to_malware_attributes(malware):
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
malware.name = "Cryptolocker II"
|
malware.name = "Cryptolocker II"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
import stix2
|
||||||
|
from stix2.markings import TLP_WHITE
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
EXPECTED_TLP_MARKING_DEFINITION = """{
|
||||||
|
"created": "2017-01-20T00:00:00.000Z",
|
||||||
|
"definition": {
|
||||||
|
"tlp": "white"
|
||||||
|
},
|
||||||
|
"definition_type": "tlp",
|
||||||
|
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
"type": "marking-definition"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
EXPECTED_STATEMENT_MARKING_DEFINITION = """{
|
||||||
|
"created": "2017-01-20T00:00:00.000Z",
|
||||||
|
"definition": {
|
||||||
|
"statement": "Copyright 2016, Example Corp"
|
||||||
|
},
|
||||||
|
"definition_type": "statement",
|
||||||
|
"id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
"type": "marking-definition"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
EXPECTED_GRANULAR_MARKING = """{
|
||||||
|
"marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
"selectors": [
|
||||||
|
"abc",
|
||||||
|
"abc.[23]",
|
||||||
|
"abc.def",
|
||||||
|
"abc.[2].efg"
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS = """{
|
||||||
|
"created": "2016-04-06T20:03:00.000Z",
|
||||||
|
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||||
|
"granular_markings": [
|
||||||
|
{
|
||||||
|
"marking_ref": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
"selectors": [
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
"modified": "2016-04-06T20:03:00.000Z",
|
||||||
|
"name": "Green Group Attacks Against Finance",
|
||||||
|
"type": "campaign"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_marking_def_example_with_tlp():
|
||||||
|
assert str(TLP_WHITE) == EXPECTED_TLP_MARKING_DEFINITION
|
||||||
|
|
||||||
|
|
||||||
|
def test_marking_def_example_with_statement():
|
||||||
|
marking_definition = stix2.MarkingDefinition(
|
||||||
|
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="statement",
|
||||||
|
definition=stix2.StatementMarking(statement="Copyright 2016, Example Corp")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(marking_definition) == EXPECTED_STATEMENT_MARKING_DEFINITION
|
||||||
|
|
||||||
|
|
||||||
|
def test_marking_def_example_with_positional_statement():
|
||||||
|
marking_definition = stix2.MarkingDefinition(
|
||||||
|
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="statement",
|
||||||
|
definition=stix2.StatementMarking("Copyright 2016, Example Corp")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(marking_definition) == EXPECTED_STATEMENT_MARKING_DEFINITION
|
||||||
|
|
||||||
|
|
||||||
|
def test_granular_example():
|
||||||
|
granular_marking = stix2.GranularMarking(
|
||||||
|
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
selectors=["abc", "abc.[23]", "abc.def", "abc.[2].efg"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(granular_marking) == EXPECTED_GRANULAR_MARKING
|
||||||
|
|
||||||
|
|
||||||
|
def test_granular_example_with_bad_selector():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.GranularMarking(
|
||||||
|
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
selectors=["abc[0]"] # missing "."
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid value for GranularMarking 'selectors': values must adhere to selector syntax"
|
||||||
|
|
||||||
|
|
||||||
|
def test_campaign_with_granular_markings_example():
|
||||||
|
campaign = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector.",
|
||||||
|
granular_markings=[
|
||||||
|
stix2.GranularMarking(
|
||||||
|
marking_ref="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
selectors=["description"])
|
||||||
|
])
|
||||||
|
print(str(campaign))
|
||||||
|
assert str(campaign) == EXPECTED_CAMPAIGN_WITH_GRANULAR_MARKINGS
|
||||||
|
|
||||||
|
# TODO: Add other examples
|
|
@ -7,7 +7,6 @@ import stix2
|
||||||
|
|
||||||
from .constants import FAKE_TIME, INDICATOR_ID, MALWARE_ID, RELATIONSHIP_ID
|
from .constants import FAKE_TIME, INDICATOR_ID, MALWARE_ID, RELATIONSHIP_ID
|
||||||
from .constants import RELATIONSHIP_KWARGS
|
from .constants import RELATIONSHIP_KWARGS
|
||||||
from .fixtures import clock, uuid4, indicator, malware, relationship # noqa: F401
|
|
||||||
|
|
||||||
|
|
||||||
EXPECTED_RELATIONSHIP = """{
|
EXPECTED_RELATIONSHIP = """{
|
||||||
|
@ -36,7 +35,7 @@ def test_relationship_all_required_fields():
|
||||||
assert str(rel) == EXPECTED_RELATIONSHIP
|
assert str(rel) == EXPECTED_RELATIONSHIP
|
||||||
|
|
||||||
|
|
||||||
def test_relationship_autogenerated_fields(relationship): # noqa: F811
|
def test_relationship_autogenerated_fields(relationship):
|
||||||
assert relationship.type == 'relationship'
|
assert relationship.type == 'relationship'
|
||||||
assert relationship.id == 'relationship--00000000-0000-0000-0000-000000000001'
|
assert relationship.id == 'relationship--00000000-0000-0000-0000-000000000001'
|
||||||
assert relationship.created == FAKE_TIME
|
assert relationship.created == FAKE_TIME
|
||||||
|
@ -89,7 +88,7 @@ def test_relationship_required_field_target_ref():
|
||||||
assert str(excinfo.value) == "Missing required field(s) for Relationship: (target_ref)."
|
assert str(excinfo.value) == "Missing required field(s) for Relationship: (target_ref)."
|
||||||
|
|
||||||
|
|
||||||
def test_cannot_assign_to_relationship_attributes(relationship): # noqa: F811
|
def test_cannot_assign_to_relationship_attributes(relationship):
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
relationship.relationship_type = "derived-from"
|
relationship.relationship_type = "derived-from"
|
||||||
|
|
||||||
|
@ -102,7 +101,7 @@ def test_invalid_kwarg_to_relationship():
|
||||||
assert str(excinfo.value) == "unexpected keyword arguments: ['my_custom_property']" in str(excinfo)
|
assert str(excinfo.value) == "unexpected keyword arguments: ['my_custom_property']" in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
def test_create_relationship_from_objects_rather_than_ids(indicator, malware): # noqa: F811
|
def test_create_relationship_from_objects_rather_than_ids(indicator, malware):
|
||||||
rel = stix2.Relationship(
|
rel = stix2.Relationship(
|
||||||
relationship_type="indicates",
|
relationship_type="indicates",
|
||||||
source_ref=indicator,
|
source_ref=indicator,
|
||||||
|
@ -115,7 +114,7 @@ def test_create_relationship_from_objects_rather_than_ids(indicator, malware):
|
||||||
assert rel.id == 'relationship--00000000-0000-0000-0000-000000000003'
|
assert rel.id == 'relationship--00000000-0000-0000-0000-000000000003'
|
||||||
|
|
||||||
|
|
||||||
def test_create_relationship_with_positional_args(indicator, malware): # noqa: F811
|
def test_create_relationship_with_positional_args(indicator, malware):
|
||||||
rel = stix2.Relationship(indicator, 'indicates', malware)
|
rel = stix2.Relationship(indicator, 'indicates', malware)
|
||||||
|
|
||||||
assert rel.relationship_type == 'indicates'
|
assert rel.relationship_type == 'indicates'
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import stix2
|
import stix2
|
||||||
|
import pytest
|
||||||
|
from .constants import INDICATOR_KWARGS
|
||||||
|
|
||||||
EXPECTED = """{
|
EXPECTED = """{
|
||||||
"created": "2015-12-21T19:59:11Z",
|
"created": "2015-12-21T19:59:11Z",
|
||||||
|
@ -39,4 +41,45 @@ def test_report_example():
|
||||||
|
|
||||||
assert str(report) == EXPECTED
|
assert str(report) == EXPECTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_example_objects_in_object_refs():
|
||||||
|
report = stix2.Report(
|
||||||
|
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||||
|
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||||
|
created="2015-12-21T19:59:11.000Z",
|
||||||
|
modified="2015-12-21T19:59:11.000Z",
|
||||||
|
name="The Black Vine Cyberespionage Group",
|
||||||
|
description="A simple report with an indicator and campaign",
|
||||||
|
published="2016-01-201T17:00:00Z",
|
||||||
|
labels=["campaign"],
|
||||||
|
object_refs=[
|
||||||
|
stix2.Indicator(id="indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", **INDICATOR_KWARGS),
|
||||||
|
"campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c",
|
||||||
|
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(report) == EXPECTED
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_example_objects_in_object_refs_with_bad_id():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.Report(
|
||||||
|
id="report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3",
|
||||||
|
created_by_ref="identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283",
|
||||||
|
created="2015-12-21T19:59:11.000Z",
|
||||||
|
modified="2015-12-21T19:59:11.000Z",
|
||||||
|
name="The Black Vine Cyberespionage Group",
|
||||||
|
description="A simple report with an indicator and campaign",
|
||||||
|
published="2016-01-201T17:00:00Z",
|
||||||
|
labels=["campaign"],
|
||||||
|
object_refs=[
|
||||||
|
stix2.Indicator(id="indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", **INDICATOR_KWARGS),
|
||||||
|
"campaign-83422c77-904c-4dc1-aff5-5c38f3a2c55c", # the "bad" id, missing a "-"
|
||||||
|
"relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a"
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid value for Report 'object_refs': must match <object-type>--<guid>."
|
||||||
|
|
||||||
# TODO: Add other examples
|
# TODO: Add other examples
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import pytz
|
||||||
|
|
||||||
|
import stix2
|
||||||
|
|
||||||
|
from .constants import INDICATOR_ID, SIGHTING_ID, SIGHTING_KWARGS
|
||||||
|
|
||||||
|
|
||||||
|
EXPECTED_SIGHTING = """{
|
||||||
|
"created": "2016-04-06T20:06:37Z",
|
||||||
|
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||||
|
"modified": "2016-04-06T20:06:37Z",
|
||||||
|
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||||
|
"type": "sighting",
|
||||||
|
"where_sighted_refs": [
|
||||||
|
"identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
BAD_SIGHTING = """{
|
||||||
|
"created": "2016-04-06T20:06:37Z",
|
||||||
|
"id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb",
|
||||||
|
"modified": "2016-04-06T20:06:37Z",
|
||||||
|
"sighting_of_ref": "indicator--01234567-89ab-cdef-0123-456789abcdef",
|
||||||
|
"type": "sighting",
|
||||||
|
"where_sighted_refs": [
|
||||||
|
"malware--8cc7afd6-5455-4d2b-a736-e614ee631d99"
|
||||||
|
]
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_sighting_all_required_fields():
|
||||||
|
now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
s = stix2.Sighting(
|
||||||
|
type='sighting',
|
||||||
|
id=SIGHTING_ID,
|
||||||
|
created=now,
|
||||||
|
modified=now,
|
||||||
|
sighting_of_ref=INDICATOR_ID,
|
||||||
|
where_sighted_refs=["identity--8cc7afd6-5455-4d2b-a736-e614ee631d99"]
|
||||||
|
)
|
||||||
|
assert str(s) == EXPECTED_SIGHTING
|
||||||
|
|
||||||
|
|
||||||
|
def test_sighting_bad_where_sighted_refs():
|
||||||
|
now = dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.Sighting(
|
||||||
|
type='sighting',
|
||||||
|
id=SIGHTING_ID,
|
||||||
|
created=now,
|
||||||
|
modified=now,
|
||||||
|
sighting_of_ref=INDICATOR_ID,
|
||||||
|
where_sighted_refs=["malware--8cc7afd6-5455-4d2b-a736-e614ee631d99"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid value for Sighting 'where_sighted_refs': must start with 'identity'."
|
||||||
|
|
||||||
|
|
||||||
|
def test_sighting_type_must_be_sightings():
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
stix2.Sighting(type='xxx', **SIGHTING_KWARGS)
|
||||||
|
|
||||||
|
assert str(excinfo.value) == "Invalid value for Sighting 'type': must equal 'sighting'."
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_kwarg_to_sighting():
|
||||||
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
stix2.Sighting(my_custom_property="foo", **SIGHTING_KWARGS)
|
||||||
|
assert str(excinfo.value) == "unexpected keyword arguments: ['my_custom_property']" in str(excinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_sighting_from_objects_rather_than_ids(malware): # noqa: F811
|
||||||
|
rel = stix2.Sighting(sighting_of_ref=malware)
|
||||||
|
|
||||||
|
assert rel.sighting_of_ref == 'malware--00000000-0000-0000-0000-000000000001'
|
||||||
|
assert rel.id == 'sighting--00000000-0000-0000-0000-000000000002'
|
Loading…
Reference in New Issue