Merge branch 'master' of github.com:oasis-open/cti-python-stix2 into dev-extensions-proposal

pull/1/head
Emmanuelle Vargas-Gonzalez 2020-12-23 15:08:10 -05:00
commit a3fce3686a
17 changed files with 274 additions and 138 deletions

View File

@ -1,6 +1,32 @@
CHANGELOG 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 2.0.2 - 2020-07-07
* #423 Fixes issue with six dependency. * #423 Fixes issue with six dependency.

View File

@ -65,43 +65,53 @@
"\n", "\n",
"```\n", "```\n",
"stix2_content/\n", "stix2_content/\n",
" /STIX2 Domain Object type\n", " STIX2 Domain Object type/\n",
" STIX2 Domain Object\n", " STIX2 Domain Object ID/\n",
" STIX2 Domain Object\n", " 'modified' timestamp.json\n",
" 'modified' timestamp.json\n",
" STIX2 Domain Object ID/\n",
" 'modified' timestamp.json\n",
" .\n", " .\n",
" .\n", " .\n",
" .\n", " STIX2 Domain Object type/\n",
" /STIX2 Domain Object type\n", " STIX2 Domain Object ID/\n",
" STIX2 Domain Object\n", " 'modified' timestamp.json\n",
" STIX2 Domain Object\n",
" .\n", " .\n",
" .\n", " .\n",
" .\n", " .\n",
" .\n", " .\n",
" .\n", " .\n",
" .\n", " .\n",
" /STIX2 Domain Object type\n", " STIX2 Domain Object type/\n",
"```\n", "```\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",
"```\n", "```\n",
"stix2_content/\n", "stix2_content/\n",
" /attack-pattern\n", " /attack-pattern\n",
" attack-pattern--00d0b012-8a03-410e-95de-5826bf542de6.json\n", " /attack-pattern--00d0b012-8a03-410e-95de-5826bf542de6\n",
" attack-pattern--0a3ead4e-6d47-4ccb-854c-a6a4f9d96b22.json\n", " 20201211035036648071.json\n",
" attack-pattern--1b7ba276-eedc-4951-a762-0ceea2c030ec.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", " /campaign\n",
" /course-of-action\n", " /course-of-action\n",
" course-of-action--2a8de25c-f743-4348-b101-3ee33ab5871b.json\n", " /course-of-action--2a8de25c-f743-4348-b101-3ee33ab5871b\n",
" course-of-action--2c3ce852-06a2-40ee-8fe6-086f6402a739.json\n", " 20201011035036648071.json\n",
" /course-of-action--2c3ce852-06a2-40ee-8fe6-086f6402a739\n",
" 20201010035036648071.json\n",
" /identity\n", " /identity\n",
" identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5.json\n", " /identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5\n",
" 20201215035036648071.json\n",
" /indicator\n", " /indicator\n",
" /intrusion-set\n", " /intrusion-set\n",
" /malware\n", " /malware\n",
" malware--1d808f62-cf63-4063-9727-ff6132514c22.json\n", " /malware--1d808f62-cf63-4063-9727-ff6132514c22\n",
" malware--2eb9b131-d333-4a48-9eb4-d8dec46c19ee.json\n", " 20201211045036648071.json\n",
" /malware--2eb9b131-d333-4a48-9eb4-d8dec46c19ee\n",
" 20201211035036648072.json\n",
" /observed-data\n", " /observed-data\n",
" /report\n", " /report\n",
" /threat-actor\n", " /threat-actor\n",
@ -1408,7 +1418,7 @@
"# add Campaign object to FileSystemSink\n", "# add Campaign object to FileSystemSink\n",
"fs_sink.add(camp)\n", "fs_sink.add(camp)\n",
"\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])" "fs_sink.add([ind, ind1])"
] ]
} }
@ -1429,7 +1439,7 @@
"name": "python", "name": "python",
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.9.0a6" "version": "3.6.7"
} }
}, },
"nbformat": 4, "nbformat": 4,

View File

@ -1,5 +1,5 @@
[bumpversion] [bumpversion]
current_version = 2.0.2 current_version = 2.1.0
commit = True commit = True
tag = True tag = True

View File

