Skip to content

Commit 2a2f1b0

Browse files
authored
Merge pull request STIXProject#344 from emmanvg/issue-310
Change stix.ToolInformation and tests for MAEC (STIX 1.2.0.X)
2 parents 97a105b + 8ee624a commit 2a2f1b0

File tree

11 files changed

+95
-52
lines changed

11 files changed

+95
-52
lines changed

.travis.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
language: python
22
python:
3-
- "2.6"
43
- "2.7"
54
- "3.3"
65
- "3.4"

docs/getting_started.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ This page gives an introduction to **python-stix** and how to use it.
1010
Prerequisites
1111
-------------
1212

13-
The python-stix library provides an API for creating or processing STIX content. As such, it is a developer tool that can be leveraged by those who know Python 2.6/2.7 and are familiar with object-oriented programming practices, Python package layouts, and are comfortable with the installation of Python libraries. To contribute code to the python-stix repository, users must be familiar with `git`_ and `GitHub pull request`_ methodologies. Understanding XML, XML Schema, and the STIX language is also incredibly helpful when using python-stix in an application.
13+
The python-stix library provides an API for creating or processing STIX content. As such, it is a developer tool that can be leveraged by those who know Python 2.7/3.3+ and are familiar with object-oriented programming practices, Python package layouts, and are comfortable with the installation of Python libraries. To contribute code to the python-stix repository, users must be familiar with `git`_ and `GitHub pull request`_ methodologies. Understanding XML, XML Schema, and the STIX language is also incredibly helpful when using python-stix in an application.
1414

1515
.. _git: http://git-scm.com/documentation
1616
.. _GitHub pull request: https://help.github.com/articles/using-pull-requests

setup.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get_version():
2727
install_requires = [
2828
'lxml>=2.3',
2929
'python-dateutil',
30-
'cybox>=2.1.0.13.dev1,<2.1.1.0',
30+
'cybox>=2.1.0.13,<2.1.1.0',
3131
'mixbox>=1.0.2',
3232
]
3333

@@ -48,7 +48,6 @@ def get_version():
4848
'License :: OSI Approved :: BSD License',
4949
'Operating System :: OS Independent',
5050
'Programming Language :: Python :: 2',
51-
'Programming Language :: Python :: 2.6',
5251
'Programming Language :: Python :: 2.7',
5352
'Programming Language :: Python :: 3',
5453
'Programming Language :: Python :: 3.3',

stix/common/tools.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
# external
55
from mixbox import fields
6+
67
import cybox.common
8+
from cybox.common.vocabs import VocabField
79

810
# internal
911
import stix
1012
import stix.bindings.stix_common as common_binding
1113

1214
# relative
1315
from .structured_text import StructuredTextList
16+
from .vocabs import AttackerToolType
1417

1518

1619
class ToolInformation(stix.Entity, cybox.common.ToolInformation):
@@ -20,6 +23,7 @@ class ToolInformation(stix.Entity, cybox.common.ToolInformation):
2023

2124
title = fields.TypedField("Title")
2225
short_descriptions = fields.TypedField("Short_Description", StructuredTextList)
26+
type_ = VocabField("Type", AttackerToolType, multiple=True)
2327

2428
def __init__(self, title=None, short_description=None, tool_name=None, tool_vendor=None):
2529
super(ToolInformation, self).__init__(tool_name=tool_name, tool_vendor=tool_vendor)

stix/common/vocabs.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from functools import partial
66

77
# mixbox
8-
from mixbox import fields
9-
from mixbox import entities
10-
from mixbox import typedlist
8+
from mixbox import entities, fields, typedlist
9+
10+
# cybox
11+
from cybox.common import vocabs
1112

1213
# stix
1314
import stix
@@ -24,7 +25,7 @@ def validate_value(instance, value):
2425
elif value in allowed:
2526
return
2627
else:
27-
error = "Value must be one of {allowed}. Received '{value}'"
28+
error = "Value for vocab {instance.__class__} must be one of {allowed}. Received '{value}'"
2829
error = error.format(**locals())
2930
raise ValueError(error)
3031

@@ -124,7 +125,6 @@ def to_dict(self):
124125
return self.value
125126
return super(VocabString, self).to_dict()
126127

127-
128128
@classmethod
129129
def from_dict(cls, cls_dict):
130130
if not cls_dict:
@@ -306,8 +306,9 @@ class DiscoveryMethod_2_0(VocabString):
306306
TERM_USER = "User"
307307

