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,282 @@
1
+ """Tests for DISPParserBot
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 copy import deepcopy
10
+ from datetime import datetime
11
+
12
+ import intelmq.lib.message as message
13
+ from dateutil.tz import UTC
14
+ from intelmq.lib.test import skip_internet
15
+
16
+ from intelmq_extensions.bots.parsers.disp.parser import DISPParserBot
17
+
18
+ from ....base import BotTestCase
19
+
20
+
21
+ class TestDISPParserBot(BotTestCase, unittest.TestCase):
22
+ @classmethod
23
+ def set_bot(cls):
24
+ cls.bot_reference = DISPParserBot
25
+ cls.sysconfig = {
26
+ "default_fields": {
27
+ "classification.taxonomy": "information-content-security",
28
+ "classification.type": "data-leak",
29
+ "classification.identifier": "leaked-credentials",
30
+ "comment": "A comment",
31
+ }
32
+ }
33
+
34
+ def _create_report_dict(self, credentials, id_: str = "id-1", **kwargs):
35
+ incident_data = {
36
+ "id": id_,
37
+ "creationDate": 1693391274125,
38
+ "publishedDate": 1693391312517,
39
+ "title": "Credentials compromised by malware-1 botnet",
40
+ "category": "Information discovery",
41
+ "type": "Credentials",
42
+ "detectionDate": 1693391031733,
43
+ "validationDate": 1693391274125,
44
+ "updateDate": 1693391446948,
45
+ "riskLevel": "High",
46
+ "tags": [
47
+ "family: malware-1",
48
+ "type: InfoStealer",
49
+ ],
50
+ "relatedAssets": ["example.at"],
51
+ }
52
+ default_credentials = {
53
+ "malware": "malware-1",
54
+ "date": "",
55
+ "application": "Some App XXX",
56
+ "url": "https://some.example.at/",
57
+ "username": "my-user-1",
58
+ "password": "pas*****",
59
+ }
60
+ credentials_data = []
61
+ for credential in credentials:
62
+ c_data = deepcopy(default_credentials)
63
+ c_data.update(credential)
64
+ credentials_data.append(c_data)
65
+ evidence_data = {"credentials": credentials_data}
66
+
67
+ raw = {"incident": incident_data, "evidences": evidence_data}
68
+ data = {
69
+ "feed.name": "Test Bot",
70
+ "feed.accuracy": 100.0,
71
+ "feed.code": "feed-code",
72
+ "time.observation": datetime(2023, 1, 1, tzinfo=UTC).isoformat(),
73
+ }
74
+ data.update(kwargs)
75
+ report = message.Report(data, harmonization=self.harmonization)
76
+ report.add("raw", json.dumps(raw))
77
+ return report.to_dict(with_type=True)
78
+
79
+ def _create_event_dict(
80
+ self,
81
+ username: str = "my-user-1",
82
+ password: str = "pas*****",
83
+ fqdn: str = "some.example.at",
84
+ url: str = "https://some.example.at/",
85
+ urlpath: str = "/",
86
+ full_url: str = "hxxps://some.example.at/",
87
+ compromise_time: str = "",
88
+ incident_data: dict = None,
89
+ evidences_data: dict = None,
90
+ compromise_time_full: str = None,
91
+ ip: str = None,
92
+ **kwargs,
93
+ ):
94
+ data = {
95
+ "classification.identifier": "leaked-credentials",
96
+ "classification.taxonomy": "information-content-security",
97
+ "classification.type": "data-leak",
98
+ "comment": "A comment",
99
+ "event_description.text": "Credentials compromised by malware-1 botnet",
100
+ "source.fqdn": fqdn,
101
+ "source.urlpath": urlpath,
102
+ "source.url": url,
103
+ "source.ip": ip,
104
+ "source.account": username,
105
+ "extra.account": username, # Intentionally copied
106
+ "extra.password": password,
107
+ "extra.compromise_time": compromise_time,
108
+ "extra.compromise_time_full": compromise_time_full,
109
+ "extra.application": "Some App XXX",
110
+ "extra.full_url": full_url,
111
+ "extra.feed_event_id": "id-1",
112
+ "malware.name": "malware-1",
113
+ "feed.code": "feed-code",
114
+ "feed.name": "Test Bot",
115
+ "feed.accuracy": 100.0,
116
+ "extra.monitored_asset": "example.at", # Domain we monitor in DISP
117
+ "time.observation": datetime(2023, 1, 1, tzinfo=UTC).isoformat(),
118
+ }
119
+ data.update(kwargs)
120
+ event = message.Event(data, harmonization=self.harmonization)
121
+ event.add(
122
+ "time.source",
123
+ datetime.fromtimestamp(1693391274125 // 1000, tz=UTC).isoformat(),
124
+ )
125
+ event.add(
126
+ "raw", json.dumps({"incident": incident_data, "evidences": evidences_data})
127
+ )
128
+ return event.to_dict(with_type=True)
129
+
130
+ def test_parsing_report_default(self):
131
+ self.input_message = self._create_report_dict(
132
+ [
133
+ {
134
+ "malware": "malware-1",
135
+ "date": "",
136
+ "application": "Some App XXX",
137
+ "url": "https://some.example.at/",
138
+ "username": "my-user-1",
139
+ "password": "pas*****",
140
+ },
141
+ {
142
+ "malware": "malware-1",
143
+ "date": "09.27.2021 16:17:18",
144
+ "application": "Some App XXX",
145
+ "url": "https://example.at/some/sub/page",
146
+ "username": "my-user-2",
147
+ "password": "lea*******",
148
+ },
149
+ ],
150
+ )
151
+
152
+ self.run_bot()
153
+
154
+ self.assertMessageEqual(
155
+ 0,
156
+ self._create_event_dict(username="my-user-1", password="pas*****"),
157
+ compare_raw=False,
158
+ )
159
+ self.assertMessageEqual(
160
+ 1,
161
+ self._create_event_dict(
162
+ username="my-user-2",
163
+ password="lea*******",
164
+ fqdn="example.at",
165
+ url="https://example.at/[REDACTED]",
166
+ urlpath="/[REDACTED]",
167
+ full_url="hxxps://example.at/some/sub/page",
168
+ compromise_time="2021-09",
169
+ compromise_time_full="09.27.2021 16:17:18",
170
+ ),
171
+ compare_raw=False,
172
+ )
173
+
174
+ def test_compromise_time_settings(self):
175
+ report = self._create_report_dict([{"date": "09.27.2021 16:17:18"}])
176
+
177
+ self.input_message = deepcopy(report)
178
+ self.run_bot(parameters={"compromise_time_format": "original"})
179
+ self.assertMessageEqual(
180
+ 0,
181
+ self._create_event_dict(
182
+ compromise_time="09.27.2021 16:17:18",
183
+ compromise_time_full="09.27.2021 16:17:18",
184
+ ),
185
+ compare_raw=False,
186
+ )
187
+
188
+ self.input_message = deepcopy(report)
189
+ self.run_bot(parameters={"compromise_time_format": ""})
190
+ self.assertMessageEqual(
191
+ 0,
192
+ self._create_event_dict(
193
+ compromise_time="",
194
+ compromise_time_full="09.27.2021 16:17:18",
195
+ ),
196
+ compare_raw=False,
197
+ )
198
+
199
+ self.input_message = deepcopy(report)
200
+ self.run_bot(parameters={"compromise_time_format": "%Y-%m-%d"})
201
+ self.assertMessageEqual(
202
+ 0,
203
+ self._create_event_dict(
204
+ compromise_time="2021-09-27",
205
+ compromise_time_full="09.27.2021 16:17:18",
206
+ ),
207
+ compare_raw=False,
208
+ )
209
+
210
+ self.input_message = self._create_report_dict([{"date": "invalid"}])
211
+ self.run_bot(
212
+ parameters={"compromise_time_format": "%Y-%m-%d"}, allowed_warning_count=1
213
+ )
214
+ self.assertMessageEqual(
215
+ 0,
216
+ self._create_event_dict(
217
+ compromise_time="",
218
+ compromise_time_full="invalid",
219
+ ),
220
+ compare_raw=False,
221
+ )
222
+
223
+ def test_redacting_url_settings(self):
224
+ report = self._create_report_dict(
225
+ [{"url": "https://some.example.at/my/sub?url=1"}]
226
+ )
227
+
228
+ self.input_message = deepcopy(report)
229
+ self.run_bot(parameters={"redact_url_path": True})
230
+ self.assertMessageEqual(
231
+ 0,
232
+ self._create_event_dict(
233
+ fqdn="some.example.at",
234
+ url="https://some.example.at/[REDACTED]",
235
+ urlpath="/[REDACTED]",
236
+ full_url="hxxps://some.example.at/my/sub?url=1",
237
+ ),
238
+ compare_raw=False,
239
+ )
240
+
241
+ self.input_message = deepcopy(report)
242
+ self.run_bot(parameters={"redact_url_path": False})
243
+ self.assertMessageEqual(
244
+ 0,
245
+ self._create_event_dict(
246
+ fqdn="some.example.at",
247
+ url="https://some.example.at/my/sub",
248
+ urlpath="/my/sub",
249
+ full_url="hxxps://some.example.at/my/sub?url=1",
250
+ ),
251
+ compare_raw=False,
252
+ )
253
+
254
+ @skip_internet()
255
+ def test_resolve_ip_settings(self):
256
+ report = self._create_report_dict([{"url": "https://cert.at/"}])
257
+
258
+ self.input_message = deepcopy(report)
259
+ self.run_bot(parameters={"resolve_ip": True})
260
+ self.assertMessageEqual(
261
+ 0,
262
+ self._create_event_dict(
263
+ fqdn="cert.at",
264
+ ip="131.130.249.234",
265
+ url="https://cert.at/",
266
+ full_url="hxxps://cert.at/",
267
+ ),
268
+ compare_raw=False,
269
+ )
270
+
271
+ self.input_message = deepcopy(report)
272
+ self.run_bot(parameters={"resolve_ip": False})
273
+ self.assertMessageEqual(
274
+ 0,
275
+ self._create_event_dict(
276
+ fqdn="cert.at",
277
+ ip="",
278
+ url="https://cert.at/",
279
+ full_url="hxxps://cert.at/",
280
+ ),
281
+ compare_raw=False,
282
+ )
@@ -0,0 +1,62 @@
1
+ # -*- coding: utf-8 -*-
2
+ import base64
3
+ import os
4
+ import unittest
5
+
6
+ import intelmq.lib.test as test
7
+
8
+ from intelmq_extensions.bots.parsers.malwaredomains.parser import (
9
+ MalwareDomainsParserBot,
10
+ )
11
+
12
+ with open(os.path.join(os.path.dirname(__file__), "domains.txt"), "rb") as fh:
13
+ RAW = base64.b64encode(fh.read()).decode()
14
+
15
+ OUTPUT1 = {
16
+ "__type": "Event",
17
+ "classification.type": "phishing",
18
+ "event_description.text": "phishing",
19
+ "classification.identifier": "phishing",
20
+ "raw": "CQlleGFtcGxlLmNvbQlwaGlzaGluZwlvcGVucGhpc2guY29tCTIwMTYwNTI3CTIwMTYwMTA4",
21
+ "source.fqdn": "example.com",
22
+ "time.source": "2016-05-27T00:00:00+00:00",
23
+ }
24
+ OUTPUT2 = {
25
+ "__type": "Event",
26
+ "classification.type": "phishing",
27
+ "event_description.text": "phishing",
28
+ "classification.identifier": "phishing",
29
+ "raw": "CQlleGFtcGxlLmludmFsaWQJcGhpc2hpbmcJb3BlbnBoaXNoLmNvbQkyMDE2MDUyNwkyMDE2MDEwOA==",
30
+ "source.fqdn": "example.invalid",
31
+ "time.source": "2016-05-27T00:00:00+00:00",
32
+ }
33
+ OUTPUT3 = {
34
+ "__type": "Event",
35
+ "classification.type": "c2-server",
36
+ "event_description.text": "C&C",
37
+ "classification.identifier": "C&C",
38
+ "raw": "CQlleGFtcGxlLm5ldAlDJkMJc291cmNlLmV4YW1wbGUuY29tCTIwMTcxMjAxCTIwMTYwNzE5CTIwMTYwMzEw",
39
+ "source.fqdn": "example.net",
40
+ "time.source": "2017-12-01T00:00:00+00:00",
41
+ }
42
+
43
+
44
+ class TestMalwareDomainsParserBot(test.BotTestCase, unittest.TestCase):
45
+ """
46
+ A TestCase for MalwareDomainsParserBot.
47
+ """
48
+
49
+ @classmethod
50
+ def set_bot(cls):
51
+ cls.bot_reference = MalwareDomainsParserBot
52
+ cls.default_input_message = {"__type": "Report", "raw": RAW}
53
+
54
+ def test_event(self):
55
+ self.run_bot()
56
+ self.assertMessageEqual(0, OUTPUT1)
57
+ self.assertMessageEqual(1, OUTPUT2)
58
+ self.assertMessageEqual(2, OUTPUT3)
59
+
60
+
61
+ if __name__ == "__main__": # pragma: no cover
62
+ unittest.main()
File without changes
@@ -0,0 +1,97 @@
1
+ from intelmq.lib.test import skip_database
2
+
3
+ from intelmq_extensions.cli.create_reports import IntelMQCLIContoller
4
+
5
+ from ..base import CLITestCase
6
+
7
+
8
+ @skip_database()
9
+ class TestCreatingReports(CLITestCase):
10
+ CLI_CONTROLLER = IntelMQCLIContoller
11
+
12
+ def setUp(self) -> None:
13
+ super().setUp()
14
+ self.event_ids = [
15
+ self.db_add_event({"feed.code": "feed1", "constituency": "energy"}),
16
+ self.db_add_event({"feed.code": "feed2", "constituency": "energy"}),
17
+ self.db_add_event({"feed.code": "feed3", "constituency": "national"}),
18
+ self.db_add_event({"feed.code": "feed4", "constituency": "national"}),
19
+ self.db_add_event({"feed.code": "feed5", "constituency": None}),
20
+ ]
21
+
22
+ def test_list_open_feeds(self):
23
+ self.config.update({"constituency": {"key": "energy"}})
24
+
25
+ self.run_cli(["-l"])
26
+
27
+ self.assertEqual(2, len(self.stdout))
28
+ self.assertIn("feed1\n", self.stdout)
29
+ self.assertIn("feed2\n", self.stdout)
30
+
31
+ self.config.update({"constituency": {"key": "national", "default": True}})
32
+
33
+ self.run_cli(["-l"])
34
+ self.assertEqual(3, len(self.stdout))
35
+ self.assertIn("feed3\n", self.stdout)
36
+ self.assertIn("feed4\n", self.stdout)
37
+ self.assertIn("feed5\n", self.stdout)
38
+
39
+ def test_run_create_reports(self):
40
+ self.config.update({"constituency": {"key": "energy"}})
41
+
42
+ self.run_cli([])
43
+
44
+ self.assertInLogs("Handling feedcode 'feed1'")
45
+ self.assertInLogs("Handling feedcode 'feed2'")
46
+ self.assertRTTicketCount(2)
47
+
48
+ event_feed1 = self.db_get_event(self.event_ids[0])
49
+ self.assertEqual(event_feed1["rtir_report_id"], 1)
50
+
51
+ event_feed2 = self.db_get_event(self.event_ids[1])
52
+ self.assertEqual(event_feed2["rtir_report_id"], 2)
53
+
54
+ self.assertIn("Owner", self.rt_mock.create_ticket.call_args.kwargs)
55
+
56
+ def test_create_internal_only_notifications(self):
57
+ self.config.update({"constituency": {"key": "national", "default": True}})
58
+ id_1 = self.db_add_event(
59
+ {"feed.code": "internal-only"},
60
+ notify=True,
61
+ abuse_contact="",
62
+ cc="PL",
63
+ extra={"monitored_asset": "something"},
64
+ )
65
+ id_2 = self.db_add_event(
66
+ {"feed.code": "internal-only2"}, notify=True, abuse_contact="", cc="AT"
67
+ )
68
+ id_3 = self.db_add_event(
69
+ {"feed.code": "internal-only2"},
70
+ notify=True,
71
+ abuse_contact="",
72
+ cc="US",
73
+ extra={"monitored_asset": "something"},
74
+ )
75
+
76
+ self.run_cli(
77
+ [
78
+ "--internal-notify",
79
+ "--monitored-assets",
80
+ "--feed",
81
+ "internal-only",
82
+ "internal-only2",
83
+ ]
84
+ )
85
+
86
+ self.assertRTTicketCount(2)
87
+ self.assertEqual(self.db_get_event(id_1)["rtir_report_id"], 1)
88
+ for event_id in [id_2, id_3]:
89
+ event = self.db_get_event(event_id)
90
+ self.assertEqual(event["rtir_report_id"], 2)
91
+
92
+ self.assertNotIn("Owner", self.rt_mock.create_ticket.call_args.kwargs)
93
+
94
+ # Other events are untouched
95
+ for event_id in self.event_ids:
96
+ event = self.db_get_event(event_id)
97
+ self.assertIsNone(event["rtir_report_id"])
@@ -0,0 +1,158 @@
1
+ from intelmq.lib.test import skip_database
2
+
3
+ from intelmq_extensions.cli.intelmqcli import IntelMQCLIContoller
4
+
5
+ from ..base import CLITestCase
6
+
7
+
8
+ @skip_database()
9
+ class TestIntelMQCLI(CLITestCase):
10
+ CLI_CONTROLLER = IntelMQCLIContoller
11
+
12
+ def test_list_feeds(self):
13
+ self.db_add_event({"feed.code": "my-feed"})
14
+ self.db_add_event({"feed.code": "feed-2"})
15
+
16
+ self.run_cli(["-l"])
17
+
18
+ self.assertIn("my-feed\n", self.stdout)
19
+ self.assertIn("feed-2\n", self.stdout)
20
+
21
+ def test_add_event_description_to_text(self):
22
+ self.add_boilerplate(
23
+ "test", "my generic description and \n{event_descriptions}"
24
+ )
25
+ self.add_boilerplate("event-description-divider", "\n ### \n")
26
+ self.db_add_event(
27
+ {
28
+ "feed.code": "feed1",
29
+ "rtir_report_id": 10,
30
+ "event_description.text": "descrpt1",
31
+ }
32
+ )
33
+ self.db_add_event(
34
+ {
35
+ "feed.code": "feed1",
36
+ "rtir_report_id": 10,
37
+ "event_description.text": "description with \\n encoded new line",
38
+ }
39
+ )
40
+
41
+ self.run_cli(["--batch", "--quiet"])
42
+
43
+ self.assertRTTicketCount(2)
44
+
45
+ text = self.get_rt_text(2)
46
+ self.assertIn(
47
+ "description with \n encoded new line",
48
+ text,
49
+ )
50
+ self.assertIn("\n ### \n", text)
51
+ self.assertIn("descrpt1", text)
52
+
53
+ def test_add_event_description_to_text_default_divider(self):
54
+ self.add_boilerplate(
55
+ "test", "my generic description and \n{event_descriptions}"
56
+ )
57
+ self.db_add_event(
58
+ {
59
+ "feed.code": "feed1",
60
+ "rtir_report_id": 10,
61
+ "event_description.text": "descrpt1",
62
+ }
63
+ )
64
+ self.db_add_event(
65
+ {
66
+ "feed.code": "feed1",
67
+ "rtir_report_id": 10,
68
+ "event_description.text": "description with \\n encoded new line",
69
+ }
70
+ )
71
+
72
+ self.run_cli(["--batch", "--quiet"])
73
+
74
+ self.assertRTTicketCount(2)
75
+
76
+ text = self.get_rt_text(2)
77
+ self.assertIn("\n --- \n", text)
78
+
79
+
80
+ @skip_database()
81
+ class TestConstituencySupport(CLITestCase):
82
+ CLI_CONTROLLER = IntelMQCLIContoller
83
+
84
+ def setUp(self) -> None:
85
+ super().setUp()
86
+ self.config.update({"constituency": {"key": "energy"}})
87
+
88
+ self.add_boilerplate("test", "some_body")
89
+
90
+ self.event_ids = [
91
+ self.db_add_event(
92
+ {"feed.code": "feed1", "constituency": "energy", "rtir_report_id": 10}
93
+ ),
94
+ self.db_add_event(
95
+ {"feed.code": "feed2", "constituency": "energy", "rtir_report_id": 20}
96
+ ),
97
+ self.db_add_event(
98
+ {"feed.code": "feed3", "constituency": "national", "rtir_report_id": 30}
99
+ ),
100
+ self.db_add_event(
101
+ {"feed.code": "feed4", "constituency": "national", "rtir_report_id": 40}
102
+ ),
103
+ self.db_add_event(
104
+ {"feed.code": "feed5", "constituency": None, "rtir_report_id": 50}
105
+ ),
106
+ ]
107
+
108
+ def test_all_list_feeds(self):
109
+ """intelmqcli ignore constituency when listing - FIXME"""
110
+ self.run_cli(["-l"])
111
+
112
+ self.assertEqual(5, len(self.stdout))
113
+
114
+ def test_run_create_reports(self):
115
+ self.run_cli(["--batch", "--quiet"])
116
+
117
+ self.assertInLogs("All taxonomies: test")
118
+
119
+ # 1 new incident + 1 new investigation
120
+ # events have the same abuse contact and taxonomy, so they are processed together
121
+ self.assertRTTicketCount(2)
122
+
123
+ for event_id in self.event_ids[:2]:
124
+ event = self.db_get_event(event_id)
125
+ self.assertEqual(event["rtir_incident_id"], 1)
126
+ self.assertEqual(event["rtir_investigation_id"], 2)
127
+ self.assertIsNotNone(event["sent_at"])
128
+
129
+ # Other events untouched
130
+ for event_id in self.event_ids[2:]:
131
+ event = self.db_get_event(event_id)
132
+ self.assertIsNone(event["rtir_incident_id"])
133
+ self.assertIsNone(event["rtir_investigation_id"])
134
+ self.assertIsNone(event["sent_at"])
135
+
136
+ def test_creating_attachment_removes_new_lines_from_description(self):
137
+ self.db_add_event(
138
+ {
139
+ "feed.code": "feed2",
140
+ "constituency": "energy",
141
+ "rtir_report_id": 20,
142
+ "event_description.text": (
143
+ "Some very long description \n with \n\n multiple \n new lines."
144
+ ),
145
+ }
146
+ )
147
+
148
+ self.run_cli(["--batch", "--quiet"])
149
+
150
+ self.assertInLogs("All taxonomies: test")
151
+
152
+ self.assertRTTicketCount(2)
153
+
154
+ _, content_io, __ = self.get_rt_attachment(investigation_id=2)
155
+ content: str = content_io.getvalue()
156
+ self.assertIn(
157
+ "Some very long description with multiple new lines.", content
158
+ )
File without changes
@@ -0,0 +1,81 @@
1
+ """Base for testing APIs with OAuth token
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
+ from base64 import urlsafe_b64encode
9
+ from datetime import datetime, timedelta
10
+ from urllib.parse import parse_qs
11
+
12
+ import requests
13
+ from requests_mock import MockerCore
14
+
15
+
16
+ def _get_form_matcher(expected: dict):
17
+ def _matcher(request: requests.Request):
18
+ sent_data = parse_qs(request.body)
19
+ for key, value in sent_data.items():
20
+ if len(value) == 1:
21
+ sent_data[key] = value[0]
22
+ return expected == sent_data
23
+
24
+ return _matcher
25
+
26
+
27
+ class OAuthAccess_APIMockMixIn:
28
+ def setup_oauth_mock(
29
+ self,
30
+ oauth_clientid: str = "client1",
31
+ oauth_clientsecret: str = "secret1",
32
+ oauth_url: str = "https://oauthip.example.at/v1",
33
+ oauth_scope: str = "",
34
+ oauth_grant_type: str = "client_credentials",
35
+ session: requests.Session = None,
36
+ ):
37
+ self._oauth_clientid = oauth_clientid
38
+ self._oauth_clientsecret = oauth_clientsecret
39
+ self._oauth_url = oauth_url
40
+ self._oauth_scope = oauth_scope
41
+ self._oauth_grant_type = oauth_grant_type
42
+ self.__session = session
43
+ self._mock_access_token()
44
+
45
+ def _mock_access_token(self):
46
+ self.auth_requests = MockerCore(session=self.__session, real_http=True)
47
+ self.auth_requests.start()
48
+ self.addCleanup(self.auth_requests.stop)
49
+
50
+ expected = {
51
+ "scope": self._oauth_scope,
52
+ "client_id": self._oauth_clientid,
53
+ "client_secret": self._oauth_clientsecret,
54
+ "grant_type": self._oauth_grant_type,
55
+ }
56
+ expected = dict(filter(lambda kv: kv[1], expected.items()))
57
+ self.mocked_access_token = self.gen_fake_token()
58
+
59
+ def _gen_response(*_, **__):
60
+ return {
61
+ "token_type": "Bearer",
62
+ "expires_in": 3599,
63
+ "ext_expires_in": 3599,
64
+ "access_token": self.mocked_access_token,
65
+ }
66
+
67
+ # TODO: matcher
68
+
69
+ self.auth_requests.post(
70
+ url=self._oauth_url,
71
+ json=_gen_response,
72
+ additional_matcher=_get_form_matcher(expected),
73
+ )
74
+
75
+ @staticmethod
76
+ def gen_fake_token(expiry: datetime = None):
77
+ expiry = expiry or (datetime.utcnow() + timedelta(hours=1))
78
+ data = urlsafe_b64encode(
79
+ json.dumps({"exp": int(expiry.timestamp())}).encode()
80
+ ).decode()
81
+ return f"xxxxx.{data}.xxx"