intelmq-extensions 1.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. intelmq_extensions/__init__.py +0 -0
  2. intelmq_extensions/bots/__init__.py +0 -0
  3. intelmq_extensions/bots/collectors/blackkite/__init__.py +0 -0
  4. intelmq_extensions/bots/collectors/blackkite/_client.py +167 -0
  5. intelmq_extensions/bots/collectors/blackkite/collector.py +182 -0
  6. intelmq_extensions/bots/collectors/disp/__init__.py +0 -0
  7. intelmq_extensions/bots/collectors/disp/_client.py +121 -0
  8. intelmq_extensions/bots/collectors/disp/collector.py +104 -0
  9. intelmq_extensions/bots/collectors/xmpp/__init__.py +0 -0
  10. intelmq_extensions/bots/collectors/xmpp/collector.py +210 -0
  11. intelmq_extensions/bots/experts/__init__.py +0 -0
  12. intelmq_extensions/bots/experts/certat_contact_intern/__init__.py +0 -0
  13. intelmq_extensions/bots/experts/certat_contact_intern/expert.py +139 -0
  14. intelmq_extensions/bots/experts/copy_extra/__init__.py +0 -0
  15. intelmq_extensions/bots/experts/copy_extra/expert.py +27 -0
  16. intelmq_extensions/bots/experts/event_group_splitter/__init__.py +0 -0
  17. intelmq_extensions/bots/experts/event_group_splitter/expert.py +117 -0
  18. intelmq_extensions/bots/experts/event_splitter/__init__.py +0 -0
  19. intelmq_extensions/bots/experts/event_splitter/expert.py +41 -0
  20. intelmq_extensions/bots/experts/squelcher/__init__.py +0 -0
  21. intelmq_extensions/bots/experts/squelcher/expert.py +316 -0
  22. intelmq_extensions/bots/experts/vulnerability_lookup/__init__.py +0 -0
  23. intelmq_extensions/bots/experts/vulnerability_lookup/expert.py +136 -0
  24. intelmq_extensions/bots/outputs/__init__.py +0 -0
  25. intelmq_extensions/bots/outputs/mattermost/__init__.py +0 -0
  26. intelmq_extensions/bots/outputs/mattermost/output.py +113 -0
  27. intelmq_extensions/bots/outputs/to_logs/__init__.py +0 -0
  28. intelmq_extensions/bots/outputs/to_logs/output.py +12 -0
  29. intelmq_extensions/bots/outputs/xmpp/__init__.py +0 -0
  30. intelmq_extensions/bots/outputs/xmpp/output.py +180 -0
  31. intelmq_extensions/bots/parsers/__init__.py +0 -0
  32. intelmq_extensions/bots/parsers/blackkite/__init__.py +0 -0
  33. intelmq_extensions/bots/parsers/blackkite/_transformers.py +202 -0
  34. intelmq_extensions/bots/parsers/blackkite/parser.py +65 -0
  35. intelmq_extensions/bots/parsers/disp/__init__.py +0 -0
  36. intelmq_extensions/bots/parsers/disp/parser.py +125 -0
  37. intelmq_extensions/bots/parsers/malwaredomains/__init__.py +0 -0
  38. intelmq_extensions/bots/parsers/malwaredomains/parser.py +63 -0
  39. intelmq_extensions/cli/__init__.py +0 -0
  40. intelmq_extensions/cli/create_reports.py +161 -0
  41. intelmq_extensions/cli/intelmqcli.py +657 -0
  42. intelmq_extensions/cli/lib.py +670 -0
  43. intelmq_extensions/cli/utils.py +12 -0
  44. intelmq_extensions/etc/harmonization.conf +434 -0
  45. intelmq_extensions/etc/squelcher.conf +52 -0
  46. intelmq_extensions/lib/__init__.py +0 -0
  47. intelmq_extensions/lib/api_helpers.py +105 -0
  48. intelmq_extensions/lib/blackkite.py +29 -0
  49. intelmq_extensions/tests/__init__.py +0 -0
  50. intelmq_extensions/tests/base.py +336 -0
  51. intelmq_extensions/tests/bots/__init__.py +0 -0
  52. intelmq_extensions/tests/bots/collectors/__init__.py +0 -0
  53. intelmq_extensions/tests/bots/collectors/blackkite/__init__.py +0 -0
  54. intelmq_extensions/tests/bots/collectors/blackkite/base.py +45 -0
  55. intelmq_extensions/tests/bots/collectors/blackkite/test_client.py +154 -0
  56. intelmq_extensions/tests/bots/collectors/blackkite/test_collector.py +287 -0
  57. intelmq_extensions/tests/bots/collectors/disp/__init__.py +0 -0
  58. intelmq_extensions/tests/bots/collectors/disp/base.py +147 -0
  59. intelmq_extensions/tests/bots/collectors/disp/test_client.py +134 -0
  60. intelmq_extensions/tests/bots/collectors/disp/test_collector.py +137 -0
  61. intelmq_extensions/tests/bots/collectors/xmpp/__init__.py +0 -0
  62. intelmq_extensions/tests/bots/collectors/xmpp/test_collector.py +10 -0
  63. intelmq_extensions/tests/bots/experts/__init__.py +0 -0
  64. intelmq_extensions/tests/bots/experts/certat_contact_intern/__init__.py +0 -0
  65. intelmq_extensions/tests/bots/experts/certat_contact_intern/test_expert.py +176 -0
  66. intelmq_extensions/tests/bots/experts/copy_extra/__init__.py +0 -0
  67. intelmq_extensions/tests/bots/experts/copy_extra/test_expert.py +42 -0
  68. intelmq_extensions/tests/bots/experts/event_group_splitter/__init__.py +0 -0
  69. intelmq_extensions/tests/bots/experts/event_group_splitter/test_expert.py +302 -0
  70. intelmq_extensions/tests/bots/experts/event_splitter/__init__.py +0 -0
  71. intelmq_extensions/tests/bots/experts/event_splitter/test_expert.py +101 -0
  72. intelmq_extensions/tests/bots/experts/squelcher/__init__.py +0 -0
  73. intelmq_extensions/tests/bots/experts/squelcher/test_expert.py +548 -0
  74. intelmq_extensions/tests/bots/experts/vulnerability_lookup/__init__.py +0 -0
  75. intelmq_extensions/tests/bots/experts/vulnerability_lookup/test_expert.py +203 -0
  76. intelmq_extensions/tests/bots/outputs/__init__.py +0 -0
  77. intelmq_extensions/tests/bots/outputs/mattermost/__init__.py +0 -0
  78. intelmq_extensions/tests/bots/outputs/mattermost/test_output.py +138 -0
  79. intelmq_extensions/tests/bots/outputs/xmpp/__init__.py +0 -0
  80. intelmq_extensions/tests/bots/outputs/xmpp/test_output.py +10 -0
  81. intelmq_extensions/tests/bots/parsers/__init__.py +0 -0
  82. intelmq_extensions/tests/bots/parsers/blackkite/__init__.py +0 -0
  83. intelmq_extensions/tests/bots/parsers/blackkite/data.py +69 -0
  84. intelmq_extensions/tests/bots/parsers/blackkite/test_parser.py +197 -0
  85. intelmq_extensions/tests/bots/parsers/disp/__init__.py +0 -0
  86. intelmq_extensions/tests/bots/parsers/disp/test_parser.py +282 -0
  87. intelmq_extensions/tests/bots/parsers/malwaredomains/__init__.py +0 -0
  88. intelmq_extensions/tests/bots/parsers/malwaredomains/test_parser.py +62 -0
  89. intelmq_extensions/tests/cli/__init__.py +0 -0
  90. intelmq_extensions/tests/cli/test_create_reports.py +97 -0
  91. intelmq_extensions/tests/cli/test_intelmqcli.py +158 -0
  92. intelmq_extensions/tests/lib/__init__.py +0 -0
  93. intelmq_extensions/tests/lib/base.py +81 -0
  94. intelmq_extensions/tests/lib/test_api_helpers.py +126 -0
  95. intelmq_extensions-1.8.1.dist-info/METADATA +60 -0
  96. intelmq_extensions-1.8.1.dist-info/RECORD +100 -0
  97. intelmq_extensions-1.8.1.dist-info/WHEEL +5 -0
  98. intelmq_extensions-1.8.1.dist-info/entry_points.txt +33 -0
  99. intelmq_extensions-1.8.1.dist-info/licenses/LICENSE +661 -0
  100. intelmq_extensions-1.8.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,287 @@
