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
=========
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.

View File

@ -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,

View File

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

View File

@ -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):

View File

@ -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,

View File

@ -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:

View File

@ -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) \

View File

@ -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)

View File

@ -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

View File

@ -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)

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
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

View File

@ -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)

View File

@ -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]

View File

@ -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):

View File

@ -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,

View File

@ -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)),

View File

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