diff --git a/CHANGELOG b/CHANGELOG index 9911e841..f4cce282 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,15 @@ CHANGELOG ========= +1.1.3 - 2019-08-12 + +* #258 Ignores empty values for optional fields +* #259 Adds support for lang granular markings +* #261 Prevents instantiation or serialization of TLP marking-definitions that don't follow the spec +* #262 Supports actual objects in _valid_refs instead of just strings +* #264 Supports accessing objects in bundles via STIX Object IDs +* #274 Fixes bug parsing bundle containing custom objects + 1.1.2 - 2019-02-13 * #86 Adds helper function to Location objects to generate a URL to the location in an online map engine. diff --git a/README.rst b/README.rst index c509d467..256e4d69 100644 --- a/README.rst +++ b/README.rst @@ -135,6 +135,9 @@ select additional or substitute Maintainers, per `consensus agreements https://github.com/emmanvg/; WWW: `MITRE Corporation `__ +- `Jason Keirstead `__; GitHub ID: + https://github.com/JasonKeirstead; WWW: `IBM `__ + About OASIS TC Open Repositories -------------------------------- diff --git a/docs/guide/custom.ipynb b/docs/guide/custom.ipynb index 6031e5f6..042f11e2 100644 --- a/docs/guide/custom.ipynb +++ b/docs/guide/custom.ipynb @@ -175,9 +175,9 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "identity",\n",
-       "    "id": "identity--87aac643-341b-413a-b702-ea5820416155",\n",
-       "    "created": "2018-04-05T18:38:10.269Z",\n",
-       "    "modified": "2018-04-05T18:38:10.269Z",\n",
+       "    "id": "identity--e7fd0fe0-25ff-4fcb-abe5-b6254a9d1a22",\n",
+       "    "created": "2019-07-25T18:18:18.241Z",\n",
+       "    "modified": "2019-07-25T18:18:18.241Z",\n",
        "    "name": "John Smith",\n",
        "    "identity_class": "individual",\n",
        "    "x_foo": "bar"\n",
@@ -194,8 +194,6 @@
     }
    ],
    "source": [
-    "from stix2 import Identity\n",
-    "\n",
     "identity = Identity(name=\"John Smith\",\n",
     "                    identity_class=\"individual\",\n",
     "                    custom_properties={\n",
@@ -289,9 +287,9 @@
        ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n",
        ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "identity",\n",
-       "    "id": "identity--a1ad0a6f-39ab-4642-9a72-aaa198b1eee2",\n",
-       "    "created": "2018-04-05T18:38:12.270Z",\n",
-       "    "modified": "2018-04-05T18:38:12.270Z",\n",
+       "    "id": "identity--033b5f42-c94f-488f-8efa-2b6a167f0d6f",\n",
+       "    "created": "2019-07-25T18:18:21.352Z",\n",
+       "    "modified": "2019-07-25T18:18:21.352Z",\n",
        "    "name": "John Smith",\n",
        "    "identity_class": "individual",\n",
        "    "x_foo": "bar"\n",
@@ -426,6 +424,113 @@
     "print(identity3.x_foo)"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "To remove a custom properties, use `new_version()` and set it to `None`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
{\n",
+       "    "type": "identity",\n",
+       "    "id": "identity--311b2d2d-f010-4473-83ec-1edf84858f4c",\n",
+       "    "created": "2015-12-21T19:59:11.000Z",\n",
+       "    "modified": "2019-07-25T18:18:25.927Z",\n",
+       "    "name": "John Smith",\n",
+       "    "identity_class": "individual"\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "identity4 = identity3.new_version(x_foo=None)\n", + "print(identity4)" + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/docs/guide/versioning.ipynb b/docs/guide/versioning.ipynb index 6074d00d..eb0708aa 100644 --- a/docs/guide/versioning.ipynb +++ b/docs/guide/versioning.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": { "nbsphinx": "hidden" }, @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "nbsphinx": "hidden" }, @@ -63,12 +63,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "To create a new version of an existing object, specify the property(ies) you want to change and their new values:" + "To create a new version of an existing object, specify the property(ies) you want to change and their new values. For example, here we change the label from \"anomalous-activity\" to \"malicious-activity\":" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -144,12 +144,13 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "indicator",\n",
-       "    "id": "indicator--dd052ff6-e404-444b-beb9-eae96d1e79ea",\n",
+       "    "id": "indicator--8ad18fc7-457c-475d-b292-1ec44febe0fd",\n",
        "    "created": "2016-01-01T08:00:00.000Z",\n",
-       "    "modified": "2018-04-05T20:02:51.161Z",\n",
+       "    "modified": "2019-07-25T17:59:34.815Z",\n",
        "    "name": "File hash for Foobar malware",\n",
+       "    "description": "A file indicator",\n",
        "    "pattern": "[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",\n",
-       "    "valid_from": "2018-04-05T20:02:51.138312Z",\n",
+       "    "valid_from": "2019-07-25T17:59:34.779826Z",\n",
        "    "labels": [\n",
        "        "malicious-activity"\n",
        "    ]\n",
@@ -160,7 +161,7 @@
        ""
       ]
      },
