Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix versionability detection #401

Merged
merged 8 commits into from
Jun 10, 2020
2 changes: 1 addition & 1 deletion stix2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
RepeatQualifier, StartStopQualifier, StringConstant, TimestampConstant,
WithinQualifier,
)
from .utils import new_version, revoke
from .v20 import * # This import will always be the latest STIX 2.X version
from .version import __version__
from .versioning import new_version, revoke

_collect_stix2_mappings()
4 changes: 2 additions & 2 deletions stix2/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
from .utils import (
NOW, PREFIX_21_REGEX, find_property_index, format_datetime, get_timestamp,
)
from .utils import new_version as _new_version
from .utils import revoke as _revoke
from .versioning import new_version as _new_version
from .versioning import revoke as _revoke

try:
from collections.abc import Mapping
Expand Down
3 changes: 2 additions & 1 deletion stix2/markings/granular_markings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

from stix2 import exceptions
from stix2.markings import utils
from stix2.utils import is_marking, new_version
from stix2.utils import is_marking
from stix2.versioning import new_version


def get_markings(obj, selectors, inherited=False, descendants=False, marking_ref=True, lang=True):
Expand Down
2 changes: 1 addition & 1 deletion stix2/markings/object_markings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from stix2 import exceptions
from stix2.markings import utils
from stix2.utils import new_version
from stix2.versioning import new_version


def get_markings(obj):
Expand Down
7 changes: 3 additions & 4 deletions stix2/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,15 @@
MutuallyExclusivePropertiesError,
)
from .parsing import STIX2_OBJ_MAPS, parse, parse_observable
from .utils import (
TYPE_21_REGEX, TYPE_REGEX, _get_dict, get_class_hierarchy_names,
parse_into_datetime,
)
from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime

try:
from collections.abc import Mapping
except ImportError:
from collections import Mapping

TYPE_REGEX = re.compile(r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$')
TYPE_21_REGEX = re.compile(r'^([a-z][a-z0-9]*)+(-[a-z0-9]+)*\-?$')
ERROR_INVALID_ID = (
"not a valid STIX identifier, must match <object-type>--<UUID>: {}"
)
Expand Down
1 change: 1 addition & 0 deletions stix2/test/v20/test_object_markings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
MALWARE_KWARGS = MALWARE_KWARGS_CONST.copy()
MALWARE_KWARGS.update({
'id': MALWARE_ID,
'type': 'malware',
'created': FAKE_TIME,
'modified': FAKE_TIME,
})
Expand Down
153 changes: 142 additions & 11 deletions stix2/test/v20/test_versioning.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import pytest

import stix2
import stix2.exceptions
import stix2.utils
import stix2.v20
import stix2.versioning

from .constants import CAMPAIGN_MORE_KWARGS

Expand Down Expand Up @@ -142,7 +146,7 @@ def test_versioning_error_revoke_of_revoked():

def test_making_new_version_dict():
campaign_v1 = CAMPAIGN_MORE_KWARGS
campaign_v2 = stix2.utils.new_version(CAMPAIGN_MORE_KWARGS, name="fred")
campaign_v2 = stix2.versioning.new_version(CAMPAIGN_MORE_KWARGS, name="fred")

assert campaign_v1['id'] == campaign_v2['id']
assert campaign_v1['created_by_ref'] == campaign_v2['created_by_ref']
Expand All @@ -155,7 +159,7 @@ def test_making_new_version_dict():

def test_versioning_error_dict_bad_modified_value():
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
stix2.utils.new_version(CAMPAIGN_MORE_KWARGS, modified="2015-04-06T20:03:00.000Z")
stix2.versioning.new_version(CAMPAIGN_MORE_KWARGS, modified="2015-04-06T20:03:00.000Z")

assert excinfo.value.cls == dict
assert excinfo.value.prop_name == "modified"
Expand All @@ -171,22 +175,22 @@ def test_versioning_error_dict_no_modified_value():
'created': "2016-04-06T20:03:00.000Z",
'name': "Green Group Attacks Against Finance",
}
campaign_v2 = stix2.utils.new_version(campaign_v1, modified="2017-04-06T20:03:00.000Z")
campaign_v2 = stix2.versioning.new_version(campaign_v1, modified="2017-04-06T20:03:00.000Z")

assert str(campaign_v2['modified']) == "2017-04-06T20:03:00.000Z"


def test_making_new_version_invalid_cls():
campaign_v1 = "This is a campaign."
with pytest.raises(ValueError) as excinfo:
stix2.utils.new_version(campaign_v1, name="fred")
stix2.versioning.new_version(campaign_v1, name="fred")

assert 'cannot create new version of object of this type' in str(excinfo.value)


def test_revoke_dict():
campaign_v1 = CAMPAIGN_MORE_KWARGS
campaign_v2 = stix2.utils.revoke(campaign_v1)
campaign_v2 = stix2.versioning.revoke(campaign_v1)

assert campaign_v1['id'] == campaign_v2['id']
assert campaign_v1['created_by_ref'] == campaign_v2['created_by_ref']
Expand All @@ -198,20 +202,26 @@ def test_revoke_dict():
assert campaign_v2['revoked']


def test_revoke_unversionable():
sco = stix2.v20.File(name="data.txt")
with pytest.raises(ValueError):
sco.revoke()


