├── .deepsource.toml ├── .gitignore ├── .readthedocs.yaml ├── LICENSE.txt ├── README.md ├── __init__.py ├── dfir_iris_client ├── __init__.py ├── admin.py ├── alert.py ├── case.py ├── customer.py ├── global_search.py ├── helper │ ├── __init__.py │ ├── alert_status.py │ ├── analysis_status.py │ ├── assets_type.py │ ├── authorization.py │ ├── case_classifications.py │ ├── colors.py │ ├── compromise_status.py │ ├── doc_tracer.py │ ├── docker_helper.py │ ├── errors.py │ ├── events_categories.py │ ├── ioc_types.py │ ├── iris_object.py │ ├── objects_def.py │ ├── outcome_status.py │ ├── report_template_types.py │ ├── task_status.py │ ├── tlps.py │ └── utils.py ├── session.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── resources │ │ ├── .env │ │ ├── alert.json │ │ └── test_report.md │ ├── test_admin.py │ ├── test_alert.py │ ├── test_alert_status.py │ ├── test_analysis_status.py │ ├── test_asset_type.py │ ├── test_authorization.py │ ├── test_case.py │ ├── test_case_classifications.py │ ├── test_compromise_status.py │ ├── test_customer.py │ ├── test_event_categories.py │ ├── test_global_search.py │ ├── test_ioc_types.py │ ├── test_task_status.py │ ├── test_tlps.py │ └── tests_helper.py └── users.py ├── docs ├── Makefile ├── build │ └── html │ │ ├── .buildinfo │ │ ├── _sources │ │ ├── admin.rst.txt │ │ ├── case.rst.txt │ │ ├── client.rst.txt │ │ ├── customer.rst.txt │ │ ├── global_search.rst.txt │ │ ├── index.rst.txt │ │ ├── session.rst.txt │ │ └── users.rst.txt │ │ ├── _static │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── css │ │ │ ├── badge_only.css │ │ │ ├── fonts │ │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ ├── fontawesome-webfont.woff2 │ │ │ │ ├── lato-bold-italic.woff │ │ │ │ ├── lato-bold-italic.woff2 │ │ │ │ ├── lato-bold.woff │ │ │ │ ├── lato-bold.woff2 │ │ │ │ ├── lato-normal-italic.woff │ │ │ │ ├── lato-normal-italic.woff2 │ │ │ │ ├── lato-normal.woff │ │ │ │ └── lato-normal.woff2 │ │ │ └── theme.css │ │ ├── custom.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── js │ │ │ ├── badge_only.js │ │ │ ├── html5shiv-printshiv.min.js │ │ │ ├── html5shiv.min.js │ │ │ └── theme.js │ │ ├── language_data.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── pygments.css │ │ ├── searchtools.js │ │ └── sphinx_highlight.js │ │ ├── admin.html │ │ ├── case.html │ │ ├── client.html │ │ ├── customer.html │ │ ├── genindex.html │ │ ├── global_search.html │ │ ├── index.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ ├── searchindex.js │ │ ├── session.html │ │ └── users.html ├── make.bat ├── requirements.txt └── source │ ├── admin.rst │ ├── case.rst │ ├── conf.py │ ├── customer.rst │ ├── global_search.rst │ ├── helpers.rst │ ├── index.rst │ ├── session.rst │ └── users.rst ├── examples ├── create_new_managed_case.py ├── ex_helper.py └── update_existing_case.py ├── pyproject.toml ├── requirements.txt └── setup.py /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | exclude_patterns = [ 4 | "examples/**", 5 | "dfir_iris_client/tests/*.py" 6 | ] 7 | 8 | [[analyzers]] 9 | name = "python" 10 | 11 | [analyzers.meta] 12 | runtime_version = "3.x.x" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | *.pyc 3 | 4 | # pytest 5 | /.cache/ 6 | /.pytest_cache/ 7 | 8 | # setuptools 9 | /build/ 10 | /dist/ 11 | .eggs 12 | 13 | /docs/build 14 | 15 | # VirtualEnv 16 | /venv 17 | 18 | # IntelliJ 19 | *.iml 20 | /.idea 21 | 22 | # Custom 23 | .DS_Store 24 | dfir_iris_client.egg-info/ 25 | in_test/ 26 | dfir_iris_client/tests/traces/ -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.9" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/source/conf.py 17 | 18 | python: 19 | install: 20 | - requirements: docs/requirements.txt 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python client 2 | 3 | `dfir_iris_client` offers a Python interface to communicate with IRIS. 4 | 5 | It relies exclusively on the API, which means output of the methods are the same as specified in the API reference. 6 | 7 | ## Versions 8 | The Python client version follows the API versions (until the patch level). Meaning for API v2.0.1, one need to install `dfir_iris_client-2.0.1`. 9 | Please refer to the [documentation](https://dfir-iris.github.io/operations/api/#references) to check which version applies to your IRIS instance. 10 | 11 | ## Install 12 | IRIS Client is now part of PyPI. You can simply install it with : 13 | ``` 14 | pip3 install dfir-iris-client 15 | ``` 16 | 17 | ## Build 18 | To build a wheel from the sources: 19 | 20 | 1. `pip3 install wheel` 21 | 2. `python setup.py bdist_wheel` 22 | 3. `pip3 install dist/XXX.whl` 23 | 24 | 25 | ## Examples 26 | Some examples are available [here](https://github.com/dfir-iris/iris-client/tree/master/examples). 27 | 28 | ## Documentation 29 | The documentation is available in the [IRIS documentation](https://docs.dfir-iris.org/python_client/). 30 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/__init__.py -------------------------------------------------------------------------------- /dfir_iris_client/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /dfir_iris_client/alert.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | import json 18 | import warnings 19 | from typing import List 20 | 21 | from requests import Response 22 | 23 | from dfir_iris_client.helper.utils import ApiResponse, ClientApiError 24 | from dfir_iris_client.session import ClientSession 25 | 26 | 27 | class Alert(object): 28 | """Handles alert operations""" 29 | 30 | def __init__(self, session: ClientSession): 31 | """Init 32 | 33 | Args: 34 | session (ClientSession): Client session 35 | """ 36 | self._s = session 37 | 38 | def get_alert(self, alert_id: int) -> ApiResponse: 39 | """Get an alert 40 | 41 | Args: 42 | alert_id (int): Alert id 43 | 44 | Returns: 45 | ApiResponse: Response object 46 | """ 47 | return self._s.pi_get(f"alerts/{alert_id}") 48 | 49 | def get_alerts(self, alert_ids: List[int]) -> ApiResponse: 50 | """Get alerts from their ids 51 | 52 | Args: 53 | alert_ids (list): Alert ids 54 | 55 | Returns: 56 | ApiResponse: Response object 57 | """ 58 | 59 | if not all(isinstance(element, int) for element in alert_ids): 60 | return ClientApiError('Expected a list of integers for alert_ids') 61 | 62 | return self._s.pi_get(f"alerts/filter?alert_ids={','.join(str(element) for element in alert_ids)}") 63 | 64 | def add_alert(self, alert_data: dict) -> ApiResponse: 65 | """Add an alert 66 | 67 | Args: 68 | alert_data (dict): Alert data - The data is defined in the API documentation 69 | 70 | Returns: 71 | ApiResponse: Response object 72 | """ 73 | return self._s.pi_post("alerts/add", alert_data) 74 | 75 | def update_alert(self, alert_id: int, alert_data: dict) -> ApiResponse: 76 | """Update an alert 77 | 78 | Args: 79 | alert_id (int): Alert id 80 | alert_data (dict): Alert data - The data is defined in the API documentation 81 | 82 | Returns: 83 | ApiResponse: Response object 84 | """ 85 | return self._s.pi_post(f"alerts/update/{alert_id}", alert_data) 86 | 87 | def delete_alert(self, alert_id: int) -> ApiResponse: 88 | """Delete an alert 89 | 90 | Args: 91 | alert_id (int): Alert id 92 | 93 | Returns: 94 | ApiResponse: Response object 95 | """ 96 | return self._s.pi_post(f"alerts/delete/{alert_id}") 97 | 98 | def escalate_alert(self, alert_id: int, iocs_import_list: List[str], assets_import_list: List[str], 99 | escalation_note: str, case_title:str, case_tags: str, case_template_id: int = None, 100 | import_as_event: bool = False) -> ApiResponse: 101 | """Escalate an alert 102 | 103 | Args: 104 | alert_id (int): Alert id 105 | iocs_import_list (list): List of IOCs UUID from the alert to import 106 | assets_import_list (list): List of assets UUIDs from the alert to import 107 | escalation_note (str): Escalation note 108 | case_title (str): Case title 109 | case_tags (str): Case tags, a string of comma separated tags 110 | case_template_id (int): Case template id 111 | import_as_event (bool): Import as event 112 | 113 | Returns: 114 | ApiResponse: Response object 115 | """ 116 | payload = { 117 | "iocs_import_list": iocs_import_list, 118 | "assets_import_list": assets_import_list, 119 | "note": escalation_note, 120 | "case_title": case_title, 121 | "case_tags": case_tags, 122 | "case_template_id": case_template_id, 123 | "import_as_event": import_as_event 124 | } 125 | 126 | return self._s.pi_post(f"alerts/escalate/{alert_id}", data=payload) 127 | 128 | def merge_alert(self, alert_id: int, target_case_id: int, iocs_import_list: List[str], 129 | assets_import_list: List[str], merge_note: str, import_as_event: bool = False) -> ApiResponse: 130 | """Merge an alert 131 | 132 | Args: 133 | alert_id (int): Alert id 134 | target_case_id (int): Target case id 135 | iocs_import_list (list): List of IOCs UUID from the alert to import 136 | assets_import_list (list): List of assets UUIDs from the alert to import 137 | merge_note (str): Merge note 138 | import_as_event (bool): Import as event 139 | 140 | Returns: 141 | ApiResponse: Response object 142 | """ 143 | payload = { 144 | "target_case_id": target_case_id, 145 | "iocs_import_list": iocs_import_list, 146 | "assets_import_list": assets_import_list, 147 | "note": merge_note, 148 | "import_as_event": import_as_event 149 | } 150 | 151 | return self._s.pi_post(f"alerts/merge/{alert_id}", data=payload) 152 | 153 | def unmerge_alert(self, alert_id: int, target_case_id: int) -> ApiResponse: 154 | """ Unmerge an alert 155 | 156 | Args: 157 | alert_id (int): Alert id 158 | target_case_id (int): Target case id 159 | 160 | Returns: 161 | ApiResponse: Response object 162 | """ 163 | payload = { 164 | "target_case_id": target_case_id 165 | } 166 | 167 | return self._s.pi_post(f"alerts/unmerge/{alert_id}", data=payload) 168 | 169 | def filter_alerts(self, alert_title: str = None, alert_description: str = None, alert_source: str = None, 170 | alert_tags: str = None, alert_status_id: int = None, alert_severity_id: int = None, 171 | alert_classification_id: int = None, alert_customer_id: int = None, alert_start_date: str = None, 172 | alert_end_date: str = None, alert_assets: str = None, alert_iocs: str = None, alert_ids: str = None, 173 | case_id: int = None, alert_owner_id: int = None, 174 | page: int = 1, per_page: int = 20, sort: str = 'desc') -> ApiResponse: 175 | """ Filter alerts 176 | 177 | Args: 178 | alert_title (str): Alert title 179 | alert_description (str): Alert description 180 | alert_source (str): Alert source 181 | alert_tags (str): Alert tags 182 | alert_status_id (int): Alert status id 183 | alert_severity_id (int): Alert severity id 184 | alert_classification_id (int): Alert classification id 185 | alert_customer_id (int): Alert customer id 186 | alert_start_date (str): Alert start date 187 | alert_end_date (str): Alert end date 188 | alert_assets (str): Alert assets 189 | alert_iocs (str): Alert IOCs 190 | alert_ids (str): Alert ids 191 | case_id (int): Case id 192 | alert_owner_id (int): Alert owner id 193 | page (int): Page number 194 | per_page (int): Number of alerts per page 195 | sort (str): Sort order 196 | 197 | 198 | Returns: 199 | ApiResponse: Response object 200 | """ 201 | uri = f"alerts/filter?page={page}&per_page={per_page}&sort={sort}" 202 | if alert_title: 203 | uri += f"&alert_title={alert_title}" 204 | 205 | if alert_description: 206 | uri += f"&alert_description={alert_description}" 207 | 208 | if alert_source: 209 | uri += f"&alert_source={alert_source}" 210 | 211 | if alert_tags: 212 | uri += f"&alert_tags={alert_tags}" 213 | 214 | if alert_status_id: 215 | uri += f"&alert_status_id={alert_status_id}" 216 | 217 | if alert_severity_id: 218 | uri += f"&alert_severity_id={alert_severity_id}" 219 | 220 | if alert_classification_id: 221 | uri += f"&alert_classification_id={alert_classification_id}" 222 | 223 | if alert_customer_id: 224 | uri += f"&alert_customer_id={alert_customer_id}" 225 | 226 | if alert_start_date: 227 | uri += f"&alert_start_date={alert_start_date}" 228 | 229 | if alert_end_date: 230 | uri += f"&alert_end_date={alert_end_date}" 231 | 232 | if alert_assets: 233 | uri += f"&alert_assets={alert_assets}" 234 | 235 | if alert_iocs: 236 | uri += f"&alert_iocs={alert_iocs}" 237 | 238 | if alert_ids: 239 | uri += f"&alert_ids={alert_ids}" 240 | 241 | if case_id: 242 | uri += f"&case_id={case_id}" 243 | 244 | if alert_owner_id: 245 | uri += f"&alert_owner_id={alert_owner_id}" 246 | 247 | return self._s.pi_get(uri) 248 | -------------------------------------------------------------------------------- /dfir_iris_client/customer.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.utils import ClientApiError, ApiResponse, ClientApiData 19 | 20 | 21 | class Customer(object): 22 | """Handles the customer methods""" 23 | def __init__(self, session): 24 | self._s = session 25 | 26 | def lookup_customer(self, customer_name) -> ApiResponse: 27 | """ 28 | Returns a customer ID if customer name is found. Customer names are unique in the database. 29 | Customer ID is in the data section of the API response aka id = parse_api_data(resp.get_data(), 'customer_id') 30 | 31 | Args: 32 | customer_name: Name of the customer to lookup 33 | 34 | Returns: 35 | ApiResponse object 36 | 37 | """ 38 | resp = self._s.pi_get('/manage/customers/list') 39 | 40 | if resp.is_success(): 41 | 42 | customer_list = resp.get_data() 43 | 44 | for customer in customer_list: 45 | if customer.get('customer_name').lower() == customer_name.lower(): 46 | response = ClientApiData(data=customer) 47 | return ApiResponse(response=response, uri=resp.get_uri()) 48 | 49 | return ClientApiError(f"Customer {customer_name} not found") 50 | 51 | def get_customer_by_id(self, customer_id: int) -> ApiResponse: 52 | """ 53 | Returns a customer from its ID 54 | 55 | Args: 56 | customer_id: Customer ID to look up 57 | 58 | Returns: 59 | ApiResponse object 60 | 61 | """ 62 | resp = self._s.pi_get(f'/manage/customers/{customer_id}') 63 | 64 | return resp 65 | 66 | def list_customers(self) -> ApiResponse: 67 | """ 68 | Returns a list of the available customers 69 | 70 | :return: ApiResponse object 71 | 72 | Args: 73 | 74 | Returns: 75 | ApiResponse object 76 | """ 77 | 78 | return self._s.pi_get(f'/manage/customers/list') -------------------------------------------------------------------------------- /dfir_iris_client/global_search.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.utils import ApiResponse, ClientApiError 19 | from dfir_iris_client.session import ClientSession 20 | 21 | 22 | def global_search_ioc(session: ClientSession, search_term: str) -> ApiResponse: 23 | """Searches an IOC across all investigation 24 | 25 | Args: 26 | session: Client Session to use for request 27 | search_term: Search term to search for IOC 28 | 29 | Returns: 30 | ApiResponse object 31 | 32 | """ 33 | if not session: 34 | return ClientApiError(msg=f'session is not a valid. Expected ClientSession got {type(session)}') 35 | 36 | if not search_term: 37 | return ClientApiError(msg='search_term cannot be null. Use % for wildcard') 38 | 39 | body = { 40 | "search_value": search_term, 41 | "search_type": "ioc", 42 | "cid": 1 43 | } 44 | 45 | return session.pi_post('search', data=body) 46 | 47 | 48 | def global_search_notes(session: ClientSession, search_term: str) -> ApiResponse: 49 | """Searches in note contents across all investigation 50 | 51 | Args: 52 | session: Client Session to use for request 53 | search_term: Search term to search for notes 54 | 55 | Returns: 56 | ApiResponse object 57 | 58 | """ 59 | if not session: 60 | return ClientApiError(msg=f'session is not a valid. Expected ClientSession got {type(session)}') 61 | 62 | if not search_term: 63 | return ClientApiError(msg='search_term cannot be null. Use % for wildcard') 64 | 65 | body = { 66 | "search_value": search_term, 67 | "search_type": "notes", 68 | "cid": 1 69 | } 70 | 71 | return session.pi_post('search', data=body) -------------------------------------------------------------------------------- /dfir_iris_client/helper/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/dfir_iris_client/helper/__init__.py -------------------------------------------------------------------------------- /dfir_iris_client/helper/alert_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class AlertStatusHelper(object): 23 | """Handles the alert status methods""" 24 | 25 | def __init__(self, session): 26 | self._s = session 27 | 28 | def list_alert_status_types(self) -> ApiResponse: 29 | """ 30 | Returns a list of all alert statuses 31 | 32 | Args: 33 | 34 | Returns: 35 | APIResponse object 36 | """ 37 | return self._s.pi_get('manage/alert-status/list') 38 | 39 | def lookup_alert_status_name(self, alert_status_name: str) -> Union[int, None]: 40 | """ 41 | Returns an alert status ID from its name otherwise None 42 | 43 | Args: 44 | alert_status_name: str: 45 | 46 | Returns: 47 | Union[int, None] - alert status ID matching provided alert status name or None if not found 48 | 49 | """ 50 | ast_list = self.list_alert_status_types() 51 | for ast in ast_list.get_data(): 52 | if ast.get('status_name').lower() == alert_status_name.lower(): 53 | return ast.get('status_id') 54 | 55 | return None 56 | 57 | def get_alert_status(self, alert_status_id: int) -> ApiResponse: 58 | """ 59 | Returns an alert status from its ID 60 | 61 | Args: 62 | alert_status_id: int: 63 | 64 | Returns: 65 | APIResponse object 66 | 67 | """ 68 | return self._s.pi_get('manage/alert-status/{}'.format(alert_status_id)) -------------------------------------------------------------------------------- /dfir_iris_client/helper/analysis_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class AnalysisStatusHelper(object): 23 | """Handles the analysis status methods""" 24 | 25 | def __init__(self, session): 26 | self._s = session 27 | 28 | def list_analysis_status_types(self) -> ApiResponse: 29 | """ 30 | Returns a list of all analysis statuses 31 | 32 | Args: 33 | 34 | Returns: 35 | APIResponse object 36 | """ 37 | return self._s.pi_get('manage/analysis-status/list') 38 | 39 | def lookup_analysis_status_name(self, analysis_status_name: str) -> Union[int, None]: 40 | """ 41 | Returns an analysis status ID from its name otherwise None 42 | 43 | Args: 44 | analysis_status_name: str: 45 | 46 | Returns: 47 | Union[int, None] - analysis status ID matching provided analysis status name or None if not found 48 | 49 | """ 50 | ast_list = self.list_analysis_status_types() 51 | for ast in ast_list.get_data(): 52 | if ast.get('name').lower() == analysis_status_name.lower(): 53 | return ast.get('id') 54 | 55 | return None 56 | 57 | def get_analysis_status(self, analysis_status_id: int) -> ApiResponse: 58 | """ 59 | Returns an analysis status from its ID 60 | 61 | Args: 62 | analysis_status_id: Status ID to lookup 63 | 64 | Returns: 65 | ApiResponse object 66 | 67 | """ 68 | 69 | return self._s.pi_get(f'manage/analysis-status/{analysis_status_id}') 70 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/assets_type.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.errors import IrisClientException 20 | from dfir_iris_client.helper.utils import ApiResponse 21 | import logging as logger 22 | 23 | log = logger.getLogger(__name__) 24 | 25 | 26 | class AssetTypeHelper(object): 27 | """Handles the assets type methods""" 28 | def __init__(self, session): 29 | self._s = session 30 | 31 | def list_asset_types(self) -> ApiResponse: 32 | """ 33 | Returns a list of all assets types available 34 | 35 | Args: 36 | 37 | Returns: 38 | APIResponse object 39 | """ 40 | return self._s.pi_get('manage/asset-type/list') 41 | 42 | def get_asset_type(self, asset_type_id: int) -> ApiResponse: 43 | """ 44 | Returns an asset type data from its id 45 | 46 | Args: 47 | asset_type_id: ID of asset type to fetch 48 | 49 | Returns: 50 | ApiResponse 51 | 52 | """ 53 | return self._s.pi_get(f'manage/asset-type/{asset_type_id}') 54 | 55 | def lookup_asset_type_name(self, asset_type_name:str) -> Union[int, None]: 56 | """ 57 | Returns an asset type ID from its name otherwise None 58 | 59 | :raise: Exception if server data is invalid 60 | 61 | Args: 62 | asset_type_name: Name of the asset type to lookup 63 | 64 | Returns: 65 | Union[int, None]: Asset type ID matching provided asset type name 66 | 67 | """ 68 | ast_list = self.list_asset_types() 69 | for ast in ast_list.get_data(): 70 | if ast.get('asset_name') and ast.get('asset_id'): 71 | if ast.get('asset_name').lower() == asset_type_name.lower(): 72 | return ast.get('asset_id') 73 | else: 74 | log.error('Unexpected server response. asset_name and asset_id not found in data') 75 | raise IrisClientException('Unexpected server response. asset_name and asset_id not found in data') 76 | 77 | return None -------------------------------------------------------------------------------- /dfir_iris_client/helper/authorization.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | import enum 18 | 19 | 20 | class CaseAccessLevel(enum.Enum): 21 | deny_all = 0x1 22 | read_only = 0x2 23 | full_access = 0x4 24 | 25 | 26 | class Permissions(enum.Enum): 27 | standard_user = 0x1 28 | server_administrator = 0x2 29 | 30 | alerts_read = 0x4 31 | alerts_write = 0x8 32 | alerts_delete = 0x10 33 | 34 | search_across_cases = 0x20 35 | 36 | customers_read = 0x40 37 | customers_write = 0x80 38 | 39 | case_templates_read = 0x100 40 | case_templates_write = 0x200 41 | 42 | activities_read = 0x400 43 | all_activities_read = 0x800 -------------------------------------------------------------------------------- /dfir_iris_client/helper/case_classifications.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class CaseClassificationsHelper(object): 23 | """Handles the case classifications methods""" 24 | def __init__(self, session): 25 | self._s = session 26 | 27 | def list_case_classifications(self) -> ApiResponse: 28 | """ 29 | Returns a list of all case classifications 30 | 31 | Args: 32 | 33 | Returns: 34 | APIResponse object 35 | """ 36 | return self._s.pi_get('manage/case-classifications/list', cid=1) 37 | 38 | def lookup_case_classification_name(self, case_classification_name: str) -> Union[None, int]: 39 | """ 40 | Returns a case_classification_name from its name otherwise None 41 | 42 | Args: 43 | case_classification_name: Case classification name to lookup 44 | 45 | Returns: 46 | case_classification_name matching provided case classification name otherwise none 47 | 48 | """ 49 | ast_list = self.list_case_classifications() 50 | if ast_list: 51 | for ast in ast_list.get_data(): 52 | if ast.get('name').lower() == case_classification_name.lower(): 53 | return ast.get('id') 54 | 55 | return None 56 | 57 | def get_case_classification(self, case_classification_id: int) -> ApiResponse: 58 | """ 59 | Returns a case classification from its ID 60 | 61 | Args: 62 | case_classification_id: Case classification ID 63 | 64 | Returns: 65 | APIResponse object 66 | """ 67 | return self._s.pi_get(f'manage/case-classifications/{case_classification_id}', cid=1) 68 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/colors.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | """ 19 | Defines standard colors of events used in the GUI. 20 | While custom color can be used, it's recommended to use the same 21 | to keep consistency. 22 | """ 23 | EventWhite = "#fff" 24 | EventBlue = "#1572E899" 25 | EventPurple = "#6861CE99" 26 | EventCyan = "#48ABF799" 27 | EventGreen = "#31CE3699" 28 | EventRed = "#F2596199" 29 | EventOrange = "#FFAD4699" 30 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/compromise_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class CompromiseStatusHelper(object): 23 | """Handles the compromise status methods""" 24 | def __init__(self, session): 25 | self._s = session 26 | 27 | def lookup_compromise_status_name(self, compromise_status_name: str) -> Union[int, None]: 28 | """ 29 | Returns a compromise status ID from its name otherwise None 30 | 31 | Args: 32 | compromise_status_name: str: 33 | 34 | Returns: 35 | Union[int, None] - compromise status ID matching provided analysis status name or None if not found 36 | 37 | """ 38 | cst_list = self.list_compromise_status_types() 39 | for ast in cst_list.get_data(): 40 | if ast.get('name').lower() == compromise_status_name.lower(): 41 | return ast.get('value') 42 | 43 | return None 44 | 45 | def list_compromise_status_types(self): 46 | """ 47 | Returns a list of all compromise statuses 48 | """ 49 | return self._s.pi_get('manage/compromise-status/list') 50 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/doc_tracer.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # 18 | 19 | # 20 | # This file isn't meant to be used for standard operations 21 | # 22 | 23 | import os 24 | 25 | os.environ['IRIS_CLIENT_TRACE_REQUESTS'] = "True" 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/docker_helper.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | # Based on https://github.com/airbus-cyber/iris-httpsend-module/blob/main/tests/docker_compose.py 18 | # 19 | import subprocess 20 | from pathlib import Path 21 | import logging as log 22 | 23 | 24 | class DockerHelper(object): 25 | 26 | def __init__(self, docker_compose_path: str) -> None: 27 | 28 | self._compose_file = docker_compose_path 29 | if not Path(self._compose_file).exists(): 30 | raise FileNotFoundError(f"File {self._compose_file} does not exist") 31 | 32 | def _run_command(self, command: str) -> None: 33 | """Runs a docker-compose command""" 34 | command = f"docker-compose -f {self._compose_file} {command}" 35 | log.info(f"Running command: {command}") 36 | 37 | subprocess.run(command, check=True, shell=True) 38 | 39 | def start(self) -> None: 40 | """Starts the docker-compose environment""" 41 | self._run_command("up -d") 42 | 43 | def stop(self) -> None: 44 | """Stops the docker-compose environment""" 45 | self._run_command("logs") 46 | self._run_command("down --volumes") 47 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/errors.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | class IrisStatus(object): 19 | """Defines a custom status class, used by the abstraction layer 20 | to communicate about API and operations feedbacks 21 | 22 | Args: 23 | 24 | Returns: 25 | 26 | """ 27 | def __init__(self, message=None, data=None, uri=None, is_error=False): 28 | """ 29 | Mimics the JSON feedback returned by the API 30 | 31 | Args: 32 | message: Message from the server about the request 33 | data: Data from the server 34 | uri: URI that was requested 35 | is_error: True if the status is an error, else False 36 | """ 37 | self.message = message 38 | self.data = data 39 | self.uri = uri 40 | self._is_error = is_error 41 | 42 | def __bool__(self): 43 | """Defines the boolean state of IrisStatus""" 44 | return not self._is_error 45 | 46 | def __str__(self): 47 | """Defines the standard str of IrisStatus""" 48 | msg = "" 49 | if self.uri: 50 | msg += f"{self.uri} - " 51 | if self.message: 52 | msg += f"{self.message} - " 53 | if self.data: 54 | msg += f"{self.data}" 55 | if len(msg) == 0 and not self._is_error: 56 | msg = "Success" 57 | else: 58 | msg = "Unspecified error" if not msg else msg 59 | 60 | return msg 61 | 62 | def is_error(self) -> bool: 63 | """Simply return true if status is an error 64 | 65 | Args: 66 | 67 | Returns: 68 | bool 69 | """ 70 | return self._is_error 71 | 72 | def is_success(self) -> bool: 73 | """Simply return true if status is a success 74 | 75 | :return: True if status is a success 76 | 77 | Args: 78 | 79 | Returns: 80 | bool 81 | """ 82 | return not self._is_error 83 | 84 | def set_error(self) -> None: 85 | """Force the status to error 86 | 87 | Args: 88 | 89 | Returns: 90 | None 91 | """ 92 | self._is_error = True 93 | 94 | def set_success(self) -> None: 95 | """Force the status to success 96 | 97 | Args: 98 | 99 | Returns: 100 | None 101 | """ 102 | self._is_error = False 103 | 104 | pass 105 | 106 | 107 | class IrisStatusError(IrisStatus): 108 | """Overlay of IrisStatus, defining a base error status""" 109 | def __init__(self, message=None, data=None, uri=None): 110 | super().__init__(message=message, data=data, uri=uri, is_error=True) 111 | 112 | 113 | class IrisStatusSuccess(IrisStatus): 114 | """Overlay of IrisStatus, defining a base success status""" 115 | def __init__(self, message=None, data=None, uri=None): 116 | super().__init__(message=message, data=data, uri=uri, is_error=False) 117 | 118 | 119 | class OperationSuccess(IrisStatusSuccess): 120 | """ """ 121 | pass 122 | 123 | 124 | BaseOperationSuccess = OperationSuccess() 125 | 126 | 127 | class OperationFailure(IrisStatusError): 128 | """ """ 129 | pass 130 | 131 | 132 | class InvalidObjectMapping(IrisStatusError): 133 | """ """ 134 | pass 135 | 136 | 137 | class InvalidCaseId(IrisStatusError): 138 | """ """ 139 | pass 140 | 141 | 142 | class InvalidObjectId(IrisStatusError): 143 | """ """ 144 | pass 145 | 146 | 147 | class ObjectNotFound(IrisStatusError): 148 | """ """ 149 | pass 150 | 151 | 152 | class InvalidObjectType(IrisStatusError): 153 | """ """ 154 | pass 155 | 156 | 157 | class CaseNotInitialized(IrisStatusError): 158 | """ """ 159 | pass 160 | 161 | 162 | class ApiRequestFailure(IrisStatusError): 163 | """ """ 164 | pass 165 | 166 | 167 | class InvalidApiResponse(IrisStatusError): 168 | """ """ 169 | pass 170 | 171 | 172 | class ObjectNotInitialized(IrisStatusError): 173 | """ """ 174 | pass 175 | 176 | 177 | class ObjectAlreadyInitialized(IrisStatusError): 178 | """ """ 179 | pass 180 | 181 | 182 | class IrisClientException(Exception): 183 | """ """ 184 | pass 185 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/events_categories.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class EventCategoryHelper(object): 23 | """Handles the event category methods""" 24 | def __init__(self, session): 25 | self._s = session 26 | 27 | def list_events_categories(self) -> ApiResponse: 28 | """ 29 | Returns a list of all events categories available 30 | 31 | Args: 32 | 33 | Returns: 34 | ApiResponse object 35 | """ 36 | return self._s.pi_get('manage/event-categories/list') 37 | 38 | def lookup_event_category_name(self, event_category: str) -> Union[None, int]: 39 | """ 40 | Returns an event category ID from its name otherwise None 41 | 42 | Args: 43 | event_category: Name of the event to lookup 44 | 45 | Returns: 46 | Union[None, int]: Event category ID matching provided event_category name 47 | 48 | """ 49 | evt_list = self.list_events_categories() 50 | if evt_list: 51 | for evt in evt_list.get_data(): 52 | if evt.get('name').lower() == event_category.lower(): 53 | return evt.get('id') 54 | 55 | return None 56 | 57 | def get_event_category(self, event_category_id: int) -> ApiResponse: 58 | """ 59 | Returns an event category from its ID 60 | 61 | Args: 62 | event_category_id: Event category to lookup 63 | 64 | Returns: 65 | ApiResponse object 66 | 67 | """ 68 | return self._s.pi_get(f'manage/event-categories/{event_category_id}') -------------------------------------------------------------------------------- /dfir_iris_client/helper/ioc_types.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class IocTypeHelper(object): 23 | """Handles the IOC types methods""" 24 | def __init__(self, session): 25 | self._s = session 26 | 27 | def list_ioc_types(self) -> ApiResponse: 28 | """ 29 | Returns a list of all ioc types 30 | 31 | Args: 32 | 33 | Returns: 34 | APIResponse object 35 | """ 36 | return self._s.pi_get('manage/ioc-types/list', cid=1) 37 | 38 | def lookup_ioc_type_name(self, ioc_type_name: str) -> Union[None, int]: 39 | """ 40 | Returns an ioc_type_name from its name otherwise None 41 | 42 | Args: 43 | ioc_type_name: IOC type name to lookup 44 | 45 | Returns: 46 | ioc_type_name matching provided ioc type name otherwise none 47 | 48 | """ 49 | ast_list = self.list_ioc_types() 50 | if ast_list: 51 | for ast in ast_list.get_data(): 52 | if ast.get('type_name').lower() == ioc_type_name.lower(): 53 | return ast.get('type_id') 54 | 55 | return None 56 | 57 | def get_ioc_type(self, ioc_type_id: int) -> ApiResponse: 58 | """ 59 | Returns an ioc type from its ID 60 | 61 | Args: 62 | ioc_type_id: Type ID to lookup 63 | 64 | Returns: 65 | ApiResponse object 66 | 67 | """ 68 | return self._s.pi_get(f'manage/ioc-types/{ioc_type_id}') -------------------------------------------------------------------------------- /dfir_iris_client/helper/objects_def.py: -------------------------------------------------------------------------------- 1 | objects_map = { 2 | "ioc_type": { 3 | "id": "type_id", 4 | "name": "type_name", 5 | "description": "type_description", 6 | "taxonomy": "type_taxonomy" 7 | }, 8 | "tlp": { 9 | "id": "tlp_id", 10 | "name": "tlp_name", 11 | "color": "tlp_bscolor" 12 | }, 13 | "ioc": { 14 | "id": "ioc_id", 15 | "_value": "ioc_value", 16 | "_description": "ioc_description", 17 | "_tags": "ioc_tags", 18 | "_tlp": "ioc_tlp_id", 19 | "_ioc_type": "ioc_type_id" 20 | }, 21 | "customer": { 22 | "id": "customer_id", 23 | "name": "customer_name" 24 | }, 25 | "analysis_status": { 26 | "id": "id", 27 | "name": "name" 28 | }, 29 | "asset_type": { 30 | "id": "asset_id", 31 | "description": "asset_description", 32 | "name": "asset_name" 33 | }, 34 | "asset": { 35 | "id": "asset_id", 36 | "_name": "asset_name", 37 | "_description": "asset_description", 38 | "_ip": "asset_ip", 39 | "tags": "asset_tags", 40 | "_domain": "asset_domain", 41 | "_compromised": "asset_compromised", 42 | "_additional_info": "asset_info", 43 | "analysis_status": "analysis_status_id", 44 | "ioc_links": "linked_ioc", 45 | "asset_type": "asset_type_id" 46 | }, 47 | "notes_group": { 48 | "id": "group_id", 49 | "_title": "group_title" 50 | }, 51 | "note": { 52 | "id": "note_id", 53 | "_title": "note_title", 54 | "_content": "note_content" 55 | }, 56 | "event": { 57 | "id": "event_id", 58 | "_category": "event_category_id", 59 | "_color": "event_color", 60 | "_content": "event_content", 61 | "date": "event_date", 62 | "_date_wtz": "event_date_wtz", 63 | "_in_graph": "event_in_graph", 64 | "_in_summary": "event_in_summary", 65 | "_raw_content": "event_raw", 66 | "_source": "event_source", 67 | "_tags": "event_tag", 68 | "_title": "event_title", 69 | "_tz": "event_tz" 70 | }, 71 | "event_category": { 72 | "id": "id", 73 | "_name": "name" 74 | }, 75 | "case_task": { 76 | "id": "task_id", 77 | "_title": "task_title", 78 | "assignee": "task_assignee_id", 79 | "_description": "task_description", 80 | "open_date": "task_open_date", 81 | "close_date": "task_close_date", 82 | "last_update_date": "task_last_update", 83 | "status": "task_status", 84 | "tags": "task_tags" 85 | }, 86 | "global_task": { 87 | "id": "task_id", 88 | "_title": "task_title", 89 | "assignee": "task_assignee_id", 90 | "_description": "task_description", 91 | "open_date": "task_open_date", 92 | "close_date": "task_close_date", 93 | "last_update_date": "task_last_update", 94 | "status": "task_status_id", 95 | "tags": "task_tags" 96 | }, 97 | "task_status": { 98 | "id": "id", 99 | "_name": "status_name", 100 | "_description": "status_description", 101 | "_bscolor": "status_bscolor" 102 | }, 103 | "user": { 104 | "id": "user_id", 105 | "login": "user_login", 106 | "name": "user_name", 107 | "active": "user_active" 108 | }, 109 | "evidence": { 110 | "id": "id", 111 | "_description": "file_description", 112 | "_filename": "filename", 113 | "_hash": "file_hash", 114 | "_size": "file_size" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/outcome_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | class CaseOutcomeStatusHelper(object): 19 | """Handles the case outcome status API calls.""" 20 | 21 | def __init__(self, session): 22 | """Initialize the class.""" 23 | self._s = session 24 | 25 | def list_case_outcome_status_types(self): 26 | """ 27 | List all case outcome status types. 28 | """ 29 | return self._s.pi_get('/manage/outcome-status/list', cid=1) 30 | 31 | def lookup_case_outcome_status_name(self, case_outcome_status_name): 32 | """ 33 | Lookup a case outcome status ID from its name. 34 | """ 35 | cst_list = self.list_case_outcome_status_types() 36 | for ast in cst_list.get_data(): 37 | if ast.get('name').lower() == case_outcome_status_name.lower(): 38 | return ast.get('value') 39 | 40 | return None 41 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/report_template_types.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | """ 19 | Defines standard template types 20 | """ 21 | from enum import Enum 22 | 23 | 24 | class ReportTemplateType(Enum): 25 | InvestigationReport = 1 26 | ActivityReport = 2 27 | 28 | 29 | class ReportTemplateLanguage(Enum): 30 | french = 1 31 | english = 2 32 | german = 3 33 | bulgarian = 4 34 | croatian = 5 35 | danish = 6 36 | dutch = 7 37 | estonian = 8 38 | finnish = 9 39 | greek = 10 40 | hungarian = 11 41 | irish = 12 42 | italian = 13 43 | latvian = 14 44 | lithuanian = 15 45 | maltese = 16 46 | polish = 17 47 | portuguese = 18 48 | romanian = 19 49 | slovak = 20 50 | slovenian = 21 51 | spanish = 22 52 | swedish = 23 53 | indian = 24 54 | chinese = 25 55 | korean = 26 56 | arabic = 27 57 | japanese = 28 58 | turkish = 29 59 | vietnamese = 30 60 | thai = 31 61 | hebrew = 32 62 | czech = 33 63 | norwegian = 34 64 | brazilian = 35 65 | ukrainian = 36 66 | catalan = 37 67 | serbian = 38 68 | persian = 39 69 | afrikaans = 40 70 | albanian = 41 71 | 72 | 73 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/task_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class TaskStatusHelper(object): 23 | """Handles the analysis status methods""" 24 | def __init__(self, session): 25 | self._s = session 26 | 27 | def list_task_status_types(self) -> ApiResponse: 28 | """ 29 | Returns a list of all tasks statuses 30 | 31 | Args: 32 | 33 | Returns: 34 | ApiResponse 35 | """ 36 | return self._s.pi_get('manage/task-status/list') 37 | 38 | def lookup_task_status_name(self, task_status_name: str) -> Union[int, None]: 39 | """ 40 | Returns a task status ID from its name otherwise None 41 | 42 | Args: 43 | task_status_name: str: Task name to lookup 44 | 45 | Returns: 46 | Union[int, None] - task status ID matching provided task status name 47 | 48 | """ 49 | ast_list = self.list_task_status_types() 50 | if ast_list: 51 | for ast in ast_list.get_data(): 52 | if ast.get('status_name').lower() == task_status_name.lower(): 53 | return ast.get('id') 54 | 55 | return None 56 | 57 | def get_task_status(self, task_status_id: int) -> ApiResponse: 58 | """ 59 | Returns a task status from its ID 60 | 61 | Args: 62 | task_status_id: int: Task ID to lookup 63 | 64 | Returns: 65 | ApiResponse object 66 | 67 | """ 68 | 69 | return self._s.pi_get(f'manage/task-status/{task_status_id}') 70 | -------------------------------------------------------------------------------- /dfir_iris_client/helper/tlps.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import Union 18 | 19 | from dfir_iris_client.helper.utils import ApiResponse 20 | 21 | 22 | class TlpHelper(object): 23 | """Handles the TLP methods""" 24 | 25 | def __init__(self, session): 26 | self._s = session 27 | 28 | def list_tlps(self) -> ApiResponse: 29 | """ 30 | Returns a list of all tlps available 31 | 32 | Args: 33 | 34 | Returns: 35 | ApiResponse object 36 | """ 37 | 38 | return self._s.pi_get('manage/tlp/list') 39 | 40 | def lookup_tlp_name(self, tlp_name: str) -> Union[int, None]: 41 | """ 42 | Returns a tlp ID from its name otherwise None 43 | 44 | :return: tlp ID matching provided tlp name or None 45 | 46 | Args: 47 | tlp_name: str: Name of the TLP 48 | 49 | Returns: 50 | Union[int, None] 51 | """ 52 | tlp_list_req = self.list_tlps() 53 | 54 | if tlp_list_req: 55 | for tlp in tlp_list_req.get_data(): 56 | if tlp['tlp_name'].lower() == tlp_name.lower(): 57 | return tlp['tlp_id'] 58 | 59 | return None 60 | 61 | def get_tlp(self, tlp_id: int) -> ApiResponse: 62 | """ 63 | Returns a tlp from its ID 64 | 65 | Args: 66 | tlp_id: TLP ID to lookup 67 | 68 | Returns: 69 | ApiResponse object 70 | 71 | """ 72 | return self._s.pi_get(f'manage/tlp/{tlp_id}') 73 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/dfir_iris_client/tests/__init__.py -------------------------------------------------------------------------------- /dfir_iris_client/tests/conftest.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | from typing import List 18 | 19 | import pytest 20 | from dfir_iris_client.helper.authorization import Permissions 21 | 22 | 23 | class User(object): 24 | """ """ 25 | def __init__(self, username=None, login=None, password=None, email=None, api_key=None): 26 | """User helper class """ 27 | self.username = username 28 | self.login = login 29 | self.password = password 30 | self.email = email 31 | self.api_key = api_key 32 | 33 | 34 | class Group(object): 35 | """Group helper class """ 36 | def __init__(self, **kwargs): 37 | """ """ 38 | self.name = kwargs.get('name') 39 | self.description = kwargs.get('description') 40 | self.permissions = [] 41 | self.group_auto_follow = kwargs.get('group_auto_follow', False) 42 | self.group_auto_follow_access_level = kwargs.get('group_auto_follow_access_level', 0) 43 | 44 | 45 | class Case(object): 46 | """Case helper class """ 47 | 48 | def __init__(self, **kwargs): 49 | """ """ 50 | self.case_name = kwargs.get('case_name') 51 | self.case_description = kwargs.get('case_description') 52 | self.case_customer = kwargs.get('case_customer') 53 | self.case_customer_id = kwargs.get('case_customer_id') 54 | self.soc_id = kwargs.get('soc_id') 55 | self.case_classification = kwargs.get('case_classification') 56 | 57 | 58 | @pytest.fixture(scope="class") 59 | def standard_user(request): 60 | """Create a standard user for testing """ 61 | user = User() 62 | user.login = 'test_user' 63 | user.password = 'TestPassword1-' 64 | user.username = 'test_user' 65 | user.email = 'test@iris.local' 66 | 67 | request.cls.standard_user = user 68 | 69 | 70 | @pytest.fixture(scope="class") 71 | def standard_group(request): 72 | """Create a standard group for testing """ 73 | group = Group() 74 | group.name = 'test_group' 75 | group.description = 'test group description' 76 | group.permissions = [Permissions.standard_user] 77 | 78 | request.cls.standard_group = group 79 | 80 | 81 | @pytest.fixture(scope="class") 82 | def admin_group(request): 83 | """Create an admin group for testing """ 84 | group = Group() 85 | group.name = 'test_adm_group' 86 | group.description = 'test adm group description' 87 | group.permissions = [Permissions.server_administrator, Permissions.standard_user] 88 | 89 | request.cls.admin_group = group 90 | 91 | 92 | @pytest.fixture(scope="class") 93 | def native_admin_group(request): 94 | """Fetch the native admin group for testing """ 95 | group = Group() 96 | group.name = 'Administrators' 97 | group.description = 'Administrators' 98 | group.permissions = [Permissions.server_administrator, Permissions.standard_user] 99 | group.group_auto_follow = True 100 | group.group_auto_follow_access_level = 4 101 | 102 | request.cls.native_admin_group = group 103 | 104 | 105 | @pytest.fixture(scope="class") 106 | def standard_case(request): 107 | """Create a standard case for testing""" 108 | case = Case() 109 | case.case_name = 'Administrators' 110 | case.case_description = 'Administrators' 111 | case.case_customer = "IrisInitialClient" 112 | case.case_customer_id = 1 113 | case.soc_id = "Dummy SOC ID" 114 | case.case_classification = 'other:other' 115 | 116 | request.cls.standard_case = case 117 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/resources/.env: -------------------------------------------------------------------------------- 1 | ## ENVIRONMENT used by docker-compose to set the environment variables 2 | ## for testing purposes only 3 | ## DO NOT USE THIS FILE IN PRODUCTION 4 | 5 | #### NGINX 6 | SERVER_NAME=test.iris.local 7 | KEY_FILENAME=iris_dev_key.pem 8 | CERT_FILENAME=iris_dev_cert.pem 9 | 10 | #### DATABASE 11 | POSTGRES_PASSWORD=APasswordForTesting 12 | POSTGRES_ADMIN_PASSWORD=APasswordForTesting 13 | POSTGRES_ADMIN_USER=raptor 14 | POSTGRES_DB=iris_db 15 | POSTGRES_USER=postgres 16 | 17 | POSTGRES_SERVER=db 18 | POSTGRES_PORT=5432 19 | 20 | ## APP 21 | DOCKERIZED=1 22 | IRIS_SECRET_KEY=A_Secret_For_Testing 23 | IRIS_SECURITY_PASSWORD_SALT=A_Salt_For_Testing 24 | IRIS_ADM_API_KEY=lC6BWdeKn8BHxyChULnIghsRY5VRlIx_vwiGj1imYw40JdwnbRL-ajZa4vt9wM8JgqNH3RqosAS8hwvlGS70uw 25 | IRIS_ADM_PASSWORD=AdminP@ssw0rd 26 | IRIS_UPSTREAM_SERVER=app 27 | IRIS_UPSTREAM_PORT=8000 28 | 29 | ## Authentication 30 | IRIS_AUTHENTICATION_TYPE=local 31 | 32 | IRIS_DEMO_ENABLED=False 33 | 34 | IRIS_ORGANISATION_NAME="DEMO" 35 | INTERFACE_HTTPS_PORT=443 36 | 37 | #TEST_WITH_DOCKER=True 38 | IRIS_CLIENT_TRACE_REQUESTS=True -------------------------------------------------------------------------------- /dfir_iris_client/tests/resources/alert.json: -------------------------------------------------------------------------------- 1 | { 2 | "alert_title": "Super alert 5", 3 | "alert_description": "This is a test alert", 4 | "alert_source": "Test Source", 5 | "alert_source_ref": "Test-123", 6 | "alert_source_link": "https://source_link.com", 7 | "alert_source_content": {}, 8 | "alert_severity_id": 4, 9 | "alert_status_id": 3, 10 | "alert_context": { 11 | "context_key": "context_value" 12 | }, 13 | "alert_source_event_time": "2023-03-26T03:00:30", 14 | "alert_note": "Test note", 15 | "alert_tags": "defender", 16 | "alert_iocs": [ 17 | { 18 | "ioc_value": "tarzan5", 19 | "ioc_description": "description kwekwe", 20 | "ioc_tlp_id": 1, 21 | "ioc_type_id": 2, 22 | "ioc_tags": "tag1,tag2", 23 | "ioc_enrichment": { "provider_1": { "data": 2, "new_data": 3 }, "provider_3": { "enric": "true" } 24 | } 25 | 26 | }, 27 | { 28 | "ioc_value": "tarzan2", 29 | "ioc_description": "description_hey", 30 | "ioc_tlp_id": 2, 31 | "ioc_type_id": 4, 32 | "ioc_tags": "tag1,tag2", 33 | "ioc_enrichment": { "provider_1": { "data": "a very long\nblablablabdjsjofiasofiasjdxaisjhfaiosxhd bla\nddijwedoijwedw\ndhasdhaifuhafiassfsakjfhaskljfhaslkfjhaslkfdjhdqwleiuhxioauwedhoqwiuhzndoqwuehxdnzoiuwehfoqwiufhxnwoquhoiwefhxnqwoiuhwqomifuhqzwofuhqwofeuzhqwofeiuqhwe fifuhqwiofuh qwofuqh fuq hwfoiqwhfoiquhfe quhfqiouwhf qoufhq hufou qufhqowiufhowufih qwfuhqwioufh wqoufh wifhufdhas", "new_data": 3 }, "provider_3": { "enric": "true" }} 34 | 35 | } 36 | ], 37 | "alert_assets": [{ 38 | "asset_name": "My super nop", 39 | "asset_description": "Asset description", 40 | "asset_type_id": 1, 41 | "asset_ip": "1.1.1.1", 42 | "asset_domain": "", 43 | "asset_tags": "tag1,tag2", 44 | "asset_enrichment": {"enrich": {"enrich2": "super_enrich"}} 45 | }], 46 | "alert_customer_id": 1, 47 | "alert_classification_id": 1 48 | } -------------------------------------------------------------------------------- /dfir_iris_client/tests/resources/test_report.md: -------------------------------------------------------------------------------- 1 | # Test report 2 | 3 | ## {{ case.case_name }} -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_alert_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.alert_status import AlertStatusHelper 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class AlertStatusTest(InitIrisClientTest): 24 | """ """ 25 | 26 | def setUp(self) -> None: 27 | """ """ 28 | self.asset_type = AlertStatusHelper(self.session) 29 | 30 | def test_list_alert_status(self): 31 | """ """ 32 | ret = self.asset_type.list_alert_status_types() 33 | 34 | assert assert_api_resp(ret) 35 | 36 | assert ret.get_data_field('status_name', index=0) is not None 37 | assert ret.get_data_field('status_description', index=0) is not None 38 | assert ret.get_data_field('status_id', index=0) is not None 39 | 40 | def test_get_alert_status_by_id(self): 41 | """ """ 42 | ret = self.asset_type.list_alert_status_types() 43 | 44 | assert assert_api_resp(ret, soft_fail=False) 45 | 46 | ret = self.asset_type.get_alert_status(ret.get_data_field('status_id', index=0)) 47 | assert assert_api_resp(ret, soft_fail=False) 48 | 49 | assert ret.get_data_field('status_name') is not None 50 | assert ret.get_data_field('status_description') is not None 51 | assert ret.get_data_field('status_id') is not None 52 | 53 | def test_get_alert_status_by_name(self): 54 | """ """ 55 | ret = self.asset_type.list_alert_status_types() 56 | 57 | assert assert_api_resp(ret, soft_fail=False) 58 | 59 | ret = self.asset_type.lookup_alert_status_name(ret.get_data_field('status_name', index=0)) 60 | assert ret is not None 61 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_analysis_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.analysis_status import AnalysisStatusHelper 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class AnalysisStatusTest(InitIrisClientTest): 24 | """ """ 25 | 26 | def setUp(self): 27 | """ """ 28 | self.asset_type = AnalysisStatusHelper(self.session) 29 | 30 | def test_list_analysis_status(self): 31 | """ """ 32 | ret = self.asset_type.list_analysis_status_types() 33 | 34 | assert assert_api_resp(ret) 35 | 36 | data = get_data_from_resp(ret) 37 | assert parse_api_data(data[0], 'id') is not None 38 | assert parse_api_data(data[0], 'name') is not None 39 | 40 | def test_get_analysis_status_by_id(self): 41 | """ """ 42 | ret = self.asset_type.list_analysis_status_types() 43 | 44 | assert assert_api_resp(ret, soft_fail=False) 45 | data = get_data_from_resp(ret) 46 | 47 | ret = self.asset_type.get_analysis_status(parse_api_data(data[0], 'id')) 48 | assert assert_api_resp(ret, soft_fail=False) 49 | 50 | data = get_data_from_resp(ret) 51 | assert parse_api_data(data, 'id') is not None 52 | assert parse_api_data(data, 'name') is not None 53 | 54 | def test_get_analysis_status_by_name(self): 55 | """ """ 56 | ret = self.asset_type.list_analysis_status_types() 57 | 58 | assert assert_api_resp(ret, soft_fail=False) 59 | data = get_data_from_resp(ret) 60 | 61 | ret = self.asset_type.lookup_analysis_status_name(parse_api_data(data[0], 'name')) 62 | assert ret is not None 63 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_asset_type.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.assets_type import AssetTypeHelper 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class AssetTypeTest(InitIrisClientTest): 24 | """ """ 25 | 26 | def setUp(self): 27 | """ """ 28 | self.asset_type = AssetTypeHelper(self.session) 29 | 30 | def test_list_asset_types(self): 31 | """ """ 32 | ret = self.asset_type.list_asset_types() 33 | 34 | assert assert_api_resp(ret) 35 | 36 | data = get_data_from_resp(ret) 37 | assert parse_api_data(data[0], 'asset_description') is not None 38 | assert parse_api_data(data[0], 'asset_id') is not None 39 | assert parse_api_data(data[0], 'asset_name') is not None 40 | 41 | def test_get_asset_type_by_id(self): 42 | """ """ 43 | ret = self.asset_type.get_asset_type(1) 44 | 45 | assert assert_api_resp(ret) 46 | 47 | data = get_data_from_resp(ret) 48 | assert parse_api_data(data, 'asset_description') is not None 49 | assert parse_api_data(data, 'asset_id') is not None 50 | assert parse_api_data(data, 'asset_name') is not None 51 | 52 | def test_get_asset_type_by_name(self): 53 | """ """ 54 | ret = self.asset_type.lookup_asset_type_name('Account') 55 | 56 | assert ret is not None -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_case_classifications.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.case_classifications import CaseClassificationsHelper 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class CaseClassificationsTest(InitIrisClientTest): 24 | """ """ 25 | def setUp(self): 26 | """ """ 27 | self.ccl = CaseClassificationsHelper(self.session) 28 | 29 | def test_case_classifications(self): 30 | """ """ 31 | ret = self.ccl.list_case_classifications() 32 | 33 | assert assert_api_resp(ret) 34 | 35 | data = get_data_from_resp(ret) 36 | assert isinstance(parse_api_data(data[0], 'creation_date'), str) 37 | assert isinstance(parse_api_data(data[0], 'description'), str) 38 | assert isinstance(parse_api_data(data[0], 'id'), int) 39 | assert isinstance(parse_api_data(data[0], 'name'), str) 40 | assert isinstance(parse_api_data(data[0], 'name_expanded'), str) 41 | 42 | def test_lookup_case_classification_name_valid(self): 43 | """ """ 44 | ret = self.ccl.lookup_case_classification_name('abusive-content:spam') 45 | assert isinstance(ret, int) 46 | 47 | def test_lookup_case_classification_name_invalid(self): 48 | """ """ 49 | ret = self.ccl.lookup_case_classification_name('invalid') 50 | assert ret is None 51 | 52 | def test_get_case_classification_valid(self): 53 | """ """ 54 | ret = self.ccl.get_case_classification(1) 55 | assert assert_api_resp(ret) 56 | data = get_data_from_resp(ret) 57 | assert isinstance(parse_api_data(data, 'creation_date'), str) 58 | assert isinstance(parse_api_data(data, 'description'), str) 59 | assert isinstance(parse_api_data(data, 'id'), int) 60 | assert isinstance(parse_api_data(data, 'name'), str) 61 | assert isinstance(parse_api_data(data, 'name_expanded'), str) 62 | 63 | def test_get_case_classification_invalid(self): 64 | """ """ 65 | ret = self.ccl.get_case_classification(999999) 66 | assert assert_api_resp(ret).is_error() is True 67 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_compromise_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | import unittest 19 | 20 | from dfir_iris_client.helper.analysis_status import AnalysisStatusHelper 21 | from dfir_iris_client.helper.compromise_status import CompromiseStatusHelper 22 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 23 | from dfir_iris_client.tests.tests_helper import new_session, new_adm_session, InitIrisClientTest 24 | 25 | 26 | class CompromiseStatusTest(InitIrisClientTest): 27 | """ """ 28 | 29 | def setUp(self): 30 | """ """ 31 | self.csh = CompromiseStatusHelper(self.session) 32 | 33 | def test_list_compromise_status(self): 34 | """ """ 35 | ret = self.csh.list_compromise_status_types() 36 | 37 | assert assert_api_resp(ret) 38 | 39 | data = get_data_from_resp(ret) 40 | assert parse_api_data(data[0], 'value') is not None 41 | assert type(parse_api_data(data[0], 'value')) is int 42 | assert parse_api_data(data[0], 'name') is not None 43 | assert type(parse_api_data(data[0], 'name')) is str 44 | 45 | def test_get_analysis_status_by_name(self): 46 | """ """ 47 | ret = self.csh.list_compromise_status_types() 48 | 49 | assert assert_api_resp(ret, soft_fail=False) 50 | data = get_data_from_resp(ret) 51 | 52 | ret = self.csh.lookup_compromise_status_name(parse_api_data(data[0], 'name')) 53 | assert ret is not None 54 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_customer.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.customer import Customer 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class CustomerTest(InitIrisClientTest): 24 | """ """ 25 | def setUp(self): 26 | """ """ 27 | self.customers = Customer(self.session) 28 | 29 | def test_list_customers(self): 30 | """ """ 31 | ret = self.customers.list_customers() 32 | 33 | assert assert_api_resp(ret) 34 | 35 | data = get_data_from_resp(ret) 36 | assert parse_api_data(data[0], 'customer_id') is not None 37 | assert parse_api_data(data[0], 'customer_name') is not None 38 | 39 | def test_get_customer_by_id(self): 40 | """ """ 41 | ret = self.customers.list_customers() 42 | 43 | assert assert_api_resp(ret, soft_fail=False) 44 | data = get_data_from_resp(ret) 45 | 46 | ret = self.customers.get_customer_by_id(parse_api_data(data[0], 'customer_id')) 47 | assert assert_api_resp(ret, soft_fail=False) 48 | 49 | data = get_data_from_resp(ret) 50 | assert parse_api_data(data, 'customer_id') is not None 51 | assert parse_api_data(data, 'customer_name') is not None 52 | 53 | def test_get_customer_by_name(self): 54 | """ """ 55 | ret = self.customers.list_customers() 56 | 57 | assert assert_api_resp(ret, soft_fail=False) 58 | data = get_data_from_resp(ret) 59 | 60 | ret = self.customers.lookup_customer(parse_api_data(data[0], 'customer_name')) 61 | assert ret is not None -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_event_categories.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.helper.events_categories import EventCategoryHelper 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class EventCategoryTest(InitIrisClientTest): 24 | """ """ 25 | def setUp(self): 26 | """ """ 27 | self.ec = EventCategoryHelper(self.session) 28 | 29 | def test_list_event_categories(self): 30 | """ """ 31 | ret = self.ec.list_events_categories() 32 | 33 | assert assert_api_resp(ret, soft_fail=False) 34 | 35 | data = get_data_from_resp(ret) 36 | assert parse_api_data(data[0], 'id') is not None 37 | assert parse_api_data(data[0], 'name') is not None 38 | 39 | def test_get_event_category_by_id(self): 40 | """ """ 41 | ret = self.ec.list_events_categories() 42 | 43 | assert assert_api_resp(ret, soft_fail=False) 44 | data = get_data_from_resp(ret) 45 | 46 | ret = self.ec.get_event_category(parse_api_data(data[0], 'id')) 47 | assert assert_api_resp(ret, soft_fail=False) 48 | 49 | data = get_data_from_resp(ret) 50 | assert parse_api_data(data, 'id') is not None 51 | assert parse_api_data(data, 'name') is not None 52 | 53 | def test_get_event_category_by_name(self): 54 | """ """ 55 | ret = self.ec.list_events_categories() 56 | 57 | assert assert_api_resp(ret, soft_fail=False) 58 | data = get_data_from_resp(ret) 59 | 60 | ret = self.ec.lookup_event_category_name(parse_api_data(data[0], 'name')) 61 | assert ret is not None 62 | 63 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_global_search.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | from dfir_iris_client.global_search import global_search_ioc, global_search_notes 19 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 20 | from dfir_iris_client.tests.tests_helper import InitIrisClientTest 21 | 22 | 23 | class GlobalSearchTest(InitIrisClientTest): 24 | """ """ 25 | def test_search_ioc(self): 26 | """ """ 27 | ret = global_search_ioc(self.session, search_term='%') 28 | 29 | assert assert_api_resp(ret, soft_fail=False) 30 | 31 | data = get_data_from_resp(ret) 32 | assert type(parse_api_data(data[0], 'case_name')) is str 33 | assert type(parse_api_data(data[0], 'customer_name')) is str 34 | assert type(parse_api_data(data[0], 'ioc_description')) is str 35 | 36 | def test_search_notes(self): 37 | """ """ 38 | ret = global_search_notes(self.session, search_term='%') 39 | 40 | assert assert_api_resp(ret, soft_fail=False) 41 | 42 | data = get_data_from_resp(ret) 43 | assert type(parse_api_data(data[0], 'case_name')) is str 44 | assert type(parse_api_data(data[0], 'client_name')) is str 45 | assert type(parse_api_data(data[0], 'note_id')) is int 46 | assert type(parse_api_data(data[0], 'note_title')) is str 47 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_ioc_types.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | import unittest 19 | 20 | from dfir_iris_client.helper.ioc_types import IocTypeHelper 21 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 22 | from dfir_iris_client.tests.tests_helper import new_session, new_adm_session, InitIrisClientTest 23 | 24 | 25 | class IocTypesTest(InitIrisClientTest): 26 | """ """ 27 | def setUp(self): 28 | """ """ 29 | self.ioct = IocTypeHelper(self.session) 30 | 31 | def test_list_ioc_types(self): 32 | """ """ 33 | ret = self.ioct.list_ioc_types() 34 | 35 | assert assert_api_resp(ret) 36 | 37 | data = get_data_from_resp(ret) 38 | parse_api_data(data[0], 'type_description') 39 | parse_api_data(data[0], 'type_name') 40 | parse_api_data(data[0], 'type_taxonomy') 41 | parse_api_data(data[0], 'type_id') 42 | 43 | def test_get_ioc_types_by_id(self): 44 | """ """ 45 | ret = self.ioct.list_ioc_types() 46 | 47 | assert assert_api_resp(ret, soft_fail=False) 48 | data = get_data_from_resp(ret) 49 | 50 | ret = self.ioct.get_ioc_type(parse_api_data(data[0], 'type_id')) 51 | assert assert_api_resp(ret, soft_fail=False) 52 | 53 | data = get_data_from_resp(ret) 54 | parse_api_data(data, 'type_description') 55 | parse_api_data(data, 'type_name') 56 | parse_api_data(data, 'type_taxonomy') 57 | 58 | def test_get_analysis_status_by_name(self): 59 | """ """ 60 | ret = self.ioct.list_ioc_types() 61 | 62 | assert assert_api_resp(ret, soft_fail=False) 63 | data = get_data_from_resp(ret) 64 | 65 | ret = self.ioct.lookup_ioc_type_name(parse_api_data(data[0], 'type_name')) 66 | assert ret is not None 67 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_task_status.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | import unittest 19 | 20 | from dfir_iris_client.helper.task_status import TaskStatusHelper 21 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 22 | from dfir_iris_client.tests.tests_helper import new_session, new_adm_session, InitIrisClientTest 23 | 24 | 25 | class TaskStatusTest(InitIrisClientTest): 26 | """ """ 27 | def setUp(self): 28 | """ """ 29 | self.tsh = TaskStatusHelper(self.session) 30 | 31 | def test_list_task_status(self): 32 | """ """ 33 | ret = self.tsh.list_task_status_types() 34 | 35 | assert assert_api_resp(ret) 36 | 37 | data = get_data_from_resp(ret) 38 | assert parse_api_data(data[0], 'id') is not None 39 | assert parse_api_data(data[0], 'status_name') is not None 40 | assert parse_api_data(data[0], 'status_description') is not None 41 | assert parse_api_data(data[0], 'status_bscolor') is not None 42 | 43 | def test_get_task_status_by_id(self): 44 | """ """ 45 | ret = self.tsh.list_task_status_types() 46 | 47 | assert assert_api_resp(ret, soft_fail=False) 48 | data = get_data_from_resp(ret) 49 | 50 | ret = self.tsh.get_task_status(parse_api_data(data[0], 'id')) 51 | assert assert_api_resp(ret, soft_fail=False) 52 | 53 | data = get_data_from_resp(ret) 54 | assert parse_api_data(data, 'id') is not None 55 | assert parse_api_data(data, 'status_name') is not None 56 | assert parse_api_data(data, 'status_description') is not None 57 | assert parse_api_data(data, 'status_bscolor') is not None 58 | 59 | def test_get_analysis_status_by_name(self): 60 | """ """ 61 | ret = self.tsh.list_task_status_types() 62 | 63 | assert assert_api_resp(ret, soft_fail=False) 64 | data = get_data_from_resp(ret) 65 | 66 | ret = self.tsh.lookup_task_status_name(parse_api_data(data[0], 'status_name')) 67 | assert ret is not None 68 | 69 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/test_tlps.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | 18 | import unittest 19 | 20 | from dfir_iris_client.helper.tlps import TlpHelper 21 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 22 | from dfir_iris_client.tests.tests_helper import new_session, new_adm_session, InitIrisClientTest 23 | 24 | 25 | class TlpTest(InitIrisClientTest): 26 | """ """ 27 | def setUp(self): 28 | """ """ 29 | self.tsh = TlpHelper(self.session) 30 | 31 | def test_list_task_status(self): 32 | """ """ 33 | ret = self.tsh.list_tlps() 34 | 35 | assert assert_api_resp(ret, soft_fail=False) 36 | 37 | data = get_data_from_resp(ret) 38 | assert parse_api_data(data[0], 'tlp_id') is not None 39 | assert parse_api_data(data[0], 'tlp_name') is not None 40 | assert parse_api_data(data[0], 'tlp_bscolor') is not None 41 | 42 | def test_get_task_status_by_id(self): 43 | """ """ 44 | ret = self.tsh.list_tlps() 45 | 46 | assert assert_api_resp(ret, soft_fail=False) 47 | data = get_data_from_resp(ret) 48 | 49 | ret = self.tsh.get_tlp(parse_api_data(data[0], 'tlp_id')) 50 | assert assert_api_resp(ret, soft_fail=False) 51 | 52 | data = get_data_from_resp(ret) 53 | assert parse_api_data(data, 'tlp_id') is not None 54 | assert parse_api_data(data, 'tlp_name') is not None 55 | assert parse_api_data(data, 'tlp_bscolor') is not None 56 | 57 | def test_get_analysis_status_by_name(self): 58 | """ """ 59 | ret = self.tsh.list_tlps() 60 | 61 | assert assert_api_resp(ret, soft_fail=False) 62 | data = get_data_from_resp(ret) 63 | 64 | ret = self.tsh.lookup_tlp_name(parse_api_data(data[0], 'tlp_name')) 65 | assert ret is not None 66 | 67 | -------------------------------------------------------------------------------- /dfir_iris_client/tests/tests_helper.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | import os 18 | import unittest 19 | from pathlib import Path 20 | from time import sleep 21 | 22 | import requests 23 | from dotenv import load_dotenv 24 | 25 | from dfir_iris_client.helper.docker_helper import DockerHelper 26 | from dfir_iris_client.helper.utils import assert_api_resp, get_data_from_resp, parse_api_data 27 | from dfir_iris_client.session import ClientSession 28 | 29 | API_KEY = os.getenv('IRIS_ADM_API_KEY') 30 | API_URL = os.getenv('IRIS_URL', default="http://127.0.0.1:8000") 31 | COMPOSE_FILE = os.getenv('COMPOSE_FILE', default="../../../iris-web/docker-compose.yml") 32 | 33 | 34 | def new_session(): 35 | """ """ 36 | session = ClientSession(apikey=API_KEY, 37 | host=API_URL, ssl_verify=False) 38 | 39 | return session 40 | 41 | 42 | def new_adm_session(session: ClientSession = None): 43 | """ """ 44 | dot_path = Path(__file__).parent / "resources" / ".env" 45 | if not load_dotenv(dotenv_path=dot_path, override=True): 46 | raise FileNotFoundError(f"File {dot_path} not found") 47 | 48 | docker_compose = None 49 | 50 | if os.getenv('TEST_WITH_DOCKER', default=False): 51 | docker_compose = DockerHelper(docker_compose_path=COMPOSE_FILE) 52 | docker_compose.start() 53 | 54 | while True: 55 | try: 56 | requests.head(API_URL, timeout=500) 57 | break 58 | except ConnectionError: 59 | sleep(1) 60 | pass 61 | 62 | if session is None: 63 | session = ClientSession(apikey=os.getenv('IRIS_ADM_API_KEY', API_KEY), 64 | host=API_URL, ssl_verify=False, timeout=500) 65 | 66 | return session, docker_compose 67 | 68 | 69 | class InitIrisClientTest(unittest.TestCase): 70 | docker_compose = None 71 | session = None 72 | 73 | @classmethod 74 | def setUpClass(cls) -> None: 75 | cls.session, cls.docker_compose = new_adm_session(session=cls.session) 76 | 77 | @classmethod 78 | def tearDownClass(cls) -> None: 79 | if cls.docker_compose is not None: 80 | cls.docker_compose.stop() 81 | 82 | if cls.session._do_trace: 83 | print('Writing traces') 84 | from pathlib import Path 85 | import datetime 86 | import json 87 | 88 | traces_dir = Path(__file__).parent / 'traces' 89 | if not traces_dir.exists(): 90 | traces_dir.mkdir() 91 | trace_file = traces_dir / f'{datetime.datetime.now()}.json' 92 | 93 | with open(trace_file, 'w') as f: 94 | f.write(json.dumps(cls.session._trace, indent=4)) 95 | 96 | print(f'Traces written in {trace_file}') 97 | 98 | @staticmethod 99 | def assertIrisPermissionDenied(method: callable, *args, **kwargs) -> None: 100 | """ 101 | Assert that the method raise an IrisClientException with the message "Permission denied" 102 | 103 | Args: 104 | method: Method to call and assert 105 | 106 | Returns: 107 | None 108 | """ 109 | try: 110 | method(*args, **kwargs) 111 | except Exception as e: 112 | assert "Permission denied" in str(e) 113 | 114 | 115 | def create_standard_user(session, suffix: str = None): 116 | """ 117 | Create a new standard user 118 | """ 119 | login = session.standard_user.login if suffix is None else f"{session.standard_user.login}_{suffix}" 120 | username = session.standard_user.username if suffix is None else f"{session.standard_user.username}_{suffix}" 121 | email = session.standard_user.email if suffix is None else f"{session.standard_user.email}_{suffix}" 122 | 123 | ret = session.adm.add_user(login=login, 124 | name=username, 125 | password=session.standard_user.password, 126 | email=email) 127 | 128 | assert assert_api_resp(ret, soft_fail=False) 129 | 130 | data = get_data_from_resp(ret) 131 | api_key = parse_api_data(data, 'user_api_key') 132 | session.standard_user.api_key = api_key 133 | 134 | return ret 135 | 136 | 137 | def get_standard_user_session(session): 138 | """ 139 | Get a session with standard user 140 | """ 141 | return ClientSession(apikey=session.standard_user.api_key, 142 | host=API_URL, ssl_verify=False) 143 | 144 | 145 | def delete_standard_user_auto(session, suffix: str = None): 146 | """ 147 | Delete user 148 | """ 149 | login = session.standard_user.login if suffix is None else f"{session.standard_user.login}_{suffix}" 150 | ret = session.adm.deactivate_user(login) 151 | assert assert_api_resp(ret, soft_fail=False) 152 | 153 | ret = session.adm.delete_user(login) 154 | assert assert_api_resp(ret, soft_fail=False) 155 | 156 | return ret 157 | 158 | 159 | def create_standard_group(session): 160 | """ 161 | Create a new standard group 162 | """ 163 | return session.adm.add_group(group_name=session.standard_group.name, 164 | group_description=session.standard_group.description, 165 | group_permissions=session.standard_group.permissions) 166 | 167 | 168 | def delete_standard_group(session): 169 | """ 170 | Delete group 171 | """ 172 | 173 | return session.adm.delete_group(session.standard_group.name) 174 | 175 | 176 | def get_random_string(length: int = 10): 177 | """ 178 | Get a random string 179 | """ 180 | import random 181 | import string 182 | 183 | letters = string.ascii_lowercase 184 | return ''.join(random.choice(letters) for _ in range(length)) 185 | -------------------------------------------------------------------------------- /dfir_iris_client/users.py: -------------------------------------------------------------------------------- 1 | # IRIS Client API Source Code 2 | # contact@dfir-iris.org 3 | # 4 | # This program is free software; you can redistribute it and/or 5 | # modify it under the terms of the GNU Lesser General Public 6 | # License as published by the Free Software Foundation; either 7 | # version 3 of the License, or (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | # Lesser General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU Lesser General Public License 15 | # along with this program; if not, write to the Free Software Foundation, 16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | import warnings 18 | from typing import Union 19 | 20 | from deprecated.classic import deprecated 21 | 22 | from dfir_iris_client.helper.utils import ApiResponse 23 | 24 | 25 | class User(object): 26 | """Handles the users type methods""" 27 | def __init__(self, session): 28 | self._s = session 29 | 30 | @deprecated('Use the new user_exists method', version="2.0.0", action="error") 31 | def user_id_exists(self, user_id: int) -> bool: 32 | return self.user_exists(user=user_id) 33 | 34 | @deprecated('Use the new user_exists method', version="2.0.0", action="error") 35 | def username_exists(self, username: str) -> bool: 36 | return self.user_exists(user=username) 37 | 38 | def user_exists(self, user: Union[str, int]) -> bool: 39 | """ 40 | Returns True if the user (login) exists, else false. User ID can also be looked up. 41 | 42 | Args: 43 | user: Login or user ID to lookup 44 | 45 | Returns: 46 | True if exists else false 47 | """ 48 | if isinstance(user, int): 49 | req = self.get_user(user=user) 50 | else: 51 | req = self.lookup_username(username=user) 52 | 53 | return req.is_success() 54 | 55 | def lookup_username(self, username: str) -> ApiResponse: 56 | """ 57 | Returns a user ID corresponding to the username, else None 58 | 59 | Args: 60 | username: Username to lookup 61 | 62 | Returns: 63 | ApiResponse 64 | 65 | """ 66 | return self._s.pi_get(f'manage/users/lookup/login/{username}') 67 | 68 | def get_user(self, user: Union[int, str], **kwargs) -> ApiResponse: 69 | """Return a user data 70 | 71 | Args: 72 | user: User ID or login of the user to get 73 | 74 | Returns: 75 | ApiResponse object 76 | """ 77 | if kwargs.get('user_id') is not None: 78 | warnings.warn("\'user_id\' argument is deprecated, use \'user\' instead", 79 | DeprecationWarning) 80 | user = kwargs.get('user_id') 81 | 82 | if isinstance(user, str): 83 | return self.lookup_username(username=user) 84 | 85 | return self._s.pi_get(f'manage/users/lookup/id/{user}') 86 | 87 | def list_users(self) -> ApiResponse: 88 | """ 89 | Returns a list of the users with a restricted view so it can be called by unprivileged users. 90 | 91 | Args: 92 | 93 | Returns: 94 | ApiResponse object 95 | """ 96 | return self._s.pi_get(f'manage/users/restricted/list') 97 | 98 | 99 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 3aa1efe4cac6083b086913c76de931e1 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_sources/admin.rst.txt: -------------------------------------------------------------------------------- 1 | Administration 2 | =============== 3 | 4 | .. automodule:: dfir_iris_client.admin 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/case.rst.txt: -------------------------------------------------------------------------------- 1 | Case 2 | ===== 3 | 4 | .. automodule:: dfir_iris_client.case 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/client.rst.txt: -------------------------------------------------------------------------------- 1 | Client documentation 2 | ==================== 3 | 4 | .. autoclass:: dfir_iris_client.case.Case 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/customer.rst.txt: -------------------------------------------------------------------------------- 1 | Customers 2 | ========= 3 | 4 | .. automodule:: dfir_iris_client.customer 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/global_search.rst.txt: -------------------------------------------------------------------------------- 1 | Global search 2 | ============== 3 | 4 | .. automodule:: dfir_iris_client.global_search 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. DFIR-IRIS Client documentation master file, created by 2 | sphinx-quickstart on Fri Mar 24 10:14:02 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to IRIS Client's documentation! 7 | ============================================ 8 | 9 | The IRIS Client is a python client that allows users to easily interact with the IRIS API. 10 | Examples are available in the `examples` folder. An `API key `_ is required to use the client. 11 | The API reference is available on the `documentation website `_. 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | session 17 | case 18 | admin 19 | customer 20 | global_search 21 | users 22 | helpers 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /docs/build/html/_sources/session.rst.txt: -------------------------------------------------------------------------------- 1 | Session 2 | ======== 3 | 4 | .. automodule:: dfir_iris_client.session 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_sources/users.rst.txt: -------------------------------------------------------------------------------- 1 | Users 2 | ======= 3 | 4 | .. automodule:: dfir_iris_client.users 5 | :members: -------------------------------------------------------------------------------- /docs/build/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/build/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Base JavaScript utilities for all Sphinx HTML documentation. 6 | * 7 | * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | "use strict"; 12 | 13 | const _ready = (callback) => { 14 | if (document.readyState !== "loading") { 15 | callback(); 16 | } else { 17 | document.addEventListener("DOMContentLoaded", callback); 18 | } 19 | }; 20 | 21 | /** 22 | * highlight a given string on a node by wrapping it in 23 | * span elements with the given class name. 24 | */ 25 | const _highlight = (node, addItems, text, className) => { 26 | if (node.nodeType === Node.TEXT_NODE) { 27 | const val = node.nodeValue; 28 | const parent = node.parentNode; 29 | const pos = val.toLowerCase().indexOf(text); 30 | if ( 31 | pos >= 0 && 32 | !parent.classList.contains(className) && 33 | !parent.classList.contains("nohighlight") 34 | ) { 35 | let span; 36 | 37 | const closestNode = parent.closest("body, svg, foreignObject"); 38 | const isInSVG = closestNode && closestNode.matches("svg"); 39 | if (isInSVG) { 40 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 41 | } else { 42 | span = document.createElement("span"); 43 | span.classList.add(className); 44 | } 45 | 46 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 47 | parent.insertBefore( 48 | span, 49 | parent.insertBefore( 50 | document.createTextNode(val.substr(pos + text.length)), 51 | node.nextSibling 52 | ) 53 | ); 54 | node.nodeValue = val.substr(0, pos); 55 | 56 | if (isInSVG) { 57 | const rect = document.createElementNS( 58 | "http://www.w3.org/2000/svg", 59 | "rect" 60 | ); 61 | const bbox = parent.getBBox(); 62 | rect.x.baseVal.value = bbox.x; 63 | rect.y.baseVal.value = bbox.y; 64 | rect.width.baseVal.value = bbox.width; 65 | rect.height.baseVal.value = bbox.height; 66 | rect.setAttribute("class", className); 67 | addItems.push({ parent: parent, target: rect }); 68 | } 69 | } 70 | } else if (node.matches && !node.matches("button, select, textarea")) { 71 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 72 | } 73 | }; 74 | const _highlightText = (thisNode, text, className) => { 75 | let addItems = []; 76 | _highlight(thisNode, addItems, text, className); 77 | addItems.forEach((obj) => 78 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 79 | ); 80 | }; 81 | 82 | /** 83 | * Small JavaScript module for the documentation. 84 | */ 85 | const Documentation = { 86 | init: () => { 87 | Documentation.highlightSearchWords(); 88 | Documentation.initDomainIndexTable(); 89 | Documentation.initOnKeyListeners(); 90 | }, 91 | 92 | /** 93 | * i18n support 94 | */ 95 | TRANSLATIONS: {}, 96 | PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), 97 | LOCALE: "unknown", 98 | 99 | // gettext and ngettext don't access this so that the functions 100 | // can safely bound to a different name (_ = Documentation.gettext) 101 | gettext: (string) => { 102 | const translated = Documentation.TRANSLATIONS[string]; 103 | switch (typeof translated) { 104 | case "undefined": 105 | return string; // no translation 106 | case "string": 107 | return translated; // translation exists 108 | default: 109 | return translated[0]; // (singular, plural) translation tuple exists 110 | } 111 | }, 112 | 113 | ngettext: (singular, plural, n) => { 114 | const translated = Documentation.TRANSLATIONS[singular]; 115 | if (typeof translated !== "undefined") 116 | return translated[Documentation.PLURAL_EXPR(n)]; 117 | return n === 1 ? singular : plural; 118 | }, 119 | 120 | addTranslations: (catalog) => { 121 | Object.assign(Documentation.TRANSLATIONS, catalog.messages); 122 | Documentation.PLURAL_EXPR = new Function( 123 | "n", 124 | `return (${catalog.plural_expr})` 125 | ); 126 | Documentation.LOCALE = catalog.locale; 127 | }, 128 | 129 | /** 130 | * highlight the search words provided in the url in the text 131 | */ 132 | highlightSearchWords: () => { 133 | const highlight = 134 | new URLSearchParams(window.location.search).get("highlight") || ""; 135 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 136 | if (terms.length === 0) return; // nothing to do 137 | 138 | // There should never be more than one element matching "div.body" 139 | const divBody = document.querySelectorAll("div.body"); 140 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 141 | window.setTimeout(() => { 142 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 143 | }, 10); 144 | 145 | const searchBox = document.getElementById("searchbox"); 146 | if (searchBox === null) return; 147 | searchBox.appendChild( 148 | document 149 | .createRange() 150 | .createContextualFragment( 151 | '" 155 | ) 156 | ); 157 | }, 158 | 159 | /** 160 | * helper function to hide the search marks again 161 | */ 162 | hideSearchWords: () => { 163 | document 164 | .querySelectorAll("#searchbox .highlight-link") 165 | .forEach((el) => el.remove()); 166 | document 167 | .querySelectorAll("span.highlighted") 168 | .forEach((el) => el.classList.remove("highlighted")); 169 | const url = new URL(window.location); 170 | url.searchParams.delete("highlight"); 171 | window.history.replaceState({}, "", url); 172 | }, 173 | 174 | /** 175 | * helper function to focus on search bar 176 | */ 177 | focusSearchBar: () => { 178 | document.querySelectorAll("input[name=q]")[0]?.focus(); 179 | }, 180 | 181 | /** 182 | * Initialise the domain index toggle buttons 183 | */ 184 | initDomainIndexTable: () => { 185 | const toggler = (el) => { 186 | const idNumber = el.id.substr(7); 187 | const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); 188 | if (el.src.substr(-9) === "minus.png") { 189 | el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; 190 | toggledRows.forEach((el) => (el.style.display = "none")); 191 | } else { 192 | el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; 193 | toggledRows.forEach((el) => (el.style.display = "")); 194 | } 195 | }; 196 | 197 | const togglerElements = document.querySelectorAll("img.toggler"); 198 | togglerElements.forEach((el) => 199 | el.addEventListener("click", (event) => toggler(event.currentTarget)) 200 | ); 201 | togglerElements.forEach((el) => (el.style.display = "")); 202 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); 203 | }, 204 | 205 | initOnKeyListeners: () => { 206 | // only install a listener if it is really needed 207 | if ( 208 | !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && 209 | !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS 210 | ) 211 | return; 212 | 213 | const blacklistedElements = new Set([ 214 | "TEXTAREA", 215 | "INPUT", 216 | "SELECT", 217 | "BUTTON", 218 | ]); 219 | document.addEventListener("keydown", (event) => { 220 | if (blacklistedElements.has(document.activeElement.tagName)) return; // bail for input elements 221 | if (event.altKey || event.ctrlKey || event.metaKey) return; // bail with special keys 222 | 223 | if (!event.shiftKey) { 224 | switch (event.key) { 225 | case "ArrowLeft": 226 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 227 | 228 | const prevLink = document.querySelector('link[rel="prev"]'); 229 | if (prevLink && prevLink.href) { 230 | window.location.href = prevLink.href; 231 | event.preventDefault(); 232 | } 233 | break; 234 | case "ArrowRight": 235 | if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; 236 | 237 | const nextLink = document.querySelector('link[rel="next"]'); 238 | if (nextLink && nextLink.href) { 239 | window.location.href = nextLink.href; 240 | event.preventDefault(); 241 | } 242 | break; 243 | case "Escape": 244 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 245 | Documentation.hideSearchWords(); 246 | event.preventDefault(); 247 | } 248 | } 249 | 250 | // some keyboard layouts may need Shift to get / 251 | switch (event.key) { 252 | case "/": 253 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; 254 | Documentation.focusSearchBar(); 255 | event.preventDefault(); 256 | } 257 | }); 258 | }, 259 | }; 260 | 261 | // quick alias for translations 262 | const _ = Documentation.gettext; 263 | 264 | _ready(Documentation.init); 265 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '2.0.4', 4 | LANGUAGE: 'en', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false, 12 | SHOW_SEARCH_SUMMARY: true, 13 | ENABLE_SEARCH_SHORTCUTS: false, 14 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/build/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f8f8f8; } 8 | .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #9C6500 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ 21 | .highlight .gr { color: #E40000 } /* Generic.Error */ 22 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 23 | .highlight .gi { color: #008400 } /* Generic.Inserted */ 24 | .highlight .go { color: #717171 } /* Generic.Output */ 25 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 26 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 27 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 28 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 29 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 30 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 31 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 32 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 33 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 34 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 35 | .highlight .m { color: #666666 } /* Literal.Number */ 36 | .highlight .s { color: #BA2121 } /* Literal.String */ 37 | .highlight .na { color: #687822 } /* Name.Attribute */ 38 | .highlight .nb { color: #008000 } /* Name.Builtin */ 39 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 40 | .highlight .no { color: #880000 } /* Name.Constant */ 41 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 42 | .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ 43 | .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ 44 | .highlight .nf { color: #0000FF } /* Name.Function */ 45 | .highlight .nl { color: #767600 } /* Name.Label */ 46 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 47 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 48 | .highlight .nv { color: #19177C } /* Name.Variable */ 49 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 50 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 51 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 52 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 53 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 54 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 55 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 56 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 57 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 58 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 59 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 60 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 61 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 62 | .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ 63 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 64 | .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ 65 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 66 | .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ 67 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 68 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 69 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 70 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 71 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 72 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 73 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 74 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 75 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sphinx_highlight.js: -------------------------------------------------------------------------------- 1 | /* Highlighting utilities for Sphinx HTML documentation. */ 2 | "use strict"; 3 | 4 | const SPHINX_HIGHLIGHT_ENABLED = true 5 | 6 | /** 7 | * highlight a given string on a node by wrapping it in 8 | * span elements with the given class name. 9 | */ 10 | const _highlight = (node, addItems, text, className) => { 11 | if (node.nodeType === Node.TEXT_NODE) { 12 | const val = node.nodeValue; 13 | const parent = node.parentNode; 14 | const pos = val.toLowerCase().indexOf(text); 15 | if ( 16 | pos >= 0 && 17 | !parent.classList.contains(className) && 18 | !parent.classList.contains("nohighlight") 19 | ) { 20 | let span; 21 | 22 | const closestNode = parent.closest("body, svg, foreignObject"); 23 | const isInSVG = closestNode && closestNode.matches("svg"); 24 | if (isInSVG) { 25 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 26 | } else { 27 | span = document.createElement("span"); 28 | span.classList.add(className); 29 | } 30 | 31 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 32 | parent.insertBefore( 33 | span, 34 | parent.insertBefore( 35 | document.createTextNode(val.substr(pos + text.length)), 36 | node.nextSibling 37 | ) 38 | ); 39 | node.nodeValue = val.substr(0, pos); 40 | 41 | if (isInSVG) { 42 | const rect = document.createElementNS( 43 | "http://www.w3.org/2000/svg", 44 | "rect" 45 | ); 46 | const bbox = parent.getBBox(); 47 | rect.x.baseVal.value = bbox.x; 48 | rect.y.baseVal.value = bbox.y; 49 | rect.width.baseVal.value = bbox.width; 50 | rect.height.baseVal.value = bbox.height; 51 | rect.setAttribute("class", className); 52 | addItems.push({ parent: parent, target: rect }); 53 | } 54 | } 55 | } else if (node.matches && !node.matches("button, select, textarea")) { 56 | node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); 57 | } 58 | }; 59 | const _highlightText = (thisNode, text, className) => { 60 | let addItems = []; 61 | _highlight(thisNode, addItems, text, className); 62 | addItems.forEach((obj) => 63 | obj.parent.insertAdjacentElement("beforebegin", obj.target) 64 | ); 65 | }; 66 | 67 | /** 68 | * Small JavaScript module for the documentation. 69 | */ 70 | const SphinxHighlight = { 71 | 72 | /** 73 | * highlight the search words provided in localstorage in the text 74 | */ 75 | highlightSearchWords: () => { 76 | if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight 77 | 78 | // get and clear terms from localstorage 79 | const url = new URL(window.location); 80 | const highlight = 81 | localStorage.getItem("sphinx_highlight_terms") 82 | || url.searchParams.get("highlight") 83 | || ""; 84 | localStorage.removeItem("sphinx_highlight_terms") 85 | url.searchParams.delete("highlight"); 86 | window.history.replaceState({}, "", url); 87 | 88 | // get individual terms from highlight string 89 | const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); 90 | if (terms.length === 0) return; // nothing to do 91 | 92 | // There should never be more than one element matching "div.body" 93 | const divBody = document.querySelectorAll("div.body"); 94 | const body = divBody.length ? divBody[0] : document.querySelector("body"); 95 | window.setTimeout(() => { 96 | terms.forEach((term) => _highlightText(body, term, "highlighted")); 97 | }, 10); 98 | 99 | const searchBox = document.getElementById("searchbox"); 100 | if (searchBox === null) return; 101 | searchBox.appendChild( 102 | document 103 | .createRange() 104 | .createContextualFragment( 105 | '" 109 | ) 110 | ); 111 | }, 112 | 113 | /** 114 | * helper function to hide the search marks again 115 | */ 116 | hideSearchWords: () => { 117 | document 118 | .querySelectorAll("#searchbox .highlight-link") 119 | .forEach((el) => el.remove()); 120 | document 121 | .querySelectorAll("span.highlighted") 122 | .forEach((el) => el.classList.remove("highlighted")); 123 | localStorage.removeItem("sphinx_highlight_terms") 124 | }, 125 | 126 | initEscapeListener: () => { 127 | // only install a listener if it is really needed 128 | if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; 129 | 130 | document.addEventListener("keydown", (event) => { 131 | // bail for input elements 132 | if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; 133 | // bail with special keys 134 | if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; 135 | if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { 136 | SphinxHighlight.hideSearchWords(); 137 | event.preventDefault(); 138 | } 139 | }); 140 | }, 141 | }; 142 | 143 | _ready(SphinxHighlight.highlightSearchWords); 144 | _ready(SphinxHighlight.initEscapeListener); 145 | -------------------------------------------------------------------------------- /docs/build/html/customer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Customers — DFIR-IRIS Client 2.0.4 documentation 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 60 | 61 |
65 | 66 |
67 |
68 |
69 | 76 |
77 |
78 |
79 |
80 | 81 |
82 |

