Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into main
commit
24374e7a5f
|
@ -0,0 +1,33 @@
|
|||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
|
||||
name: cti-python-stix2 test harness
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.6, 3.7, 3.8, 3.9]
|
||||
|
||||
name: Python ${{ matrix.python-version }} Build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install and update essential dependencies
|
||||
run: |
|
||||
pip install -U pip setuptools
|
||||
pip install tox-gh-actions
|
||||
pip install codecov
|
||||
- name: Test with Tox
|
||||
run: |
|
||||
tox
|
||||
- name: Upload coverage information to Codecov
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
fail_ci_if_error: true # optional (default = false)
|
||||
verbose: true # optional (default = false)
|
|
@ -1,17 +1,25 @@
|
|||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v1.3.0
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: flake8
|
||||
args:
|
||||
- --max-line-length=160
|
||||
- id: check-merge-conflict
|
||||
- repo: https://github.com/asottile/add-trailing-comma
|
||||
rev: v0.6.4
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
- repo: https://github.com/FalconSocial/pre-commit-python-sorter
|
||||
sha: b57843b0b874df1d16eb0bef00b868792cb245c2
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 3.8.4
|
||||
hooks:
|
||||
- id: python-import-sorter
|
||||
- id: flake8
|
||||
name: Check project styling
|
||||
args:
|
||||
- --max-line-length=160
|
||||
- repo: https://github.com/PyCQA/isort
|
||||
rev: 5.7.0
|
||||
hooks:
|
||||
- id: isort
|
||||
name: Sort python imports (shows diff)
|
||||
args: ["-c", "--diff"]
|
||||
- id: isort
|
||||
name: Sort python imports (fixes files)
|
||||
|
|
19
.travis.yml
19
.travis.yml
|
@ -1,19 +0,0 @@
|
|||
os: linux
|
||||
language: python
|
||||
cache: pip
|
||||
dist: bionic
|
||||
python:
|
||||
- "3.5"
|
||||
- "3.6"
|
||||
- "3.7"
|
||||
- "3.8"
|
||||
install:
|
||||
- pip install -U pip setuptools
|
||||
- pip install tox-travis
|
||||
- pip install codecov
|
||||
- pip install pre-commit
|
||||
script:
|
||||
- tox
|
||||
- pre-commit run --all-files
|
||||
after_success:
|
||||
- codecov
|
26
CHANGELOG
26
CHANGELOG
|
@ -1,6 +1,32 @@
|
|||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.1.0 - 2020-11-20
|
||||
|
||||
* #337 Switches fuzzywuzzy dependency for rapidfuzz (@maxbachmann)
|
||||
* #430 Adds ability to mix single objects and lists in the Bundle constructor
|
||||
* #445, #475 Adds ability to calculate semantic equivalence of indicator patterns
|
||||
* #449 Adds ability to calculate semantic equivalence of entire graphs of objects
|
||||
* #427 Fixes protocol_family property on network socket extension
|
||||
* #436 Fixes pattern visitor to handle expressions with both AND and OR
|
||||
* #431 Fixes bug when adding custom object to FileSystemSink if the object type
|
||||
hasn't been registered
|
||||
* #439 Fixes bug with custom wrapped classes not retaining their name (@maybe-sybr)
|
||||
* #438 Fixes bug with patterns when the object path contains numeric index steps
|
||||
* #454 Fixes stix2.patterns.make_constant() to create TimestampConstants
|
||||
* #455 Fixes bug with AND comparisons in patterns
|
||||
* #460 Fixes bug when retrieving custom object from TAXIICollectionSource if
|
||||
the object type hasn't been registered
|
||||
* #444 Fixes bug so CompositeDataSource and deduplicate() handle unversioned
|
||||
objects correctly
|
||||
* #467 Fixes bug in semantic equivalence when Location objects don't have
|
||||
latitude and longitude properties
|
||||
* #470 Fixes bug where Sighting's where_sighted_refs property couldn't point to
|
||||
a Location object
|
||||
* #473 Fixes typo in name of X509V3ExtensionsType class
|
||||
* #474 Fixes order of object properties when serialized to match examples from
|
||||
the STIX specification
|
||||
|
||||
2.0.2 - 2020-07-07
|
||||
|
||||
* #423 Fixes issue with six dependency.
|
||||
|
|
|
@ -163,8 +163,8 @@ questions about TC Open Repository participation to OASIS Staff at
|
|||
repository-admin@oasis-open.org and any specific CLA-related questions
|
||||
to repository-cla@oasis-open.org.
|
||||
|
||||
.. |Build_Status| image:: https://travis-ci.org/oasis-open/cti-python-stix2.svg?branch=master
|
||||
:target: https://travis-ci.org/oasis-open/cti-python-stix2
|
||||
.. |Build_Status| image:: https://github.com/oasis-open/cti-python-stix2/workflows/cti-python-stix2%20test%20harness/badge.svg
|
||||
:target: https://github.com/oasis-open/cti-python-stix2/actions?query=workflow%3A%22cti-python-stix2+test+harness%22
|
||||
:alt: Build Status
|
||||
.. |Coverage| image:: https://codecov.io/gh/oasis-open/cti-python-stix2/branch/master/graph/badge.svg
|
||||
:target: https://codecov.io/gh/oasis-open/cti-python-stix2
|
||||
|
|
|
@ -65,43 +65,53 @@
|
|||
"\n",
|
||||
"```\n",
|
||||
"stix2_content/\n",
|
||||
" /STIX2 Domain Object type\n",
|
||||
" STIX2 Domain Object\n",
|
||||
" STIX2 Domain Object\n",
|
||||
" STIX2 Domain Object type/\n",
|
||||
" STIX2 Domain Object ID/\n",
|
||||
" 'modified' timestamp.json\n",
|
||||
" 'modified' timestamp.json\n",
|
||||
" STIX2 Domain Object ID/\n",
|
||||
" 'modified' timestamp.json\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" /STIX2 Domain Object type\n",
|
||||
" STIX2 Domain Object\n",
|
||||
" STIX2 Domain Object\n",
|
||||
" STIX2 Domain Object type/\n",
|
||||
" STIX2 Domain Object ID/\n",
|
||||
" 'modified' timestamp.json\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" .\n",
|
||||
" /STIX2 Domain Object type\n",
|
||||
" STIX2 Domain Object type/\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"The master STIX 2 content directory contains subdirectories, each of which aligns to a STIX 2 domain object type (i.e. \"attack-pattern\", \"campaign\", \"malware\", etc.). Within each STIX 2 domain object subdirectory are JSON files that are STIX 2 domain objects of the specified type. The name of the json files correspond to the ID of the STIX 2 domain object found within that file. A real example of the FileSystem directory structure:\n",
|
||||
"The master STIX 2 content directory contains subdirectories, each of which aligns to a STIX 2 domain object type (i.e. \"attack-pattern\", \"campaign\", \"malware\", etc.). Within each STIX 2 domain object type's subdirectory are further subdirectories containing JSON files that are STIX 2 domain objects of the specified type; the name of each of these subdirectories is the ID of the associated STIX 2 domain object. Inside each of these subdirectories are JSON files, the names of which correspond to the ``modified`` timestamp of the STIX 2 domain object found within that file. A real example of the FileSystem directory structure:\n",
|
||||
"\n",
|
||||
"```\n",
|
||||
"stix2_content/\n",
|
||||
" /attack-pattern\n",
|
||||
" attack-pattern--00d0b012-8a03-410e-95de-5826bf542de6.json\n",
|
||||
" attack-pattern--0a3ead4e-6d47-4ccb-854c-a6a4f9d96b22.json\n",
|
||||
" attack-pattern--1b7ba276-eedc-4951-a762-0ceea2c030ec.json\n",
|
||||
" /attack-pattern--00d0b012-8a03-410e-95de-5826bf542de6\n",
|
||||
" 20201211035036648071.json\n",
|
||||
" /attack-pattern--0a3ead4e-6d47-4ccb-854c-a6a4f9d96b22\n",
|
||||
" 20201210035036648071.json\n",
|
||||
" /attack-pattern--1b7ba276-eedc-4951-a762-0ceea2c030ec\n",
|
||||
" 20201111035036648071.json\n",
|
||||
" /campaign\n",
|
||||
" /course-of-action\n",
|
||||
" course-of-action--2a8de25c-f743-4348-b101-3ee33ab5871b.json\n",
|
||||
" course-of-action--2c3ce852-06a2-40ee-8fe6-086f6402a739.json\n",
|
||||
" /course-of-action--2a8de25c-f743-4348-b101-3ee33ab5871b\n",
|
||||
" 20201011035036648071.json\n",
|
||||
" /course-of-action--2c3ce852-06a2-40ee-8fe6-086f6402a739\n",
|
||||
" 20201010035036648071.json\n",
|
||||
" /identity\n",
|
||||
" identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5.json\n",
|
||||
" /identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5\n",
|
||||
" 20201215035036648071.json\n",
|
||||
" /indicator\n",
|
||||
" /intrusion-set\n",
|
||||
" /malware\n",
|
||||
" malware--1d808f62-cf63-4063-9727-ff6132514c22.json\n",
|
||||
" malware--2eb9b131-d333-4a48-9eb4-d8dec46c19ee.json\n",
|
||||
" /malware--1d808f62-cf63-4063-9727-ff6132514c22\n",
|
||||
" 20201211045036648071.json\n",
|
||||
" /malware--2eb9b131-d333-4a48-9eb4-d8dec46c19ee\n",
|
||||
" 20201211035036648072.json\n",
|
||||
" /observed-data\n",
|
||||
" /report\n",
|
||||
" /threat-actor\n",
|
||||
|
@ -1408,7 +1418,7 @@
|
|||
"# add Campaign object to FileSystemSink\n",
|
||||
"fs_sink.add(camp)\n",
|
||||
"\n",
|
||||
"# can also add STIX objects to FileSystemSink in on call\n",
|
||||
"# can also add STIX objects to FileSystemSink in one call\n",
|
||||
"fs_sink.add([ind, ind1])"
|
||||
]
|
||||
}
|
||||
|
@ -1429,7 +1439,7 @@
|
|||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.0a6"
|
||||
"version": "3.6.7"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 2.0.2
|
||||
current_version = 2.1.0
|
||||
commit = True
|
||||
tag = True
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -40,10 +40,10 @@ setup(
|
|||
'Topic :: Security',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],
|
||||
keywords='stix stix2 json cti cyber threat intelligence',
|
||||
packages=find_packages(exclude=['*.test', '*.test.*']),
|
||||
|
|
|
@ -24,8 +24,6 @@
|
|||
|
||||
# flake8: noqa
|
||||
|
||||
DEFAULT_VERSION = '2.1' # Default version will always be the latest STIX 2.X version
|
||||
|
||||
from .confidence import scales
|
||||
from .datastore import CompositeDataSource
|
||||
from .datastore.filesystem import (
|
||||
|
@ -41,7 +39,7 @@ from .markings import (
|
|||
add_markings, clear_markings, get_markings, is_marked, remove_markings,
|
||||
set_markings,
|
||||
)
|
||||
from .parsing import _collect_stix2_mappings, parse, parse_observable
|
||||
from .parsing import parse, parse_observable
|
||||
from .patterns import (
|
||||
AndBooleanExpression, AndObservationExpression, BasicObjectPathComponent,
|
||||
BinaryConstant, BooleanConstant, EqualityComparisonExpression,
|
||||
|
@ -57,8 +55,9 @@ from .patterns import (
|
|||
RepeatQualifier, StartStopQualifier, StringConstant, TimestampConstant,
|
||||
WithinQualifier,
|
||||
)
|
||||
from .registry import _collect_stix2_mappings
|
||||
from .v21 import * # This import will always be the latest STIX 2.X version
|
||||
from .version import __version__
|
||||
from .version import DEFAULT_VERSION, __version__
|
||||
from .versioning import new_version, revoke
|
||||
|
||||
_collect_stix2_mappings()
|
||||
|
|
|
@ -165,8 +165,10 @@ class _STIXBase(Mapping):
|
|||
defaulted = []
|
||||
for name, prop in self._properties.items():
|
||||
try:
|
||||
if (not prop.required and not hasattr(prop, '_fixed_value') and
|
||||
prop.default() == setting_kwargs[name]):
|
||||
if (
|
||||
not prop.required and not hasattr(prop, '_fixed_value') and
|
||||
prop.default() == setting_kwargs[name]
|
||||
):
|
||||
defaulted.append(name)
|
||||
except (AttributeError, KeyError):
|
||||
continue
|
||||
|
@ -195,8 +197,10 @@ class _STIXBase(Mapping):
|
|||
unpickling = '_inner' not in self.__dict__
|
||||
if not unpickling and name in self:
|
||||
return self.__getitem__(name)
|
||||
raise AttributeError("'%s' object has no attribute '%s'" %
|
||||
(self.__class__.__name__, name))
|
||||
raise AttributeError(
|
||||
"'%s' object has no attribute '%s'" %
|
||||
(self.__class__.__name__, name),
|
||||
)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if not name.startswith("_"):
|
||||
|
|
|
@ -3,7 +3,7 @@ from collections import OrderedDict
|
|||
import six
|
||||
|
||||
from .base import _cls_init
|
||||
from .parsing import (
|
||||
from .registration import (
|
||||
_register_marking, _register_object, _register_observable,
|
||||
_register_observable_extension,
|
||||
)
|
||||
|
|
|
@ -75,8 +75,10 @@ class _ObjectFamily(object):
|
|||
|
||||
def add(self, obj):
|
||||
self.all_versions[obj["modified"]] = obj
|
||||
if (self.latest_version is None or
|
||||
obj["modified"] > self.latest_version["modified"]):
|
||||
if (
|
||||
self.latest_version is None or
|
||||
obj["modified"] > self.latest_version["modified"]
|
||||
):
|
||||
self.latest_version = obj
|
||||
|
||||
def __str__(self):
|
||||
|
@ -188,11 +190,13 @@ class MemorySink(DataSink):
|
|||
def save_to_file(self, path, encoding="utf-8"):
|
||||
path = os.path.abspath(path)
|
||||
|
||||
all_objs = list(itertools.chain.from_iterable(
|
||||
value.all_versions.values() if isinstance(value, _ObjectFamily)
|
||||
else [value]
|
||||
for value in self._data.values()
|
||||
))
|
||||
all_objs = list(
|
||||
itertools.chain.from_iterable(
|
||||
value.all_versions.values() if isinstance(value, _ObjectFamily)
|
||||
else [value]
|
||||
for value in self._data.values()
|
||||
),
|
||||
)
|
||||
|
||||
if any("spec_version" in x for x in all_objs):
|
||||
bundle = v21.Bundle(all_objs, allow_custom=self.allow_custom)
|
||||
|
|
|
@ -4,6 +4,7 @@ import time
|
|||
|
||||
from ...datastore import Filter
|
||||
from ...utils import STIXdatetime, parse_into_datetime
|
||||
from ..pattern import equivalent_patterns
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -68,7 +69,7 @@ def semantically_equivalent(obj1, obj2, prop_scores={}, **weight_dict):
|
|||
sum_weights = 0.0
|
||||
|
||||
for prop in weights[type1]:
|
||||
if check_property_present(prop, obj1, obj2) or prop == "longitude_latitude":
|
||||
if check_property_present(prop, obj1, obj2):
|
||||
w = weights[type1][prop][0]
|
||||
comp_funct = weights[type1][prop][1]
|
||||
|
||||
|
@ -117,7 +118,10 @@ def semantically_equivalent(obj1, obj2, prop_scores={}, **weight_dict):
|
|||
|
||||
def check_property_present(prop, obj1, obj2):
|
||||
"""Helper method checks if a property is present on both objects."""
|
||||
if prop in obj1 and prop in obj2:
|
||||
if prop == "longitude_latitude":
|
||||
if all(x in obj1 and x in obj2 for x in ['latitude', 'longitude']):
|
||||
return True
|
||||
elif prop in obj1 and prop in obj2:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -208,8 +212,7 @@ def custom_pattern_based(pattern1, pattern2):
|
|||
float: Number between 0.0 and 1.0 depending on match criteria.
|
||||
|
||||
"""
|
||||
logger.warning("Indicator pattern equivalence is not fully defined; will default to zero if not completely identical")
|
||||
return exact_match(pattern1, pattern2) # TODO: Implement pattern based equivalence
|
||||
return equivalent_patterns(pattern1, pattern2)
|
||||
|
||||
|
||||
def partial_external_reference_based(refs1, refs2):
|
||||
|
|
|
@ -9,18 +9,14 @@
|
|||
|
|
||||
"""
|
||||
|
||||
import stix2
|
||||
from stix2.equivalence.pattern.compare.observation import (
|
||||
observation_expression_cmp,
|
||||
)
|
||||
from stix2.equivalence.pattern.transform import (
|
||||
ChainTransformer, SettleTransformer,
|
||||
)
|
||||
from stix2.equivalence.pattern.transform.observation import (
|
||||
from ... import pattern_visitor
|
||||
from ...version import DEFAULT_VERSION
|
||||
from .compare.observation import observation_expression_cmp
|
||||
from .transform import ChainTransformer, SettleTransformer
|
||||
from .transform.observation import (
|
||||
AbsorptionTransformer, CanonicalizeComparisonExpressionsTransformer,
|
||||
DNFTransformer, FlattenTransformer, OrderDedupeTransformer,
|
||||
)
|
||||
import stix2.pattern_visitor
|
||||
|
||||
# Lazy-initialize
|
||||
_pattern_canonicalizer = None
|
||||
|
@ -30,7 +26,8 @@ def _get_pattern_canonicalizer():
|
|||
"""
|
||||
Get a canonicalization transformer for STIX patterns.
|
||||
|
||||
:return: The transformer
|
||||
Returns:
|
||||
The transformer
|
||||
"""
|
||||
|
||||
# The transformers are either stateless or contain no state which changes
|
||||
|
@ -60,20 +57,23 @@ def _get_pattern_canonicalizer():
|
|||
return _pattern_canonicalizer
|
||||
|
||||
|
||||
def equivalent_patterns(pattern1, pattern2, stix_version=stix2.DEFAULT_VERSION):
|
||||
def equivalent_patterns(pattern1, pattern2, stix_version=DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether two STIX patterns are semantically equivalent.
|
||||
|
||||
:param pattern1: The first STIX pattern
|
||||
:param pattern2: The second STIX pattern
|
||||
:param stix_version: The STIX version to use for pattern parsing, as a
|
||||
string ("2.0", "2.1", etc). Defaults to library-wide default version.
|
||||
:return: True if the patterns are semantically equivalent; False if not
|
||||
Args:
|
||||
pattern1: The first STIX pattern
|
||||
pattern2: The second STIX pattern
|
||||
stix_version: The STIX version to use for pattern parsing, as a string
|
||||
("2.0", "2.1", etc). Defaults to library-wide default version.
|
||||
|
||||
Returns:
|
||||
True if the patterns are semantically equivalent; False if not
|
||||
"""
|
||||
patt_ast1 = stix2.pattern_visitor.create_pattern_object(
|
||||
patt_ast1 = pattern_visitor.create_pattern_object(
|
||||
pattern1, version=stix_version,
|
||||
)
|
||||
patt_ast2 = stix2.pattern_visitor.create_pattern_object(
|
||||
patt_ast2 = pattern_visitor.create_pattern_object(
|
||||
pattern2, version=stix_version,
|
||||
)
|
||||
|
||||
|
@ -87,7 +87,7 @@ def equivalent_patterns(pattern1, pattern2, stix_version=stix2.DEFAULT_VERSION):
|
|||
|
||||
|
||||
def find_equivalent_patterns(
|
||||
search_pattern, patterns, stix_version=stix2.DEFAULT_VERSION,
|
||||
search_pattern, patterns, stix_version=DEFAULT_VERSION,
|
||||
):
|
||||
"""
|
||||
Find patterns from a sequence which are equivalent to a given pattern.
|
||||
|
@ -96,14 +96,16 @@ def find_equivalent_patterns(
|
|||
on an input iterable and is implemented as a generator of matches. So you
|
||||
can "stream" patterns in and matching patterns will be streamed out.
|
||||
|
||||
:param search_pattern: A search pattern as a string
|
||||
:param patterns: An iterable over patterns as strings
|
||||
:param stix_version: The STIX version to use for pattern parsing, as a
|
||||
string ("2.0", "2.1", etc). Defaults to library-wide default version.
|
||||
:return: A generator iterator producing the semantically equivalent
|
||||
patterns
|
||||
Args:
|
||||
search_pattern: A search pattern as a string
|
||||
patterns: An iterable over patterns as strings
|
||||
stix_version: The STIX version to use for pattern parsing, as a string
|
||||
("2.0", "2.1", etc). Defaults to library-wide default version.
|
||||
|
||||
Returns:
|
||||
A generator iterator producing the semantically equivalent patterns
|
||||
"""
|
||||
search_pattern_ast = stix2.pattern_visitor.create_pattern_object(
|
||||
search_pattern_ast = pattern_visitor.create_pattern_object(
|
||||
search_pattern, version=stix_version,
|
||||
)
|
||||
|
||||
|
@ -113,7 +115,7 @@ def find_equivalent_patterns(
|
|||
)
|
||||
|
||||
for pattern in patterns:
|
||||
pattern_ast = stix2.pattern_visitor.create_pattern_object(
|
||||
pattern_ast = pattern_visitor.create_pattern_object(
|
||||
pattern, version=stix_version,
|
||||
)
|
||||
canon_pattern_ast, _ = pattern_canonicalizer.transform(pattern_ast)
|
||||
|
|
|
@ -16,9 +16,12 @@ def generic_cmp(value1, value2):
|
|||
Generic comparator of values which uses the builtin '<' and '>' operators.
|
||||
Assumes the values can be compared that way.
|
||||
|
||||
:param value1: The first value
|
||||
:param value2: The second value
|
||||
:return: -1, 0, or 1 depending on whether value1 is less, equal, or greater
|
||||
Args:
|
||||
value1: The first value
|
||||
value2: The second value
|
||||
|
||||
Returns:
|
||||
-1, 0, or 1 depending on whether value1 is less, equal, or greater
|
||||
than value2
|
||||
"""
|
||||
|
||||
|
@ -30,12 +33,15 @@ def iter_lex_cmp(seq1, seq2, cmp):
|
|||
Generic lexicographical compare function, which works on two iterables and
|
||||
a comparator function.
|
||||
|
||||
:param seq1: The first iterable
|
||||
:param seq2: The second iterable
|
||||
:param cmp: a two-arg callable comparator for values iterated over. It
|
||||
must behave analogously to this function, returning <0, 0, or >0 to
|
||||
express the ordering of the two values.
|
||||
:return: <0 if seq1 < seq2; >0 if seq1 > seq2; 0 if they're equal
|
||||
Args:
|
||||
seq1: The first iterable
|
||||
seq2: The second iterable
|
||||
cmp: a two-arg callable comparator for values iterated over. It
|
||||
must behave analogously to this function, returning <0, 0, or >0 to
|
||||
express the ordering of the two values.
|
||||
|
||||
Returns:
|
||||
<0 if seq1 < seq2; >0 if seq1 > seq2; 0 if they're equal
|
||||
"""
|
||||
|
||||
it1 = iter(seq1)
|
||||
|
@ -84,11 +90,14 @@ def iter_in(value, seq, cmp):
|
|||
a comparator function. This function checks whether the given value is
|
||||
contained in the given iterable.
|
||||
|
||||
:param value: A value
|
||||
:param seq: An iterable
|
||||
:param cmp: A 2-arg comparator function which must return 0 if the args
|
||||
are equal
|
||||
:return: True if the value is found in the iterable, False if it is not
|
||||
Args:
|
||||
value: A value
|
||||
seq: An iterable
|
||||
cmp: A 2-arg comparator function which must return 0 if the args
|
||||
are equal
|
||||
|
||||
Returns:
|
||||
True if the value is found in the iterable, False if it is not
|
||||
"""
|
||||
result = False
|
||||
for seq_val in seq:
|
||||
|
|
|
@ -32,9 +32,12 @@ def generic_constant_cmp(const1, const2):
|
|||
Generic comparator for most _Constant instances. They must have a "value"
|
||||
attribute whose value supports the builtin comparison operators.
|
||||
|
||||
:param const1: The first _Constant instance
|
||||
:param const2: The second _Constant instance
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
const1: The first _Constant instance
|
||||
const2: The second _Constant instance
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
return generic_cmp(const1.value, const2.value)
|
||||
|
@ -44,9 +47,12 @@ def bool_cmp(value1, value2):
|
|||
"""
|
||||
Compare two boolean constants.
|
||||
|
||||
:param value1: The first BooleanConstant instance
|
||||
:param value2: The second BooleanConstant instance
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
value1: The first BooleanConstant instance
|
||||
value2: The second BooleanConstant instance
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
|
||||
|
@ -72,9 +78,12 @@ def hex_cmp(value1, value2):
|
|||
Compare two STIX "hex" values. This decodes to bytes and compares that.
|
||||
It does *not* do a string compare on the hex representations.
|
||||
|
||||
:param value1: The first HexConstant
|
||||
:param value2: The second HexConstant
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
value1: The first HexConstant
|
||||
value2: The second HexConstant
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
bytes1 = bytes.fromhex(value1.value)
|
||||
|
@ -88,9 +97,12 @@ def bin_cmp(value1, value2):
|
|||
Compare two STIX "binary" values. This decodes to bytes and compares that.
|
||||
It does *not* do a string compare on the base64 representations.
|
||||
|
||||
:param value1: The first BinaryConstant
|
||||
:param value2: The second BinaryConstant
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
value1: The first BinaryConstant
|
||||
value2: The second BinaryConstant
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
bytes1 = base64.standard_b64decode(value1.value)
|
||||
|
@ -103,9 +115,12 @@ def list_cmp(value1, value2):
|
|||
"""
|
||||
Compare lists order-insensitively.
|
||||
|
||||
:param value1: The first ListConstant
|
||||
:param value2: The second ListConstant
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
value1: The first ListConstant
|
||||
value2: The second ListConstant
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
|
||||
|
@ -144,9 +159,12 @@ def object_path_component_cmp(comp1, comp2):
|
|||
Ints and strings compare as usual to each other; ints compare less than
|
||||
strings.
|
||||
|
||||
:param comp1: An object path component (string or int)
|
||||
:param comp2: An object path component (string or int)
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
comp1: An object path component (string or int)
|
||||
comp2: An object path component (string or int)
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
|
||||
|
@ -172,8 +190,11 @@ def object_path_to_raw_values(path):
|
|||
properties; "*" index steps become that string; and numeric index steps
|
||||
become integers.
|
||||
|
||||
:param path: An ObjectPath instance
|
||||
:return: A generator iterator over the values
|
||||
Args:
|
||||
path: An ObjectPath instance
|
||||
|
||||
Returns:
|
||||
A generator iterator over the values
|
||||
"""
|
||||
|
||||
for comp in path.property_path:
|
||||
|
@ -195,9 +216,12 @@ def object_path_cmp(path1, path2):
|
|||
"""
|
||||
Compare two object paths.
|
||||
|
||||
:param path1: The first ObjectPath instance
|
||||
:param path2: The second ObjectPath instance
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
path1: The first ObjectPath instance
|
||||
path2: The second ObjectPath instance
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
if path1.object_type_name < path2.object_type_name:
|
||||
|
@ -224,9 +248,12 @@ def comparison_operator_cmp(op1, op2):
|
|||
"""
|
||||
Compare two comparison operators.
|
||||
|
||||
:param op1: The first comparison operator (a string)
|
||||
:param op2: The second comparison operator (a string)
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
op1: The first comparison operator (a string)
|
||||
op2: The second comparison operator (a string)
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
op1_idx = _COMPARISON_OP_ORDER.index(op1)
|
||||
|
@ -241,9 +268,12 @@ def constant_cmp(value1, value2):
|
|||
"""
|
||||
Compare two constants.
|
||||
|
||||
:param value1: The first _Constant instance
|
||||
:param value2: The second _Constant instance
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
value1: The first _Constant instance
|
||||
value2: The second _Constant instance
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
|
||||
|
@ -284,9 +314,12 @@ def simple_comparison_expression_cmp(expr1, expr2):
|
|||
Compare "simple" comparison expressions: those which aren't AND/OR
|
||||
combinations, just <path> <op> <value> comparisons.
|
||||
|
||||
:param expr1: first _ComparisonExpression instance
|
||||
:param expr2: second _ComparisonExpression instance
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
expr1: first _ComparisonExpression instance
|
||||
expr2: second _ComparisonExpression instance
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
|
||||
|
@ -315,9 +348,12 @@ def comparison_expression_cmp(expr1, expr2):
|
|||
expressions' sub-components. To achieve an order-insensitive comparison,
|
||||
the ASTs must be canonically ordered first.
|
||||
|
||||
:param expr1: The first comparison expression
|
||||
:param expr2: The second comparison expression
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
expr1: The first comparison expression
|
||||
expr2: The second comparison expression
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
if isinstance(expr1, _ComparisonExpression) \
|
||||
|
|
|
@ -64,9 +64,12 @@ def observation_expression_cmp(expr1, expr2):
|
|||
the expressions' sub-components. To achieve an order-insensitive
|
||||
comparison, the ASTs must be canonically ordered first.
|
||||
|
||||
:param expr1: The first observation expression
|
||||
:param expr2: The second observation expression
|
||||
:return: <0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
Args:
|
||||
expr1: The first observation expression
|
||||
expr2: The second observation expression
|
||||
|
||||
Returns:
|
||||
<0, 0, or >0 depending on whether the first arg is less, equal or
|
||||
greater than the second
|
||||
"""
|
||||
type1 = type(expr1)
|
||||
|
|
|
@ -22,13 +22,17 @@ def _dupe_ast(ast):
|
|||
"""
|
||||
Create a duplicate of the given AST.
|
||||
|
||||
Note: the comparison expression "leaves", i.e. simple <path> <op> <value>
|
||||
comparisons are currently not duplicated. I don't think it's necessary as
|
||||
of this writing; they are never changed. But revisit this if/when
|
||||
necessary.
|
||||
Note:
|
||||
The comparison expression "leaves", i.e. simple <path> <op> <value>
|
||||
comparisons are currently not duplicated. I don't think it's necessary
|
||||
as of this writing; they are never changed. But revisit this if/when
|
||||
necessary.
|
||||
|
||||
:param ast: The AST to duplicate
|
||||
:return: The duplicate AST
|
||||
Args:
|
||||
ast: The AST to duplicate
|
||||
|
||||
Returns:
|
||||
The duplicate AST
|
||||
"""
|
||||
if isinstance(ast, AndBooleanExpression):
|
||||
result = AndBooleanExpression([
|
||||
|
@ -108,8 +112,11 @@ class ComparisonExpressionTransformer(Transformer):
|
|||
Invoke a transformer callback method based on the given ast root node
|
||||
type.
|
||||
|
||||
:param ast: The AST
|
||||
:return: The callback's result
|
||||
Args:
|
||||
ast: The AST
|
||||
|
||||
Returns:
|
||||
The callback's result
|
||||
"""
|
||||
|
||||
if isinstance(ast, AndBooleanExpression):
|
||||
|
@ -137,7 +144,7 @@ class ComparisonExpressionTransformer(Transformer):
|
|||
|
||||
|
||||
class OrderDedupeTransformer(
|
||||
ComparisonExpressionTransformer
|
||||
ComparisonExpressionTransformer,
|
||||
):
|
||||
"""
|
||||
Canonically order the children of all nodes in the AST. Because the
|
||||
|
@ -153,8 +160,11 @@ class OrderDedupeTransformer(
|
|||
"""
|
||||
Sort/dedupe children. AND and OR can be treated identically.
|
||||
|
||||
:param ast: The comparison expression AST
|
||||
:return: The same AST node, but with sorted children
|
||||
Args:
|
||||
ast: The comparison expression AST
|
||||
|
||||
Returns:
|
||||
The same AST node, but with sorted children
|
||||
"""
|
||||
sorted_children = sorted(
|
||||
ast.operands, key=functools.cmp_to_key(comparison_expression_cmp),
|
||||
|
@ -201,8 +211,11 @@ class FlattenTransformer(ComparisonExpressionTransformer):
|
|||
little difference is that we can absorb AND children if we're an AND
|
||||
ourselves; and OR for OR.
|
||||
|
||||
:param ast: The comparison expression AST
|
||||
:return: The same AST node, but with flattened children
|
||||
Args:
|
||||
ast: The comparison expression AST
|
||||
|
||||
Returns:
|
||||
The same AST node, but with flattened children
|
||||
"""
|
||||
|
||||
changed = False
|
||||
|
@ -234,7 +247,7 @@ class FlattenTransformer(ComparisonExpressionTransformer):
|
|||
|
||||
|
||||
class AbsorptionTransformer(
|
||||
ComparisonExpressionTransformer
|
||||
ComparisonExpressionTransformer,
|
||||
):
|
||||
"""
|
||||
Applies boolean "absorption" rules for AST simplification. E.g.:
|
||||
|
|
|
@ -38,8 +38,11 @@ def _dupe_ast(ast):
|
|||
observation expressions are currently not duplicated. I don't think it's
|
||||
necessary as of this writing. But revisit this if/when necessary.
|
||||
|
||||
:param ast: The AST to duplicate
|
||||
:return: The duplicate AST
|
||||
Args:
|
||||
ast: The AST to duplicate
|
||||
|
||||
Returns:
|
||||
The duplicate AST
|
||||
"""
|
||||
if isinstance(ast, AndObservationExpression):
|
||||
result = AndObservationExpression([
|
||||
|
@ -149,9 +152,11 @@ class ObservationExpressionTransformer(Transformer):
|
|||
changed = True
|
||||
|
||||
else:
|
||||
raise TypeError("Not an observation expression: {}: {}".format(
|
||||
type(ast).__name__, str(ast),
|
||||
))
|
||||
raise TypeError(
|
||||
"Not an observation expression: {}: {}".format(
|
||||
type(ast).__name__, str(ast),
|
||||
),
|
||||
)
|
||||
|
||||
return result, changed
|
||||
|
||||
|
@ -160,8 +165,11 @@ class ObservationExpressionTransformer(Transformer):
|
|||
Invoke a transformer callback method based on the given ast root node
|
||||
type.
|
||||
|
||||
:param ast: The AST
|
||||
:return: The callback's result
|
||||
Args:
|
||||
ast: The AST
|
||||
|
||||
Returns:
|
||||
The callback's result
|
||||
"""
|
||||
|
||||
dispatch_name = self._DISPATCH_NAME_MAP.get(type(ast))
|
||||
|
@ -223,7 +231,7 @@ class FlattenTransformer(ObservationExpressionTransformer):
|
|||
|
||||
|
||||
class OrderDedupeTransformer(
|
||||
ObservationExpressionTransformer
|
||||
ObservationExpressionTransformer,
|
||||
):
|
||||
"""
|
||||
Canonically order AND/OR expressions, and dedupe ORs. E.g.:
|
||||
|
@ -266,7 +274,7 @@ class OrderDedupeTransformer(
|
|||
|
||||
|
||||
class AbsorptionTransformer(
|
||||
ObservationExpressionTransformer
|
||||
ObservationExpressionTransformer,
|
||||
):
|
||||
"""
|
||||
Applies boolean "absorption" rules for observation expressions, for AST
|
||||
|
@ -292,10 +300,12 @@ class AbsorptionTransformer(
|
|||
the right does not "contain" the left. You would need two A's on the
|
||||
right.
|
||||
|
||||
:param exprs_containee: The expressions we want to check for containment
|
||||
:param exprs_container: The expressions acting as the "container"
|
||||
:return: True if the containee is contained in the container; False if
|
||||
not
|
||||
Args:
|
||||
exprs_containee: The expressions we want to check for containment
|
||||
exprs_container: The expressions acting as the "container"
|
||||
|
||||
Returns:
|
||||
True if the containee is contained in the container; False if not
|
||||
"""
|
||||
|
||||
# make our own list we are free to manipulate without affecting the
|
||||
|
@ -336,10 +346,12 @@ class AbsorptionTransformer(
|
|||
in the container (rhs), B follows A, so it "contains" the lhs even
|
||||
though there is other stuff mixed in.
|
||||
|
||||
:param exprs_containee: The expressions we want to check for containment
|
||||
:param exprs_container: The expressions acting as the "container"
|
||||
:return: True if the containee is contained in the container; False if
|
||||
not
|
||||
Args:
|
||||
exprs_containee: The expressions we want to check for containment
|
||||
exprs_container: The expressions acting as the "container"
|
||||
|
||||
Returns:
|
||||
True if the containee is contained in the container; False if not
|
||||
"""
|
||||
|
||||
ee_iter = iter(exprs_containee)
|
||||
|
@ -469,7 +481,7 @@ class DNFTransformer(ObservationExpressionTransformer):
|
|||
|
||||
|
||||
class CanonicalizeComparisonExpressionsTransformer(
|
||||
ObservationExpressionTransformer
|
||||
ObservationExpressionTransformer,
|
||||
):
|
||||
"""
|
||||
Canonicalize all comparison expressions.
|
||||
|
|
|
@ -25,9 +25,12 @@ def _path_is(object_path, path_pattern):
|
|||
index path step; _ANY_KEY matches any key path step, and _ANY matches any
|
||||
path step.
|
||||
|
||||
:param object_path: An ObjectPath instance
|
||||
:param path_pattern: An iterable giving the pattern path steps
|
||||
:return: True if the path matches the pattern; False if not
|
||||
Args:
|
||||
object_path: An ObjectPath instance
|
||||
path_pattern: An iterable giving the pattern path steps
|
||||
|
||||
Returns:
|
||||
True if the path matches the pattern; False if not
|
||||
"""
|
||||
path_values = object_path_to_raw_values(object_path)
|
||||
|
||||
|
@ -70,8 +73,9 @@ def _mask_bytes(ip_bytes, prefix_size):
|
|||
Retain the high-order 'prefix_size' bits from ip_bytes, and zero out the
|
||||
remaining low-order bits. This side-effects ip_bytes.
|
||||
|
||||
:param ip_bytes: A mutable byte sequence (e.g. a bytearray)
|
||||
:param prefix_size: An integer prefix size
|
||||
Args:
|
||||
ip_bytes: A mutable byte sequence (e.g. a bytearray)
|
||||
prefix_size: An integer prefix size
|
||||
"""
|
||||
addr_size_bytes = len(ip_bytes)
|
||||
addr_size_bits = 8 * addr_size_bytes
|
||||
|
@ -99,8 +103,9 @@ def windows_reg_key(comp_expr):
|
|||
being compared. This enables case-insensitive comparisons between two
|
||||
patterns, for those values. This side-effects the given AST.
|
||||
|
||||
:param comp_expr: A _ComparisonExpression object whose type is
|
||||
windows-registry-key
|
||||
Args:
|
||||
comp_expr: A _ComparisonExpression object whose type is
|
||||
windows-registry-key
|
||||
"""
|
||||
if _path_is(comp_expr.lhs, ("key",)) \
|
||||
or _path_is(comp_expr.lhs, ("values", _ANY_IDX, "name")):
|
||||
|
@ -119,7 +124,8 @@ def ipv4_addr(comp_expr):
|
|||
|
||||
This side-effects the given AST.
|
||||
|
||||
:param comp_expr: A _ComparisonExpression object whose type is ipv4-addr.
|
||||
Args:
|
||||
comp_expr: A _ComparisonExpression object whose type is ipv4-addr.
|
||||
"""
|
||||
if _path_is(comp_expr.lhs, ("value",)):
|
||||
value = comp_expr.rhs.value
|
||||
|
@ -179,7 +185,8 @@ def ipv6_addr(comp_expr):
|
|||
|
||||
This side-effects the given AST.
|
||||
|
||||
:param comp_expr: A _ComparisonExpression object whose type is ipv6-addr.
|
||||
Args:
|
||||
comp_expr: A _ComparisonExpression object whose type is ipv6-addr.
|
||||
"""
|
||||
if _path_is(comp_expr.lhs, ("value",)):
|
||||
value = comp_expr.rhs.value
|
||||
|
|
265
stix2/parsing.py
265
stix2/parsing.py
|
@ -1,17 +1,10 @@
|
|||
"""STIX2 Core parsing methods."""
|
||||
|
||||
import copy
|
||||
import importlib
|
||||
import pkgutil
|
||||
import re
|
||||
|
||||
import stix2
|
||||
|
||||
from .base import _DomainObject, _Observable
|
||||
from .exceptions import DuplicateRegistrationError, ParseError
|
||||
from .utils import PREFIX_21_REGEX, _get_dict, get_class_hierarchy_names
|
||||
|
||||
STIX2_OBJ_MAPS = {}
|
||||
from . import registry
|
||||
from .exceptions import ParseError
|
||||
from .utils import _get_dict, detect_spec_version
|
||||
|
||||
|
||||
def parse(data, allow_custom=False, interoperability=False, version=None):
|
||||
|
@ -122,22 +115,19 @@ def dict_to_stix2(stix_dict, allow_custom=False, interoperability=False, version
|
|||
if 'type' not in stix_dict:
|
||||
raise ParseError("Can't parse object with no 'type' property: %s" % str(stix_dict))
|
||||
|
||||
if version:
|
||||
# If the version argument was passed, override other approaches.
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
v = _detect_spec_version(stix_dict)
|
||||
if not version:
|
||||
version = detect_spec_version(stix_dict)
|
||||
|
||||
OBJ_MAP = dict(STIX2_OBJ_MAPS[v]['objects'], **STIX2_OBJ_MAPS[v]['observables'])
|
||||
obj_type = stix_dict["type"]
|
||||
obj_class = registry.class_for_type(obj_type, version, "objects") \
|
||||
or registry.class_for_type(obj_type, version, "observables")
|
||||
|
||||
try:
|
||||
obj_class = OBJ_MAP[stix_dict['type']]
|
||||
except KeyError:
|
||||
if not obj_class:
|
||||
if allow_custom:
|
||||
# flag allows for unknown custom objects too, but will not
|
||||
# be parsed into STIX object, returned as is
|
||||
return stix_dict
|
||||
raise ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % stix_dict['type'])
|
||||
raise ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % obj_type)
|
||||
|
||||
return obj_class(allow_custom=allow_custom, interoperability=interoperability, **stix_dict)
|
||||
|
||||
|
@ -172,236 +162,19 @@ def parse_observable(data, _valid_refs=None, allow_custom=False, version=None):
|
|||
|
||||
obj['_valid_refs'] = _valid_refs or []
|
||||
|
||||
if version:
|
||||
# If the version argument was passed, override other approaches.
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
v = _detect_spec_version(obj)
|
||||
if not version:
|
||||
version = detect_spec_version(obj)
|
||||
|
||||
try:
|
||||
OBJ_MAP_OBSERVABLE = STIX2_OBJ_MAPS[v]['observables']
|
||||
obj_class = OBJ_MAP_OBSERVABLE[obj['type']]
|
||||
except KeyError:
|
||||
obj_type = obj["type"]
|
||||
obj_class = registry.class_for_type(obj_type, version, "observables")
|
||||
if not obj_class:
|
||||
if allow_custom:
|
||||
# flag allows for unknown custom objects too, but will not
|
||||
# be parsed into STIX observable object, just returned as is
|
||||
return obj
|
||||
raise ParseError("Can't parse unknown observable type '%s'! For custom observables, "
|
||||
"use the CustomObservable decorator." % obj['type'])
|
||||
raise ParseError(
|
||||
"Can't parse unknown observable type '%s'! For custom observables, "
|
||||
"use the CustomObservable decorator." % obj['type'],
|
||||
)
|
||||
|
||||
return obj_class(allow_custom=allow_custom, **obj)
|
||||
|
||||
|
||||
def _register_object(new_type, version=stix2.DEFAULT_VERSION):
|
||||
"""Register a custom STIX Object type.
|
||||
|
||||
Args:
|
||||
new_type (class): A class to register in the Object map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
Raises:
|
||||
ValueError: If the class being registered wasn't created with the
|
||||
@CustomObject decorator.
|
||||
DuplicateRegistrationError: If the class has already been registered.
|
||||
|
||||
"""
|
||||
|
||||
if not issubclass(new_type, _DomainObject):
|
||||
raise ValueError(
|
||||
"'%s' must be created with the @CustomObject decorator." %
|
||||
new_type.__name__,
|
||||
)
|
||||
|
||||
properties = new_type._properties
|
||||
|
||||
if version == "2.1":
|
||||
for prop_name, prop in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character" % prop_name)
|
||||
|
||||
if version:
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
# Use default version (latest) if no version was provided.
|
||||
v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
|
||||
|
||||
OBJ_MAP = STIX2_OBJ_MAPS[v]['objects']
|
||||
if new_type._type in OBJ_MAP.keys():
|
||||
raise DuplicateRegistrationError("STIX Object", new_type._type)
|
||||
OBJ_MAP[new_type._type] = new_type
|
||||
|
||||
|
||||
def _register_marking(new_marking, version=stix2.DEFAULT_VERSION):
|
||||
"""Register a custom STIX Marking Definition type.
|
||||
|
||||
Args:
|
||||
new_marking (class): A class to register in the Marking map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
"""
|
||||
|
||||
mark_type = new_marking._type
|
||||
properties = new_marking._properties
|
||||
|
||||
stix2.properties._validate_type(mark_type, version)
|
||||
|
||||
if version == "2.1":
|
||||
for prop_name, prop_value in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
|
||||
if version:
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
# Use default version (latest) if no version was provided.
|
||||
v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
|
||||
|
||||
OBJ_MAP_MARKING = STIX2_OBJ_MAPS[v]['markings']
|
||||
if mark_type in OBJ_MAP_MARKING.keys():
|
||||
raise DuplicateRegistrationError("STIX Marking", mark_type)
|
||||
OBJ_MAP_MARKING[mark_type] = new_marking
|
||||
|
||||
|
||||
def _register_observable(new_observable, version=stix2.DEFAULT_VERSION):
|
||||
"""Register a custom STIX Cyber Observable type.
|
||||
|
||||
Args:
|
||||
new_observable (class): A class to register in the Observables map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
"""
|
||||
properties = new_observable._properties
|
||||
|
||||
if version == "2.0":
|
||||
# If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties
|
||||
for prop_name, prop in properties.items():
|
||||
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference property but "
|
||||
"is not an ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference list property but "
|
||||
"is not a ListProperty containing ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
else:
|
||||
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
|
||||
for prop_name, prop in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
elif prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference property but "
|
||||
"is not a ReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (prop_name.endswith('_refs') and ('ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ReferenceProperty' not in get_class_hierarchy_names(prop.contained))):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference list property but "
|
||||
"is not a ListProperty containing ReferenceProperty." % prop_name,
|
||||
)
|
||||
|
||||
if version:
|
||||
v = 'v' + version.replace('.', '')
|
||||
else:
|
||||
# Use default version (latest) if no version was provided.
|
||||
v = 'v' + stix2.DEFAULT_VERSION.replace('.', '')
|
||||
|
||||
OBJ_MAP_OBSERVABLE = STIX2_OBJ_MAPS[v]['observables']
|
||||
if new_observable._type in OBJ_MAP_OBSERVABLE.keys():
|
||||
raise DuplicateRegistrationError("Cyber Observable", new_observable._type)
|
||||
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
|
||||
|
||||
|
||||
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 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").
|
||||
Defaults to the latest supported version.
|
||||
|
||||
"""
|
||||
obs_class = observable if isinstance(observable, type) else \
|
||||
type(observable)
|
||||
ext_type = new_extension._type
|
||||
properties = new_extension._properties
|
||||
|
||||
if not issubclass(obs_class, _Observable):
|
||||
raise ValueError("'observable' must be a valid Observable class!")
|
||||
|
||||
stix2.properties._validate_type(ext_type, version)
|
||||
|
||||
if not new_extension._properties:
|
||||
raise ValueError(
|
||||
"Invalid extension: must define at least one property: " +
|
||||
ext_type,
|
||||
)
|
||||
|
||||
if version == "2.1":
|
||||
if not ext_type.endswith('-ext'):
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must end with '-ext'." %
|
||||
ext_type,
|
||||
)
|
||||
|
||||
for prop_name, prop_value in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
|
||||
v = 'v' + version.replace('.', '')
|
||||
|
||||
try:
|
||||
observable_type = observable._type
|
||||
except AttributeError:
|
||||
raise ValueError(
|
||||
"Unknown observable type. Custom observables must be "
|
||||
"created with the @CustomObservable decorator.",
|
||||
)
|
||||
|
||||
OBJ_MAP_OBSERVABLE = STIX2_OBJ_MAPS[v]['observables']
|
||||
EXT_MAP = STIX2_OBJ_MAPS[v]['observable-extensions']
|
||||
|
||||
try:
|
||||
if ext_type in EXT_MAP[observable_type].keys():
|
||||
raise DuplicateRegistrationError("Observable Extension", ext_type)
|
||||
EXT_MAP[observable_type][ext_type] = new_extension
|
||||
except KeyError:
|
||||
if observable_type not in OBJ_MAP_OBSERVABLE:
|
||||
raise ValueError(
|
||||
"Unknown observable type '%s'. Custom observables "
|
||||
"must be created with the @CustomObservable decorator."
|
||||
% observable_type,
|
||||
)
|
||||
else:
|
||||
EXT_MAP[observable_type] = {ext_type: new_extension}
|
||||
|
||||
|
||||
def _collect_stix2_mappings():
|
||||
"""Navigate the package once and retrieve all object mapping dicts for each
|
||||
v2X package. Includes OBJ_MAP, OBJ_MAP_OBSERVABLE, EXT_MAP."""
|
||||
if not STIX2_OBJ_MAPS:
|
||||
top_level_module = importlib.import_module('stix2')
|
||||
path = top_level_module.__path__
|
||||
prefix = str(top_level_module.__name__) + '.'
|
||||
|
||||
for module_loader, name, is_pkg in pkgutil.walk_packages(path=path, prefix=prefix):
|
||||
ver = name.split('.')[1]
|
||||
if re.match(r'^stix2\.v2[0-9]$', name) and is_pkg:
|
||||
mod = importlib.import_module(name, str(top_level_module.__name__))
|
||||
STIX2_OBJ_MAPS[ver] = {}
|
||||
STIX2_OBJ_MAPS[ver]['objects'] = mod.OBJ_MAP
|
||||
STIX2_OBJ_MAPS[ver]['observables'] = mod.OBJ_MAP_OBSERVABLE
|
||||
STIX2_OBJ_MAPS[ver]['observable-extensions'] = mod.EXT_MAP
|
||||
elif re.match(r'^stix2\.v2[0-9]\.common$', name) and is_pkg is False:
|
||||
mod = importlib.import_module(name, str(top_level_module.__name__))
|
||||
STIX2_OBJ_MAPS[ver]['markings'] = mod.OBJ_MAP_MARKING
|
||||
|
|
|
@ -17,10 +17,9 @@ from stix2patterns.v21.grammars.STIXPatternVisitor import \
|
|||
STIXPatternVisitor as STIXPatternVisitor21
|
||||
from stix2patterns.v21.pattern import Pattern as Pattern21
|
||||
|
||||
import stix2
|
||||
|
||||
from .patterns import *
|
||||
from .patterns import _BooleanExpression
|
||||
from .version import DEFAULT_VERSION
|
||||
|
||||
# flake8: noqa F405
|
||||
|
||||
|
@ -261,11 +260,13 @@ class STIXPatternVisitorForSTIX2():
|
|||
property_path.append(self.instantiate("ListObjectPathComponent", current.property_name, next.getText()))
|
||||
i += 2
|
||||
elif isinstance(next, IntegerConstant):
|
||||
property_path.append(self.instantiate(
|
||||
"ListObjectPathComponent",
|
||||
current.property_name if isinstance(current, BasicObjectPathComponent) else text_type(current),
|
||||
next.value,
|
||||
))
|
||||
property_path.append(
|
||||
self.instantiate(
|
||||
"ListObjectPathComponent",
|
||||
current.property_name if isinstance(current, BasicObjectPathComponent) else text_type(current),
|
||||
next.value,
|
||||
),
|
||||
)
|
||||
i += 2
|
||||
else:
|
||||
property_path.append(current)
|
||||
|
@ -389,7 +390,7 @@ class STIXPatternVisitorForSTIX20(STIXPatternVisitorForSTIX2, STIXPatternVisitor
|
|||
super(STIXPatternVisitor20, self).__init__()
|
||||
|
||||
|
||||
def create_pattern_object(pattern, module_suffix="", module_name="", version=stix2.DEFAULT_VERSION):
|
||||
def create_pattern_object(pattern, module_suffix="", module_name="", version=DEFAULT_VERSION):
|
||||
"""
|
||||
Create a STIX pattern AST from a pattern string.
|
||||
"""
|
||||
|
|
|
@ -9,15 +9,15 @@ import uuid
|
|||
|
||||
from six import string_types, text_type
|
||||
|
||||
import stix2
|
||||
|
||||
from .base import _STIXBase
|
||||
from .exceptions import (
|
||||
CustomContentError, DictionaryKeyError, MissingPropertiesError,
|
||||
MutuallyExclusivePropertiesError, STIXError,
|
||||
)
|
||||
from .parsing import STIX2_OBJ_MAPS, parse, parse_observable
|
||||
from .parsing import parse, parse_observable
|
||||
from .registry import STIX2_OBJ_MAPS
|
||||
from .utils import _get_dict, get_class_hierarchy_names, parse_into_datetime
|
||||
from .version import DEFAULT_VERSION
|
||||
|
||||
ID_REGEX_interoperability = re.compile(r"[0-9a-fA-F]{8}-"
|
||||
"[0-9a-fA-F]{4}-"
|
||||
|
@ -256,9 +256,11 @@ class ListProperty(Property):
|
|||
valid = self.contained(**item)
|
||||
|
||||
else:
|
||||
raise ValueError("Can't create a {} out of {}".format(
|
||||
self.contained._type, str(item),
|
||||
))
|
||||
raise ValueError(
|
||||
"Can't create a {} out of {}".format(
|
||||
self.contained._type, str(item),
|
||||
),
|
||||
)
|
||||
|
||||
result.append(valid)
|
||||
|
||||
|
@ -282,7 +284,7 @@ class StringProperty(Property):
|
|||
|
||||
class TypeProperty(Property):
|
||||
|
||||
def __init__(self, type, spec_version=stix2.DEFAULT_VERSION):
|
||||
def __init__(self, type, spec_version=DEFAULT_VERSION):
|
||||
_validate_type(type, spec_version)
|
||||
self.spec_version = spec_version
|
||||
super(TypeProperty, self).__init__(fixed=type)
|
||||
|
@ -290,7 +292,7 @@ class TypeProperty(Property):
|
|||
|
||||
class IDProperty(Property):
|
||||
|
||||
def __init__(self, type, spec_version=stix2.DEFAULT_VERSION):
|
||||
def __init__(self, type, spec_version=DEFAULT_VERSION):
|
||||
self.required_prefix = type + "--"
|
||||
self.spec_version = spec_version
|
||||
super(IDProperty, self).__init__()
|
||||
|
@ -390,7 +392,7 @@ class TimestampProperty(Property):
|
|||
|
||||
class DictionaryProperty(Property):
|
||||
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, **kwargs):
|
||||
def __init__(self, spec_version=DEFAULT_VERSION, **kwargs):
|
||||
self.spec_version = spec_version
|
||||
super(DictionaryProperty, self).__init__(**kwargs)
|
||||
|
||||
|
@ -479,7 +481,7 @@ class HexProperty(Property):
|
|||
|
||||
class ReferenceProperty(Property):
|
||||
|
||||
def __init__(self, valid_types=None, invalid_types=None, spec_version=stix2.DEFAULT_VERSION, **kwargs):
|
||||
def __init__(self, valid_types=None, invalid_types=None, spec_version=DEFAULT_VERSION, **kwargs):
|
||||
"""
|
||||
references sometimes must be to a specific object type
|
||||
"""
|
||||
|
@ -511,14 +513,14 @@ class ReferenceProperty(Property):
|
|||
possible_prefix = value[:value.index('--')]
|
||||
|
||||
if self.valid_types:
|
||||
ref_valid_types = enumerate_types(self.valid_types, 'v' + self.spec_version.replace(".", ""))
|
||||
ref_valid_types = enumerate_types(self.valid_types, self.spec_version)
|
||||
|
||||
if possible_prefix in ref_valid_types or self.allow_custom:
|
||||
required_prefix = possible_prefix + '--'
|
||||
else:
|
||||
raise ValueError("The type-specifying prefix '%s' for this property is not valid" % (possible_prefix))
|
||||
elif self.invalid_types:
|
||||
ref_invalid_types = enumerate_types(self.invalid_types, 'v' + self.spec_version.replace(".", ""))
|
||||
ref_invalid_types = enumerate_types(self.invalid_types, self.spec_version)
|
||||
|
||||
if possible_prefix not in ref_invalid_types:
|
||||
required_prefix = possible_prefix + '--'
|
||||
|
@ -613,7 +615,7 @@ class ObservableProperty(Property):
|
|||
"""Property for holding Cyber Observable Objects.
|
||||
"""
|
||||
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, *args, **kwargs):
|
||||
def __init__(self, spec_version=DEFAULT_VERSION, allow_custom=False, *args, **kwargs):
|
||||
self.allow_custom = allow_custom
|
||||
self.spec_version = spec_version
|
||||
super(ObservableProperty, self).__init__(*args, **kwargs)
|
||||
|
@ -648,7 +650,7 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
"""Property for representing extensions on Observable objects.
|
||||
"""
|
||||
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, enclosing_type=None, required=False):
|
||||
def __init__(self, spec_version=DEFAULT_VERSION, allow_custom=False, enclosing_type=None, required=False):
|
||||
self.allow_custom = allow_custom
|
||||
self.enclosing_type = enclosing_type
|
||||
super(ExtensionsProperty, self).__init__(spec_version=spec_version, required=required)
|
||||
|
@ -663,9 +665,7 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
except ValueError:
|
||||
raise ValueError("The extensions property must contain a dictionary")
|
||||
|
||||
v = 'v' + self.spec_version.replace('.', '')
|
||||
|
||||
specific_type_map = STIX2_OBJ_MAPS[v]['observable-extensions'].get(self.enclosing_type, {})
|
||||
specific_type_map = STIX2_OBJ_MAPS[self.spec_version]['observable-extensions'].get(self.enclosing_type, {})
|
||||
for key, subvalue in dictified.items():
|
||||
if key in specific_type_map:
|
||||
cls = specific_type_map[key]
|
||||
|
@ -690,7 +690,7 @@ class ExtensionsProperty(DictionaryProperty):
|
|||
|
||||
class STIXObjectProperty(Property):
|
||||
|
||||
def __init__(self, spec_version=stix2.DEFAULT_VERSION, allow_custom=False, interoperability=False, *args, **kwargs):
|
||||
def __init__(self, spec_version=DEFAULT_VERSION, allow_custom=False, interoperability=False, *args, **kwargs):
|
||||
self.allow_custom = allow_custom
|
||||
self.spec_version = spec_version
|
||||
self.interoperability = interoperability
|
||||
|
@ -699,8 +699,10 @@ class STIXObjectProperty(Property):
|
|||
def clean(self, value):
|
||||
# Any STIX Object (SDO, SRO, or Marking Definition) can be added to
|
||||
# a bundle with no further checks.
|
||||
if any(x in ('_DomainObject', '_RelationshipObject', 'MarkingDefinition')
|
||||
for x in get_class_hierarchy_names(value)):
|
||||
if any(
|
||||
x in ('_DomainObject', '_RelationshipObject', 'MarkingDefinition')
|
||||
for x in get_class_hierarchy_names(value)
|
||||
):
|
||||
# A simple "is this a spec version 2.1+ object" test. For now,
|
||||
# limit 2.0 bundles to 2.0 objects. It's not possible yet to
|
||||
# have validation co-constraints among properties, e.g. have
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
import re
|
||||
|
||||
from . import registry
|
||||
from .base import _DomainObject, _Observable
|
||||
from .exceptions import DuplicateRegistrationError
|
||||
from .properties import _validate_type
|
||||
from .utils import PREFIX_21_REGEX, get_class_hierarchy_names
|
||||
from .version import DEFAULT_VERSION
|
||||
|
||||
|
||||
def _register_object(new_type, version=DEFAULT_VERSION):
|
||||
"""Register a custom STIX Object type.
|
||||
|
||||
Args:
|
||||
new_type (class): A class to register in the Object map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
Raises:
|
||||
ValueError: If the class being registered wasn't created with the
|
||||
@CustomObject decorator.
|
||||
DuplicateRegistrationError: If the class has already been registered.
|
||||
|
||||
"""
|
||||
|
||||
if not issubclass(new_type, _DomainObject):
|
||||
raise ValueError(
|
||||
"'%s' must be created with the @CustomObject decorator." %
|
||||
new_type.__name__,
|
||||
)
|
||||
|
||||
properties = new_type._properties
|
||||
|
||||
if not version:
|
||||
version = DEFAULT_VERSION
|
||||
|
||||
if version == "2.1":
|
||||
for prop_name, prop in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character" % prop_name)
|
||||
|
||||
OBJ_MAP = registry.STIX2_OBJ_MAPS[version]['objects']
|
||||
if new_type._type in OBJ_MAP.keys():
|
||||
raise DuplicateRegistrationError("STIX Object", new_type._type)
|
||||
OBJ_MAP[new_type._type] = new_type
|
||||
|
||||
|
||||
def _register_marking(new_marking, version=DEFAULT_VERSION):
|
||||
"""Register a custom STIX Marking Definition type.
|
||||
|
||||
Args:
|
||||
new_marking (class): A class to register in the Marking map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
"""
|
||||
|
||||
mark_type = new_marking._type
|
||||
properties = new_marking._properties
|
||||
|
||||
if not version:
|
||||
version = DEFAULT_VERSION
|
||||
|
||||
_validate_type(mark_type, version)
|
||||
|
||||
if version == "2.1":
|
||||
for prop_name, prop_value in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
|
||||
OBJ_MAP_MARKING = registry.STIX2_OBJ_MAPS[version]['markings']
|
||||
if mark_type in OBJ_MAP_MARKING.keys():
|
||||
raise DuplicateRegistrationError("STIX Marking", mark_type)
|
||||
OBJ_MAP_MARKING[mark_type] = new_marking
|
||||
|
||||
|
||||
def _register_observable(new_observable, version=DEFAULT_VERSION):
|
||||
"""Register a custom STIX Cyber Observable type.
|
||||
|
||||
Args:
|
||||
new_observable (class): A class to register in the Observables map.
|
||||
version (str): Which STIX2 version to use. (e.g. "2.0", "2.1"). If
|
||||
None, use latest version.
|
||||
|
||||
"""
|
||||
properties = new_observable._properties
|
||||
|
||||
if not version:
|
||||
version = DEFAULT_VERSION
|
||||
|
||||
if version == "2.0":
|
||||
# If using STIX2.0, check properties ending in "_ref/s" are ObjectReferenceProperties
|
||||
for prop_name, prop in properties.items():
|
||||
if prop_name.endswith('_ref') and ('ObjectReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference property but "
|
||||
"is not an ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (
|
||||
prop_name.endswith('_refs') and (
|
||||
'ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ObjectReferenceProperty' not in get_class_hierarchy_names(prop.contained)
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
"'%s' is named like an object reference list property but "
|
||||
"is not a ListProperty containing ObjectReferenceProperty." % prop_name,
|
||||
)
|
||||
else:
|
||||
# If using STIX2.1 (or newer...), check properties ending in "_ref/s" are ReferenceProperties
|
||||
for prop_name, prop in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
elif prop_name.endswith('_ref') and ('ReferenceProperty' not in get_class_hierarchy_names(prop)):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference property but "
|
||||
"is not a ReferenceProperty." % prop_name,
|
||||
)
|
||||
elif (
|
||||
prop_name.endswith('_refs') and (
|
||||
'ListProperty' not in get_class_hierarchy_names(prop) or
|
||||
'ReferenceProperty' not in get_class_hierarchy_names(prop.contained)
|
||||
)
|
||||
):
|
||||
raise ValueError(
|
||||
"'%s' is named like a reference list property but "
|
||||
"is not a ListProperty containing ReferenceProperty." % prop_name,
|
||||
)
|
||||
|
||||
OBJ_MAP_OBSERVABLE = registry.STIX2_OBJ_MAPS[version]['observables']
|
||||
if new_observable._type in OBJ_MAP_OBSERVABLE.keys():
|
||||
raise DuplicateRegistrationError("Cyber Observable", new_observable._type)
|
||||
OBJ_MAP_OBSERVABLE[new_observable._type] = new_observable
|
||||
|
||||
|
||||
def _register_observable_extension(
|
||||
observable, new_extension, version=DEFAULT_VERSION,
|
||||
):
|
||||
"""Register a custom extension to a STIX Cyber Observable type.
|
||||
|
||||
Args:
|
||||
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").
|
||||
Defaults to the latest supported version.
|
||||
|
||||
"""
|
||||
obs_class = observable if isinstance(observable, type) else \
|
||||
type(observable)
|
||||
ext_type = new_extension._type
|
||||
properties = new_extension._properties
|
||||
|
||||
if not issubclass(obs_class, _Observable):
|
||||
raise ValueError("'observable' must be a valid Observable class!")
|
||||
|
||||
_validate_type(ext_type, version)
|
||||
|
||||
if not new_extension._properties:
|
||||
raise ValueError(
|
||||
"Invalid extension: must define at least one property: " +
|
||||
ext_type,
|
||||
)
|
||||
|
||||
if version == "2.1":
|
||||
if not ext_type.endswith('-ext'):
|
||||
raise ValueError(
|
||||
"Invalid extension type name '%s': must end with '-ext'." %
|
||||
ext_type,
|
||||
)
|
||||
|
||||
for prop_name, prop_value in properties.items():
|
||||
if not re.match(PREFIX_21_REGEX, prop_name):
|
||||
raise ValueError("Property name '%s' must begin with an alpha character." % prop_name)
|
||||
|
||||
try:
|
||||
observable_type = observable._type
|
||||
except AttributeError:
|
||||
raise ValueError(
|
||||
"Unknown observable type. Custom observables must be "
|
||||
"created with the @CustomObservable decorator.",
|
||||
)
|
||||
|
||||
OBJ_MAP_OBSERVABLE = registry.STIX2_OBJ_MAPS[version]['observables']
|
||||
EXT_MAP = registry.STIX2_OBJ_MAPS[version]['observable-extensions']
|
||||
|
||||
try:
|
||||
if ext_type in EXT_MAP[observable_type].keys():
|
||||
raise DuplicateRegistrationError("Observable Extension", ext_type)
|
||||
EXT_MAP[observable_type][ext_type] = new_extension
|
||||
except KeyError:
|
||||
if observable_type not in OBJ_MAP_OBSERVABLE:
|
||||
raise ValueError(
|
||||
"Unknown observable type '%s'. Custom observables "
|
||||
"must be created with the @CustomObservable decorator."
|
||||
% observable_type,
|
||||
)
|
||||
else:
|
||||
EXT_MAP[observable_type] = {ext_type: new_extension}
|
|
@ -0,0 +1,80 @@
|
|||
import importlib
|
||||
import pkgutil
|
||||
import re
|
||||
|
||||
# Collects information on which classes implement which STIX types, for the
|
||||
# various STIX spec versions.
|
||||
STIX2_OBJ_MAPS = {}
|
||||
|
||||
|
||||
def _stix_vid_to_version(stix_vid):
|
||||
"""
|
||||
Convert a python package name representing a STIX version in the form "vXX"
|
||||
to the dotted style used in the public APIs of this library, "X.X".
|
||||
|
||||
:param stix_vid: A package name in the form "vXX"
|
||||
:return: A STIX version in dotted style
|
||||
"""
|
||||
assert len(stix_vid) >= 3
|
||||
|
||||
stix_version = stix_vid[1] + "." + stix_vid[2:]
|
||||
return stix_version
|
||||
|
||||
|
||||
def _collect_stix2_mappings():
|
||||
"""Navigate the package once and retrieve all object mapping dicts for each
|
||||
v2X package. Includes OBJ_MAP, OBJ_MAP_OBSERVABLE, EXT_MAP."""
|
||||
if not STIX2_OBJ_MAPS:
|
||||
top_level_module = importlib.import_module('stix2')
|
||||
path = top_level_module.__path__
|
||||
prefix = str(top_level_module.__name__) + '.'
|
||||
|
||||
for module_loader, name, is_pkg in pkgutil.walk_packages(path=path, prefix=prefix):
|
||||
stix_vid = name.split('.')[1]
|
||||
if re.match(r'^stix2\.v2[0-9]$', name) and is_pkg:
|
||||
ver = _stix_vid_to_version(stix_vid)
|
||||
mod = importlib.import_module(name, str(top_level_module.__name__))
|
||||
STIX2_OBJ_MAPS[ver] = {}
|
||||
STIX2_OBJ_MAPS[ver]['objects'] = mod.OBJ_MAP
|
||||
STIX2_OBJ_MAPS[ver]['observables'] = mod.OBJ_MAP_OBSERVABLE
|
||||
STIX2_OBJ_MAPS[ver]['observable-extensions'] = mod.EXT_MAP
|
||||
elif re.match(r'^stix2\.v2[0-9]\.common$', name) and is_pkg is False:
|
||||
ver = _stix_vid_to_version(stix_vid)
|
||||
mod = importlib.import_module(name, str(top_level_module.__name__))
|
||||
STIX2_OBJ_MAPS[ver]['markings'] = mod.OBJ_MAP_MARKING
|
||||
|
||||
|
||||
def class_for_type(stix_type, stix_version, category=None):
|
||||
"""
|
||||
Get the registered class which implements a particular STIX type for a
|
||||
particular STIX version.
|
||||
|
||||
:param stix_type: A STIX type as a string
|
||||
:param stix_version: A STIX version as a string, e.g. "2.1"
|
||||
:param category: An optional "category" value, which is just used directly
|
||||
as a second key after the STIX version, and depends on how the types
|
||||
are internally categorized. This would be useful if the same STIX type
|
||||
is used to mean two different things within the same STIX version. So
|
||||
it's unlikely to be necessary. Pass None to just search all the
|
||||
categories and return the first class found.
|
||||
:return: A registered python class which implements the given STIX type, or
|
||||
None if one is not found.
|
||||
"""
|
||||
cls = None
|
||||
|
||||
cat_map = STIX2_OBJ_MAPS.get(stix_version)
|
||||
if cat_map:
|
||||
if category:
|
||||
class_map = cat_map.get(category)
|
||||
if class_map:
|
||||
cls = class_map.get(stix_type)
|
||||
else:
|
||||
cls = cat_map["objects"].get(stix_type) \
|
||||
or cat_map["observables"].get(stix_type) \
|
||||
or cat_map["markings"].get(stix_type)
|
||||
|
||||
# Left "observable-extensions" out; it has a different
|
||||
# substructure. A version->category->type lookup would result
|
||||
# in another map, not a class. So it doesn't fit the pattern.
|
||||
|
||||
return cls
|
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||
|
||||
import pytest
|
||||
|
||||
from stix2.parsing import _detect_spec_version
|
||||
from stix2.utils import detect_spec_version
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -17,7 +17,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"name": "alice",
|
||||
"identity_class": "individual",
|
||||
},
|
||||
"v20",
|
||||
"2.0",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -29,14 +29,14 @@ from stix2.parsing import _detect_spec_version
|
|||
"target_ref": "identity--ba18dde2-56d3-4a34-aa0b-fc56f5be568f",
|
||||
"relationship_type": "targets",
|
||||
},
|
||||
"v20",
|
||||
"2.0",
|
||||
),
|
||||
(
|
||||
{
|
||||
"type": "file",
|
||||
"name": "notes.txt",
|
||||
},
|
||||
"v20",
|
||||
"2.0",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -48,7 +48,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"statement": "Copyright (c) ACME Corp.",
|
||||
},
|
||||
},
|
||||
"v20",
|
||||
"2.0",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -75,7 +75,7 @@ from stix2.parsing import _detect_spec_version
|
|||
},
|
||||
],
|
||||
},
|
||||
"v20",
|
||||
"2.0",
|
||||
),
|
||||
# STIX 2.1 examples
|
||||
(
|
||||
|
@ -87,7 +87,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"modified": "2001-07-01T09:33:17.000Z",
|
||||
"name": "alice",
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -100,7 +100,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"target_ref": "identity--ba18dde2-56d3-4a34-aa0b-fc56f5be568f",
|
||||
"relationship_type": "targets",
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -109,7 +109,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"spec_version": "2.1",
|
||||
"name": "notes.txt",
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -117,7 +117,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"id": "file--5eef3404-6a94-4db3-9a1a-5684cbea0dfe",
|
||||
"name": "notes.txt",
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -131,7 +131,7 @@ from stix2.parsing import _detect_spec_version
|
|||
"tlp": "green",
|
||||
},
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -153,7 +153,7 @@ from stix2.parsing import _detect_spec_version
|
|||
},
|
||||
],
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
# Mixed spec examples
|
||||
(
|
||||
|
@ -180,7 +180,7 @@ from stix2.parsing import _detect_spec_version
|
|||
},
|
||||
],
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
(
|
||||
{
|
||||
|
@ -202,11 +202,11 @@ from stix2.parsing import _detect_spec_version
|
|||
},
|
||||
],
|
||||
},
|
||||
"v21",
|
||||
"2.1",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_spec_version_detect(obj_dict, expected_ver):
|
||||
detected_ver = _detect_spec_version(obj_dict)
|
||||
detected_ver = detect_spec_version(obj_dict)
|
||||
|
||||
assert detected_ver == expected_ver
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
import pytest
|
||||
|
||||
import stix2.utils
|
||||
|
||||
###
|
||||
# Tests using types/behaviors common to STIX 2.0 and 2.1.
|
||||
###
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"attack-pattern",
|
||||
"campaign",
|
||||
"course-of-action",
|
||||
"identity",
|
||||
"indicator",
|
||||
"intrusion-set",
|
||||
"malware",
|
||||
"observed-data",
|
||||
"report",
|
||||
"threat-actor",
|
||||
"tool",
|
||||
"vulnerability",
|
||||
],
|
||||
)
|
||||
def test_is_sdo(type_, stix_version):
|
||||
assert stix2.utils.is_sdo(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert stix2.utils.is_sdo(id_, stix_version)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SDO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"relationship",
|
||||
"sighting",
|
||||
"marking-definition",
|
||||
"bundle",
|
||||
"language-content",
|
||||
"ipv4-addr",
|
||||
"foo",
|
||||
],
|
||||
)
|
||||
def test_is_not_sdo(type_, stix_version):
|
||||
assert not stix2.utils.is_sdo(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert not stix2.utils.is_sdo(id_, stix_version)
|
||||
|
||||
d = {
|
||||
"type": type_,
|
||||
}
|
||||
assert not stix2.utils.is_sdo(d, stix_version)
|
||||
|
||||
assert not stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SDO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"artifact",
|
||||
"autonomous-system",
|
||||
"directory",
|
||||
"domain-name",
|
||||
"email-addr",
|
||||
"email-message",
|
||||
"file",
|
||||
"ipv4-addr",
|
||||
"ipv6-addr",
|
||||
"mac-addr",
|
||||
"mutex",
|
||||
"network-traffic",
|
||||
"process",
|
||||
"software",
|
||||
"url",
|
||||
"user-account",
|
||||
"windows-registry-key",
|
||||
"x509-certificate",
|
||||
],
|
||||
)
|
||||
def test_is_sco(type_, stix_version):
|
||||
assert stix2.utils.is_sco(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert stix2.utils.is_sco(id_, stix_version)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SCO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"identity",
|
||||
"sighting",
|
||||
"marking-definition",
|
||||
"bundle",
|
||||
"language-content",
|
||||
"foo",
|
||||
],
|
||||
)
|
||||
def test_is_not_sco(type_, stix_version):
|
||||
assert not stix2.utils.is_sco(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert not stix2.utils.is_sco(id_, stix_version)
|
||||
|
||||
d = {
|
||||
"type": type_,
|
||||
}
|
||||
assert not stix2.utils.is_sco(d, stix_version)
|
||||
|
||||
assert not stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SCO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"relationship",
|
||||
"sighting",
|
||||
],
|
||||
)
|
||||
def test_is_sro(type_, stix_version):
|
||||
assert stix2.utils.is_sro(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert stix2.utils.is_sro(id_, stix_version)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SRO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"identity",
|
||||
"marking-definition",
|
||||
"bundle",
|
||||
"language-content",
|
||||
"ipv4-addr",
|
||||
"foo",
|
||||
],
|
||||
)
|
||||
def test_is_not_sro(type_, stix_version):
|
||||
assert not stix2.utils.is_sro(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert not stix2.utils.is_sro(id_, stix_version)
|
||||
|
||||
d = {
|
||||
"type": type_,
|
||||
}
|
||||
assert not stix2.utils.is_sro(d, stix_version)
|
||||
|
||||
assert not stix2.utils.is_stix_type(
|
||||
type_, stix_version, stix2.utils.STIXTypeClass.SRO,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
def test_is_marking(stix_version):
|
||||
assert stix2.utils.is_marking("marking-definition", stix_version)
|
||||
|
||||
id_ = "marking-definition--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert stix2.utils.is_marking(id_, stix_version)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
"marking-definition", stix_version, "marking-definition",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"identity",
|
||||
"bundle",
|
||||
"language-content",
|
||||
"ipv4-addr",
|
||||
"foo",
|
||||
],
|
||||
)
|
||||
def test_is_not_marking(type_, stix_version):
|
||||
assert not stix2.utils.is_marking(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert not stix2.utils.is_marking(id_, stix_version)
|
||||
|
||||
d = {
|
||||
"type": type_,
|
||||
}
|
||||
assert not stix2.utils.is_marking(d, stix_version)
|
||||
|
||||
assert not stix2.utils.is_stix_type(
|
||||
type_, stix_version, "marking-definition",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"identity",
|
||||
"relationship",
|
||||
"sighting",
|
||||
"marking-definition",
|
||||
"bundle",
|
||||
"ipv4-addr",
|
||||
],
|
||||
)
|
||||
def test_is_object(type_, stix_version):
|
||||
assert stix2.utils.is_object(type_, stix_version)
|
||||
|
||||
id_ = type_ + "--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert stix2.utils.is_object(id_, stix_version)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
def test_is_not_object(stix_version):
|
||||
assert not stix2.utils.is_object("foo", stix_version)
|
||||
|
||||
id_ = "foo--a12fa04c-6586-4128-8d1a-cfe0d1c081f5"
|
||||
assert not stix2.utils.is_object(id_, stix_version)
|
||||
|
||||
d = {
|
||||
"type": "foo",
|
||||
}
|
||||
assert not stix2.utils.is_object(d, stix_version)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("stix_version", ["2.0", "2.1"])
|
||||
def test_is_stix_type(stix_version):
|
||||
|
||||
assert not stix2.utils.is_stix_type(
|
||||
"foo", stix_version, stix2.utils.STIXTypeClass.SDO, "foo",
|
||||
)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
"bundle", stix_version, "foo", "bundle",
|
||||
)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
"identity", stix_version,
|
||||
stix2.utils.STIXTypeClass.SDO,
|
||||
stix2.utils.STIXTypeClass.SRO,
|
||||
)
|
||||
|
||||
assert stix2.utils.is_stix_type(
|
||||
"software", stix_version,
|
||||
stix2.utils.STIXTypeClass.SDO,
|
||||
stix2.utils.STIXTypeClass.SCO,
|
||||
)
|
|
@ -1,7 +1,9 @@
|
|||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2 import parsing
|
||||
import stix2.parsing
|
||||
import stix2.registration
|
||||
import stix2.registry
|
||||
import stix2.v20
|
||||
|
||||
from ...exceptions import DuplicateRegistrationError, InvalidValueError
|
||||
|
@ -981,7 +983,7 @@ def test_register_custom_object():
|
|||
_type = 'awesome-object'
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
stix2.parsing._register_object(CustomObject2, version="2.0")
|
||||
stix2.registration._register_object(CustomObject2, version="2.0")
|
||||
|
||||
|
||||
def test_extension_property_location():
|
||||
|
@ -1041,10 +1043,9 @@ def test_register_custom_object_with_version():
|
|||
"id": "x-new-type-2--00000000-0000-4000-8000-000000000007",
|
||||
}
|
||||
|
||||
cust_obj_1 = parsing.dict_to_stix2(custom_obj_1, version='2.0')
|
||||
v = 'v20'
|
||||
cust_obj_1 = stix2.parsing.dict_to_stix2(custom_obj_1, version='2.0')
|
||||
|
||||
assert cust_obj_1.type in parsing.STIX2_OBJ_MAPS[v]['objects']
|
||||
assert cust_obj_1.type in stix2.registry.STIX2_OBJ_MAPS['2.0']['objects']
|
||||
# spec_version is not in STIX 2.0, and is required in 2.1, so this
|
||||
# suffices as a test for a STIX 2.0 object.
|
||||
assert "spec_version" not in cust_obj_1
|
||||
|
@ -1074,9 +1075,8 @@ class NewObservable2(object):
|
|||
|
||||
def test_register_observable_with_version():
|
||||
custom_obs = NewObservable2(property1="Test Observable")
|
||||
v = 'v20'
|
||||
|
||||
assert custom_obs.type in parsing.STIX2_OBJ_MAPS[v]['observables']
|
||||
assert custom_obs.type in stix2.registry.STIX2_OBJ_MAPS['2.0']['observables']
|
||||
|
||||
|
||||
def test_register_duplicate_observable_with_version():
|
||||
|
@ -1099,10 +1099,9 @@ def test_register_marking_with_version():
|
|||
)
|
||||
class NewObj2():
|
||||
pass
|
||||
v = 'v20'
|
||||
|
||||
no = NewObj2(property1='something')
|
||||
assert no._type in parsing.STIX2_OBJ_MAPS[v]['markings']
|
||||
assert no._type in stix2.registry.STIX2_OBJ_MAPS['2.0']['markings']
|
||||
|
||||
|
||||
def test_register_observable_extension_with_version():
|
||||
|
@ -1114,10 +1113,9 @@ def test_register_observable_extension_with_version():
|
|||
class SomeCustomExtension2:
|
||||
pass
|
||||
|
||||
v = 'v20'
|
||||
example = SomeCustomExtension2(keys='test123')
|
||||
|
||||
assert example._type in parsing.STIX2_OBJ_MAPS[v]['observable-extensions']['user-account']
|
||||
assert example._type in stix2.registry.STIX2_OBJ_MAPS['2.0']['observable-extensions']['user-account']
|
||||
|
||||
|
||||
def test_register_duplicate_observable_extension():
|
||||
|
|
|
@ -175,12 +175,14 @@ def test_memory_source_get_nonexistant_object(mem_source):
|
|||
|
||||
def test_memory_store_all_versions(mem_store):
|
||||
# Add bundle of items to sink
|
||||
mem_store.add(dict(
|
||||
id="bundle--%s" % make_id(),
|
||||
objects=STIX_OBJS2,
|
||||
spec_version="2.0",
|
||||
type="bundle",
|
||||
))
|
||||
mem_store.add(
|
||||
dict(
|
||||
id="bundle--%s" % make_id(),
|
||||
objects=STIX_OBJS2,
|
||||
spec_version="2.0",
|
||||
type="bundle",
|
||||
),
|
||||
)
|
||||
|
||||
resp = mem_store.all_versions("indicator--00000000-0000-4000-8000-000000000001")
|
||||
assert len(resp) == 3
|
||||
|
|
|
@ -39,15 +39,19 @@ def ds2():
|
|||
cam = stix2.v20.Campaign(id=CAMPAIGN_ID, **CAMPAIGN_KWARGS)
|
||||
idy = stix2.v20.Identity(id=IDENTITY_ID, **IDENTITY_KWARGS)
|
||||
ind = stix2.v20.Indicator(id=INDICATOR_ID, created_by_ref=idy.id, **INDICATOR_KWARGS)
|
||||
indv2 = ind.new_version(external_references=[{
|
||||
"source_name": "unknown",
|
||||
"url": "https://examplewebsite.com/",
|
||||
}])
|
||||
indv2 = ind.new_version(
|
||||
external_references=[{
|
||||
"source_name": "unknown",
|
||||
"url": "https://examplewebsite.com/",
|
||||
}],
|
||||
)
|
||||
mal = stix2.v20.Malware(id=MALWARE_ID, created_by_ref=idy.id, **MALWARE_KWARGS)
|
||||
malv2 = mal.new_version(external_references=[{
|
||||
"source_name": "unknown",
|
||||
"url": "https://examplewebsite2.com/",
|
||||
}])
|
||||
malv2 = mal.new_version(
|
||||
external_references=[{
|
||||
"source_name": "unknown",
|
||||
"url": "https://examplewebsite2.com/",
|
||||
}],
|
||||
)
|
||||
rel1 = stix2.v20.Relationship(ind, 'indicates', mal, id=RELATIONSHIP_IDS[0])
|
||||
rel2 = stix2.v20.Relationship(mal, 'targets', idy, id=RELATIONSHIP_IDS[1])
|
||||
rel3 = stix2.v20.Relationship(cam, 'uses', mal, id=RELATIONSHIP_IDS[2])
|
||||
|
|
|
@ -20,7 +20,8 @@ EXPECTED_INDICATOR = """{
|
|||
]
|
||||
}"""
|
||||
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join(
|
||||
"""
|
||||
type='indicator',
|
||||
id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7',
|
||||
created='2017-01-01T00:00:01.000Z',
|
||||
|
@ -28,7 +29,8 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
|||
pattern="[file:hashes.MD5 = 'd41d8cd98f00b204e9800998ecf8427e']",
|
||||
valid_from='1970-01-01T00:00:01Z',
|
||||
labels=['malicious-activity']
|
||||
""".split()) + ")"
|
||||
""".split(),
|
||||
) + ")"
|
||||
|
||||
|
||||
def test_indicator_with_all_required_properties():
|
||||
|
|
|
@ -1180,50 +1180,56 @@ def test_process_example_extensions_empty():
|
|||
|
||||
|
||||
def test_process_example_with_WindowsProcessExt_Object():
|
||||
p = stix2.v20.Process(extensions={
|
||||
"windows-process-ext": stix2.v20.WindowsProcessExt(
|
||||
aslr_enabled=True,
|
||||
dep_enabled=True,
|
||||
priority="HIGH_PRIORITY_CLASS",
|
||||
owner_sid="S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
), # noqa
|
||||
})
|
||||
p = stix2.v20.Process(
|
||||
extensions={
|
||||
"windows-process-ext": stix2.v20.WindowsProcessExt(
|
||||
aslr_enabled=True,
|
||||
dep_enabled=True,
|
||||
priority="HIGH_PRIORITY_CLASS",
|
||||
owner_sid="S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
), # noqa
|
||||
},
|
||||
)
|
||||
|
||||
assert p.extensions["windows-process-ext"].dep_enabled
|
||||
assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
|
||||
|
||||
|
||||
def test_process_example_with_WindowsServiceExt():
|
||||
p = stix2.v20.Process(extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
p = stix2.v20.Process(
|
||||
extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
||||
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
||||
|
||||
|
||||
def test_process_example_with_WindowsProcessServiceExt():
|
||||
p = stix2.v20.Process(extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
p = stix2.v20.Process(
|
||||
extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
},
|
||||
"windows-process-ext": {
|
||||
"aslr_enabled": True,
|
||||
"dep_enabled": True,
|
||||
"priority": "HIGH_PRIORITY_CLASS",
|
||||
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
},
|
||||
},
|
||||
"windows-process-ext": {
|
||||
"aslr_enabled": True,
|
||||
"dep_enabled": True,
|
||||
"priority": "HIGH_PRIORITY_CLASS",
|
||||
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
||||
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
||||
|
|
|
@ -2,8 +2,7 @@ from collections import OrderedDict
|
|||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2 import exceptions, parsing
|
||||
from stix2 import DEFAULT_VERSION, exceptions, parsing, registration, registry
|
||||
|
||||
BUNDLE = {
|
||||
"type": "bundle",
|
||||
|
@ -59,7 +58,7 @@ def test_parse_observable_with_version():
|
|||
assert v in str(obs_obj.__class__)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="The default version is no longer 2.0", condition=stix2.DEFAULT_VERSION != "2.0")
|
||||
@pytest.mark.xfail(reason="The default version is no longer 2.0", condition=DEFAULT_VERSION != "2.0")
|
||||
def test_parse_observable_with_no_version():
|
||||
observable = {"type": "file", "name": "foo.exe"}
|
||||
obs_obj = parsing.parse_observable(observable)
|
||||
|
@ -73,8 +72,7 @@ def test_register_marking_with_version():
|
|||
_type = 'x-new-marking1'
|
||||
_properties = OrderedDict()
|
||||
|
||||
parsing._register_marking(NewMarking1, version='2.0')
|
||||
v = 'v20'
|
||||
registration._register_marking(NewMarking1, version='2.0')
|
||||
|
||||
assert NewMarking1._type in parsing.STIX2_OBJ_MAPS[v]['markings']
|
||||
assert v in str(parsing.STIX2_OBJ_MAPS[v]['markings'][NewMarking1._type])
|
||||
assert NewMarking1._type in registry.STIX2_OBJ_MAPS['2.0']['markings']
|
||||
assert 'v20' in str(registry.STIX2_OBJ_MAPS['2.0']['markings'][NewMarking1._type])
|
||||
|
|
|
@ -306,10 +306,12 @@ def test_multiple_qualifiers():
|
|||
|
||||
|
||||
def test_set_op():
|
||||
exp = stix2.ObservationExpression(stix2.IsSubsetComparisonExpression(
|
||||
"network-traffic:dst_ref.value",
|
||||
"2001:0db8:dead:beef:0000:0000:0000:0000/64",
|
||||
))
|
||||
exp = stix2.ObservationExpression(
|
||||
stix2.IsSubsetComparisonExpression(
|
||||
"network-traffic:dst_ref.value",
|
||||
"2001:0db8:dead:beef:0000:0000:0000:0000/64",
|
||||
),
|
||||
)
|
||||
assert str(exp) == "[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']"
|
||||
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ def test_parse_datetime_invalid(ts):
|
|||
{"a": 1},
|
||||
'{"a": 1}',
|
||||
StringIO(u'{"a": 1}'),
|
||||
[("a", 1,)],
|
||||
[("a", 1)],
|
||||
],
|
||||
)
|
||||
def test_get_dict(data):
|
||||
|
@ -237,3 +237,146 @@ def test_find_property_index(object, tuple_to_find, expected_index):
|
|||
)
|
||||
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"attack-pattern",
|
||||
"campaign",
|
||||
"course-of-action",
|
||||
"identity",
|
||||
"indicator",
|
||||
"intrusion-set",
|
||||
"malware",
|
||||
"observed-data",
|
||||
"report",
|
||||
"threat-actor",
|
||||
"tool",
|
||||
"vulnerability",
|
||||
],
|
||||
)
|
||||
def test_is_sdo_dict(type_):
|
||||
d = {
|
||||
"type": type_,
|
||||
}
|
||||
assert stix2.utils.is_sdo(d, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "software"},
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "relationship"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
{"type": "foo"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sdo_dict(dict_):
|
||||
assert not stix2.utils.is_sdo(dict_, "2.0")
|
||||
|
||||
|
||||
def test_is_sco_dict():
|
||||
d = {
|
||||
"type": "file",
|
||||
}
|
||||
|
||||
assert stix2.utils.is_sco(d, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity"},
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "relationship"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
{"type": "foo"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sco_dict(dict_):
|
||||
assert not stix2.utils.is_sco(dict_, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "relationship"},
|
||||
{"type": "sighting"},
|
||||
],
|
||||
)
|
||||
def test_is_sro_dict(dict_):
|
||||
assert stix2.utils.is_sro(dict_, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "identity"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "sighting", "spec_version": "2.1"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
{"type": "foo"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sro_dict(dict_):
|
||||
assert not stix2.utils.is_sro(dict_, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition"},
|
||||
{
|
||||
"type": "bundle",
|
||||
"id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
|
||||
"spec_version": "2.0",
|
||||
"objects": [
|
||||
{"type": "identity"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition"},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_is_object_dict(dict_):
|
||||
assert stix2.utils.is_object(dict_, "2.0")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "sighting", "spec_version": "2.1"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
{"type": "foo"},
|
||||
],
|
||||
)
|
||||
def test_is_not_object_dict(dict_):
|
||||
assert not stix2.utils.is_object(dict_, "2.0")
|
||||
|
|
|
@ -46,10 +46,12 @@ def test_making_new_version_with_embedded_object():
|
|||
**CAMPAIGN_MORE_KWARGS
|
||||
)
|
||||
|
||||
campaign_v2 = campaign_v1.new_version(external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-164",
|
||||
}])
|
||||
campaign_v2 = campaign_v1.new_version(
|
||||
external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-164",
|
||||
}],
|
||||
)
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.created_by_ref == campaign_v2.created_by_ref
|
||||
|
@ -237,8 +239,10 @@ def test_remove_custom_stix_property():
|
|||
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") <
|
||||
stix2.utils.parse_into_datetime(mal_nc["modified"], precision="millisecond"))
|
||||
assert (
|
||||
stix2.utils.parse_into_datetime(mal["modified"], precision="millisecond") <
|
||||
stix2.utils.parse_into_datetime(mal_nc["modified"], precision="millisecond")
|
||||
)
|
||||
|
||||
|
||||
def test_remove_custom_stix_object():
|
||||
|
|
|
@ -4,6 +4,8 @@ import pytest
|
|||
|
||||
import stix2
|
||||
import stix2.base
|
||||
import stix2.registration
|
||||
import stix2.registry
|
||||
import stix2.v21
|
||||
|
||||
from ...exceptions import DuplicateRegistrationError, InvalidValueError
|
||||
|
@ -1199,7 +1201,7 @@ def test_register_custom_object():
|
|||
_type = 'awesome-object'
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
stix2.parsing._register_object(CustomObject2, version="2.1")
|
||||
stix2.registration._register_object(CustomObject2, version="2.1")
|
||||
assert '@CustomObject decorator' in str(excinfo)
|
||||
|
||||
|
||||
|
@ -1263,9 +1265,8 @@ def test_register_custom_object_with_version():
|
|||
}
|
||||
|
||||
cust_obj_1 = stix2.parsing.dict_to_stix2(custom_obj_1, version='2.1')
|
||||
v = 'v21'
|
||||
|
||||
assert cust_obj_1.type in stix2.parsing.STIX2_OBJ_MAPS[v]['objects']
|
||||
assert cust_obj_1.type in stix2.registry.STIX2_OBJ_MAPS['2.1']['objects']
|
||||
assert cust_obj_1.spec_version == "2.1"
|
||||
|
||||
|
||||
|
@ -1293,9 +1294,8 @@ class NewObservable3(object):
|
|||
|
||||
def test_register_observable():
|
||||
custom_obs = NewObservable3(property1="Test Observable")
|
||||
v = 'v21'
|
||||
|
||||
assert custom_obs.type in stix2.parsing.STIX2_OBJ_MAPS[v]['observables']
|
||||
assert custom_obs.type in stix2.registry.STIX2_OBJ_MAPS['2.1']['observables']
|
||||
|
||||
|
||||
def test_register_duplicate_observable():
|
||||
|
@ -1321,10 +1321,9 @@ def test_register_observable_custom_extension():
|
|||
pass
|
||||
|
||||
example = NewExtension2(property1="Hi there")
|
||||
v = 'v21'
|
||||
|
||||
assert 'domain-name' in stix2.parsing.STIX2_OBJ_MAPS[v]['observables']
|
||||
assert example._type in stix2.parsing.STIX2_OBJ_MAPS[v]['observable-extensions']['domain-name']
|
||||
assert 'domain-name' in stix2.registry.STIX2_OBJ_MAPS['2.1']['observables']
|
||||
assert example._type in stix2.registry.STIX2_OBJ_MAPS['2.1']['observable-extensions']['domain-name']
|
||||
|
||||
|
||||
def test_register_duplicate_observable_extension():
|
||||
|
|
|
@ -191,11 +191,13 @@ def test_memory_source_get_nonexistant_object(mem_source):
|
|||
|
||||
def test_memory_store_all_versions(mem_store):
|
||||
# Add bundle of items to sink
|
||||
mem_store.add(dict(
|
||||
id="bundle--%s" % make_id(),
|
||||
objects=STIX_OBJS2,
|
||||
type="bundle",
|
||||
))
|
||||
mem_store.add(
|
||||
dict(
|
||||
id="bundle--%s" % make_id(),
|
||||
objects=STIX_OBJS2,
|
||||
type="bundle",
|
||||
),
|
||||
)
|
||||
|
||||
resp = mem_store.all_versions("indicator--00000000-0000-4000-8000-000000000001")
|
||||
assert len(resp) == 3
|
||||
|
|
|
@ -11,9 +11,9 @@ import stix2.exceptions
|
|||
from .constants import (
|
||||
ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
|
||||
FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
|
||||
LOCATION_ID, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS, REPORT_ID,
|
||||
REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID, TOOL_KWARGS,
|
||||
VULNERABILITY_ID, VULNERABILITY_KWARGS,
|
||||
LOCATION_ID, LOCATION_KWARGS, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS,
|
||||
REPORT_ID, REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID,
|
||||
TOOL_KWARGS, VULNERABILITY_ID, VULNERABILITY_KWARGS,
|
||||
)
|
||||
|
||||
FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data")
|
||||
|
@ -495,26 +495,34 @@ def test_semantic_equivalence_on_same_indicator():
|
|||
|
||||
|
||||
def test_semantic_equivalence_on_same_location1():
|
||||
LOCATION_KWARGS = dict(latitude=45, longitude=179)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
location_kwargs = dict(latitude=45, longitude=179)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
|
||||
env = stix2.Environment().semantically_equivalent(loc1, loc2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_location2():
|
||||
LOCATION_KWARGS = dict(
|
||||
location_kwargs = dict(
|
||||
latitude=38.889,
|
||||
longitude=-77.023,
|
||||
region="northern-america",
|
||||
country="us",
|
||||
)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
|
||||
env = stix2.Environment().semantically_equivalent(loc1, loc2)
|
||||
assert round(env) == 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_location_with_no_latlong():
|
||||
loc_kwargs = dict(country="US", administrative_area="US-DC")
|
||||
loc1 = stix2.v21.Location(id=LOCATION_ID, **LOCATION_KWARGS)
|
||||
loc2 = stix2.v21.Location(id=LOCATION_ID, **loc_kwargs)
|
||||
env = stix2.Environment().semantically_equivalent(loc1, loc2)
|
||||
assert round(env) != 100
|
||||
|
||||
|
||||
def test_semantic_equivalence_on_same_malware():
|
||||
malw1 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
malw2 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
|
||||
|
|
|
@ -20,7 +20,8 @@ EXPECTED_INDICATOR = """{
|
|||
"valid_from": "1970-01-01T00:00:01Z"
|
||||
}"""
|
||||
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
||||
EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join(
|
||||
"""
|
||||
type='indicator',
|
||||
spec_version='2.1',
|
||||
id='indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7',
|
||||
|
@ -30,7 +31,8 @@ EXPECTED_INDICATOR_REPR = "Indicator(" + " ".join("""
|
|||
pattern_type='stix',
|
||||
pattern_version='2.1',
|
||||
valid_from='1970-01-01T00:00:01Z'
|
||||
""".split()) + ")"
|
||||
""".split(),
|
||||
) + ")"
|
||||
|
||||
|
||||
def test_indicator_with_all_required_properties():
|
||||
|
|
|
@ -19,14 +19,16 @@ EXPECTED_LOCATION_1 = """{
|
|||
"longitude": 2.3522
|
||||
}"""
|
||||
|
||||
EXPECTED_LOCATION_1_REPR = "Location(" + " ".join("""
|
||||
EXPECTED_LOCATION_1_REPR = "Location(" + " ".join(
|
||||
"""
|
||||
type='location',
|
||||
spec_version='2.1',
|
||||
id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
|
||||
created='2016-04-06T20:03:00.000Z',
|
||||
modified='2016-04-06T20:03:00.000Z',
|
||||
latitude=48.8566,
|
||||
longitude=2.3522""".split()) + ")"
|
||||
longitude=2.3522""".split(),
|
||||
) + ")"
|
||||
|
||||
EXPECTED_LOCATION_2 = """{
|
||||
"type": "location",
|
||||
|
@ -38,13 +40,15 @@ EXPECTED_LOCATION_2 = """{
|
|||
}
|
||||
"""
|
||||
|
||||
EXPECTED_LOCATION_2_REPR = "Location(" + " ".join("""
|
||||
EXPECTED_LOCATION_2_REPR = "Location(" + " ".join(
|
||||
"""
|
||||
type='location',
|
||||
spec_version='2.1',
|
||||
id='location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64',
|
||||
created='2016-04-06T20:03:00.000Z',
|
||||
modified='2016-04-06T20:03:00.000Z',
|
||||
region='north-america'""".split()) + ")"
|
||||
region='north-america'""".split(),
|
||||
) + ")"
|
||||
|
||||
|
||||
def test_location_with_some_required_properties():
|
||||
|
|
|
@ -10,13 +10,9 @@ MALWARE_ANALYSIS_JSON = """{
|
|||
"type": "malware-analysis",
|
||||
"spec_version": "2.1",
|
||||
"id": "malware-analysis--f8afc020-f92f-4906-a971-88ee5882eb46",
|
||||
"created_by_ref": "identity--e0353ed3-991e-4f71-a332-114c2f10b84f",
|
||||
"created": "2017-11-28T09:44:58.418Z",
|
||||
"modified": "2017-12-31T21:27:49.754Z",
|
||||
"created_by_ref": "identity--e0353ed3-991e-4f71-a332-114c2f10b84f",
|
||||
"labels": [
|
||||
"label1",
|
||||
"label2"
|
||||
],
|
||||
"product": "Acme Malware Analyzer",
|
||||
"version": "2.5",
|
||||
"host_vm_ref": "software--1bda7336-fe67-469f-a8ca-ab6268b0449b",
|
||||
|
@ -40,7 +36,11 @@ MALWARE_ANALYSIS_JSON = """{
|
|||
"file--fc27e371-6c88-4c5c-868a-4dda0e60b167",
|
||||
"url--6f7a74cd-8eb2-4b88-a4da-aa878e50ac2e"
|
||||
],
|
||||
"sample_ref": "email-addr--499a32d7-74c1-4276-ace9-725ac933e243"
|
||||
"sample_ref": "email-addr--499a32d7-74c1-4276-ace9-725ac933e243",
|
||||
"labels": [
|
||||
"label1",
|
||||
"label2"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
||||
|
|
|
@ -496,12 +496,14 @@ def test_parse_email_message_not_multipart(data):
|
|||
def test_parse_file_archive(data):
|
||||
odata_str = OBJECTS_REGEX.sub('"objects": { %s }' % data, EXPECTED)
|
||||
odata = stix2.parse(odata_str, version="2.1")
|
||||
assert all(x in odata.objects["3"].extensions['archive-ext'].contains_refs
|
||||
for x in [
|
||||
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
||||
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
||||
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
||||
])
|
||||
assert all(
|
||||
x in odata.objects["3"].extensions['archive-ext'].contains_refs
|
||||
for x in [
|
||||
"file--ecd47d73-15e4-5250-afda-ef8897b22340",
|
||||
"file--65f2873d-38c2-56b4-bfa5-e3ef21e8a3c3",
|
||||
"file--ef2d6dca-ec7d-5ab7-8dd9-ec9c0dee0eac",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -904,14 +906,14 @@ def test_file_with_archive_ext_object():
|
|||
f_obj = stix2.v21.File(
|
||||
name="foo", extensions={
|
||||
"archive-ext": {
|
||||
"contains_refs": [ad, ],
|
||||
"contains_refs": [ad],
|
||||
},
|
||||
},
|
||||
)
|
||||
f_ref = stix2.v21.File(
|
||||
name="foo", extensions={
|
||||
"archive-ext": {
|
||||
"contains_refs": [ad.id, ],
|
||||
"contains_refs": [ad.id],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -1229,9 +1231,11 @@ def test_process_example_empty_error():
|
|||
|
||||
def test_process_example_empty_with_extensions():
|
||||
with pytest.raises(stix2.exceptions.InvalidValueError) as excinfo:
|
||||
stix2.v21.Process(extensions={
|
||||
"windows-process-ext": {},
|
||||
})
|
||||
stix2.v21.Process(
|
||||
extensions={
|
||||
"windows-process-ext": {},
|
||||
},
|
||||
)
|
||||
|
||||
assert excinfo.value.cls == stix2.v21.Process
|
||||
|
||||
|
@ -1276,50 +1280,56 @@ def test_process_example_extensions_empty():
|
|||
|
||||
|
||||
def test_process_example_with_WindowsProcessExt_Object():
|
||||
p = stix2.v21.Process(extensions={
|
||||
"windows-process-ext": stix2.v21.WindowsProcessExt(
|
||||
aslr_enabled=True,
|
||||
dep_enabled=True,
|
||||
priority="HIGH_PRIORITY_CLASS",
|
||||
owner_sid="S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
), # noqa
|
||||
})
|
||||
p = stix2.v21.Process(
|
||||
extensions={
|
||||
"windows-process-ext": stix2.v21.WindowsProcessExt(
|
||||
aslr_enabled=True,
|
||||
dep_enabled=True,
|
||||
priority="HIGH_PRIORITY_CLASS",
|
||||
owner_sid="S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
), # noqa
|
||||
},
|
||||
)
|
||||
|
||||
assert p.extensions["windows-process-ext"].dep_enabled
|
||||
assert p.extensions["windows-process-ext"].owner_sid == "S-1-5-21-186985262-1144665072-74031268-1309"
|
||||
|
||||
|
||||
def test_process_example_with_WindowsServiceExt():
|
||||
p = stix2.v21.Process(extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
p = stix2.v21.Process(
|
||||
extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
||||
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
||||
|
||||
|
||||
def test_process_example_with_WindowsProcessServiceExt():
|
||||
p = stix2.v21.Process(extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
p = stix2.v21.Process(
|
||||
extensions={
|
||||
"windows-service-ext": {
|
||||
"service_name": "sirvizio",
|
||||
"display_name": "Sirvizio",
|
||||
"start_type": "SERVICE_AUTO_START",
|
||||
"service_type": "SERVICE_WIN32_OWN_PROCESS",
|
||||
"service_status": "SERVICE_RUNNING",
|
||||
},
|
||||
"windows-process-ext": {
|
||||
"aslr_enabled": True,
|
||||
"dep_enabled": True,
|
||||
"priority": "HIGH_PRIORITY_CLASS",
|
||||
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
},
|
||||
},
|
||||
"windows-process-ext": {
|
||||
"aslr_enabled": True,
|
||||
"dep_enabled": True,
|
||||
"priority": "HIGH_PRIORITY_CLASS",
|
||||
"owner_sid": "S-1-5-21-186985262-1144665072-74031268-1309",
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
assert p.extensions["windows-service-ext"].service_name == "sirvizio"
|
||||
assert p.extensions["windows-service-ext"].service_type == "SERVICE_WIN32_OWN_PROCESS"
|
||||
|
|
|
@ -2,8 +2,7 @@ from collections import OrderedDict
|
|||
|
||||
import pytest
|
||||
|
||||
import stix2
|
||||
from stix2 import exceptions, parsing
|
||||
from stix2 import DEFAULT_VERSION, exceptions, parsing, registration, registry
|
||||
|
||||
BUNDLE = {
|
||||
"type": "bundle",
|
||||
|
@ -64,7 +63,7 @@ def test_parse_observable_with_version():
|
|||
assert v in str(obs_obj.__class__)
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="The default version is not 2.1", condition=stix2.DEFAULT_VERSION != "2.1")
|
||||
@pytest.mark.xfail(reason="The default version is not 2.1", condition=DEFAULT_VERSION != "2.1")
|
||||
def test_parse_observable_with_no_version():
|
||||
observable = {"type": "file", "name": "foo.exe", "spec_version": "2.1"}
|
||||
obs_obj = parsing.parse_observable(observable)
|
||||
|
@ -78,22 +77,20 @@ def test_register_marking_with_version():
|
|||
_type = 'x-new-marking1'
|
||||
_properties = OrderedDict()
|
||||
|
||||
parsing._register_marking(NewMarking1, version='2.1')
|
||||
v = 'v21'
|
||||
registration._register_marking(NewMarking1, version='2.1')
|
||||
|
||||
assert NewMarking1._type in parsing.STIX2_OBJ_MAPS[v]['markings']
|
||||
assert v in str(parsing.STIX2_OBJ_MAPS[v]['markings'][NewMarking1._type])
|
||||
assert NewMarking1._type in registry.STIX2_OBJ_MAPS['2.1']['markings']
|
||||
assert 'v21' in str(registry.STIX2_OBJ_MAPS['2.1']['markings'][NewMarking1._type])
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="The default version is not 2.1", condition=stix2.DEFAULT_VERSION != "2.1")
|
||||
@pytest.mark.xfail(reason="The default version is not 2.1", condition=DEFAULT_VERSION != "2.1")
|
||||
def test_register_marking_with_no_version():
|
||||
# Uses default version (2.1 in this case)
|
||||
class NewMarking2:
|
||||
_type = 'x-new-marking2'
|
||||
_properties = OrderedDict()
|
||||
|
||||
parsing._register_marking(NewMarking2)
|
||||
v = 'v21'
|
||||
registration._register_marking(NewMarking2)
|
||||
|
||||
assert NewMarking2._type in parsing.STIX2_OBJ_MAPS[v]['markings']
|
||||
assert v in str(parsing.STIX2_OBJ_MAPS[v]['markings'][NewMarking2._type])
|
||||
assert NewMarking2._type in registry.STIX2_OBJ_MAPS['2.1']['markings']
|
||||
assert 'v21' in str(registry.STIX2_OBJ_MAPS['2.1']['markings'][NewMarking2._type])
|
||||
|
|
|
@ -444,10 +444,12 @@ def test_multiple_qualifiers():
|
|||
|
||||
|
||||
def test_set_op():
|
||||
exp = stix2.ObservationExpression(stix2.IsSubsetComparisonExpression(
|
||||
"network-traffic:dst_ref.value",
|
||||
"2001:0db8:dead:beef:0000:0000:0000:0000/64",
|
||||
))
|
||||
exp = stix2.ObservationExpression(
|
||||
stix2.IsSubsetComparisonExpression(
|
||||
"network-traffic:dst_ref.value",
|
||||
"2001:0db8:dead:beef:0000:0000:0000:0000/64",
|
||||
),
|
||||
)
|
||||
assert str(exp) == "[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']"
|
||||
|
||||
|
||||
|
@ -712,12 +714,12 @@ def test_parsing_boolean():
|
|||
|
||||
|
||||
def test_parsing_mixed_boolean_expression_1():
|
||||
patt_obj = create_pattern_object("[a:b = 1 AND a:b = 2 OR a:b = 3]",)
|
||||
patt_obj = create_pattern_object("[a:b = 1 AND a:b = 2 OR a:b = 3]")
|
||||
assert str(patt_obj) == "[a:b = 1 AND a:b = 2 OR a:b = 3]"
|
||||
|
||||
|
||||
def test_parsing_mixed_boolean_expression_2():
|
||||
patt_obj = create_pattern_object("[a:b = 1 OR a:b = 2 AND a:b = 3]",)
|
||||
patt_obj = create_pattern_object("[a:b = 1 OR a:b = 2 AND a:b = 3]")
|
||||
assert str(patt_obj) == "[a:b = 1 OR a:b = 2 AND a:b = 3]"
|
||||
|
||||
|
||||
|
|
|
@ -5,7 +5,9 @@ import pytz
|
|||
|
||||
import stix2
|
||||
|
||||
from .constants import IDENTITY_ID, INDICATOR_ID, SIGHTING_ID, SIGHTING_KWARGS
|
||||
from .constants import (
|
||||
IDENTITY_ID, INDICATOR_ID, LOCATION_ID, SIGHTING_ID, SIGHTING_KWARGS,
|
||||
)
|
||||
|
||||
EXPECTED_SIGHTING = """{
|
||||
"type": "sighting",
|
||||
|
@ -15,7 +17,8 @@ EXPECTED_SIGHTING = """{
|
|||
"modified": "2016-04-06T20:06:37.000Z",
|
||||
"sighting_of_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7",
|
||||
"where_sighted_refs": [
|
||||
"identity--311b2d2d-f010-4473-83ec-1edf84858f4c"
|
||||
"identity--311b2d2d-f010-4473-83ec-1edf84858f4c",
|
||||
"location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64"
|
||||
]
|
||||
}"""
|
||||
|
||||
|
@ -41,7 +44,7 @@ def test_sighting_all_required_properties():
|
|||
created=now,
|
||||
modified=now,
|
||||
sighting_of_ref=INDICATOR_ID,
|
||||
where_sighted_refs=[IDENTITY_ID],
|
||||
where_sighted_refs=[IDENTITY_ID, LOCATION_ID],
|
||||
)
|
||||
assert str(s) == EXPECTED_SIGHTING
|
||||
|
||||
|
@ -101,6 +104,7 @@ def test_create_sighting_from_objects_rather_than_ids(malware): # noqa: F811
|
|||
"type": "sighting",
|
||||
"where_sighted_refs": [
|
||||
IDENTITY_ID,
|
||||
LOCATION_ID,
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -114,4 +118,4 @@ def test_parse_sighting(data):
|
|||
assert sighting.created == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert sighting.modified == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
|
||||
assert sighting.sighting_of_ref == INDICATOR_ID
|
||||
assert sighting.where_sighted_refs == [IDENTITY_ID]
|
||||
assert sighting.where_sighted_refs == [IDENTITY_ID, LOCATION_ID]
|
||||
|
|
|
@ -71,7 +71,7 @@ def test_parse_datetime_invalid(ts):
|
|||
{"a": 1},
|
||||
'{"a": 1}',
|
||||
StringIO(u'{"a": 1}'),
|
||||
[("a", 1,)],
|
||||
[("a", 1)],
|
||||
],
|
||||
)
|
||||
def test_get_dict(data):
|
||||
|
@ -241,3 +241,153 @@ def test_find_property_index(object, tuple_to_find, expected_index):
|
|||
)
|
||||
def test_iterate_over_values(dict_value, tuple_to_find, expected_index):
|
||||
assert stix2.serialization._find_property_in_seq(dict_value.values(), *tuple_to_find) == expected_index
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"type_", [
|
||||
"attack-pattern",
|
||||
"campaign",
|
||||
"course-of-action",
|
||||
"identity",
|
||||
"indicator",
|
||||
"intrusion-set",
|
||||
"malware",
|
||||
"observed-data",
|
||||
"report",
|
||||
"threat-actor",
|
||||
"tool",
|
||||
"vulnerability",
|
||||
|
||||
# New in 2.1
|
||||
"grouping",
|
||||
"infrastructure",
|
||||
"location",
|
||||
"malware-analysis",
|
||||
"note",
|
||||
"opinion",
|
||||
],
|
||||
)
|
||||
def test_is_sdo_dict(type_):
|
||||
d = {
|
||||
"type": type_,
|
||||
"spec_version": "2.1",
|
||||
}
|
||||
assert stix2.utils.is_sdo(d, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "software"},
|
||||
{"type": "identity"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "relationship"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sdo_dict(dict_):
|
||||
assert not stix2.utils.is_sdo(dict_, "2.1")
|
||||
|
||||
|
||||
def test_is_sco_dict():
|
||||
d = {
|
||||
"type": "file",
|
||||
"spec_version": "2.1",
|
||||
}
|
||||
|
||||
assert stix2.utils.is_sco(d, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity"},
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "relationship"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sco_dict(dict_):
|
||||
assert not stix2.utils.is_sco(dict_, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "relationship", "spec_version": "2.1"},
|
||||
{"type": "sighting", "spec_version": "2.1"},
|
||||
],
|
||||
)
|
||||
def test_is_sro_dict(dict_):
|
||||
assert stix2.utils.is_sro(dict_, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "identity"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle", "spec_version": "2.1"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship"},
|
||||
{"type": "sighting"},
|
||||
{"type": "foo", "spec_version": "2.1"},
|
||||
],
|
||||
)
|
||||
def test_is_not_sro_dict(dict_):
|
||||
assert not stix2.utils.is_sro(dict_, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
{
|
||||
"type": "bundle",
|
||||
"id": "bundle--8f431680-6278-4767-ba43-5edb682d7086",
|
||||
"objects": [
|
||||
{"type": "identity", "spec_version": "2.1"},
|
||||
{"type": "software", "spec_version": "2.1"},
|
||||
{"type": "marking-definition", "spec_version": "2.1"},
|
||||
{"type": "language-content", "spec_version": "2.1"},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
def test_is_object_dict(dict_):
|
||||
assert stix2.utils.is_object(dict_, "2.1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dict_", [
|
||||
{"type": "identity"},
|
||||
{"type": "software"},
|
||||
{"type": "marking-definition"},
|
||||
{"type": "bundle"},
|
||||
{"type": "language-content"},
|
||||
{"type": "relationship"},
|
||||
{"type": "sighting"},
|
||||
{"type": "foo"},
|
||||
],
|
||||
)
|
||||
def test_is_not_object_dict(dict_):
|
||||
assert not stix2.utils.is_object(dict_, "2.1")
|
||||
|
|
|
@ -50,10 +50,12 @@ def test_making_new_version_with_embedded_object():
|
|||
**CAMPAIGN_MORE_KWARGS
|
||||
)
|
||||
|
||||
campaign_v2 = campaign_v1.new_version(external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-164",
|
||||
}])
|
||||
campaign_v2 = campaign_v1.new_version(
|
||||
external_references=[{
|
||||
"source_name": "capec",
|
||||
"external_id": "CAPEC-164",
|
||||
}],
|
||||
)
|
||||
|
||||
assert campaign_v1.id == campaign_v2.id
|
||||
assert campaign_v1.spec_version == campaign_v2.spec_version
|
||||
|
@ -344,6 +346,38 @@ def test_version_sco_with_custom():
|
|||
assert revoked_obj.revoked
|
||||
|
||||
|
||||
def test_version_sco_id_contributing_properties():
|
||||
file_sco_obj = stix2.v21.File(
|
||||
name="data.txt",
|
||||
created="1973-11-23T02:31:37Z",
|
||||
modified="1991-05-13T19:24:57Z",
|
||||
revoked=False,
|
||||
allow_custom=True,
|
||||
)
|
||||
|
||||
with pytest.raises(stix2.exceptions.UnmodifiablePropertyError) as e:
|
||||
stix2.versioning.new_version(file_sco_obj, name="foo.dat")
|
||||
|
||||
assert e.value.unchangable_properties == {"name"}
|
||||
|
||||
|
||||
def test_version_sco_id_contributing_properties_dict():
|
||||
file_sco_dict = {
|
||||
"type": "file",
|
||||
"id": "file--c27c572c-2e17-5ce1-817e-67bb97629a56",
|
||||
"spec_version": "2.1",
|
||||
"name": "data.txt",
|
||||
"created": "1973-11-23T02:31:37Z",
|
||||
"modified": "1991-05-13T19:24:57Z",
|
||||
"revoked": False,
|
||||
}
|
||||
|
||||
with pytest.raises(stix2.exceptions.UnmodifiablePropertyError) as e:
|
||||
stix2.versioning.new_version(file_sco_dict, name="foo.dat")
|
||||
|
||||
assert e.value.unchangable_properties == {"name"}
|
||||
|
||||
|
||||
def test_version_disable_custom():
|
||||
m = stix2.v21.Malware(
|
||||
name="foo", description="Steals your identity!", is_family=False,
|
||||
|
|
274
stix2/utils.py
274
stix2/utils.py
|
@ -1,5 +1,6 @@
|
|||
"""Utility functions and classes for the STIX2 library."""
|
||||
|
||||
import collections.abc
|
||||
import datetime as dt
|
||||
import enum
|
||||
import json
|
||||
|
@ -8,7 +9,8 @@ import re
|
|||
import pytz
|
||||
import six
|
||||
|
||||
import stix2
|
||||
import stix2.registry as mappings
|
||||
import stix2.version
|
||||
|
||||
# Sentinel value for properties that should be set to the current time.
|
||||
# We can't use the standard 'default' approach, since if there are multiple
|
||||
|
@ -71,9 +73,11 @@ def _to_enum(value, enum_type, enum_default=None):
|
|||
elif isinstance(value, six.string_types):
|
||||
value = enum_type[value.upper()]
|
||||
else:
|
||||
raise TypeError("Not a valid {}: {}".format(
|
||||
enum_type.__name__, value,
|
||||
))
|
||||
raise TypeError(
|
||||
"Not a valid {}: {}".format(
|
||||
enum_type.__name__, value,
|
||||
),
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
@ -311,18 +315,262 @@ def get_type_from_id(stix_id):
|
|||
return stix_id.split('--', 1)[0]
|
||||
|
||||
|
||||
def is_marking(obj_or_id):
|
||||
"""Determines whether the given object or object ID is/is for a marking
|
||||
definition.
|
||||
def detect_spec_version(stix_dict):
|
||||
"""
|
||||
Given a dict representing a STIX object, try to detect what spec version
|
||||
it is likely to comply with.
|
||||
|
||||
:param obj_or_id: A STIX object or object ID as a string.
|
||||
:return: True if a marking definition, False otherwise.
|
||||
:param stix_dict: A dict with some STIX content. Must at least have a
|
||||
"type" property.
|
||||
:return: A STIX version in "X.Y" format
|
||||
"""
|
||||
|
||||
if isinstance(obj_or_id, (stix2.base._STIXBase, dict)):
|
||||
result = obj_or_id["type"] == "marking-definition"
|
||||
obj_type = stix_dict["type"]
|
||||
|
||||
if 'spec_version' in stix_dict:
|
||||
# For STIX 2.0, applies to bundles only.
|
||||
# For STIX 2.1+, applies to SCOs, SDOs, SROs, and markings only.
|
||||
v = stix_dict['spec_version']
|
||||
elif "id" not in stix_dict:
|
||||
# Only 2.0 SCOs don't have ID properties
|
||||
v = "2.0"
|
||||
elif obj_type == 'bundle':
|
||||
# Bundle without a spec_version property: must be 2.1. But to
|
||||
# future-proof, use max version over all contained SCOs, with 2.1
|
||||
# minimum.
|
||||
v = max(
|
||||
"2.1",
|
||||
max(
|
||||
detect_spec_version(obj) for obj in stix_dict["objects"]
|
||||
),
|
||||
)
|
||||
elif obj_type in mappings.STIX2_OBJ_MAPS["2.1"]["observables"]:
|
||||
# Non-bundle object with an ID and without spec_version. Could be a
|
||||
# 2.1 SCO or 2.0 SDO/SRO/marking. Check for 2.1 SCO...
|
||||
v = "2.1"
|
||||
else:
|
||||
# it's a string ID
|
||||
result = obj_or_id.startswith("marking-definition--")
|
||||
# Not a 2.1 SCO; must be a 2.0 object.
|
||||
v = "2.0"
|
||||
|
||||
return v
|
||||
|
||||
|
||||
def _stix_type_of(value):
|
||||
"""
|
||||
Get a STIX type from the given value: if a STIX ID is passed, the type
|
||||
prefix is extracted; if string which is not a STIX ID is passed, it is
|
||||
assumed to be a STIX type and is returned; otherwise it is assumed to be a
|
||||
mapping with a "type" property, and the value of that property is returned.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:return: A STIX type
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
if "--" in value:
|
||||
type_ = get_type_from_id(value)
|
||||
else:
|
||||
type_ = value
|
||||
else:
|
||||
type_ = value["type"]
|
||||
|
||||
return type_
|
||||
|
||||
|
||||
def is_sdo(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether the given object, type, or ID is/is for an SDO of the
|
||||
given STIX version. If value is a type or ID, this just checks whether
|
||||
the type was registered as an SDO in the given STIX version. If a mapping,
|
||||
*simple* STIX version inference is additionally done on the value, and the
|
||||
result is checked against stix_version. It does not attempt to fully
|
||||
validate the value.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:param stix_version: A STIX version as a string
|
||||
:return: True if the type of the given value is an SDO type of the given
|
||||
version; False if not
|
||||
"""
|
||||
|
||||
result = True
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
value_stix_version = detect_spec_version(value)
|
||||
if value_stix_version != stix_version:
|
||||
result = False
|
||||
|
||||
if result:
|
||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||
type_ = _stix_type_of(value)
|
||||
result = type_ in cls_maps["objects"] and type_ not in {
|
||||
"relationship", "sighting", "marking-definition", "bundle",
|
||||
"language-content",
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def is_sco(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether the given object, type, or ID is/is for an SCO of the
|
||||
given STIX version. If value is a type or ID, this just checks whether
|
||||
the type was registered as an SCO in the given STIX version. If a mapping,
|
||||
*simple* STIX version inference is additionally done on the value, and the
|
||||
result is checked against stix_version. It does not attempt to fully
|
||||
validate the value.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:param stix_version: A STIX version as a string
|
||||
:return: True if the type of the given value is an SCO type of the given
|
||||
version; False if not
|
||||
"""
|
||||
|
||||
result = True
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
value_stix_version = detect_spec_version(value)
|
||||
if value_stix_version != stix_version:
|
||||
result = False
|
||||
|
||||
if result:
|
||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||
type_ = _stix_type_of(value)
|
||||
result = type_ in cls_maps["observables"]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def is_sro(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether the given object, type, or ID is/is for an SRO of the
|
||||
given STIX version. If value is a type or ID, this just checks whether
|
||||
the type is "sighting" or "relationship". If a mapping, *simple* STIX
|
||||
version inference is additionally done on the value, and the result is
|
||||
checked against stix_version. It does not attempt to fully validate the
|
||||
value.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:param stix_version: A STIX version as a string
|
||||
:return: True if the type of the given value is an SRO type of the given
|
||||
version; False if not
|
||||
"""
|
||||
|
||||
result = True
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
value_stix_version = detect_spec_version(value)
|
||||
if value_stix_version != stix_version:
|
||||
result = False
|
||||
|
||||
if result:
|
||||
# No need to check registration in this case
|
||||
type_ = _stix_type_of(value)
|
||||
result = type_ in ("sighting", "relationship")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def is_object(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether an object, type, or ID is/is for any STIX object. This
|
||||
includes all SDOs, SCOs, meta-objects, and bundle. If value is a type or
|
||||
ID, this just checks whether the type was registered in the given STIX
|
||||
version. If a mapping, *simple* STIX version inference is additionally
|
||||
done on the value, and the result is checked against stix_version. It does
|
||||
not attempt to fully validate the value.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:param stix_version: A STIX version as a string
|
||||
:return: True if the type of the given value is a valid STIX type with
|
||||
respect to the given STIX version; False if not
|
||||
"""
|
||||
|
||||
result = True
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
value_stix_version = detect_spec_version(value)
|
||||
if value_stix_version != stix_version:
|
||||
result = False
|
||||
|
||||
if result:
|
||||
cls_maps = mappings.STIX2_OBJ_MAPS[stix_version]
|
||||
type_ = _stix_type_of(value)
|
||||
result = type_ in cls_maps["observables"] \
|
||||
or type_ in cls_maps["objects"]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def is_marking(value, stix_version=stix2.version.DEFAULT_VERSION):
|
||||
"""
|
||||
Determine whether the given object, type, or ID is/is for an marking
|
||||
definition of the given STIX version. If value is a type or ID, this just
|
||||
checks whether the type is "marking-definition". If a mapping, *simple*
|
||||
STIX version inference is additionally done on the value, and the result
|
||||
is checked against stix_version. It does not attempt to fully validate the
|
||||
value.
|
||||
|
||||
:param value: A STIX object, object ID, or type as a string.
|
||||
:param stix_version: A STIX version as a string
|
||||
:return: True if the value is/is for a marking definition, False otherwise.
|
||||
"""
|
||||
|
||||
result = True
|
||||
if isinstance(value, collections.abc.Mapping):
|
||||
value_stix_version = detect_spec_version(value)
|
||||
if value_stix_version != stix_version:
|
||||
result = False
|
||||
|
||||
if result:
|
||||
# No need to check registration in this case
|
||||
type_ = _stix_type_of(value)
|
||||
result = type_ == "marking-definition"
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class STIXTypeClass(enum.Enum):
|
||||
"""
|
||||
Represents different classes of STIX type.
|
||||
"""
|
||||
SDO = 0
|
||||
SCO = 1
|
||||
SRO = 2
|
||||
|
||||
|
||||
def is_stix_type(value, stix_version=stix2.version.DEFAULT_VERSION, *types):
|
||||
"""
|
||||
Determine whether the type of the given value satisfies the given
|
||||
constraints. 'types' must contain STIX types as strings, and/or the
|
||||
STIXTypeClass enum values. STIX types imply an exact match constraint;
|
||||
STIXTypeClass enum values imply a more general constraint, that the object
|
||||
or type be in that class of STIX type. These constraints are implicitly
|
||||
OR'd together.
|
||||
|
||||
:param value: A mapping with a "type" property, or a STIX ID or type
|
||||
as a string
|
||||
:param stix_version: A STIX version as a string
|
||||
:param types: A sequence of STIX type strings or STIXTypeClass enum values
|
||||
:return: True if the object or type satisfies the constraints; False if not
|
||||
"""
|
||||
|
||||
for type_ in types:
|
||||
if type_ is STIXTypeClass.SDO:
|
||||
result = is_sdo(value, stix_version)
|
||||
elif type_ is STIXTypeClass.SCO:
|
||||
result = is_sco(value, stix_version)
|
||||
elif type_ is STIXTypeClass.SRO:
|
||||
result = is_sro(value, stix_version)
|
||||
else:
|
||||
# Assume a string STIX type is given instead of a class enum,
|
||||
# and just check for exact match.
|
||||
obj_type = _stix_type_of(value)
|
||||
result = obj_type == type_ and is_object(value, stix_version)
|
||||
|
||||
if result:
|
||||
break
|
||||
|
||||
else:
|
||||
result = False
|
||||
|
||||
return result
|
||||
|
|
|
@ -32,7 +32,7 @@ from .observables import (
|
|||
UNIXAccountExt, UserAccount, WindowsPEBinaryExt,
|
||||
WindowsPEOptionalHeaderType, WindowsPESection, WindowsProcessExt,
|
||||
WindowsRegistryKey, WindowsRegistryValueType, WindowsServiceExt,
|
||||
X509Certificate, X509V3ExtenstionsType,
|
||||
X509Certificate, X509V3ExtensionsType,
|
||||
)
|
||||
from .sdo import (
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Identity, Indicator,
|
||||
|
@ -123,7 +123,7 @@ __all__ = """
|
|||
UNIXAccountExt, UserAccount, WindowsPEBinaryExt,
|
||||
WindowsPEOptionalHeaderType, WindowsPESection, WindowsProcessExt,
|
||||
WindowsRegistryKey, WindowsRegistryValueType, WindowsServiceExt,
|
||||
X509Certificate, X509V3ExtenstionsType,
|
||||
X509Certificate, X509V3ExtensionsType,
|
||||
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Identity, Indicator,
|
||||
IntrusionSet, Malware, ObservedData, Report, ThreatActor, Tool,
|
||||
|
|
|
@ -124,11 +124,11 @@ class MarkingDefinition(_STIXBase20, _MarkingsMixin):
|
|||
('id', IDProperty(_type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW)),
|
||||
('definition_type', StringProperty(required=True)),
|
||||
('definition', MarkingProperty(required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('definition_type', StringProperty(required=True)),
|
||||
('definition', MarkingProperty(required=True)),
|
||||
])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
|
@ -440,24 +440,28 @@ class SocketExt(_Extension):
|
|||
('is_blocking', BooleanProperty()),
|
||||
('is_listening', BooleanProperty()),
|
||||
(
|
||||
'protocol_family', EnumProperty(allowed=[
|
||||
"PF_INET",
|
||||
"PF_IPX",
|
||||
"PF_APPLETALK",
|
||||
"PF_INET6",
|
||||
"PF_AX25",
|
||||
"PF_NETROM",
|
||||
]),
|
||||
'protocol_family', EnumProperty(
|
||||
allowed=[
|
||||
"PF_INET",
|
||||
"PF_IPX",
|
||||
"PF_APPLETALK",
|
||||
"PF_INET6",
|
||||
"PF_AX25",
|
||||
"PF_NETROM",
|
||||
],
|
||||
),
|
||||
),
|
||||
('options', DictionaryProperty(spec_version="2.0")),
|
||||
(
|
||||
'socket_type', EnumProperty(allowed=[
|
||||
"SOCK_STREAM",
|
||||
"SOCK_DGRAM",
|
||||
"SOCK_RAW",
|
||||
"SOCK_RDM",
|
||||
"SOCK_SEQPACKET",
|
||||
]),
|
||||
'socket_type', EnumProperty(
|
||||
allowed=[
|
||||
"SOCK_STREAM",
|
||||
"SOCK_DGRAM",
|
||||
"SOCK_RAW",
|
||||
"SOCK_RDM",
|
||||
"SOCK_SEQPACKET",
|
||||
],
|
||||
),
|
||||
),
|
||||
('socket_descriptor', IntegerProperty()),
|
||||
('socket_handle', IntegerProperty()),
|
||||
|
@ -537,33 +541,39 @@ class WindowsServiceExt(_Extension):
|
|||
('display_name', StringProperty()),
|
||||
('group_name', StringProperty()),
|
||||
(
|
||||
'start_type', EnumProperty(allowed=[
|
||||
"SERVICE_AUTO_START",
|
||||
"SERVICE_BOOT_START",
|
||||
"SERVICE_DEMAND_START",
|
||||
"SERVICE_DISABLED",
|
||||
"SERVICE_SYSTEM_ALERT",
|
||||
]),
|
||||
'start_type', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_AUTO_START",
|
||||
"SERVICE_BOOT_START",
|
||||
"SERVICE_DEMAND_START",
|
||||
"SERVICE_DISABLED",
|
||||
"SERVICE_SYSTEM_ALERT",
|
||||
],
|
||||
),
|
||||
),
|
||||
('service_dll_refs', ListProperty(ObjectReferenceProperty(valid_types='file'))),
|
||||
(
|
||||
'service_type', EnumProperty(allowed=[
|
||||
"SERVICE_KERNEL_DRIVER",
|
||||
"SERVICE_FILE_SYSTEM_DRIVER",
|
||||
"SERVICE_WIN32_OWN_PROCESS",
|
||||
"SERVICE_WIN32_SHARE_PROCESS",
|
||||
]),
|
||||
'service_type', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_KERNEL_DRIVER",
|
||||
"SERVICE_FILE_SYSTEM_DRIVER",
|
||||
"SERVICE_WIN32_OWN_PROCESS",
|
||||
"SERVICE_WIN32_SHARE_PROCESS",
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'service_status', EnumProperty(allowed=[
|
||||
"SERVICE_CONTINUE_PENDING",
|
||||
"SERVICE_PAUSE_PENDING",
|
||||
"SERVICE_PAUSED",
|
||||
"SERVICE_RUNNING",
|
||||
"SERVICE_START_PENDING",
|
||||
"SERVICE_STOP_PENDING",
|
||||
"SERVICE_STOPPED",
|
||||
]),
|
||||
'service_status', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_CONTINUE_PENDING",
|
||||
"SERVICE_PAUSE_PENDING",
|
||||
"SERVICE_PAUSED",
|
||||
"SERVICE_RUNNING",
|
||||
"SERVICE_START_PENDING",
|
||||
"SERVICE_STOP_PENDING",
|
||||
"SERVICE_STOPPED",
|
||||
],
|
||||
),
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -687,21 +697,23 @@ class WindowsRegistryValueType(_STIXBase20):
|
|||
('name', StringProperty(required=True)),
|
||||
('data', StringProperty()),
|
||||
(
|
||||
'data_type', EnumProperty(allowed=[
|
||||
"REG_NONE",
|
||||
"REG_SZ",
|
||||
"REG_EXPAND_SZ",
|
||||
"REG_BINARY",
|
||||
"REG_DWORD",
|
||||
"REG_DWORD_BIG_ENDIAN",
|
||||
"REG_LINK",
|
||||
"REG_MULTI_SZ",
|
||||
"REG_RESOURCE_LIST",
|
||||
"REG_FULL_RESOURCE_DESCRIPTION",
|
||||
"REG_RESOURCE_REQUIREMENTS_LIST",
|
||||
"REG_QWORD",
|
||||
"REG_INVALID_TYPE",
|
||||
]),
|
||||
'data_type', EnumProperty(
|
||||
allowed=[
|
||||
"REG_NONE",
|
||||
"REG_SZ",
|
||||
"REG_EXPAND_SZ",
|
||||
"REG_BINARY",
|
||||
"REG_DWORD",
|
||||
"REG_DWORD_BIG_ENDIAN",
|
||||
"REG_LINK",
|
||||
"REG_MULTI_SZ",
|
||||
"REG_RESOURCE_LIST",
|
||||
"REG_FULL_RESOURCE_DESCRIPTION",
|
||||
"REG_RESOURCE_REQUIREMENTS_LIST",
|
||||
"REG_QWORD",
|
||||
"REG_INVALID_TYPE",
|
||||
],
|
||||
),
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -724,7 +736,7 @@ class WindowsRegistryKey(_Observable):
|
|||
])
|
||||
|
||||
|
||||
class X509V3ExtenstionsType(_STIXBase20):
|
||||
class X509V3ExtensionsType(_STIXBase20):
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.0 specification <http://docs.oasis-open.org/cti/stix/v2.0/cs01/part4-cyber-observable-objects/stix-v2.0-cs01-part4-cyber-observable-objects.html#_Toc496716298>`__.
|
||||
""" # noqa
|
||||
|
@ -770,7 +782,7 @@ class X509Certificate(_Observable):
|
|||
('subject_public_key_algorithm', StringProperty()),
|
||||
('subject_public_key_modulus', StringProperty()),
|
||||
('subject_public_key_exponent', IntegerProperty()),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtenstionsType)),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtensionsType)),
|
||||
('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=_type)),
|
||||
])
|
||||
|
||||
|
@ -790,11 +802,13 @@ def CustomObservable(type='x-custom-observable', properties=None):
|
|||
|
||||
"""
|
||||
def wrapper(cls):
|
||||
_properties = list(itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type, spec_version='2.0'))],
|
||||
properties,
|
||||
[('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=type))],
|
||||
]))
|
||||
_properties = list(
|
||||
itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type, spec_version='2.0'))],
|
||||
properties,
|
||||
[('extensions', ExtensionsProperty(spec_version="2.0", enclosing_type=type))],
|
||||
]),
|
||||
)
|
||||
return _custom_observable_builder(cls, type, _properties, '2.0', _Observable)
|
||||
return wrapper
|
||||
|
||||
|
|
|
@ -362,23 +362,25 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
|
||||
"""
|
||||
def wrapper(cls):
|
||||
_properties = list(itertools.chain.from_iterable([
|
||||
[
|
||||
('type', TypeProperty(type, spec_version='2.0')),
|
||||
('id', IDProperty(type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
],
|
||||
[x for x in properties if not x[0].startswith('x_')],
|
||||
[
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
]))
|
||||
_properties = list(
|
||||
itertools.chain.from_iterable([
|
||||
[
|
||||
('type', TypeProperty(type, spec_version='2.0')),
|
||||
('id', IDProperty(type, spec_version='2.0')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond')),
|
||||
],
|
||||
[x for x in properties if not x[0].startswith('x_')],
|
||||
[
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
]),
|
||||
)
|
||||
return _custom_object_builder(cls, type, _properties, '2.0', _DomainObject)
|
||||
return wrapper
|
||||
|
|
|
@ -32,7 +32,7 @@ from .observables import (
|
|||
UNIXAccountExt, UserAccount, WindowsPEBinaryExt,
|
||||
WindowsPEOptionalHeaderType, WindowsPESection, WindowsProcessExt,
|
||||
WindowsRegistryKey, WindowsRegistryValueType, WindowsServiceExt,
|
||||
X509Certificate, X509V3ExtenstionsType,
|
||||
X509Certificate, X509V3ExtensionsType,
|
||||
)
|
||||
from .sdo import (
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Grouping, Identity,
|
||||
|
@ -131,7 +131,7 @@ __all__ = """
|
|||
UNIXAccountExt, UserAccount, WindowsPEBinaryExt,
|
||||
WindowsPEOptionalHeaderType, WindowsPESection, WindowsProcessExt,
|
||||
WindowsRegistryKey, WindowsRegistryValueType, WindowsServiceExt,
|
||||
X509Certificate, X509V3ExtenstionsType,
|
||||
X509Certificate, X509V3ExtensionsType,
|
||||
|
||||
AttackPattern, Campaign, CourseOfAction, CustomObject, Grouping, Identity,
|
||||
Indicator, Infrastructure, IntrusionSet, Location, Malware,
|
||||
|
|
|
@ -94,7 +94,7 @@ class LanguageContent(_STIXBase21):
|
|||
('object_modified', TimestampProperty(precision='millisecond')),
|
||||
# TODO: 'contents' https://docs.google.com/document/d/1ShNq4c3e1CkfANmD9O--mdZ5H0O_GLnjN28a_yrEaco/edit#heading=h.cfz5hcantmvx
|
||||
('contents', DictionaryProperty(spec_version='2.1', required=True)),
|
||||
('revoked', BooleanProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
|
@ -156,12 +156,12 @@ class MarkingDefinition(_STIXBase21, _MarkingsMixin):
|
|||
('id', IDProperty(_type)),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('definition_type', StringProperty(required=True)),
|
||||
('name', StringProperty()),
|
||||
('definition', MarkingProperty(required=True)),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
|
|
@ -28,6 +28,7 @@ class Artifact(_Observable):
|
|||
_type = 'artifact'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('mime_type', StringProperty()),
|
||||
('payload_bin', BinaryProperty()),
|
||||
|
@ -35,11 +36,10 @@ class Artifact(_Observable):
|
|||
('hashes', HashesProperty(spec_version='2.1')),
|
||||
('encryption_algorithm', StringProperty()),
|
||||
('decryption_key', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "payload_bin"]
|
||||
|
||||
|
@ -57,15 +57,15 @@ class AutonomousSystem(_Observable):
|
|||
_type = 'autonomous-system'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('number', IntegerProperty(required=True)),
|
||||
('name', StringProperty()),
|
||||
('rir', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["number"]
|
||||
|
||||
|
@ -78,6 +78,7 @@ class Directory(_Observable):
|
|||
_type = 'directory'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('path', StringProperty(required=True)),
|
||||
('path_enc', StringProperty()),
|
||||
|
@ -86,11 +87,10 @@ class Directory(_Observable):
|
|||
('mtime', TimestampProperty()),
|
||||
('atime', TimestampProperty()),
|
||||
('contains_refs', ListProperty(ReferenceProperty(valid_types=['file', 'directory'], spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["path"]
|
||||
|
||||
|
@ -103,14 +103,14 @@ class DomainName(_Observable):
|
|||
_type = 'domain-name'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types=['ipv4-addr', 'ipv6-addr', 'domain-name'], spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -123,15 +123,15 @@ class EmailAddress(_Observable):
|
|||
_type = 'email-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('display_name', StringProperty()),
|
||||
('belongs_to_ref', ReferenceProperty(valid_types='user-account', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -161,6 +161,7 @@ class EmailMessage(_Observable):
|
|||
_type = 'email-message'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_multipart', BooleanProperty(required=True)),
|
||||
('date', TimestampProperty()),
|
||||
|
@ -177,11 +178,10 @@ class EmailMessage(_Observable):
|
|||
('body', StringProperty()),
|
||||
('body_multipart', ListProperty(EmbeddedObjectProperty(type=EmailMIMEComponent))),
|
||||
('raw_email_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["from_ref", "subject", "body"]
|
||||
|
||||
|
@ -345,6 +345,7 @@ class File(_Observable):
|
|||
_type = 'file'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('hashes', HashesProperty(spec_version='2.1')),
|
||||
('size', IntegerProperty(min=0)),
|
||||
|
@ -358,11 +359,10 @@ class File(_Observable):
|
|||
('parent_directory_ref', ReferenceProperty(valid_types='directory', spec_version='2.1')),
|
||||
('contains_refs', ListProperty(ReferenceProperty(valid_types=["SCO"], spec_version='2.1'))),
|
||||
('content_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "name", "parent_directory_ref", "extensions"]
|
||||
|
||||
|
@ -379,15 +379,15 @@ class IPv4Address(_Observable):
|
|||
_type = 'ipv4-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))),
|
||||
('belongs_to_refs', ListProperty(ReferenceProperty(valid_types='autonomous-system', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -400,15 +400,15 @@ class IPv6Address(_Observable):
|
|||
_type = 'ipv6-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('resolves_to_refs', ListProperty(ReferenceProperty(valid_types='mac-addr', spec_version='2.1'))),
|
||||
('belongs_to_refs', ListProperty(ReferenceProperty(valid_types='autonomous-system', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -421,13 +421,13 @@ class MACAddress(_Observable):
|
|||
_type = 'mac-addr'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -440,13 +440,13 @@ class Mutex(_Observable):
|
|||
_type = 'mutex'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('name', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["name"]
|
||||
|
||||
|
@ -505,13 +505,15 @@ class SocketExt(_Extension):
|
|||
('is_listening', BooleanProperty()),
|
||||
('options', DictionaryProperty(spec_version='2.1')),
|
||||
(
|
||||
'socket_type', EnumProperty(allowed=[
|
||||
"SOCK_STREAM",
|
||||
"SOCK_DGRAM",
|
||||
"SOCK_RAW",
|
||||
"SOCK_RDM",
|
||||
"SOCK_SEQPACKET",
|
||||
]),
|
||||
'socket_type', EnumProperty(
|
||||
allowed=[
|
||||
"SOCK_STREAM",
|
||||
"SOCK_DGRAM",
|
||||
"SOCK_RAW",
|
||||
"SOCK_RDM",
|
||||
"SOCK_SEQPACKET",
|
||||
],
|
||||
),
|
||||
),
|
||||
('socket_descriptor', IntegerProperty(min=0)),
|
||||
('socket_handle', IntegerProperty()),
|
||||
|
@ -551,6 +553,7 @@ class NetworkTraffic(_Observable):
|
|||
_type = 'network-traffic'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('start', TimestampProperty()),
|
||||
('end', TimestampProperty()),
|
||||
|
@ -569,11 +572,10 @@ class NetworkTraffic(_Observable):
|
|||
('dst_payload_ref', ReferenceProperty(valid_types='artifact', spec_version='2.1')),
|
||||
('encapsulates_refs', ListProperty(ReferenceProperty(valid_types='network-traffic', spec_version='2.1'))),
|
||||
('encapsulated_by_ref', ReferenceProperty(valid_types='network-traffic', spec_version='2.1')),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["start", "src_ref", "dst_ref", "src_port", "dst_port", "protocols"]
|
||||
|
||||
|
@ -612,12 +614,14 @@ class WindowsProcessExt(_Extension):
|
|||
('window_title', StringProperty()),
|
||||
('startup_info', DictionaryProperty(spec_version='2.1')),
|
||||
(
|
||||
'integrity_level', EnumProperty(allowed=[
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"system",
|
||||
]),
|
||||
'integrity_level', EnumProperty(
|
||||
allowed=[
|
||||
"low",
|
||||
"medium",
|
||||
"high",
|
||||
"system",
|
||||
],
|
||||
),
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -634,33 +638,39 @@ class WindowsServiceExt(_Extension):
|
|||
('display_name', StringProperty()),
|
||||
('group_name', StringProperty()),
|
||||
(
|
||||
'start_type', EnumProperty(allowed=[
|
||||
"SERVICE_AUTO_START",
|
||||
"SERVICE_BOOT_START",
|
||||
"SERVICE_DEMAND_START",
|
||||
"SERVICE_DISABLED",
|
||||
"SERVICE_SYSTEM_ALERT",
|
||||
]),
|
||||
'start_type', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_AUTO_START",
|
||||
"SERVICE_BOOT_START",
|
||||
"SERVICE_DEMAND_START",
|
||||
"SERVICE_DISABLED",
|
||||
"SERVICE_SYSTEM_ALERT",
|
||||
],
|
||||
),
|
||||
),
|
||||
('service_dll_refs', ListProperty(ReferenceProperty(valid_types='file', spec_version="2.1"))),
|
||||
(
|
||||
'service_type', EnumProperty(allowed=[
|
||||
"SERVICE_KERNEL_DRIVER",
|
||||
"SERVICE_FILE_SYSTEM_DRIVER",
|
||||
"SERVICE_WIN32_OWN_PROCESS",
|
||||
"SERVICE_WIN32_SHARE_PROCESS",
|
||||
]),
|
||||
'service_type', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_KERNEL_DRIVER",
|
||||
"SERVICE_FILE_SYSTEM_DRIVER",
|
||||
"SERVICE_WIN32_OWN_PROCESS",
|
||||
"SERVICE_WIN32_SHARE_PROCESS",
|
||||
],
|
||||
),
|
||||
),
|
||||
(
|
||||
'service_status', EnumProperty(allowed=[
|
||||
"SERVICE_CONTINUE_PENDING",
|
||||
"SERVICE_PAUSE_PENDING",
|
||||
"SERVICE_PAUSED",
|
||||
"SERVICE_RUNNING",
|
||||
"SERVICE_START_PENDING",
|
||||
"SERVICE_STOP_PENDING",
|
||||
"SERVICE_STOPPED",
|
||||
]),
|
||||
'service_status', EnumProperty(
|
||||
allowed=[
|
||||
"SERVICE_CONTINUE_PENDING",
|
||||
"SERVICE_PAUSE_PENDING",
|
||||
"SERVICE_PAUSED",
|
||||
"SERVICE_RUNNING",
|
||||
"SERVICE_START_PENDING",
|
||||
"SERVICE_STOP_PENDING",
|
||||
"SERVICE_STOPPED",
|
||||
],
|
||||
),
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -673,6 +683,7 @@ class Process(_Observable):
|
|||
_type = 'process'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_hidden', BooleanProperty()),
|
||||
('pid', IntegerProperty()),
|
||||
|
@ -686,11 +697,10 @@ class Process(_Observable):
|
|||
('image_ref', ReferenceProperty(valid_types='file', spec_version='2.1')),
|
||||
('parent_ref', ReferenceProperty(valid_types='process', spec_version='2.1')),
|
||||
('child_refs', ListProperty(ReferenceProperty(valid_types='process', spec_version='2.1'))),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = []
|
||||
|
||||
|
@ -717,6 +727,7 @@ class Software(_Observable):
|
|||
_type = 'software'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('name', StringProperty(required=True)),
|
||||
('cpe', StringProperty()),
|
||||
|
@ -724,11 +735,10 @@ class Software(_Observable):
|
|||
('languages', ListProperty(StringProperty)),
|
||||
('vendor', StringProperty()),
|
||||
('version', StringProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["name", "cpe", "swid", "vendor", "version"]
|
||||
|
||||
|
@ -741,13 +751,13 @@ class URL(_Observable):
|
|||
_type = 'url'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('value', StringProperty(required=True)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["value"]
|
||||
|
||||
|
@ -774,6 +784,7 @@ class UserAccount(_Observable):
|
|||
_type = 'user-account'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('user_id', StringProperty()),
|
||||
('credential', StringProperty()),
|
||||
|
@ -789,11 +800,10 @@ class UserAccount(_Observable):
|
|||
('credential_last_changed', TimestampProperty()),
|
||||
('account_first_login', TimestampProperty()),
|
||||
('account_last_login', TimestampProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["account_type", "user_id", "account_login"]
|
||||
|
||||
|
@ -808,21 +818,23 @@ class WindowsRegistryValueType(_STIXBase21):
|
|||
('name', StringProperty()),
|
||||
('data', StringProperty()),
|
||||
(
|
||||
'data_type', EnumProperty(allowed=[
|
||||
"REG_NONE",
|
||||
"REG_SZ",
|
||||
"REG_EXPAND_SZ",
|
||||
"REG_BINARY",
|
||||
"REG_DWORD",
|
||||
"REG_DWORD_BIG_ENDIAN",
|
||||
"REG_LINK",
|
||||
"REG_MULTI_SZ",
|
||||
"REG_RESOURCE_LIST",
|
||||
"REG_FULL_RESOURCE_DESCRIPTION",
|
||||
"REG_RESOURCE_REQUIREMENTS_LIST",
|
||||
"REG_QWORD",
|
||||
"REG_INVALID_TYPE",
|
||||
]),
|
||||
'data_type', EnumProperty(
|
||||
allowed=[
|
||||
"REG_NONE",
|
||||
"REG_SZ",
|
||||
"REG_EXPAND_SZ",
|
||||
"REG_BINARY",
|
||||
"REG_DWORD",
|
||||
"REG_DWORD_BIG_ENDIAN",
|
||||
"REG_LINK",
|
||||
"REG_MULTI_SZ",
|
||||
"REG_RESOURCE_LIST",
|
||||
"REG_FULL_RESOURCE_DESCRIPTION",
|
||||
"REG_RESOURCE_REQUIREMENTS_LIST",
|
||||
"REG_QWORD",
|
||||
"REG_INVALID_TYPE",
|
||||
],
|
||||
),
|
||||
),
|
||||
])
|
||||
|
||||
|
@ -835,6 +847,7 @@ class WindowsRegistryKey(_Observable):
|
|||
_type = 'windows-registry-key'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('key', StringProperty()),
|
||||
('values', ListProperty(EmbeddedObjectProperty(type=WindowsRegistryValueType))),
|
||||
|
@ -842,16 +855,15 @@ class WindowsRegistryKey(_Observable):
|
|||
('modified_time', TimestampProperty()),
|
||||
('creator_user_ref', ReferenceProperty(valid_types='user-account', spec_version='2.1')),
|
||||
('number_of_subkeys', IntegerProperty()),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["key", "values"]
|
||||
|
||||
|
||||
class X509V3ExtenstionsType(_STIXBase21):
|
||||
class X509V3ExtensionsType(_STIXBase21):
|
||||
"""For more detailed information on this object's properties, see
|
||||
`the STIX 2.1 specification <https://docs.oasis-open.org/cti/stix/v2.1/cs01/stix-v2.1-cs01.html#_c1kt4dheb6vz>`__.
|
||||
"""
|
||||
|
@ -885,6 +897,7 @@ class X509Certificate(_Observable):
|
|||
_type = 'x509-certificate'
|
||||
_properties = OrderedDict([
|
||||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('is_self_signed', BooleanProperty()),
|
||||
('hashes', HashesProperty(spec_version='2.1')),
|
||||
|
@ -898,12 +911,11 @@ class X509Certificate(_Observable):
|
|||
('subject_public_key_algorithm', StringProperty()),
|
||||
('subject_public_key_modulus', StringProperty()),
|
||||
('subject_public_key_exponent', IntegerProperty()),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtenstionsType)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('x509_v3_extensions', EmbeddedObjectProperty(type=X509V3ExtensionsType)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('defanged', BooleanProperty(default=lambda: False)),
|
||||
('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=_type)),
|
||||
])
|
||||
_id_contributing_properties = ["hashes", "serial_number"]
|
||||
|
||||
|
@ -935,12 +947,18 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro
|
|||
|
||||
"""
|
||||
def wrapper(cls):
|
||||
_properties = list(itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type, spec_version='2.1'))],
|
||||
[('id', IDProperty(type, spec_version='2.1'))],
|
||||
properties,
|
||||
[('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))],
|
||||
]))
|
||||
_properties = list(
|
||||
itertools.chain.from_iterable([
|
||||
[('type', TypeProperty(type, spec_version='2.1'))],
|
||||
[('spec_version', StringProperty(fixed='2.1'))],
|
||||
[('id', IDProperty(type, spec_version='2.1'))],
|
||||
properties,
|
||||
[('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1')))],
|
||||
[('granular_markings', ListProperty(GranularMarking))],
|
||||
[('defanged', BooleanProperty(default=lambda: False))],
|
||||
[('extensions', ExtensionsProperty(spec_version='2.1', enclosing_type=type))],
|
||||
]),
|
||||
)
|
||||
return _custom_observable_builder(cls, type, _properties, '2.1', _Observable, id_contrib_props)
|
||||
return wrapper
|
||||
|
||||
|
|
|
@ -122,9 +122,13 @@ class Grouping(_DomainObject):
|
|||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('context', StringProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
|
@ -132,10 +136,6 @@ class Grouping(_DomainObject):
|
|||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty()),
|
||||
('description', StringProperty()),
|
||||
('context', StringProperty(required=True)),
|
||||
('object_refs', ListProperty(ReferenceProperty(valid_types=["SCO", "SDO", "SRO"], spec_version='2.1'), required=True)),
|
||||
])
|
||||
|
||||
|
||||
|
@ -240,13 +240,6 @@ class Infrastructure(_DomainObject):
|
|||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('name', StringProperty(required=True)),
|
||||
('description', StringProperty()),
|
||||
('infrastructure_types', ListProperty(StringProperty)),
|
||||
|
@ -254,6 +247,13 @@ class Infrastructure(_DomainObject):
|
|||
('kill_chain_phases', ListProperty(KillChainPhase)),
|
||||
('first_seen', TimestampProperty()),
|
||||
('last_seen', TimestampProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -478,16 +478,9 @@ class MalwareAnalysis(_DomainObject):
|
|||
('type', TypeProperty(_type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(_type, spec_version='2.1')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
('product', StringProperty(required=True)),
|
||||
('version', StringProperty()),
|
||||
('host_vm_ref', ReferenceProperty(valid_types='software', spec_version='2.1')),
|
||||
|
@ -503,7 +496,14 @@ class MalwareAnalysis(_DomainObject):
|
|||
('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")),
|
||||
('sample_ref', ReferenceProperty(valid_types="SCO", spec_version='2.1')),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
])
|
||||
|
||||
def _check_object_constraints(self):
|
||||
|
@ -794,27 +794,29 @@ def CustomObject(type='x-custom-type', properties=None):
|
|||
|
||||
"""
|
||||
def wrapper(cls):
|
||||
_properties = list(itertools.chain.from_iterable([
|
||||
[
|
||||
('type', TypeProperty(type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(type, spec_version='2.1')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
],
|
||||
[x for x in properties if not x[0].startswith('x_')],
|
||||
[
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
]))
|
||||
_properties = list(
|
||||
itertools.chain.from_iterable([
|
||||
[
|
||||
('type', TypeProperty(type, spec_version='2.1')),
|
||||
('spec_version', StringProperty(fixed='2.1')),
|
||||
('id', IDProperty(type, spec_version='2.1')),
|
||||
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.1')),
|
||||
('created', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
('modified', TimestampProperty(default=lambda: NOW, precision='millisecond', precision_constraint='min')),
|
||||
],
|
||||
[x for x in properties if not x[0].startswith('x_')],
|
||||
[
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
('confidence', IntegerProperty()),
|
||||
('lang', StringProperty()),
|
||||
('external_references', ListProperty(ExternalReference)),
|
||||
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.1'))),
|
||||
('granular_markings', ListProperty(GranularMarking)),
|
||||
],
|
||||
sorted([x for x in properties if x[0].startswith('x_')], key=lambda x: x[0]),
|
||||
]),
|
||||
)
|
||||
return _custom_object_builder(cls, type, _properties, '2.1', _DomainObject)
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -89,7 +89,7 @@ class Sighting(_RelationshipObject):
|
|||
('count', IntegerProperty(min=0, max=999999999)),
|
||||
('sighting_of_ref', ReferenceProperty(valid_types="SDO", spec_version='2.1', required=True)),
|
||||
('observed_data_refs', ListProperty(ReferenceProperty(valid_types='observed-data', spec_version='2.1'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(valid_types='identity', spec_version='2.1'))),
|
||||
('where_sighted_refs', ListProperty(ReferenceProperty(valid_types=['identity', 'location'], spec_version='2.1'))),
|
||||
('summary', BooleanProperty()),
|
||||
('revoked', BooleanProperty(default=lambda: False)),
|
||||
('labels', ListProperty(StringProperty)),
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
__version__ = "2.0.2"
|
||||
__version__ = "2.1.0"
|
||||
|
||||
DEFAULT_VERSION = '2.1' # Default version will always be the latest STIX 2.X version
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
"""STIX2 core versioning methods."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import copy
|
||||
import datetime as dt
|
||||
import itertools
|
||||
import uuid
|
||||
|
||||
import six
|
||||
from six.moves.collections_abc import Mapping
|
||||
|
||||
import stix2.base
|
||||
from stix2.utils import get_timestamp, parse_into_datetime
|
||||
import stix2.registry
|
||||
from stix2.utils import (
|
||||
detect_spec_version, get_timestamp, is_sco, is_sdo, is_sro,
|
||||
parse_into_datetime,
|
||||
)
|
||||
import stix2.v20
|
||||
|
||||
from .exceptions import (
|
||||
|
@ -73,58 +75,47 @@ def _is_versionable(data):
|
|||
"""
|
||||
|
||||
is_versionable = False
|
||||
is_21 = False
|
||||
stix_vid = None
|
||||
stix_version = None
|
||||
|
||||
if isinstance(data, Mapping):
|
||||
|
||||
# First, determine spec version. It's easy for our stix2 objects; more
|
||||
# work for dicts.
|
||||
is_21 = False
|
||||
if isinstance(data, stix2.base._STIXBase) and \
|
||||
not isinstance(data, stix2.v20._STIXBase20):
|
||||
# (is_21 means 2.1 or later; try not to be 2.1-specific)
|
||||
is_21 = True
|
||||
if isinstance(data, stix2.v20._STIXBase20):
|
||||
stix_version = "2.0"
|
||||
elif isinstance(data, stix2.v21._STIXBase21):
|
||||
stix_version = "2.1"
|
||||
elif isinstance(data, dict):
|
||||
stix_vid = stix2.parsing._detect_spec_version(data)
|
||||
is_21 = stix_vid != "v20"
|
||||
stix_version = detect_spec_version(data)
|
||||
|
||||
# Then, determine versionability.
|
||||
|
||||
if six.PY2:
|
||||
# dumb python2 compatibility: map.keys() returns a list, not a set!
|
||||
# six.viewkeys() compatibility function uses dict.viewkeys() on
|
||||
# python2, which is not a Mapping mixin method, so that doesn't
|
||||
# work either (for our stix2 objects).
|
||||
keys = set(data)
|
||||
else:
|
||||
keys = data.keys()
|
||||
|
||||
# This should be sufficient for STIX objects; maybe we get lucky with
|
||||
# dicts here but probably not.
|
||||
if keys >= _VERSIONING_PROPERTIES:
|
||||
if data.keys() >= _VERSIONING_PROPERTIES:
|
||||
is_versionable = True
|
||||
|
||||
# Tougher to handle dicts. We need to consider STIX version, map to a
|
||||
# registered class, and from that get a more complete picture of its
|
||||
# properties.
|
||||
elif isinstance(data, dict):
|
||||
class_maps = stix2.parsing.STIX2_OBJ_MAPS[stix_vid]
|
||||
obj_type = data["type"]
|
||||
|
||||
if obj_type in class_maps["objects"]:
|
||||
if is_sdo(obj_type, stix_version) or is_sro(obj_type, stix_version):
|
||||
# Should we bother checking properties for SDOs/SROs?
|
||||
# They were designed to be versionable.
|
||||
is_versionable = True
|
||||
|
||||
elif obj_type in class_maps["observables"]:
|
||||
elif is_sco(obj_type, stix_version):
|
||||
# but do check SCOs
|
||||
cls = class_maps["observables"][obj_type]
|
||||
cls = stix2.registry.class_for_type(
|
||||
obj_type, stix_version, "observables",
|
||||
)
|
||||
is_versionable = _VERSIONING_PROPERTIES.issubset(
|
||||
cls._properties,
|
||||
)
|
||||
|
||||
return is_versionable, is_21
|
||||
return is_versionable, stix_version
|
||||
|
||||
|
||||
def new_version(data, allow_custom=None, **kwargs):
|
||||
|
@ -143,7 +134,7 @@ def new_version(data, allow_custom=None, **kwargs):
|
|||
:return: The new object.
|
||||
"""
|
||||
|
||||
is_versionable, is_21 = _is_versionable(data)
|
||||
is_versionable, stix_version = _is_versionable(data)
|
||||
|
||||
if not is_versionable:
|
||||
raise ValueError(
|
||||
|
@ -164,10 +155,17 @@ def new_version(data, allow_custom=None, **kwargs):
|
|||
# probably were). That would imply an ID change, which is not allowed
|
||||
# across versions.
|
||||
sco_locked_props = []
|
||||
if is_21 and isinstance(data, stix2.base._Observable):
|
||||
if is_sco(data, "2.1"):
|
||||
uuid_ = uuid.UUID(data["id"][-36:])
|
||||
if uuid_.variant == uuid.RFC_4122 and uuid_.version == 5:
|
||||
sco_locked_props = data._id_contributing_properties
|
||||
if isinstance(data, stix2.base._Observable):
|
||||
cls = data.__class__
|
||||
else:
|
||||
cls = stix2.registry.class_for_type(
|
||||
data["type"], stix_version, "observables",
|
||||
)
|
||||
|
||||
sco_locked_props = cls._id_contributing_properties
|
||||
|
||||
unchangable_properties = set()
|
||||
for prop in itertools.chain(STIX_UNMOD_PROPERTIES, sco_locked_props):
|
||||
|
@ -178,7 +176,7 @@ def new_version(data, allow_custom=None, **kwargs):
|
|||
|
||||
# Different versioning precision rules in STIX 2.0 vs 2.1, so we need
|
||||
# to know which rules to apply.
|
||||
precision_constraint = "min" if is_21 else "exact"
|
||||
precision_constraint = "min" if stix_version == "2.1" else "exact"
|
||||
|
||||
cls = type(data)
|
||||
if 'modified' not in kwargs:
|
||||
|
@ -188,7 +186,9 @@ def new_version(data, allow_custom=None, **kwargs):
|
|||
)
|
||||
|
||||
new_modified = get_timestamp()
|
||||
new_modified = _fudge_modified(old_modified, new_modified, is_21)
|
||||
new_modified = _fudge_modified(
|
||||
old_modified, new_modified, stix_version == "2.1",
|
||||
)
|
||||
|
||||
kwargs['modified'] = new_modified
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
"""
|
||||
|
||||
import functools
|
||||
import stix2
|
||||
|
||||
from . import AttackPattern as _AttackPattern
|
||||
from . import Campaign as _Campaign
|
||||
from . import CourseOfAction as _CourseOfAction
|
||||
|
@ -34,34 +34,37 @@ from . import Location as _Location
|
|||
from . import Malware as _Malware
|
||||
from . import MalwareAnalysis as _MalwareAnalysis
|
||||
from . import Note as _Note
|
||||
from . import OBJ_MAP
|
||||
from . import ObservedData as _ObservedData
|
||||
from . import Opinion as _Opinion
|
||||
from . import Report as _Report
|
||||
from . import ThreatActor as _ThreatActor
|
||||
from . import Tool as _Tool
|
||||
from . import Vulnerability as _Vulnerability
|
||||
from . import ( # noqa: F401
|
||||
from .version import DEFAULT_VERSION
|
||||
|
||||
from . import ( # noqa: F401 isort:skip
|
||||
AlternateDataStream, ArchiveExt, Artifact, AutonomousSystem,
|
||||
Bundle, CustomExtension, CustomMarking, CustomObservable,
|
||||
Directory, DomainName, EmailAddress, EmailMessage,
|
||||
EmailMIMEComponent, Environment, ExternalReference, File,
|
||||
FileSystemSource, Filter, GranularMarking, HTTPRequestExt,
|
||||
ICMPExt, IPv4Address, IPv6Address, KillChainPhase, LanguageContent, MACAddress,
|
||||
MarkingDefinition, MemoryStore, Mutex, NetworkTraffic, NTFSExt,
|
||||
parse_observable, PDFExt, Process, RasterImageExt, Relationship,
|
||||
ICMPExt, IPv4Address, IPv6Address, KillChainPhase, LanguageContent,
|
||||
MACAddress, MarkingDefinition, MemoryStore, Mutex, NetworkTraffic,
|
||||
NTFSExt, parse_observable, PDFExt, Process, RasterImageExt, Relationship,
|
||||
Sighting, SocketExt, Software, StatementMarking,
|
||||
TAXIICollectionSource, TCPExt, TLP_AMBER, TLP_GREEN, TLP_RED,
|
||||
TLP_WHITE, TLPMarking, UNIXAccountExt, URL, UserAccount,
|
||||
WindowsPEBinaryExt, WindowsPEOptionalHeaderType,
|
||||
WindowsPESection, WindowsProcessExt, WindowsRegistryKey,
|
||||
WindowsRegistryValueType, WindowsServiceExt, X509Certificate,
|
||||
X509V3ExtenstionsType
|
||||
X509V3ExtensionsType,
|
||||
)
|
||||
from .datastore.filters import FilterSet
|
||||
from .datastore.filters import FilterSet # isort:skip
|
||||
|
||||
|
||||
# Enable some adaptation to the current default supported STIX version.
|
||||
_STIX_VID = "v" + stix2.DEFAULT_VERSION.replace(".", "")
|
||||
_STIX_VID = "v" + DEFAULT_VERSION.replace(".", "")
|
||||
|
||||
|
||||
# Use an implicit MemoryStore
|
||||
|
@ -161,7 +164,7 @@ def _setup_workbench():
|
|||
# Add our new "class" to this module's globals and to the library-wide
|
||||
# mapping. This allows parse() to use the wrapped classes.
|
||||
globals()[obj_type.__name__] = factory_func
|
||||
stix2.OBJ_MAP[obj_type._type] = factory_func
|
||||
OBJ_MAP[obj_type._type] = factory_func
|
||||
|
||||
|
||||
_setup_workbench()
|
||||
|
|
33
tox.ini
33
tox.ini
|
@ -1,5 +1,5 @@
|
|||
[tox]
|
||||
envlist = py35,py36,py37,py38,style,isort-check,packaging
|
||||
envlist = py36,py37,py38,py39,packaging,pre-commit-check
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
@ -15,33 +15,24 @@ deps =
|
|||
commands =
|
||||
python -m pytest --cov=stix2 stix2/test/ --cov-report term-missing -W ignore::stix2.exceptions.STIXDeprecationWarning
|
||||
|
||||
passenv = CI TRAVIS TRAVIS_*
|
||||
|
||||
[testenv:style]
|
||||
deps =
|
||||
flake8
|
||||
commands =
|
||||
flake8
|
||||
|
||||
[flake8]
|
||||
max-line-length = 160
|
||||
|
||||
[testenv:isort-check]
|
||||
deps = isort
|
||||
commands =
|
||||
isort stix2 examples --df
|
||||
isort stix2 examples -c
|
||||
passenv = GITHUB_*
|
||||
|
||||
[testenv:packaging]
|
||||
deps =
|
||||
twine
|
||||
commands =
|
||||
python setup.py bdist_wheel --universal
|
||||
python setup.py sdist bdist_wheel --universal
|
||||
twine check dist/*
|
||||
|
||||
[travis]
|
||||
[testenv:pre-commit-check]
|
||||
deps =
|
||||
pre-commit
|
||||
commands =
|
||||
pre-commit run --all-files
|
||||
|
||||
[gh-actions]
|
||||
python =
|
||||
3.5: py35
|
||||
3.6: py36
|
||||
3.7: py37
|
||||
3.8: py38, style, packaging
|
||||
3.8: py38
|
||||
3.9: py39, packaging, pre-commit-check
|
||||
|
|
Loading…
Reference in New Issue