Improved the exception class hierarchy:

- Removed all plain python base classes (e.g. ValueError, TypeError)
- Renamed InvalidPropertyConfigurationError -> PropertyPresenceError,
  since incorrect values could be considered a property config error, and
  I really just wanted this class to apply to presence (co-)constraint
  violations.
- Added ObjectConfigurationError as a superclass of InvalidValueError,
  PropertyPresenceError, and any other exception that could be raised
  during _STIXBase object init, which is when the spec compliance
  checks happen.  This class is intended to represent general spec
  violations.
- Did some class reordering in exceptions.py, so all the
  ObjectConfigurationError subclasses were together.

Changed how property "cleaning" errors were handled:
- Previous docs said they should all be ValueErrors, but that would require
  extra exception check-and-replace complexity in the property
  implementations, so that requirement is removed.  Doc is changed to just
  say that cleaning problems should cause exceptions to be raised.
  _STIXBase._check_property() now handles most exception types, not just
  ValueError.
- Decided to try chaining the original clean error to the InvalidValueError,
  in case the extra diagnostics would be helpful in the future.  This is
  done via 'six' adapter function and only works on python3.
- A small amount of testing was removed, since it was looking at custom
  exception properties which became unavailable once the exception was
  replaced with InvalidValueError.

Did another pass through unit tests to fix breakage caused by the changed
exception class hierarchy.

Removed unnecessary observable extension handling code from
parse_observable(), since it was all duplicated in ExtensionsProperty.
The redundant code in parse_observable() had different exception behavior
than ExtensionsProperty, which makes the API inconsistent and unit tests
more complicated.  (Problems in ExtensionsProperty get replaced with
InvalidValueError, but extensions problems handled directly in
parse_observable() don't get the same replacement, and so the exception
type is different.)

Redid the workbench monkeypatching.  The old way was impossible to make
work, and had caused ugly ripple effect hackage in other parts of the
codebase.  Now, it replaces the global object maps with factory functions
which behave the same way when called, as real classes.  Had to fix up a
few unit tests to get them all passing with this monkeypatching in place.
Also remove all the xfail markings in the workbench test suite, since all
tests now pass.

Since workbench monkeypatching isn't currently affecting any unit tests,
tox.ini was simplified to remove the special-casing for running the
workbench tests.

Removed the v20 workbench test suite, since the workbench currently only
works with the latest stix object version.
master
Michael Chisholm 2019-07-19 14:50:11 -04:00
parent cd0c4984fa
commit 5589480980
28 changed files with 261 additions and 576 deletions

View File

@ -54,7 +54,7 @@ from .patterns import (
WithinQualifier,
)
from .utils import new_version, revoke
from .v20 import * # This import will always be the latest STIX 2.X version
from .v21 import * # This import will always be the latest STIX 2.X version
from .version import __version__
_collect_stix2_mappings()

View File

@ -5,6 +5,7 @@ import copy
import datetime as dt
import simplejson as json
import six
from .exceptions import (
AtLeastOnePropertyError, CustomContentError, DependentPropertiesError,
@ -88,10 +89,25 @@ 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 CustomContentError as exc:
if not self.__allow_custom:
six.raise_from(
InvalidValueError(
self.__class__, prop_name, reason=str(exc),
),
exc,
)
except Exception as exc:
six.raise_from(
InvalidValueError(
self.__class__, prop_name, reason=str(exc),
),
exc,
)
# interproperty constraint methods

View File

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

View File

@ -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,18 +27,18 @@ class InvalidValueError(STIXError, ValueError):
return msg.format(self)
class InvalidPropertyConfigurationError(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(InvalidPropertyConfigurationError, self).__init__(message)
super(PropertyPresenceError, self).__init__(message)
self.cls = cls
class MissingPropertiesError(InvalidPropertyConfigurationError):
class MissingPropertiesError(PropertyPresenceError):
"""Missing one or more required properties when constructing STIX object."""
def __init__(self, cls, properties):
@ -44,7 +52,7 @@ class MissingPropertiesError(InvalidPropertyConfigurationError):
super(MissingPropertiesError, self).__init__(msg, cls)
class ExtraPropertiesError(InvalidPropertyConfigurationError):
class ExtraPropertiesError(PropertyPresenceError):
"""One or more extra properties were provided when constructing STIX object."""
def __init__(self, cls, properties):
@ -58,7 +66,7 @@ class ExtraPropertiesError(InvalidPropertyConfigurationError):
super(ExtraPropertiesError, self).__init__(msg, cls)
class MutuallyExclusivePropertiesError(InvalidPropertyConfigurationError):
class MutuallyExclusivePropertiesError(PropertyPresenceError):
"""Violating interproperty mutually exclusive constraint of a STIX object type."""
def __init__(self, cls, properties):
@ -72,7 +80,7 @@ class MutuallyExclusivePropertiesError(InvalidPropertyConfigurationError):
super(MutuallyExclusivePropertiesError, self).__init__(msg, cls)
class DependentPropertiesError(InvalidPropertyConfigurationError):
class DependentPropertiesError(PropertyPresenceError):
"""Violating interproperty dependency constraint of a STIX object type."""
def __init__(self, cls, dependencies):
@ -86,7 +94,7 @@ class DependentPropertiesError(InvalidPropertyConfigurationError):
super(DependentPropertiesError, self).__init__(msg, cls)
class AtLeastOnePropertyError(InvalidPropertyConfigurationError):
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):
@ -94,27 +102,14 @@ class AtLeastOnePropertyError(InvalidPropertyConfigurationError):
msg = "At least one of the ({1}) properties for {0} must be " \
"populated.".format(
cls.__name__,
", ".join(x for x in self.properties),
)
cls.__name__,
", ".join(x for x in self.properties),
)
super(AtLeastOnePropertyError, self).__init__(msg, cls)
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)
class DictionaryKeyError(STIXError, ValueError):
class DictionaryKeyError(ObjectConfigurationError):
"""Dictionary key does not conform to the correct format."""
def __init__(self, key, reason):
@ -127,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):
@ -141,47 +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 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):
@ -194,20 +149,7 @@ class InvalidSelectorError(STIXError, AssertionError):
return msg.format(self.key, self.cls.__class__.__name__)
class MarkingNotFoundError(STIXError, AssertionError):
"""Marking violation. The marking reference must be present in SDO or SRO."""
def __init__(self, cls, key):
super(MarkingNotFoundError, self).__init__()
self.cls = cls
self.key = key
def __str__(self):
msg = "Marking {0} was not found in {1}!"
return msg.format(self.key, self.cls.__class__.__name__)
class TLPMarkingDefinitionError(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):
@ -218,3 +160,69 @@ class TLPMarkingDefinitionError(STIXError, AssertionError):
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):
super(MarkingNotFoundError, self).__init__()
self.cls = cls
self.key = key
def __str__(self):
msg = "Marking {0} was not found in {1}!"
return msg.format(self.key, self.cls.__class__.__name__)