Customers

83 |
84 |
85 | class dfir_iris_client.customer.Customer(session)
86 |

Handles the customer methods

87 |
88 |
89 | get_customer_by_id(customer_id: int) ApiResponse
90 |

Returns a customer from its ID

91 |
92 |
Parameters:
93 |

customer_id – Customer ID to look up

94 |
95 |
Returns:
96 |

ApiResponse object

97 |
98 |
99 |
100 | 101 |
102 |
103 | list_customers() ApiResponse
104 |

Returns a list of the available customers

105 |
106 |
Returns:
107 |

ApiResponse object

108 |
109 |
110 |

Args:

111 |
112 |
Returns:
113 |

ApiResponse object

114 |
115 |
116 |
117 | 118 |
119 |
120 | lookup_customer(customer_name) ApiResponse
121 |

Returns a customer ID if customer name is found. Customer names are unique in the database. 122 | Customer ID is in the data section of the API response aka id = parse_api_data(resp.get_data(), ‘customer_id’)

123 |
124 |
Parameters:
125 |

customer_name – Name of the customer to lookup

126 |
127 |
Returns:
128 |

ApiResponse object

129 |
130 |
131 |
132 | 133 |
134 | 135 |
136 | 137 | 138 |
139 |
140 |
144 | 145 |
146 | 147 |
148 |

