Skip to content

Commit

Permalink
Merge pull request #401 from chisholm/fix_versionable_detection
Browse files Browse the repository at this point in the history
Fix versionability detection
  • Loading branch information
clenk authored Jun 10, 2020
2 parents fdb8875 + c13cb18 commit 9d05c9d
Show file tree
Hide file tree
Showing 11 changed files with 540 additions and 219 deletions.
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

0 comments on commit 9d05c9d

Please sign in to comment.