Skip to content

Commit

Permalink
Merge pull request #344 from emmanvg/issue-310
Browse files Browse the repository at this point in the history
Change stix.ToolInformation and tests for MAEC (STIX 1.2.0.X)
  • Loading branch information
clenk committed Mar 6, 2018
2 parents 97a105b + 8ee624a commit 2a2f1b0
Show file tree
Hide file tree
Showing 11 changed files with 95 additions and 52 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
language: python
python:
- "2.6"
- "2.7"
- "3.3"
- "3.4"
Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This page gives an introduction to **python-stix** and how to use it.
Prerequisites
-------------

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.
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.

.. _git: http://git-scm.com/documentation
.. _GitHub pull request: https://help.github.com/articles/using-pull-requests
Expand Down
3 changes: 1 addition & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def get_version():
install_requires = [
'lxml>=2.3',
'python-dateutil',
'cybox>=2.1.0.13.dev1,<2.1.1.0',
'cybox>=2.1.0.13,<2.1.1.0',
'mixbox>=1.0.2',
]

Expand All @@ -48,7 +48,6 @@ def get_version():
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.3',
Expand Down
4 changes: 4 additions & 0 deletions stix/common/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@

# external
from mixbox import fields

import cybox.common
from cybox.common.vocabs import VocabField

# internal
import stix
import stix.bindings.stix_common as common_binding

# relative
from .structured_text import StructuredTextList
from .vocabs import AttackerToolType


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

title = fields.TypedField("Title")
short_descriptions = fields.TypedField("Short_Description", StructuredTextList)
type_ = VocabField("Type", AttackerToolType, multiple=True)

def __init__(self, title=None, short_description=None, tool_name=None, tool_vendor=None):
super(ToolInformation, self).__init__(tool_name=tool_name, tool_vendor=tool_vendor)
Expand Down
13 changes: 7 additions & 6 deletions stix/common/vocabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
from functools import partial

# mixbox
from mixbox import fields
from mixbox import entities
from mixbox import typedlist
from mixbox import entities, fields, typedlist

# cybox
from cybox.common import vocabs

# stix
import stix
Expand All @@ -24,7 +25,7 @@ def validate_value(instance, value):
elif value in allowed:
return
else:
error = "Value must be one of {allowed}. Received '{value}'"
error = "Value for vocab {instance.__class__} must be one of {allowed}. Received '{value}'"
error = error.format(**locals())
raise ValueError(error)

Expand Down Expand Up @@ -124,7 +125,6 @@ def to_dict(self):
return self.value
return super(VocabString, self).to_dict()


@classmethod
def from_dict(cls, cls_dict):
if not cls_dict:
Expand Down Expand Up @@ -306,8 +306,9 @@ class DiscoveryMethod_2_0(VocabString):
TERM_USER = "User"


@vocabs.register_vocab
@register_vocab
class AttackerToolType_1_0(VocabString):
class AttackerToolType_1_0(vocabs.VocabString):
_namespace = 'http://stix.mitre.org/default_vocabularies-1'
_XSI_TYPE = 'stixVocabs:AttackerToolTypeVocab-1.0'
_VOCAB_VERSION = '1.0'
Expand Down
14 changes: 7 additions & 7 deletions stix/incident/history.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
# Copyright (c) 2017, The MITRE Corporation. All rights reserved.
# See LICENSE.txt for complete terms.

from mixbox import fields

# internal
import stix
import stix.utils as utils
import stix.bindings.incident as incident_binding
from stix.common.datetimewithprecision import DATETIME_PRECISION_VALUES

# relative
from .coa import COATaken

from mixbox import fields, entities

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


class JournalEntry(stix.Entity):
_namespace = "http://stix.mitre.org/Incident-1"
_binding = incident_binding
_binding_class = incident_binding.JournalEntryType

value = fields.TypedField("valueOf_", key_name="value")
author = fields.TypedField("author")
time = fields.TypedField("time") #TDOO: utils.dates.parse_value(value)
time = fields.DateTimeField("time")
time_precision = fields.TypedField("time_precision", preset_hook=validate_precision)

def __init__(self, value=None):
Expand All @@ -38,10 +39,10 @@ class HistoryItem(stix.Entity):
_namespace = "http://stix.mitre.org/Incident-1"
_binding = incident_binding
_binding_class = incident_binding.HistoryItemType

action_entry = fields.TypedField("Action_Entry", COATaken)
journal_entry = fields.TypedField("Journal_Entry", JournalEntry)

def __init__(self):
super(HistoryItem, self).__init__()

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

history_items = fields.TypedField("History_Item", HistoryItem, multiple=True, key_name="history_items")

@classmethod
def _dict_as_list(cls):
return False

4 changes: 2 additions & 2 deletions stix/report/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class Report(stix.Entity):
``datetime.datetime`` or ``str``.
header: A Report :class:`.Header` object.
campaigns: A collection of :class:`.Campaign` objects.
course_of_action: A collection of :class:`.CourseOfAction` objects.
courses_of_action: A collection of :class:`.CourseOfAction` objects.
exploit_targets: A collection of :class:`.ExploitTarget` objects.
incidents: A collection of :class:`.Incident` objects.
indicators: A collection of :class:`.Indicator` objects.
Expand All @@ -59,7 +59,7 @@ class Report(stix.Entity):

