Add EmailMessage and EmbeddedObjectProperty (for embedded object types

like EmailMIMEComponent)
stix2.1
clenk 2017-05-09 11:03:19 -04:00
parent d26662776c
commit 555c81d30f
6 changed files with 132 additions and 17 deletions

View File

@ -3,7 +3,8 @@
# flake8: noqa # flake8: noqa
from .bundle import Bundle from .bundle import Bundle
from .observables import Artifact, AutonomousSystem, EmailAddress, File from .observables import Artifact, AutonomousSystem, EmailAddress, \
EmailMessage, File
from .other import ExternalReference, KillChainPhase, MarkingDefinition, \ from .other import ExternalReference, KillChainPhase, MarkingDefinition, \
GranularMarking, StatementMarking, TLPMarking GranularMarking, StatementMarking, TLPMarking
from .sdo import AttackPattern, Campaign, CourseOfAction, Identity, Indicator, \ from .sdo import AttackPattern, Campaign, CourseOfAction, Identity, Indicator, \
@ -36,6 +37,7 @@ OBJ_MAP_OBSERVABLE = {
'artifact': Artifact, 'artifact': Artifact,
'autonomous-system': AutonomousSystem, 'autonomous-system': AutonomousSystem,
'email-address': EmailAddress, 'email-address': EmailAddress,
'email-message': EmailMessage,
'file': File, 'file': File,
} }

View File

@ -153,13 +153,12 @@ class Observable(_STIXBase):
def _check_property(self, prop_name, prop, kwargs): def _check_property(self, prop_name, prop, kwargs):
super(Observable, self)._check_property(prop_name, prop, kwargs) super(Observable, self)._check_property(prop_name, prop, kwargs)
if prop_name.endswith('_ref'): if prop_name.endswith('_ref') and prop_name in kwargs:
ref = kwargs[prop_name].split('--', 1)[0] ref = kwargs[prop_name]
if ref not in self._STIXBase__valid_refs: if ref not in self._STIXBase__valid_refs:
raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref) raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref)
if prop_name.endswith('_refs'): elif prop_name.endswith('_refs') and prop_name in kwargs:
for r in kwargs[prop_name]: for ref in kwargs[prop_name]:
ref = r.split('--', 1)[0]
if ref not in self._STIXBase__valid_refs: if ref not in self._STIXBase__valid_refs:
raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref) raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref)
# TODO also check the type of the object referenced, not just that the key exists # TODO also check the type of the object referenced, not just that the key exists

View File

