Source code for colander_data_converter.formats.stix2.utils
"""
Utility functions for STIX2 to Colander conversion and vice versa.
"""
from typing import Dict, Any, Optional
from uuid import uuid4, UUID
[docs]
def extract_uuid_from_stix2_id(stix2_id: str) -> UUID:
"""
Extract a UUID from a STIX2 ID.
This function parses a STIX2 identifier string to extract the UUID portion.
STIX2 IDs follow the format ``{type}--{uuid}``, where the UUID is the part
after the double dash delimiter.
:param stix2_id: The STIX2 ID to extract the UUID from
:type stix2_id: str
:return: The extracted UUID, or a new UUID if extraction fails
:rtype: UUID
.. important::
If the input format is invalid or UUID extraction fails, a new random
UUID is generated and returned instead of raising an exception.
Examples:
>>> # Valid STIX2 ID with UUID
>>> stix_id = "indicator--44af6c9f-4bbc-4984-a74b-1404d1ac07ea"
>>> uuid_obj = extract_uuid_from_stix2_id(stix_id)
>>> str(uuid_obj)
'44af6c9f-4bbc-4984-a74b-1404d1ac07ea'
>>> # Invalid STIX2 ID format (no delimiter)
>>> stix_id = "indicator-invalid-format"
>>> uuid_obj = extract_uuid_from_stix2_id(stix_id)
>>> isinstance(uuid_obj, UUID) # Returns a new random UUID
True
>>> # Invalid UUID part
>>> stix_id = "indicator--not-a-valid-uuid"
>>> uuid_obj = extract_uuid_from_stix2_id(stix_id)
>>> isinstance(uuid_obj, UUID) # Returns a new random UUID
True
"""
try:
if stix2_id and "--" in stix2_id:
# Extract the part after the "--" delimiter
uuid_part = stix2_id.split("--", 1)[1]
# Try to create a UUID from the extracted part
return UUID(uuid_part)
except (ValueError, IndexError):
# If anything goes wrong, return a new UUID
pass
return uuid4()
[docs]
def extract_stix2_pattern_name(stix2_pattern: str) -> Optional[str]:
"""
Extract the name from a STIX 2 pattern string.
This function parses STIX2 pattern expressions to extract the field name
portion before the equality operator. It removes brackets and extracts
the left side of the comparison.
:param stix2_pattern: The STIX 2 pattern string to extract the name from
:type stix2_pattern: str
:return: The extracted name or None if no name is found
:rtype: Optional[str]
.. note::
The function handles various STIX2 pattern formats including nested
hash references like ``file:hashes.'SHA-256'``.
Examples:
>>> pattern = "[ipv4-addr:value = '192.168.1.1']"
>>> extract_stix2_pattern_name(pattern)
'ipv4-addr:value'
>>> pattern = "[file:hashes.'SHA-256' = '123abc']"
>>> extract_stix2_pattern_name(pattern)
"file:hashes.'SHA-256'"
"""
_to_replace = [
("[", ""),
("]", ""),
]
if "=" not in stix2_pattern:
return ""
_stix2_pattern = stix2_pattern
for _replace in _to_replace:
_stix2_pattern = _stix2_pattern.replace(_replace[0], _replace[1])
return _stix2_pattern.split("=")[0].strip()
[docs]
def get_nested_value(obj: Dict[str, Any], path: str) -> Any:
"""
Get a value from a nested dictionary using a dot-separated path.
This function safely navigates through nested dictionaries using a
dot-separated path string. It returns the value at the specified path
or None if any part of the path is missing or invalid.
:param obj: The dictionary to get the value from
:type obj: Dict[str, Any]
:param path: The dot-separated path to the value
:type path: str
:return: The value at the specified path, or None if not found
:rtype: Any
.. warning::
This function returns None for missing paths rather than raising
exceptions. Check for None return values when path existence is critical.
Examples:
>>> data = {
... "user": {
... "profile": {
... "name": "John",
... "age": 30
... },
... "settings": {
... "theme": "dark"
... }
... }
... }
>>> get_nested_value(data, "user.profile.name")
'John'
>>> get_nested_value(data, "user.settings.theme")
'dark'
"""
if not path:
return None
parts = path.split(".")
current = obj
for part in parts:
if isinstance(current, dict) and part in current:
current = current[part]
else:
return None
return current
[docs]
def set_nested_value(obj: Dict[str, Any], path: str, value: Any) -> None:
"""
Set a value in a nested dictionary using a dot-separated path.
This function creates nested dictionaries as needed to set a value at
the specified dot-separated path. If intermediate dictionaries don't
exist, they are automatically created.
:param obj: The dictionary to set the value in
:type obj: Dict[str, Any]
:param path: The dot-separated path to the value
:type path: str
:param value: The value to set
:type value: Any
.. note::
The function modifies the input dictionary in-place and automatically
creates any missing intermediate dictionary levels.
Examples:
>>> data = {}
>>> set_nested_value(data, "user.profile.name", "John")
>>> data
{'user': {'profile': {'name': 'John'}}}
>>> # Update existing nested value
>>> data = {'user': {'settings': {'theme': 'light'}}}
>>> set_nested_value(data, "user.settings.theme", "dark")
>>> data
{'user': {'settings': {'theme': 'dark'}}}
>>> # Add new nested path to existing structure
>>> set_nested_value(data, "user.profile.age", 30)
>>> data
{'user': {'settings': {'theme': 'dark'}, 'profile': {'age': 30}}}
>>> # Empty path does nothing
>>> original = {'a': 1}
>>> set_nested_value(original, "", "value")
>>> original
{'a': 1}
"""
if not path:
return
parts = path.split(".")
current = obj
# Navigate to the parent of the final part
for part in parts[:-1]:
if part not in current:
current[part] = {}
current = current[part]
# Set the value at the final part
current[parts[-1]] = value