Skip to content

Commit 86c9fe3

Browse files
committed
Make MAECInstance extension fully compatible with TypedFields. closes STIXProject#339
1 parent b9c6fe4 commit 86c9fe3

File tree

4 files changed

+104
-299
lines changed

4 files changed

+104
-299
lines changed

stix/bindings/extensions/malware/maec_4_1.py

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,18 @@
1515
from stix.bindings import register_extension
1616
import stix.bindings.ttp as ttp_binding
1717

18-
try:
19-
from maec.bindings.maec_package import PackageType
20-
maec_installed = True
21-
except ImportError:
22-
PackageType = None
23-
maec_installed = False
2418

2519
XML_NS = "http://stix.mitre.org/extensions/Malware#MAEC4.1-1"
2620

2721
#
2822
# Data representation classes.
2923
#
3024

25+
3126
@register_extension
3227
class MAEC4_1InstanceType(ttp_binding.MalwareInstanceType):
3328
"""The MAEC4.1InstanceType provides an extension to ttp_binding.MalwareInstanceType
34-
which imports and leverages the MAEC 4.0.1 schema for structured
29+
which imports and leverages the MAEC 4.1 schema for structured
3530
characterization of Malware."""
3631
subclass = None
3732
superclass = ttp_binding.MalwareInstanceType
@@ -89,11 +84,7 @@ def exportChildren(self, lwrite, level, nsmap, namespace_=XML_NS, name_='MAEC4.1
8984
else:
9085
eol_ = ''
9186
if self.MAEC is not None:
92-
if maec_installed and isinstance(self.MAEC, PackageType):
93-
self.MAEC.export(lwrite, level, namespace_='stix-maec:', name_='MAEC', pretty_print=pretty_print)
94-
else:
95-
showIndent(lwrite, level, pretty_print)
96-
lwrite(etree_.tostring(self.MAEC, pretty_print=pretty_print).decode())
87+
self.MAEC.export(lwrite, level, namespace_='stix-maec:', name_='MAEC', pretty_print=pretty_print)
9788
def build(self, node):
9889
self.__sourcenode__ = node
9990
already_processed = set()
@@ -105,12 +96,11 @@ def buildAttributes(self, node, attrs, already_processed):
10596
super(MAEC4_1InstanceType, self).buildAttributes(node, attrs, already_processed)
10697
def buildChildren(self, child_, node, nodeName_, fromsubclass_=False):
10798
if nodeName_ == 'MAEC':
108-
if maec_installed:
109-
obj_ = PackageType.factory()
110-
obj_.build(child_)
111-
self.set_MAEC(obj_)
112-
else:
113-
self.set_MAEC(child_)
99+
# Fails hard if maec library is not installed in your Python environment.
100+
from maec.bindings.maec_package import PackageType
101+
obj_ = PackageType.factory()
102+
obj_.build(child_)
103+
self.set_MAEC(obj_)
114104
super(MAEC4_1InstanceType, self).buildChildren(child_, node, nodeName_, True)
115105
# end class MAEC4_1InstanceType
116106

Lines changed: 16 additions & 210 deletions
Original file line numberDiff line numberDiff line change
@@ -1,231 +1,37 @@
11
# Copyright (c) 2017, The MITRE Corporation. All rights reserved.
22
# See LICENSE.txt for complete terms.
33

4-
# stdlib
5-
from distutils.version import LooseVersion
6-
74
# external
8-
from lxml import etree
9-
import mixbox.xml
10-
from mixbox.vendor.six import BytesIO, iteritems, binary_type
5+
from mixbox import fields
6+
117

128
# internal
139
import stix
14-
import stix.utils as utils
15-
import stix.ttp.malware_instance
10+
from stix.bindings.extensions.malware import maec_4_1 as maec_instance_binding
1611
from stix.ttp.malware_instance import MalwareInstance
17-
import stix.bindings.extensions.malware.maec_4_1 as ext_binding
18-
from mixbox import fields
19-
from stix.bindings.extensions.malware.maec_4_1 import maec_installed
20-
from lxml.etree import _ElementTree
21-
22-
_MIN_PYTHON_MAEC_VERSION = '4.1.0.12'
23-
24-
25-
class UnsupportedVersion(Exception):
26-
def __init__(self, message, expected, found):
27-
super(UnsupportedVersion, self).__init__(message)
28-
self.expected = expected
29-
self.found = found
3012

3113

32-
def _check_maec_version():
33-
"""Checks that the installed python-maec has a version greater than or
34-
equal to the minimum supported version.
35-
36-
Note:
37-
We do this rather than having a python-maec dependency requirement
38-
listed in setup.py because MAEC is used as an extension to STIX and
39-
not a core component to STIX (like CybOX).
40-
41-
Raises:
42-
ImportError: If python-maec is not installed.
43-
UnsupportedVersion: If the python-maec installation does not satisfy
44-
the version requirements.
45-
14+
@stix.register_extension
15+
class MAECInstance(MalwareInstance):
4616
"""
47-
import maec
48-
49-
found = maec.__version__
50-
expected = _MIN_PYTHON_MAEC_VERSION
51-
52-
if LooseVersion(found) >= LooseVersion(expected):
53-
return
17+
The MAECInstance object provides an extension to the MalwareInstanceType
18+
which imports and leverages the MAEC 4.1 schema for structured
19+
characterization of Malware.
5420
55-
fmt = ("Unsupported python-maec version installed: '%s'. Minimum version "
56-
"is '%s'.")
57-
error = fmt % (found, expected)
58-
raise UnsupportedVersion(error, expected=expected, found=found)
59-
60-
61-
try:
62-
# Check that the correct version of python-maec is installed.
63-
_check_maec_version()
64-
65-
# Import maecPackage into global space
66-
from maec.package.package import Package as maecPackage
67-
68-
_MAEC_INSTALLED = True
69-
except ImportError:
70-
maecPackage, Package = None, None
71-
_MAEC_INSTALLED = False
72-
73-
74-
def is_maec(obj):
75-
"""Checks if the input object is python-maec object.
76-
77-
Returns:
78-
True if python-maec is ins
21+
This class extension is automatically registered by the
22+
MalwareInstanceFactory.
7923
24+
Warnings:
25+
Interacting with the ``maec`` field will fail if the maec library is
26+
not installed in your Python environment.
8027
"""
81-
if not _MAEC_INSTALLED:
82-
return False
83-
84-
return isinstance(obj, maecPackage)
85-
86-
def validate_maec_input(instance, value):
87-
if value is None:
88-
return
89-
elif _MAEC_INSTALLED and is_maec(value):
90-
return
91-
elif mixbox.xml.is_element(value) or mixbox.xml.is_etree(value):
92-
return
93-
else:
94-
error = (
95-
"Cannot set maec to '{0}'. Expected 'lxml.etree._Element' or "
96-
"'maec.package.package.Package'."
97-
)
98-
error = error.format(type(value))
99-
raise ValueError(error)
100-
101-
@stix.register_extension
102-
class MAECInstance(MalwareInstance):
103-
_binding = ext_binding
28+
_binding = maec_instance_binding
10429
_binding_class = _binding.MAEC4_1InstanceType
105-
_namespace = 'http://stix.mitre.org/extensions/Malware#MAEC4.1-1'
106-
_xml_ns_prefix = "stix-maec"
30+
_namespace = "http://stix.mitre.org/extensions/Malware#MAEC4.1-1"
10731
_XSI_TYPE = "stix-maec:MAEC4.1InstanceType"
108-
_TAG_MAEC = "{%s}MAEC" % _namespace
10932

110-
maec = fields.TypedField("MAEC", preset_hook=validate_maec_input)
33+
maec = fields.TypedField("MAEC", type_="maec.package.package.Package")
11134

11235
def __init__(self, maec=None):
11336
super(MAECInstance, self).__init__()
114-
self.__input_namespaces__ = {}
115-
self.__input_schemalocations__ = {}
11637
self.maec = maec
117-
118-
def _parse_etree(self, root):
119-
node_tag = root.tag
120-
121-
if node_tag != self._TAG_MAEC:
122-
self._cast_maec(root)
123-
124-
self._collect_namespaces(root)
125-
self._collect_schemalocs(root)
126-
127-
def _cast_maec(self, node):
128-
ns_maec = "http://maec.mitre.org/XMLSchema/maec-package-2"
129-
node_ns = etree.QName(node).namespace
130-
131-
if node_ns == ns_maec:
132-
etree.register_namespace(self._xml_ns_prefix, self._namespace)
133-
node.tag = self._TAG_MAEC
134-
else:
135-
error = "Cannot set maec. Expected tag '{0}' found '{1}'."
136-
error = error.format(self._TAG_MAEC, node.tag)
137-
raise ValueError(error)
138-
139-
def _collect_schemalocs(self, node):
140-
try:
141-
schemaloc = mixbox.xml.get_schemaloc_pairs(node)
142-
self.__input_schemalocations__ = dict(schemaloc)
143-
except KeyError:
144-
self.__input_schemalocations__ = {}
145-
146-
def _collect_namespaces(self, node):
147-
self.__input_namespaces__ = dict(iteritems(node.nsmap))
148-
149-
@classmethod
150-
def from_obj(cls, obj):
151-
if not obj:
152-
return None
153-
154-
return_obj = cls()
155-
156-
if _MAEC_INSTALLED:
157-
obj.MAEC = maecPackage.from_obj(obj.MAEC)
158-
else:
159-
obj.MAEC = obj.MAEC
160-
161-
return_obj = super(MAECInstance, cls).from_obj(obj)
162-
163-
return return_obj
164-
165-
def to_obj(self, ns_info=None):
166-
return_obj = super(MAECInstance, self).to_obj(ns_info=ns_info)
167-
168-
if mixbox.xml.is_element(self.maec) or mixbox.xml.is_etree(self.maec):
169-
tree = mixbox.xml.get_etree(self.maec)
170-
root = mixbox.xml.get_etree_root(tree)
171-
self._parse_etree(root)
172-
self.maec = root
173-
174-
if _MAEC_INSTALLED and isinstance(self.maec, maecPackage):
175-
return_obj.MAEC = self.maec.to_obj(ns_info=ns_info)
176-
else:
177-
return_obj.MAEC = self.maec
178-
179-
return return_obj
180-
181-
@classmethod
182-
def _maec_from_dict(cls, d):
183-
if _MAEC_INSTALLED:
184-
return maecPackage.from_dict(d)
185-
186-
raise ValueError(
187-
"Unable to parse 'maec' value in dictionary. Please "
188-
"install python-maec to parse dictionary value."
189-
)
190-
191-
@classmethod
192-
def from_dict(cls, d, return_obj=None):
193-
if not d:
194-
return None
195-
196-
d = d.copy()
197-
198-
maec = d.get('maec')
199-
200-
if maec is None:
201-
pass
202-
elif isinstance(maec, dict):
203-
d['maec'] = cls._maec_from_dict(maec)
204-
elif isinstance(maec, binary_type):
205-
d['maec'] = mixbox.xml.get_etree_root(BytesIO(maec))
206-
else:
207-
raise TypeError("Unknown type for 'maec' entry.")
208-
209-
return_obj = super(MAECInstance, cls).from_dict(d)
210-
211-
return return_obj
212-
213-
def to_dict(self):
214-
d = super(MAECInstance, self).to_dict()
215-
216-
if self.maec is not None:
217-
if mixbox.xml.is_element(self.maec) or mixbox.xml.is_etree(self.maec):
218-
tree = mixbox.xml.get_etree(self.maec)
219-
root = mixbox.xml.get_etree_root(tree)
220-
self._parse_etree(root)
221-
self.maec = root
222-
223-
if _MAEC_INSTALLED and isinstance(self.maec, maecPackage):
224-
d['maec'] = self.maec.to_dict()
225-
else:
226-
d['maec'] = etree.tostring(self.maec)
227-
228-
if self._XSI_TYPE:
229-
d['xsi:type'] = self._XSI_TYPE
230-
231-
return d

0 commit comments

Comments
 (0)