@ -4,6 +4,7 @@ import time
from ...datastore import Filter from ...datastore import Filter
from ...utils import STIXdatetime, parse_into_datetime from ...utils import STIXdatetime, parse_into_datetime
from ..pattern import equivalent_patterns
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -68,7 +69,7 @@ def semantically_equivalent(obj1, obj2, prop_scores={}, **weight_dict):
sum_weights = 0.0 sum_weights = 0.0
for prop in weights[type1]: 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] w = weights[type1][prop][0]
comp_funct = weights[type1][prop][1] 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): def check_property_present(prop, obj1, obj2):
"""Helper method checks if a property is present on both objects.""" """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 True
return False return False
@ -208,8 +212,7 @@ def custom_pattern_based(pattern1, pattern2):
float: Number between 0.0 and 1.0 depending on match criteria. 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 equivalent_patterns(pattern1, pattern2)
return exact_match(pattern1, pattern2) # TODO: Implement pattern based equivalence
def partial_external_reference_based(refs1, refs2): def partial_external_reference_based(refs1, refs2):

View File

@ -30,7 +30,8 @@ def _get_pattern_canonicalizer():
""" """
Get a canonicalization transformer for STIX patterns. Get a canonicalization transformer for STIX patterns.
:return: The transformer Returns:
The transformer
""" """
# The transformers are either stateless or contain no state which changes # 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. Determine whether two STIX patterns are semantically equivalent.
:param pattern1: The first STIX pattern Args:
:param pattern2: The second STIX pattern pattern1: The first STIX pattern
:param stix_version: The STIX version to use for pattern parsing, as a pattern2: The second STIX pattern
string ("2.0", "2.1", etc). Defaults to library-wide default version. stix_version: The STIX version to use for pattern parsing, as a string
:return: True if the patterns are semantically equivalent; False if not ("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 = stix2.pattern_visitor.create_pattern_object(
pattern1, version=stix_version, 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 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. can "stream" patterns in and matching patterns will be streamed out.
:param search_pattern: A search pattern as a string Args:
:param patterns: An iterable over patterns as strings search_pattern: A search pattern as a string
:param stix_version: The STIX version to use for pattern parsing, as a patterns: An iterable over patterns as strings
string ("2.0", "2.1", etc). Defaults to library-wide default version. stix_version: The STIX version to use for pattern parsing, as a string
:return: A generator iterator producing the semantically equivalent ("2.0", "2.1", etc). Defaults to library-wide default version.
patterns
Returns:
A generator iterator producing the semantically equivalent patterns
""" """
search_pattern_ast = stix2.pattern_visitor.create_pattern_object( search_pattern_ast = stix2.pattern_visitor.create_pattern_object(
search_pattern, version=stix_version, search_pattern, version=stix_version,

View File

@ -16,9 +16,12 @@ def generic_cmp(value1, value2):
Generic comparator of values which uses the builtin '<' and '>' operators. Generic comparator of values which uses the builtin '<' and '>' operators.
Assumes the values can be compared that way. Assumes the values can be compared that way.
:param value1: The first value Args:
:param value2: The second value value1: The first value
:return: -1, 0, or 1 depending on whether value1 is less, equal, or greater value2: The second value
Returns:
-1, 0, or 1 depending on whether value1 is less, equal, or greater
than value2 than value2
""" """
@ -30,12 +33,15 @@ def iter_lex_cmp(seq1, seq2, cmp):
Generic lexicographical compare function, which works on two iterables and Generic lexicographical compare function, which works on two iterables and
a comparator function. a comparator function.
:param seq1: The first iterable Args:
:param seq2: The second iterable seq1: The first iterable
:param cmp: a two-arg callable comparator for values iterated over. It 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 must behave analogously to this function, returning <0, 0, or >0 to
express the ordering of the two values. express the ordering of the two values.
:return: <0 if seq1 < seq2; >0 if seq1 > seq2; 0 if they're equal
Returns:
<0 if seq1 < seq2; >0 if seq1 > seq2; 0 if they're equal
""" """
it1 = iter(seq1) it1 = iter(seq1)
@ -84,11 +90,14 @@ def iter_in(value, seq, cmp):
a comparator function. This function checks whether the given value is a comparator function. This function checks whether the given value is
contained in the given iterable. contained in the given iterable.
:param value: A value Args:
:param seq: An iterable value: A value
:param cmp: A 2-arg comparator function which must return 0 if the args seq: An iterable
cmp: A 2-arg comparator function which must return 0 if the args
are equal are equal
:return: True if the value is found in the iterable, False if it is not
Returns:
True if the value is found in the iterable, False if it is not
""" """
result = False result = False
for seq_val in seq: for seq_val in seq:

View File

@ -32,9 +32,12 @@ def generic_constant_cmp(const1, const2):
Generic comparator for most _Constant instances. They must have a "value" Generic comparator for most _Constant instances. They must have a "value"
attribute whose value supports the builtin comparison operators. attribute whose value supports the builtin comparison operators.
:param const1: The first _Constant instance Args:
:param const2: The second _Constant instance const1: The first _Constant instance
:return: <0, 0, or >0 depending on whether the first arg is less, equal or const2: The second _Constant instance
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
return generic_cmp(const1.value, const2.value) return generic_cmp(const1.value, const2.value)
@ -44,9 +47,12 @@ def bool_cmp(value1, value2):
""" """
Compare two boolean constants. Compare two boolean constants.
:param value1: The first BooleanConstant instance Args:
:param value2: The second BooleanConstant instance value1: The first BooleanConstant instance
:return: <0, 0, or >0 depending on whether the first arg is less, equal or value2: The second BooleanConstant instance
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second 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. Compare two STIX "hex" values. This decodes to bytes and compares that.
It does *not* do a string compare on the hex representations. It does *not* do a string compare on the hex representations.
:param value1: The first HexConstant Args:
:param value2: The second HexConstant value1: The first HexConstant
:return: <0, 0, or >0 depending on whether the first arg is less, equal or value2: The second HexConstant
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
bytes1 = bytes.fromhex(value1.value) 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. Compare two STIX "binary" values. This decodes to bytes and compares that.
It does *not* do a string compare on the base64 representations. It does *not* do a string compare on the base64 representations.
:param value1: The first BinaryConstant Args:
:param value2: The second BinaryConstant value1: The first BinaryConstant
:return: <0, 0, or >0 depending on whether the first arg is less, equal or value2: The second BinaryConstant
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
bytes1 = base64.standard_b64decode(value1.value) bytes1 = base64.standard_b64decode(value1.value)
@ -103,9 +115,12 @@ def list_cmp(value1, value2):
""" """
Compare lists order-insensitively. Compare lists order-insensitively.
:param value1: The first ListConstant Args:
:param value2: The second ListConstant value1: The first ListConstant
:return: <0, 0, or >0 depending on whether the first arg is less, equal or value2: The second ListConstant
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second 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 Ints and strings compare as usual to each other; ints compare less than
strings. strings.
:param comp1: An object path component (string or int) Args:
:param comp2: An object path component (string or int) comp1: An object path component (string or int)
:return: <0, 0, or >0 depending on whether the first arg is less, equal or 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 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 properties; "*" index steps become that string; and numeric index steps
become integers. become integers.
:param path: An ObjectPath instance Args:
:return: A generator iterator over the values path: An ObjectPath instance
Returns:
A generator iterator over the values
""" """
for comp in path.property_path: for comp in path.property_path:
@ -195,9 +216,12 @@ def object_path_cmp(path1, path2):
""" """
Compare two object paths. Compare two object paths.
:param path1: The first ObjectPath instance Args:
:param path2: The second ObjectPath instance path1: The first ObjectPath instance
:return: <0, 0, or >0 depending on whether the first arg is less, equal or path2: The second ObjectPath instance
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
if path1.object_type_name < path2.object_type_name: if path1.object_type_name < path2.object_type_name:
@ -224,9 +248,12 @@ def comparison_operator_cmp(op1, op2):
""" """
Compare two comparison operators. Compare two comparison operators.
:param op1: The first comparison operator (a string) Args:
:param op2: The second comparison operator (a string) op1: The first comparison operator (a string)
:return: <0, 0, or >0 depending on whether the first arg is less, equal or 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 greater than the second
""" """
op1_idx = _COMPARISON_OP_ORDER.index(op1) op1_idx = _COMPARISON_OP_ORDER.index(op1)
@ -241,9 +268,12 @@ def constant_cmp(value1, value2):
""" """
Compare two constants. Compare two constants.
:param value1: The first _Constant instance Args:
:param value2: The second _Constant instance value1: The first _Constant instance
:return: <0, 0, or >0 depending on whether the first arg is less, equal or value2: The second _Constant instance
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second 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 Compare "simple" comparison expressions: those which aren't AND/OR
combinations, just <path> <op> <value> comparisons. combinations, just <path> <op> <value> comparisons.
:param expr1: first _ComparisonExpression instance Args:
:param expr2: second _ComparisonExpression instance expr1: first _ComparisonExpression instance
:return: <0, 0, or >0 depending on whether the first arg is less, equal or expr2: second _ComparisonExpression instance
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
@ -315,9 +348,12 @@ def comparison_expression_cmp(expr1, expr2):
expressions' sub-components. To achieve an order-insensitive comparison, expressions' sub-components. To achieve an order-insensitive comparison,
the ASTs must be canonically ordered first. the ASTs must be canonically ordered first.
:param expr1: The first comparison expression Args:
:param expr2: The second comparison expression expr1: The first comparison expression
:return: <0, 0, or >0 depending on whether the first arg is less, equal or expr2: The second comparison expression
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
if isinstance(expr1, _ComparisonExpression) \ if isinstance(expr1, _ComparisonExpression) \

View File

@ -64,9 +64,12 @@ def observation_expression_cmp(expr1, expr2):
the expressions' sub-components. To achieve an order-insensitive the expressions' sub-components. To achieve an order-insensitive
comparison, the ASTs must be canonically ordered first. comparison, the ASTs must be canonically ordered first.
:param expr1: The first observation expression Args:
:param expr2: The second observation expression expr1: The first observation expression
:return: <0, 0, or >0 depending on whether the first arg is less, equal or expr2: The second observation expression
Returns:
<0, 0, or >0 depending on whether the first arg is less, equal or
greater than the second greater than the second
""" """
type1 = type(expr1) type1 = type(expr1)

View File

@ -22,13 +22,17 @@ def _dupe_ast(ast):
""" """
Create a duplicate of the given AST. Create a duplicate of the given AST.
Note: the comparison expression "leaves", i.e. simple <path> <op> <value> Note:
comparisons are currently not duplicated. I don't think it's necessary as The comparison expression "leaves", i.e. simple <path> <op> <value>
of this writing; they are never changed. But revisit this if/when 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. necessary.
:param ast: The AST to duplicate Args:
:return: The duplicate AST ast: The AST to duplicate
Returns:
The duplicate AST
""" """
if isinstance(ast, AndBooleanExpression): if isinstance(ast, AndBooleanExpression):
result = AndBooleanExpression([ result = AndBooleanExpression([
@ -108,8 +112,11 @@ class ComparisonExpressionTransformer(Transformer):
Invoke a transformer callback method based on the given ast root node Invoke a transformer callback method based on the given ast root node
type. type.
:param ast: The AST Args:
:return: The callback's result ast: The AST
Returns:
The callback's result
""" """
if isinstance(ast, AndBooleanExpression): if isinstance(ast, AndBooleanExpression):
@ -153,8 +160,11 @@ class OrderDedupeTransformer(
""" """
Sort/dedupe children. AND and OR can be treated identically. Sort/dedupe children. AND and OR can be treated identically.
:param ast: The comparison expression AST Args:
:return: The same AST node, but with sorted children ast: The comparison expression AST
Returns:
The same AST node, but with sorted children
""" """
sorted_children = sorted( sorted_children = sorted(
ast.operands, key=functools.cmp_to_key(comparison_expression_cmp), 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 little difference is that we can absorb AND children if we're an AND
ourselves; and OR for OR. ourselves; and OR for OR.
:param ast: The comparison expression AST Args:
:return: The same AST node, but with flattened children ast: The comparison expression AST
Returns:
The same AST node, but with flattened children
""" """
changed = False changed = False

View File

@ -38,8 +38,11 @@ def _dupe_ast(ast):
observation expressions are currently not duplicated. I don't think it's observation expressions are currently not duplicated. I don't think it's
necessary as of this writing. But revisit this if/when necessary. necessary as of this writing. But revisit this if/when necessary.
:param ast: The AST to duplicate Args:
:return: The duplicate AST ast: The AST to duplicate
Returns:
The duplicate AST
""" """
if isinstance(ast, AndObservationExpression): if isinstance(ast, AndObservationExpression):
result = AndObservationExpression([ result = AndObservationExpression([
@ -160,8 +163,11 @@ class ObservationExpressionTransformer(Transformer):
Invoke a transformer callback method based on the given ast root node Invoke a transformer callback method based on the given ast root node
type. type.
:param ast: The AST Args:
:return: The callback's result ast: The AST
Returns:
The callback's result
""" """
dispatch_name = self._DISPATCH_NAME_MAP.get(type(ast)) 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 the right does not "contain" the left. You would need two A's on the
right. right.
:param exprs_containee: The expressions we want to check for containment Args:
:param exprs_container: The expressions acting as the "container" exprs_containee: The expressions we want to check for containment
:return: True if the containee is contained in the container; False if exprs_container: The expressions acting as the "container"
not
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 # 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 in the container (rhs), B follows A, so it "contains" the lhs even
though there is other stuff mixed in. though there is other stuff mixed in.
:param exprs_containee: The expressions we want to check for containment Args:
:param exprs_container: The expressions acting as the "container" exprs_containee: The expressions we want to check for containment
:return: True if the containee is contained in the container; False if exprs_container: The expressions acting as the "container"
not
Returns:
True if the containee is contained in the container; False if not
""" """
ee_iter = iter(exprs_containee) ee_iter = iter(exprs_containee)

View File

@ -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 index path step; _ANY_KEY matches any key path step, and _ANY matches any
path step. path step.
:param object_path: An ObjectPath instance Args:
:param path_pattern: An iterable giving the pattern path steps object_path: An ObjectPath instance
:return: True if the path matches the pattern; False if not 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) 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 Retain the high-order 'prefix_size' bits from ip_bytes, and zero out the
remaining low-order bits. This side-effects ip_bytes. remaining low-order bits. This side-effects ip_bytes.
:param ip_bytes: A mutable byte sequence (e.g. a bytearray) Args:
:param prefix_size: An integer prefix size ip_bytes: A mutable byte sequence (e.g. a bytearray)
prefix_size: An integer prefix size
""" """
addr_size_bytes = len(ip_bytes) addr_size_bytes = len(ip_bytes)
addr_size_bits = 8 * addr_size_bytes addr_size_bits = 8 * addr_size_bytes
@ -99,7 +103,8 @@ def windows_reg_key(comp_expr):
being compared. This enables case-insensitive comparisons between two being compared. This enables case-insensitive comparisons between two
patterns, for those values. This side-effects the given AST. patterns, for those values. This side-effects the given AST.
:param comp_expr: A _ComparisonExpression object whose type is Args:
comp_expr: A _ComparisonExpression object whose type is
windows-registry-key windows-registry-key
""" """
if _path_is(comp_expr.lhs, ("key",)) \ if _path_is(comp_expr.lhs, ("key",)) \
@ -119,7 +124,8 @@ def ipv4_addr(comp_expr):
This side-effects the given AST. 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",)): if _path_is(comp_expr.lhs, ("value",)):
value = comp_expr.rhs.value value = comp_expr.rhs.value
@ -179,7 +185,8 @@ def ipv6_addr(comp_expr):
This side-effects the given AST. 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",)): if _path_is(comp_expr.lhs, ("value",)):
value = comp_expr.rhs.value value = comp_expr.rhs.value

View File

@ -11,9 +11,9 @@ import stix2.exceptions
from .constants import ( from .constants import (
ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS, ATTACK_PATTERN_ID, ATTACK_PATTERN_KWARGS, CAMPAIGN_ID, CAMPAIGN_KWARGS,
FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS, FAKE_TIME, IDENTITY_ID, IDENTITY_KWARGS, INDICATOR_ID, INDICATOR_KWARGS,
LOCATION_ID, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS, REPORT_ID, LOCATION_ID, LOCATION_KWARGS, MALWARE_ID, MALWARE_KWARGS, RELATIONSHIP_IDS,
REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID, TOOL_KWARGS, REPORT_ID, REPORT_KWARGS, THREAT_ACTOR_ID, THREAT_ACTOR_KWARGS, TOOL_ID,
VULNERABILITY_ID, VULNERABILITY_KWARGS, TOOL_KWARGS, VULNERABILITY_ID, VULNERABILITY_KWARGS,
) )
FS_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "stix2_data") 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(): def test_semantic_equivalence_on_same_location1():
LOCATION_KWARGS = dict(latitude=45, longitude=179) location_kwargs = dict(latitude=45, longitude=179)
loc1 = 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) loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
env = stix2.Environment().semantically_equivalent(loc1, loc2) env = stix2.Environment().semantically_equivalent(loc1, loc2)
assert round(env) == 100 assert round(env) == 100
def test_semantic_equivalence_on_same_location2(): def test_semantic_equivalence_on_same_location2():
LOCATION_KWARGS = dict( location_kwargs = dict(
latitude=38.889, latitude=38.889,
longitude=-77.023, longitude=-77.023,
region="northern-america", region="northern-america",
country="us", country="us",
) )
loc1 = 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) loc2 = stix2.v21.Location(id=LOCATION_ID, **location_kwargs)
env = stix2.Environment().semantically_equivalent(loc1, loc2) env = stix2.Environment().semantically_equivalent(loc1, loc2)
assert round(env) == 100 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(): def test_semantic_equivalence_on_same_malware():
malw1 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS) malw1 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)
malw2 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS) malw2 = stix2.v21.Malware(id=MALWARE_ID, **MALWARE_KWARGS)

View File

@ -5,7 +5,9 @@ import pytz
import stix2 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 = """{ EXPECTED_SIGHTING = """{
"type": "sighting", "type": "sighting",
@ -15,7 +17,8 @@ EXPECTED_SIGHTING = """{
"modified": "2016-04-06T20:06:37.000Z", "modified": "2016-04-06T20:06:37.000Z",
"sighting_of_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7", "sighting_of_ref": "indicator--a740531e-63ff-4e49-a9e1-a0a3eed0e3e7",
"where_sighted_refs": [ "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, created=now,
modified=now, modified=now,
sighting_of_ref=INDICATOR_ID, sighting_of_ref=INDICATOR_ID,
where_sighted_refs=[IDENTITY_ID], where_sighted_refs=[IDENTITY_ID, LOCATION_ID],
) )
assert str(s) == EXPECTED_SIGHTING assert str(s) == EXPECTED_SIGHTING
@ -101,6 +104,7 @@ def test_create_sighting_from_objects_rather_than_ids(malware): # noqa: F811
"type": "sighting", "type": "sighting",
"where_sighted_refs": [ "where_sighted_refs": [
IDENTITY_ID, 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.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.modified == dt.datetime(2016, 4, 6, 20, 6, 37, tzinfo=pytz.utc)
assert sighting.sighting_of_ref == INDICATOR_ID assert sighting.sighting_of_ref == INDICATOR_ID
assert sighting.where_sighted_refs == [IDENTITY_ID] assert sighting.where_sighted_refs == [IDENTITY_ID, LOCATION_ID]

View File

@ -124,11 +124,11 @@ class MarkingDefinition(_STIXBase20, _MarkingsMixin):
('id', IDProperty(_type, spec_version='2.0')), ('id', IDProperty(_type, spec_version='2.0')),
('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')), ('created_by_ref', ReferenceProperty(valid_types='identity', spec_version='2.0')),
('created', TimestampProperty(default=lambda: NOW)), ('created', TimestampProperty(default=lambda: NOW)),
('definition_type', StringProperty(required=True)),
('definition', MarkingProperty(required=True)),
('external_references', ListProperty(ExternalReference)), ('external_references', ListProperty(ExternalReference)),
('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))), ('object_marking_refs', ListProperty(ReferenceProperty(valid_types='marking-definition', spec_version='2.0'))),
('granular_markings', ListProperty(GranularMarking)), ('granular_markings', ListProperty(GranularMarking)),
('definition_type', StringProperty(required=True)),
('definition', MarkingProperty(required=True)),
]) ])
def __init__(self, **kwargs): def __init__(self, **kwargs):