def test_versioning_error_revoke_of_revoked_dict():
campaign_v1 = CAMPAIGN_MORE_KWARGS
campaign_v2 = stix2.utils.revoke(campaign_v1)
campaign_v2 = stix2.versioning.revoke(campaign_v1)

with pytest.raises(stix2.exceptions.RevokeError) as excinfo:
stix2.utils.revoke(campaign_v2)
stix2.versioning.revoke(campaign_v2)

assert excinfo.value.called_by == "revoke"


def test_revoke_invalid_cls():
campaign_v1 = "This is a campaign."
with pytest.raises(ValueError) as excinfo:
stix2.utils.revoke(campaign_v1)
stix2.versioning.revoke(campaign_v1)

assert 'cannot revoke object of this type' in str(excinfo.value)

Expand All @@ -224,7 +234,7 @@ def test_remove_custom_stix_property():
allow_custom=True,
)

mal_nc = stix2.utils.remove_custom_stix(mal)
mal_nc = stix2.versioning.remove_custom_stix(mal)

assert "x_custom" not in mal_nc
assert (stix2.utils.parse_into_datetime(mal["modified"], precision="millisecond") <
Expand All @@ -243,15 +253,136 @@ class Animal(object):

animal = Animal(species="lion", animal_class="mammal")

nc = stix2.utils.remove_custom_stix(animal)
nc = stix2.versioning.remove_custom_stix(animal)

assert nc is None


def test_remove_custom_stix_no_custom():
campaign_v1 = stix2.v20.Campaign(**CAMPAIGN_MORE_KWARGS)
campaign_v2 = stix2.utils.remove_custom_stix(campaign_v1)
campaign_v2 = stix2.versioning.remove_custom_stix(campaign_v1)

assert len(campaign_v1.keys()) == len(campaign_v2.keys())
assert campaign_v1.id == campaign_v2.id
assert campaign_v1.description == campaign_v2.description


def test_version_unversionable_dict():
f = {
"type": "file",
"name": "data.txt",
}

with pytest.raises(ValueError):
stix2.versioning.new_version(f)


def test_version_sco_with_modified():
"""
Ensure new_version() doesn't get tripped up over unversionable objects with
properties not used for versioning, but whose names conflict with
versioning properties.
"""

file_sco = {
"type": "file",
"name": "data.txt",
"created": "1973-11-23T02:31:37Z",
"modified": "1991-05-13T19:24:57Z",
}

with pytest.raises(ValueError):
stix2.versioning.new_version(file_sco, name="newname.txt")

with pytest.raises(ValueError):
stix2.versioning.revoke(file_sco)

file_sco_obj = stix2.v20.File(
name="data.txt",
created="1973-11-23T02:31:37Z",
modified="1991-05-13T19:24:57Z",
)

with pytest.raises(ValueError):
stix2.versioning.new_version(file_sco_obj, name="newname.txt")

with pytest.raises(ValueError):
stix2.versioning.revoke(file_sco_obj)


def test_version_sco_with_custom():
"""
If we add custom properties named like versioning properties to an object
type which is otherwise unversionable, versioning should start working.
"""

file_sco_obj = stix2.v20.File(
name="data.txt",
created="1973-11-23T02:31:37Z",
modified="1991-05-13T19:24:57Z",
revoked=False, # the custom property
allow_custom=True,
)

new_file_sco_obj = stix2.versioning.new_version(
file_sco_obj, name="newname.txt",
)

assert new_file_sco_obj.name == "newname.txt"

revoked_obj = stix2.versioning.revoke(new_file_sco_obj)
assert revoked_obj.revoked


def test_version_disable_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"], description="Steals your identity!",
x_custom=123, allow_custom=True,
)

# Remove the custom property, and disallow custom properties in the
# resulting object.
m2 = stix2.versioning.new_version(m, x_custom=None, allow_custom=False)
assert "x_custom" not in m2

# Remove a regular property and leave the custom one, disallow custom
# properties, and make sure we get an error.
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
stix2.versioning.new_version(m, description=None, allow_custom=False)


def test_version_enable_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"], description="Steals your identity!",
)

# Add a custom property to an object for which it was previously disallowed
m2 = stix2.versioning.new_version(m, x_custom=123, allow_custom=True)
assert "x_custom" in m2

# Add a custom property without enabling it, make sure we get an error
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
stix2.versioning.new_version(m, x_custom=123, allow_custom=False)


def test_version_propagate_custom():
m = stix2.v20.Malware(
name="foo", labels=["label"],
)

# Remember custom-not-allowed setting from original; produce error
with pytest.raises(stix2.exceptions.ExtraPropertiesError):
stix2.versioning.new_version(m, x_custom=123)

m2 = stix2.versioning.new_version(m, description="Steals your identity!")
assert "description" in m2
assert m2.description == "Steals your identity!"

m_custom = stix2.v20.Malware(
name="foo", labels=["label"], x_custom=123, allow_custom=True,
)

# Remember custom-allowed setting from original; should work
m2_custom = stix2.versioning.new_version(m_custom, x_other_custom="abc")
assert "x_other_custom" in m2_custom
assert m2_custom.x_other_custom == "abc"
1 change: 1 addition & 0 deletions stix2/test/v21/test_object_markings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
MALWARE_KWARGS = MALWARE_KWARGS_CONST.copy()
MALWARE_KWARGS.update({
'id': MALWARE_ID,
'type': 'malware',
'created': FAKE_TIME,
'modified': FAKE_TIME,
})
Expand Down
Loading