Repository: oasis-open/cti-python-stix2
Pull request #273 (2019-06-27 01:04:51)

Update course of action for stix2.1 (again)
I found that two property types were wrong, for `action_bin` and `action_reference`.  The former was a string property, but should be binary.  The latter was also a string property, but should be external-reference.  I don't know if I just goofed when I created those properties originally, or if the spec changed right after I wrote them.  Seems like I wouldn't have used string properties if they weren't string typed!  Maybe the spec is just changing too fast...

Also added tests for courses of action which have references (as opposed to binaries, via `action_bin`).

After having had the experience of writing the malware-analysis tests, I decided to change these tests to be written in a similar style.  It means a lot less tedious writing of long lists of keyword args and big dicts.  So the tests are better now, with less code (I hope).


Patch:
diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py
index 522aeda..7f32cfa 100644
--- a/stix2/v21/sdo.py
+++ b/stix2/v21/sdo.py
@@ -8,9 +8,10 @@ from six.moves.urllib.parse import quote_plus
 from ..core import STIXDomainObject
 from ..custom import _custom_object_builder
 from ..properties import (
-    BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
-    ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
-    StringProperty, TimestampProperty, TypeProperty,
+    BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
+    FloatProperty, IDProperty, IntegerProperty, ListProperty,
+    ObservableProperty, PatternProperty, ReferenceProperty, StringProperty,
+    TimestampProperty, TypeProperty,
 )
 from ..utils import NOW
 from .common import ExternalReference, GranularMarking, KillChainPhase
@@ -101,8 +102,8 @@ class CourseOfAction(STIXDomainObject):
         ('description', StringProperty()),
         ('action_type', StringProperty()),
         ('os_execution_envs', ListProperty(StringProperty)),
-        ('action_bin', StringProperty()),
-        ('action_reference', StringProperty()),
+        ('action_bin', BinaryProperty()),
+        ('action_reference', EmbeddedObjectProperty(ExternalReference)),
         ('revoked', BooleanProperty(default=lambda: False)),
         ('labels', ListProperty(StringProperty)),
         ('confidence', IntegerProperty()),


Test patch:
diff --git a/stix2/test/v21/test_course_of_action.py b/stix2/test/v21/test_course_of_action.py
index b86471a..44079fb 100644
--- a/stix2/test/v21/test_course_of_action.py
+++ b/stix2/test/v21/test_course_of_action.py
@@ -1,14 +1,12 @@
-import datetime as dt
+import json
 
 import pytest
-import pytz
 
 import stix2
 import stix2.exceptions
+import stix2.utils
 
-from .constants import COURSE_OF_ACTION_ID, IDENTITY_ID
-
-EXPECTED = """{
+COA_WITH_BIN_JSON = """{
     "type": "course-of-action",
     "spec_version": "2.1",
     "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
@@ -27,54 +25,63 @@ EXPECTED = """{
 }"""
 
 
-def test_course_of_action_example():
-    coa = stix2.v21.CourseOfAction(
-        id=COURSE_OF_ACTION_ID,
-        created_by_ref=IDENTITY_ID,
-        created="2016-04-06T20:03:48.000Z",
-        modified="2016-04-06T20:03:48.000Z",
-        name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
-        description="This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
-        action_type="textual:text/plain",
-        os_execution_envs=["a", "b", "c"],
-        action_bin="aGVsbG8gd29ybGQ=",
-    )
+COA_WITH_REF_JSON = """{
+    "type": "course-of-action",
+    "spec_version": "2.1",
+    "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
+    "created_by_ref": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
+    "created": "2016-04-06T20:03:48.000Z",
+    "modified": "2016-04-06T20:03:48.000Z",
+    "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
+    "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
+    "action_type": "textual:text/plain",
+    "os_execution_envs": [
+        "a",
+        "b",
+        "c"
+    ],
+    "action_reference": {
+        "source_name": "a source",
+        "description": "description of a source"
+    }
+}"""
+
+
+COA_WITH_BIN_DICT = json.loads(COA_WITH_BIN_JSON)
+COA_WITH_REF_DICT = json.loads(COA_WITH_REF_JSON)
 
-    assert str(coa) == EXPECTED
+
+@pytest.mark.parametrize(
+    "sdo_json,sdo_dict", [
+        (COA_WITH_BIN_JSON, COA_WITH_BIN_DICT),
+        (COA_WITH_REF_JSON, COA_WITH_REF_DICT),
+    ],
+)
+def test_course_of_action_example(sdo_json, sdo_dict):
+    coa = stix2.v21.CourseOfAction(**sdo_dict)
+    assert str(coa) == sdo_json
 
 
 @pytest.mark.parametrize(
-    "data", [
-        EXPECTED,
-        {
-            "created": "2016-04-06T20:03:48.000Z",
-            "created_by_ref": IDENTITY_ID,
-            "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
-            "id": COURSE_OF_ACTION_ID,
-            "modified": "2016-04-06T20:03:48.000Z",
-            "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
-            "spec_version": "2.1",
-            "type": "course-of-action",
-            "action_type": "textual:text/plain",
-            "os_execution_envs": ["a", "b", "c"],
-            "action_bin": "aGVsbG8gd29ybGQ=",
-        },
+    "sdo_json,sdo_dict", [
+        (COA_WITH_BIN_JSON, COA_WITH_BIN_DICT),
+        (COA_WITH_REF_JSON, COA_WITH_REF_DICT),
     ],
 )
-def test_parse_course_of_action(data):
-    coa = stix2.parse(data, version="2.1")
-
-    assert coa.type == 'course-of-action'
-    assert coa.spec_version == '2.1'
-    assert coa.id == COURSE_OF_ACTION_ID
-    assert coa.created == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
-    assert coa.modified == dt.datetime(2016, 4, 6, 20, 3, 48, tzinfo=pytz.utc)
-    assert coa.created_by_ref == IDENTITY_ID
-    assert coa.description == "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
-    assert coa.name == "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter"
-    assert coa.action_type == "textual:text/plain"
-    assert coa.os_execution_envs == ["a", "b", "c"]
-    assert coa.action_bin == "aGVsbG8gd29ybGQ="
+def test_parse_course_of_action(sdo_json, sdo_dict):
+
+    # Names of timestamp-valued attributes
+    ts_attrs = {"created", "modified"}
+
+    for data in (sdo_json, sdo_dict):
+        coa = stix2.parse(data, version="2.1")
+
+        # sdo_dict is handy as a source of attribute names/values to check
+        for attr_name, attr_value in sdo_dict.items():
+            cmp_value = stix2.utils.parse_into_datetime(attr_value) \
+                if attr_name in ts_attrs else attr_value
+
+            assert getattr(coa, attr_name) == cmp_value
 
 
 def test_course_of_action_constraint():