View File

@ -937,6 +937,7 @@ def CustomObservable(type='x-custom-observable', properties=None, id_contrib_pro
def wrapper(cls): def wrapper(cls):
_properties = list(itertools.chain.from_iterable([ _properties = list(itertools.chain.from_iterable([
[('type', TypeProperty(type, spec_version='2.1'))], [('type', TypeProperty(type, spec_version='2.1'))],
[('spec_version', StringProperty(fixed='2.1'))],
[('id', IDProperty(type, spec_version='2.1'))], [('id', IDProperty(type, spec_version='2.1'))],
[('spec_version', StringProperty(fixed='2.1'))], [('spec_version', StringProperty(fixed='2.1'))],
properties, properties,

View File

@ -88,7 +88,7 @@ class Sighting(_RelationshipObject):
('count', IntegerProperty(min=0, max=999999999)), ('count', IntegerProperty(min=0, max=999999999)),
('sighting_of_ref', ReferenceProperty(valid_types="SDO", spec_version='2.1', required=True)), ('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'))), ('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()), ('summary', BooleanProperty()),
('revoked', BooleanProperty(default=lambda: False)), ('revoked', BooleanProperty(default=lambda: False)),
('labels', ListProperty(StringProperty)), ('labels', ListProperty(StringProperty)),

View File

@ -1 +1 @@
__version__ = "2.0.2" __version__ = "2.1.0"