308308

309+
@vocabs.register_vocab
309310
@register_vocab
310-
class AttackerToolType_1_0(VocabString):
311+
class AttackerToolType_1_0(vocabs.VocabString):
311312
_namespace = 'http://stix.mitre.org/default_vocabularies-1'
312313
_XSI_TYPE = 'stixVocabs:AttackerToolTypeVocab-1.0'
313314
_VOCAB_VERSION = '1.0'

stix/incident/history.py

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

4+
from mixbox import fields
5+
46
# internal
57
import stix
6-
import stix.utils as utils
78
import stix.bindings.incident as incident_binding
89
from stix.common.datetimewithprecision import DATETIME_PRECISION_VALUES
910

1011
# relative
1112
from .coa import COATaken
1213

13-
from mixbox import fields, entities
1414

1515
def validate_precision(instance, value):
1616
if value and (value not in DATETIME_PRECISION_VALUES):
1717
error = "time_precision must be one of {0}. Received '{1}'"
1818
error = error.format(DATETIME_PRECISION_VALUES, value)
1919
raise ValueError(error)
2020

21+
2122
class JournalEntry(stix.Entity):
2223
_namespace = "http://stix.mitre.org/Incident-1"
2324
_binding = incident_binding
2425
_binding_class = incident_binding.JournalEntryType
2526

2627
value = fields.TypedField("valueOf_", key_name="value")
2728
author = fields.TypedField("author")
28-
time = fields.TypedField("time") #TDOO: utils.dates.parse_value(value)
29+
time = fields.DateTimeField("time")
2930
time_precision = fields.TypedField("time_precision", preset_hook=validate_precision)
3031

3132
def __init__(self, value=None):
@@ -38,10 +39,10 @@ class HistoryItem(stix.Entity):
3839
_namespace = "http://stix.mitre.org/Incident-1"
3940
_binding = incident_binding
4041
_binding_class = incident_binding.HistoryItemType
41-
42+
4243
action_entry = fields.TypedField("Action_Entry", COATaken)
4344
journal_entry = fields.TypedField("Journal_Entry", JournalEntry)
44-
45+
4546
def __init__(self):
4647
super(HistoryItem, self).__init__()
4748

@@ -52,8 +53,7 @@ class History(stix.EntityList):
5253
_binding_class = incident_binding.HistoryType
5354

5455
history_items = fields.TypedField("History_Item", HistoryItem, multiple=True, key_name="history_items")
55-
56+
5657
@classmethod
5758
def _dict_as_list(cls):
5859
return False
59-

stix/report/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Report(stix.Entity):
4343
``datetime.datetime`` or ``str``.
4444
header: A Report :class:`.Header` object.
4545
campaigns: A collection of :class:`.Campaign` objects.
46-
course_of_action: A collection of :class:`.CourseOfAction` objects.
46+
courses_of_action: A collection of :class:`.CourseOfAction` objects.
4747
exploit_targets: A collection of :class:`.ExploitTarget` objects.
4848
incidents: A collection of :class:`.Incident` objects.
4949
indicators: A collection of :class:`.Indicator` objects.
@@ -59,7 +59,7 @@ class Report(stix.Entity):
5959

6060
id_ = fields.IdField("id")
6161
idref = fields.IdrefField("idref")
62-
timestamp = fields.TypedField("timestamp")
62+
timestamp = fields.DateTimeField("timestamp")
6363
version = fields.TypedField("version")
6464
header = fields.TypedField("Header", Header)
6565
campaigns = fields.TypedField("Campaigns", type_="stix.report.Campaigns")

stix/test/extensions/malware/maec_4_1_malware_test.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import unittest
22
from stix.core import STIXPackage
3-
from mixbox.vendor.six import StringIO, BytesIO, text_type
4-
5-
from lxml import etree
6-
import mixbox.xml
3+
from mixbox.vendor.six import StringIO, text_type
74

85
from stix.test import EntityTestCase
96
from stix.extensions.malware.maec_4_1_malware import MAECInstance
107

8+
try:
9+
import maec
10+
maec_present = True
11+
except ImportError:
12+
maec_present = False
13+
1114

15+
@unittest.skipIf(condition=maec_present is False, reason="These tests require the 'maec' library.")
1216
class PythonMAECTests(EntityTestCase, unittest.TestCase):
1317
klass = MAECInstance
1418