© Copyright 2024, DFIR-IRIS Team.

149 |
150 | 151 | Built with Sphinx using a 152 | theme 153 | provided by Read the Docs. 154 | 155 | 156 |
157 |
158 |
159 |
160 |
161 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /docs/build/html/global_search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Global search — DFIR-IRIS Client 2.0.4 documentation 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 60 | 61 |
65 | 66 |
67 |
68 |
69 | 76 |
77 |
78 |
79 |
80 | 81 | 118 | 119 | 120 |
121 |
122 |
126 | 127 |
128 | 129 |
130 |

© Copyright 2024, DFIR-IRIS Team.

131 |
132 | 133 | Built with Sphinx using a 134 | theme 135 | provided by Read the Docs. 136 | 137 | 138 |
139 |
140 |
141 |
142 |
143 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Welcome to IRIS Client’s documentation! — DFIR-IRIS Client 2.0.4 documentation 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 59 | 60 |
64 | 65 |
66 |
67 |
68 |
    69 |
  • 70 | 71 |
  • 72 | View page source 73 |
  • 74 |
75 |
76 |
77 |
78 |
79 | 80 |
81 |

Welcome to IRIS Client’s documentation!

82 |

The IRIS Client is a python client that allows users to easily interact with the IRIS API. 83 | Examples are available in the examples folder. An API key is required to use the client. 84 | The API reference is available on the documentation website.