View File

@ -102,7 +102,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

View File

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

View File

@ -2,6 +2,7 @@ import pytest
import stix2
from ...exceptions import InvalidValueError
from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID
IDENTITY_CUSTOM_PROP = stix2.v20.Identity(
@ -133,7 +134,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 +200,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 +719,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={
@ -911,7 +912,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)

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,9 @@ import uuid
import pytest
import stix2
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
from stix2.exceptions import (
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
)
from stix2.properties import (
BinaryProperty, BooleanProperty, DictionaryProperty,
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
@ -465,23 +467,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.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(spec_version="2.0", enclosing_type='indicator')
with pytest.raises(ValueError) as excinfo:
with pytest.raises(CustomContentError) as excinfo:
ext_prop.clean(
{
'windows-pebinary-ext': {

View File

@ -1,316 +0,0 @@
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,
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,
)
def test_workbench_environment():
# Create a STIX object
ind = create(Indicator, id=INDICATOR_ID, **INDICATOR_KWARGS)
save(ind)
resp = get(INDICATOR_ID)
assert resp['labels'][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
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
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
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
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
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.v20.Bundle(vuln)
assert bundle.objects[0].name == 'Heartbleed'
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
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
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)
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
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.v20.NTFSExt(
allow_custom=True,
sid=1,
x_foo='bar',
)
artifact = stix2.v20.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.v20.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)

View File

@ -77,7 +77,7 @@ GROUPING_KWARGS = dict(
context="suspicious-activity",
object_refs=[
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
"identity--988145ed-a3b4-4421-b7a7-273376be67ce"
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
],
)

View File

@ -4,6 +4,7 @@ import pytest
import stix2
from ...exceptions import InvalidValueError
from .constants import IDENTITY_ID
EXPECTED_BUNDLE = """{
@ -164,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'
@ -183,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'

View File

@ -79,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():

View File

@ -3,6 +3,7 @@ import pytest
import stix2
import stix2.base
from ...exceptions import InvalidValueError
from .constants import FAKE_TIME, IDENTITY_ID, MARKING_DEFINITION_ID
IDENTITY_CUSTOM_PROP = stix2.v21.Identity(
@ -97,7 +98,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 +137,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 +204,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 +723,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={
@ -915,7 +916,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)

View File

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

View File

@ -124,5 +124,5 @@ def test_parse_grouping(data):
assert grp.context == "suspicious-activity"
assert grp.object_refs == [
"malware--c8d2fae5-7271-400c-b81d-931a4caf20b9",
"identity--988145ed-a3b4-4421-b7a7-273376be67ce"
"identity--988145ed-a3b4-4421-b7a7-273376be67ce",
]

View File

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

View File

@ -6,6 +6,7 @@ import pytz
import stix2
from ...exceptions import InvalidValueError
from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS
EXPECTED_MALWARE = """{
@ -136,7 +137,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)
assert "Invalid value for Malware 'malware_types'" in str(excinfo.value)

View File

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

View File

@ -305,7 +305,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")
@ -534,11 +534,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(stix2.exceptions.InvalidValueError) as excinfo:
stix2.parse_observable(data, valid_refs, 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)
@ -788,7 +787,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={
@ -796,8 +795,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():
@ -1152,14 +1150,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():
@ -1181,7 +1177,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={
@ -1189,9 +1185,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():
@ -1324,7 +1318,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",

View File

@ -1,7 +1,9 @@
import pytest
import stix2
from stix2.exceptions import AtLeastOnePropertyError, DictionaryKeyError
from stix2.exceptions import (
AtLeastOnePropertyError, CustomContentError, DictionaryKeyError,
)
from stix2.properties import (
BinaryProperty, BooleanProperty, DictionaryProperty,
EmbeddedObjectProperty, EnumProperty, ExtensionsProperty, FloatProperty,
@ -474,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': {

View File

@ -1,7 +1,5 @@
import os
import pytest
import stix2
from stix2.workbench import (
AttackPattern, Campaign, CourseOfAction, ExternalReference,
@ -24,7 +22,6 @@ from .constants import (
)
@pytest.mark.xfail(reason='The workbench is not working correctly for 2.1')
def test_workbench_environment():
# Create a STIX object
@ -79,7 +76,6 @@ def test_workbench_get_all_identities():
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
@ -95,7 +91,6 @@ def test_workbench_get_all_intrusion_sets():
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)
@ -114,7 +109,6 @@ def test_workbench_get_all_observed_data():
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)
@ -124,7 +118,6 @@ def test_workbench_get_all_reports():
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)
@ -134,7 +127,6 @@ def test_workbench_get_all_threat_actors():
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)
@ -159,7 +151,6 @@ def test_workbench_add_to_bundle():
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)
@ -179,7 +170,6 @@ def test_workbench_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)
@ -195,7 +185,6 @@ def test_workbench_related():
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",
@ -216,7 +205,6 @@ def test_workbench_related_with_filters():
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)