id_ = fields.IdField("id")
idref = fields.IdrefField("idref")
timestamp = fields.TypedField("timestamp")
timestamp = fields.DateTimeField("timestamp")
version = fields.TypedField("version")
header = fields.TypedField("Header", Header)
campaigns = fields.TypedField("Campaigns", type_="stix.report.Campaigns")
Expand Down
62 changes: 46 additions & 16 deletions stix/test/extensions/malware/maec_4_1_malware_test.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import unittest
from stix.core import STIXPackage
from mixbox.vendor.six import StringIO, BytesIO, text_type

from lxml import etree
import mixbox.xml
from mixbox.vendor.six import StringIO, text_type

from stix.test import EntityTestCase
from stix.extensions.malware.maec_4_1_malware import MAECInstance

try:
import maec
maec_present = True
except ImportError:
maec_present = False


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

Expand All @@ -17,18 +21,20 @@ class PythonMAECTests(EntityTestCase, unittest.TestCase):
'maec': {
'malware_subjects':
[
{'malware_instance_object_attributes':
{'id': 'maec-tst-obj-1',
'properties': {
'hashes':
[
{
'simple_hash_value': '9d7006e30fdf15e9c8e03e62534b3a3e',
'type': 'MD5'
}
],
'xsi:type': 'FileObjectType'}
}
{
'malware_instance_object_attributes': {
'id': 'maec-tst-obj-1',
'properties': {
'hashes':
[
{
'simple_hash_value': '9d7006e30fdf15e9c8e03e62534b3a3e',
'type': 'MD5'
}
],
'xsi:type': 'FileObjectType'
}
}
}
]
}
Expand All @@ -43,6 +49,7 @@ def test_add_name_type(self):
self.assertTrue("Remote Access Trojan" in maec_xml)


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


@unittest.skipIf(condition=maec_present is True, reason="These tests require the 'maec' library to be missing.")
class PythonMAECNotInstalledTest(unittest.TestCase):

def test_parsing_maec_fails(self):
try:
STIXPackage.from_xml(PythonMAECInPackageTests.XML_MAEC)
except ImportError as e:
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))

def test_handling_maec_object_fails(self):
try:
MAECInstance().from_dict(PythonMAECTests._full_dict)
except ImportError as e:
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))

def test_setting_maec_property_fails(self):
try:
m = MAECInstance()
m.maec = "foo"
except ImportError as e:
self.assertTrue(all(x in str(e) for x in ("No module named", "maec")))


if __name__ == "__main__":
unittest.main()
11 changes: 9 additions & 2 deletions stix/test/ttp_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ class ResourcesTests(EntityTestCase, unittest.TestCase):
'personas': PersonasTests._full_dict,
'tools': [
{
'title': "Tool"
'title': "Tool",
'type': [
{
'value': 'Malware',
'xsi:type': 'stixVocabs:AttackerToolTypeVocab-1.0'
}
]
}
],
'infrastructure': InfrastructureTests._full_dict
Expand All @@ -81,7 +87,7 @@ class ResourcesTests(EntityTestCase, unittest.TestCase):
class MalwareInstanceTests(EntityTestCase, unittest.TestCase):
klass = malware_instance.MalwareInstance

_full_dict = _full_dict = {
_full_dict = {
'id': 'example:test-1',
'title': 'Title',
'description': 'Description',
Expand Down Expand Up @@ -156,6 +162,7 @@ class BehaviorTests(EntityTestCase, unittest.TestCase):
'attack_patterns': AttackPatternsTests._full_dict
}


class TTPTests(EntityTestCase, unittest.TestCase):
klass = ttp.TTP
_full_dict = {
Expand Down
10 changes: 3 additions & 7 deletions stix/ttp/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,15 @@
# See LICENSE.txt for complete terms.

# mixbox
from mixbox import fields
from mixbox import typedlist
from mixbox import fields, typedlist

# internal
import stix
import stix.bindings.ttp as ttp_binding
from stix.common import ToolInformation
from stix.common.identity import Identity, IdentityFactory
import stix.bindings.ttp as ttp_binding

# relative
from .infrastructure import Infrastructure
from stix.ttp.infrastructure import Infrastructure

from mixbox import entities, fields

class _IdentityList(typedlist.TypedList):
def __init__(self, *args):
Expand Down
23 changes: 15 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[tox]
envlist = py26, py27, py33, py34, py35, py36, lxml23
envlist = py27, py33, py34, py35, py36, lxml23, no-maec

[testenv]
commands =
Expand All @@ -13,7 +13,7 @@ deps =
# We call this "lxml23" instead of "rhel6", since RHEL6 ships with LXML 2.2.3.
# python-stix requires at least 2.3.
[testenv:lxml23]
basepython=python2.6
basepython=python2.7
commands =
nosetests stix
deps =
Expand All @@ -22,11 +22,18 @@ deps =
python-dateutil==1.4.1
-rrequirements.txt

# Test the behavior when MAEC is not installed in the environment.
[testenv:no-maec]
commands =
nosetests stix
deps =
nose==1.3.7
tox==2.7.0

[travis]
python =
2.6: py26, lxml23
2.7: py27, docs
3.3: py33
3.4: py34
3.5: py35
3.6: py36
2.7: py27, docs, lxml23, no-maec
3.3: py33, no-maec
3.4: py34, no-maec
3.5: py35, no-maec
3.6: py36, no-maec

0 comments on commit 2a2f1b0

Please sign in to comment.