1
+ """Tests for BlackKite Collector
2
+
3
+ SPDX-FileCopyrightText: 2023 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ import json
8
+ import unittest
9
+ from unittest import mock
10
+
11
+ import intelmq.lib.message as message
12
+ import requests
13
+
14
+ from intelmq_extensions.bots.collectors.blackkite.collector import BlackKiteCollectorBot
15
+
16
+ from ....base import BotTestCase
17
+ from .base import BlackKite_APIMockMixIn
18
+
19
+
20
+ class TestBlackKiteCollectorBot(BotTestCase, BlackKite_APIMockMixIn, unittest.TestCase):
21
+ @classmethod
22
+ def set_bot(cls):
23
+ cls.bot_reference = BlackKiteCollectorBot
24
+ cls.sysconfig = {
25
+ "url": "https://blackkite.example.at/v1",
26
+ "client_id": "client1",
27
+ "client_secret": "secret1",
28
+ "code": "feed-code",
29
+ "categories": {"PATCH": {}},
30
+ }
31
+
32
+ def catch_http_session(self):
33
+ session = requests.Session()
34
+ session_mock = mock.patch(
35
+ "intelmq_extensions.bots.collectors.blackkite.collector.create_request_session",
36
+ return_value=session,
37
+ )
38
+ session_mock.start()
39
+ self.addCleanup(session_mock.stop)
40
+ return session
41
+
42
+ def setUp(self) -> None:
43
+ super().setUp()
44
+ session = self.catch_http_session()
45
+ self.setup_api_mock(session=session)
46
+
47
+ def test_static_check_validates_filters(self):
48
+ correct_params = {
49
+ "severities": ["Info", "Low", "Medium", "High", "Critical"],
50
+ "statuses": [
51
+ "Active",
52
+ "FalsePositive",
53
+ "Suppressed",
54
+ "Acknowledged",
55
+ "Deleted",
56
+ ],
57
+ "outputs": ["Info", "Passed", "Warning", "Failed"],
58
+ "categories": {
59
+ "PATCH": {
60
+ "statuses": ["Active"],
61
+ "severities": ["Critical"],
62
+ "include": ["PATCH-001"],
63
+ },
64
+ "APPSEC": {},
65
+ "DNS": {"outputs": ["Failed"]},
66
+ "LEAK": None,
67
+ },
68
+ }
69
+ self.assertIsNone(self.bot_reference.check(correct_params))
70
+
71
+ def test_static_check_invalid_params(self):
72
+ invalid_params = {
73
+ "severities": ["Some", "Other"],
74
+ "statuses": ["NonActive"],
75
+ "outputs": ["Other"],
76
+ "categories": {
77
+ "PATCH": {
78
+ "include": ["PATCH-001"],
79
+ "exclude": ["PATCH-002"],
80
+ },
81
+ "DNS": {
82
+ "statuses": ["NonActive"],
83
+ },
84
+ "FRADOM": {
85
+ "outputs": ["Failed"],
86
+ },
87
+ "non-existing": {},
88
+ "SMTP": {"not-existing-key": []},
89
+ },
90
+ }
91
+ results = self.bot_reference.check(invalid_params)
92
+ self.assertEqual(8, len([r for r in results if r[0] == "error"]))
93
+
94
+ def _create_report_dict(self, company_data: dict, finding_data: dict, **kwargs):
95
+ report = message.Report(kwargs, harmonization=self.harmonization)
96
+ data = {"company": company_data, "finding": finding_data}
97
+ report.add("feed.name", "Test Bot")
98
+ report.add("feed.accuracy", 100.0)
99
+ report.add("feed.code", "feed-code")
100
+ report.add("raw", json.dumps(data))
101
+ return report.to_dict(with_type=True)
102
+
103
+ def _mock_companies(self, number: int = 2):
104
+ self.mock_request(
105
+ "companies",
106
+ json=[{"CompanyId": id_ + 1} for id_ in range(number)],
107
+ headers={"X-Total-Items": str(number)},
108
+ )
109
+
110
+ def test_get_data_for_all_companies(self):
111
+ self._mock_companies()
112
+ self.mock_request(
113
+ "companies/1/findings/patchmanagement?" "status=Active&severity=Critical",
114
+ json=[{"FindingId": 1}, {"FindingId": 2}],
115
+ )
116
+ self.mock_request(
117
+ "companies/2/findings/patchmanagement?&status=Active&severity=Critical",
118
+ json=[{"FindingId": 3}],
119
+ )
120
+
121
+ self.run_bot()
122
+
123
+ self.assertOutputQueueLen(3)
124
+ self.assertMessageEqual(
125
+ 0, self._create_report_dict({"CompanyId": 1}, {"FindingId": 1})
126
+ )
127
+ self.assertMessageEqual(
128
+ 1, self._create_report_dict({"CompanyId": 1}, {"FindingId": 2})
129
+ )
130
+ self.assertMessageEqual(
131
+ 2, self._create_report_dict({"CompanyId": 2}, {"FindingId": 3})
132
+ )
133
+
134
+ def test_single_exception_doesnt_stop_processing(self):
135
+ self._mock_companies()
136
+ self.mock_request(
137
+ "companies/1/findings/patchmanagement?status=Active&severity=Critical",
138
+ status_code=500,
139
+ )
140
+ self.mock_request(
141
+ "companies/1/findings/applicationsecurity?status=Active&severity=Critical",
142
+ json=[{"FindingId": 1}],
143
+ )
144
+ self.mock_request(
145
+ "companies/2/findings/patchmanagement?status=Active&severity=Critical",
146
+ json=[{"FindingId": 2}],
147
+ )
148
+ self.mock_request(
149
+ "companies/2/findings/applicationsecurity?status=Active&severity=Critical",
150
+ json=[{"FindingId": 3}],
151
+ )
152
+
153
+ self.run_bot(
154
+ parameters={**self.sysconfig, "categories": {"PATCH": {}, "APPSEC": {}}},
155
+ allowed_error_count=2, # log from collector + from client
156
+ )
157
+
158
+ self.assertOutputQueueLen(3)
159
+ self.assertMessageEqual(
160
+ 0, self._create_report_dict({"CompanyId": 1}, {"FindingId": 1})
161
+ )
162
+ self.assertMessageEqual(
163
+ 1, self._create_report_dict({"CompanyId": 2}, {"FindingId": 2})
164
+ )
165
+ self.assertMessageEqual(
166
+ 2, self._create_report_dict({"CompanyId": 2}, {"FindingId": 3})
167
+ )
168
+
169
+ def test_overriding_filters(self):
170
+ self._mock_companies(1)
171
+ self.mock_request(
172
+ "companies/1/findings/patchmanagement?status=Acknowledged&severity=Critical",
173
+ json=[{"FindingId": 1}],
174
+ )
175
+ self.mock_request(
176
+ "companies/1/findings/applicationsecurity?status=Active&severity=Low,Critical",
177
+ headers={"X-Total-Items": "1"},
178
+ json=[{"FindingId": 2}],
179
+ )
180
+
181
+ self.run_bot(
182
+ parameters={
183
+ **self.sysconfig,
184
+ "severities": ["Critical"],
185
+ "statuses": ["Active"],
186
+ "categories": {
187
+ "PATCH": {"statuses": ["Acknowledged"]},
188
+ "APPSEC": {"severities": ["Low", "Critical"]},
189
+ },
190
+ }
191
+ )
192
+
193
+ self.assertOutputQueueLen(2)
194
+ self.assertMessageEqual(
195
+ 0, self._create_report_dict({"CompanyId": 1}, {"FindingId": 1})
196
+ )
197
+ self.assertMessageEqual(
198
+ 1, self._create_report_dict({"CompanyId": 1}, {"FindingId": 2})
199
+ )
200
+
201
+ def test_category_with_no_settings(self):
202
+ self._mock_companies(1)
203
+ self.mock_request(
204
+ "companies/1/findings/patchmanagement?status=Active&severity=Critical",
205
+ json=[{"FindingId": 1}],
206
+ )
207
+
208
+ self.run_bot(
209
+ parameters={
210
+ **self.sysconfig,
211
+ "categories": {
212
+ "PATCH": None,
213
+ },
214
+ }
215
+ )
216
+
217
+ self.assertOutputQueueLen(1)
218
+ self.assertMessageEqual(
219
+ 0, self._create_report_dict({"CompanyId": 1}, {"FindingId": 1})
220
+ )
221
+
222
+ def test_include_and_exclude_findings(self):
223
+ self._mock_companies(1)
224
+ self.mock_request(
225
+ "companies/1/findings/patchmanagement",
226
+ json=[
227
+ {"ControlId": "PATCH-001"},
228
+ {"ControlId": "PATCH-002"},
229
+ {"ControlId": "PATCH-003"},
230
+ ],
231
+ )
232
+ self.mock_request(
233
+ "companies/1/findings/applicationsecurity",
234
+ headers={"X-Total-Items": "1"},
235
+ json=[
236
+ {"ControlId": "APPSEC-001"},
237
+ {"ControlId": "APPSEC-002"},
238
+ {"ControlId": "APPSEC-003"},
239
+ ],
240
+ )
241
+
242
+ self.run_bot(
243
+ parameters={
244
+ **self.sysconfig,
245
+ "categories": {
246
+ "PATCH": {"include": ["PATCH-001", "PATCH-003"]},
247
+ "APPSEC": {"exclude": ["APPSEC-003"]},
248
+ },
249
+ }
250
+ )
251
+
252
+ self.assertOutputQueueLen(4)
253
+ self.assertMessageEqual(
254
+ 0, self._create_report_dict({"CompanyId": 1}, {"ControlId": "PATCH-001"})
255
+ )
256
+ self.assertMessageEqual(
257
+ 1, self._create_report_dict({"CompanyId": 1}, {"ControlId": "PATCH-003"})
258
+ )
259
+ self.assertMessageEqual(
260
+ 2, self._create_report_dict({"CompanyId": 1}, {"ControlId": "APPSEC-001"})
261
+ )
262
+ self.assertMessageEqual(
263
+ 3, self._create_report_dict({"CompanyId": 1}, {"ControlId": "APPSEC-002"})
264
+ )
265
+
266
+ def test_acknowledge_findings(self):
267
+ self._mock_companies(1)
268
+ self.mock_request(
269
+ "companies/1/findings/credentialmanagement",
270
+ json=[
271
+ {"ControlId": "LEAK-001", "FindingId": 1},
272
+ {"ControlId": "LEAK-002", "FindingId": 2},
273
+ ],
274
+ headers={"X-Total-Items": "2"},
275
+ )
276
+ self.mock_request(
277
+ "companies/1/findings/1", json={"Status": "Acknowledged"}, method="patch"
278
+ )
279
+ self.mock_request(
280
+ "companies/1/findings/2", json={"Status": "Acknowledged"}, method="patch"
281
+ )
282
+
283
+ self.run_bot(
284
+ parameters={**self.sysconfig, "categories": {"LEAK": {"acknowledge": True}}}
285
+ )
286
+
287
+ self.assertEqual(2 + 2, self.requests.call_count)
@@ -0,0 +1,147 @@
1
+ """Base for DISP client and collector tests
2
+
3
+ SPDX-FileCopyrightText: 2023 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ from datetime import datetime, timedelta
8
+
9
+ import requests
10
+ from requests_mock import MockerCore
11
+
12
+ from ....lib.base import OAuthAccess_APIMockMixIn
13
+
14
+ _DEFAULT_TAGS = ["_BotnetCreds", "family: Redline", "type: InfoStealer", "_Parsed"]
15
+ _DEFAULT_ASSETS = ["nic.at"]
16
+
17
+
18
+ class DISP_APIMockMixIn(OAuthAccess_APIMockMixIn):
19
+ def setup_api_mock(
20
+ self,
21
+ url: str = "https://disp.example.at/v1",
22
+ auth_token: str = "XXX",
23
+ oauth_clientid: str = "client1",
24
+ oauth_clientsecret: str = "secret1",
25
+ oauth_url: str = "https://oauthip.example.at/v1",
26
+ session: requests.Session = None,
27
+ ):
28
+ self._api_url = url
29
+ self._auth_token = auth_token
30
+ self.session = session
31
+ self.requests = MockerCore(session=self.session)
32
+ self.requests.start()
33
+ self.addCleanup(self.requests.stop)
34
+
35
+ self.setup_oauth_mock(
36
+ oauth_clientid=oauth_clientid,
37
+ oauth_clientsecret=oauth_clientsecret,
38
+ oauth_url=oauth_url,
39
+ oauth_scope="https://gateway.disp.deloitte.com/.default",
40
+ oauth_grant_type="client_credentials",
41
+ session=self.session,
42
+ )
43
+
44
+ self._incident_counter = 0
45
+ self._incidents = []
46
+
47
+ def create_requests_mock(self):
48
+ # create nested requests mock to separate tested requests
49
+ mocker = MockerCore(session=self.session, real_http=True)
50
+ mocker.start()
51
+ self.addCleanup(mocker.stop)
52
+ return mocker
53
+
54
+ def mock_request(
55
+ self, path: str, mocker: MockerCore = None, method: str = "get", **kwargs
56
+ ):
57
+ mocker = mocker or self.requests
58
+ mocking_method = getattr(mocker, method)
59
+ mocking_method(
60
+ f"{self._api_url}/{path}",
61
+ request_headers={
62
+ "Authorization": f"Bearer {self._auth_token}",
63
+ "OAuth": self.mocked_access_token,
64
+ },
65
+ **kwargs,
66
+ )
67
+
68
+ def add_incident(
69
+ self,
70
+ *,
71
+ viewed: bool = False,
72
+ validation_date: datetime = None,
73
+ title: str = "Credentials compromised by a botnet",
74
+ category: str = "Information discovery",
75
+ type_: str = "Credentials",
76
+ tags: list = None,
77
+ related_assets: list = None,
78
+ evidence_id: str = None,
79
+ other: dict = None,
80
+ ):
81
+ """Add mocked incident to the API responses, but only selected used fields fields"""
82
+ incident_id = f"incident-{self._incident_counter}"
83
+ self._incident_counter += 1
84
+ if tags is None:
85
+ tags = _DEFAULT_TAGS
86
+ if related_assets is None:
87
+ related_assets = _DEFAULT_ASSETS
88
+ if validation_date is None:
89
+ validation_date = datetime.utcnow() - timedelta(days=1)
90
+
91
+ incident = {
92
+ "id": incident_id,
93
+ "metadata": {
94
+ "viewed": viewed,
95
+ },
96
+ "validationDate": int(validation_date.timestamp() * 1000),
97
+ "title": title,
98
+ "category": category,
99
+ "type": type_,
100
+ "tags": tags,
101
+ "relatedAssets": related_assets,
102
+ "url": f"https://disp.app/document/incident/{incident_id}",
103
+ }
104
+
105
+ if evidence_id:
106
+ incident.update(
107
+ {
108
+ "evidences": [
109
+ {"idStoredFile": evidence_id, "name": f"{incident_id}.json.txt"}
110
+ ]
111
+ }
112
+ )
113
+ else:
114
+ incident.update({"evidences": []})
115
+
116
+ if other:
117
+ incident.update(other)
118
+
119
+ self._incidents.append(incident)
120
+ return incident_id, incident
121
+
122
+ def mock_evidence(
123
+ self,
124
+ incident_id: str,
125
+ file_id: str,
126
+ data: dict = None,
127
+ mocker: MockerCore = None,
128
+ ):
129
+ mocker = mocker or self.requests
130
+ self.mock_request(
131
+ f"incident/{incident_id}/file/{file_id}", json=data, mocker=mocker
132
+ )
133
+
134
+ def _mock_incidents(self, mocker: MockerCore = None, after: datetime = None):
135
+ after = after or datetime.utcnow() - timedelta(days=7)
136
+ mocker = mocker or self.requests
137
+ self.mock_request(
138
+ (
139
+ f"incident/?query=validationDate%20%3E%20{int(after.timestamp() * 1000)}"
140
+ "%20AND%20UNREAD&page=0&size=10"
141
+ ),
142
+ mocker=mocker,
143
+ json={
144
+ "content": self._incidents,
145
+ "last": True,
146
+ },
147
+ )
@@ -0,0 +1,134 @@
1
+ """Testing DISP Collector
2
+
3
+ SPDX-FileCopyrightText: 2023 CERT.at GmbH <https://cert.at/>
4
+ SPDX-License-Identifier: AGPL-3.0-or-later
5
+ """
6
+
7
+ from datetime import datetime, timezone
8
+ from unittest import TestCase
9
+
10
+ import requests
11
+
12
+ from intelmq_extensions.bots.collectors.disp._client import DISPClient
13
+
14
+ from .base import DISP_APIMockMixIn
15
+
16
+
17
+ class DISPClientTestCase(DISP_APIMockMixIn, TestCase):
18
+ def setUp(self) -> None:
19
+ super().setUp()
20
+ self.config = {
21
+ "api_url": "https://disp.example.at/v1",
22
+ "auth_token": "XXX",
23
+ "oauth_clientid": "client1",
24
+ "oauth_clientsecret": "secret1",
25
+ "oauth_url": "https://oauthip.example.at/v1",
26
+ }
27
+ self.setup_api_mock(
28
+ url=self.config["api_url"],
29
+ auth_token=self.config["auth_token"],
30
+ oauth_clientid=self.config["oauth_clientid"],
31
+ oauth_clientsecret=self.config["oauth_clientsecret"],
32
+ oauth_url=self.config["oauth_url"],
33
+ session=requests.Session(),
34
+ )
35
+
36
+ self.client = DISPClient(session=self.session, **self.config)
37
+
38
+ def test_get_simple_response(self):
39
+ self.mock_request(
40
+ "incident/",
41
+ json={"id": "some-id"},
42
+ )
43
+
44
+ response = self.client.get("incident/")
45
+ self.assertEqual({"id": "some-id"}, response)
46
+
47
+ def test_get_paginated_response_1_page(self):
48
+ self.mock_request(
49
+ "incident/?page=0&size=10",
50
+ json={
51
+ "content": [{"id": "id-1"}, {"id": "id-2"}],
52
+ "last": True,
53
+ },
54
+ )
55
+
56
+ responses = list(self.client.get_paginated("incident/"))
57
+ self.assertEqual([{"id": "id-1"}, {"id": "id-2"}], responses)
58
+
59
+ def test_get_paginated_response_3_pages(self):
60
+ self.mock_request(
61
+ "incident/?page=0&size=10",
62
+ json={
63
+ "content": [{"id": "id-1"}, {"id": "id-2"}],
64
+ "last": False,
65
+ },
66
+ )
67
+ self.mock_request(
68
+ "incident/?page=1&size=10",
69
+ json={
70
+ "content": [{"id": "id-3"}],
71
+ "last": False,
72
+ },
73
+ )
74
+ self.mock_request(
75
+ "incident/?page=2&size=10",
76
+ json={
77
+ "content": [{"id": "id-4"}],
78
+ "last": True,
79
+ },
80
+ )
81
+
82
+ responses = list(self.client.get_paginated("incident/"))
83
+ self.assertEqual(
84
+ [{"id": "id-1"}, {"id": "id-2"}, {"id": "id-3"}, {"id": "id-4"}], responses
85
+ )
86
+
87
+ def test_get_incidents(self):
88
+ after = datetime(2023, 1, 1, 19, 10, tzinfo=timezone.utc)
89
+
90
+ self.mock_request(
91
+ "incident/?query=validationDate%20%3E%201672600200000%20AND%20UNREAD&page=0&size=10",
92
+ json={"content": [{"id": "id-1"}]},
93
+ )
94
+ self.mock_request(
95
+ "incident/?query=validationDate%20%3E%201672600200000&page=0&size=10",
96
+ json={"content": [{"id": "id-2"}]},
97
+ )
98
+ self.mock_request(
99
+ "incident/?query=NOT%20SOMETHING&page=0&size=10",
100
+ json={"content": [{"id": "id-3"}]},
101
+ )
102
+
103
+ self.assertEqual([{"id": "id-1"}], list(self.client.incidents(after, True)))
104
+ self.assertEqual([{"id": "id-2"}], list(self.client.incidents(after, False)))
105
+ self.assertEqual(
106
+ [{"id": "id-3"}], list(self.client.incidents(query="NOT SOMETHING"))
107
+ )
108
+
109
+ def test_get_evidence(self):
110
+ # For credential tracking, the expected file is in JSON format.
111
+ # For other incidents (not covered now), the format may differ
112
+ self.mock_request("incident/id-1/file/f-1", text='{"credentials": []}')
113
+
114
+ result = self.client.download_evidence_json("id-1", "f-1")
115
+
116
+ self.assertEqual({"credentials": []}, result)
117
+
118
+ def test_mark_read(self):
119
+ self.mock_request("incident/read?id=id-1&read=true", method="post")
120
+
121
+ self.client.mark_incident_read("id-1")
122
+
123
+ self.assertEqual(1, self.requests.call_count)
124
+
125
+ def test_raise_on_connection_issue(self):
126
+ self.mock_request("/", status_code=500)
127
+
128
+ with self.assertRaises(RuntimeError):
129
+ self.client.get("/")
130
+
131
+ self.mock_request("/", method="post", status_code=500)
132
+
133
+ with self.assertRaises(RuntimeError):
134
+ self.client.post("/")