Added versioning api, with tests
parent
7a8e6341b2
commit
4efe5357b1
|
@ -3,9 +3,10 @@
|
||||||
import collections
|
import collections
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
import json
|
import json
|
||||||
|
import copy
|
||||||
|
|
||||||
from .exceptions import ExtraFieldsError, ImmutableError, InvalidValueError, \
|
from .exceptions import ExtraFieldsError, ImmutableError, InvalidValueError, \
|
||||||
MissingFieldsError
|
MissingFieldsError, VersioningError
|
||||||
from .utils import format_datetime, get_timestamp, NOW
|
from .utils import format_datetime, get_timestamp, NOW
|
||||||
|
|
||||||
__all__ = ['STIXJSONEncoder', '_STIXBase']
|
__all__ = ['STIXJSONEncoder', '_STIXBase']
|
||||||
|
@ -99,3 +100,33 @@ class _STIXBase(collections.Mapping):
|
||||||
props = [(k, self[k]) for k in sorted(self._properties) if self.get(k)]
|
props = [(k, self[k]) for k in sorted(self._properties) if self.get(k)]
|
||||||
return "{0}({1})".format(self.__class__.__name__,
|
return "{0}({1})".format(self.__class__.__name__,
|
||||||
", ".join(["{0!s}={1!r}".format(k, v) for k, v in props]))
|
", ".join(["{0!s}={1!r}".format(k, v) for k, v in props]))
|
||||||
|
|
||||||
|
# Versioning API
|
||||||
|
|
||||||
|
def new_version(self, **kwargs):
|
||||||
|
unchangable_properties = []
|
||||||
|
if self.revoked:
|
||||||
|
raise VersioningError("Cannot create a new version of a revoked object")
|
||||||
|
new_obj_inner = copy.deepcopy(self._inner)
|
||||||
|
properties_to_change = kwargs.keys()
|
||||||
|
if "type" in properties_to_change:
|
||||||
|
unchangable_properties.append("type")
|
||||||
|
if "id" in properties_to_change:
|
||||||
|
unchangable_properties.append("id")
|
||||||
|
if "created" in properties_to_change:
|
||||||
|
unchangable_properties.append("created")
|
||||||
|
if "created_by_ref" in properties_to_change:
|
||||||
|
unchangable_properties.append("created_by_ref")
|
||||||
|
if unchangable_properties:
|
||||||
|
raise VersioningError("These properties cannot be changed when making a new version: " + ", ".join(unchangable_properties))
|
||||||
|
if 'modified' not in kwargs:
|
||||||
|
kwargs['modified'] = get_timestamp()
|
||||||
|
new_obj_inner.update(kwargs)
|
||||||
|
|
||||||
|
cls = type(self)
|
||||||
|
return cls(**new_obj_inner)
|
||||||
|
|
||||||
|
def revoke(self):
|
||||||
|
if self.revoked:
|
||||||
|
raise VersioningError("Cannot revoke an already revoked object")
|
||||||
|
return self.new_version(revoked=True)
|
||||||
|
|
|
@ -49,3 +49,10 @@ class ImmutableError(STIXError, ValueError):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ImmutableError, self).__init__("Cannot modify properties after creation.")
|
super(ImmutableError, self).__init__("Cannot modify properties after creation.")
|
||||||
|
|
||||||
|
|
||||||
|
class VersioningError(STIXError, ValueError):
|
||||||
|
"""Execption while using the Versioning API"""
|
||||||
|
|
||||||
|
def __init__(self, msg):
|
||||||
|
super(VersioningError, self).__init__(msg)
|
|
@ -0,0 +1,107 @@
|
||||||
|
import stix2
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
EXPECTED = """{
|
||||||
|
"created": "2016-04-06T20:03:00.000Z",
|
||||||
|
"created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
"description": "Campaign by Green Group against a series of targets in the financial services sector.",
|
||||||
|
"id": "campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
"modified": "2016-04-06T20:03:00.000Z",
|
||||||
|
"name": "Green Group Attacks Against Finance",
|
||||||
|
"type": "campaign"
|
||||||
|
}"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_making_new_version():
|
||||||
|
campaign_v1 = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||||
|
)
|
||||||
|
|
||||||
|
campaign_v2 = campaign_v1.new_version(name="fred")
|
||||||
|
|
||||||
|
assert campaign_v1.id == campaign_v2.id
|
||||||
|
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||||
|
assert campaign_v1.created == campaign_v2.created
|
||||||
|
assert campaign_v1.name != campaign_v2.name
|
||||||
|
assert campaign_v2.name == "fred"
|
||||||
|
assert campaign_v1.description == campaign_v2.description
|
||||||
|
assert campaign_v1.modified < campaign_v2.modified
|
||||||
|
|
||||||
|
|
||||||
|
def test_revoke():
|
||||||
|
campaign_v1 = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||||
|
)
|
||||||
|
|
||||||
|
campaign_v2 = campaign_v1.revoke()
|
||||||
|
|
||||||
|
assert campaign_v1.id == campaign_v2.id
|
||||||
|
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||||
|
assert campaign_v1.created == campaign_v2.created
|
||||||
|
assert campaign_v1.name == campaign_v2.name
|
||||||
|
assert campaign_v1.description == campaign_v2.description
|
||||||
|
assert campaign_v1.modified < campaign_v2.modified
|
||||||
|
|
||||||
|
assert campaign_v2.revoked
|
||||||
|
|
||||||
|
|
||||||
|
def test_versioning_error_invalid_property():
|
||||||
|
campaign_v1 = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(stix2.exceptions.VersioningError) as excinfo:
|
||||||
|
campaign_v2 = campaign_v1.new_version(type="threat-actor")
|
||||||
|
|
||||||
|
str(excinfo.value) == "These properties cannot be changed when making a new version: type"
|
||||||
|
|
||||||
|
|
||||||
|
def test_versioning_error_new_version_of_revoked():
|
||||||
|
campaign_v1 = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||||
|
)
|
||||||
|
|
||||||
|
campaign_v2 = campaign_v1.revoke()
|
||||||
|
|
||||||
|
with pytest.raises(stix2.exceptions.VersioningError) as excinfo:
|
||||||
|
campaign_v3 = campaign_v2.new_version(name="barney")
|
||||||
|
|
||||||
|
str(excinfo.value) == "Cannot create a new version of a revoked object"
|
||||||
|
|
||||||
|
|
||||||
|
def test_versioning_error_revoke_of_revoked():
|
||||||
|
campaign_v1 = stix2.Campaign(
|
||||||
|
id="campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f",
|
||||||
|
created_by_ref="identity--f431f809-377b-45e0-aa1c-6a4751cae5ff",
|
||||||
|
created="2016-04-06T20:03:00.000Z",
|
||||||
|
modified="2016-04-06T20:03:00.000Z",
|
||||||
|
name="Green Group Attacks Against Finance",
|
||||||
|
description="Campaign by Green Group against a series of targets in the financial services sector."
|
||||||
|
)
|
||||||
|
|
||||||
|
campaign_v2 = campaign_v1.revoke()
|
||||||
|
|
||||||
|
with pytest.raises(stix2.exceptions.VersioningError) as excinfo:
|
||||||
|
campaign_v3 = campaign_v2.revoke()
|
||||||
|
|
||||||
|
str(excinfo.value) == "Cannot revoke an already revoked object"
|
Loading…
Reference in New Issue