insightconnect-plugin-runtime 6.2.1__py3-none-any.whl → 6.2.2__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.
- insightconnect_plugin_runtime/helper.py +69 -32
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.2.dist-info}/METADATA +2 -1
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.2.dist-info}/RECORD +6 -6
- tests/unit/test_helpers.py +43 -17
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.2.dist-info}/WHEEL +0 -0
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.2.dist-info}/top_level.txt +0 -0
|
@@ -10,7 +10,7 @@ import subprocess
|
|
|
10
10
|
import time
|
|
11
11
|
from datetime import datetime, timedelta
|
|
12
12
|
from io import IOBase
|
|
13
|
-
from typing import Any, Callable, Dict, List, Union, Tuple
|
|
13
|
+
from typing import Any, Callable, Dict, List, Union, Tuple, Optional
|
|
14
14
|
from urllib import request
|
|
15
15
|
from hashlib import sha1
|
|
16
16
|
from json import JSONDecodeError
|
|
@@ -33,17 +33,45 @@ DEFAULTS_HOURS_AGO = 24
|
|
|
33
33
|
logger = logging.getLogger()
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def hash_sha1(log: Dict) -> str:
|
|
36
|
+
def hash_sha1(log: Dict[str, Any], keys: Optional[List[str]] = None) -> str:
|
|
37
37
|
"""
|
|
38
38
|
Iterate through a dictionary and hash each value.
|
|
39
|
+
Optionally only hash certain keys in the dictionary.
|
|
40
|
+
|
|
39
41
|
:param log: Dictionary to be hashed.
|
|
40
|
-
:
|
|
42
|
+
:param keys: Optional list of keys to hash on if provided
|
|
43
|
+
|
|
41
44
|
:return: Hex digest of hash.
|
|
42
|
-
:rtype: str
|
|
43
45
|
"""
|
|
46
|
+
|
|
44
47
|
hash_ = sha1() # nosec B303
|
|
45
|
-
|
|
48
|
+
|
|
49
|
+
# Leaving no room for developer error and ensuring they know exactly where it went wrong
|
|
50
|
+
# if they provide a key not in list format
|
|
51
|
+
if keys is not None and not isinstance(keys, list):
|
|
52
|
+
raise TypeError(
|
|
53
|
+
f"The 'keys' parameter must be a list or None in the 'hash_sha1' function, not {type(keys).__name__}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Hash all key-value pairs if no keys provided
|
|
57
|
+
if keys is None:
|
|
58
|
+
items_to_hash = log.items()
|
|
59
|
+
|
|
60
|
+
# Otherwise, only include specified keys
|
|
61
|
+
else:
|
|
62
|
+
items_to_hash = []
|
|
63
|
+
for key in keys:
|
|
64
|
+
if key in log:
|
|
65
|
+
items_to_hash.append((key, log[key]))
|
|
66
|
+
|
|
67
|
+
# Alert if the key is not found in the log
|
|
68
|
+
else:
|
|
69
|
+
raise KeyError(f"Key '{key}' not found in the provided log.")
|
|
70
|
+
|
|
71
|
+
# Iterate through items to hash and hash
|
|
72
|
+
for key, value in items_to_hash:
|
|
46
73
|
hash_.update(f"{key}{value}".encode(ENCODE_TYPE))
|
|
74
|
+
|
|
47
75
|
return hash_.hexdigest()
|
|
48
76
|
|
|
49
77
|
|
|
@@ -137,7 +165,12 @@ def make_request(
|
|
|
137
165
|
raise PluginException(
|
|
138
166
|
preset=PluginException.Preset.UNKNOWN, data=str(exception)
|
|
139
167
|
)
|
|
140
|
-
response_handler(
|
|
168
|
+
response_handler(
|
|
169
|
+
response,
|
|
170
|
+
exception_custom_configs,
|
|
171
|
+
exception_data_location,
|
|
172
|
+
allowed_status_codes,
|
|
173
|
+
)
|
|
141
174
|
return response
|
|
142
175
|
|
|
143
176
|
|
|
@@ -192,7 +225,7 @@ def request_error_handling(
|
|
|
192
225
|
exception.response,
|
|
193
226
|
data_location=exception_data_location,
|
|
194
227
|
custom_configs=custom_configs,
|
|
195
|
-
allowed_status_codes=allowed_status_codes
|
|
228
|
+
allowed_status_codes=allowed_status_codes,
|
|
196
229
|
)
|
|
197
230
|
else:
|
|
198
231
|
raise PluginException(
|
|
@@ -443,15 +476,19 @@ def convert_dict_to_snake_case(
|
|
|
443
476
|
|
|
444
477
|
if isinstance(input_dict, list):
|
|
445
478
|
return [
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
479
|
+
(
|
|
480
|
+
convert_dict_to_snake_case(element)
|
|
481
|
+
if isinstance(element, (dict, list))
|
|
482
|
+
else element
|
|
483
|
+
)
|
|
449
484
|
for element in input_dict
|
|
450
485
|
]
|
|
451
486
|
return {
|
|
452
|
-
convert_to_snake_case(key):
|
|
453
|
-
|
|
454
|
-
|
|
487
|
+
convert_to_snake_case(key): (
|
|
488
|
+
convert_dict_to_snake_case(value)
|
|
489
|
+
if isinstance(value, (dict, list))
|
|
490
|
+
else value
|
|
491
|
+
)
|
|
455
492
|
for key, value in input_dict.items()
|
|
456
493
|
}
|
|
457
494
|
|
|
@@ -665,7 +702,7 @@ def open_cachefile(cache_file, append=False):
|
|
|
665
702
|
logger.info("OpenCacheFile: %s created", cache_file)
|
|
666
703
|
f = open(cache_file, "a+" if append else "r+")
|
|
667
704
|
return f
|
|
668
|
-
|
|
705
|
+
logger.error("OpenCacheFile: %s directory or does not exist", cache_dir)
|
|
669
706
|
|
|
670
707
|
|
|
671
708
|
def remove_cachefile(cache_file):
|
|
@@ -748,11 +785,11 @@ def open_url(url, timeout=None, verify=True, **kwargs):
|
|
|
748
785
|
urlobj = request.urlopen(req, timeout=timeout, context=ctx)
|
|
749
786
|
return urlobj
|
|
750
787
|
except request.HTTPError as e:
|
|
751
|
-
|
|
788
|
+
logger.error("HTTPError: %s for %s", str(e.code), url)
|
|
752
789
|
if e.code == 304:
|
|
753
790
|
return None
|
|
754
791
|
except request.URLError as e:
|
|
755
|
-
|
|
792
|
+
logger.error("URLError: %s for %s", str(e.reason), url)
|
|
756
793
|
raise Exception("GetURL Failed")
|
|
757
794
|
|
|
758
795
|
|
|
@@ -780,17 +817,17 @@ def check_url(url):
|
|
|
780
817
|
return True
|
|
781
818
|
|
|
782
819
|
except requests.exceptions.HTTPError:
|
|
783
|
-
|
|
820
|
+
logger.error(
|
|
784
821
|
"Requests: HTTPError: status code %s for %s",
|
|
785
822
|
str(resp.status_code) if resp else None,
|
|
786
823
|
url,
|
|
787
824
|
)
|
|
788
825
|
except requests.exceptions.Timeout:
|
|
789
|
-
|
|
826
|
+
logger.error("Requests: Timeout for %s", url)
|
|
790
827
|
except requests.exceptions.TooManyRedirects:
|
|
791
|
-
|
|
828
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
792
829
|
except requests.ConnectionError:
|
|
793
|
-
|
|
830
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
794
831
|
return False
|
|
795
832
|
|
|
796
833
|
|
|
@@ -810,7 +847,7 @@ def exec_command(command):
|
|
|
810
847
|
rcode = p.poll()
|
|
811
848
|
return {"stdout": stdout, "stderr": stderr, "rcode": rcode}
|
|
812
849
|
except OSError as e:
|
|
813
|
-
|
|
850
|
+
logger.error(
|
|
814
851
|
"SubprocessError: %s %s: %s", str(e.filename), str(e.strerror), str(e.errno)
|
|
815
852
|
)
|
|
816
853
|
raise Exception("ExecCommand")
|
|
@@ -836,7 +873,7 @@ def encode_file(file_path):
|
|
|
836
873
|
return efile
|
|
837
874
|
return None
|
|
838
875
|
except (IOError, OSError) as e:
|
|
839
|
-
|
|
876
|
+
logger.error("EncodeFile: Failed to open file: %s", e.strerror)
|
|
840
877
|
raise Exception("EncodeFile")
|
|
841
878
|
finally:
|
|
842
879
|
if isinstance(f, IOBase):
|
|
@@ -859,17 +896,17 @@ def check_url_modified(url):
|
|
|
859
896
|
if resp.status_code == 200:
|
|
860
897
|
return True
|
|
861
898
|
except requests.exceptions.HTTPError:
|
|
862
|
-
|
|
899
|
+
logger.error(
|
|
863
900
|
"Requests: HTTPError: status code %s for %s",
|
|
864
901
|
str(resp.status_code) if resp else None,
|
|
865
902
|
url,
|
|
866
903
|
)
|
|
867
904
|
except requests.exceptions.Timeout:
|
|
868
|
-
|
|
905
|
+
logger.error("Requests: Timeout for %s", url)
|
|
869
906
|
except requests.exceptions.TooManyRedirects:
|
|
870
|
-
|
|
907
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
871
908
|
except requests.ConnectionError:
|
|
872
|
-
|
|
909
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
873
910
|
return False
|
|
874
911
|
|
|
875
912
|
|
|
@@ -895,7 +932,7 @@ def get_url_path_filename(url):
|
|
|
895
932
|
if name[n].endswith("."):
|
|
896
933
|
return name
|
|
897
934
|
except IndexError:
|
|
898
|
-
|
|
935
|
+
logger.error("Range: IndexError: URL basename is short: %s of %s", name, url)
|
|
899
936
|
return None
|
|
900
937
|
return None
|
|
901
938
|
|
|
@@ -915,16 +952,16 @@ def get_url_filename(url):
|
|
|
915
952
|
return name
|
|
916
953
|
return None
|
|
917
954
|
except requests.exceptions.MissingSchema:
|
|
918
|
-
|
|
955
|
+
logger.error("Requests: MissingSchema: Requires ftp|http(s):// for %s", url)
|
|
919
956
|
except requests.exceptions.HTTPError:
|
|
920
|
-
|
|
957
|
+
logger.error(
|
|
921
958
|
"Requests: HTTPError: status code %s for %s",
|
|
922
959
|
str(resp.status_code) if resp else None,
|
|
923
960
|
url,
|
|
924
961
|
)
|
|
925
962
|
except requests.exceptions.Timeout:
|
|
926
|
-
|
|
963
|
+
logger.error("Requests: Timeout for %s", url)
|
|
927
964
|
except requests.exceptions.TooManyRedirects:
|
|
928
|
-
|
|
965
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
929
966
|
except requests.ConnectionError:
|
|
930
|
-
|
|
967
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: insightconnect-plugin-runtime
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.2
|
|
4
4
|
Summary: InsightConnect Plugin Runtime
|
|
5
5
|
Home-page: https://github.com/rapid7/komand-plugin-sdk-python
|
|
6
6
|
Author: Rapid7 Integrations Alliance
|
|
@@ -211,6 +211,7 @@ contributed. Black is installed as a test dependency and the hook can be initial
|
|
|
211
211
|
after cloning this repository.
|
|
212
212
|
|
|
213
213
|
## Changelog
|
|
214
|
+
* 6.2.2 - Fix instances where logging errors would lead to duplicate entries being output | Add option to hash only on provided keys for `hash_sha1` function
|
|
214
215
|
* 6.2.1 - Fix instances where logging would lead to duplicate entries being output
|
|
215
216
|
* 6.2.0 - Update base images to pull Python 3.11.10 | changed the pep-8 check in tox to `pycodestyle`
|
|
216
217
|
* 6.1.4 - Address vulnerabilities within local development requirements.txt and vulnerabilities in slim image.
|
|
@@ -4,7 +4,7 @@ insightconnect_plugin_runtime/cli.py,sha256=Pb-Janu-XfRlSXxPHh30OIquljWptrhhS51C
|
|
|
4
4
|
insightconnect_plugin_runtime/connection.py,sha256=4bHHV2B0UFGsAtvLu1fiYQRwx7fissUakHPUyjLQO0E,2340
|
|
5
5
|
insightconnect_plugin_runtime/dispatcher.py,sha256=ru7njnyyWE1-oD-VbZJ-Z8tELwvDf69rM7Iezs4rbnw,1774
|
|
6
6
|
insightconnect_plugin_runtime/exceptions.py,sha256=Pvcdkx81o6qC2qU661x-DzNjuIMP82x52nPMSEqEo4s,8491
|
|
7
|
-
insightconnect_plugin_runtime/helper.py,sha256=
|
|
7
|
+
insightconnect_plugin_runtime/helper.py,sha256=WiCFu4S33BL8wugBmyFH-06inkcXPmdC5cv0UGlSHfA,32646
|
|
8
8
|
insightconnect_plugin_runtime/metrics.py,sha256=hf_Aoufip_s4k4o8Gtzz90ymZthkaT2e5sXh5B4LcF0,3186
|
|
9
9
|
insightconnect_plugin_runtime/plugin.py,sha256=Yf4LNczykDVc31F9G8uuJ9gxEsgmxmAr0n4pcZzichM,26393
|
|
10
10
|
insightconnect_plugin_runtime/schema.py,sha256=jTNc6KAMqFpaDVWrAYhkVC6e8I63P3X7uVlJkAr1hiY,583
|
|
@@ -68,7 +68,7 @@ tests/unit/test_aws_action.py,sha256=pBE23Qn4aXKJqPmwiHMcEU5zPdyvbKO-eK-6jUlrsQw
|
|
|
68
68
|
tests/unit/test_custom_encoder.py,sha256=KLYyVOTq9MEkZXyhVHqjm5LVSW6uJS4Davgghsw9DGk,2207
|
|
69
69
|
tests/unit/test_endpoints.py,sha256=LuXOfLBu47rDjGa5YEsOwTZBEdvQdl_C6-r46oxWZA8,6401
|
|
70
70
|
tests/unit/test_exceptions.py,sha256=Y4F-ij8WkEJkUU3mPvxlEchqE9NCdxDvR8bJzPVVNao,5328
|
|
71
|
-
tests/unit/test_helpers.py,sha256=
|
|
71
|
+
tests/unit/test_helpers.py,sha256=9Y5N5cUBtesfr289oOZFekMJb84VYuFjucqQ9VEk3WQ,17431
|
|
72
72
|
tests/unit/test_metrics.py,sha256=PjjTrB9w7uQ2Q5UN-893-SsH3EGJuBseOMHSD1I004s,7979
|
|
73
73
|
tests/unit/test_oauth.py,sha256=nbFG0JH1x04ExXqSe-b5BGdt_hJs7DP17eUa6bQzcYI,2093
|
|
74
74
|
tests/unit/test_plugin.py,sha256=ZTNAZWwZhDIAbxkVuWhnz9FzmojbijgMmsLWM2mXQI0,4160
|
|
@@ -78,7 +78,7 @@ tests/unit/test_server_spec.py,sha256=je97BaktgK0Fiz3AwFPkcmHzYtOJJNqJV_Fw5hrvqX
|
|
|
78
78
|
tests/unit/test_trigger.py,sha256=E53mAUoVyponWu_4IQZ0IC1gQ9lakBnTn_9vKN2IZfg,1692
|
|
79
79
|
tests/unit/test_variables.py,sha256=OUEOqGYZA3Nd5oKk5GVY3hcrWKHpZpxysBJcO_v5gzs,291
|
|
80
80
|
tests/unit/utils.py,sha256=VooVmfpIgxmglNdtmT32AkEDFxHxyRHLK8RsCWjjYRY,2153
|
|
81
|
-
insightconnect_plugin_runtime-6.2.
|
|
82
|
-
insightconnect_plugin_runtime-6.2.
|
|
83
|
-
insightconnect_plugin_runtime-6.2.
|
|
84
|
-
insightconnect_plugin_runtime-6.2.
|
|
81
|
+
insightconnect_plugin_runtime-6.2.2.dist-info/METADATA,sha256=aQsntlkFksgMSHJvZyCnixCTdVSbzypDrq81UQVzBFU,15275
|
|
82
|
+
insightconnect_plugin_runtime-6.2.2.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
83
|
+
insightconnect_plugin_runtime-6.2.2.dist-info/top_level.txt,sha256=AJtyJOpiFzHxsbHUICTcUKXyrGQ3tZxhrEHsPjJBvEA,36
|
|
84
|
+
insightconnect_plugin_runtime-6.2.2.dist-info/RECORD,,
|
tests/unit/test_helpers.py
CHANGED
|
@@ -510,20 +510,46 @@ class TestRequestsHelpers(TestCase):
|
|
|
510
510
|
)
|
|
511
511
|
|
|
512
512
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
513
|
+
class TestHashing(TestCase):
|
|
514
|
+
def setUp(self) -> None:
|
|
515
|
+
self.log = {"example": "value", "sample": "value"}
|
|
516
|
+
|
|
517
|
+
def test_hash_sha1_no_keys(self):
|
|
518
|
+
# Test hash with no keys provided
|
|
519
|
+
expected_hash = "2e1ccc1a95e9b2044f13546c25fe380bbd039293"
|
|
520
|
+
self.assertEqual(helper.hash_sha1(self.log), expected_hash)
|
|
521
|
+
|
|
522
|
+
def test_hash_sha1_keys(self):
|
|
523
|
+
# Test hash with valid key provided
|
|
524
|
+
expected_hash = "61c908e52d66a763ceed0798b8e5f4b7f0328a21"
|
|
525
|
+
self.assertEqual(helper.hash_sha1(self.log, keys=["example"]), expected_hash)
|
|
526
|
+
|
|
527
|
+
def test_hash_sha1_keys_wrong_type(self):
|
|
528
|
+
# Test hash with wrong type for keys
|
|
529
|
+
with self.assertRaises(TypeError) as context:
|
|
530
|
+
helper.hash_sha1(self.log, keys="test")
|
|
531
|
+
|
|
532
|
+
self.assertEqual(
|
|
533
|
+
str(context.exception),
|
|
534
|
+
"The 'keys' parameter must be a list or None in the 'hash_sha1' function, not str"
|
|
535
|
+
)
|
|
536
|
+
|
|
537
|
+
def test_hash_sha1_keys_not_found(self):
|
|
538
|
+
# Test hash with key not found
|
|
539
|
+
with self.assertRaises(KeyError) as context:
|
|
540
|
+
helper.hash_sha1(self.log, keys=["example", "test"])
|
|
541
|
+
|
|
542
|
+
self.assertEqual(str(context.exception), "\"Key 'test' not found in the provided log.\"")
|
|
543
|
+
|
|
544
|
+
def test_compare_and_dedupe_hashes(self):
|
|
545
|
+
hashes = ["2e1ccc1a95e9b2044f13546c25fe380bbd039293"]
|
|
546
|
+
logs = [
|
|
547
|
+
{
|
|
548
|
+
"example": "value",
|
|
549
|
+
"sample": "value",
|
|
550
|
+
},
|
|
551
|
+
{"specimen": "new_value"},
|
|
552
|
+
]
|
|
553
|
+
assert [{"specimen": "new_value"}], [
|
|
554
|
+
"ad6ae80c0356e02b1561cb58408ee678eb1070bb"
|
|
555
|
+
] == helper.compare_and_dedupe_hashes(hashes, logs)
|
|
File without changes
|
|
File without changes
|