Convert ID properties to class style
parent
cf688c3851
commit
a264ca1e5e
|
@ -50,7 +50,7 @@ provided as keyword arguments:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
>>> indicator = Indicator(id="campaign--63ce9068-b5ab-47fa-a2cf-a602ea01f21a")
|
>>> indicator = Indicator(id="campaign--63ce9068-b5ab-47fa-a2cf-a602ea01f21a")
|
||||||
ValueError: Indicator id values must begin with 'indicator--'
|
ValueError: Invalid value for Indicator 'id': must start with 'indicator--'.
|
||||||
```
|
```
|
||||||
|
|
||||||
- If not provided, `created` and `modified` will be set to the (same) current
|
- If not provided, `created` and `modified` will be set to the (same) current
|
||||||
|
|
|
@ -74,6 +74,19 @@ class _STIXBase(collections.Mapping):
|
||||||
)
|
)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
def _check_property(self, prop_name, prop, kwargs):
|
||||||
|
if prop_name not in kwargs:
|
||||||
|
kwargs[prop_name] = prop.default()
|
||||||
|
# if default == NOW:
|
||||||
|
# kwargs[prop_name] = self.__now
|
||||||
|
try:
|
||||||
|
kwargs[prop_name] = prop.validate(kwargs[prop_name])
|
||||||
|
except ValueError as exc:
|
||||||
|
msg = "Invalid value for {0} '{1}': {2}"
|
||||||
|
raise ValueError(msg.format(self.__class__.__name__,
|
||||||
|
prop_name,
|
||||||
|
exc))
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
cls = self.__class__
|
cls = self.__class__
|
||||||
class_name = cls.__name__
|
class_name = cls.__name__
|
||||||
|
@ -98,8 +111,7 @@ class _STIXBase(collections.Mapping):
|
||||||
if isinstance(prop_metadata, dict):
|
if isinstance(prop_metadata, dict):
|
||||||
self._handle_old_style_property(prop_name, prop_metadata, kwargs)
|
self._handle_old_style_property(prop_name, prop_metadata, kwargs)
|
||||||
else: # This is a Property Subclasses
|
else: # This is a Property Subclasses
|
||||||
# self.check_property(prop_name, prop_metadata, kwargs)
|
self._check_property(prop_name, prop_metadata, kwargs)
|
||||||
pass
|
|
||||||
|
|
||||||
self._inner = kwargs
|
self._inner = kwargs
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
"""STIX 2 Bundle object"""
|
"""STIX 2 Bundle object"""
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .common import TYPE_PROPERTY, ID_PROPERTY
|
from .common import TYPE_PROPERTY
|
||||||
|
from .properties import IDProperty
|
||||||
|
|
||||||
|
|
||||||
class Bundle(_STIXBase):
|
class Bundle(_STIXBase):
|
||||||
|
@ -9,7 +10,7 @@ class Bundle(_STIXBase):
|
||||||
_type = 'bundle'
|
_type = 'bundle'
|
||||||
_properties = {
|
_properties = {
|
||||||
'type': TYPE_PROPERTY,
|
'type': TYPE_PROPERTY,
|
||||||
'id': ID_PROPERTY,
|
'id': IDProperty(_type),
|
||||||
'spec_version': {
|
'spec_version': {
|
||||||
'fixed': "2.0",
|
'fixed': "2.0",
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,13 +9,6 @@ TYPE_PROPERTY = {
|
||||||
'validate': (lambda x, val: val == x._type)
|
'validate': (lambda x, val: val == x._type)
|
||||||
}
|
}
|
||||||
|
|
||||||
ID_PROPERTY = {
|
|
||||||
'default': (lambda x: x._make_id()),
|
|
||||||
'validate': (lambda x, val: val.startswith(x._type + "--")),
|
|
||||||
'expected': (lambda x: x._type + "--"),
|
|
||||||
'error_msg': "{type} {field} values must begin with '{expected}'."
|
|
||||||
}
|
|
||||||
|
|
||||||
ref_regex = ("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}"
|
ref_regex = ("^[a-z][a-z-]+[a-z]--[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}"
|
||||||
"-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
"-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
||||||
|
|
||||||
|
@ -31,7 +24,7 @@ BOOL_PROPERTY = {
|
||||||
|
|
||||||
COMMON_PROPERTIES = {
|
COMMON_PROPERTIES = {
|
||||||
'type': TYPE_PROPERTY,
|
'type': TYPE_PROPERTY,
|
||||||
'id': ID_PROPERTY,
|
# 'id' should be defined on each individual type
|
||||||
'created': {
|
'created': {
|
||||||
'default': NOW,
|
'default': NOW,
|
||||||
},
|
},
|
||||||
|
|
|
@ -100,11 +100,14 @@ class List(Property):
|
||||||
class IDProperty(Property):
|
class IDProperty(Property):
|
||||||
|
|
||||||
def __init__(self, type):
|
def __init__(self, type):
|
||||||
self.type = type
|
self.required_prefix = type + "--"
|
||||||
|
super(IDProperty, self).__init__()
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
# TODO: validate GUID as well
|
# TODO: validate GUID as well
|
||||||
return value.startswith(self.type + "--")
|
if not value.startswith(self.required_prefix):
|
||||||
|
raise ValueError("must start with '{0}'.".format(self.required_prefix))
|
||||||
|
return value
|
||||||
|
|
||||||
def default(self):
|
def default(self):
|
||||||
return self.type + "--" + str(uuid.uuid4())
|
return self.required_prefix + str(uuid.uuid4())
|
||||||
|
|
13
stix2/sdo.py
13
stix2/sdo.py
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .common import COMMON_PROPERTIES
|
from .common import COMMON_PROPERTIES
|
||||||
|
from .properties import IDProperty
|
||||||
from .utils import NOW
|
from .utils import NOW
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ class AttackPattern(_STIXBase):
|
||||||
_type = 'attack-pattern'
|
_type = 'attack-pattern'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -35,6 +37,7 @@ class Campaign(_STIXBase):
|
||||||
_type = 'campaign'
|
_type = 'campaign'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -66,6 +69,7 @@ class CourseOfAction(_STIXBase):
|
||||||
_type = 'course-of-action'
|
_type = 'course-of-action'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -89,6 +93,7 @@ class Identity(_STIXBase):
|
||||||
_type = 'identity'
|
_type = 'identity'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -120,6 +125,7 @@ class Indicator(_STIXBase):
|
||||||
_type = 'indicator'
|
_type = 'indicator'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'labels': {
|
'labels': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -155,6 +161,7 @@ class IntrusionSet(_STIXBase):
|
||||||
_type = 'intrusion-set'
|
_type = 'intrusion-set'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -192,6 +199,7 @@ class Malware(_STIXBase):
|
||||||
_type = 'malware'
|
_type = 'malware'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'labels': {
|
'labels': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -220,6 +228,7 @@ class ObservedData(_STIXBase):
|
||||||
_type = 'observed-data'
|
_type = 'observed-data'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'first_observed': {},
|
'first_observed': {},
|
||||||
'last_observed': {},
|
'last_observed': {},
|
||||||
'number_observed': {},
|
'number_observed': {},
|
||||||
|
@ -246,6 +255,7 @@ class Report(_STIXBase):
|
||||||
_type = 'report'
|
_type = 'report'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'labels': {
|
'labels': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -276,6 +286,7 @@ class ThreatActor(_STIXBase):
|
||||||
_type = 'threat-actor'
|
_type = 'threat-actor'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'labels': {
|
'labels': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -318,6 +329,7 @@ class Tool(_STIXBase):
|
||||||
_type = 'tool'
|
_type = 'tool'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'labels': {
|
'labels': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
@ -348,6 +360,7 @@ class Vulnerability(_STIXBase):
|
||||||
_type = 'vulnerability'
|
_type = 'vulnerability'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'name': {
|
'name': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
from .common import COMMON_PROPERTIES
|
from .common import COMMON_PROPERTIES
|
||||||
|
from .properties import IDProperty
|
||||||
|
|
||||||
|
|
||||||
class Relationship(_STIXBase):
|
class Relationship(_STIXBase):
|
||||||
|
@ -9,6 +10,7 @@ class Relationship(_STIXBase):
|
||||||
_type = 'relationship'
|
_type = 'relationship'
|
||||||
_properties = COMMON_PROPERTIES.copy()
|
_properties = COMMON_PROPERTIES.copy()
|
||||||
_properties.update({
|
_properties.update({
|
||||||
|
'id': IDProperty(_type),
|
||||||
'relationship_type': {
|
'relationship_type': {
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
|
|
@ -49,6 +49,7 @@ def test_fixed_property():
|
||||||
def test_id_property():
|
def test_id_property():
|
||||||
idprop = IDProperty('my-type')
|
idprop = IDProperty('my-type')
|
||||||
|
|
||||||
assert idprop.validate('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') is True
|
assert idprop.validate('my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||||
assert idprop.validate('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c') is False
|
with pytest.raises(ValueError):
|
||||||
|
idprop.validate('not-my-type--90aaca8a-1110-5d32-956d-ac2f34a1bd8c')
|
||||||
assert idprop.validate(idprop.default())
|
assert idprop.validate(idprop.default())
|
||||||
|
|
|
@ -161,7 +161,7 @@ def test_indicator_id_must_start_with_indicator():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
indicator = stix2.Indicator(id='my-prefix--', **INDICATOR_KWARGS)
|
indicator = stix2.Indicator(id='my-prefix--', **INDICATOR_KWARGS)
|
||||||
|
|
||||||
assert str(excinfo.value) == "Indicator id values must begin with 'indicator--'."
|
assert str(excinfo.value) == "Invalid value for Indicator 'id': must start with 'indicator--'."
|
||||||
|
|
||||||
|
|
||||||
def test_indicator_required_fields():
|
def test_indicator_required_fields():
|
||||||
|
@ -262,7 +262,7 @@ def test_malware_id_must_start_with_malware():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
malware = stix2.Malware(id='my-prefix--', **MALWARE_KWARGS)
|
malware = stix2.Malware(id='my-prefix--', **MALWARE_KWARGS)
|
||||||
|
|
||||||
assert str(excinfo.value) == "Malware id values must begin with 'malware--'."
|
assert str(excinfo.value) == "Invalid value for Malware 'id': must start with 'malware--'."
|
||||||
|
|
||||||
|
|
||||||
def test_malware_required_fields():
|
def test_malware_required_fields():
|
||||||
|
@ -345,7 +345,7 @@ def test_relationship_id_must_start_with_relationship():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
relationship = stix2.Relationship(id='my-prefix--', **RELATIONSHIP_KWARGS)
|
relationship = stix2.Relationship(id='my-prefix--', **RELATIONSHIP_KWARGS)
|
||||||
|
|
||||||
assert str(excinfo.value) == "Relationship id values must begin with 'relationship--'."
|
assert str(excinfo.value) == "Invalid value for Relationship 'id': must start with 'relationship--'."
|
||||||
|
|
||||||
|
|
||||||
def test_relationship_required_field_relationship_type():
|
def test_relationship_required_field_relationship_type():
|
||||||
|
@ -464,7 +464,7 @@ def test_bundle_id_must_start_with_bundle():
|
||||||
with pytest.raises(ValueError) as excinfo:
|
with pytest.raises(ValueError) as excinfo:
|
||||||
bundle = stix2.Bundle(id='my-prefix--')
|
bundle = stix2.Bundle(id='my-prefix--')
|
||||||
|
|
||||||
assert str(excinfo.value) == "Bundle id values must begin with 'bundle--'."
|
assert str(excinfo.value) == "Invalid value for Bundle 'id': must start with 'bundle--'."
|
||||||
|
|
||||||
|
|
||||||
def test_bundle_with_wrong_spec_version():
|
def test_bundle_with_wrong_spec_version():
|
||||||
|
|
Loading…
Reference in New Issue