diff --git a/.travis.yml b/.travis.yml index 0d5a046..7c3a4bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ python: - "3.6" - "3.6-dev" - "nightly" +matrix: + allow_failures: + - python: "nightly" install: - pip install -U pip setuptools - pip install tox-travis pre-commit diff --git a/docs/guide/datastore.ipynb b/docs/guide/datastore.ipynb index 866b432..31635ba 100644 --- a/docs/guide/datastore.ipynb +++ b/docs/guide/datastore.ipynb @@ -73,7 +73,7 @@ "\n", "CTI Python STIX2 features a new interface for pulling and pushing STIX2 content. The new interface consists of [DataStore](../api/stix2.sources.rst#stix2.sources.DataStore), [DataSource](../api/stix2.sources.rst#stix2.sources.DataSource) and [DataSink](../api/stix2.sources.rst#stix2.sources.DataSink) constructs: a [DataSource](../api/stix2.sources.rst#stix2.sources.DataSource) for pulling STIX2 content, a [DataSink](../api/stix2.sources.rst#stix2.sources.DataSink) for pushing STIX2 content, and a [DataStore](../api/stix2.sources.rst#stix2.sources.DataStore) for both pulling and pushing.\n", "\n", - "The DataStore, [DataSource](../api/stix2.sources.rst#stix2.sources.DataSource), [DataSink](../api/stix2.sources.rst#stix2.sources.DataSink) (collectively referred to as the \"DataStore suite\") APIs are not referenced directly by a user but are used as base classes, which are then sublcassed by real DataStore suites. CTI Python STIX2 provides the DataStore suites of [FileSystem](../api/sources/stix2.sources.filesystem.rst), [Memory](../api/sources/stix2.sources.memory.rst), and [TAXII](../api/sources/stix2.sources.taxii.rst). Users are also encouraged to subclass the base classes and create their own custom DataStore suites." + "The DataStore, [DataSource](../api/stix2.sources.rst#stix2.sources.DataSource), [DataSink](../api/stix2.sources.rst#stix2.sources.DataSink) (collectively referred to as the \"DataStore suite\") APIs are not referenced directly by a user but are used as base classes, which are then subclassed by real DataStore suites. CTI Python STIX2 provides the DataStore suites of [FileSystem](../api/sources/stix2.sources.filesystem.rst), [Memory](../api/sources/stix2.sources.memory.rst), and [TAXII](../api/sources/stix2.sources.taxii.rst). Users are also encouraged to subclass the base classes and create their own custom DataStore suites." ] }, { diff --git a/stix2/base.py b/stix2/base.py index 76b07b8..fc13094 100644 --- a/stix2/base.py +++ b/stix2/base.py @@ -161,9 +161,12 @@ class _STIXBase(collections.Mapping): ", ".join(["{0!s}={1!r}".format(k, v) for k, v in props])) def __deepcopy__(self, memo): - # Assumption: we can ignore the memo argument, because no object will ever contain the same sub-object multiple times. + # Assume: we can ignore the memo argument, because no object will ever contain the same sub-object multiple times. new_inner = copy.deepcopy(self._inner, memo) cls = type(self) + if isinstance(self, _Observable): + # Assume: valid references in the original object are still valid in the new version + new_inner['_valid_refs'] = {'*': '*'} return cls(**new_inner) def properties_populated(self): @@ -221,6 +224,9 @@ class _Observable(_STIXBase): super(_Observable, self).__init__(**kwargs) def _check_ref(self, ref, prop, prop_name): + if '*' in self._STIXBase__valid_refs: + return # don't check if refs are valid + if ref not in self._STIXBase__valid_refs: raise InvalidObjRefError(self.__class__, prop_name, "'%s' is not a valid object in local scope" % ref) diff --git a/stix2/test/test_attack_pattern.py b/stix2/test/test_attack_pattern.py index 07d0898..0be118c 100644 --- a/stix2/test/test_attack_pattern.py +++ b/stix2/test/test_attack_pattern.py @@ -7,7 +7,6 @@ import stix2 from .constants import ATTACK_PATTERN_ID - EXPECTED = """{ "type": "attack-pattern", "id": "attack-pattern--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", diff --git a/stix2/test/test_campaign.py b/stix2/test/test_campaign.py index 202534d..b226478 100644 --- a/stix2/test/test_campaign.py +++ b/stix2/test/test_campaign.py @@ -7,7 +7,6 @@ import stix2 from .constants import CAMPAIGN_ID - EXPECTED = """{ "type": "campaign", "id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", diff --git a/stix2/test/test_course_of_action.py b/stix2/test/test_course_of_action.py index 3dc379d..e376f0d 100644 --- a/stix2/test/test_course_of_action.py +++ b/stix2/test/test_course_of_action.py @@ -7,7 +7,6 @@ import stix2 from .constants import COURSE_OF_ACTION_ID - EXPECTED = """{ "type": "course-of-action", "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", diff --git a/stix2/test/test_external_reference.py b/stix2/test/test_external_reference.py index c4c3755..2b79f01 100644 --- a/stix2/test/test_external_reference.py +++ b/stix2/test/test_external_reference.py @@ -6,7 +6,6 @@ import pytest import stix2 - VERIS = """{ "source_name": "veris", "url": "https://github.com/vz-risk/VCDB/blob/master/data/json/0001AA7F-C601-424A-B2B8-BE6C9F5164E7.json", diff --git a/stix2/test/test_identity.py b/stix2/test/test_identity.py index 8e3dd42..ff3631f 100644 --- a/stix2/test/test_identity.py +++ b/stix2/test/test_identity.py @@ -7,7 +7,6 @@ import stix2 from .constants import IDENTITY_ID - EXPECTED = """{ "type": "identity", "id": "identity--311b2d2d-f010-5473-83ec-1edf84858f4c", diff --git a/stix2/test/test_indicator.py b/stix2/test/test_indicator.py index c501da4..78d1bf2 100644 --- a/stix2/test/test_indicator.py +++ b/stix2/test/test_indicator.py @@ -8,7 +8,6 @@ import stix2 from .constants import FAKE_TIME, INDICATOR_ID, INDICATOR_KWARGS - EXPECTED_INDICATOR = """{ "type": "indicator", "id": "indicator--01234567-89ab-cdef-0123-456789abcdef", diff --git a/stix2/test/test_intrusion_set.py b/stix2/test/test_intrusion_set.py index 481b3cb..53e18f5 100644 --- a/stix2/test/test_intrusion_set.py +++ b/stix2/test/test_intrusion_set.py @@ -7,7 +7,6 @@ import stix2 from .constants import INTRUSION_SET_ID - EXPECTED = """{ "type": "intrusion-set", "id": "intrusion-set--4e78f46f-a023-4e5f-bc24-71b3ca22ec29", diff --git a/stix2/test/test_kill_chain_phases.py b/stix2/test/test_kill_chain_phases.py index 9cddcb9..220c714 100644 --- a/stix2/test/test_kill_chain_phases.py +++ b/stix2/test/test_kill_chain_phases.py @@ -4,7 +4,6 @@ import pytest import stix2 - LMCO_RECON = """{ "kill_chain_name": "lockheed-martin-cyber-kill-chain", "phase_name": "reconnaissance" diff --git a/stix2/test/test_malware.py b/stix2/test/test_malware.py index 7f665ea..8c565cd 100644 --- a/stix2/test/test_malware.py +++ b/stix2/test/test_malware.py @@ -8,7 +8,6 @@ import stix2 from .constants import FAKE_TIME, MALWARE_ID, MALWARE_KWARGS - EXPECTED_MALWARE = """{ "type": "malware", "id": "malware--fedcba98-7654-3210-fedc-ba9876543210", diff --git a/stix2/test/test_markings.py b/stix2/test/test_markings.py index d2271f0..efd0476 100644 --- a/stix2/test/test_markings.py +++ b/stix2/test/test_markings.py @@ -8,7 +8,6 @@ from stix2 import TLP_WHITE from .constants import MARKING_DEFINITION_ID - EXPECTED_TLP_MARKING_DEFINITION = """{ "type": "marking-definition", "id": "marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9", diff --git a/stix2/test/test_object_markings.py b/stix2/test/test_object_markings.py index 10949ab..f216355 100644 --- a/stix2/test/test_object_markings.py +++ b/stix2/test/test_object_markings.py @@ -3,8 +3,9 @@ import pytest from stix2 import TLP_AMBER, Malware, exceptions, markings -from .constants import FAKE_TIME, MALWARE_ID, MARKING_IDS +from .constants import FAKE_TIME, MALWARE_ID from .constants import MALWARE_KWARGS as MALWARE_KWARGS_CONST +from .constants import MARKING_IDS """Tests for the Data Markings API.""" diff --git a/stix2/test/test_observed_data.py b/stix2/test/test_observed_data.py index 3029b68..30c3cab 100644 --- a/stix2/test/test_observed_data.py +++ b/stix2/test/test_observed_data.py @@ -1162,3 +1162,25 @@ def test_x509_certificate_example(): assert x509.type == "x509-certificate" assert x509.issuer == "C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com" # noqa assert x509.subject == "C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org" # noqa + + +def test_new_version_with_related_objects(): + data = stix2.ObservedData( + first_observed="2016-03-12T12:00:00Z", + last_observed="2016-03-12T12:00:00Z", + number_observed=1, + objects={ + 'src_ip': { + 'type': 'ipv4-addr', + 'value': '127.0.0.1/32' + }, + 'domain': { + 'type': 'domain-name', + 'value': 'example.com', + 'resolves_to_refs': ['src_ip'] + } + } + ) + new_version = data.new_version(last_observed="2017-12-12T12:00:00Z") + assert new_version.last_observed.year == 2017 + assert new_version.objects['domain'].resolves_to_refs[0] == 'src_ip' diff --git a/stix2/test/test_relationship.py b/stix2/test/test_relationship.py index 6d65544..c6e2d6f 100644 --- a/stix2/test/test_relationship.py +++ b/stix2/test/test_relationship.py @@ -8,7 +8,6 @@ import stix2 from .constants import (FAKE_TIME, INDICATOR_ID, MALWARE_ID, RELATIONSHIP_ID, RELATIONSHIP_KWARGS) - EXPECTED_RELATIONSHIP = """{ "type": "relationship", "id": "relationship--00000000-1111-2222-3333-444444444444", diff --git a/stix2/test/test_report.py b/stix2/test/test_report.py index a5775e3..b38ab40 100644 --- a/stix2/test/test_report.py +++ b/stix2/test/test_report.py @@ -7,7 +7,6 @@ import stix2 from .constants import INDICATOR_KWARGS, REPORT_ID - EXPECTED = """{ "type": "report", "id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3", diff --git a/stix2/test/test_sighting.py b/stix2/test/test_sighting.py index af91413..ce1fab9 100644 --- a/stix2/test/test_sighting.py +++ b/stix2/test/test_sighting.py @@ -7,7 +7,6 @@ import stix2 from .constants import INDICATOR_ID, SIGHTING_ID, SIGHTING_KWARGS - EXPECTED_SIGHTING = """{ "type": "sighting", "id": "sighting--bfbc19db-ec35-4e45-beed-f8bde2a772fb", diff --git a/stix2/test/test_threat_actor.py b/stix2/test/test_threat_actor.py index c095c3b..8079a21 100644 --- a/stix2/test/test_threat_actor.py +++ b/stix2/test/test_threat_actor.py @@ -7,7 +7,6 @@ import stix2 from .constants import THREAT_ACTOR_ID - EXPECTED = """{ "type": "threat-actor", "id": "threat-actor--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", diff --git a/stix2/test/test_tool.py b/stix2/test/test_tool.py index be52f6d..21ece24 100644 --- a/stix2/test/test_tool.py +++ b/stix2/test/test_tool.py @@ -7,7 +7,6 @@ import stix2 from .constants import TOOL_ID - EXPECTED = """{ "type": "tool", "id": "tool--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", diff --git a/stix2/test/test_vulnerability.py b/stix2/test/test_vulnerability.py index a6426d8..e7358df 100644 --- a/stix2/test/test_vulnerability.py +++ b/stix2/test/test_vulnerability.py @@ -7,7 +7,6 @@ import stix2 from .constants import VULNERABILITY_ID - EXPECTED = """{ "type": "vulnerability", "id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", diff --git a/stix2/v20/sdo.py b/stix2/v20/sdo.py index 1af0777..2d36aaf 100644 --- a/stix2/v20/sdo.py +++ b/stix2/v20/sdo.py @@ -144,7 +144,7 @@ class IntrusionSet(STIXDomainObject): ('description', StringProperty()), ('aliases', ListProperty(StringProperty)), ('first_seen', TimestampProperty()), - ('last_seen ', TimestampProperty()), + ('last_seen', TimestampProperty()), ('goals', ListProperty(StringProperty)), ('resource_level', StringProperty()), ('primary_motivation', StringProperty()),