diff --git a/stix2/core.py b/stix2/core.py
index 6594f51..4be3b1c 100644
--- a/stix2/core.py
+++ b/stix2/core.py
@@ -7,10 +7,10 @@ import re
import stix2
-from .base import _STIXBase
+from .base import _Observable, _STIXBase
from .exceptions import ParseError
from .markings import _MarkingsMixin
-from .utils import _get_dict
+from .utils import SCO21_EXT_REGEX, TYPE_REGEX, _get_dict
STIX2_OBJ_MAPS = {}
@@ -275,22 +275,54 @@ def _register_observable(new_observable, version=None):
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
-def _register_observable_extension(observable, new_extension, version=None):
+def _register_observable_extension(
+ observable, new_extension, version=stix2.DEFAULT_VERSION,
+):
"""Register a custom extension to a STIX Cyber Observable type.
Args:
- observable: An observable object
+ observable: An observable class or instance
new_extension (class): A class to register in the Observables
Extensions map.
- version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
- None, use latest version.
+ version (str): Which STIX2 version to use. (e.g. "2.0", "2.1").
+ Defaults to the latest supported version.
"""
- if version:
- v = 'v' + version.replace('.', '')
- else:
- # Use default version (latest) if no version was provided.
- v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
+ obs_class = observable if isinstance(observable, type) else \
+ type(observable)
+ ext_type = new_extension._type
+
+ if not issubclass(obs_class, _Observable):
+ raise ValueError("'observable' must be a valid Observable class!")
+
+ if version == "2.0":
+ if not re.match(TYPE_REGEX, ext_type):
+ raise ValueError(
+ "Invalid extension type name '%s': must only contain the "
+ "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." %
+ ext_type,
+ )
+ else: # 2.1+
+ if not re.match(SCO21_EXT_REGEX, ext_type):
+ raise ValueError(
+ "Invalid extension type name '%s': must only contain the "
+ "characters a-z (lowercase ASCII), 0-9, hyphen (-), and end "
+ "with '-ext'." % ext_type,
+ )
+
+ if len(ext_type) < 3 or len(ext_type) > 250:
+ raise ValueError(
+ "Invalid extension type name '%s': must be between 3 and 250"
+ " characters." % ext_type,
+ )
+
+ if not new_extension._properties:
+ raise ValueError(
+ "Invalid extension: must define at least one property: " +
+ ext_type,
+ )
+
+ v = 'v' + version.replace('.', '')
try:
observable_type = observable._type
@@ -304,7 +336,7 @@ def _register_observable_extension(observable, new_extension, version=None):
EXT_MAP = STIX2_OBJ_MAPS[v]['observable-extensions']
try:
- EXT_MAP[observable_type][new_extension._type] = new_extension
+ EXT_MAP[observable_type][ext_type] = new_extension
except KeyError:
if observable_type not in OBJ_MAP_OBSERVABLE:
raise ValueError(
@@ -313,7 +345,7 @@ def _register_observable_extension(observable, new_extension, version=None):
% observable_type,
)
else:
- EXT_MAP[observable_type] = {new_extension._type: new_extension}
+ EXT_MAP[observable_type] = {ext_type: new_extension}
def _collect_stix2_mappings():
diff --git a/stix2/custom.py b/stix2/custom.py
index 802fd07..f3c89cf 100644
--- a/stix2/custom.py
+++ b/stix2/custom.py
@@ -1,6 +1,8 @@
from collections import OrderedDict
import re
+import six
+
from .base import _cls_init, _Extension, _Observable, _STIXBase
from .core import (
STIXDomainObject, _register_marking, _register_object,
@@ -113,24 +115,23 @@ def _custom_observable_builder(cls, type, properties, version, id_contrib_props=
def _custom_extension_builder(cls, observable, type, properties, version):
- if not observable or not issubclass(observable, _Observable):
- raise ValueError("'observable' must be a valid Observable class!")
+
+ try:
+ prop_dict = OrderedDict(properties)
+ except TypeError as e:
+ six.raise_from(
+ ValueError(
+ "Extension properties must be dict-like, e.g. a list "
+ "containing tuples. For example, "
+ "[('property1', IntegerProperty())]",
+ ),
+ e,
+ )
class _CustomExtension(cls, _Extension):
- if not re.match(TYPE_REGEX, type):
- raise ValueError(
- "Invalid extension type name '%s': must only contain the "
- "characters a-z (lowercase ASCII), 0-9, and hyphen (-)." % type,
- )
- elif len(type) < 3 or len(type) > 250:
- raise ValueError("Invalid extension type name '%s': must be between 3 and 250 characters." % type)
-
- if not properties or not isinstance(properties, list):
- raise ValueError("Must supply a list, containing tuples. For example, [('property1', IntegerProperty())]")
-
_type = type
- _properties = OrderedDict(properties)
+ _properties = prop_dict
def __init__(self, **kwargs):
_Extension.__init__(self, **kwargs)
diff --git a/stix2/test/v20/test_custom.py b/stix2/test/v20/test_custom.py
index ce1aac3..b986777 100644
--- a/stix2/test/v20/test_custom.py
+++ b/stix2/test/v20/test_custom.py
@@ -821,27 +821,24 @@ def test_custom_extension_invalid_type_name():
def test_custom_extension_no_properties():
- with pytest.raises(ValueError) as excinfo:
+ with pytest.raises(ValueError):
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', None)
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_empty_properties():
- with pytest.raises(ValueError) as excinfo:
+ with pytest.raises(ValueError):
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', [])
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_dict_properties():
- with pytest.raises(ValueError) as excinfo:
+ with pytest.raises(ValueError):
@stix2.v20.CustomExtension(stix2.v20.DomainName, 'x-new-ext2', {})
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_no_init_1():
diff --git a/stix2/test/v21/test_course_of_action.py b/stix2/test/v21/test_course_of_action.py
index 44079fb..5eea908 100644
--- a/stix2/test/v21/test_course_of_action.py
+++ b/stix2/test/v21/test_course_of_action.py
@@ -14,14 +14,7 @@ COA_WITH_BIN_JSON = """{
"created": "2016-04-06T20:03:48.000Z",
"modified": "2016-04-06T20:03:48.000Z",
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
- "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
- "action_type": "textual:text/plain",
- "os_execution_envs": [
- "a",
- "b",
- "c"
- ],
- "action_bin": "aGVsbG8gd29ybGQ="
+ "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
}"""
@@ -33,17 +26,7 @@ COA_WITH_REF_JSON = """{
"created": "2016-04-06T20:03:48.000Z",
"modified": "2016-04-06T20:03:48.000Z",
"name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
- "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ...",
- "action_type": "textual:text/plain",
- "os_execution_envs": [
- "a",
- "b",
- "c"
- ],
- "action_reference": {
- "source_name": "a source",
- "description": "description of a source"
- }
+ "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..."
}"""
@@ -84,15 +67,4 @@ def test_parse_course_of_action(sdo_json, sdo_dict):
assert getattr(coa, attr_name) == cmp_value
-def test_course_of_action_constraint():
- with pytest.raises(stix2.exceptions.MutuallyExclusivePropertiesError):
- stix2.v21.CourseOfAction(
- name="Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter",
- action_bin="aGVsbG8gd29ybGQ=",
- action_reference=stix2.v21.ExternalReference(
- source_name="a source",
- description="description of a source",
- ),
- )
-
# TODO: Add other examples
diff --git a/stix2/test/v21/test_custom.py b/stix2/test/v21/test_custom.py
index b46288d..1e6f629 100644
--- a/stix2/test/v21/test_custom.py
+++ b/stix2/test/v21/test_custom.py
@@ -800,7 +800,7 @@ def test_custom_extension_wrong_observable_type():
)
def test_custom_extension_with_list_and_dict_properties_observable_type(data):
@stix2.v21.CustomExtension(
- stix2.v21.UserAccount, 'some-extension', [
+ stix2.v21.UserAccount, 'x-some-extension-ext', [
('keys', stix2.properties.ListProperty(stix2.properties.DictionaryProperty, required=True)),
],
)
@@ -876,32 +876,29 @@ def test_custom_extension_invalid_type_name():
def test_custom_extension_no_properties():
- with pytest.raises(ValueError) as excinfo:
- @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', None)
+ with pytest.raises(ValueError):
+ @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', None)
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_empty_properties():
- with pytest.raises(ValueError) as excinfo:
- @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', [])
+ with pytest.raises(ValueError):
+ @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', [])
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_dict_properties():
- with pytest.raises(ValueError) as excinfo:
- @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new-ext2', {})
+ with pytest.raises(ValueError):
+ @stix2.v21.CustomExtension(stix2.v21.DomainName, 'x-new2-ext', {})
class BarExtension():
pass
- assert "Must supply a list, containing tuples." in str(excinfo.value)
def test_custom_extension_no_init_1():
@stix2.v21.CustomExtension(
- stix2.v21.DomainName, 'x-new-extension', [
+ stix2.v21.DomainName, 'x-new-extension-ext', [
('property1', stix2.properties.StringProperty(required=True)),
],
)
@@ -914,7 +911,7 @@ def test_custom_extension_no_init_1():
def test_custom_extension_no_init_2():
@stix2.v21.CustomExtension(
- stix2.v21.DomainName, 'x-new-ext2', [
+ stix2.v21.DomainName, 'x-new2-ext', [
('property1', stix2.properties.StringProperty(required=True)),
],
)
@@ -949,14 +946,14 @@ def test_custom_and_spec_extension_mix():
file_obs = stix2.v21.File(
name="my_file.dat",
extensions={
- "x-custom1": {
+ "x-custom1-ext": {
"a": 1,
"b": 2,
},
"ntfs-ext": {
"sid": "S-1-whatever",
},
- "x-custom2": {
+ "x-custom2-ext": {
"z": 99.9,
"y": False,
},
@@ -969,8 +966,8 @@ def test_custom_and_spec_extension_mix():
allow_custom=True,
)
- assert file_obs.extensions["x-custom1"] == {"a": 1, "b": 2}
- assert file_obs.extensions["x-custom2"] == {"y": False, "z": 99.9}
+ assert file_obs.extensions["x-custom1-ext"] == {"a": 1, "b": 2}
+ assert file_obs.extensions["x-custom2-ext"] == {"y": False, "z": 99.9}
assert file_obs.extensions["ntfs-ext"].sid == "S-1-whatever"
assert file_obs.extensions["raster-image-ext"].image_height == 1024
diff --git a/stix2/test/v21/test_external_reference.py b/stix2/test/v21/test_external_reference.py
index d192a11..f347191 100644
--- a/stix2/test/v21/test_external_reference.py
+++ b/stix2/test/v21/test_external_reference.py
@@ -120,3 +120,14 @@ def test_external_reference_source_required():
assert excinfo.value.cls == stix2.v21.ExternalReference
assert excinfo.value.properties == ["source_name"]
+
+
+def test_external_reference_bad_hash():
+ with pytest.raises(stix2.exceptions.InvalidValueError):
+ stix2.v21.ExternalReference(
+ source_name="ACME Threat Intel",
+ description="Threat report",
+ hashes={
+ "SHA-123": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ },
+ )
diff --git a/stix2/test/v21/test_indicator.py b/stix2/test/v21/test_indicator.py
index d7d7e47..76fe86b 100644
--- a/stix2/test/v21/test_indicator.py
+++ b/stix2/test/v21/test_indicator.py
@@ -14,9 +14,6 @@ EXPECTED_INDICATOR = """{
"id": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7",
"created": "2017-01-01T00:00:01.000Z",
"modified": "2017-01-01T00:00:01.000Z",
- "indicator_types": [
- "malicious-activity"
- ],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix",
"pattern_version": "2.1",
@@ -29,7 +26,6 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7',
created='2017-01-01T00:00:01.000Z',
modified='2017-01-01T00:00:01.000Z',
- indicator_types=['malicious-activity'],
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
pattern_type='stix',
pattern_version='2.1',
@@ -49,7 +45,6 @@ def test_indicator_with_all_required_properties():
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
pattern_type="stix",
valid_from=epoch,
- indicator_types=['malicious-activity'],
)
assert ind.revoked is False
@@ -103,8 +98,8 @@ def test_indicator_required_properties():
stix2.v21.Indicator()
assert excinfo.value.cls == stix2.v21.Indicator
- assert excinfo.value.properties == ["indicator_types", "pattern", "pattern_type", "valid_from"]
- assert str(excinfo.value) == "No values for required properties for Indicator: (indicator_types, pattern, pattern_type, valid_from)."
+ assert excinfo.value.properties == ["pattern", "pattern_type", "valid_from"]
+ assert str(excinfo.value) == "No values for required properties for Indicator: (pattern, pattern_type, valid_from)."
def test_indicator_required_property_pattern():
@@ -163,9 +158,6 @@ def test_created_modified_time_are_identical_by_default():
"id": INDICATOR_ID,
"created": "2017-01-01T00:00:01Z",
"modified": "2017-01-01T00:00:01Z",
- "indicator_types": [
- "malicious-activity",
- ],
"pattern": "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
"pattern_type": "stix",
"valid_from": "1970-01-01T00:00:01Z",
@@ -181,7 +173,6 @@ def test_parse_indicator(data):
assert idctr.created == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
assert idctr.modified == dt.datetime(2017, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
assert idctr.valid_from == dt.datetime(1970, 1, 1, 0, 0, 1, tzinfo=pytz.utc)
- assert idctr.indicator_types[0] == "malicious-activity"
assert idctr.pattern == "[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']"
diff --git a/stix2/test/v21/test_infrastructure.py b/stix2/test/v21/test_infrastructure.py
index 30632bb..3e9feb7 100644
--- a/stix2/test/v21/test_infrastructure.py
+++ b/stix2/test/v21/test_infrastructure.py
@@ -13,10 +13,7 @@ EXPECTED_INFRASTRUCTURE = """{
"id": "infrastructure--3000ae1b-784c-f03d-8abc-0a625b2ff018",
"created": "2017-01-01T12:34:56.000Z",
"modified": "2017-01-01T12:34:56.000Z",
- "name": "Poison Ivy C2",
- "infrastructure_types": [
- "command-and-control"
- ]
+ "name": "Poison Ivy C2"
}"""
@@ -29,7 +26,6 @@ def test_infrastructure_with_all_required_properties():
created=now,
modified=now,
name="Poison Ivy C2",
- infrastructure_types=["command-and-control"],
)
assert str(infra) == EXPECTED_INFRASTRUCTURE
@@ -76,7 +72,7 @@ def test_infrastructure_required_properties():
stix2.v21.Infrastructure()
assert excinfo.value.cls == stix2.v21.Infrastructure
- assert excinfo.value.properties == ["infrastructure_types", "name"]
+ assert excinfo.value.properties == ["name"]
def test_infrastructure_required_property_name():
@@ -105,7 +101,6 @@ def test_invalid_kwarg_to_infrastructure():
"id": INFRASTRUCTURE_ID,
"created": "2017-01-01T12:34:56.000Z",
"modified": "2017-01-01T12:34:56.000Z",
- "infrastructure_types": ["command-and-control"],
"name": "Poison Ivy C2",
},
],
@@ -118,7 +113,6 @@ def test_parse_infrastructure(data):
assert infra.id == INFRASTRUCTURE_ID
assert infra.created == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
assert infra.modified == dt.datetime(2017, 1, 1, 12, 34, 56, tzinfo=pytz.utc)
- assert infra.infrastructure_types == ['command-and-control']
assert infra.name == 'Poison Ivy C2'
diff --git a/stix2/test/v21/test_malware.py b/stix2/test/v21/test_malware.py
index 53838c9..f111826 100644
--- a/stix2/test/v21/test_malware.py
+++ b/stix2/test/v21/test_malware.py
@@ -1,5 +1,5 @@
import datetime as dt
-import re
+import json
import pytest
import pytz
@@ -16,9 +16,6 @@ EXPECTED_MALWARE = """{
"created": "2016-05-12T08:17:27.000Z",
"modified": "2016-05-12T08:17:27.000Z",
"name": "Cryptolocker",
- "malware_types": [
- "ransomware"
- ],
"is_family": false
}"""
@@ -31,7 +28,6 @@ def test_malware_with_all_required_properties():
id=MALWARE_ID,
created=now,
modified=now,
- malware_types=["ransomware"],
name="Cryptolocker",
is_family=False,
)
@@ -80,7 +76,7 @@ def test_malware_required_properties():
stix2.v21.Malware()
assert excinfo.value.cls == stix2.v21.Malware
- assert excinfo.value.properties == ["is_family", "malware_types"]
+ assert excinfo.value.properties == ["is_family"]
def test_malware_required_property_name():
@@ -116,7 +112,6 @@ def test_invalid_kwarg_to_malware():
"id": MALWARE_ID,
"created": "2016-05-12T08:17:27.000Z",
"modified": "2016-05-12T08:17:27.000Z",
- "malware_types": ["ransomware"],
"name": "Cryptolocker",
"is_family": False,
},
@@ -130,13 +125,14 @@ def test_parse_malware(data):
assert mal.id == MALWARE_ID
assert mal.created == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
assert mal.modified == dt.datetime(2016, 5, 12, 8, 17, 27, tzinfo=pytz.utc)
- assert mal.malware_types == ['ransomware']
assert mal.name == 'Cryptolocker'
assert not mal.is_family
-def test_parse_malware_invalid_labels():
- data = re.compile('\\[.+\\]', re.DOTALL).sub('1', EXPECTED_MALWARE)
+def test_parse_malware_invalid_types():
+ data = json.loads(EXPECTED_MALWARE)
+ data["malware_types"] = 1 # Oops, not a list
+ data = json.dumps(data)
with pytest.raises(InvalidValueError) as excinfo:
stix2.parse(data)
assert "Invalid value for Malware 'malware_types'" in str(excinfo.value)
@@ -197,3 +193,22 @@ def test_malware_non_family_no_name():
"is_family": False,
"malware_types": ["something"],
})
+
+
+def test_malware_with_os_refs():
+ software = stix2.parse({
+ "type": "software",
+ "name": "SuperOS",
+ "spec_version": "2.1",
+ })
+
+ malware = stix2.parse({
+ "type": "malware",
+ "id": MALWARE_ID,
+ "spec_version": "2.1",
+ "is_family": False,
+ "malware_types": ["something"],
+ "operating_system_refs": [software],
+ })
+
+ assert malware["operating_system_refs"][0] == software["id"]
diff --git a/stix2/test/v21/test_malware_analysis.py b/stix2/test/v21/test_malware_analysis.py
index bfb4ff4..22f4171 100644
--- a/stix2/test/v21/test_malware_analysis.py
+++ b/stix2/test/v21/test_malware_analysis.py
@@ -34,11 +34,13 @@ MALWARE_ANALYSIS_JSON = """{
"submitted": "2018-11-23T06:45:55.747Z",
"analysis_started": "2018-11-29T07:30:03.895Z",
"analysis_ended": "2018-11-29T08:30:03.895Z",
- "av_result": "malicious",
+ "result_name": "MegaRansom",
+ "result": "malicious",
"analysis_sco_refs": [
"file--fc27e371-6c88-4c5c-868a-4dda0e60b167",
"url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e"
- ]
+ ],
+ "sample_ref": "email-addr--499a32d7-74c1-4276-ace9-725ac933e243"
}"""
diff --git a/stix2/test/v21/test_observed_data.py b/stix2/test/v21/test_observed_data.py
index 71bad46..abcbb7b 100644
--- a/stix2/test/v21/test_observed_data.py
+++ b/stix2/test/v21/test_observed_data.py
@@ -1300,6 +1300,7 @@ def test_software_example():
s = stix2.v21.Software(
name="Word",
cpe="cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*",
+ swid="com.acme.rms-ce-v4-1-5-0",
version="2002",
vendor="Microsoft",
)
diff --git a/stix2/utils.py b/stix2/utils.py
index b23b0e4..7b3b6cf 100644
--- a/stix2/utils.py
+++ b/stix2/utils.py
@@ -26,6 +26,7 @@ NOW = object()
STIX_UNMOD_PROPERTIES = ['created', 'created_by_ref', 'id', 'type']
TYPE_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-?$'
+SCO21_EXT_REGEX = r'^\-?[a-z0-9]+(-[a-z0-9]+)*\-ext$'
class STIXdatetime(dt.datetime):
diff --git a/stix2/v21/bundle.py b/stix2/v21/bundle.py
index 784608a..b4b8607 100644
--- a/stix2/v21/bundle.py
+++ b/stix2/v21/bundle.py
@@ -9,9 +9,8 @@ from ..properties import (
class Bundle(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'bundle'
diff --git a/stix2/v21/common.py b/stix2/v21/common.py
index 2cda328..f5a50cb 100644
--- a/stix2/v21/common.py
+++ b/stix2/v21/common.py
@@ -4,6 +4,7 @@ from collections import OrderedDict
from ..base import _STIXBase
from ..custom import _custom_marking_builder
+from ..exceptions import InvalidValueError
from ..markings import _MarkingsMixin
from ..markings.utils import check_tlp_marking
from ..properties import (
@@ -15,9 +16,8 @@ from ..utils import NOW, _get_dict
class ExternalReference(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -28,15 +28,30 @@ class ExternalReference(_STIXBase):
('external_id', StringProperty()),
])
+ # This is hash-algorithm-ov
+ _LEGAL_HASHES = {
+ "MD5", "SHA-1", "SHA-256", "SHA-512", "SHA3-256", "SHA3-512", "SSDEEP",
+ "TLSH",
+ }
+
def _check_object_constraints(self):
super(ExternalReference, self)._check_object_constraints()
self._check_at_least_one_property(['description', 'external_id', 'url'])
+ if "hashes" in self:
+ if any(
+ hash_ not in self._LEGAL_HASHES
+ for hash_ in self["hashes"]
+ ):
+ raise InvalidValueError(
+ ExternalReference, "hashes",
+ "Hash algorithm names must be members of hash-algorithm-ov",
+ )
+
class KillChainPhase(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -46,9 +61,8 @@ class KillChainPhase(_STIXBase):
class GranularMarking(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -63,9 +77,8 @@ class GranularMarking(_STIXBase):
class LanguageContent(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'language-content'
@@ -91,9 +104,8 @@ class LanguageContent(_STIXBase):
class TLPMarking(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'tlp'
@@ -103,9 +115,8 @@ class TLPMarking(_STIXBase):
class StatementMarking(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'statement'
@@ -134,9 +145,8 @@ class MarkingProperty(Property):
class MarkingDefinition(_STIXBase, _MarkingsMixin):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'marking-definition'
diff --git a/stix2/v21/observables.py b/stix2/v21/observables.py
index e8c1925..89de0fc 100644
--- a/stix2/v21/observables.py
+++ b/stix2/v21/observables.py
@@ -22,9 +22,8 @@ from .common import GranularMarking
class Artifact(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'artifact'
@@ -52,9 +51,8 @@ class Artifact(_Observable):
class AutonomousSystem(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'autonomous-system'
@@ -74,9 +72,8 @@ class AutonomousSystem(_Observable):
class Directory(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'directory'
@@ -100,9 +97,8 @@ class Directory(_Observable):
class DomainName(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'domain-name'
@@ -121,9 +117,8 @@ class DomainName(_Observable):
class EmailAddress(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'email-addr'
@@ -143,9 +138,8 @@ class EmailAddress(_Observable):
class EmailMIMEComponent(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -161,9 +155,8 @@ class EmailMIMEComponent(_STIXBase):
class EmailMessage(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'email-message'
@@ -202,9 +195,8 @@ class EmailMessage(_Observable):
class ArchiveExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'archive-ext'
@@ -215,9 +207,8 @@ class ArchiveExt(_Extension):
class AlternateDataStream(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -228,9 +219,8 @@ class AlternateDataStream(_STIXBase):
class NTFSExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'ntfs-ext'
@@ -241,9 +231,8 @@ class NTFSExt(_Extension):
class PDFExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'pdf-ext'
@@ -257,9 +246,8 @@ class PDFExt(_Extension):
class RasterImageExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'raster-image-ext'
@@ -272,9 +260,8 @@ class RasterImageExt(_Extension):
class WindowsPEOptionalHeaderType(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -317,9 +304,8 @@ class WindowsPEOptionalHeaderType(_STIXBase):
class WindowsPESection(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_properties = OrderedDict([
@@ -331,9 +317,8 @@ class WindowsPESection(_STIXBase):
class WindowsPEBinaryExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'windows-pebinary-ext'
@@ -354,9 +339,8 @@ class WindowsPEBinaryExt(_Extension):
class File(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'file'
@@ -382,7 +366,7 @@ class File(_Observable):
('granular_markings', ListProperty(GranularMarking)),
('defanged', BooleanProperty(default=lambda: False)),
])
- _id_contributing_properties = ["hashes", "name", "extensions"]
+ _id_contributing_properties = ["hashes", "name", "parent_directory_ref", "extensions"]
def _check_object_constraints(self):
super(File, self)._check_object_constraints()
@@ -390,9 +374,8 @@ class File(_Observable):
class IPv4Address(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'ipv4-addr'
@@ -412,9 +395,8 @@ class IPv4Address(_Observable):
class IPv6Address(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'ipv6-addr'
@@ -434,9 +416,8 @@ class IPv6Address(_Observable):
class MACAddress(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'mac-addr'
@@ -454,9 +435,8 @@ class MACAddress(_Observable):
class Mutex(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'mutex'
@@ -474,9 +454,8 @@ class Mutex(_Observable):
class HTTPRequestExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'http-request-ext'
@@ -493,7 +472,7 @@ class HTTPRequestExt(_Extension):
class ICMPExt(_Extension):
# TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'icmp-ext'
@@ -504,9 +483,8 @@ class ICMPExt(_Extension):
class SocketExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'socket-ext'
@@ -566,9 +544,8 @@ class SocketExt(_Extension):
class TCPExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'tcp-ext'
@@ -579,9 +556,8 @@ class TCPExt(_Extension):
class NetworkTraffic(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'network-traffic'
@@ -635,9 +611,8 @@ class NetworkTraffic(_Observable):
class WindowsProcessExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'windows-process-ext'
@@ -660,9 +635,8 @@ class WindowsProcessExt(_Extension):
class WindowsServiceExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'windows-service-ext'
@@ -704,9 +678,8 @@ class WindowsServiceExt(_Extension):
class Process(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'process'
@@ -749,9 +722,8 @@ class Process(_Observable):
class Software(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'software'
@@ -760,6 +732,7 @@ class Software(_Observable):
('id', IDProperty(_type, spec_version='2.1')),
('name', StringProperty(required=True)),
('cpe', StringProperty()),
+ ('swid', StringProperty()),
('languages', ListProperty(StringProperty)),
('vendor', StringProperty()),
('version', StringProperty()),
@@ -769,13 +742,12 @@ class Software(_Observable):
('granular_markings', ListProperty(GranularMarking)),
('defanged', BooleanProperty(default=lambda: False)),
])
- _id_contributing_properties = ["name", "cpe", "vendor", "version"]
+ _id_contributing_properties = ["name", "cpe", "swid", "vendor", "version"]
class URL(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'url'
@@ -793,9 +765,8 @@ class URL(_Observable):
class UNIXAccountExt(_Extension):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'unix-account-ext'
@@ -808,9 +779,8 @@ class UNIXAccountExt(_Extension):
class UserAccount(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'user-account'
@@ -841,9 +811,8 @@ class UserAccount(_Observable):
class WindowsRegistryValueType(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'windows-registry-value-type'
@@ -871,9 +840,8 @@ class WindowsRegistryValueType(_STIXBase):
class WindowsRegistryKey(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'windows-registry-key'
@@ -896,9 +864,8 @@ class WindowsRegistryKey(_Observable):
class X509V3ExtenstionsType(_STIXBase):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'x509-v3-extensions-type'
@@ -923,9 +890,8 @@ class X509V3ExtenstionsType(_STIXBase):
class X509Certificate(_Observable):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'x509-certificate'
diff --git a/stix2/v21/sdo.py b/stix2/v21/sdo.py
index c8aacd1..bc09d0e 100644
--- a/stix2/v21/sdo.py
+++ b/stix2/v21/sdo.py
@@ -13,19 +13,17 @@ from ..exceptions import (
InvalidValueError, PropertyPresenceError, STIXDeprecationWarning,
)
from ..properties import (
- BinaryProperty, BooleanProperty, EmbeddedObjectProperty, EnumProperty,
- FloatProperty, IDProperty, IntegerProperty, ListProperty,
- ObservableProperty, PatternProperty, ReferenceProperty, StringProperty,
- TimestampProperty, TypeProperty,
+ BooleanProperty, EnumProperty, FloatProperty, IDProperty, IntegerProperty,
+ ListProperty, ObservableProperty, PatternProperty, ReferenceProperty,
+ StringProperty, TimestampProperty, TypeProperty,
)
from ..utils import NOW
from .common import ExternalReference, GranularMarking, KillChainPhase
class AttackPattern(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'attack-pattern'
@@ -51,9 +49,8 @@ class AttackPattern(STIXDomainObject):
class Campaign(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'campaign'
@@ -91,9 +88,8 @@ class Campaign(STIXDomainObject):
class CourseOfAction(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'course-of-action'
@@ -106,10 +102,6 @@ class CourseOfAction(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)),
('description', StringProperty()),
- ('action_type', StringProperty()),
- ('os_execution_envs', ListProperty(StringProperty)),
- ('action_bin', BinaryProperty()),
- ('action_reference', EmbeddedObjectProperty(ExternalReference)),
('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)),
('confidence', IntegerProperty()),
@@ -119,19 +111,10 @@ class CourseOfAction(STIXDomainObject):
('granular_markings', ListProperty(GranularMarking)),
])
- def _check_object_constraints(self):
- super(CourseOfAction, self)._check_object_constraints()
-
- self._check_mutually_exclusive_properties(
- ["action_bin", "action_reference"],
- at_least_one=False,
- )
-
class Grouping(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'grouping'
@@ -157,9 +140,8 @@ class Grouping(STIXDomainObject):
class Identity(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'identity'
@@ -173,7 +155,7 @@ class Identity(STIXDomainObject):
('name', StringProperty(required=True)),
('description', StringProperty()),
('roles', ListProperty(StringProperty)),
- ('identity_class', StringProperty(required=True)),
+ ('identity_class', StringProperty()),
('sectors', ListProperty(StringProperty)),
('contact_information', StringProperty()),
('revoked', BooleanProperty(default=lambda: False)),
@@ -187,9 +169,8 @@ class Identity(STIXDomainObject):
class Indicator(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'indicator'
@@ -202,7 +183,7 @@ class Indicator(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty()),
('description', StringProperty()),
- ('indicator_types', ListProperty(StringProperty, required=True)),
+ ('indicator_types', ListProperty(StringProperty)),
('pattern', PatternProperty(required=True)),
('pattern_type', StringProperty(required=True)),
('pattern_version', StringProperty()),
@@ -247,9 +228,8 @@ class Indicator(STIXDomainObject):
class Infrastructure(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'infrastructure'
@@ -269,7 +249,7 @@ class Infrastructure(STIXDomainObject):
('granular_markings', ListProperty(GranularMarking)),
('name', StringProperty(required=True)),
('description', StringProperty()),
- ('infrastructure_types', ListProperty(StringProperty, required=True)),
+ ('infrastructure_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()),
@@ -288,9 +268,8 @@ class Infrastructure(STIXDomainObject):
class IntrusionSet(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'intrusion-set'
@@ -331,9 +310,8 @@ class IntrusionSet(STIXDomainObject):
class Location(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'location'
@@ -439,9 +417,8 @@ class Location(STIXDomainObject):
class Malware(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'malware'
@@ -454,13 +431,13 @@ class Malware(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty()),
('description', StringProperty()),
- ('malware_types', ListProperty(StringProperty, required=True)),
+ ('malware_types', ListProperty(StringProperty)),
('is_family', BooleanProperty(required=True)),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()),
- ('os_execution_envs', ListProperty(StringProperty)),
+ ('operating_system_refs', ListProperty(ReferenceProperty(valid_types='software', spec_version='2.1'))),
('architecture_execution_envs', ListProperty(StringProperty)),
('implementation_languages', ListProperty(StringProperty)),
('capabilities', ListProperty(StringProperty)),
@@ -492,9 +469,8 @@ class Malware(STIXDomainObject):
class MalwareAnalysis(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'malware-analysis'
@@ -524,20 +500,21 @@ class MalwareAnalysis(STIXDomainObject):
('submitted', TimestampProperty()),
('analysis_started', TimestampProperty()),
('analysis_ended', TimestampProperty()),
- ('av_result', StringProperty()),
+ ('result_name', StringProperty()),
+ ('result', StringProperty()),
('analysis_sco_refs', ListProperty(ReferenceProperty(valid_types="SCO", spec_version='2.1'))),
+ ('sample_ref', ReferenceProperty(valid_types="SCO", spec_version="2.1")),
])
def _check_object_constraints(self):
super(MalwareAnalysis, self)._check_object_constraints()
- self._check_at_least_one_property(["av_result", "analysis_sco_refs"])
+ self._check_at_least_one_property(["result", "analysis_sco_refs"])
class Note(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'note'
@@ -563,9 +540,8 @@ class Note(STIXDomainObject):
class ObservedData(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'observed-data'
@@ -619,9 +595,8 @@ class ObservedData(STIXDomainObject):
class Opinion(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'opinion'
@@ -657,9 +632,8 @@ class Opinion(STIXDomainObject):
class Report(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'report'
@@ -672,7 +646,7 @@ class Report(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)),
('description', StringProperty()),
- ('report_types', ListProperty(StringProperty, required=True)),
+ ('report_types', ListProperty(StringProperty)),
('published', TimestampProperty(required=True)),
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
('revoked', BooleanProperty(default=lambda: False)),
@@ -691,9 +665,8 @@ class Report(STIXDomainObject):
class ThreatActor(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'threat-actor'
@@ -706,7 +679,7 @@ class ThreatActor(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)),
('description', StringProperty()),
- ('threat_actor_types', ListProperty(StringProperty, required=True)),
+ ('threat_actor_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)),
('first_seen', TimestampProperty()),
('last_seen', TimestampProperty()),
@@ -738,9 +711,8 @@ class ThreatActor(STIXDomainObject):
class Tool(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'tool'
@@ -753,7 +725,7 @@ class Tool(STIXDomainObject):
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
('name', StringProperty(required=True)),
('description', StringProperty()),
- ('tool_types', ListProperty(StringProperty, required=True)),
+ ('tool_types', ListProperty(StringProperty)),
('aliases', ListProperty(StringProperty)),
('kill_chain_phases', ListProperty(KillChainPhase)),
('tool_version', StringProperty()),
@@ -768,9 +740,8 @@ class Tool(STIXDomainObject):
class Vulnerability(STIXDomainObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'vulnerability'
diff --git a/stix2/v21/sro.py b/stix2/v21/sro.py
index 5ba382b..f7f8c57 100644
--- a/stix2/v21/sro.py
+++ b/stix2/v21/sro.py
@@ -12,9 +12,8 @@ from .common import ExternalReference, GranularMarking
class Relationship(STIXRelationshipObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_invalid_source_target_types = ['bundle', 'language-content', 'marking-definition', 'relationship', 'sighting']
@@ -72,9 +71,8 @@ class Relationship(STIXRelationshipObject):
class Sighting(STIXRelationshipObject):
- # TODO: Add link
"""For more detailed information on this object's properties, see
- `the STIX 2.1 specification `__.
+ `the STIX 2.1 specification `__.
"""
_type = 'sighting'