-     "execution_count": 4,
+     "execution_count": 3,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -170,6 +171,7 @@
     "\n",
     "indicator = Indicator(created=\"2016-01-01T08:00:00.000Z\",\n",
     "                      name=\"File hash for suspicious file\",\n",
+    "                      description=\"A file indicator\",\n",
     "                      labels=[\"anomalous-activity\"],\n",
     "                      pattern=\"[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']\")\n",
     "\n",
@@ -187,7 +189,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 4,
    "metadata": {
     "scrolled": true
    },
@@ -205,6 +207,117 @@
     "indicator.new_version(id=\"indicator--cc42e358-8b9b-493c-9646-6ecd73b41c21\")"
    ]
   },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "You can remove optional or custom properties by setting them to `None` when you call `new_version()`."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
{\n",
+       "    "type": "indicator",\n",
+       "    "id": "indicator--8ad18fc7-457c-475d-b292-1ec44febe0fd",\n",
+       "    "created": "2016-01-01T08:00:00.000Z",\n",
+       "    "modified": "2019-07-25T17:59:42.648Z",\n",
+       "    "name": "File hash for suspicious file",\n",
+       "    "pattern": "[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",\n",
+       "    "valid_from": "2019-07-25T17:59:34.779826Z",\n",
+       "    "labels": [\n",
+       "        "anomalous-activity"\n",
+       "    ]\n",
+       "}\n",
+       "
\n" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "indicator3 = indicator.new_version(description=None)\n", + "print(indicator3)" + ] + }, { "cell_type": "markdown", "metadata": { @@ -292,15 +405,15 @@ ".highlight .vm { color: #19177C } /* Name.Variable.Magic */\n", ".highlight .il { color: #666666 } /* Literal.Number.Integer.Long */
{\n",
        "    "type": "indicator",\n",
-       "    "id": "indicator--dd052ff6-e404-444b-beb9-eae96d1e79ea",\n",
+       "    "id": "indicator--8ad18fc7-457c-475d-b292-1ec44febe0fd",\n",
        "    "created": "2016-01-01T08:00:00.000Z",\n",
-       "    "modified": "2018-04-05T20:02:54.704Z",\n",
-       "    "name": "File hash for Foobar malware",\n",
+       "    "modified": "2019-07-25T17:59:52.198Z",\n",
+       "    "name": "File hash for suspicious file",\n",
        "    "pattern": "[file:hashes.md5 = 'd41d8cd98f00b204e9800998ecf8427e']",\n",
-       "    "valid_from": "2018-04-05T20:02:51.138312Z",\n",
+       "    "valid_from": "2019-07-25T17:59:34.779826Z",\n",
        "    "revoked": true,\n",
        "    "labels": [\n",
-       "        "malicious-activity"\n",
+       "        "anomalous-activity"\n",
        "    ]\n",
        "}\n",
        "
\n" @@ -315,8 +428,8 @@ } ], "source": [ - "indicator2 = indicator2.revoke()\n", - "print(indicator2)" + "indicator4 = indicator3.revoke()\n", + "print(indicator4)" ] } ], diff --git a/setup.cfg b/setup.cfg index ae45560a..b012bb95 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.1.2 +current_version = 1.1.3 commit = True tag = True diff --git a/setup.py b/setup.py index 07de2a4c..497bf01d 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ def get_long_description(): version=get_version(), description='Produce and consume STIX 2 JSON content', long_description=get_long_description(), + long_description_content_type='text/x-rst', url='https://oasis-open.github.io/cti-documentation/', author='OASIS Cyber Threat Intelligence Technical Committee', author_email='cti-users@lists.oasis-open.org', @@ -47,7 +48,7 @@ def get_long_description(): 'Programming Language :: Python :: 3.7', ], keywords='stix stix2 json cti cyber threat intelligence', - packages=find_packages(exclude=['*.test']), + packages=find_packages(exclude=['*.test', '*.test.*']), install_requires=[ 'python-dateutil', 'pytz', diff --git a/stix2/test/v20/test_datastore_filesystem.py b/stix2/test/v20/test_datastore_filesystem.py index 25de37e2..317f927b 100644 --- a/stix2/test/v20/test_datastore_filesystem.py +++ b/stix2/test/v20/test_datastore_filesystem.py @@ -125,15 +125,13 @@ def rel_fs_store(): def test_filesystem_source_nonexistent_folder(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FileSystemSource('nonexistent-folder') - assert "for STIX data does not exist" in str(excinfo) def test_filesystem_sink_nonexistent_folder(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FileSystemSink('nonexistent-folder') - assert "for STIX data does not exist" in str(excinfo) def test_filesystem_source_bad_json_file(fs_source, bad_json_files): @@ -441,9 +439,8 @@ def test_filesystem_attempt_stix_file_overwrite(fs_store): ) # Now attempt to overwrite the existing file - with pytest.raises(DataSourceError) as excinfo: + with pytest.raises(DataSourceError): fs_store.add(camp8) - assert "Attempted to overwrite file" in str(excinfo) os.remove(filepath) diff --git a/stix2/test/v20/test_pattern_expressions.py b/stix2/test/v20/test_pattern_expressions.py index 3dc7cde9..23a401ba 100644 --- a/stix2/test/v20/test_pattern_expressions.py +++ b/stix2/test/v20/test_pattern_expressions.py @@ -257,7 +257,7 @@ def test_and_observable_expression(): def test_invalid_and_observable_expression(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression( "user-account:display_name", @@ -268,7 +268,6 @@ def test_invalid_and_observable_expression(): stix2.StringConstant("admin"), ), ]) - assert "All operands to an 'AND' expression must have the same object type" in str(excinfo) def test_hex(): @@ -352,30 +351,26 @@ def test_list2(): def test_invalid_constant_type(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.EqualityComparisonExpression( "artifact:payload_bin", {'foo': 'bar'}, ) - assert 'Unable to create a constant' in str(excinfo) def test_invalid_integer_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.IntegerConstant('foo') - assert 'must be an integer' in str(excinfo) def test_invalid_timestamp_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.TimestampConstant('foo') - assert 'Must be a datetime object or timestamp string' in str(excinfo) def test_invalid_float_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FloatConstant('foo') - assert 'must be a float' in str(excinfo) @pytest.mark.parametrize( @@ -400,9 +395,8 @@ def test_boolean_constant(data, result): def test_invalid_boolean_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.BooleanConstant('foo') - assert 'must be a boolean' in str(excinfo) @pytest.mark.parametrize( @@ -412,21 +406,18 @@ def test_invalid_boolean_constant(): ], ) def test_invalid_hash_constant(hashtype, data): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.HashConstant(data, hashtype) - assert 'is not a valid {} hash'.format(hashtype) in str(excinfo) def test_invalid_hex_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.HexConstant('mm') - assert "must contain an even number of hexadecimal characters" in str(excinfo) def test_invalid_binary_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.BinaryConstant('foo') - assert 'must contain a base64' in str(excinfo) def test_escape_quotes_and_backslashes(): @@ -459,15 +450,13 @@ def test_repeat_qualifier(): def test_invalid_repeat_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.RepeatQualifier('foo') - assert 'is not a valid argument for a Repeat Qualifier' in str(excinfo) def test_invalid_within_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.WithinQualifier('foo') - assert 'is not a valid argument for a Within Qualifier' in str(excinfo) def test_startstop_qualifier(): @@ -485,19 +474,17 @@ def test_startstop_qualifier(): def test_invalid_startstop_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.StartStopQualifier( 'foo', stix2.TimestampConstant('2016-06-01T00:00:00Z'), ) - assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.StartStopQualifier( datetime.date(2016, 6, 1), 'foo', ) - assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo) def test_make_constant_already_a_constant(): diff --git a/stix2/test/v21/test_datastore_filesystem.py b/stix2/test/v21/test_datastore_filesystem.py index 34b10881..9917ccd0 100644 --- a/stix2/test/v21/test_datastore_filesystem.py +++ b/stix2/test/v21/test_datastore_filesystem.py @@ -124,15 +124,13 @@ def rel_fs_store(): def test_filesystem_source_nonexistent_folder(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FileSystemSource('nonexistent-folder') - assert "for STIX data does not exist" in str(excinfo) def test_filesystem_sink_nonexistent_folder(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FileSystemSink('nonexistent-folder') - assert "for STIX data does not exist" in str(excinfo) def test_filesystem_source_bad_json_file(fs_source, bad_json_files): diff --git a/stix2/test/v21/test_pattern_expressions.py b/stix2/test/v21/test_pattern_expressions.py index 3dc7cde9..23a401ba 100644 --- a/stix2/test/v21/test_pattern_expressions.py +++ b/stix2/test/v21/test_pattern_expressions.py @@ -257,7 +257,7 @@ def test_and_observable_expression(): def test_invalid_and_observable_expression(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.AndBooleanExpression([ stix2.EqualityComparisonExpression( "user-account:display_name", @@ -268,7 +268,6 @@ def test_invalid_and_observable_expression(): stix2.StringConstant("admin"), ), ]) - assert "All operands to an 'AND' expression must have the same object type" in str(excinfo) def test_hex(): @@ -352,30 +351,26 @@ def test_list2(): def test_invalid_constant_type(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.EqualityComparisonExpression( "artifact:payload_bin", {'foo': 'bar'}, ) - assert 'Unable to create a constant' in str(excinfo) def test_invalid_integer_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.IntegerConstant('foo') - assert 'must be an integer' in str(excinfo) def test_invalid_timestamp_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.TimestampConstant('foo') - assert 'Must be a datetime object or timestamp string' in str(excinfo) def test_invalid_float_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.FloatConstant('foo') - assert 'must be a float' in str(excinfo) @pytest.mark.parametrize( @@ -400,9 +395,8 @@ def test_boolean_constant(data, result): def test_invalid_boolean_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.BooleanConstant('foo') - assert 'must be a boolean' in str(excinfo) @pytest.mark.parametrize( @@ -412,21 +406,18 @@ def test_invalid_boolean_constant(): ], ) def test_invalid_hash_constant(hashtype, data): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.HashConstant(data, hashtype) - assert 'is not a valid {} hash'.format(hashtype) in str(excinfo) def test_invalid_hex_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.HexConstant('mm') - assert "must contain an even number of hexadecimal characters" in str(excinfo) def test_invalid_binary_constant(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.BinaryConstant('foo') - assert 'must contain a base64' in str(excinfo) def test_escape_quotes_and_backslashes(): @@ -459,15 +450,13 @@ def test_repeat_qualifier(): def test_invalid_repeat_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.RepeatQualifier('foo') - assert 'is not a valid argument for a Repeat Qualifier' in str(excinfo) def test_invalid_within_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.WithinQualifier('foo') - assert 'is not a valid argument for a Within Qualifier' in str(excinfo) def test_startstop_qualifier(): @@ -485,19 +474,17 @@ def test_startstop_qualifier(): def test_invalid_startstop_qualifier(): - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.StartStopQualifier( 'foo', stix2.TimestampConstant('2016-06-01T00:00:00Z'), ) - assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo) - with pytest.raises(ValueError) as excinfo: + with pytest.raises(ValueError): stix2.StartStopQualifier( datetime.date(2016, 6, 1), 'foo', ) - assert 'is not a valid argument for a Start/Stop Qualifier' in str(excinfo) def test_make_constant_already_a_constant(): diff --git a/stix2/version.py b/stix2/version.py index 72f26f59..0b2f79db 100644 --- a/stix2/version.py +++ b/stix2/version.py @@ -1 +1 @@ -__version__ = "1.1.2" +__version__ = "1.1.3" diff --git a/tox.ini b/tox.ini index f3a10fba..ca7b4bc6 100644 --- a/tox.ini +++ b/tox.ini @@ -34,9 +34,10 @@ commands = [testenv:packaging] deps = - readme_renderer + twine commands = - python setup.py check -r -s + python setup.py bdist_wheel --universal + twine check dist/* [travis] python =