Merge branch 'master' into parsing

stix2.1
clenk 2017-04-11 15:05:22 -04:00
commit bc8bdccece
23 changed files with 468 additions and 65 deletions

4
.gitignore vendored
View File

@ -59,3 +59,7 @@ target/
# Vim # Vim
*.swp *.swp
#
# PyCharm
.idea/

View File

@ -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

View File

@ -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'

View File

@ -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``.

View File

@ -3,6 +3,7 @@ pre-commit
pytest pytest
pytest-cov pytest-cov
sphinx sphinx
sphinx-prompt
tox tox
-e . -e .

View File

@ -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):

View File

@ -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]

View File

@ -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
} }

76
stix2/markings.py Normal file
View File

@ -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")
)

View File

@ -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

View File

@ -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),
}) })

View File

@ -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)

View File

@ -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,
)

View File

@ -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

View File

@ -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"

View File

@ -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()

View File

@ -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"

115
stix2/test/test_markings.py Normal file
View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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'

View File

@ -17,6 +17,9 @@ commands =
ignore= ignore=
max-line-length=160 max-line-length=160
[flake8]
max-line-length=160
[travis] [travis]
python = python =
2.6: py26 2.6: py26