85 |
86 | 95 |
96 |
97 |
98 |

Indices and tables

99 | 104 |
105 | 106 | 107 |
108 |
109 |
112 | 113 |
114 | 115 |
116 |

© Copyright 2024, DFIR-IRIS Team.

117 |
118 | 119 | Built with Sphinx using a 120 | theme 121 | provided by Read the Docs. 122 | 123 | 124 |
125 |
126 |
127 |
128 |
129 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfir-iris/iris-client/769090fb4046db0c8301a0ae0f0ed081907fc99d/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Python Module Index — DFIR-IRIS Client 2.0.4 documentation 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 60 | 61 |
65 | 66 |
67 |
68 |
69 |
    70 |
  • 71 | 72 |
  • 73 |
  • 74 |
75 |
76 |
77 |
78 |
79 | 80 | 81 |

Python Module Index

82 | 83 |
84 | d 85 |
86 | 87 | 88 | 89 | 91 | 92 | 94 | 97 | 98 | 99 | 102 | 103 | 104 | 107 | 108 | 109 | 112 | 113 | 114 | 117 | 118 | 119 | 122 | 123 | 124 | 127 | 128 | 129 | 132 | 133 | 134 | 137 | 138 | 139 | 142 | 143 | 144 | 147 | 148 | 149 | 152 | 153 | 154 | 157 | 158 | 159 | 162 | 163 | 164 | 167 | 168 | 169 | 172 | 173 | 174 | 177 | 178 | 179 | 182 | 183 | 184 | 187 | 188 | 189 | 192 |
 