@ -1,11 +1,16 @@
"""STIX 2.0 Cyber Observable Objects""" """STIX 2.0 Cyber Observable Objects
from .base import Observable Embedded observable object types, such as Email MIME Component, which is
# from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty, embedded in Email Message objects, inherit from _STIXBase instead of Observable
# HashesProperty, HexProperty, IDProperty, and do not have a '_type' attribute.
# IntegerProperty, ListProperty, ReferenceProperty, """
# StringProperty, TimestampProperty, TypeProperty)
from .properties import BinaryProperty, HashesProperty, IntegerProperty, ObjectReferenceProperty, StringProperty, TypeProperty from .base import _STIXBase, Observable
from .properties import (BinaryProperty, BooleanProperty, DictionaryProperty,
EmbeddedObjectProperty, HashesProperty,
IntegerProperty, ListProperty,
ObjectReferenceProperty, StringProperty,
TimestampProperty, TypeProperty)
class Artifact(Observable): class Artifact(Observable):
@ -39,6 +44,36 @@ class EmailAddress(Observable):
} }
class EmailMIMEComponent(_STIXBase):
_properties = {
'body': StringProperty(),
'body_raw_ref': ObjectReferenceProperty(),
'content_type': StringProperty(),
'content_disposition': StringProperty(),
}
class EmailMessage(Observable):
_type = 'email-message'
_properties = {
'type': TypeProperty(_type),
'is_multipart': BooleanProperty(required=True),
'date': TimestampProperty(),
'content_type': StringProperty(),
'from_ref': ObjectReferenceProperty(),
'sender_ref': ObjectReferenceProperty(),
'to_refs': ListProperty(ObjectReferenceProperty),
'cc_refs': ListProperty(ObjectReferenceProperty),
'bcc_refs': ListProperty(ObjectReferenceProperty),
'subject': StringProperty(),
'received_lines': ListProperty(StringProperty),
'additional_header_fields': DictionaryProperty(),
'body': StringProperty(),
'body_multipart': ListProperty(EmbeddedObjectProperty(type=EmailMIMEComponent)),
'raw_email_ref': ObjectReferenceProperty(),
}
class File(Observable): class File(Observable):
_type = 'file' _type = 'file'
_properties = { _properties = {

View File

@ -115,10 +115,15 @@ class ListProperty(Property):
# TODO Should we raise an error here? # TODO Should we raise an error here?
valid = item valid = item
if isinstance(valid, collections.Mapping): if type(self.contained) is EmbeddedObjectProperty:
result.append(self.contained(**valid)) obj_type = self.contained.type
else: else:
result.append(self.contained(valid)) obj_type = self.contained
if isinstance(valid, collections.Mapping):
result.append(obj_type(**valid))
else:
result.append(obj_type(valid))
# STIX spec forbids empty lists # STIX spec forbids empty lists
if len(result) < 1: if len(result) < 1:
@ -339,3 +344,16 @@ class SelectorProperty(Property):
class ObjectReferenceProperty(StringProperty): class ObjectReferenceProperty(StringProperty):
pass pass
class EmbeddedObjectProperty(Property):
def __init__(self, type, required=False):
self.type = type
super(EmbeddedObjectProperty, self).__init__(required, type=type)
def clean(self, value):
if type(value) is dict:
value = self.type(**value)
elif not isinstance(value, self.type):
raise ValueError("must be of type %s." % self.type.__name__)
return value

View File

@ -152,4 +152,50 @@ def test_parse_email_address(data):
stix2.parse(odata_str) stix2.parse(odata_str)
@pytest.mark.parametrize("data", [
"""
{
"type": "email-message",
"is_multipart": true,
"content_type": "multipart/mixed",
"date": "2016-06-19T14:20:40.000Z",
"from_ref": "1",
"to_refs": [
"2"
],
"cc_refs": [
"3"
],
"subject": "Check out this picture of a cat!",
"additional_header_fields": {
"Content-Disposition": "inline",
"X-Mailer": "Mutt/1.5.23",
"X-Originating-IP": "198.51.100.3"
},
"body_multipart": [
{
"content_type": "text/plain; charset=utf-8",
"content_disposition": "inline",
"body": "Cats are funny!"
},
{
"content_type": "image/png",
"content_disposition": "attachment; filename=\\"tabby.png\\"",
"body_raw_ref": "4"
},
{
"content_type": "application/zip",
"content_disposition": "attachment; filename=\\"tabby_pics.zip\\"",
"body_raw_ref": "5"
}
]
}
"""
])
def test_parse_email_message(data):
odata = stix2.parse_observable(data, [str(i) for i in range(1, 6)])
assert odata.type == "email-message"
assert odata.body_multipart[0].content_disposition == "inline"
# TODO: Add other examples # TODO: Add other examples

View File

@ -1,8 +1,10 @@
import pytest import pytest
from stix2.exceptions import DictionaryKeyError from stix2.exceptions import DictionaryKeyError
from stix2.observables import EmailMIMEComponent
from stix2.properties import (BinaryProperty, BooleanProperty, from stix2.properties import (BinaryProperty, BooleanProperty,
DictionaryProperty, HashesProperty, HexProperty, DictionaryProperty, EmbeddedObjectProperty,
HashesProperty, HexProperty,
IDProperty, IntegerProperty, ListProperty, IDProperty, IntegerProperty, ListProperty,
Property, ReferenceProperty, StringProperty, Property, ReferenceProperty, StringProperty,
TimestampProperty, TypeProperty) TimestampProperty, TypeProperty)
@ -232,3 +234,16 @@ def test_hashes_property_invalid(value):
with pytest.raises(ValueError): with pytest.raises(ValueError):
hash_prop.clean(value) hash_prop.clean(value)
def test_embedded_property():
emb_prop = EmbeddedObjectProperty(type=EmailMIMEComponent)
mime = EmailMIMEComponent(
content_type="text/plain; charset=utf-8",
content_disposition="inline",
body="Cats are funny!"
)
assert emb_prop.clean(mime)
with pytest.raises(ValueError):
emb_prop.clean("string")