Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into dev-extensions-proposal
commit
a3fce3686a
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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -30,7 +30,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
|
||||
|
@ -64,11 +65,14 @@ def equivalent_patterns(pattern1, pattern2, stix_version=stix2.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(
|
||||
pattern1, version=stix_version,
|
||||
|
@ -96,12 +100,14 @@ 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, version=stix_version,
|
||||
|
|
|
@ -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):
|
||||
|
@ -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
|
||||
|
|
|
@ -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([
|
||||
|
@ -160,8 +163,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))
|
||||
|
@ -292,10 +298,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 +344,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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -937,6 +937,7 @@ 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'))],
|
||||
[('spec_version', StringProperty(fixed='2.1'))],
|
||||
[('id', IDProperty(type, spec_version='2.1'))],
|
||||
[('spec_version', StringProperty(fixed='2.1'))],
|
||||
properties,
|
||||
|
|
|
@ -88,7 +88,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 @@
|
|||
__version__ = "2.0.2"
|
||||
__version__ = "2.1.0"
|
||||
|
|
Loading…
Reference in New Issue