MarkAsEvidenceBySearch
Search entries in the war room for the pattern text, and mark them as evidence.
python · UpdateEntriesBySearch
Source
import fnmatch import re from collections.abc import Iterator from typing import Any import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 def to_string(value: Any) -> Optional[str]: if isinstance(value, list | dict) or value is None: return None try: return str(value) except ValueError: return None def to_entry_type_code(name: str) -> int: etype = {"NOTE": EntryType.NOTE, "ERROR": EntryType.ERROR}.get(name) if etype is None: raise ValueError(f"Invalid entry type: {name}") return etype def build_pattern(pattern_algorithm: str, pattern: str, case_insensitive: bool) -> re.Pattern[str]: """ Build a matching object from the pattern given. :param pattern_algorithm: A pattern matching algorithm. :param pattern: A pattern text. :param case_insensitive: True if the matching is performed in case-insensitive, False otherwise. :return A matching object built. """ if pattern_algorithm == "basic": pattern = re.escape(pattern) elif pattern_algorithm == "wildcard": pattern = fnmatch.translate(pattern) elif pattern_algorithm == "regex": pass else: raise ValueError(f"Invalid pattern algorithm: {pattern_algorithm}") return re.compile(pattern, re.IGNORECASE if case_insensitive else 0) class EntryFilter: def __init__( self, include_pattern: re.Pattern[str], exclude_pattern: Optional[re.Pattern[str]], node_paths: list[str], filter_entry_formats: list[str], filter_entry_types: list[str], ): """ Initialize the filter with the matching conditions. :param include_pattern: A pattern to perform matching. :param exclude_pattern: A pattern to exclude. :param node_paths: The list of node path of entries to which the pattern matching is performed. :param filter_entry_formats: The list of entry format to filter entries. :param filter_entry_types: The list of entry type to filter entries. """ self.__include_pattern = include_pattern self.__exclude_pattern = exclude_pattern self.__node_paths = node_paths self.__filter_entry_formats = filter_entry_formats self.__filter_entry_types = [to_entry_type_code(f) for f in filter_entry_types] # Check content format name for f in filter_entry_formats: if not EntryFormat.is_valid_type(f): raise ValueError(f"Invalid entry format: {f}") def match(self, entry: dict[str, Any]) -> Optional[tuple[re.Match, str]]: """ Search the entry for the pattern. :param entry: The entry data. :return: re.Match and the target string if the pattern matched with the entry, None otherwise. """ def iterate_value(value: Any) -> Iterator[Any]: if isinstance(value, list): for v in value: yield from iterate_value(v) elif isinstance(value, dict): for _k, v in value.items(): yield from iterate_value(v) else: yield value if self.__filter_entry_types and entry.get("Type") not in self.__filter_entry_types: return None if self.__filter_entry_formats and entry.get("ContentsFormat") not in self.__filter_entry_formats: return None matched = None for node_path in self.__node_paths: for val in iterate_value(demisto.get(entry, node_path)): s = to_string(val) if s is not None: if self.__exclude_pattern and self.__exclude_pattern.search(s): return None if not matched: m = self.__include_pattern.search(s) if m: matched = (m, s) return matched class Entry: def __init__(self, entry: dict[str, Any], match: Optional[re.Match], value_matched: Optional[str]): self.entry = entry self.match = match self.value_matched = value_matched def iterate_entries( incident_id: Optional[str], query_filter: dict[str, Any], entry_filter: Optional[EntryFilter] = None ) -> Iterator[Entry]: """ Iterate war room entries :param incident_id: The incident ID to search entries from. :param query_filter: Filters to search entries. :param entry_filter: Filters to filter entries. :return: An iterator to retrieve entries. """ query_filter = dict(**query_filter) first_id = 1 while True: query_filter["firstId"] = str(first_id) ents = demisto.executeCommand("getEntries", assign_params(id=incident_id, filter=query_filter)) if not ents: break if is_error(ents[0]): if first_id == 1: return_error("Unable to retrieve entries") break for ent in ents: if not entry_filter: yield Entry(ent, None, None) else: match = entry_filter.match(ent) if match: yield Entry(ent, match[0], match[1]) # Set the next ID last_id = ent["ID"] m = re.match("([0-9]+)", last_id) if not m: raise ValueError(f"Invalid entry ID: {last_id}") next_id = int(m[1]) + 1 if next_id <= first_id: break first_id = next_id def main(): args = demisto.args() build_pattern_args = assign_params( pattern_algorithm=args.get("algorithm", "basic"), case_insensitive=argToBoolean(args.get("case_insensitive", False)) ) build_pattern_args["pattern"] = args["pattern"] include_pattern = build_pattern(**build_pattern_args) exclude_pattern = None if args.get("exclude_pattern"): build_pattern_args["pattern"] = args["exclude_pattern"] exclude_pattern = build_pattern(**build_pattern_args) filter_options = argToList(args.get("filter_options", [])) output_option = args.get("summary", "basic") exclude_ids = [] if "exclude_this_entry" in filter_options: exclude_ids.append(demisto.parentEntry()["id"]) ents = [] for entry in iterate_entries( incident_id=args.get("incident_id"), query_filter=assign_params(categories=argToList(args.get("filter_categories")), tags=argToList(args.get("filter_tags"))), entry_filter=EntryFilter( include_pattern=include_pattern, exclude_pattern=exclude_pattern, node_paths=argToList(args.get("node_paths", "Contents")), filter_entry_formats=argToList(args.get("filter_entry_formats", [])), filter_entry_types=argToList(args.get("filter_entry_types", [])), ), ): if entry.entry["ID"] not in exclude_ids: rent = {"ID": entry.entry["ID"]} if output_option == "verbose" and entry.match: rent["Where"] = entry.match[0][:128] rent["Text"] = entry.value_matched ents.append(rent) if "first_entry" in filter_options: if "last_entry" in filter_options: del ents[1:-1] else: ents = ents[:1] elif "last_entry" in filter_options: ents = ents[-1:] if not ents: return_outputs("No entries matched") else: dry_run = argToBoolean(args.get("dry_run", False)) tags = argToList(args.get("tags", [])) for ent in ents: ent["Tags"] = ",".join(tags) if not dry_run: description = args.get("description", "") for ent in ents: entry_id = ent["ID"] res = demisto.executeCommand("markAsEvidence", {"id": entry_id, "tags": ent["Tags"], "description": description}) if not res or is_error(res[0]): return_error(f"Failed to mark an entrie as evidence: entryID={entry_id}") md = f"**Matched entries:** {len(ents)}" if output_option != "quiet": header = assign_params( ID="Entry ID", Tags="Tags", Where="Where" if output_option == "verbose" else None, Text="Text" if output_option == "verbose" else None, ) md += "\n" + tblToMd("", ents, headers=header.keys(), headerTransform=lambda h: header.get(h, "")) return_outputs(md) if __name__ in ("__builtin__", "builtins"): main()
README
Search entries in the war room for the pattern text, and mark them as evidence.
Script Data
| Name | Description |
|---|---|
| Script Type | python |
| Tags | Utility |
Inputs
| Argument Name | Description |
|---|---|
| incident_id | The incident ID to search entries from. If not given, the current incident is used. |
| tags | The list of tags to set |
| description | Evidence description |
| filter_categories | The list of categories of source entries |
| filter_options | The options to filter entries |
| filter_tags | The tags to filter entries |
| filter_entry_types | The list of entry type of source entries |
| filter_entry_formats | The list of entry format of source entries |
| algorithm | The pattern matching algorithm |
| pattern | The pattern text to search |
| exclude_pattern | The pattern text to exclude entries matched |
| node_paths | The list of node path of entries to search from |
| case_insensitive | true if the pattern is matched in case-insensitive, false otherwise. |
| dry_run | true if it only search the entries and doesn’t update, false otherwise. |
| summary | The flag to control the output of results |
–
Outputs
There are no outputs for this script.