Rework select properties to use get_dict(),
which automatically coerces values to a dictionary if possiblestix2.1
parent
aa69c38444
commit
2460fb75be
|
@ -12,6 +12,7 @@ from six import text_type
|
|||
|
||||
from .base import _Observable, _STIXBase
|
||||
from .exceptions import DictionaryKeyError
|
||||
from .utils import get_dict
|
||||
|
||||
|
||||
class Property(object):
|
||||
|
@ -181,7 +182,7 @@ class FloatProperty(Property):
|
|||
try:
|
||||
return float(value)
|
||||
except Exception:
|
||||
raise ValueError("must be an float.")
|
||||
raise ValueError("must be a float.")
|
||||
|
||||
|
||||
class BooleanProperty(Property):
|
||||
|
@ -233,7 +234,11 @@ class TimestampProperty(Property):
|
|||
class ObservableProperty(Property):
|
||||
|
||||
def clean(self, value):
|
||||
dictified = dict(value)
|
||||
try:
|
||||
dictified = get_dict(value)
|
||||
except ValueError:
|
||||
raise ValueError("The observable property must contain a dictionary")
|
||||
|
||||
from .__init__ import parse_observable # avoid circular import
|
||||
for key, obj in dictified.items():
|
||||
parsed_obj = parse_observable(obj, dictified.keys())
|
||||
|
@ -248,7 +253,11 @@ class ObservableProperty(Property):
|
|||
class DictionaryProperty(Property):
|
||||
|
||||
def clean(self, value):
|
||||
dictified = dict(value)
|
||||
try:
|
||||
dictified = get_dict(value)
|
||||
except ValueError:
|
||||
raise ValueError("The dictionary property must contain a dictionary")
|
||||
|
||||
for k in dictified.keys():
|
||||
if len(k) < 3:
|
||||
raise DictionaryKeyError(k, "shorter than 3 characters")
|
||||
|
@ -392,23 +401,25 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
super(ExtensionsProperty, self).__init__(required)
|
||||
|
||||
def clean(self, value):
|
||||
if type(value) is dict:
|
||||
try:
|
||||
dictified = get_dict(value)
|
||||
except ValueError:
|
||||
raise ValueError("The extensions property must contain a dictionary")
|
||||
|
||||
from .__init__ import EXT_MAP # avoid circular import
|
||||
if self.enclosing_type in EXT_MAP:
|
||||
specific_type_map = EXT_MAP[self.enclosing_type]
|
||||
for key, subvalue in value.items():
|
||||
for key, subvalue in dictified.items():
|
||||
if key in specific_type_map:
|
||||
cls = specific_type_map[key]
|
||||
if type(subvalue) is dict:
|
||||
value[key] = cls(**subvalue)
|
||||
dictified[key] = cls(**subvalue)
|
||||
elif type(subvalue) is cls:
|
||||
value[key] = subvalue
|
||||
dictified[key] = subvalue
|
||||
else:
|
||||
raise ValueError("Cannot determine extension type.")
|
||||
else:
|
||||
raise ValueError("The key used in the extensions dictionary is not an extension type name")
|
||||
else:
|
||||
raise ValueError("The enclosing type has no extensions defined")
|
||||
else:
|
||||
raise ValueError("The extensions property must contain a dictionary")
|
||||
return value
|
||||
return dictified
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import pytest
|
||||
|
||||
from stix2 import TCPExt
|
||||
from stix2.exceptions import DictionaryKeyError
|
||||
from stix2.observables import EmailMIMEComponent
|
||||
from stix2.properties import (BinaryProperty, BooleanProperty,
|
||||
DictionaryProperty, EmbeddedObjectProperty,
|
||||
EnumProperty, HashesProperty, HexProperty,
|
||||
IDProperty, IntegerProperty, ListProperty,
|
||||
Property, ReferenceProperty, StringProperty,
|
||||
TimestampProperty, TypeProperty)
|
||||
EnumProperty, ExtensionsProperty, HashesProperty,
|
||||
HexProperty, IDProperty, IntegerProperty,
|
||||
ListProperty, Property, ReferenceProperty,
|
||||
StringProperty, TimestampProperty, TypeProperty)
|
||||
|
||||
from .constants import FAKE_TIME
|
||||
|
||||
|
@ -255,3 +256,36 @@ def test_enum_property():
|
|||
|
||||
with pytest.raises(ValueError):
|
||||
enum_prop.clean('z')
|
||||
|
||||
|
||||
def test_extension_property_valid():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
assert ext_prop({
|
||||
'windows-pebinary-ext': {
|
||||
'pe_type': 'exe'
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("data", [
|
||||
1,
|
||||
{'foobar-ext': {
|
||||
'pe_type': 'exe'
|
||||
}},
|
||||
{'windows-pebinary-ext': TCPExt()},
|
||||
])
|
||||
def test_extension_property_invalid(data):
|
||||
ext_prop = ExtensionsProperty(enclosing_type='file')
|
||||
with pytest.raises(ValueError):
|
||||
ext_prop.clean(data)
|
||||
|
||||
|
||||
def test_extension_property_invalid_type():
|
||||
ext_prop = ExtensionsProperty(enclosing_type='indicator')
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
ext_prop.clean({
|
||||
'windows-pebinary-ext': {
|
||||
'pe_type': 'exe'
|
||||
}}
|
||||
)
|
||||
assert 'no extensions defined' in str(excinfo.value)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime as dt
|
||||
from io import StringIO
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
|
@ -17,3 +18,24 @@ eastern = pytz.timezone('US/Eastern')
|
|||
])
|
||||
def test_timestamp_formatting(dttm, timestamp):
|
||||
assert stix2.utils.format_datetime(dttm) == timestamp
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data', [
|
||||
{"a": 1},
|
||||
'{"a": 1}',
|
||||
StringIO(u'{"a": 1}'),
|
||||
[("a", 1,)],
|
||||
])
|
||||
def test_get_dict(data):
|
||||
assert stix2.utils.get_dict(data)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('data', [
|
||||
1,
|
||||
[1],
|
||||
['a', 1],
|
||||
"foobar",
|
||||
])
|
||||
def test_get_dict_invalid(data):
|
||||
with pytest.raises(ValueError):
|
||||
stix2.utils.get_dict(data)
|
||||
|
|
|
@ -63,11 +63,17 @@ def get_dict(data):
|
|||
"""
|
||||
|
||||
if type(data) is dict:
|
||||
obj = data
|
||||
return data
|
||||
else:
|
||||
try:
|
||||
obj = json.loads(data)
|
||||
return json.loads(data)
|
||||
except TypeError:
|
||||
obj = json.load(data)
|
||||
|
||||
return obj
|
||||
pass
|
||||
try:
|
||||
return json.load(data)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
return dict(data)
|
||||
except (ValueError, TypeError):
|
||||
raise ValueError("Cannot convert '%s' to dictionary." % str(data))
|
||||
|
|
Loading…
Reference in New Issue