Add EmailMessage and EmbeddedObjectProperty (for embedded object types
like EmailMIMEComponent)stix2.1
parent
d26662776c
commit
555c81d30f
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue