Source code for colander_data_converter.converters.stix2.utils

  1"""
  2Utility functions for STIX2 to Colander conversion and vice versa.
  3"""
  4
  5from typing import Dict, Any, Optional
  6from uuid import uuid4, UUID
  7
  8from pydantic import UUID4
  9
 10
[docs] 11def extract_uuid_from_stix2_id(stix2_id: str) -> UUID: 12 """ 13 Extract a UUID from a STIX2 ID. 14 15 This function parses a STIX2 identifier string to extract the UUID portion. 16 STIX2 IDs follow the format ``{type}--{uuid}``, where the UUID is the part 17 after the double dash delimiter. 18 19 :param stix2_id: The STIX2 ID to extract the UUID from 20 :type stix2_id: str 21 :return: The extracted UUID, or a new UUID if extraction fails 22 :rtype: UUID 23 24 .. important:: 25 If the input format is invalid or UUID extraction fails, a new random 26 UUID is generated and returned instead of raising an exception. 27 28 Examples: 29 >>> # Valid STIX2 ID with UUID 30 >>> stix_id = "indicator--44af6c9f-4bbc-4984-a74b-1404d1ac07ea" 31 >>> uuid_obj = extract_uuid_from_stix2_id(stix_id) 32 >>> str(uuid_obj) 33 '44af6c9f-4bbc-4984-a74b-1404d1ac07ea' 34 35 >>> # Invalid STIX2 ID format (no delimiter) 36 >>> stix_id = "indicator-invalid-format" 37 >>> uuid_obj = extract_uuid_from_stix2_id(stix_id) 38 >>> isinstance(uuid_obj, UUID) # Returns a new random UUID 39 True 40 41 >>> # Invalid UUID part 42 >>> stix_id = "indicator--not-a-valid-uuid" 43 >>> uuid_obj = extract_uuid_from_stix2_id(stix_id) 44 >>> isinstance(uuid_obj, UUID) # Returns a new random UUID 45 True 46 """ 47 try: 48 if stix2_id and "--" in stix2_id: 49 # Extract the part after the "--" delimiter 50 uuid_part = stix2_id.split("--", 1)[1] 51 # Try to create a UUID from the extracted part 52 return UUID4(uuid_part, version=4) 53 except (ValueError, IndexError): 54 # If anything goes wrong, return a new UUID 55 pass 56 57 return uuid4()
58 59
[docs] 60def extract_stix2_pattern_name(stix2_pattern: str) -> Optional[str]: 61 """ 62 Extract the name from a STIX 2 pattern string. 63 64 This function parses STIX2 pattern expressions to extract the field name 65 portion before the equality operator. It removes brackets and extracts 66 the left side of the comparison. 67 68 :param stix2_pattern: The STIX 2 pattern string to extract the name from 69 :type stix2_pattern: str 70 :return: The extracted name or None if no name is found 71 :rtype: Optional[str] 72 73 .. note:: 74 The function handles various STIX2 pattern formats including nested 75 hash references like ``file:hashes.'SHA-256'``. 76 77 Examples: 78 >>> pattern = "[ipv4-addr:value = '192.168.1.1']" 79 >>> extract_stix2_pattern_name(pattern) 80 'ipv4-addr:value' 81 82 >>> pattern = "[file:hashes.'SHA-256' = '123abc']" 83 >>> extract_stix2_pattern_name(pattern) 84 "file:hashes.'SHA-256'" 85 """ 86 _to_replace = [ 87 ("[", ""), 88 ("]", ""), 89 ] 90 if "=" not in stix2_pattern: 91 return "" 92 _stix2_pattern = stix2_pattern 93 for _replace in _to_replace: 94 _stix2_pattern = _stix2_pattern.replace(_replace[0], _replace[1]) 95 return _stix2_pattern.split("=")[0].strip()
96 97
[docs] 98def get_nested_value(obj: Dict[str, Any], path: str) -> Any: 99 """ 100 Get a value from a nested dictionary using a dot-separated path. 101 102 This function safely navigates through nested dictionaries using a 103 dot-separated path string. It returns the value at the specified path 104 or None if any part of the path is missing or invalid. 105 106 :param obj: The dictionary to get the value from 107 :type obj: Dict[str, Any] 108 :param path: The dot-separated path to the value 109 :type path: str 110 :return: The value at the specified path, or None if not found 111 :rtype: Any 112 113 .. warning:: 114 This function returns None for missing paths rather than raising 115 exceptions. Check for None return values when path existence is critical. 116 117 Examples: 118 >>> data = { 119 ... "user": { 120 ... "profile": { 121 ... "name": "John", 122 ... "age": 30 123 ... }, 124 ... "settings": { 125 ... "theme": "dark" 126 ... } 127 ... } 128 ... } 129 >>> get_nested_value(data, "user.profile.name") 130 'John' 131 >>> get_nested_value(data, "user.settings.theme") 132 'dark' 133 """ 134 if not path: 135 return None 136 137 parts = path.split(".") 138 current = obj 139 140 for part in parts: 141 if isinstance(current, dict) and part in current: 142 current = current[part] 143 else: 144 return None 145 146 return current
147 148
[docs] 149def set_nested_value(obj: Dict[str, Any], path: str, value: Any) -> None: 150 """ 151 Set a value in a nested dictionary using a dot-separated path. 152 153 This function creates nested dictionaries as needed to set a value at 154 the specified dot-separated path. If intermediate dictionaries don't 155 exist, they are automatically created. 156 157 :param obj: The dictionary to set the value in 158 :type obj: Dict[str, Any] 159 :param path: The dot-separated path to the value 160 :type path: str 161 :param value: The value to set 162 :type value: Any 163 164 .. note:: 165 The function modifies the input dictionary in-place and automatically 166 creates any missing intermediate dictionary levels. 167 168 Examples: 169 >>> data = {} 170 >>> set_nested_value(data, "user.profile.name", "John") 171 >>> data 172 {'user': {'profile': {'name': 'John'}}} 173 174 >>> # Update existing nested value 175 >>> data = {'user': {'settings': {'theme': 'light'}}} 176 >>> set_nested_value(data, "user.settings.theme", "dark") 177 >>> data 178 {'user': {'settings': {'theme': 'dark'}}} 179 180 >>> # Add new nested path to existing structure 181 >>> set_nested_value(data, "user.profile.age", 30) 182 >>> data 183 {'user': {'settings': {'theme': 'dark'}, 'profile': {'age': 30}}} 184 185 >>> # Empty path does nothing 186 >>> original = {'a': 1} 187 >>> set_nested_value(original, "", "value") 188 >>> original 189 {'a': 1} 190 """ 191 if not path: 192 return 193 194 parts = path.split(".") 195 current = obj 196 197 # Navigate to the parent of the final part 198 for part in parts[:-1]: 199 if part not in current: 200 current[part] = {} 201 current = current[part] 202 203 # Set the value at the final part 204 current[parts[-1]] = value