Add custom Cyber Observables
parent
d4e92dd813
commit
fdbb6ff337
|
@ -114,12 +114,11 @@ def parse(data, allow_custom=False):
|
||||||
try:
|
try:
|
||||||
obj_class = OBJ_MAP[obj['type']]
|
obj_class = OBJ_MAP[obj['type']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# TODO handle custom objects
|
raise exceptions.ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % obj['type'])
|
||||||
raise exceptions.ParseError("Can't parse unknown object type '%s'!" % obj['type'])
|
|
||||||
return obj_class(allow_custom=allow_custom, **obj)
|
return obj_class(allow_custom=allow_custom, **obj)
|
||||||
|
|
||||||
|
|
||||||
def parse_observable(data, _valid_refs, allow_custom=False):
|
def parse_observable(data, _valid_refs=[], allow_custom=False):
|
||||||
"""Deserialize a string or file-like object into a STIX Cyber Observable object.
|
"""Deserialize a string or file-like object into a STIX Cyber Observable object.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -139,8 +138,7 @@ def parse_observable(data, _valid_refs, allow_custom=False):
|
||||||
try:
|
try:
|
||||||
obj_class = OBJ_MAP_OBSERVABLE[obj['type']]
|
obj_class = OBJ_MAP_OBSERVABLE[obj['type']]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# TODO handle custom observable objects
|
raise exceptions.ParseError("Can't parse unknown object type '%s'! For custom observables, use the CustomObservable decorator." % obj['type'])
|
||||||
raise exceptions.ParseError("Can't parse unknown object type '%s'!" % obj['type'])
|
|
||||||
|
|
||||||
if 'extensions' in obj and obj['type'] in EXT_MAP:
|
if 'extensions' in obj and obj['type'] in EXT_MAP:
|
||||||
for name, ext in obj['extensions'].items():
|
for name, ext in obj['extensions'].items():
|
||||||
|
@ -157,3 +155,10 @@ def _register_type(new_type):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
OBJ_MAP[new_type._type] = new_type
|
OBJ_MAP[new_type._type] = new_type
|
||||||
|
|
||||||
|
|
||||||
|
def _register_observable(new_observable):
|
||||||
|
"""Register a custom STIX Cyber Observable type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
|
||||||
|
|
|
@ -5,6 +5,8 @@ embedded in Email Message objects, inherit from _STIXBase instead of Observable
|
||||||
and do not have a '_type' attribute.
|
and do not have a '_type' attribute.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import stix2
|
||||||
|
|
||||||
from .base import _Extension, _Observable, _STIXBase
|
from .base import _Extension, _Observable, _STIXBase
|
||||||
from .exceptions import AtLeastOnePropertyError, DependentPropertiesError
|
from .exceptions import AtLeastOnePropertyError, DependentPropertiesError
|
||||||
from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
|
from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
|
||||||
|
@ -586,3 +588,27 @@ class X509Certificate(_Observable):
|
||||||
'subject_public_key_exponent': IntegerProperty(),
|
'subject_public_key_exponent': IntegerProperty(),
|
||||||
'x509_v3_extensions': EmbeddedObjectProperty(type=X509V3ExtenstionsType),
|
'x509_v3_extensions': EmbeddedObjectProperty(type=X509V3ExtenstionsType),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def CustomObservable(type='x-custom-observable', properties={}):
|
||||||
|
"""Custom STIX Cyber Observable type decorator
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def custom_builder(cls):
|
||||||
|
|
||||||
|
class _Custom(cls, _Observable):
|
||||||
|
_type = type
|
||||||
|
_properties = {
|
||||||
|
'type': TypeProperty(_type),
|
||||||
|
}
|
||||||
|
_properties.update(properties)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
_Observable.__init__(self, **kwargs)
|
||||||
|
cls.__init__(self, **kwargs)
|
||||||
|
|
||||||
|
stix2._register_observable(_Custom)
|
||||||
|
return _Custom
|
||||||
|
|
||||||
|
return custom_builder
|
||||||
|
|
|
@ -199,7 +199,7 @@ def CustomObject(type='x-custom-type', properties={}):
|
||||||
|
|
||||||
Example 1:
|
Example 1:
|
||||||
|
|
||||||
@CustomObject('type-name', {
|
@CustomObject('x-type-name', {
|
||||||
'property1': StringProperty(required=True),
|
'property1': StringProperty(required=True),
|
||||||
'property2': IntegerProperty(),
|
'property2': IntegerProperty(),
|
||||||
})
|
})
|
||||||
|
@ -211,7 +211,7 @@ def CustomObject(type='x-custom-type', properties={}):
|
||||||
|
|
||||||
Example 2:
|
Example 2:
|
||||||
|
|
||||||
@CustomObject('type-name', {
|
@CustomObject('x-type-name', {
|
||||||
'property1': StringProperty(required=True),
|
'property1': StringProperty(required=True),
|
||||||
'property2': IntegerProperty(),
|
'property2': IntegerProperty(),
|
||||||
})
|
})
|
||||||
|
|
|
@ -72,7 +72,7 @@ def test_parse_identity_custom_property(data):
|
||||||
assert identity.foo == "bar"
|
assert identity.foo == "bar"
|
||||||
|
|
||||||
|
|
||||||
@stix2.sdo.CustomObject('new-type', {
|
@stix2.sdo.CustomObject('x-new-type', {
|
||||||
'property1': stix2.properties.StringProperty(required=True),
|
'property1': stix2.properties.StringProperty(required=True),
|
||||||
'property2': stix2.properties.IntegerProperty(),
|
'property2': stix2.properties.IntegerProperty(),
|
||||||
})
|
})
|
||||||
|
@ -95,10 +95,74 @@ def test_custom_object_type():
|
||||||
|
|
||||||
def test_parse_custom_object_type():
|
def test_parse_custom_object_type():
|
||||||
nt_string = """{
|
nt_string = """{
|
||||||
"type": "new-type",
|
"type": "x-new-type",
|
||||||
"created": "2015-12-21T19:59:11Z",
|
"created": "2015-12-21T19:59:11Z",
|
||||||
"property1": "something"
|
"property1": "something"
|
||||||
}"""
|
}"""
|
||||||
|
|
||||||
nt = stix2.parse(nt_string)
|
nt = stix2.parse(nt_string)
|
||||||
assert nt.property1 == 'something'
|
assert nt.property1 == 'something'
|
||||||
|
|
||||||
|
|
||||||
|
@stix2.observables.CustomObservable('x-new-observable', {
|
||||||
|
'property1': stix2.properties.StringProperty(required=True),
|
||||||
|
'property2': stix2.properties.IntegerProperty(),
|
||||||
|
})
|
||||||
|
class NewObservable():
|
||||||
|
def __init__(self, property2=None, **kwargs):
|
||||||
|
if property2 and property2 < 10:
|
||||||
|
raise ValueError("'property2' is too small.")
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_observable_object():
|
||||||
|
no = NewObservable(property1='something')
|
||||||
|
assert no.property1 == 'something'
|
||||||
|
|
||||||
|
with pytest.raises(stix2.exceptions.MissingPropertiesError):
|
||||||
|
NewObservable(property2=42)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
NewObservable(property1='something', property2=4)
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_custom_observable_object():
|
||||||
|
nt_string = """{
|
||||||
|
"type": "x-new-observable",
|
||||||
|
"property1": "something"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
nt = stix2.parse_observable(nt_string)
|
||||||
|
assert nt.property1 == 'something'
|
||||||
|
|
||||||
|
|
||||||
|
def test_observable_custom_property():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
NewObservable(
|
||||||
|
property1='something',
|
||||||
|
custom_properties="foobar",
|
||||||
|
)
|
||||||
|
|
||||||
|
no = NewObservable(
|
||||||
|
property1='something',
|
||||||
|
custom_properties={
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert no.foo == "bar"
|
||||||
|
|
||||||
|
|
||||||
|
def test_observable_custom_property_invalid():
|
||||||
|
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
|
||||||
|
NewObservable(
|
||||||
|
property1='something',
|
||||||
|
x_foo="bar",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_observable_custom_property_allowed():
|
||||||
|
no = NewObservable(
|
||||||
|
property1='something',
|
||||||
|
x_foo="bar",
|
||||||
|
allow_custom=True,
|
||||||
|
)
|
||||||
|
assert no.x_foo == "bar"
|
||||||
|
|
Loading…
Reference in New Issue