90 | d
95 | dfir_iris_client 96 |
    100 | dfir_iris_client.admin 101 |
    105 | dfir_iris_client.case 106 |
    110 | dfir_iris_client.customer 111 |
    115 | dfir_iris_client.global_search 116 |
    120 | dfir_iris_client.helper.analysis_status 121 |
    125 | dfir_iris_client.helper.assets_type 126 |
    130 | dfir_iris_client.helper.authorization 131 |
    135 | dfir_iris_client.helper.case_classifications 136 |
    140 | dfir_iris_client.helper.colors 141 |
    145 | dfir_iris_client.helper.compromise_status 146 |
    150 | dfir_iris_client.helper.errors 151 |
    155 | dfir_iris_client.helper.events_categories 156 |
    160 | dfir_iris_client.helper.ioc_types 161 |
    165 | dfir_iris_client.helper.report_template_types 166 |
    170 | dfir_iris_client.helper.task_status 171 |
    175 | dfir_iris_client.helper.tlps 176 |
    180 | dfir_iris_client.helper.utils 181 |
    185 | dfir_iris_client.session 186 |
    190 | dfir_iris_client.users 191 |
193 | 194 | 195 |
196 |
197 |
198 | 199 |
200 | 201 |
202 |

© Copyright 2024, DFIR-IRIS Team.

