diff --git a/mypy.ini b/mypy.ini index 9c8481b..0351d61 100644 --- a/mypy.ini +++ b/mypy.ini @@ -3,13 +3,11 @@ strict = True warn_return_any = False show_error_context = True pretty = True -exclude = feed-generator|examples +exclude = feed-generator|examples|pymisp/tools|pymisp/data|tests # Stuff to remove gradually -disallow_untyped_defs = False +# disallow_untyped_defs = False disallow_untyped_calls = False -check_untyped_defs = False -disable_error_code = attr-defined,type-arg,no-untyped-def [mypy-docs.source.*] diff --git a/pymisp/__init__.py b/pymisp/__init__.py index a38ad3c..9599bdc 100644 --- a/pymisp/__init__.py +++ b/pymisp/__init__.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) __version__ = importlib.metadata.version("pymisp") -def warning_2024(): +def warning_2024() -> None: if sys.version_info < (3, 10): warnings.warn(""" As our baseline system is the latest Ubuntu LTS, and Ubuntu LTS 22.04 has Python 3.10 available, @@ -62,3 +62,17 @@ try: logger.debug('pymisp loaded properly') except ImportError as e: logger.warning(f'Unable to load pymisp properly: {e}') + +__all__ = ['PyMISP', 'register_user', 'AbstractMISP', 'MISPTag', + 'MISPEvent', 'MISPAttribute', 'MISPObjectReference', 'MISPObjectAttribute', + 'MISPObject', 'MISPUser', 'MISPOrganisation', 'MISPSighting', 'MISPLog', + 'MISPShadowAttribute', 'MISPWarninglist', 'MISPTaxonomy', 'MISPNoticelist', + 'MISPObjectTemplate', 'MISPSharingGroup', 'MISPRole', 'MISPServer', 'MISPFeed', + 'MISPEventDelegation', 'MISPUserSetting', 'MISPInbox', 'MISPEventBlocklist', + 'MISPOrganisationBlocklist', 'MISPEventReport', 'MISPCorrelationExclusion', + 'MISPDecayingModel', 'MISPGalaxy', 'MISPGalaxyCluster', 'MISPGalaxyClusterElement', + 'MISPGalaxyClusterRelation', 'PyMISPError', 'NewEventError', 'NewAttributeError', + 'NoURL', 'NoKey', 'InvalidMISPObject', 'UnknownMISPObjectTemplate', 'PyMISPInvalidFormat', + 'EmailObject', 'FileObject', 'IPObject', 'DomainObject', 'URIObject', 'ASNObject', + 'Distribution', 'ThreatLevel', 'Analysis' + ] diff --git a/pymisp/abstract.py b/pymisp/abstract.py index 879079f..ec17e0f 100644 --- a/pymisp/abstract.py +++ b/pymisp/abstract.py @@ -30,7 +30,7 @@ logger = logging.getLogger('pymisp') resources_path = Path(__file__).parent / 'data' misp_objects_path = resources_path / 'misp-objects' / 'objects' with (resources_path / 'describeTypes.json').open('rb') as f: - describe_types = loads(f.read())['result'] + describe_types: dict[str, Any] = loads(f.read())['result'] class MISPFileCache: @@ -38,7 +38,7 @@ class MISPFileCache: @staticmethod @lru_cache(maxsize=150) - def _load_json(path: Path) -> dict | None: + def _load_json(path: Path) -> dict[str, Any] | None: if not path.exists(): return None with path.open('rb') as f: @@ -80,7 +80,7 @@ def _int_to_str(d: dict[str, Any]) -> dict[str, Any]: @deprecated(reason=" Use method default=pymisp_json_default instead of cls=MISPEncode", version='2.4.117', action='default') class MISPEncode(JSONEncoder): - def default(self, obj): + def default(self, obj: Any) -> dict[str, Any] | str: if isinstance(obj, AbstractMISP): return obj.jsonable() elif isinstance(obj, (datetime, date)): @@ -92,12 +92,12 @@ class MISPEncode(JSONEncoder): return JSONEncoder.default(self, obj) -class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): +class AbstractMISP(MutableMapping[str, Any], MISPFileCache, metaclass=ABCMeta): __resources_path = resources_path __misp_objects_path = misp_objects_path __describe_types = describe_types - def __init__(self, **kwargs: dict): + def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Abstract class for all the MISP objects. NOTE: Every method in every classes inheriting this one are doing changes in memory and do not modify data on a remote MISP instance. @@ -107,8 +107,8 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): super().__init__() self.__edited: bool = True # As we create a new object, we assume it is edited self.__not_jsonable: list[str] = [] - self._fields_for_feed: set - self.__self_defined_describe_types: dict | None = None + self._fields_for_feed: set[str] + self.__self_defined_describe_types: dict[str, Any] | None = None self.uuid: str if kwargs.get('force_timestamps') is not None: @@ -118,13 +118,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): self.__force_timestamps = False @property - def describe_types(self) -> dict: + def describe_types(self) -> dict[str, Any]: if self.__self_defined_describe_types: return self.__self_defined_describe_types return self.__describe_types @describe_types.setter - def describe_types(self, describe_types: dict): + def describe_types(self, describe_types: dict[str, Any]) -> None: self.__self_defined_describe_types = describe_types @property @@ -136,12 +136,12 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__misp_objects_path @misp_objects_path.setter - def misp_objects_path(self, misp_objects_path: str | Path): + def misp_objects_path(self, misp_objects_path: str | Path) -> None: if isinstance(misp_objects_path, str): misp_objects_path = Path(misp_objects_path) self.__misp_objects_path = misp_objects_path - def from_dict(self, **kwargs) -> None: + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Loading all the parameters as class properties, if they aren't `None`. This method aims to be called when all the properties requiring a special treatment are processed. @@ -154,7 +154,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # We load an existing dictionary, marking it an not-edited self.__edited = False - def update_not_jsonable(self, *args) -> None: + def update_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def] """Add entries to the __not_jsonable list""" self.__not_jsonable += args @@ -162,7 +162,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): """Set __not_jsonable to a new list""" self.__not_jsonable = args - def _remove_from_not_jsonable(self, *args) -> None: + def _remove_from_not_jsonable(self, *args) -> None: # type: ignore[no-untyped-def] """Remove the entries that are in the __not_jsonable list""" for entry in args: try: @@ -174,7 +174,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): """Load a JSON string""" self.from_dict(**loads(json_string)) - def to_dict(self, json_format: bool = False) -> dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: """Dump the class to a dictionary. This method automatically removes the timestamp recursively in every object that has been edited is order to let MISP update the event accordingly.""" @@ -216,11 +216,11 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): to_return = _int_to_str(to_return) return to_return - def jsonable(self) -> dict: + def jsonable(self) -> dict[str, Any]: """This method is used by the JSON encoder""" return self.to_dict() - def _to_feed(self) -> dict: + def _to_feed(self) -> dict[str, Any]: if not hasattr(self, '_fields_for_feed') or not self._fields_for_feed: raise PyMISPError('Unable to export in the feed format, _fields_for_feed is missing.') if hasattr(self, '_set_default') and callable(self._set_default): @@ -256,7 +256,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return dumps(self, default=pymisp_json_default, sort_keys=sort_keys, indent=indent) - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: try: if key[0] != '_' and key not in self.__not_jsonable: return self.__dict__[key] @@ -265,13 +265,13 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): # Expected by pop and other dict-related methods raise KeyError - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: setattr(self, key, value) - def __delitem__(self, key): + def __delitem__(self, key: str) -> None: delattr(self, key) - def __iter__(self): + def __iter__(self) -> Any: '''When we call **self, skip keys: * starting with _ * in __not_jsonable @@ -290,7 +290,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__force_timestamps @force_timestamp.setter - def force_timestamp(self, force: bool): + def force_timestamp(self, force: bool) -> None: self.__force_timestamps = force @property @@ -310,14 +310,14 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return self.__edited @edited.setter - def edited(self, val: bool): + def edited(self, val: bool) -> None: """Set the edit flag""" if isinstance(val, bool): self.__edited = val else: raise PyMISPError('edited can only be True or False') - def __setattr__(self, name: str, value: Any): + def __setattr__(self, name: str, value: Any) -> None: if name[0] != '_' and not self.__edited and name in self: # The private members don't matter # If we already have a key with that name, we're modifying it. @@ -331,7 +331,7 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): return int(d) return int(d.timestamp()) - def _add_tag(self, tag: str | MISPTag | Mapping | None = None, **kwargs): + def _add_tag(self, tag: str | MISPTag | Mapping[str, Any] | None = None, **kwargs): # type: ignore[no-untyped-def] """Add a tag to the attribute (by name or a MISPTag object)""" if isinstance(tag, str): misp_tag = MISPTag() @@ -351,14 +351,14 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): self.edited = True return misp_tag - def _set_tags(self, tags: list[MISPTag]): + def _set_tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" if all(isinstance(x, MISPTag) for x in tags): self.Tag = tags else: raise PyMISPInvalidFormat('All the attributes have to be of type MISPTag.') - def __eq__(self, other) -> bool: + def __eq__(self, other: object) -> bool: if isinstance(other, AbstractMISP): return self.to_dict() == other.to_dict() elif isinstance(other, dict): @@ -372,21 +372,21 @@ class AbstractMISP(MutableMapping, MISPFileCache, metaclass=ABCMeta): class MISPTag(AbstractMISP): - _fields_for_feed: set = {'name', 'colour', 'relationship_type', 'local'} + _fields_for_feed: set[str] = {'name', 'colour', 'relationship_type', 'local'} - def __init__(self, **kwargs: dict): + def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.name: str self.exportable: bool self.local: bool self.relationship_type: str | None - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if kwargs.get('Tag'): - kwargs = kwargs.get('Tag') + kwargs = kwargs.get('Tag') # type: ignore[assignment] super().from_dict(**kwargs) - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'relationship_type'): self.relationship_type = '' if not hasattr(self, 'colour'): @@ -394,14 +394,14 @@ class MISPTag(AbstractMISP): if not hasattr(self, 'local'): self.local = False - def _to_feed(self, with_local: bool = True) -> dict: + def _to_feed(self, with_local: bool = True) -> dict[str, Any]: if hasattr(self, 'exportable') and not self.exportable: return {} if with_local is False and hasattr(self, 'local') and self.local: return {} return super()._to_feed() - def delete(self): + def delete(self) -> None: self.deleted = True self.edited = True @@ -412,7 +412,7 @@ class MISPTag(AbstractMISP): # UUID, datetime, date and Enum is serialized by ORJSON by default -def pymisp_json_default(obj: AbstractMISP | datetime | date | Enum | UUID) -> dict | str: +def pymisp_json_default(obj: AbstractMISP | datetime | date | Enum | UUID) -> dict[str, Any] | str: if isinstance(obj, AbstractMISP): return obj.jsonable() elif isinstance(obj, (datetime, date)): diff --git a/pymisp/api.py b/pymisp/api.py index 771f691..583239c 100644 --- a/pymisp/api.py +++ b/pymisp/api.py @@ -13,8 +13,8 @@ from uuid import UUID import warnings import sys import copy -import urllib3 from io import BytesIO, StringIO +from importlib.metadata import version try: # orjson is optional dependency that speedups parsing and encoding JSON @@ -64,7 +64,7 @@ ToIDSType = TypeVar('ToIDSType', str, int, bool) logger = logging.getLogger('pymisp') -def get_uuid_or_id_from_abstract_misp(obj: AbstractMISP | int | str | UUID | dict) -> str | int: +def get_uuid_or_id_from_abstract_misp(obj: AbstractMISP | int | str | UUID | dict[str, Any]) -> str | int: """Extract the relevant ID accordingly to the given type passed as parameter""" if isinstance(obj, UUID): return str(obj) @@ -100,7 +100,7 @@ def register_user(misp_url: str, email: str, org_id: str | None = None, org_name: str | None = None, message: str | None = None, custom_perms: str | None = None, perm_sync: bool = False, perm_publish: bool = False, perm_admin: bool = False, - verify: bool = True) -> dict: + verify: bool = True) -> dict[str, Any]: """Ask for the creation of an account for the user with the given email address""" data = copy.deepcopy(locals()) if organisation: @@ -125,7 +125,7 @@ def brotli_supported() -> bool: patch: int # urllib >= 1.25.1 includes brotli support - version_splitted = urllib3.__version__.split('.') # noqa: F811 + version_splitted = version('urllib3').split('.') # noqa: F811 if len(version_splitted) == 2: major, minor = version_splitted # type: ignore patch = 0 @@ -224,7 +224,7 @@ class PyMISP: if e := user_infos.get('errors'): raise PyMISPError(f'Unable to get the user settings: {e}') raise PyMISPError(f'Unexpected error when initializing the connection: {user_infos}') - elif len(user_infos) == 3: + elif isinstance(user_infos, tuple) and len(user_infos) == 3: self._current_user, self._current_role, self._current_user_settings = user_infos else: raise PyMISPError(f'Unexpected error when initializing the connection: {user_infos}') @@ -243,7 +243,7 @@ class PyMISP: self.category_type_mapping = self.describe_types['category_type_mappings'] self.sane_default = self.describe_types['sane_defaults'] - def remote_acl(self, debug_type: str = 'findMissingFunctionNames') -> dict: + def remote_acl(self, debug_type: str = 'findMissingFunctionNames') -> dict[str, Any]: """This should return an empty list, unless the ACL is outdated. :param debug_type: printAllFunctionNames, findMissingFunctionNames, or printRoleAccess @@ -252,19 +252,19 @@ class PyMISP: return self._check_json_response(response) @property - def describe_types_local(self) -> dict: + def describe_types_local(self) -> dict[str, Any]: '''Returns the content of describe types from the package''' return describe_types @property - def describe_types_remote(self) -> dict: + def describe_types_remote(self) -> dict[str, Any]: '''Returns the content of describe types from the remote instance''' response = self._prepare_request('GET', 'attributes/describeTypes.json') remote_describe_types = self._check_json_response(response) return remote_describe_types['result'] @property - def recommended_pymisp_version(self) -> dict: + def recommended_pymisp_version(self) -> dict[str, Any]: """Returns the recommended API version from the server""" # Sine MISP 2.4.146 is recommended PyMISP version included in getVersion call misp_version = self.misp_instance_version @@ -275,17 +275,17 @@ class PyMISP: return self._check_json_response(response) @property - def version(self) -> dict: + def version(self) -> dict[str, Any]: """Returns the version of PyMISP you're currently using""" return {'version': __version__} @property - def pymisp_version_master(self) -> dict: + def pymisp_version_master(self) -> dict[str, Any]: """PyMISP version as defined in the main repository""" return self.pymisp_version_main @property - def pymisp_version_main(self) -> dict: + def pymisp_version_main(self) -> dict[str, Any]: """Get the most recent version of PyMISP from github""" r = requests.get('https://raw.githubusercontent.com/MISP/PyMISP/main/pyproject.toml') if r.status_code == 200: @@ -294,13 +294,13 @@ class PyMISP: return {'error': 'Impossible to retrieve the version of the main branch.'} @cached_property - def misp_instance_version(self) -> dict: + def misp_instance_version(self) -> dict[str, Any]: """Returns the version of the instance.""" response = self._prepare_request('GET', 'servers/getVersion') return self._check_json_response(response) @property - def misp_instance_version_master(self) -> dict: + def misp_instance_version_master(self) -> dict[str, Any]: """Get the most recent version from github""" r = requests.get('https://raw.githubusercontent.com/MISP/MISP/2.4/VERSION.json') if r.status_code == 200: @@ -308,12 +308,12 @@ class PyMISP: return {'version': '{}.{}.{}'.format(master_version['major'], master_version['minor'], master_version['hotfix'])} return {'error': 'Impossible to retrieve the version of the master branch.'} - def update_misp(self) -> dict: + def update_misp(self) -> dict[str, Any]: """Trigger a server update""" response = self._prepare_request('POST', 'servers/update') return self._check_json_response(response) - def set_server_setting(self, setting: str, value: str | int | bool, force: bool = False) -> dict: + def set_server_setting(self, setting: str, value: str | int | bool, force: bool = False) -> dict[str, Any]: """Set a setting on the MISP instance :param setting: server setting name @@ -324,7 +324,7 @@ class PyMISP: response = self._prepare_request('POST', f'servers/serverSettingsEdit/{setting}', data=data) return self._check_json_response(response) - def get_server_setting(self, setting: str) -> dict: + def get_server_setting(self, setting: str) -> dict[str, Any]: """Get a setting from the MISP instance :param setting: server setting name @@ -332,17 +332,17 @@ class PyMISP: response = self._prepare_request('GET', f'servers/getSetting/{setting}') return self._check_json_response(response) - def server_settings(self) -> dict: + def server_settings(self) -> dict[str, Any]: """Get all the settings from the server""" response = self._prepare_request('GET', 'servers/serverSettings') return self._check_json_response(response) - def restart_workers(self) -> dict: + def restart_workers(self) -> dict[str, Any]: """Restart all the workers""" response = self._prepare_request('POST', 'servers/restartWorkers') return self._check_json_response(response) - def db_schema_diagnostic(self) -> dict: + def db_schema_diagnostic(self) -> dict[str, Any]: """Get the schema diagnostic""" response = self._prepare_request('GET', 'servers/dbSchemaDiagnostic') return self._check_json_response(response) @@ -353,14 +353,14 @@ class PyMISP: # ## BEGIN Event ## - def events(self, pythonify: bool = False) -> dict | list[MISPEvent]: + def events(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEvent] | list[dict[str, Any]]: """Get all the events from the MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/getEvents :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'events/index') - events_r = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in events_r: + events_r = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(events_r, dict): return events_r to_return = [] for event in events_r: @@ -370,9 +370,9 @@ class PyMISP: return to_return def get_event(self, event: MISPEvent | int | str | UUID, - deleted: bool | int | list = False, + deleted: bool | int | list[int] = False, extended: bool | int = False, - pythonify: bool = False) -> dict | MISPEvent: + pythonify: bool = False) -> dict[str, Any] | MISPEvent: """Get an event from a MISP instance. Includes collections like Attribute, EventReport, Feed, Galaxy, Object, Tag, etc. so the response size may be large : https://www.misp-project.org/openapi/#tag/Events/operation/getEventById @@ -408,7 +408,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'events/view/{event_id}') return self._check_head_response(r) - def add_event(self, event: MISPEvent, pythonify: bool = False, metadata: bool = False) -> dict | MISPEvent: + def add_event(self, event: MISPEvent, pythonify: bool = False, metadata: bool = False) -> dict[str, Any] | MISPEvent: """Add a new event on a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/addEvent :param event: event to add @@ -424,7 +424,7 @@ class PyMISP: return e def update_event(self, event: MISPEvent, event_id: int | None = None, pythonify: bool = False, - metadata: bool = False) -> dict | MISPEvent: + metadata: bool = False) -> dict[str, Any] | MISPEvent: """Update an event on a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/editEvent :param event: event to update @@ -444,7 +444,7 @@ class PyMISP: e.load(updated_event) return e - def delete_event(self, event: MISPEvent | int | str | UUID) -> dict: + def delete_event(self, event: MISPEvent | int | str | UUID) -> dict[str, Any]: """Delete an event from a MISP instance: https://www.misp-project.org/openapi/#tag/Events/operation/deleteEvent :param event: event to delete @@ -453,7 +453,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/delete/{event_id}') return self._check_json_response(response) - def publish(self, event: MISPEvent | int | str | UUID, alert: bool = False) -> dict: + def publish(self, event: MISPEvent | int | str | UUID, alert: bool = False) -> dict[str, Any]: """Publish the event with one single HTTP POST: https://www.misp-project.org/openapi/#tag/Events/operation/publishEvent :param event: event to publish @@ -466,7 +466,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/publish/{event_id}') return self._check_json_response(response) - def unpublish(self, event: MISPEvent | int | str | UUID) -> dict: + def unpublish(self, event: MISPEvent | int | str | UUID) -> dict[str, Any]: """Unpublish the event with one single HTTP POST: https://www.misp-project.org/openapi/#tag/Events/operation/unpublishEvent :param event: event to unpublish @@ -475,7 +475,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/unpublish/{event_id}') return self._check_json_response(response) - def contact_event_reporter(self, event: MISPEvent | int | str | UUID, message: str) -> dict: + def contact_event_reporter(self, event: MISPEvent | int | str | UUID, message: str) -> dict[str, Any]: """Send a message to the reporter of an event :param event: event with reporter to contact @@ -491,7 +491,7 @@ class PyMISP: # ## BEGIN Event Report ### def get_event_report(self, event_report: MISPEventReport | int | str | UUID, - pythonify: bool = False) -> dict | MISPEventReport: + pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Get an event report from a MISP instance :param event_report: event report to get @@ -507,15 +507,15 @@ class PyMISP: return er def get_event_reports(self, event_id: int | str, - pythonify: bool = False) -> dict | list[MISPEventReport]: + pythonify: bool = False) -> dict[str, Any] | list[MISPEventReport] | list[dict[str, Any]]: """Get event report from a MISP instance that are attached to an event ID :param event_id: event id to get the event reports for :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. """ r = self._prepare_request('GET', f'eventReports/index/event_id:{event_id}') - event_reports = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in event_reports: + event_reports = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(event_reports, dict): return event_reports to_return = [] for event_report in event_reports: @@ -524,7 +524,7 @@ class PyMISP: to_return.append(er) return to_return - def add_event_report(self, event: MISPEvent | int | str | UUID, event_report: MISPEventReport, pythonify: bool = False) -> dict | MISPEventReport: + def add_event_report(self, event: MISPEvent | int | str | UUID, event_report: MISPEventReport, pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Add an event report to an existing MISP event :param event: event to extend @@ -540,7 +540,7 @@ class PyMISP: er.from_dict(**new_event_report) return er - def update_event_report(self, event_report: MISPEventReport, event_report_id: int | None = None, pythonify: bool = False) -> dict | MISPEventReport: + def update_event_report(self, event_report: MISPEventReport, event_report_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPEventReport: """Update an event report on a MISP instance :param event_report: event report to update @@ -559,7 +559,7 @@ class PyMISP: er.from_dict(**updated_event_report) return er - def delete_event_report(self, event_report: MISPEventReport | int | str | UUID, hard: bool = False) -> dict: + def delete_event_report(self, event_report: MISPEventReport | int | str | UUID, hard: bool = False) -> dict[str, Any]: """Delete an event report from a MISP instance :param event_report: event report to delete @@ -577,7 +577,7 @@ class PyMISP: # ## BEGIN Object ### - def get_object(self, misp_object: MISPObject | int | str | UUID, pythonify: bool = False) -> dict | MISPObject: + def get_object(self, misp_object: MISPObject | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPObject: """Get an object from the remote MISP instance: https://www.misp-project.org/openapi/#tag/Objects/operation/getObjectById :param misp_object: object to get @@ -601,7 +601,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'objects/view/{object_id}') return self._check_head_response(r) - def add_object(self, event: MISPEvent | int | str | UUID, misp_object: MISPObject, pythonify: bool = False, break_on_duplicate: bool = False) -> dict | MISPObject: + def add_object(self, event: MISPEvent | int | str | UUID, misp_object: MISPObject, pythonify: bool = False, break_on_duplicate: bool = False) -> dict[str, Any] | MISPObject: """Add a MISP Object to an existing MISP event: https://www.misp-project.org/openapi/#tag/Objects/operation/addObject :param event: event to extend @@ -619,7 +619,7 @@ class PyMISP: o.from_dict(**new_object) return o - def update_object(self, misp_object: MISPObject, object_id: int | None = None, pythonify: bool = False) -> dict | MISPObject: + def update_object(self, misp_object: MISPObject, object_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPObject: """Update an object on a MISP instance :param misp_object: object to update @@ -638,7 +638,7 @@ class PyMISP: o.from_dict(**updated_object) return o - def delete_object(self, misp_object: MISPObject | int | str | UUID, hard: bool = False) -> dict: + def delete_object(self, misp_object: MISPObject | int | str | UUID, hard: bool = False) -> dict[str, Any]: """Delete an object from a MISP instance: https://www.misp-project.org/openapi/#tag/Objects/operation/deleteObject :param misp_object: object to delete @@ -651,7 +651,7 @@ class PyMISP: r = self._prepare_request('POST', f'objects/delete/{object_id}', data=data) return self._check_json_response(r) - def add_object_reference(self, misp_object_reference: MISPObjectReference, pythonify: bool = False) -> dict | MISPObjectReference: + def add_object_reference(self, misp_object_reference: MISPObjectReference, pythonify: bool = False) -> dict[str, Any] | MISPObjectReference: """Add a reference to an object :param misp_object_reference: object reference @@ -669,7 +669,7 @@ class PyMISP: self, object_reference: MISPObjectReference | int | str | UUID, hard: bool = False, - ) -> dict: + ) -> dict[str, Any]: """Delete a reference to an object.""" object_reference_id = get_uuid_or_id_from_abstract_misp(object_reference) query_url = f"objectReferences/delete/{object_reference_id}" @@ -680,14 +680,14 @@ class PyMISP: # Object templates - def object_templates(self, pythonify: bool = False) -> dict | list[MISPObjectTemplate]: + def object_templates(self, pythonify: bool = False) -> dict[str, Any] | list[MISPObjectTemplate] | list[dict[str, Any]]: """Get all the object templates :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'objectTemplates/index') - templates = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in templates: + templates = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(templates, dict): return templates to_return = [] for object_template in templates: @@ -696,7 +696,7 @@ class PyMISP: to_return.append(o) return to_return - def get_object_template(self, object_template: MISPObjectTemplate | int | str | UUID, pythonify: bool = False) -> dict | MISPObjectTemplate: + def get_object_template(self, object_template: MISPObjectTemplate | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPObjectTemplate: """Gets the full object template :param object_template: template or ID to get @@ -711,14 +711,14 @@ class PyMISP: t.from_dict(**object_template_r) return t - def get_raw_object_template(self, uuid_or_name: str) -> dict: + def get_raw_object_template(self, uuid_or_name: str) -> dict[str, Any]: """Get a row template. It needs to be present on disk on the MISP instance you're connected to. The response of this method can be passed to MISPObject(, misp_objects_template_custom=) """ r = self._prepare_request('GET', f'objectTemplates/getRaw/{uuid_or_name}') return self._check_json_response(r) - def update_object_templates(self) -> dict: + def update_object_templates(self) -> dict[str, Any]: """Trigger an update of the object templates""" response = self._prepare_request('POST', 'objectTemplates/update') return self._check_json_response(response) @@ -727,14 +727,14 @@ class PyMISP: # ## BEGIN Attribute ### - def attributes(self, pythonify: bool = False) -> dict | list[MISPAttribute]: + def attributes(self, pythonify: bool = False) -> dict[str, Any] | list[MISPAttribute] | list[dict[str, Any]]: """Get all the attributes from the MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/getAttributes :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'attributes/index') - attributes_r = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in attributes_r: + attributes_r = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(attributes_r, dict): return attributes_r to_return = [] for attribute in attributes_r: @@ -743,7 +743,7 @@ class PyMISP: to_return.append(a) return to_return - def get_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict | MISPAttribute: + def get_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPAttribute: """Get an attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/getAttributeById :param attribute: attribute to get @@ -767,7 +767,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'attributes/view/{attribute_id}') return self._check_head_response(r) - def add_attribute(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute | Iterable, pythonify: bool = False, break_on_duplicate: bool = True) -> dict | MISPAttribute | MISPShadowAttribute: + def add_attribute(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute | Iterable[str], pythonify: bool = False, break_on_duplicate: bool = True) -> dict[str, Any] | MISPAttribute | MISPShadowAttribute: """Add an attribute to an existing MISP event: https://www.misp-project.org/openapi/#tag/Attributes/operation/addAttribute :param event: event to extend @@ -814,7 +814,7 @@ class PyMISP: a.from_dict(**new_attribute) return a - def update_attribute(self, attribute: MISPAttribute, attribute_id: int | None = None, pythonify: bool = False) -> dict | MISPAttribute | MISPShadowAttribute: + def update_attribute(self, attribute: MISPAttribute, attribute_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPAttribute | MISPShadowAttribute: """Update an attribute on a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/editAttribute :param attribute: attribute to update @@ -839,7 +839,7 @@ class PyMISP: a.from_dict(**updated_attribute) return a - def delete_attribute(self, attribute: MISPAttribute | int | str | UUID, hard: bool = False) -> dict: + def delete_attribute(self, attribute: MISPAttribute | int | str | UUID, hard: bool = False) -> dict[str, Any]: """Delete an attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/deleteAttribute :param attribute: attribute to delete @@ -859,7 +859,7 @@ class PyMISP: return self.delete_attribute_proposal(attribute_id) return response - def restore_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict | MISPAttribute: + def restore_attribute(self, attribute: MISPAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPAttribute: """Restore a soft deleted attribute from a MISP instance: https://www.misp-project.org/openapi/#tag/Attributes/operation/restoreAttribute :param attribute: attribute to restore @@ -877,7 +877,7 @@ class PyMISP: # ## BEGIN Attribute Proposal ### - def attribute_proposals(self, event: MISPEvent | int | str | UUID | None = None, pythonify: bool = False) -> dict | list[MISPShadowAttribute]: + def attribute_proposals(self, event: MISPEvent | int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPShadowAttribute] | list[dict[str, Any]]: """Get all the attribute proposals :param event: event @@ -888,8 +888,8 @@ class PyMISP: r = self._prepare_request('GET', f'shadowAttributes/index/{event_id}') else: r = self._prepare_request('GET', 'shadowAttributes/index') - attribute_proposals = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in attribute_proposals: + attribute_proposals = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(attribute_proposals, dict): return attribute_proposals to_return = [] for attribute_proposal in attribute_proposals: @@ -898,7 +898,7 @@ class PyMISP: to_return.append(a) return to_return - def get_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID, pythonify: bool = False) -> dict | MISPShadowAttribute: + def get_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Get an attribute proposal :param proposal: proposal to get @@ -915,7 +915,7 @@ class PyMISP: # NOTE: the tree following method have a very specific meaning, look at the comments - def add_attribute_proposal(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict | MISPShadowAttribute: + def add_attribute_proposal(self, event: MISPEvent | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Propose a new attribute in an event :param event: event to receive new attribute @@ -931,7 +931,7 @@ class PyMISP: a.from_dict(**new_attribute_proposal) return a - def update_attribute_proposal(self, initial_attribute: MISPAttribute | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict | MISPShadowAttribute: + def update_attribute_proposal(self, initial_attribute: MISPAttribute | int | str | UUID, attribute: MISPAttribute, pythonify: bool = False) -> dict[str, Any] | MISPShadowAttribute: """Propose a change for an attribute :param initial_attribute: attribute to change @@ -947,7 +947,7 @@ class PyMISP: a.from_dict(**update_attribute_proposal) return a - def delete_attribute_proposal(self, attribute: MISPAttribute | int | str | UUID) -> dict: + def delete_attribute_proposal(self, attribute: MISPAttribute | int | str | UUID) -> dict[str, Any]: """Propose the deletion of an attribute :param attribute: attribute to delete @@ -956,7 +956,7 @@ class PyMISP: response = self._prepare_request('POST', f'shadowAttributes/delete/{attribute_id}') return self._check_json_response(response) - def accept_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict: + def accept_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict[str, Any]: """Accept a proposal. You cannot modify an existing proposal, only accept/discard :param proposal: attribute proposal to accept @@ -965,7 +965,7 @@ class PyMISP: response = self._prepare_request('POST', f'shadowAttributes/accept/{proposal_id}') return self._check_json_response(response) - def discard_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict: + def discard_attribute_proposal(self, proposal: MISPShadowAttribute | int | str | UUID) -> dict[str, Any]: """Discard a proposal. You cannot modify an existing proposal, only accept/discard :param proposal: attribute proposal to discard @@ -980,7 +980,7 @@ class PyMISP: def sightings(self, misp_entity: AbstractMISP | None = None, org: MISPOrganisation | int | str | UUID | None = None, - pythonify: bool = False) -> dict | list[MISPSighting]: + pythonify: bool = False) -> dict[str, Any] | list[MISPSighting] | list[dict[str, Any]]: """Get the list of sightings related to a MISPEvent or a MISPAttribute (depending on type of misp_entity): https://www.misp-project.org/openapi/#tag/Sightings/operation/getSightingsByEventId :param misp_entity: MISP entity @@ -1002,8 +1002,8 @@ class PyMISP: to_post['org_id'] = org_id r = self._prepare_request('POST', url, data=to_post) - sightings = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in sightings: + sightings = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(sightings, dict): return sightings to_return = [] for sighting in sightings: @@ -1014,7 +1014,7 @@ class PyMISP: def add_sighting(self, sighting: MISPSighting, attribute: MISPAttribute | int | str | UUID | None = None, - pythonify: bool = False) -> dict | MISPSighting: + pythonify: bool = False) -> dict[str, Any] | MISPSighting: """Add a new sighting (globally, or to a specific attribute): https://www.misp-project.org/openapi/#tag/Sightings/operation/addSighting and https://www.misp-project.org/openapi/#tag/Sightings/operation/getSightingsByEventId :param sighting: sighting to add @@ -1034,7 +1034,7 @@ class PyMISP: s.from_dict(**new_sighting) return s - def delete_sighting(self, sighting: MISPSighting | int | str | UUID) -> dict: + def delete_sighting(self, sighting: MISPSighting | int | str | UUID) -> dict[str, Any]: """Delete a sighting from a MISP instance: https://www.misp-project.org/openapi/#tag/Sightings/operation/deleteSighting :param sighting: sighting to delete @@ -1047,7 +1047,7 @@ class PyMISP: # ## BEGIN Tags ### - def tags(self, pythonify: bool = False, **kw_params) -> dict | list[MISPTag]: + def tags(self, pythonify: bool = False, **kw_params) -> dict[str, Any] | list[MISPTag]: # type: ignore[no-untyped-def] """Get the list of existing tags: https://www.misp-project.org/openapi/#tag/Tags/operation/getTags :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM @@ -1063,7 +1063,7 @@ class PyMISP: to_return.append(t) return to_return - def get_tag(self, tag: MISPTag | int | str | UUID, pythonify: bool = False) -> dict | MISPTag: + def get_tag(self, tag: MISPTag | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Get a tag by id: https://www.misp-project.org/openapi/#tag/Tags/operation/getTagById :param tag: tag to get @@ -1078,7 +1078,7 @@ class PyMISP: t.from_dict(**tag_r) return t - def add_tag(self, tag: MISPTag, pythonify: bool = False) -> dict | MISPTag: + def add_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Add a new tag on a MISP instance: https://www.misp-project.org/openapi/#tag/Tags/operation/addTag The user calling this method needs the Tag Editor permission. It doesn't add a tag to an event, simply creates it on the MISP instance. @@ -1094,7 +1094,7 @@ class PyMISP: t.from_dict(**new_tag) return t - def enable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict | MISPTag: + def enable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Enable a tag :param tag: tag to enable @@ -1103,7 +1103,7 @@ class PyMISP: tag.hide_tag = False return self.update_tag(tag, pythonify=pythonify) - def disable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict | MISPTag: + def disable_tag(self, tag: MISPTag, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Disable a tag :param tag: tag to disable @@ -1112,7 +1112,7 @@ class PyMISP: tag.hide_tag = True return self.update_tag(tag, pythonify=pythonify) - def update_tag(self, tag: MISPTag, tag_id: int | None = None, pythonify: bool = False) -> dict | MISPTag: + def update_tag(self, tag: MISPTag, tag_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPTag: """Edit only the provided parameters of a tag: https://www.misp-project.org/openapi/#tag/Tags/operation/editTag :param tag: tag to update @@ -1131,7 +1131,7 @@ class PyMISP: t.from_dict(**updated_tag) return t - def delete_tag(self, tag: MISPTag | int | str | UUID) -> dict: + def delete_tag(self, tag: MISPTag | int | str | UUID) -> dict[str, Any]: """Delete a tag from a MISP instance: https://www.misp-project.org/openapi/#tag/Tags/operation/deleteTag :param tag: tag to delete @@ -1140,7 +1140,7 @@ class PyMISP: response = self._prepare_request('POST', f'tags/delete/{tag_id}') return self._check_json_response(response) - def search_tags(self, tagname: str, strict_tagname: bool = False, pythonify: bool = False) -> dict | list[MISPTag]: + def search_tags(self, tagname: str, strict_tagname: bool = False, pythonify: bool = False) -> dict[str, Any] | list[MISPTag] | list[dict[str, Any]]: """Search for tags by name: https://www.misp-project.org/openapi/#tag/Tags/operation/searchTag :param tag_name: Name to search, use % for substrings matches. @@ -1148,8 +1148,8 @@ class PyMISP: """ query = {'tagname': tagname, 'strict_tagname': strict_tagname} response = self._prepare_request('POST', 'tags/search', data=query) - normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + normalized_response = self._check_json_response_list(response) + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return: list[MISPTag] = [] for tag in normalized_response: @@ -1162,14 +1162,14 @@ class PyMISP: # ## BEGIN Taxonomies ### - def taxonomies(self, pythonify: bool = False) -> dict | list[MISPTaxonomy]: + def taxonomies(self, pythonify: bool = False) -> dict[str, Any] | list[MISPTaxonomy] | list[dict[str, Any]]: """Get all the taxonomies: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/getTaxonomies :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'taxonomies/index') - taxonomies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in taxonomies: + taxonomies = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(taxonomies, dict): return taxonomies to_return = [] for taxonomy in taxonomies: @@ -1178,7 +1178,7 @@ class PyMISP: to_return.append(t) return to_return - def get_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID, pythonify: bool = False) -> dict | MISPTaxonomy: + def get_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPTaxonomy: """Get a taxonomy by id or namespace from a MISP instance: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/getTaxonomyById :param taxonomy: taxonomy to get @@ -1193,7 +1193,7 @@ class PyMISP: t.from_dict(**taxonomy_r) return t - def enable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict: + def enable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any]: """Enable a taxonomy: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/enableTaxonomy :param taxonomy: taxonomy to enable @@ -1202,7 +1202,7 @@ class PyMISP: response = self._prepare_request('POST', f'taxonomies/enable/{taxonomy_id}') return self._check_json_response(response) - def disable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict: + def disable_taxonomy(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any]: """Disable a taxonomy: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/disableTaxonomy :param taxonomy: taxonomy to disable @@ -1212,7 +1212,7 @@ class PyMISP: response = self._prepare_request('POST', f'taxonomies/disable/{taxonomy_id}') return self._check_json_response(response) - def disable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict: + def disable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any]: """Disable all the tags of a taxonomy :param taxonomy: taxonomy with tags to disable @@ -1221,7 +1221,7 @@ class PyMISP: response = self._prepare_request('POST', f'taxonomies/disableTag/{taxonomy_id}') return self._check_json_response(response) - def enable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict: + def enable_taxonomy_tags(self, taxonomy: MISPTaxonomy | int | str | UUID) -> dict[str, Any]: """Enable all the tags of a taxonomy. NOTE: this is automatically done when you call enable_taxonomy :param taxonomy: taxonomy with tags to enable @@ -1238,12 +1238,12 @@ class PyMISP: response = self._prepare_request('POST', url) return self._check_json_response(response) - def update_taxonomies(self) -> dict: + def update_taxonomies(self) -> dict[str, Any]: """Update all the taxonomies: https://www.misp-project.org/openapi/#tag/Taxonomies/operation/updateTaxonomies""" response = self._prepare_request('POST', 'taxonomies/update') return self._check_json_response(response) - def set_taxonomy_required(self, taxonomy: MISPTaxonomy | int | str, required: bool = False) -> dict: + def set_taxonomy_required(self, taxonomy: MISPTaxonomy | int | str, required: bool = False) -> dict[str, Any]: taxonomy_id = get_uuid_or_id_from_abstract_misp(taxonomy) url = urljoin(self.root_url, f'taxonomies/toggleRequired/{taxonomy_id}') payload = { @@ -1258,7 +1258,7 @@ class PyMISP: # ## BEGIN Warninglists ### - def warninglists(self, pythonify: bool = False) -> dict | list[MISPWarninglist]: + def warninglists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPWarninglist]: """Get all the warninglists: https://www.misp-project.org/openapi/#tag/Warninglists/operation/getWarninglists :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM @@ -1274,7 +1274,7 @@ class PyMISP: to_return.append(w) return to_return - def get_warninglist(self, warninglist: MISPWarninglist | int | str | UUID, pythonify: bool = False) -> dict | MISPWarninglist: + def get_warninglist(self, warninglist: MISPWarninglist | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPWarninglist: """Get a warninglist by id: https://www.misp-project.org/openapi/#tag/Warninglists/operation/getWarninglistById :param warninglist: warninglist to get @@ -1289,7 +1289,7 @@ class PyMISP: w.from_dict(**wl) return w - def toggle_warninglist(self, warninglist_id: str | int | list[int] | None = None, warninglist_name: str | list[str] | None = None, force_enable: bool = False) -> dict: + def toggle_warninglist(self, warninglist_id: str | int | list[int] | None = None, warninglist_name: str | list[str] | None = None, force_enable: bool = False) -> dict[str, Any]: '''Toggle (enable/disable) the status of a warninglist by id: https://www.misp-project.org/openapi/#tag/Warninglists/operation/toggleEnableWarninglist :param warninglist_id: ID of the WarningList @@ -1314,7 +1314,7 @@ class PyMISP: response = self._prepare_request('POST', 'warninglists/toggleEnable', data=query) return self._check_json_response(response) - def enable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict: + def enable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict[str, Any]: """Enable a warninglist :param warninglist: warninglist to enable @@ -1322,7 +1322,7 @@ class PyMISP: warninglist_id = get_uuid_or_id_from_abstract_misp(warninglist) return self.toggle_warninglist(warninglist_id=warninglist_id, force_enable=True) - def disable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict: + def disable_warninglist(self, warninglist: MISPWarninglist | int | str | UUID) -> dict[str, Any]: """Disable a warninglist :param warninglist: warninglist to disable @@ -1330,7 +1330,7 @@ class PyMISP: warninglist_id = get_uuid_or_id_from_abstract_misp(warninglist) return self.toggle_warninglist(warninglist_id=warninglist_id, force_enable=False) - def values_in_warninglist(self, value: Iterable) -> dict: + def values_in_warninglist(self, value: Iterable[str]) -> dict[str, Any]: """Check if IOC values are in warninglist :param value: iterator with values to check @@ -1338,7 +1338,7 @@ class PyMISP: response = self._prepare_request('POST', 'warninglists/checkValue', data=value) return self._check_json_response(response) - def update_warninglists(self) -> dict: + def update_warninglists(self) -> dict[str, Any]: """Update all the warninglists: https://www.misp-project.org/openapi/#tag/Warninglists/operation/updateWarninglists""" response = self._prepare_request('POST', 'warninglists/update') return self._check_json_response(response) @@ -1347,14 +1347,14 @@ class PyMISP: # ## BEGIN Noticelist ### - def noticelists(self, pythonify: bool = False) -> dict | list[MISPNoticelist]: + def noticelists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPNoticelist] | list[dict[str, Any]]: """Get all the noticelists: https://www.misp-project.org/openapi/#tag/Noticelists/operation/getNoticelists :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'noticelists/index') - noticelists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in noticelists: + noticelists = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(noticelists, dict): return noticelists to_return = [] for noticelist in noticelists: @@ -1363,7 +1363,7 @@ class PyMISP: to_return.append(n) return to_return - def get_noticelist(self, noticelist: MISPNoticelist | int | str | UUID, pythonify: bool = False) -> dict | MISPNoticelist: + def get_noticelist(self, noticelist: MISPNoticelist | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPNoticelist: """Get a noticelist by id: https://www.misp-project.org/openapi/#tag/Noticelists/operation/getNoticelistById :param notistlist: Noticelist to get @@ -1378,7 +1378,7 @@ class PyMISP: n.from_dict(**noticelist_j) return n - def enable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict: + def enable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict[str, Any]: """Enable a noticelist by id: https://www.misp-project.org/openapi/#tag/Noticelists/operation/toggleEnableNoticelist :param noticelist: Noticelist to enable @@ -1389,7 +1389,7 @@ class PyMISP: response = self._prepare_request('POST', f'noticelists/enableNoticelist/{noticelist_id}/true') return self._check_json_response(response) - def disable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict: + def disable_noticelist(self, noticelist: MISPNoticelist | int | str | UUID) -> dict[str, Any]: """Disable a noticelist by id :param noticelist: Noticelist to disable @@ -1400,7 +1400,7 @@ class PyMISP: response = self._prepare_request('POST', f'noticelists/enableNoticelist/{noticelist_id}') return self._check_json_response(response) - def update_noticelists(self) -> dict: + def update_noticelists(self) -> dict[str, Any]: """Update all the noticelists: https://www.misp-project.org/openapi/#tag/Noticelists/operation/updateNoticelists""" response = self._prepare_request('POST', 'noticelists/update') return self._check_json_response(response) @@ -1409,14 +1409,14 @@ class PyMISP: # ## BEGIN Correlation Exclusions ### - def correlation_exclusions(self, pythonify: bool = False) -> dict | list[MISPCorrelationExclusion]: + def correlation_exclusions(self, pythonify: bool = False) -> dict[str, Any] | list[MISPCorrelationExclusion] | list[dict[str, Any]]: """Get all the correlation exclusions :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'correlation_exclusions') - correlation_exclusions = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in correlation_exclusions: + correlation_exclusions = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(correlation_exclusions, dict): return correlation_exclusions to_return = [] for correlation_exclusion in correlation_exclusions: @@ -1425,7 +1425,7 @@ class PyMISP: to_return.append(c) return to_return - def get_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID, pythonify: bool = False) -> dict | MISPCorrelationExclusion: + def get_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPCorrelationExclusion: """Get a correlation exclusion by ID :param correlation_exclusion: Correlation exclusion to get @@ -1440,7 +1440,7 @@ class PyMISP: c.from_dict(**correlation_exclusion_j) return c - def add_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion, pythonify: bool = False) -> dict | MISPCorrelationExclusion: + def add_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion, pythonify: bool = False) -> dict[str, Any] | MISPCorrelationExclusion: """Add a new correlation exclusion :param correlation_exclusion: correlation exclusion to add @@ -1454,7 +1454,7 @@ class PyMISP: c.from_dict(**new_correlation_exclusion) return c - def delete_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID) -> dict: + def delete_correlation_exclusion(self, correlation_exclusion: MISPCorrelationExclusion | int | str | UUID) -> dict[str, Any]: """Delete a correlation exclusion :param correlation_exclusion: The MISPCorrelationExclusion you wish to delete from MISP @@ -1463,7 +1463,7 @@ class PyMISP: r = self._prepare_request('POST', f'correlation_exclusions/delete/{exclusion_id}') return self._check_json_response(r) - def clean_correlation_exclusions(self): + def clean_correlation_exclusions(self) -> dict[str, Any]: """Initiate correlation exclusions cleanup""" r = self._prepare_request('POST', 'correlation_exclusions/clean') return self._check_json_response(r) @@ -1476,14 +1476,14 @@ class PyMISP: self, withCluster: bool = False, pythonify: bool = False, - ) -> dict | list[MISPGalaxy]: + ) -> dict[str, Any] | list[MISPGalaxy] | list[dict[str, Any]]: """Get all the galaxies: https://www.misp-project.org/openapi/#tag/Galaxies/operation/getGalaxies :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'galaxies/index') - galaxies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in galaxies: + galaxies = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(galaxies, dict): return galaxies to_return = [] for galaxy in galaxies: @@ -1497,11 +1497,11 @@ class PyMISP: value: str, withCluster: bool = False, pythonify: bool = False, - ) -> dict | list[MISPGalaxy]: + ) -> dict[str, Any] | list[MISPGalaxy] | list[dict[str, Any]]: """Text search to find a matching galaxy name, namespace, description, or uuid.""" r = self._prepare_request("POST", "galaxies", data={"value": value}) - galaxies = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or "errors" in galaxies: + galaxies = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(galaxies, dict): return galaxies to_return = [] for galaxy in galaxies: @@ -1510,7 +1510,7 @@ class PyMISP: to_return.append(g) return to_return - def get_galaxy(self, galaxy: MISPGalaxy | int | str | UUID, withCluster: bool = False, pythonify: bool = False) -> dict | MISPGalaxy: + def get_galaxy(self, galaxy: MISPGalaxy | int | str | UUID, withCluster: bool = False, pythonify: bool = False) -> dict[str, Any] | MISPGalaxy: """Get a galaxy by id: https://www.misp-project.org/openapi/#tag/Galaxies/operation/getGalaxyById :param galaxy: galaxy to get @@ -1526,7 +1526,7 @@ class PyMISP: g.from_dict(**galaxy_j, withCluster=withCluster) return g - def search_galaxy_clusters(self, galaxy: MISPGalaxy | int | str | UUID, context: str = "all", searchall: str | None = None, pythonify: bool = False) -> dict | list[MISPGalaxyCluster]: + def search_galaxy_clusters(self, galaxy: MISPGalaxy | int | str | UUID, context: str = "all", searchall: str | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPGalaxyCluster] | list[dict[str, Any]]: """Searches the galaxy clusters within a specific galaxy: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/getGalaxyClusters and https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/getGalaxyClusterById :param galaxy: The MISPGalaxy you wish to search in @@ -1543,8 +1543,8 @@ class PyMISP: if searchall: kw_params["searchall"] = searchall r = self._prepare_request('POST', f"galaxy_clusters/index/{galaxy_id}", data=kw_params) - clusters_j = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in clusters_j: + clusters_j = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(clusters_j, dict): return clusters_j response = [] for cluster in clusters_j: @@ -1553,12 +1553,12 @@ class PyMISP: response.append(c) return response - def update_galaxies(self) -> dict: + def update_galaxies(self) -> dict[str, Any]: """Update all the galaxies: https://www.misp-project.org/openapi/#tag/Galaxies/operation/updateGalaxies""" response = self._prepare_request('POST', 'galaxies/update') return self._check_json_response(response) - def get_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, pythonify: bool = False) -> dict | MISPGalaxyCluster: + def get_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Gets a specific galaxy cluster :param galaxy_cluster: The MISPGalaxyCluster you want to get @@ -1574,7 +1574,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def add_galaxy_cluster(self, galaxy: MISPGalaxy | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict | MISPGalaxyCluster: + def add_galaxy_cluster(self, galaxy: MISPGalaxy | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Add a new galaxy cluster to a MISP Galaxy: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/addGalaxyCluster :param galaxy: A MISPGalaxy (or UUID) where you wish to add the galaxy cluster @@ -1594,7 +1594,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def update_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict | MISPGalaxyCluster: + def update_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Update a custom galaxy cluster: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/editGalaxyCluster ;param galaxy_cluster: The MISPGalaxyCluster you wish to update @@ -1613,7 +1613,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def publish_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID) -> dict: + def publish_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID) -> dict[str, Any]: """Publishes a galaxy cluster: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/publishGalaxyCluster :param galaxy_cluster: The galaxy cluster you wish to publish @@ -1625,7 +1625,7 @@ class PyMISP: response = self._check_json_response(r) return response - def fork_galaxy_cluster(self, galaxy: MISPGalaxy | int | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict | MISPGalaxyCluster: + def fork_galaxy_cluster(self, galaxy: MISPGalaxy | int | str | UUID, galaxy_cluster: MISPGalaxyCluster, pythonify: bool = False) -> dict[str, Any] | MISPGalaxyCluster: """Forks an existing galaxy cluster, creating a new one with matching attributes :param galaxy: The galaxy (or galaxy ID) where the cluster you want to fork resides @@ -1649,7 +1649,7 @@ class PyMISP: gc.from_dict(**cluster_j) return gc - def delete_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, hard=False) -> dict: + def delete_galaxy_cluster(self, galaxy_cluster: MISPGalaxyCluster | int | str | UUID, hard: bool=False) -> dict[str, Any]: """Deletes a galaxy cluster from MISP: https://www.misp-project.org/openapi/#tag/Galaxy-Clusters/operation/deleteGalaxyCluster :param galaxy_cluster: The MISPGalaxyCluster you wish to delete from MISP @@ -1665,7 +1665,7 @@ class PyMISP: r = self._prepare_request('POST', f'galaxy_clusters/delete/{cluster_id}', data=data) return self._check_json_response(r) - def add_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict: + def add_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict[str, Any]: """Add a galaxy cluster relation, cluster relation must include cluster UUIDs in both directions @@ -1675,7 +1675,7 @@ class PyMISP: cluster_rel_j = self._check_json_response(r) return cluster_rel_j - def update_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict: + def update_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation) -> dict[str, Any]: """Update a galaxy cluster relation :param galaxy_cluster_relation: The MISPGalaxyClusterRelation to update @@ -1685,7 +1685,7 @@ class PyMISP: cluster_rel_j = self._check_json_response(r) return cluster_rel_j - def delete_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation | int | str | UUID) -> dict: + def delete_galaxy_cluster_relation(self, galaxy_cluster_relation: MISPGalaxyClusterRelation | int | str | UUID) -> dict[str, Any]: """Delete a galaxy cluster relation :param galaxy_cluster_relation: The MISPGalaxyClusterRelation to delete @@ -1699,14 +1699,14 @@ class PyMISP: # ## BEGIN Feed ### - def feeds(self, pythonify: bool = False) -> dict | list[MISPFeed]: + def feeds(self, pythonify: bool = False) -> dict[str, Any] | list[MISPFeed] | list[dict[str, Any]]: """Get the list of existing feeds: https://www.misp-project.org/openapi/#tag/Feeds/operation/getFeeds :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'feeds/index') - feeds = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in feeds: + feeds = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(feeds, dict): return feeds to_return = [] for feed in feeds: @@ -1715,7 +1715,7 @@ class PyMISP: to_return.append(f) return to_return - def get_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict | MISPFeed: + def get_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Get a feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/getFeedById :param feed: feed to get @@ -1730,7 +1730,7 @@ class PyMISP: f.from_dict(**feed_j) return f - def add_feed(self, feed: MISPFeed, pythonify: bool = False) -> dict | MISPFeed: + def add_feed(self, feed: MISPFeed, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Add a new feed on a MISP instance: https://www.misp-project.org/openapi/#tag/Feeds/operation/addFeed :param feed: feed to add @@ -1745,7 +1745,7 @@ class PyMISP: f.from_dict(**new_feed) return f - def enable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict | MISPFeed: + def enable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Enable a feed; fetching it will create event(s): https://www.misp-project.org/openapi/#tag/Feeds/operation/enableFeed :param feed: feed to enable @@ -1760,7 +1760,7 @@ class PyMISP: f.enabled = True return self.update_feed(feed=f, pythonify=pythonify) - def disable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict | MISPFeed: + def disable_feed(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Disable a feed: https://www.misp-project.org/openapi/#tag/Feeds/operation/disableFeed :param feed: feed to disable @@ -1775,7 +1775,7 @@ class PyMISP: f.enabled = False return self.update_feed(feed=f, pythonify=pythonify) - def enable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict | MISPFeed: + def enable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Enable the caching of a feed :param feed: feed to enable caching @@ -1790,7 +1790,7 @@ class PyMISP: f.caching_enabled = True return self.update_feed(feed=f, pythonify=pythonify) - def disable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict | MISPFeed: + def disable_feed_cache(self, feed: MISPFeed | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Disable the caching of a feed :param feed: feed to disable caching @@ -1805,7 +1805,7 @@ class PyMISP: f.caching_enabled = False return self.update_feed(feed=f, pythonify=pythonify) - def update_feed(self, feed: MISPFeed, feed_id: int | None = None, pythonify: bool = False) -> dict | MISPFeed: + def update_feed(self, feed: MISPFeed, feed_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPFeed: """Update a feed on a MISP instance :param feed: feed to update @@ -1825,7 +1825,7 @@ class PyMISP: f.from_dict(**updated_feed) return f - def delete_feed(self, feed: MISPFeed | int | str | UUID) -> dict: + def delete_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any]: """Delete a feed from a MISP instance :param feed: feed to delete @@ -1834,7 +1834,7 @@ class PyMISP: response = self._prepare_request('POST', f'feeds/delete/{feed_id}') return self._check_json_response(response) - def fetch_feed(self, feed: MISPFeed | int | str | UUID) -> dict: + def fetch_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any]: """Fetch one single feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/fetchFromFeed :param feed: feed to fetch @@ -1843,12 +1843,12 @@ class PyMISP: response = self._prepare_request('GET', f'feeds/fetchFromFeed/{feed_id}') return self._check_json_response(response) - def cache_all_feeds(self) -> dict: + def cache_all_feeds(self) -> dict[str, Any]: """ Cache all the feeds: https://www.misp-project.org/openapi/#tag/Feeds/operation/cacheFeeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/all') return self._check_json_response(response) - def cache_feed(self, feed: MISPFeed | int | str | UUID) -> dict: + def cache_feed(self, feed: MISPFeed | int | str | UUID) -> dict[str, Any]: """Cache a specific feed by id: https://www.misp-project.org/openapi/#tag/Feeds/operation/cacheFeeds :param feed: feed to cache @@ -1857,22 +1857,22 @@ class PyMISP: response = self._prepare_request('GET', f'feeds/cacheFeeds/{feed_id}') return self._check_json_response(response) - def cache_freetext_feeds(self) -> dict: + def cache_freetext_feeds(self) -> dict[str, Any]: """Cache all the freetext feeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/freetext') return self._check_json_response(response) - def cache_misp_feeds(self) -> dict: + def cache_misp_feeds(self) -> dict[str, Any]: """Cache all the MISP feeds""" response = self._prepare_request('GET', 'feeds/cacheFeeds/misp') return self._check_json_response(response) - def compare_feeds(self) -> dict: + def compare_feeds(self) -> dict[str, Any]: """Generate the comparison matrix for all the MISP feeds""" response = self._prepare_request('GET', 'feeds/compareFeeds') return self._check_json_response(response) - def load_default_feeds(self) -> dict: + def load_default_feeds(self) -> dict[str, Any]: """Load all the default feeds.""" response = self._prepare_request('POST', 'feeds/loadDefaultFeeds') return self._check_json_response(response) @@ -1881,14 +1881,14 @@ class PyMISP: # ## BEGIN Server ### - def servers(self, pythonify: bool = False) -> dict | list[MISPServer]: + def servers(self, pythonify: bool = False) -> dict[str, Any] | list[MISPServer] | list[dict[str, Any]]: """Get the existing servers the MISP instance can synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'servers/index') - servers = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in servers: + servers = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(servers, dict): return servers to_return = [] for server in servers: @@ -1897,7 +1897,7 @@ class PyMISP: to_return.append(s) return to_return - def get_sync_config(self, pythonify: bool = False) -> dict | MISPServer: + def get_sync_config(self, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Get the sync server config. WARNING: This method only works if the user calling it is a sync user @@ -1911,7 +1911,7 @@ class PyMISP: s.from_dict(**server) return s - def import_server(self, server: MISPServer, pythonify: bool = False) -> dict | MISPServer: + def import_server(self, server: MISPServer, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Import a sync server config received from get_sync_config :param server: sync server config @@ -1925,7 +1925,7 @@ class PyMISP: s.from_dict(**server_j) return s - def add_server(self, server: MISPServer, pythonify: bool = False) -> dict | MISPServer: + def add_server(self, server: MISPServer, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Add a server to synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers Note: You probably want to use PyMISP.get_sync_config and PyMISP.import_server instead @@ -1940,7 +1940,7 @@ class PyMISP: s.from_dict(**server_j) return s - def update_server(self, server: MISPServer, server_id: int | None = None, pythonify: bool = False) -> dict | MISPServer: + def update_server(self, server: MISPServer, server_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPServer: """Update a server to synchronise with: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param server: sync server config @@ -1958,7 +1958,7 @@ class PyMISP: s.from_dict(**updated_server) return s - def delete_server(self, server: MISPServer | int | str | UUID) -> dict: + def delete_server(self, server: MISPServer | int | str | UUID) -> dict[str, Any]: """Delete a sync server: https://www.misp-project.org/openapi/#tag/Servers/operation/getServers :param server: sync server config @@ -1967,7 +1967,7 @@ class PyMISP: response = self._prepare_request('POST', f'servers/delete/{server_id}') return self._check_json_response(response) - def server_pull(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict: + def server_pull(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict[str, Any]: """Initialize a pull from a sync server, optionally limited to one event: https://www.misp-project.org/openapi/#tag/Servers/operation/pullServer :param server: sync server config @@ -1983,7 +1983,7 @@ class PyMISP: # FIXME: can we pythonify? return self._check_json_response(response) - def server_push(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict: + def server_push(self, server: MISPServer | int | str | UUID, event: MISPEvent | int | str | UUID | None = None) -> dict[str, Any]: """Initialize a push to a sync server, optionally limited to one event: https://www.misp-project.org/openapi/#tag/Servers/operation/pushServer :param server: sync server config @@ -1999,7 +1999,7 @@ class PyMISP: # FIXME: can we pythonify? return self._check_json_response(response) - def test_server(self, server: MISPServer | int | str | UUID) -> dict: + def test_server(self, server: MISPServer | int | str | UUID) -> dict[str, Any]: """Test if a sync link is working as expected :param server: sync server config @@ -2012,14 +2012,14 @@ class PyMISP: # ## BEGIN Sharing group ### - def sharing_groups(self, pythonify: bool = False) -> dict | list[MISPSharingGroup]: + def sharing_groups(self, pythonify: bool = False) -> dict[str, Any] | list[MISPSharingGroup] | list[dict[str, Any]]: """Get the existing sharing groups: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/getSharingGroup :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'sharingGroups/index') - sharing_groups = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in sharing_groups: + sharing_groups = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(sharing_groups, dict): return sharing_groups to_return = [] for sharing_group in sharing_groups: @@ -2028,7 +2028,7 @@ class PyMISP: to_return.append(s) return to_return - def get_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, pythonify: bool = False) -> dict | MISPSharingGroup: + def get_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Get a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/getSharingGroupById :param sharing_group: sharing group to find @@ -2043,7 +2043,7 @@ class PyMISP: s.from_dict(**sharing_group_resp) return s - def add_sharing_group(self, sharing_group: MISPSharingGroup, pythonify: bool = False) -> dict | MISPSharingGroup: + def add_sharing_group(self, sharing_group: MISPSharingGroup, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Add a new sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addSharingGroup :param sharing_group: sharing group to add @@ -2057,7 +2057,7 @@ class PyMISP: s.from_dict(**sharing_group_j) return s - def update_sharing_group(self, sharing_group: MISPSharingGroup | dict, sharing_group_id: int | None = None, pythonify: bool = False) -> dict | MISPSharingGroup: + def update_sharing_group(self, sharing_group: MISPSharingGroup | dict[str, Any], sharing_group_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPSharingGroup: """Update sharing group parameters: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/editSharingGroup :param sharing_group: MISP Sharing Group @@ -2086,7 +2086,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'sharing_groups/view/{sharing_group_id}') return self._check_head_response(r) - def delete_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID) -> dict: + def delete_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID) -> dict[str, Any]: """Delete a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/deleteSharingGroup :param sharing_group: sharing group to delete @@ -2096,7 +2096,7 @@ class PyMISP: return self._check_json_response(response) def add_org_to_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, - organisation: MISPOrganisation | int | str | UUID, extend: bool = False) -> dict: + organisation: MISPOrganisation | int | str | UUID, extend: bool = False) -> dict[str, Any]: '''Add an organisation to a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addOrganisationToSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2110,7 +2110,7 @@ class PyMISP: return self._check_json_response(response) def remove_org_from_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, - organisation: MISPOrganisation | int | str | UUID) -> dict: + organisation: MISPOrganisation | int | str | UUID) -> dict[str, Any]: '''Remove an organisation from a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/removeOrganisationFromSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2123,7 +2123,7 @@ class PyMISP: return self._check_json_response(response) def add_server_to_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, - server: MISPServer | int | str | UUID, all_orgs: bool = False) -> dict: + server: MISPServer | int | str | UUID, all_orgs: bool = False) -> dict[str, Any]: '''Add a server to a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/addServerToSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2137,7 +2137,7 @@ class PyMISP: return self._check_json_response(response) def remove_server_from_sharing_group(self, sharing_group: MISPSharingGroup | int | str | UUID, - server: MISPServer | int | str | UUID) -> dict: + server: MISPServer | int | str | UUID) -> dict[str, Any]: '''Remove a server from a sharing group: https://www.misp-project.org/openapi/#tag/Sharing-Groups/operation/removeServerFromSharingGroup :param sharing_group: Sharing group's local instance ID, or Sharing group's global UUID @@ -2153,7 +2153,7 @@ class PyMISP: # ## BEGIN Organisation ### - def organisations(self, scope="local", search: str | None = None, pythonify: bool = False) -> dict | list[MISPOrganisation]: + def organisations(self, scope: str="local", search: str | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPOrganisation] | list[dict[str, Any]]: """Get all the organisations: https://www.misp-project.org/openapi/#tag/Organisations/operation/getOrganisations :param scope: scope of organizations to get @@ -2165,8 +2165,8 @@ class PyMISP: url_path += f"/searchall:{search}" r = self._prepare_request('GET', url_path) - organisations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in organisations: + organisations = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(organisations, dict): return organisations to_return = [] for organisation in organisations: @@ -2175,7 +2175,7 @@ class PyMISP: to_return.append(o) return to_return - def get_organisation(self, organisation: MISPOrganisation | int | str | UUID, pythonify: bool = False) -> dict | MISPOrganisation: + def get_organisation(self, organisation: MISPOrganisation | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Get an organisation by id: https://www.misp-project.org/openapi/#tag/Organisations/operation/getOrganisationById :param organisation: organization to get @@ -2199,7 +2199,7 @@ class PyMISP: r = self._prepare_request('HEAD', f'organisations/view/{organisation_id}') return self._check_head_response(r) - def add_organisation(self, organisation: MISPOrganisation, pythonify: bool = False) -> dict | MISPOrganisation: + def add_organisation(self, organisation: MISPOrganisation, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Add an organisation: https://www.misp-project.org/openapi/#tag/Organisations/operation/addOrganisation :param organisation: organization to add @@ -2213,7 +2213,7 @@ class PyMISP: o.from_dict(**new_organisation) return o - def update_organisation(self, organisation: MISPOrganisation, organisation_id: int | None = None, pythonify: bool = False) -> dict | MISPOrganisation: + def update_organisation(self, organisation: MISPOrganisation, organisation_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPOrganisation: """Update an organisation: https://www.misp-project.org/openapi/#tag/Organisations/operation/editOrganisation :param organisation: organization to update @@ -2232,7 +2232,7 @@ class PyMISP: o.from_dict(**organisation) return o - def delete_organisation(self, organisation: MISPOrganisation | int | str | UUID) -> dict: + def delete_organisation(self, organisation: MISPOrganisation | int | str | UUID) -> dict[str, Any]: """Delete an organisation by id: https://www.misp-project.org/openapi/#tag/Organisations/operation/deleteOrganisation :param organisation: organization to delete @@ -2246,7 +2246,7 @@ class PyMISP: # ## BEGIN User ### - def users(self, search: str | None = None, organisation: int | None = None, pythonify: bool = False) -> dict | list[MISPUser]: + def users(self, search: str | None = None, organisation: int | None = None, pythonify: bool = False) -> dict[str, Any] | list[MISPUser] | list[dict[str, Any]]: """Get all the users, or a filtered set of users: https://www.misp-project.org/openapi/#tag/Users/operation/getUsers :param search: The search to make against the list of users @@ -2260,8 +2260,8 @@ class PyMISP: organisation_id = get_uuid_or_id_from_abstract_misp(organisation) urlpath += f"/searchorg:{organisation_id}" r = self._prepare_request('GET', urlpath) - users = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in users: + users = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(users, dict): return users to_return = [] for user in users: @@ -2270,7 +2270,7 @@ class PyMISP: to_return.append(u) return to_return - def get_user(self, user: MISPUser | int | str | UUID = 'me', pythonify: bool = False, expanded: bool = False) -> dict | MISPUser | tuple[MISPUser, MISPRole, list[MISPUserSetting]]: + def get_user(self, user: MISPUser | int | str | UUID = 'me', pythonify: bool = False, expanded: bool = False) -> dict[str, Any] | MISPUser | tuple[MISPUser, MISPRole, list[MISPUserSetting]]: """Get a user by id: https://www.misp-project.org/openapi/#tag/Users/operation/getUsers :param user: user to get; `me` means the owner of the API key doing the query @@ -2310,7 +2310,7 @@ class PyMISP: else: raise PyMISPUnexpectedResponse(f'Unable to get authkey: {authkey}') - def add_user(self, user: MISPUser, pythonify: bool = False) -> dict | MISPUser: + def add_user(self, user: MISPUser, pythonify: bool = False) -> dict[str, Any] | MISPUser: """Add a new user: https://www.misp-project.org/openapi/#tag/Users/operation/addUser :param user: user to add @@ -2324,7 +2324,7 @@ class PyMISP: u.from_dict(**user_j) return u - def update_user(self, user: MISPUser, user_id: int | None = None, pythonify: bool = False) -> dict | MISPUser: + def update_user(self, user: MISPUser, user_id: int | None = None, pythonify: bool = False) -> dict[str, Any] | MISPUser: """Update a user on a MISP instance: https://www.misp-project.org/openapi/#tag/Users/operation/editUser :param user: user to update @@ -2346,7 +2346,7 @@ class PyMISP: e.from_dict(**updated_user) return e - def delete_user(self, user: MISPUser | int | str | UUID) -> dict: + def delete_user(self, user: MISPUser | int | str | UUID) -> dict[str, Any]: """Delete a user by id: https://www.misp-project.org/openapi/#tag/Users/operation/deleteUser :param user: user to delete @@ -2356,7 +2356,7 @@ class PyMISP: response = self._prepare_request('POST', f'admin/users/delete/{user_id}') return self._check_json_response(response) - def change_user_password(self, new_password: str) -> dict: + def change_user_password(self, new_password: str) -> dict[str, Any]: """Change the password of the curent user: :param new_password: password to set @@ -2364,14 +2364,14 @@ class PyMISP: response = self._prepare_request('POST', 'users/change_pw', data={'password': new_password}) return self._check_json_response(response) - def user_registrations(self, pythonify: bool = False) -> dict | list[MISPInbox]: + def user_registrations(self, pythonify: bool = False) -> dict[str, Any] | list[MISPInbox] | list[dict[str, Any]]: """Get all the user registrations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'users/registrations/index') - registrations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in registrations: + registrations = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(registrations, dict): return registrations to_return = [] for registration in registrations: @@ -2383,8 +2383,9 @@ class PyMISP: def accept_user_registration(self, registration: MISPInbox | int | str | UUID, organisation: MISPOrganisation | int | str | UUID | None = None, role: MISPRole | int | str | None = None, - perm_sync: bool = False, perm_publish: bool = False, perm_admin: bool = False, - unsafe_fallback: bool = False): + perm_sync: bool = False, perm_publish: bool = False, + perm_admin: bool = False, + unsafe_fallback: bool = False) -> dict[str, Any]: """Accept a user registration :param registration: the registration to accept @@ -2399,11 +2400,11 @@ class PyMISP: if role: role_id = role_id = get_uuid_or_id_from_abstract_misp(role) else: - for role in self.roles(pythonify=True): - if not isinstance(role, MISPRole): + for _r in self.roles(pythonify=True): + if not isinstance(_r, MISPRole): continue - if role.default_role: # type: ignore - role_id = get_uuid_or_id_from_abstract_misp(role) + if _r.default_role: # type: ignore + role_id = get_uuid_or_id_from_abstract_misp(_r) break else: raise PyMISPError('Unable to find default role') @@ -2431,7 +2432,7 @@ class PyMISP: r = self._prepare_request('POST', f'users/acceptRegistrations/{registration_id}', data=to_post) return self._check_json_response(r) - def discard_user_registration(self, registration: MISPInbox | int | str | UUID): + def discard_user_registration(self, registration: MISPInbox | int | str | UUID) -> dict[str, Any]: """Discard a user registration :param registration: the registration to discard @@ -2444,14 +2445,14 @@ class PyMISP: # ## BEGIN Role ### - def roles(self, pythonify: bool = False) -> dict | list[MISPRole]: + def roles(self, pythonify: bool = False) -> dict[str, Any] | list[MISPRole] | list[dict[str, Any]]: """Get the existing roles :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'roles/index') - roles = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in roles: + roles = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(roles, dict): return roles to_return = [] for role in roles: @@ -2460,7 +2461,7 @@ class PyMISP: to_return.append(nr) return to_return - def set_default_role(self, role: MISPRole | int | str | UUID) -> dict: + def set_default_role(self, role: MISPRole | int | str | UUID) -> dict[str, Any]: """Set a default role for the new user accounts :param role: the default role to set @@ -2474,19 +2475,19 @@ class PyMISP: # ## BEGIN Decaying Models ### - def update_decaying_models(self) -> dict: + def update_decaying_models(self) -> dict[str, Any]: """Update all the Decaying models""" response = self._prepare_request('POST', 'decayingModel/update') return self._check_json_response(response) - def decaying_models(self, pythonify: bool = False) -> dict | list[MISPDecayingModel]: + def decaying_models(self, pythonify: bool = False) -> dict[str, Any] | list[MISPDecayingModel] | list[dict[str, Any]]: """Get all the decaying models :param pythonify: Returns a list of PyMISP Objects instead of the plain json output """ r = self._prepare_request('GET', 'decayingModel/index') - models = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in models: + models = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(models, dict): return models to_return = [] for model in models: @@ -2495,7 +2496,7 @@ class PyMISP: to_return.append(n) return to_return - def enable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict: + def enable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict[str, Any]: """Enable a decaying Model""" if isinstance(decaying_model, MISPDecayingModel): decaying_model_id = decaying_model.id @@ -2504,7 +2505,7 @@ class PyMISP: response = self._prepare_request('POST', f'decayingModel/enable/{decaying_model_id}') return self._check_json_response(response) - def disable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict: + def disable_decaying_model(self, decaying_model: MISPDecayingModel | int | str) -> dict[str, Any]: """Disable a decaying Model""" if isinstance(decaying_model, MISPDecayingModel): decaying_model_id = decaying_model.id @@ -2517,7 +2518,7 @@ class PyMISP: # ## BEGIN Search methods ### - def search(self, controller: str = 'events', return_format: str = 'json', + def search(self, controller: str = 'events', return_format: str = 'json', # type: ignore[no-untyped-def] limit: int | None = None, page: int | None = None, value: SearchParameterTypes | None = None, type_attribute: SearchParameterTypes | None = None, @@ -2564,7 +2565,7 @@ class PyMISP: exclude_decayed: bool | None = None, sharinggroup: int | list[int] | None = None, pythonify: bool | None = False, - **kwargs) -> dict | str | list[MISPEvent | MISPAttribute | MISPObject]: + **kwargs) -> dict[str, Any] | str | list[MISPEvent | MISPAttribute | MISPObject] | list[dict[str, Any]]: '''Search in the MISP instance :param controller: Controller to search on, it can be `events`, `objects`, `attributes`. The response will either be a list of events, objects, or attributes. @@ -2730,6 +2731,7 @@ class PyMISP: elif return_format not in ['json', 'yara-json']: return self._check_response(response) + # This one is truly fucked: event returns a list, attributes doesn't. normalized_response = self._check_json_response(response) if 'errors' in normalized_response: @@ -2739,6 +2741,8 @@ class PyMISP: # The response is in json, we can convert it to a list of pythonic MISP objects to_return: list[MISPEvent | MISPAttribute | MISPObject] = [] if controller == 'events': + if isinstance(normalized_response, dict): + return normalized_response for e in normalized_response: me = MISPEvent() me.load(e) @@ -2772,6 +2776,8 @@ class PyMISP: ma.Sighting = sightings to_return.append(ma) elif controller == 'objects': + if isinstance(normalized_response, dict): + return normalized_response for o in normalized_response: mo = MISPObject(o['Object']['name']) mo.from_dict(**o) @@ -2809,7 +2815,7 @@ class PyMISP: desc: bool | None = None, limit: int | None = None, page: int | None = None, - pythonify: bool | None = None) -> dict | list[MISPEvent]: + pythonify: bool | None = None) -> dict[str, Any] | list[MISPEvent] | list[dict[str, Any]]: """Search event metadata shown on the event index page. Using ! in front of a value means NOT, except for parameters date_from, date_to and timestamp which cannot be negated. Criteria are AND-ed together; values in lists are OR-ed together. Return matching events @@ -2866,9 +2872,9 @@ class PyMISP: query["direction"] = "desc" if desc else "asc" url = urljoin(self.root_url, 'events/index') response = self._prepare_request('POST', url, data=query) - normalized_response = self._check_json_response(response) + normalized_response = self._check_json_response_list(response) - if not (self.global_pythonify or pythonify): + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] for e_meta in normalized_response: @@ -2895,7 +2901,7 @@ class PyMISP: include_attribute: bool | None = None, include_event_meta: bool | None = None, pythonify: bool | None = False - ) -> dict | list[dict[str, MISPEvent | MISPAttribute | MISPSighting]]: + ) -> dict[str, Any] | list[dict[str, MISPEvent | MISPAttribute | MISPSighting]]: '''Search sightings :param context: The context of the search. Can be either "attribute", "event", or nothing (will then match on events and attributes). @@ -2942,8 +2948,8 @@ class PyMISP: url = urljoin(self.root_url, url_path) response = self._prepare_request('POST', url, data=query) - normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + normalized_response = self._check_json_response_list(response) + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response if self.global_pythonify or pythonify: @@ -2974,7 +2980,7 @@ class PyMISP: action: str | None = None, user_id: int | None = None, change: str | None = None, email: str | None = None, org: str | None = None, description: str | None = None, - ip: str | None = None, pythonify: bool | None = False) -> dict | list[MISPLog]: + ip: str | None = None, pythonify: bool | None = False) -> dict[str, Any] | list[MISPLog] | list[dict[str, Any]]: '''Search in logs Note: to run substring queries simply append/prepend/encapsulate the search term with % @@ -3003,8 +3009,8 @@ class PyMISP: query['created'] = query.pop('created').timestamp() response = self._prepare_request('POST', 'admin/logs/index', data=query) - normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + normalized_response = self._check_json_response_list(response) + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] @@ -3014,11 +3020,11 @@ class PyMISP: to_return.append(ml) return to_return - def search_feeds(self, value: SearchParameterTypes | None = None, pythonify: bool | None = False) -> dict | list[MISPFeed]: + def search_feeds(self, value: SearchParameterTypes | None = None, pythonify: bool | None = False) -> dict[str, Any] | list[MISPFeed] | list[dict[str, Any]]: '''Search in the feeds cached on the servers''' response = self._prepare_request('POST', 'feeds/searchCaches', data={'value': value}) - normalized_response = self._check_json_response(response) - if not (self.global_pythonify or pythonify) or 'errors' in normalized_response: + normalized_response = self._check_json_response_list(response) + if not (self.global_pythonify or pythonify) or isinstance(normalized_response, dict): return normalized_response to_return = [] for feed in normalized_response: @@ -3031,14 +3037,14 @@ class PyMISP: # ## BEGIN Communities ### - def communities(self, pythonify: bool = False) -> dict | list[MISPCommunity]: + def communities(self, pythonify: bool = False) -> dict[str, Any] | list[MISPCommunity] | list[dict[str, Any]]: """Get all the communities :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'communities/index') - communities = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in communities: + communities = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(communities, dict): return communities to_return = [] for community in communities: @@ -3047,7 +3053,7 @@ class PyMISP: to_return.append(c) return to_return - def get_community(self, community: MISPCommunity | int | str | UUID, pythonify: bool = False) -> dict | MISPCommunity: + def get_community(self, community: MISPCommunity | int | str | UUID, pythonify: bool = False) -> dict[str, Any] | MISPCommunity: """Get a community by id from a MISP instance :param community: community to get @@ -3070,7 +3076,7 @@ class PyMISP: requestor_organisation_description: str | None = None, message: str | None = None, sync: bool = False, anonymise_requestor_server: bool = False, - mock: bool = False) -> dict: + mock: bool = False) -> dict[str, Any]: """Request the access to a community :param community: community to request access @@ -3098,14 +3104,14 @@ class PyMISP: # ## BEGIN Event Delegation ### - def event_delegations(self, pythonify: bool = False) -> dict | list[MISPEventDelegation]: + def event_delegations(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEventDelegation] | list[dict[str, Any]]: """Get all the event delegations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'eventDelegations') - delegations = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in delegations: + delegations = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(delegations, dict): return delegations to_return = [] for delegation in delegations: @@ -3114,7 +3120,7 @@ class PyMISP: to_return.append(d) return to_return - def accept_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict: + def accept_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict[str, Any]: """Accept the delegation of an event :param delegation: event delegation to accept @@ -3124,7 +3130,7 @@ class PyMISP: r = self._prepare_request('POST', f'eventDelegations/acceptDelegation/{delegation_id}') return self._check_json_response(r) - def discard_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict: + def discard_event_delegation(self, delegation: MISPEventDelegation | int | str, pythonify: bool = False) -> dict[str, Any]: """Discard the delegation of an event :param delegation: event delegation to discard @@ -3137,7 +3143,7 @@ class PyMISP: def delegate_event(self, event: MISPEvent | int | str | UUID | None = None, organisation: MISPOrganisation | int | str | UUID | None = None, event_delegation: MISPEventDelegation | None = None, - distribution: int = -1, message: str = '', pythonify: bool = False) -> dict | MISPEventDelegation: + distribution: int = -1, message: str = '', pythonify: bool = False) -> dict[str, Any] | MISPEventDelegation: """Delegate an event. Either event and organisation OR event_delegation are required :param event: event to delegate @@ -3167,7 +3173,7 @@ class PyMISP: # ## BEGIN Others ### - def push_event_to_ZMQ(self, event: MISPEvent | int | str | UUID) -> dict: + def push_event_to_ZMQ(self, event: MISPEvent | int | str | UUID) -> dict[str, Any]: """Force push an event by id on ZMQ :param event: the event to push @@ -3176,7 +3182,7 @@ class PyMISP: response = self._prepare_request('POST', f'events/pushEventToZMQ/{event_id}.json') return self._check_json_response(response) - def direct_call(self, url: str, data: dict | None = None, params: Mapping = {}, kw_params: Mapping = {}) -> Any: + def direct_call(self, url: str, data: dict[str, Any] | None = None, params: Mapping[str, Any] = {}, kw_params: Mapping[str, Any] = {}) -> Any: """Very lightweight call that posts a data blob (python dictionary or json string) on the URL :param url: URL to post to @@ -3190,8 +3196,8 @@ class PyMISP: response = self._prepare_request('POST', url, data=data, params=params, kw_params=kw_params) return self._check_response(response, lenient_response_type=True) - def freetext(self, event: MISPEvent | int | str | UUID, string: str, adhereToWarninglists: bool | str = False, - distribution: int | None = None, returnMetaAttributes: bool = False, pythonify: bool = False, **kwargs) -> dict | list[MISPAttribute]: + def freetext(self, event: MISPEvent | int | str | UUID, string: str, adhereToWarninglists: bool | str = False, # type: ignore[no-untyped-def] + distribution: int | None = None, returnMetaAttributes: bool = False, pythonify: bool = False, **kwargs) -> dict[str, Any] | list[MISPAttribute] | list[dict[str, Any]]: """Pass a text to the freetext importer :param event: event @@ -3215,8 +3221,8 @@ class PyMISP: if returnMetaAttributes: query['returnMetaAttributes'] = returnMetaAttributes r = self._prepare_request('POST', f'events/freeTextImport/{event_id}', data=query, **kwargs) - attributes = self._check_json_response(r) - if returnMetaAttributes or not (self.global_pythonify or pythonify) or 'errors' in attributes: + attributes = self._check_json_response_list(r) + if returnMetaAttributes or not (self.global_pythonify or pythonify) or isinstance(attributes, dict): return attributes to_return = [] for attribute in attributes: @@ -3225,7 +3231,8 @@ class PyMISP: to_return.append(a) return to_return - def upload_stix(self, path: str | Path | BytesIO | StringIO | None = None, data: str | bytes | None = None, version: str = '2'): + def upload_stix(self, path: str | Path | BytesIO | StringIO | None = None, + data: str | bytes | None = None, version: str = '2') -> requests.Response: """Upload a STIX file to MISP. :param path: Path to the STIX on the disk (can be a path-like object, or a pseudofile) @@ -3256,7 +3263,7 @@ class PyMISP: # ## BEGIN Statistics ### - def attributes_statistics(self, context: str = 'type', percentage: bool = False) -> dict: + def attributes_statistics(self, context: str = 'type', percentage: bool = False) -> dict[str, Any]: """Get attribute statistics from the MISP instance :param context: "type" or "category" @@ -3272,7 +3279,7 @@ class PyMISP: response = self._prepare_request('GET', path) return self._check_json_response(response) - def tags_statistics(self, percentage: bool = False, name_sort: bool = False) -> dict: + def tags_statistics(self, percentage: bool = False, name_sort: bool = False) -> dict[str, Any]: """Get tag statistics from the MISP instance :param percentage: get percentages @@ -3291,7 +3298,7 @@ class PyMISP: response = self._prepare_request('GET', f'tags/tagStatistics/{p}/{ns}') return self._check_json_response(response) - def users_statistics(self, context: str = 'data') -> dict: + def users_statistics(self, context: str = 'data') -> dict[str, Any]: """Get user statistics from the MISP instance :param context: one of 'data', 'orgs', 'users', 'tags', 'attributehistogram', 'sightings', 'galaxyMatrix' @@ -3306,14 +3313,14 @@ class PyMISP: # ## BEGIN User Settings ### - def user_settings(self, pythonify: bool = False) -> dict | list[MISPUserSetting]: + def user_settings(self, pythonify: bool = False) -> dict[str, Any] | list[MISPUserSetting] | list[dict[str, Any]]: """Get all the user settings: https://www.misp-project.org/openapi/#tag/UserSettings/operation/getUserSettings :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'userSettings/index') - user_settings = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in user_settings: + user_settings = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(user_settings, dict): return user_settings to_return = [] for user_setting in user_settings: @@ -3323,7 +3330,7 @@ class PyMISP: return to_return def get_user_setting(self, user_setting: str, user: MISPUser | int | str | UUID | None = None, - pythonify: bool = False) -> dict | MISPUserSetting: + pythonify: bool = False) -> dict[str, Any] | MISPUserSetting: """Get a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/getUserSettingById :param user_setting: name of user setting @@ -3341,8 +3348,8 @@ class PyMISP: u.from_dict(**user_setting_j) return u - def set_user_setting(self, user_setting: str, value: str | dict, user: MISPUser | int | str | UUID | None = None, - pythonify: bool = False) -> dict | MISPUserSetting: + def set_user_setting(self, user_setting: str, value: str | dict[str, Any], user: MISPUser | int | str | UUID | None = None, + pythonify: bool = False) -> dict[str, Any] | MISPUserSetting: """Set a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/setUserSetting :param user_setting: name of user setting @@ -3364,7 +3371,7 @@ class PyMISP: u.from_dict(**user_setting_j) return u - def delete_user_setting(self, user_setting: str, user: MISPUser | int | str | UUID | None = None) -> dict: + def delete_user_setting(self, user_setting: str, user: MISPUser | int | str | UUID | None = None) -> dict[str, Any]: """Delete a user setting: https://www.misp-project.org/openapi/#tag/UserSettings/operation/deleteUserSettingById :param user_setting: name of user setting @@ -3380,14 +3387,14 @@ class PyMISP: # ## BEGIN Blocklists ### - def event_blocklists(self, pythonify: bool = False) -> dict | list[MISPEventBlocklist]: + def event_blocklists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPEventBlocklist] | list[dict[str, Any]]: """Get all the blocklisted events :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'eventBlocklists/index') - event_blocklists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in event_blocklists: + event_blocklists = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(event_blocklists, dict): return event_blocklists to_return = [] for event_blocklist in event_blocklists: @@ -3396,14 +3403,14 @@ class PyMISP: to_return.append(ebl) return to_return - def organisation_blocklists(self, pythonify: bool = False) -> dict | list[MISPOrganisationBlocklist]: + def organisation_blocklists(self, pythonify: bool = False) -> dict[str, Any] | list[MISPOrganisationBlocklist] | list[dict[str, Any]]: """Get all the blocklisted organisations :param pythonify: Returns a list of PyMISP Objects instead of the plain json output. Warning: it might use a lot of RAM """ r = self._prepare_request('GET', 'orgBlocklists/index') - organisation_blocklists = self._check_json_response(r) - if not (self.global_pythonify or pythonify) or 'errors' in organisation_blocklists: + organisation_blocklists = self._check_json_response_list(r) + if not (self.global_pythonify or pythonify) or isinstance(organisation_blocklists, dict): return organisation_blocklists to_return = [] for organisation_blocklist in organisation_blocklists: @@ -3412,7 +3419,7 @@ class PyMISP: to_return.append(obl) return to_return - def _add_entries_to_blocklist(self, blocklist_type: str, uuids: str | list[str], **kwargs) -> dict: + def _add_entries_to_blocklist(self, blocklist_type: str, uuids: str | list[str], **kwargs) -> dict[str, Any]: # type: ignore[no-untyped-def] if blocklist_type == 'event': url = 'eventBlocklists/add' elif blocklist_type == 'organisation': @@ -3428,7 +3435,7 @@ class PyMISP: return self._check_json_response(r) def add_event_blocklist(self, uuids: str | list[str], comment: str | None = None, - event_info: str | None = None, event_orgc: str | None = None) -> dict: + event_info: str | None = None, event_orgc: str | None = None) -> dict[str, Any]: """Add a new event in the blocklist :param uuids: UUIDs @@ -3439,7 +3446,7 @@ class PyMISP: return self._add_entries_to_blocklist('event', uuids=uuids, comment=comment, event_info=event_info, event_orgc=event_orgc) def add_organisation_blocklist(self, uuids: str | list[str], comment: str | None = None, - org_name: str | None = None) -> dict: + org_name: str | None = None) -> dict[str, Any]: """Add a new organisation in the blocklist :param uuids: UUIDs @@ -3448,7 +3455,7 @@ class PyMISP: """ return self._add_entries_to_blocklist('organisation', uuids=uuids, comment=comment, org_name=org_name) - def _update_entries_in_blocklist(self, blocklist_type: str, uuid, **kwargs) -> dict: + def _update_entries_in_blocklist(self, blocklist_type: str, uuid, **kwargs) -> dict[str, Any]: # type: ignore[no-untyped-def] if blocklist_type == 'event': url = f'eventBlocklists/edit/{uuid}' elif blocklist_type == 'organisation': @@ -3459,7 +3466,7 @@ class PyMISP: r = self._prepare_request('POST', url, data=data) return self._check_json_response(r) - def update_event_blocklist(self, event_blocklist: MISPEventBlocklist, event_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict | MISPEventBlocklist: + def update_event_blocklist(self, event_blocklist: MISPEventBlocklist, event_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | MISPEventBlocklist: """Update an event in the blocklist :param event_blocklist: event block list @@ -3477,7 +3484,7 @@ class PyMISP: e.from_dict(**updated_event_blocklist) return e - def update_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist, organisation_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict | MISPOrganisationBlocklist: + def update_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist, organisation_blocklist_id: int | str | UUID | None = None, pythonify: bool = False) -> dict[str, Any] | MISPOrganisationBlocklist: """Update an organisation in the blocklist :param organisation_blocklist: organization block list @@ -3495,7 +3502,7 @@ class PyMISP: o.from_dict(**updated_organisation_blocklist) return o - def delete_event_blocklist(self, event_blocklist: MISPEventBlocklist | str | UUID) -> dict: + def delete_event_blocklist(self, event_blocklist: MISPEventBlocklist | str | UUID) -> dict[str, Any]: """Delete a blocklisted event by id :param event_blocklist: event block list to delete @@ -3504,7 +3511,7 @@ class PyMISP: response = self._prepare_request('POST', f'eventBlocklists/delete/{event_blocklist_id}') return self._check_json_response(response) - def delete_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist | str | UUID) -> dict: + def delete_organisation_blocklist(self, organisation_blocklist: MISPOrganisationBlocklist | str | UUID) -> dict[str, Any]: """Delete a blocklisted organisation by id :param organisation_blocklist: organization block list to delete @@ -3517,7 +3524,8 @@ class PyMISP: # ## BEGIN Global helpers ### - def change_sharing_group_on_entity(self, misp_entity: MISPEvent | MISPAttribute | MISPObject, sharing_group_id, pythonify: bool = False) -> dict | MISPEvent | MISPObject | MISPAttribute | MISPShadowAttribute: + def change_sharing_group_on_entity(self, misp_entity: MISPEvent | MISPAttribute | MISPObject, + sharing_group_id: int, pythonify: bool = False) -> dict[str, Any] | MISPEvent | MISPObject | MISPAttribute | MISPShadowAttribute: """Change the sharing group of an event, an attribute, or an object :param misp_entity: entity to change @@ -3539,8 +3547,8 @@ class PyMISP: raise PyMISPError('The misp_entity must be MISPEvent, MISPObject or MISPAttribute') - def tag(self, misp_entity: AbstractMISP | str | dict, tag: MISPTag | str, - local: bool = False, relationship_type: str | None = None) -> dict: + def tag(self, misp_entity: AbstractMISP | str | dict[str, Any], tag: MISPTag | str, + local: bool = False, relationship_type: str | None = None) -> dict[str, Any]: """Tag an event or an attribute. :param misp_entity: a MISPEvent, a MISP Attribute, or a UUID @@ -3559,7 +3567,7 @@ class PyMISP: response = self._prepare_request('POST', 'tags/attachTagToObject', data=to_post) return self._check_json_response(response) - def untag(self, misp_entity: AbstractMISP | str | dict, tag: MISPTag | str) -> dict: + def untag(self, misp_entity: AbstractMISP | str | dict[str, Any], tag: MISPTag | str) -> dict[str, Any]: """Untag an event or an attribute :param misp_entity: misp_entity can be a UUID @@ -3600,7 +3608,7 @@ class PyMISP: # ## MISP internal tasks ### - def get_all_functions(self, not_implemented: bool = False): + def get_all_functions(self, not_implemented: bool = False) -> list[str]: '''Get all methods available via the API, including ones that are not implemented.''' response = self._prepare_request('GET', 'servers/queryACL/printAllFunctionNames') functions = self._check_json_response(response) @@ -3617,7 +3625,7 @@ class PyMISP: paths.append(path) if not not_implemented: - return path + return [str(path)] with open(__file__) as f: content = f.read() @@ -3631,7 +3639,7 @@ class PyMISP: # ## Internal methods ### - def _old_misp(self, minimal_version_required: tuple, removal_date: str | date | datetime, method: str | None = None, message: str | None = None) -> bool: + def _old_misp(self, minimal_version_required: tuple[int], removal_date: str | date | datetime, method: str | None = None, message: str | None = None) -> bool: if self._misp_version >= minimal_version_required: return False if isinstance(removal_date, (datetime, date)): @@ -3669,11 +3677,21 @@ class PyMISP: return value return value - def _check_json_response(self, response: requests.Response) -> dict: # type: ignore + def _check_json_response_list(self, response: requests.Response) -> dict[str, Any] | list[dict[str, Any]]: r = self._check_response(response, expect_json=True) - if isinstance(r, (dict, list)): + if isinstance(r, dict) and 'errors' in r: + return r + if isinstance(r, list): return r # Else: an exception was raised anyway + raise PyMISPUnexpectedResponse(f'A list was expected, got a {type(r)}: {r}') + + def _check_json_response(self, response: requests.Response) -> dict[str, Any]: + r = self._check_response(response, expect_json=True) + if isinstance(r, dict): + return r + # Else: an exception was raised anyway + raise PyMISPUnexpectedResponse(f'A dict was expected, got a string: {r}') def _check_head_response(self, response: requests.Response) -> bool: if response.status_code == 200: @@ -3683,7 +3701,7 @@ class PyMISP: else: raise MISPServerError(f'Error code {response.status_code} for HEAD request') - def _check_response(self, response: requests.Response, lenient_response_type: bool = False, expect_json: bool = False) -> dict | str: + def _check_response(self, response: requests.Response, lenient_response_type: bool = False, expect_json: bool = False) -> dict[str, Any] | str: """Check if the response from the server is not an unexpected error""" if response.status_code >= 500: headers_without_auth = {i: response.request.headers[i] for i in response.request.headers if i != 'Authorization'} @@ -3722,11 +3740,11 @@ class PyMISP: return {'errors': 'The response is empty.'} return response.text - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(url={self.root_url})' - def _prepare_request(self, request_type: str, url: str, data: Iterable | Mapping | AbstractMISP | bytes | None = None, - params: Mapping = {}, kw_params: Mapping = {}, + def _prepare_request(self, request_type: str, url: str, data: Iterable[Any] | Mapping[str, Any] | AbstractMISP | bytes | None = None, + params: Mapping[str, Any] = {}, kw_params: Mapping[str, Any] = {}, output_type: str = 'json', content_type: str = 'json') -> requests.Response: '''Prepare a request for python-requests''' if url[0] == '/': @@ -3769,7 +3787,7 @@ class PyMISP: verify=self.ssl, cert=self.cert) return self.__session.send(prepped, timeout=self.timeout, **settings) - def _csv_to_dict(self, csv_content: str) -> list[dict]: + def _csv_to_dict(self, csv_content: str) -> list[dict[str, Any]]: '''Makes a list of dict out of a csv file (requires headers)''' fieldnames, lines = csv_content.split('\n', 1) fields = fieldnames.split(',') diff --git a/pymisp/exceptions.py b/pymisp/exceptions.py index 2479bec..a0dd736 100644 --- a/pymisp/exceptions.py +++ b/pymisp/exceptions.py @@ -2,7 +2,7 @@ from __future__ import annotations class PyMISPError(Exception): - def __init__(self, message): + def __init__(self, message: str) -> None: super().__init__(message) self.message = message diff --git a/pymisp/mispevent.py b/pymisp/mispevent.py index 899fadc..ac1361c 100644 --- a/pymisp/mispevent.py +++ b/pymisp/mispevent.py @@ -35,22 +35,8 @@ try: except ImportError: logger.exception("Cannot import dateutil") -try: - # pyme renamed to gpg the 2016-10-28 - import gpg # type: ignore - from gpg.constants.sig import mode # type: ignore - has_pyme = True -except ImportError: - try: - # pyme renamed to gpg the 2016-10-28 - import pyme as gpg # type: ignore - from pyme.constants.sig import mode # type: ignore - has_pyme = True - except ImportError: - has_pyme = False - -def _make_datetime(value) -> datetime: +def _make_datetime(value: int | float | str | datetime | date) -> datetime: if isinstance(value, (int, float)): # Timestamp value = datetime.fromtimestamp(value) @@ -59,7 +45,7 @@ def _make_datetime(value) -> datetime: # faster value = datetime.fromisoformat(value) except Exception: - value = parse(value) + value = parse(value) # type: ignore[arg-type] elif isinstance(value, datetime): pass elif isinstance(value, date): # NOTE: date has to be *after* datetime, or it will be overwritten @@ -73,7 +59,7 @@ def _make_datetime(value) -> datetime: return value -def make_bool(value: bool | int | str | dict | list | None) -> bool: +def make_bool(value: bool | int | str | dict[str, Any] | list[Any] | None) -> bool: """Converts the supplied value to a boolean. :param value: Value to interpret as a boolean. An empty string, dict @@ -96,14 +82,14 @@ def make_bool(value: bool | int | str | dict | list | None) -> bool: class MISPOrganisation(AbstractMISP): - _fields_for_feed: set = {'name', 'uuid'} + _fields_for_feed: set[str] = {'name', 'uuid'} def __init__(self) -> None: super().__init__() self.id: int self.name: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Organisation' in kwargs: kwargs = kwargs['Organisation'] super().from_dict(**kwargs) @@ -115,14 +101,14 @@ class MISPOrganisation(AbstractMISP): class MISPSharingGroupOrg(AbstractMISP): - _fields_for_feed: set = {'extend', 'Organisation'} + _fields_for_feed: set[str] = {'extend', 'Organisation'} def __init__(self) -> None: super().__init__() self.extend: bool self.Organisation: MISPOrganisation - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'SharingGroupOrg' in kwargs: kwargs = kwargs['SharingGroupOrg'] if 'Organisation' in kwargs: @@ -135,14 +121,14 @@ class MISPSharingGroupOrg(AbstractMISP): return f'<{self.__class__.__name__}(Org={self.Organisation.name}, extend={self.extend})' return f'<{self.__class__.__name__}(NotInitialized)' - def _to_feed(self) -> dict: + def _to_feed(self) -> dict[str, Any]: to_return = super()._to_feed() to_return['Organisation'] = self.Organisation._to_feed() return to_return class MISPSharingGroup(AbstractMISP): - _fields_for_feed: set = {'uuid', 'name', 'roaming', 'created', 'organisation_uuid', 'Organisation', 'SharingGroupOrg', 'SharingGroupServer'} + _fields_for_feed: set[str] = {'uuid', 'name', 'roaming', 'created', 'organisation_uuid', 'Organisation', 'SharingGroupOrg', 'SharingGroupServer'} def __init__(self) -> None: super().__init__() @@ -154,19 +140,19 @@ class MISPSharingGroup(AbstractMISP): return self.SharingGroupOrg @sgorgs.setter - def sgorgs(self, sgorgs: list[MISPSharingGroupOrg]): + def sgorgs(self, sgorgs: list[MISPSharingGroupOrg]) -> None: if all(isinstance(x, MISPSharingGroupOrg) for x in sgorgs): self.SharingGroupOrg = sgorgs else: raise PyMISPError('All the attributes have to be of type MISPSharingGroupOrg.') - def add_sgorg(self, sgorg): + def add_sgorg(self, sgorg: dict[str, Any]) -> MISPSharingGroupOrg: misp_sgorg = MISPSharingGroupOrg() misp_sgorg.from_dict(**sgorg) self.SharingGroupOrg.append(misp_sgorg) return misp_sgorg - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'SharingGroupOrg' in kwargs: [self.add_sgorg(sgorg) for sgorg in kwargs.pop('SharingGroupOrg')] if 'SharingGroup' in kwargs: @@ -178,7 +164,7 @@ class MISPSharingGroup(AbstractMISP): return f'<{self.__class__.__name__}(name={self.name})>' return f'<{self.__class__.__name__}(NotInitialized)>' - def _to_feed(self) -> dict: + def _to_feed(self) -> dict[str, Any]: to_return = super()._to_feed() to_return['SharingGroupOrg'] = [sgorg._to_feed() for sgorg in self.SharingGroupOrg] to_return['Organisation'].pop('id', None) @@ -197,7 +183,7 @@ class MISPShadowAttribute(AbstractMISP): self.type: str self.value: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ShadowAttribute' in kwargs: kwargs = kwargs['ShadowAttribute'] super().from_dict(**kwargs) @@ -215,7 +201,7 @@ class MISPSighting(AbstractMISP): self.id: int self.value: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Initialize the MISPSighting from a dictionary :param value: Value of the attribute the sighting is related too. Pushing this object @@ -241,11 +227,11 @@ class MISPSighting(AbstractMISP): class MISPAttribute(AbstractMISP): - _fields_for_feed: set = {'uuid', 'value', 'category', 'type', 'comment', 'data', - 'deleted', 'timestamp', 'to_ids', 'disable_correlation', - 'first_seen', 'last_seen'} + _fields_for_feed: set[str] = {'uuid', 'value', 'category', 'type', 'comment', 'data', + 'deleted', 'timestamp', 'to_ids', 'disable_correlation', + 'first_seen', 'last_seen'} - def __init__(self, describe_types: dict | None = None, strict: bool = False): + def __init__(self, describe_types: dict[str, Any] | None = None, strict: bool = False): """Represents an Attribute :param describe_types: Use it if you want to overwrite the default describeTypes.json file (you don't) @@ -268,6 +254,9 @@ class MISPAttribute(AbstractMISP): self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] + self.expand: str + self.timestamp: float | int | datetime + # For search self.Event: MISPEvent self.RelatedAttribute: list[MISPAttribute] @@ -275,7 +264,7 @@ class MISPAttribute(AbstractMISP): # For malware sample self._malware_binary: BytesIO | None - def add_tag(self, tag: str | MISPTag | dict | None = None, **kwargs) -> MISPTag: + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property @@ -284,11 +273,11 @@ class MISPAttribute(AbstractMISP): return self.Tag @tags.setter - def tags(self, tags: list[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) - def add_galaxy(self, galaxy: MISPGalaxy | dict | None = None, **kwargs) -> MISPGalaxy: + def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def] """Add a galaxy to the Attribute, either by passing a MISPGalaxy or a dictionary""" if isinstance(galaxy, MISPGalaxy): self.galaxies.append(galaxy) @@ -309,7 +298,7 @@ class MISPAttribute(AbstractMISP): """Returns a list of galaxies associated to this Attribute""" return self.Galaxy - def _prepare_data(self, data: Path | str | bytes | BytesIO | None): + def _prepare_data(self, data: Path | str | bytes | BytesIO | None) -> None: if not data: super().__setattr__('data', None) return @@ -341,7 +330,7 @@ class MISPAttribute(AbstractMISP): # not a encrypted zip file, assuming it is a new malware sample self._prepare_new_malware_sample() - def __setattr__(self, name: str, value: Any): + def __setattr__(self, name: str, value: Any) -> None: if name in ['first_seen', 'last_seen']: _datetime = _make_datetime(value) @@ -376,13 +365,13 @@ class MISPAttribute(AbstractMISP): h.update(to_encode.encode("utf-8")) return [h.hexdigest()] - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def _to_feed(self, with_distribution=False) -> dict: + def _to_feed(self, with_distribution: bool=False) -> dict[str, Any]: if with_distribution: self._fields_for_feed.add('distribution') to_return = super()._to_feed() @@ -429,7 +418,7 @@ class MISPAttribute(AbstractMISP): return self.ShadowAttribute @shadow_attributes.setter - def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]): + def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]) -> None: """Set a list of prepared MISPShadowAttribute.""" if all(isinstance(x, MISPShadowAttribute) for x in shadow_attributes): self.ShadowAttribute = shadow_attributes @@ -441,22 +430,22 @@ class MISPAttribute(AbstractMISP): return self.Sighting @sightings.setter - def sightings(self, sightings: list[MISPSighting]): + def sightings(self, sightings: list[MISPSighting]) -> None: """Set a list of prepared MISPSighting.""" if all(isinstance(x, MISPSighting) for x in sightings): self.Sighting = sightings else: raise PyMISPError('All the attributes have to be of type MISPSighting.') - def delete(self): + def delete(self) -> None: """Mark the attribute as deleted (soft delete)""" self.deleted = True - def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Alias for add_shadow_attribute""" return self.add_shadow_attribute(shadow_attribute, **kwargs) - def add_shadow_attribute(self, shadow_attribute: MISPShadowAttribute | dict | None = None, **kwargs) -> MISPShadowAttribute: + def add_shadow_attribute(self, shadow_attribute: MISPShadowAttribute | dict[str, Any] | None = None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Add a shadow attribute to the attribute (by name or a MISPShadowAttribute object)""" if isinstance(shadow_attribute, MISPShadowAttribute): misp_shadow_attribute = shadow_attribute @@ -467,12 +456,12 @@ class MISPAttribute(AbstractMISP): misp_shadow_attribute = MISPShadowAttribute() misp_shadow_attribute.from_dict(**kwargs) else: - raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {shadow_attribute}") + raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {shadow_attribute}") self.shadow_attributes.append(misp_shadow_attribute) self.edited = True return misp_shadow_attribute - def add_sighting(self, sighting: MISPSighting | dict | None = None, **kwargs) -> MISPSighting: + def add_sighting(self, sighting: MISPSighting | dict[str, Any] | None = None, **kwargs) -> MISPSighting: # type: ignore[no-untyped-def] """Add a sighting to the attribute (by name or a MISPSighting object)""" if isinstance(sighting, MISPSighting): misp_sighting = sighting @@ -483,12 +472,12 @@ class MISPAttribute(AbstractMISP): misp_sighting = MISPSighting() misp_sighting.from_dict(**kwargs) else: - raise PyMISPError(f"The sighting is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {sighting}") + raise PyMISPError(f"The sighting is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {sighting}") self.sightings.append(misp_sighting) self.edited = True return misp_sighting - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Attribute' in kwargs: kwargs = kwargs['Attribute'] if kwargs.get('type') and kwargs.get('category'): @@ -610,13 +599,13 @@ class MISPAttribute(AbstractMISP): super().from_dict(**kwargs) - def to_dict(self, json_format: bool = False) -> dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: to_return = super().to_dict(json_format) if self.data: to_return['data'] = base64.b64encode(self.data.getvalue()).decode() return to_return - def _prepare_new_malware_sample(self): + def _prepare_new_malware_sample(self) -> None: if '|' in self.value: # Get the filename, ignore the md5, because humans. self.malware_filename, md5 = self.value.rsplit('|', 1) @@ -627,7 +616,7 @@ class MISPAttribute(AbstractMISP): self._malware_binary = self.data self.encrypt = True - def __is_misp_encrypted_file(self, f) -> bool: + def __is_misp_encrypted_file(self, f: ZipFile) -> bool: files_list = f.namelist() if len(files_list) != 2: return False @@ -642,48 +631,16 @@ class MISPAttribute(AbstractMISP): return False return True - def __repr__(self): + def __repr__(self) -> str: if hasattr(self, 'value'): return '<{self.__class__.__name__}(type={self.type}, value={self.value})'.format(self=self) return f'<{self.__class__.__name__}(NotInitialized)' - def verify(self, gpg_uid): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - signed_data = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(signed_data, signature=base64.b64decode(self.sig), verify=keys[:1]) - return {self.uuid: True} - except Exception: - return {self.uuid: False} - - def _serialize(self): # pragma: no cover - # Not used - return '{type}{category}{to_ids}{uuid}{timestamp}{comment}{deleted}{value}'.format( - type=self.type, category=self.category, to_ids=self.to_ids, uuid=self.uuid, timestamp=self.timestamp, - comment=self.comment, deleted=self.deleted, value=self.value).encode() - - def sign(self, gpg_uid, passphrase=None): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_sign = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign, mode=mode.DETACH) - self.sig = base64.b64encode(signed).decode() - class MISPObjectReference(AbstractMISP): - _fields_for_feed: set = {'uuid', 'timestamp', 'relationship_type', 'comment', - 'object_uuid', 'referenced_uuid'} + _fields_for_feed: set[str] = {'uuid', 'timestamp', 'relationship_type', 'comment', + 'object_uuid', 'referenced_uuid'} def __init__(self) -> None: super().__init__() @@ -692,13 +649,13 @@ class MISPObjectReference(AbstractMISP): self.referenced_uuid: str self.relationship_type: str - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ObjectReference' in kwargs: kwargs = kwargs['ObjectReference'] super().from_dict(**kwargs) @@ -711,11 +668,12 @@ class MISPObjectReference(AbstractMISP): class MISPObject(AbstractMISP): - _fields_for_feed: set = {'name', 'meta-category', 'description', 'template_uuid', - 'template_version', 'uuid', 'timestamp', 'comment', - 'first_seen', 'last_seen', 'deleted'} + _fields_for_feed: set[str] = {'name', 'meta-category', 'description', 'template_uuid', + 'template_version', 'uuid', 'timestamp', 'comment', + 'first_seen', 'last_seen', 'deleted'} - def __init__(self, name: str, strict: bool = False, standalone: bool = True, default_attributes_parameters: dict = {}, **kwargs): + def __init__(self, name: str, strict: bool = False, standalone: bool = True, # type: ignore[no-untyped-def] + default_attributes_parameters: dict[str, Any] = {}, **kwargs) -> None: ''' Master class representing a generic MISP object :param name: Name of the object @@ -731,7 +689,8 @@ class MISPObject(AbstractMISP): self.name: str = name self._known_template: bool = False self.id: int - self._definition: dict | None + self._definition: dict[str, Any] | None + self.timestamp: float | int | datetime misp_objects_template_custom = kwargs.pop('misp_objects_template_custom', None) misp_objects_path_custom = kwargs.pop('misp_objects_path_custom', None) @@ -744,12 +703,12 @@ class MISPObject(AbstractMISP): self.uuid: str = str(uuid.uuid4()) self.first_seen: datetime self.last_seen: datetime - self.__fast_attribute_access: dict = defaultdict(list) # Hashtable object_relation: [attributes] + self.__fast_attribute_access: dict[str, Any] = defaultdict(list) # Hashtable object_relation: [attributes] self.ObjectReference: list[MISPObjectReference] = [] self._standalone: bool = False self.Attribute: list[MISPObjectAttribute] = [] self.SharingGroup: MISPSharingGroup - self._default_attributes_parameters: dict + self._default_attributes_parameters: dict[str, Any] if isinstance(default_attributes_parameters, MISPAttribute): # Just make sure we're not modifying an existing MISPAttribute self._default_attributes_parameters = default_attributes_parameters.to_dict() @@ -784,20 +743,20 @@ class MISPObject(AbstractMISP): self._load_template(template) return True - def _load_template(self, template: dict) -> None: + def _load_template(self, template: dict[str, Any]) -> None: self._definition = template setattr(self, 'meta-category', self._definition['meta-category']) self.template_uuid = self._definition['uuid'] self.description = self._definition['description'] self.template_version = self._definition['version'] - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'comment'): self.comment = '' if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) - def _to_feed(self, with_distribution=False) -> dict: + def _to_feed(self, with_distribution: bool=False) -> dict[str, Any]: if with_distribution: self._fields_for_feed.add('distribution') if not hasattr(self, 'template_uuid'): # workaround for old events where the template_uuid was not yet mandatory @@ -816,7 +775,7 @@ class MISPObject(AbstractMISP): pass return to_return - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any) -> None: if name in ['first_seen', 'last_seen']: value = _make_datetime(value) @@ -826,12 +785,12 @@ class MISPObject(AbstractMISP): logger.warning(f'first_seen ({value}) has to be before last_seen ({self.last_seen})') super().__setattr__(name, value) - def force_misp_objects_path_custom(self, misp_objects_path_custom: Path | str, object_name: str | None = None): + def force_misp_objects_path_custom(self, misp_objects_path_custom: Path | str, object_name: str | None = None) -> None: if object_name: self.name = object_name self._set_template(misp_objects_path_custom) - def _set_template(self, misp_objects_path_custom: Path | str | None = None, misp_objects_template_custom: dict | None = None): + def _set_template(self, misp_objects_path_custom: Path | str | None = None, misp_objects_template_custom: dict[str, Any] | None = None) -> None: if misp_objects_template_custom: # A complete template was given to the constructor self._load_template(misp_objects_template_custom) @@ -853,13 +812,14 @@ class MISPObject(AbstractMISP): # Then we have no meta-category, template_uuid, description and template_version pass - def delete(self): + def delete(self) -> None: """Mark the object as deleted (soft delete)""" self.deleted = True - [a.delete() for a in self.attributes] + for a in self.attributes: + a.delete() @property - def disable_validation(self): + def disable_validation(self) -> None: self._strict = False @property @@ -867,7 +827,7 @@ class MISPObject(AbstractMISP): return self.Attribute @attributes.setter - def attributes(self, attributes: list[MISPObjectAttribute]): + def attributes(self, attributes: list[MISPObjectAttribute]) -> None: if all(isinstance(x, MISPObjectAttribute) for x in attributes): self.Attribute = attributes self.__fast_attribute_access = defaultdict(list) @@ -879,18 +839,18 @@ class MISPObject(AbstractMISP): return self.ObjectReference @references.setter - def references(self, references: list[MISPObjectReference]): + def references(self, references: list[MISPObjectReference]) -> None: if all(isinstance(x, MISPObjectReference) for x in references): self.ObjectReference = references else: raise PyMISPError('All the attributes have to be of type MISPObjectReference.') @property - def standalone(self): + def standalone(self) -> bool: return self._standalone @standalone.setter - def standalone(self, new_standalone: bool): + def standalone(self, new_standalone: bool) -> None: if self._standalone != new_standalone: if new_standalone: self.update_not_jsonable("ObjectReference") @@ -900,7 +860,7 @@ class MISPObject(AbstractMISP): else: pass - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Object' in kwargs: kwargs = kwargs['Object'] if self._known_template: @@ -965,7 +925,7 @@ class MISPObject(AbstractMISP): super().from_dict(**kwargs) - def add_reference(self, referenced_uuid: AbstractMISP | str, relationship_type: str, comment: str | None = None, **kwargs) -> MISPObjectReference: + def add_reference(self, referenced_uuid: AbstractMISP | str, relationship_type: str, comment: str | None = None, **kwargs) -> MISPObjectReference: # type: ignore[no-untyped-def] """Add a link (uuid) to another object""" if isinstance(referenced_uuid, AbstractMISP): # Allow to pass an object or an attribute instead of its UUID @@ -992,7 +952,7 @@ class MISPObject(AbstractMISP): return self._fast_attribute_access.get(object_relation, []) @property - def _fast_attribute_access(self) -> dict: + def _fast_attribute_access(self) -> dict[str, Any]: if not self.__fast_attribute_access: for a in self.attributes: self.__fast_attribute_access[a.object_relation].append(a) @@ -1002,7 +962,7 @@ class MISPObject(AbstractMISP): '''True if all the relations in the list are defined in the object''' return all(relation in self._fast_attribute_access for relation in list_of_relations) - def add_attribute(self, object_relation: str, simple_value: str | int | float | None = None, **value) -> MISPAttribute | None: + def add_attribute(self, object_relation: str, simple_value: str | int | float | None = None, **value) -> MISPAttribute | None: # type: ignore[no-untyped-def] """Add an attribute. :param object_relation: The object relation of the attribute you're adding to the object :param simple_value: The value @@ -1050,7 +1010,7 @@ class MISPObject(AbstractMISP): self.edited = True return attribute - def add_attributes(self, object_relation: str, *attributes) -> list[MISPAttribute | None]: + def add_attributes(self, object_relation: str, *attributes: list[dict[str, Any] | MISPAttribute]) -> list[MISPAttribute | None]: '''Add multiple attributes with the same object_relation. Helper for object_relation when multiple is True in the template. It is the same as calling multiple times add_attribute with the same object_relation. @@ -1064,7 +1024,7 @@ class MISPObject(AbstractMISP): to_return.append(a) return to_return - def to_dict(self, json_format: bool = False, strict: bool = False) -> dict: + def to_dict(self, json_format: bool = False, strict: bool = False) -> dict[str, Any]: if strict or self._strict and self._known_template: self._validate() return super().to_dict(json_format) @@ -1103,9 +1063,11 @@ class MISPObject(AbstractMISP): class MISPEventReport(AbstractMISP): - _fields_for_feed: set = {'uuid', 'name', 'content', 'timestamp', 'deleted'} + _fields_for_feed: set[str] = {'uuid', 'name', 'content', 'timestamp', 'deleted'} - def from_dict(self, **kwargs): + timestamp: float | int | datetime + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventReport' in kwargs: kwargs = kwargs['EventReport'] @@ -1154,7 +1116,7 @@ class MISPEventReport(AbstractMISP): return '<{self.__class__.__name__}(name={self.name})'.format(self=self) return f'<{self.__class__.__name__}(NotInitialized)' - def _set_default(self): + def _set_default(self) -> None: if not hasattr(self, 'timestamp'): self.timestamp = datetime.timestamp(datetime.now()) if not hasattr(self, 'name'): @@ -1182,13 +1144,13 @@ class MISPGalaxyClusterElement(AbstractMISP): return '<{self.__class__.__name__}(key={self.key}, value={self.value})'.format(self=self) return f'<{self.__class__.__name__}(NotInitialized)' - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any) -> None: if key == "value" and isinstance(value, list): raise PyMISPError("You tried to set a list to a cluster element's value. " "Instead, create seperate elements for each value") super().__setattr__(key, value) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if kwargs.get('id'): self.id = int(kwargs.pop('id')) if kwargs.get('galaxy_cluster_id'): @@ -1222,7 +1184,7 @@ class MISPGalaxyClusterRelation(AbstractMISP): self.referenced_galaxy_cluster_type: str self.Tag: list[MISPTag] = [] - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] # Default values for a valid event to send to a MISP instance self.distribution = int(kwargs.pop('distribution', 0)) if self.distribution not in [0, 1, 2, 3, 4, 5]: @@ -1258,7 +1220,7 @@ class MISPGalaxyClusterRelation(AbstractMISP): self.SharingGroup.from_dict(**kwargs.pop('SharingGroup')) super().from_dict(**kwargs) - def add_tag(self, tag: str | MISPTag | dict | None = None, **kwargs) -> MISPTag: + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property @@ -1267,7 +1229,7 @@ class MISPGalaxyClusterRelation(AbstractMISP): return self.Tag @tags.setter - def tags(self, tags: list[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) @@ -1298,7 +1260,7 @@ class MISPGalaxyCluster(AbstractMISP): super().__init__() self.Galaxy: MISPGalaxy self.GalaxyElement: list[MISPGalaxyClusterElement] = [] - self.meta: dict = {} + self.meta: dict[str, Any] = {} self.GalaxyClusterRelation: list[MISPGalaxyClusterRelation] = [] self.Org: MISPOrganisation self.Orgc: MISPOrganisation @@ -1312,7 +1274,7 @@ class MISPGalaxyCluster(AbstractMISP): return self.GalaxyElement @cluster_elements.setter - def cluster_elements(self, cluster_elements: list[MISPGalaxyClusterElement]): + def cluster_elements(self, cluster_elements: list[MISPGalaxyClusterElement]) -> None: self.GalaxyElement = cluster_elements @property @@ -1320,10 +1282,10 @@ class MISPGalaxyCluster(AbstractMISP): return self.GalaxyClusterRelation @cluster_relations.setter - def cluster_relations(self, cluster_relations: list[MISPGalaxyClusterRelation]): + def cluster_relations(self, cluster_relations: list[MISPGalaxyClusterRelation]) -> None: self.GalaxyClusterRelation = cluster_relations - def parse_meta_as_elements(self): + def parse_meta_as_elements(self) -> None: """Function to parse the meta field into GalaxyClusterElements""" # Parse the cluster elements from the kwargs meta fields for key, value in self.meta.items(): @@ -1334,7 +1296,7 @@ class MISPGalaxyCluster(AbstractMISP): self.add_cluster_element(key=key, value=v) @property - def elements_meta(self) -> dict: + def elements_meta(self) -> dict[str, Any]: """Function to return the galaxy cluster elements as a dictionary structure of lists that comes from a MISPGalaxy within a MISPEvent. Lossy, you lose the element ID """ @@ -1343,7 +1305,7 @@ class MISPGalaxyCluster(AbstractMISP): response[element.key].append(element.value) return dict(response) - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'GalaxyCluster' in kwargs: kwargs = kwargs['GalaxyCluster'] self.default = kwargs.pop('default', False) @@ -1393,7 +1355,7 @@ class MISPGalaxyCluster(AbstractMISP): self.SharingGroup.from_dict(**kwargs.pop('SharingGroup')) super().from_dict(**kwargs) - def add_cluster_element(self, key: str, value: str, **kwargs) -> MISPGalaxyClusterElement: + def add_cluster_element(self, key: str, value: str, **kwargs) -> MISPGalaxyClusterElement: # type: ignore[no-untyped-def] """Add a cluster relation to a MISPGalaxyCluster, key and value are required :param key: The key name of the element @@ -1407,7 +1369,7 @@ class MISPGalaxyCluster(AbstractMISP): self.cluster_elements.append(cluster_element) return cluster_element - def add_cluster_relation(self, referenced_galaxy_cluster_uuid: MISPGalaxyCluster | str | UUID, referenced_galaxy_cluster_type: str, galaxy_cluster_uuid: str | None = None, **kwargs: dict) -> MISPGalaxyClusterRelation: + def add_cluster_relation(self, referenced_galaxy_cluster_uuid: MISPGalaxyCluster | str | UUID, referenced_galaxy_cluster_type: str, galaxy_cluster_uuid: str | None = None, **kwargs: dict[str, Any]) -> MISPGalaxyClusterRelation: """Add a cluster relation to a MISPGalaxyCluster. :param referenced_galaxy_cluster_uuid: UUID of the related cluster @@ -1448,7 +1410,7 @@ class MISPGalaxy(AbstractMISP): self.GalaxyCluster: list[MISPGalaxyCluster] = [] self.name: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] """Galaxy could be in one of the following formats: {'Galaxy': {}, 'GalaxyCluster': []} {'Galaxy': {'GalaxyCluster': []}} @@ -1466,7 +1428,7 @@ class MISPGalaxy(AbstractMISP): def clusters(self) -> list[MISPGalaxyCluster]: return self.GalaxyCluster - def add_galaxy_cluster(self, **kwargs) -> MISPGalaxyCluster: + def add_galaxy_cluster(self, **kwargs) -> MISPGalaxyCluster: # type: ignore[no-untyped-def] """Add a MISP galaxy cluster into a MISPGalaxy. Supports all other parameters supported by MISPGalaxyCluster""" @@ -1483,10 +1445,10 @@ class MISPGalaxy(AbstractMISP): class MISPEvent(AbstractMISP): - _fields_for_feed: set = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp', - 'publish_timestamp', 'published', 'date', 'extends_uuid'} + _fields_for_feed: set[str] = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp', + 'publish_timestamp', 'published', 'date', 'extends_uuid'} - def __init__(self, describe_types: dict | None = None, strict_validation: bool = False, **kwargs): + def __init__(self, describe_types: dict[str, Any] | None = None, strict_validation: bool = False, **kwargs) -> None: # type: ignore[no-untyped-def] super().__init__(**kwargs) self.__schema_file = 'schema.json' if strict_validation else 'schema-lax.json' @@ -1505,7 +1467,10 @@ class MISPEvent(AbstractMISP): self.Tag: list[MISPTag] = [] self.Galaxy: list[MISPGalaxy] = [] - def add_tag(self, tag: str | MISPTag | dict | None = None, **kwargs) -> MISPTag: + self.publish_timestamp: float | int | datetime + self.timestamp: float | int | datetime + + def add_tag(self, tag: str | MISPTag | dict[str, Any] | None = None, **kwargs) -> MISPTag: # type: ignore[no-untyped-def] return super()._add_tag(tag, **kwargs) @property @@ -1514,11 +1479,11 @@ class MISPEvent(AbstractMISP): return self.Tag @tags.setter - def tags(self, tags: list[MISPTag]): + def tags(self, tags: list[MISPTag]) -> None: """Set a list of prepared MISPTag.""" super()._set_tags(tags) - def _set_default(self): + def _set_default(self) -> None: """There are a few keys that could, or need to be set by default for the feed generator""" if not hasattr(self, 'published'): self.published = True @@ -1540,7 +1505,7 @@ class MISPEvent(AbstractMISP): self.threat_level_id = 4 @property - def manifest(self) -> dict: + def manifest(self) -> dict[str, Any]: required = ['info', 'Orgc'] for r in required: if not hasattr(self, r): @@ -1569,7 +1534,7 @@ class MISPEvent(AbstractMISP): to_return += attribute.hash_values(algorithm) return to_return - def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution=False, with_local_tags: bool = True, with_event_reports: bool = True) -> dict: + def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True) -> dict[str, Any]: """ Generate a json output for MISP Feed. :param valid_distributions: only makes sense if the distribution key is set; i.e., the event is exported from a MISP instance. @@ -1655,7 +1620,7 @@ class MISPEvent(AbstractMISP): return self.Orgc @orgc.setter - def orgc(self, orgc: MISPOrganisation): + def orgc(self, orgc: MISPOrganisation) -> None: if isinstance(orgc, MISPOrganisation): self.Orgc = orgc else: @@ -1666,7 +1631,7 @@ class MISPEvent(AbstractMISP): return self.Attribute @attributes.setter - def attributes(self, attributes: list[MISPAttribute]): + def attributes(self, attributes: list[MISPAttribute]) -> None: if all(isinstance(x, MISPAttribute) for x in attributes): self.Attribute = attributes else: @@ -1681,7 +1646,7 @@ class MISPEvent(AbstractMISP): return self.ShadowAttribute @shadow_attributes.setter - def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]): + def shadow_attributes(self, shadow_attributes: list[MISPShadowAttribute]) -> None: if all(isinstance(x, MISPShadowAttribute) for x in shadow_attributes): self.ShadowAttribute = shadow_attributes else: @@ -1696,7 +1661,7 @@ class MISPEvent(AbstractMISP): return self.Galaxy @galaxies.setter - def galaxies(self, galaxies: list[MISPGalaxy]): + def galaxies(self, galaxies: list[MISPGalaxy]) -> None: if all(isinstance(x, MISPGalaxy) for x in galaxies): self.Galaxy = galaxies else: @@ -1707,20 +1672,20 @@ class MISPEvent(AbstractMISP): return self.Object @objects.setter - def objects(self, objects: list[MISPObject]): + def objects(self, objects: list[MISPObject]) -> None: if all(isinstance(x, MISPObject) for x in objects): self.Object = objects else: raise PyMISPError('All the attributes have to be of type MISPObject.') - def load_file(self, event_path: Path | str, validate: bool = False, metadata_only: bool = False): + def load_file(self, event_path: Path | str, validate: bool = False, metadata_only: bool = False) -> None: """Load a JSON dump from a file on the disk""" if not os.path.exists(event_path): raise PyMISPError('Invalid path, unable to load the event.') with open(event_path, 'rb') as f: self.load(f, validate, metadata_only) - def load(self, json_event: IO | str | bytes | dict, validate: bool = False, metadata_only: bool = False): + def load(self, json_event: IO[bytes] | IO[str] | str | bytes | dict[str, Any], validate: bool = False, metadata_only: bool = False) -> None: """Load a JSON dump from a pseudo file or a JSON string""" if isinstance(json_event, (BufferedIOBase, TextIOBase)): json_event = json_event.read() @@ -1741,7 +1706,7 @@ class MISPEvent(AbstractMISP): if validate: warnings.warn('The validate parameter is deprecated because PyMISP is more flexible at loading event than the schema') - def __setattr__(self, name, value): + def __setattr__(self, name: str, value: Any) -> None: if name in ['date']: if isinstance(value, date): pass @@ -1759,7 +1724,7 @@ class MISPEvent(AbstractMISP): raise NewEventError(f'Invalid format for the date: {type(value)} - {value}') super().__setattr__(name, value) - def set_date(self, d: str | int | float | datetime | date | None = None, ignore_invalid: bool = False): + def set_date(self, d: str | int | float | datetime | date | None = None, ignore_invalid: bool = False) -> None: """Set a date for the event :param d: String, datetime, or date object @@ -1772,7 +1737,7 @@ class MISPEvent(AbstractMISP): else: raise NewEventError(f'Invalid format for the date: {type(d)} - {d}') - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Event' in kwargs: kwargs = kwargs['Event'] # Required value @@ -1831,7 +1796,7 @@ class MISPEvent(AbstractMISP): for rel_event in kwargs.pop('RelatedEvent'): sub_event = MISPEvent() sub_event.load(rel_event) - self.RelatedEvent.append({'Event': sub_event}) + self.RelatedEvent.append({'Event': sub_event}) # type: ignore[arg-type] if kwargs.get('Tag'): [self.add_tag(tag) for tag in kwargs.pop('Tag')] if kwargs.get('Object'): @@ -1848,7 +1813,7 @@ class MISPEvent(AbstractMISP): super().from_dict(**kwargs) - def to_dict(self, json_format: bool = False) -> dict: + def to_dict(self, json_format: bool = False) -> dict[str, Any]: to_return = super().to_dict(json_format) if to_return.get('date'): @@ -1862,11 +1827,11 @@ class MISPEvent(AbstractMISP): return to_return - def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_proposal(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Alias for add_shadow_attribute""" return self.add_shadow_attribute(shadow_attribute, **kwargs) - def add_shadow_attribute(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: + def add_shadow_attribute(self, shadow_attribute=None, **kwargs) -> MISPShadowAttribute: # type: ignore[no-untyped-def] """Add a tag to the attribute (by name or a MISPTag object)""" if isinstance(shadow_attribute, MISPShadowAttribute): misp_shadow_attribute = shadow_attribute @@ -1877,7 +1842,7 @@ class MISPEvent(AbstractMISP): misp_shadow_attribute = MISPShadowAttribute() misp_shadow_attribute.from_dict(**kwargs) else: - raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict): {shadow_attribute}") + raise PyMISPError(f"The shadow_attribute is in an invalid format (can be either string, MISPShadowAttribute, or an expanded dict[str, Any]): {shadow_attribute}") self.shadow_attributes.append(misp_shadow_attribute) self.edited = True return misp_shadow_attribute @@ -1889,7 +1854,7 @@ class MISPEvent(AbstractMISP): """ tags: list[MISPTag] = [] for a in self.attributes + [attribute for o in self.objects for attribute in o.attributes]: - if ((hasattr(a, 'id') and a.id == attribute_identifier) + if ((hasattr(a, 'id') and str(a.id) == attribute_identifier) or (hasattr(a, 'uuid') and a.uuid == attribute_identifier) or (hasattr(a, 'value') and attribute_identifier == a.value or (isinstance(a.value, str) and attribute_identifier in a.value.split('|')))): @@ -1904,7 +1869,7 @@ class MISPEvent(AbstractMISP): """ attributes = [] for a in self.attributes + [attribute for o in self.objects for attribute in o.attributes]: - if ((hasattr(a, 'id') and a.id == attribute_identifier) + if ((hasattr(a, 'id') and str(a.id) == attribute_identifier) or (hasattr(a, 'uuid') and a.uuid == attribute_identifier) or (hasattr(a, 'value') and attribute_identifier == a.value or (isinstance(a.value, str) and attribute_identifier in a.value.split('|')))): @@ -1920,24 +1885,24 @@ class MISPEvent(AbstractMISP): """Mark the attribute as published""" self.published = True - def unpublish(self): + def unpublish(self) -> None: """Mark the attribute as un-published (set publish flag to false)""" self.published = False - def delete_attribute(self, attribute_id: str): + def delete_attribute(self, attribute_id: str) -> None: """Delete an attribute :param attribute_id: ID or UUID """ for a in self.attributes: - if ((hasattr(a, 'id') and a.id == attribute_id) + if ((hasattr(a, 'id') and str(a.id) == attribute_id) or (hasattr(a, 'uuid') and a.uuid == attribute_id)): a.delete() break else: raise PyMISPError(f'No attribute with UUID/ID {attribute_id} found.') - def add_attribute(self, type: str, value: str | int | float, **kwargs) -> MISPAttribute | list[MISPAttribute]: + def add_attribute(self, type: str, value: str | int | float, **kwargs) -> MISPAttribute | list[MISPAttribute]: # type: ignore[no-untyped-def] """Add an attribute. type and value are required but you can pass all other parameters supported by MISPAttribute""" attr_list: list[MISPAttribute] = [] @@ -1952,7 +1917,7 @@ class MISPEvent(AbstractMISP): return attr_list return attribute - def add_event_report(self, name: str, content: str, **kwargs) -> MISPEventReport: + def add_event_report(self, name: str, content: str, **kwargs) -> MISPEventReport: # type: ignore[no-untyped-def] """Add an event report. name and value are requred but you can pass all other parameters supported by MISPEventReport""" event_report = MISPEventReport() @@ -1961,7 +1926,7 @@ class MISPEvent(AbstractMISP): self.edited = True return event_report - def add_galaxy(self, galaxy: MISPGalaxy | dict | None = None, **kwargs) -> MISPGalaxy: + def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def] """Add a galaxy and sub-clusters into an event, either by passing a MISPGalaxy or a dictionary. Supports all other parameters supported by MISPGalaxy""" @@ -2007,7 +1972,7 @@ class MISPEvent(AbstractMISP): objects.append(obj) return objects - def add_object(self, obj: MISPObject | dict | None = None, **kwargs) -> MISPObject: + def add_object(self, obj: MISPObject | dict[str, Any] | None = None, **kwargs) -> MISPObject: # type: ignore[no-untyped-def] """Add an object to the Event, either by passing a MISPObject, or a dictionary""" if isinstance(obj, MISPObject): misp_obj = obj @@ -2028,7 +1993,7 @@ class MISPEvent(AbstractMISP): self.edited = True return misp_obj - def delete_object(self, object_id: str): + def delete_object(self, object_id: str) -> None: """Delete an object :param object_id: ID or UUID @@ -2041,7 +2006,7 @@ class MISPEvent(AbstractMISP): else: raise PyMISPError(f'No object with UUID/ID {object_id} found.') - def run_expansions(self): + def run_expansions(self) -> None: for index, attribute in enumerate(self.attributes): if 'expand' not in attribute: continue @@ -2069,70 +2034,10 @@ class MISPEvent(AbstractMISP): return '<{self.__class__.__name__}(info={self.info})'.format(self=self) return f'<{self.__class__.__name__}(NotInitialized)' - def _serialize(self): # pragma: no cover - return '{date}{threat_level_id}{info}{uuid}{analysis}{timestamp}'.format( - date=self.date, threat_level_id=self.threat_level_id, info=self.info, - uuid=self.uuid, analysis=self.analysis, timestamp=self.timestamp).encode() - - def _serialize_sigs(self): # pragma: no cover - # Not used - all_sigs = self.sig - for a in self.attributes: - all_sigs += a.sig - return all_sigs.encode() - - def sign(self, gpg_uid, passphrase=None): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_sign = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign, mode=mode.DETACH) - self.sig = base64.b64encode(signed).decode() - for a in self.attributes: - a.sign(gpg_uid, passphrase) - to_sign_global = self._serialize_sigs() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - c.signers = keys[:1] - if passphrase: - c.set_passphrase_cb(lambda *args: passphrase) - signed, _ = c.sign(to_sign_global, mode=mode.DETACH) - self.global_sig = base64.b64encode(signed).decode() - - def verify(self, gpg_uid): # pragma: no cover - # Not used - if not has_pyme: - raise PyMISPError('pyme is required, please install: pip install --pre pyme3. You will also need libgpg-error-dev and libgpgme11-dev.') - to_return = {} - signed_data = self._serialize() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(signed_data, signature=base64.b64decode(self.sig), verify=keys[:1]) - to_return[self.uuid] = True - except Exception: - to_return[self.uuid] = False - for a in self.attributes: - to_return.update(a.verify(gpg_uid)) - to_verify_global = self._serialize_sigs() - with gpg.Context() as c: - keys = list(c.keylist(gpg_uid)) - try: - c.verify(to_verify_global, signature=base64.b64decode(self.global_sig), verify=keys[:1]) - to_return['global'] = True - except Exception: - to_return['global'] = False - return to_return - class MISPObjectTemplate(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'ObjectTemplate' in kwargs: kwargs = kwargs['ObjectTemplate'] super().from_dict(**kwargs) @@ -2143,15 +2048,16 @@ class MISPObjectTemplate(AbstractMISP): class MISPUser(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.email: str + self.password: str | None - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'User' in kwargs: kwargs = kwargs['User'] super().from_dict(**kwargs) - if hasattr(self, 'password') and set(self.password) == {'*'}: + if hasattr(self, 'password') and self.password and set(self.password) == {'*', }: self.password = None def __repr__(self) -> str: @@ -2162,7 +2068,9 @@ class MISPUser(AbstractMISP): class MISPFeed(AbstractMISP): - def from_dict(self, **kwargs): + settings: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Feed' in kwargs: kwargs = kwargs['Feed'] super().from_dict(**kwargs) @@ -2176,7 +2084,7 @@ class MISPFeed(AbstractMISP): class MISPWarninglist(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Warninglist' in kwargs: kwargs = kwargs['Warninglist'] super().from_dict(**kwargs) @@ -2187,18 +2095,18 @@ class MISPTaxonomy(AbstractMISP): enabled: bool namespace: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Taxonomy' in kwargs: kwargs = kwargs['Taxonomy'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(namespace={self.namespace})>' class MISPNoticelist(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Noticelist' in kwargs: kwargs = kwargs['Noticelist'] super().from_dict(**kwargs) @@ -2206,7 +2114,7 @@ class MISPNoticelist(AbstractMISP): class MISPCorrelationExclusion(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'CorrelationExclusion' in kwargs: kwargs = kwargs['CorrelationExclusion'] super().from_dict(**kwargs) @@ -2214,12 +2122,12 @@ class MISPCorrelationExclusion(AbstractMISP): class MISPRole(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.perm_admin: int self.perm_site_admin: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Role' in kwargs: kwargs = kwargs['Role'] super().from_dict(**kwargs) @@ -2227,7 +2135,7 @@ class MISPRole(AbstractMISP): class MISPServer(AbstractMISP): - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Server' in kwargs: kwargs = kwargs['Server'] super().from_dict(**kwargs) @@ -2235,13 +2143,13 @@ class MISPServer(AbstractMISP): class MISPLog(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.model: str self.action: str self.title: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Log' in kwargs: kwargs = kwargs['Log'] super().from_dict(**kwargs) @@ -2252,13 +2160,13 @@ class MISPLog(AbstractMISP): class MISPEventDelegation(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.org_id: int self.requester_org_id: int self.event_id: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventDelegation' in kwargs: kwargs = kwargs['EventDelegation'] super().from_dict(**kwargs) @@ -2269,11 +2177,11 @@ class MISPEventDelegation(AbstractMISP): class MISPObjectAttribute(MISPAttribute): - _fields_for_feed: set = {'uuid', 'value', 'category', 'type', 'comment', 'data', - 'deleted', 'timestamp', 'to_ids', 'disable_correlation', - 'first_seen', 'last_seen', 'object_relation'} + _fields_for_feed: set[str] = {'uuid', 'value', 'category', 'type', 'comment', 'data', + 'deleted', 'timestamp', 'to_ids', 'disable_correlation', + 'first_seen', 'last_seen', 'object_relation'} - def __init__(self, definition): + def __init__(self, definition: dict[str, Any]) -> None: super().__init__() self._definition = definition @@ -2304,7 +2212,7 @@ class MISPObjectAttribute(MISPAttribute): raise NewAttributeError("The type of the attribute is required. Is the object template missing?") super().from_dict(**{**self, **kwargs}) - def __repr__(self): + def __repr__(self) -> str: if hasattr(self, 'value'): return '<{self.__class__.__name__}(object_relation={self.object_relation}, value={self.value})'.format(self=self) return f'<{self.__class__.__name__}(NotInitialized)' @@ -2312,82 +2220,91 @@ class MISPObjectAttribute(MISPAttribute): class MISPCommunity(AbstractMISP): - def from_dict(self, **kwargs): + def __init__(self, **kwargs: dict[str, Any]) -> None: + super().__init__(**kwargs) + self.name: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Community' in kwargs: kwargs = kwargs['Community'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.name}, uuid={self.uuid})' class MISPUserSetting(AbstractMISP): - def from_dict(self, **kwargs): + def __init__(self, **kwargs: dict[str, Any]) -> None: + super().__init__(**kwargs) + self.setting: str + + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'UserSetting' in kwargs: kwargs = kwargs['UserSetting'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.setting}' class MISPInbox(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) - self.data: dict + self.data: dict[str, Any] + self.type: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'Inbox' in kwargs: kwargs = kwargs['Inbox'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(name={self.type})>' class MISPEventBlocklist(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.event_uuid: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'EventBlocklist' in kwargs: kwargs = kwargs['EventBlocklist'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(event_uuid={self.event_uuid}' class MISPOrganisationBlocklist(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.org_uuid: str - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'OrgBlocklist' in kwargs: kwargs = kwargs['OrgBlocklist'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(org_uuid={self.org_uuid}' class MISPDecayingModel(AbstractMISP): - def __init__(self, **kwargs: dict) -> None: + def __init__(self, **kwargs: dict[str, Any]) -> None: super().__init__(**kwargs) self.uuid: str self.id: int - def from_dict(self, **kwargs): + def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def] if 'DecayingModel' in kwargs: kwargs = kwargs['DecayingModel'] super().from_dict(**kwargs) - def __repr__(self): + def __repr__(self) -> str: return f'<{self.__class__.__name__}(uuid={self.uuid})>' diff --git a/pymisp/tools/__init__.py b/pymisp/tools/__init__.py index 30ce253..45907b3 100644 --- a/pymisp/tools/__init__.py +++ b/pymisp/tools/__init__.py @@ -43,3 +43,13 @@ try: except ImportError: # Requires lief, optional [fileobjects] pass + +__all__ = ['VTReportObject', 'Neo4j', 'FileObject', 'make_binary_objects', + 'AbstractMISPObjectGenerator', 'GenericObjectGenerator', + 'load_openioc', 'load_openioc_file', 'SBSignatureObject', + 'Fail2BanObject', 'DomainIPObject', 'ASNObject', 'GeolocationObject', + 'GitVulnFinderObject', 'VehicleObject', 'CSVLoader', + 'SSHAuthorizedKeysObject', 'feed_meta_generator', 'update_objects', + 'EMailObject', 'URLObject', 'PEObject', 'PESectionObject', 'ELFObject', + 'ELFSectionObject', 'MachOObject', 'MachOSectionObject' + ] diff --git a/pymisp/tools/abstractgenerator.py b/pymisp/tools/abstractgenerator.py index 6e4b51c..2703c46 100644 --- a/pymisp/tools/abstractgenerator.py +++ b/pymisp/tools/abstractgenerator.py @@ -6,7 +6,6 @@ from .. import MISPObject from ..exceptions import InvalidMISPObject from datetime import datetime, date from dateutil.parser import parse -from typing import Union, Optional class AbstractMISPObjectGenerator(MISPObject): diff --git a/pymisp/tools/asnobject.py b/pymisp/tools/asnobject.py index ef237a2..a51c0b3 100644 --- a/pymisp/tools/asnobject.py +++ b/pymisp/tools/asnobject.py @@ -2,15 +2,18 @@ from __future__ import annotations -from .abstractgenerator import AbstractMISPObjectGenerator import logging +from typing import Any + +from .abstractgenerator import AbstractMISPObjectGenerator + logger = logging.getLogger('pymisp') class ASNObject(AbstractMISPObjectGenerator): - def __init__(self, parameters: dict, strict: bool = True, **kwargs): + def __init__(self, parameters: dict[str, Any], strict: bool = True, **kwargs) -> None: super().__init__('asn', strict=strict, **kwargs) self._parameters = parameters self.generate_attributes() diff --git a/tests/testlive_sync.py b/tests/testlive_sync.py index bad65a2..8d0e820 100644 --- a/tests/testlive_sync.py +++ b/tests/testlive_sync.py @@ -3,7 +3,6 @@ from __future__ import annotations import time -import sys import unittest import subprocess @@ -12,7 +11,7 @@ import logging logging.disable(logging.CRITICAL) try: - from pymisp import ExpandedPyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution + from pymisp import PyMISP, MISPOrganisation, MISPUser, MISPEvent, MISPObject, MISPSharingGroup, Distribution except ImportError: raise @@ -70,7 +69,7 @@ fast_mode = True class MISPInstance(): def __init__(self, params): - self.initial_user_connector = ExpandedPyMISP(params['url'], params['key'], ssl=False, debug=False) + self.initial_user_connector = PyMISP(params['url'], params['key'], ssl=False, debug=False) # Git pull self.initial_user_connector.update_misp() # Set the default role (id 3 on the VM is normal user) @@ -98,7 +97,7 @@ class MISPInstance(): user.org_id = self.test_org.id user.role_id = 1 # Site admin self.test_site_admin = self.initial_user_connector.add_user(user) - self.site_admin_connector = ExpandedPyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False) + self.site_admin_connector = PyMISP(params['url'], self.test_site_admin.authkey, ssl=False, debug=False) self.site_admin_connector.toggle_global_pythonify() # Create org admin user = MISPUser() @@ -106,14 +105,14 @@ class MISPInstance(): user.org_id = self.test_org.id user.role_id = 2 # Org admin self.test_org_admin = self.site_admin_connector.add_user(user) - self.org_admin_connector = ExpandedPyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False) + self.org_admin_connector = PyMISP(params['url'], self.test_org_admin.authkey, ssl=False, debug=False) self.org_admin_connector.toggle_global_pythonify() # Create user user = MISPUser() user.email = params['email_user'] user.org_id = self.test_org.id self.test_usr = self.org_admin_connector.add_user(user) - self.user_connector = ExpandedPyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False) + self.user_connector = PyMISP(params['url'], self.test_usr.authkey, ssl=False, debug=False) self.user_connector.toggle_global_pythonify() # Setup external_baseurl @@ -138,7 +137,7 @@ class MISPInstance(): user.org_id = sync_org.id user.role_id = 5 # Org admin sync_user = self.site_admin_connector.add_user(user) - sync_user_connector = ExpandedPyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False) + sync_user_connector = PyMISP(self.site_admin_connector.root_url, sync_user.authkey, ssl=False, debug=False) sync_server_config = sync_user_connector.get_sync_config(pythonify=True) self.sync.append((sync_org, sync_user, sync_server_config))