View File

@ -7,7 +7,7 @@ from six.moves.urllib.parse import quote_plus
from ..core import STIXDomainObject
from ..custom import _custom_object_builder
from ..exceptions import InvalidPropertyConfigurationError
from ..exceptions import PropertyPresenceError
from ..properties import (
BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
FloatProperty, IDProperty, IntegerProperty, ListProperty,
@ -76,7 +76,7 @@ class Campaign(STIXDomainObject):
])
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')
@ -215,7 +215,7 @@ class Indicator(STIXDomainObject):
])
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')
@ -256,7 +256,7 @@ class Infrastructure(STIXDomainObject):
])
def _check_object_constraints(self):
super(self.__class__, self)._check_object_constraints()
super(Infrastructure, self)._check_object_constraints()
first_seen = self.get('first_seen')
last_seen = self.get('last_seen')
@ -299,7 +299,7 @@ class IntrusionSet(STIXDomainObject):
])
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')
@ -344,7 +344,7 @@ class Location(STIXDomainObject):
])
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'])
@ -360,10 +360,10 @@ class Location(STIXDomainObject):
and 'longitude' in self
)
):
raise InvalidPropertyConfigurationError(
raise PropertyPresenceError(
"Location objects must have the properties 'region', "
"'country', or 'latitude' and 'longitude'",
Location
Location,
)
def to_maps_url(self, map_engine="Google Maps"):
@ -454,7 +454,7 @@ class Malware(STIXDomainObject):
])
def _check_object_constraints(self):
super(self.__class__, self)._check_object_constraints()
super(Malware, self)._check_object_constraints()
first_seen = self.get('first_seen')
last_seen = self.get('last_seen')
@ -464,9 +464,9 @@ class Malware(STIXDomainObject):
raise ValueError(msg.format(self))
if self.is_family and "name" not in self:
raise InvalidPropertyConfigurationError(
raise PropertyPresenceError(
"'name' is a required property for malware families",
Malware
Malware,
)
@ -576,7 +576,7 @@ class ObservedData(STIXDomainObject):
super(ObservedData, self).__init__(*args, **kwargs)
def _check_object_constraints(self):
super(self.__class__, self)._check_object_constraints()
super(ObservedData, self)._check_object_constraints()
first_observed = self.get('first_observed')
last_observed = self.get('last_observed')
@ -694,7 +694,7 @@ class ThreatActor(STIXDomainObject):
])
def _check_object_constraints(self):
super(self.__class__, self)._check_object_constraints()
super(ThreatActor, self)._check_object_constraints()
first_observed = self.get('first_seen')
last_observed = self.get('last_seen')

View File

@ -20,6 +20,7 @@
"""
import functools
import stix2
from . import AttackPattern as _AttackPattern
from . import Campaign as _Campaign
@ -122,42 +123,35 @@ def _observed_data_init(self, *args, **kwargs):
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.v21.sdo.rst#stix2.v21.sdo.{0}>`__ object. {1}'.format(obj_type.__name__, 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()

View File

@ -11,9 +11,7 @@ deps =
taxii2-client
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
pytest --cov=stix2 stix2/test/ --cov-report term-missing
passenv = CI TRAVIS TRAVIS_*