203 |
204 | 205 | Built with Sphinx using a 206 | theme 207 | provided by Read the Docs. 208 | 209 | 210 |
211 |
212 |
213 |
214 |
215 | 220 | 221 | 222 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Search — DFIR-IRIS Client 2.0.4 documentation 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 60 | 61 |
65 | 66 |
67 |
68 |
69 |
    70 |
  • 71 | 72 |
  • 73 |
  • 74 |
75 |
76 |
77 |
78 |
79 | 80 | 87 | 88 | 89 |
90 | 91 |
92 | 93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 |

© Copyright 2024, DFIR-IRIS Team.

101 |
102 | 103 | Built with Sphinx using a 104 | theme 105 | provided by Read the Docs. 106 | 107 | 108 |
109 |
110 |
111 |
112 |
113 | 118 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | packaging 3 | deprecated -------------------------------------------------------------------------------- /docs/source/admin.rst: -------------------------------------------------------------------------------- 1 | Administration 2 | =============== 3 | 4 | .. automodule:: dfir_iris_client.admin 5 | :members: -------------------------------------------------------------------------------- /docs/source/case.rst: -------------------------------------------------------------------------------- 1 | Case 2 | ===== 3 | 4 | .. automodule:: dfir_iris_client.case 5 | :members: -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | 9 | from pathlib import Path 10 | import sys 11 | 12 | parent = Path(__file__).parents[2] 13 | 14 | print(parent) 15 | 16 | sys.path.insert(0, str(parent)) 17 | 18 | project = 'DFIR-IRIS Client' 19 | copyright = '2024, DFIR-IRIS Team' 20 | author = 'DFIR-IRIS Team' 21 | release = '2.0.4' 22 | 23 | # -- General configuration --------------------------------------------------- 24 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 25 | 26 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.napoleon'] 27 | 28 | templates_path = ['_templates'] 29 | exclude_patterns = [] 30 | 31 | # -- Options for HTML output ------------------------------------------------- 32 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 33 | 34 | html_static_path = ['_static'] 35 | 36 | html_theme = 'sphinx_rtd_theme' 37 | -------------------------------------------------------------------------------- /docs/source/customer.rst: -------------------------------------------------------------------------------- 1 | Customers 2 | ========= 3 | 4 | .. automodule:: dfir_iris_client.customer 5 | :members: -------------------------------------------------------------------------------- /docs/source/global_search.rst: -------------------------------------------------------------------------------- 1 | Global search 2 | ============== 3 | 4 | .. automodule:: dfir_iris_client.global_search 5 | :members: -------------------------------------------------------------------------------- /docs/source/helpers.rst: -------------------------------------------------------------------------------- 1 | Helpers 2 | ======== 3 | 4 | .. automodule:: dfir_iris_client.helper.analysis_status 5 | :members: 6 | 7 | .. automodule:: dfir_iris_client.helper.assets_type 8 | :members: 9 | 10 | .. automodule:: dfir_iris_client.helper.authorization 11 | :members: 12 | 13 | .. automodule:: dfir_iris_client.helper.case_classifications 14 | :members: 15 | 16 | .. automodule:: dfir_iris_client.helper.colors 17 | :members: 18 | 19 | .. automodule:: dfir_iris_client.helper.compromise_status 20 | :members: 21 | 22 | .. automodule:: dfir_iris_client.helper.errors 23 | :members: 24 | 25 | .. automodule:: dfir_iris_client.helper.events_categories 26 | :members: 27 | 28 | .. automodule:: dfir_iris_client.helper.ioc_types 29 | :members: 30 | 31 | .. automodule:: dfir_iris_client.helper.report_template_types 32 | :members: 33 | 34 | .. automodule:: dfir_iris_client.helper.task_status 35 | :members: 36 | 37 | .. automodule:: dfir_iris_client.helper.tlps 38 | :members: 39 | 40 | .. automodule:: dfir_iris_client.helper.utils 41 | :members: -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. DFIR-IRIS Client documentation master file, created by 2 | sphinx-quickstart on Fri Mar 24 10:14:02 2023. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to IRIS Client's documentation! 7 | ============================================ 8 | 9 | The IRIS Client is a python client that allows users to easily interact with the IRIS API. 10 | Examples are available in the `examples` folder. An `API key `_ is required to use the client. 11 | The API reference is available on the `documentation website `_. 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | session 17 | case 18 | admin 19 | customer 20 | global_search 21 | users 22 | helpers 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`genindex` 28 | * :ref:`modindex` 29 | * :ref:`search` 30 | -------------------------------------------------------------------------------- /docs/source/session.rst: -------------------------------------------------------------------------------- 1 | Session 2 | ======== 3 | 4 | .. automodule:: dfir_iris_client.session 5 | :members: -------------------------------------------------------------------------------- /docs/source/users.rst: -------------------------------------------------------------------------------- 1 | Users 2 | ======= 3 | 4 | .. automodule:: dfir_iris_client.users 5 | :members: -------------------------------------------------------------------------------- /examples/create_new_managed_case.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dfir_iris_client.case import Case 4 | from dfir_iris_client.session import ClientSession 5 | from dfir_iris_client.helper.utils import parse_api_data, get_data_from_resp, assert_api_resp 6 | 7 | import logging 8 | import os 9 | 10 | LOG_FORMAT = '%(asctime)s %(levelname)s %(funcName)s: %(message)s' 11 | 12 | logging.basicConfig(format=LOG_FORMAT, level='INFO', datefmt='%Y-%m-%d %I:%M:%S') 13 | log = logging.getLogger(__name__) 14 | 15 | # Initiate a session with our API key and host. Session stays the same during all the script run. 16 | session = ClientSession(apikey=os.environ.get('IRIS_API_KEY'), 17 | host='http://127.0.0.1:8000', ssl_verify=False) 18 | 19 | # Initialize the case instance with the session 20 | case = Case(session=session) 21 | 22 | # Create a new case. The create_customer creates the customer if it doesn't exist, otherwise the method 23 | # would turn an error. This implies the calling user has administrative role. 24 | status = case.add_case(case_name='A new case', 25 | case_description='Short initial description, or really long ' 26 | 'description. It\'s up to you', 27 | case_customer='IrisClientApiDemo', 28 | case_classification='other:other', 29 | soc_id='soc_11', 30 | create_customer=True) 31 | 32 | # Always check the status as most of the methods do not raise exceptions. Setting soft_fail = False tells the client 33 | # to raise an exception if the request fails 34 | assert_api_resp(status, soft_fail=False) 35 | 36 | # All the methods are simply overlays of the API itself, so to know exactly what a method answers, one can either try 37 | # it or head to the API reference documentation and lookup the corresponding endpoint. 38 | # The case ID is returned by the server in case of success. We need this case ID for the next steps 39 | # Status are ApiResponse objects, and contains answers from the server. 40 | # While the ID could be retrieved with status.get_data().get('case_id'), it is preferable to use 41 | # the overlays get_data_from_resp and parse_api_data to be future proof, in case response from server are changed. 42 | case_data = get_data_from_resp(status) 43 | case_id = parse_api_data(case_data, 'case_id') 44 | 45 | log.info(f'Created case ID {case_id}') 46 | 47 | # Set the case instance with the new case ID. From now on, every action done with a method of the case instance 48 | # will be done under this case ID, except if the CID is explicitly provided on the method itself. 49 | # This can be used to directly modify existing cases etc. 50 | case.set_cid(case_id) 51 | 52 | 53 | # Let's add an IOC to our case 54 | # As in the GUI, not all attributes are mandatory. For instance here we have omitted everything not mandatory 55 | # Most of the methods auto resolve the types names. Here we set an IOC as AS directly, without specifying which ID is 56 | # the IOC AS type 57 | status_ioc = case.add_ioc(value='API IOC AS', ioc_type='AS') 58 | 59 | # We keep the ioc ID so we can add it to an asset later 60 | ioc_data = get_data_from_resp(status_ioc) 61 | ioc_id = parse_api_data(ioc_data, 'ioc_id') 62 | 63 | log.info(f'Created IOC ID {ioc_id}. Server returned {status_ioc}') 64 | 65 | # Let's add an asset and associate the ioc with an update 66 | status_asset = case.add_asset(name='API asset', asset_type='Windows - Computer', 67 | description='A comprehensive description', compromise_status=1, 68 | analysis_status='Started') 69 | assert_api_resp(status_asset, soft_fail=False) 70 | 71 | # We keep the asset ID so we can update it 72 | asset_data = get_data_from_resp(status_asset) 73 | asset_id = parse_api_data(asset_data, 'asset_id') 74 | 75 | log.info(f'Created asset ID {asset_id}') 76 | 77 | # Update the asset with the new ioc. By letting all fields empty except ioc_links, we only update this field. 78 | status_asset = case.update_asset(asset_id=asset_id, ioc_links=[ioc_id]) 79 | assert_api_resp(status, soft_fail=False) 80 | 81 | log.info(f'Asset updated. Data : {status_asset.as_json()}') 82 | 83 | # Add some notes directories 84 | status_dir1 = case.add_notes_directory('API Directory 1') 85 | assert_api_resp(status_dir1, soft_fail=False) 86 | 87 | log.info(f'Created API directory 1 notes directory') 88 | 89 | 90 | status_dir2 = case.add_notes_directory('API Directory 2') 91 | assert_api_resp(status_dir2, soft_fail=False) 92 | 93 | log.info(f'Created API directory 2 notes directory') 94 | 95 | 96 | status_dir3 = case.add_notes_directory('API Directory 3') 97 | assert_api_resp(status_dir3, soft_fail=False) 98 | 99 | log.info(f'Created API directory 3 notes group') 100 | 101 | # Get the group_id of Group 2 and add some notes 102 | dir_2_data = get_data_from_resp(status_dir2) 103 | dir_2_id = parse_api_data(data=dir_2_data, path='id') 104 | 105 | status_note = case.add_note(note_title='API note 1 for directory 2', 106 | note_content='Anything you want really', 107 | directory_id=dir_2_id) 108 | assert_api_resp(status_note, soft_fail=False) 109 | log.info(f'Created note API note 1 for group 2') 110 | 111 | 112 | status_note = case.add_note(note_title='API note 2 for directory 2', 113 | note_content='Anything you want really', 114 | directory_id=dir_2_id) 115 | assert_api_resp(status_note, soft_fail=False) 116 | log.info(f'Created note API note 2 for group 2') 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /examples/ex_helper.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from random import randrange 3 | 4 | 5 | def random_date(): 6 | d1 = datetime.datetime.strptime('1/1/2008 1:30 PM', '%m/%d/%Y %I:%M %p') 7 | d2 = datetime.datetime.strptime('1/1/2022 4:50 AM', '%m/%d/%Y %I:%M %p') 8 | delta = d2 - d1 9 | int_delta = (delta.days * 24 * 60 * 60) + delta.seconds 10 | random_second = randrange(int_delta) 11 | return d1 + datetime.timedelta(seconds=random_second) -------------------------------------------------------------------------------- /examples/update_existing_case.py: -------------------------------------------------------------------------------- 1 | from dfir_iris_client.case import Case 2 | from dfir_iris_client.session import ClientSession 3 | from dfir_iris_client.helper.utils import parse_api_data, get_data_from_resp, assert_api_resp 4 | 5 | from ex_helper import random_date 6 | 7 | import logging 8 | import os 9 | 10 | LOG_FORMAT = '%(asctime)s %(levelname)s %(funcName)s: %(message)s' 11 | 12 | logging.basicConfig(format=LOG_FORMAT, level='INFO', datefmt='%Y-%m-%d %I:%M:%S') 13 | log = logging.getLogger(__name__) 14 | 15 | # Initiate a session with our API key and host. Session stays the same during all the script run. 16 | session = ClientSession(apikey=os.environ.get('IRIS_API_KEY'), 17 | host='http://127.0.0.1:8000', ssl_verify=False) 18 | 19 | # Initialize the case instance with the session 20 | case = Case(session=session) 21 | 22 | # Fetch the case from its ID. Let's use the initial demo case and improve it 23 | if not case.case_id_exists(cid=1): 24 | # This should never happen, the server refuses to delete this case for consistency 25 | raise Exception('Case ID 1 not found !') 26 | 27 | # Attribute the cid to the case instance 28 | case.set_cid(cid=1) 29 | 30 | # We can now modify the case. Let's add some tasks. 31 | # Again task status and assignee are attribute being looked up before issuing the task addition request. 32 | # For efficiency and avoid unnecessary requests, the IDs can also be provided if they are known. 33 | status = case.add_task(title='Analysis of laptop X', 34 | description='Plaso the laptop', 35 | assignees=['administrator'], 36 | status='To do') 37 | 38 | assert_api_resp(status, soft_fail=False) 39 | log.info(f'Created new task : {status.as_json()}') 40 | 41 | status = case.add_task(title='Analysis of server Y', 42 | assignees=['administrator'], 43 | status='In progress') 44 | 45 | assert_api_resp(status, soft_fail=False) 46 | log.info(f'Created new task : {status.as_json()}') 47 | 48 | # Next add some events if the timeline is empty 49 | status = case.list_events() 50 | assert_api_resp(status, soft_fail=False) 51 | 52 | timeline = parse_api_data(status.get_data(), 'timeline') 53 | if len(timeline) < 85: 54 | for i in range(0, 150): 55 | # Only title and datetime are required, but here we play with the summary and graphs too 56 | status = case.add_event(f'Event {i}', 57 | date_time=random_date(), 58 | display_in_summary=(i % 2 == 0), 59 | display_in_graph=(i % 3 == 0)) 60 | 61 | # Status object can be check with a boolean expression directly 62 | if not status: 63 | status.log_error() 64 | continue 65 | log.info(f'Added event {i}') 66 | 67 | 68 | status = case.add_task_log("Started analysis of computer X") 69 | if not status: 70 | status.log_error() -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "dfir-iris-client" 7 | authors = [{name = "DFIR-IRIS", email = "contact@dfir-iris.org"}] 8 | dynamic = ["version", "description", "readme", "classifiers", "dependencies"] -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | packaging 3 | deprecated -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # IRIS Source Code 4 | # IRIS Client API Source Code 5 | # contact@dfir-iris.org 6 | # 7 | # This program is free software; you can redistribute it and/or 8 | # modify it under the terms of the GNU Lesser General Public 9 | # License as published by the Free Software Foundation; either 10 | # version 3 of the License, or (at your option) any later version. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | # Lesser General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Lesser General Public License 18 | # along with this program; if not, write to the Free Software Foundation, 19 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 20 | import pathlib 21 | 22 | import setuptools 23 | 24 | # The directory containing this file 25 | CURR_DIR = pathlib.Path(__file__).parent 26 | 27 | # The text of the README file 28 | README = (CURR_DIR / "README.md").read_text() 29 | 30 | setuptools.setup( 31 | name='dfir_iris_client', 32 | version='2.0.4', 33 | packages=['dfir_iris_client', 'dfir_iris_client.helper', 'dfir_iris_client.tests'], 34 | author="DFIR-IRIS", 35 | author_email="contact@dfir-iris.org", 36 | description="Client for DFIR-IRIS API", 37 | long_description=README, 38 | long_description_content_type="text/markdown", 39 | url="https://github.com/dfir-iris/dfir-iris-client", 40 | classifiers=[ 41 | "Programming Language :: Python :: 3", 42 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", 43 | "Operating System :: OS Independent", 44 | ], 45 | install_requires=[ 46 | 'requests', 47 | 'packaging', 48 | 'deprecated' 49 | ] 50 | ) 51 | --------------------------------------------------------------------------------