@@ -17,18 +21,20 @@ class PythonMAECTests(EntityTestCase, unittest.TestCase):
1721
'maec': {
1822
'malware_subjects':
1923
[
20-
{'malware_instance_object_attributes':
21-
{'id': 'maec-tst-obj-1',
22-
'properties': {
23-
'hashes':
24-
[
25-
{
26-
'simple_hash_value': '9d7006e30fdf15e9c8e03e62534b3a3e',
27-
'type': 'MD5'
28-
}
29-
],
30-
'xsi:type': 'FileObjectType'}
31-
}
24+
{
25+
'malware_instance_object_attributes': {
26+
'id': 'maec-tst-obj-1',
27+
'properties': {
28+
'hashes':
29+
[
30+
{
31+
'simple_hash_value': '9d7006e30fdf15e9c8e03e62534b3a3e',
32+
'type': 'MD5'
33+
}
34+
],
35+
'xsi:type': 'FileObjectType'
36+
}
37+
}
3238
}
3339
]
3440
}
@@ -43,6 +49,7 @@ def test_add_name_type(self):
4349
self.assertTrue("Remote Access Trojan" in maec_xml)
4450

4551

52+
@unittest.skipIf(condition=maec_present is False, reason="These tests require the 'maec' library.")
4653
class PythonMAECInPackageTests(unittest.TestCase):
4754
XML = StringIO(
4855
"""
@@ -138,5 +145,28 @@ def test_parse_malware_maec(self):
138145
self.assertTrue('names' in mw)
139146

140147

148+
@unittest.skipIf(condition=maec_present is True, reason="These tests require the 'maec' library to be missing.")
149+
class PythonMAECNotInstalledTest(unittest.TestCase):
150+
151+
def test_parsing_maec_fails(self):
152+
try:
153+
STIXPackage.from_xml(PythonMAECInPackageTests.XML_MAEC)
154+
except ImportError as e:
155+
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))
156+
157+
def test_handling_maec_object_fails(self):
158+
try:
159+
MAECInstance().from_dict(PythonMAECTests._full_dict)
160+
except ImportError as e:
161+
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))
162+
163+
def test_setting_maec_property_fails(self):
164+
try:
165+
m = MAECInstance()
166+
m.maec = "foo"
167+
except ImportError as e:
168+
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))
169+
170+
141171
if __name__ == "__main__":
142172
unittest.main()

stix/test/ttp_test.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,13 @@ class ResourcesTests(EntityTestCase, unittest.TestCase):
7171
'personas': PersonasTests._full_dict,
7272
'tools': [
7373
{
74-
'title': "Tool"
74+
'title': "Tool",
75+
'type': [
76+
{
77+
'value': 'Malware',
78+
'xsi:type': 'stixVocabs:AttackerToolTypeVocab-1.0'
79+
}
80+
]
7581
}
7682
],
7783
'infrastructure': InfrastructureTests._full_dict
@@ -81,7 +87,7 @@ class ResourcesTests(EntityTestCase, unittest.TestCase):
8187
class MalwareInstanceTests(EntityTestCase, unittest.TestCase):
8288
klass = malware_instance.MalwareInstance
8389

84-
_full_dict = _full_dict = {
90+
_full_dict = {
8591
'id': 'example:test-1',
8692
'title': 'Title',
8793
'description': 'Description',
@@ -156,6 +162,7 @@ class BehaviorTests(EntityTestCase, unittest.TestCase):
156162
'attack_patterns': AttackPatternsTests._full_dict
157163
}
158164

165+
159166
class TTPTests(EntityTestCase, unittest.TestCase):
160167
klass = ttp.TTP
161168
_full_dict = {

stix/ttp/resource.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,15 @@
22
# See LICENSE.txt for complete terms.
33

44
# mixbox
5-
from mixbox import fields
6-
from mixbox import typedlist
5+
from mixbox import fields, typedlist
76

87
# internal
98
import stix
9+
import stix.bindings.ttp as ttp_binding
1010
from stix.common import ToolInformation
1111
from stix.common.identity import Identity, IdentityFactory
12-
import stix.bindings.ttp as ttp_binding
13-
14-
# relative
15-
from .infrastructure import Infrastructure
12+
from stix.ttp.infrastructure import Infrastructure
1613

17-
from mixbox import entities, fields
1814

1915
class _IdentityList(typedlist.TypedList):
2016
def __init__(self, *args):

0 commit comments

Comments
 (0)