insightconnect-plugin-runtime 6.2.1__py3-none-any.whl → 6.2.4__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/api/endpoints.py +14 -15
- insightconnect_plugin_runtime/helper.py +86 -32
- insightconnect_plugin_runtime/schema.py +6 -9
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.4.dist-info}/METADATA +26 -15
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.4.dist-info}/RECORD +9 -9
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.4.dist-info}/WHEEL +1 -1
- tests/unit/test_helpers.py +66 -16
- tests/unit/utils.py +1 -0
- {insightconnect_plugin_runtime-6.2.1.dist-info → insightconnect_plugin_runtime-6.2.4.dist-info}/top_level.txt +0 -0
|
@@ -1,27 +1,27 @@
|
|
|
1
|
+
import importlib.metadata as importlib_metadata
|
|
1
2
|
import json
|
|
2
|
-
import subprocess
|
|
3
|
-
import yaml
|
|
4
3
|
import os
|
|
5
|
-
import pkg_resources
|
|
6
4
|
import signal
|
|
7
|
-
|
|
8
|
-
from werkzeug.exceptions import InternalServerError, HTTPException
|
|
5
|
+
import subprocess
|
|
9
6
|
from typing import Any, Dict
|
|
10
7
|
|
|
11
8
|
import structlog
|
|
9
|
+
import yaml
|
|
10
|
+
from flask import Blueprint, abort, jsonify, make_response, request
|
|
11
|
+
from werkzeug.exceptions import HTTPException, InternalServerError
|
|
12
12
|
|
|
13
|
+
from insightconnect_plugin_runtime.api.schemas import (
|
|
14
|
+
ActionTriggerDetailsSchema,
|
|
15
|
+
ConnectionDetailsSchema,
|
|
16
|
+
PluginInfoSchema,
|
|
17
|
+
TaskDetailsSchema,
|
|
18
|
+
)
|
|
13
19
|
from insightconnect_plugin_runtime.exceptions import (
|
|
14
20
|
ClientException,
|
|
15
|
-
ServerException,
|
|
16
|
-
LoggedException,
|
|
17
21
|
ConnectionTestException,
|
|
22
|
+
LoggedException,
|
|
18
23
|
PluginException,
|
|
19
|
-
|
|
20
|
-
from insightconnect_plugin_runtime.api.schemas import (
|
|
21
|
-
PluginInfoSchema,
|
|
22
|
-
ActionTriggerDetailsSchema,
|
|
23
|
-
TaskDetailsSchema,
|
|
24
|
-
ConnectionDetailsSchema,
|
|
24
|
+
ServerException,
|
|
25
25
|
)
|
|
26
26
|
from insightconnect_plugin_runtime.util import OutputMasker
|
|
27
27
|
|
|
@@ -780,11 +780,10 @@ class Endpoints:
|
|
|
780
780
|
|
|
781
781
|
def get_plugin_sdk_version(self):
|
|
782
782
|
try:
|
|
783
|
-
version =
|
|
783
|
+
version = importlib_metadata.version("insightconnect-plugin-runtime")
|
|
784
784
|
except Exception:
|
|
785
785
|
self.logger.warn("Unable to get SDK version")
|
|
786
786
|
version = "0.0.0"
|
|
787
|
-
|
|
788
787
|
return version
|
|
789
788
|
|
|
790
789
|
def add_plugin_custom_config(
|
|
@@ -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
|
|
@@ -21,6 +21,7 @@ from insightconnect_plugin_runtime.exceptions import (
|
|
|
21
21
|
PluginException,
|
|
22
22
|
HTTPStatusCodes,
|
|
23
23
|
ResponseExceptionData,
|
|
24
|
+
APIException
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
CAMEL_CASE_REGEX = r"\b[a-z0-9]+([A-Z][a-z]+[0-9]*)*\b"
|
|
@@ -33,17 +34,45 @@ DEFAULTS_HOURS_AGO = 24
|
|
|
33
34
|
logger = logging.getLogger()
|
|
34
35
|
|
|
35
36
|
|
|
36
|
-
def hash_sha1(log: Dict) -> str:
|
|
37
|
+
def hash_sha1(log: Dict[str, Any], keys: Optional[List[str]] = None) -> str:
|
|
37
38
|
"""
|
|
38
39
|
Iterate through a dictionary and hash each value.
|
|
40
|
+
Optionally only hash certain keys in the dictionary.
|
|
41
|
+
|
|
39
42
|
:param log: Dictionary to be hashed.
|
|
40
|
-
:
|
|
43
|
+
:param keys: Optional list of keys to hash on if provided
|
|
44
|
+
|
|
41
45
|
:return: Hex digest of hash.
|
|
42
|
-
:rtype: str
|
|
43
46
|
"""
|
|
47
|
+
|
|
44
48
|
hash_ = sha1() # nosec B303
|
|
45
|
-
|
|
49
|
+
|
|
50
|
+
# Leaving no room for developer error and ensuring they know exactly where it went wrong
|
|
51
|
+
# if they provide a key not in list format
|
|
52
|
+
if keys is not None and not isinstance(keys, list):
|
|
53
|
+
raise TypeError(
|
|
54
|
+
f"The 'keys' parameter must be a list or None in the 'hash_sha1' function, not {type(keys).__name__}"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
# Hash all key-value pairs if no keys provided
|
|
58
|
+
if keys is None:
|
|
59
|
+
items_to_hash = log.items()
|
|
60
|
+
|
|
61
|
+
# Otherwise, only include specified keys
|
|
62
|
+
else:
|
|
63
|
+
items_to_hash = []
|
|
64
|
+
for key in keys:
|
|
65
|
+
if key in log:
|
|
66
|
+
items_to_hash.append((key, log[key]))
|
|
67
|
+
|
|
68
|
+
# Alert if the key is not found in the log
|
|
69
|
+
else:
|
|
70
|
+
raise KeyError(f"Key '{key}' not found in the provided log.")
|
|
71
|
+
|
|
72
|
+
# Iterate through items to hash and hash
|
|
73
|
+
for key, value in items_to_hash:
|
|
46
74
|
hash_.update(f"{key}{value}".encode(ENCODE_TYPE))
|
|
75
|
+
|
|
47
76
|
return hash_.hexdigest()
|
|
48
77
|
|
|
49
78
|
|
|
@@ -83,6 +112,7 @@ def make_request(
|
|
|
83
112
|
exception_custom_configs: Dict[int, Exception] = {},
|
|
84
113
|
exception_data_location: str = None,
|
|
85
114
|
allowed_status_codes: List[str] = [],
|
|
115
|
+
max_response_size: int = None,
|
|
86
116
|
) -> Tuple[requests.Response, Dict]:
|
|
87
117
|
"""
|
|
88
118
|
Makes a HTTP request while checking for RequestErrors and JSONDecodeErrors
|
|
@@ -105,6 +135,8 @@ def make_request(
|
|
|
105
135
|
:type str:
|
|
106
136
|
:param allowed_status_codes: Status codes that will not raise an exception.
|
|
107
137
|
:type List[str]:
|
|
138
|
+
:param max_response_size: Raise an error if the stream content is bigger than this specified size
|
|
139
|
+
:type int:
|
|
108
140
|
|
|
109
141
|
:return: The request response and the response JSON.
|
|
110
142
|
:rtype: Tuple[Response, Dict]
|
|
@@ -120,6 +152,19 @@ def make_request(
|
|
|
120
152
|
cert=cert,
|
|
121
153
|
stream=stream,
|
|
122
154
|
)
|
|
155
|
+
|
|
156
|
+
# Before we close this session check and download all content, check if the returned content is too large.
|
|
157
|
+
# This may not be supported on all APIs as they need to support streaming and return the content-length
|
|
158
|
+
# header but adding this extra check will be beneficial for memory usage to those that do support it.
|
|
159
|
+
if stream and max_response_size:
|
|
160
|
+
resp_size = response.headers.get("content-length", "0")
|
|
161
|
+
if int(resp_size) > max_response_size:
|
|
162
|
+
raise APIException(
|
|
163
|
+
status_code=400,
|
|
164
|
+
cause=f"API response is exceeding allowed limit of {max_response_size} bytes.",
|
|
165
|
+
assistance="Please update the parameters to reduce the size of the data being returned.",
|
|
166
|
+
data=f"Content length returned was {resp_size} and max allowed is {max_response_size}",
|
|
167
|
+
)
|
|
123
168
|
except requests.exceptions.Timeout as exception:
|
|
124
169
|
raise PluginException(
|
|
125
170
|
preset=PluginException.Preset.TIMEOUT, data=str(exception)
|
|
@@ -137,7 +182,12 @@ def make_request(
|
|
|
137
182
|
raise PluginException(
|
|
138
183
|
preset=PluginException.Preset.UNKNOWN, data=str(exception)
|
|
139
184
|
)
|
|
140
|
-
response_handler(
|
|
185
|
+
response_handler(
|
|
186
|
+
response,
|
|
187
|
+
exception_custom_configs,
|
|
188
|
+
exception_data_location,
|
|
189
|
+
allowed_status_codes,
|
|
190
|
+
)
|
|
141
191
|
return response
|
|
142
192
|
|
|
143
193
|
|
|
@@ -192,7 +242,7 @@ def request_error_handling(
|
|
|
192
242
|
exception.response,
|
|
193
243
|
data_location=exception_data_location,
|
|
194
244
|
custom_configs=custom_configs,
|
|
195
|
-
allowed_status_codes=allowed_status_codes
|
|
245
|
+
allowed_status_codes=allowed_status_codes,
|
|
196
246
|
)
|
|
197
247
|
else:
|
|
198
248
|
raise PluginException(
|
|
@@ -443,15 +493,19 @@ def convert_dict_to_snake_case(
|
|
|
443
493
|
|
|
444
494
|
if isinstance(input_dict, list):
|
|
445
495
|
return [
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
496
|
+
(
|
|
497
|
+
convert_dict_to_snake_case(element)
|
|
498
|
+
if isinstance(element, (dict, list))
|
|
499
|
+
else element
|
|
500
|
+
)
|
|
449
501
|
for element in input_dict
|
|
450
502
|
]
|
|
451
503
|
return {
|
|
452
|
-
convert_to_snake_case(key):
|
|
453
|
-
|
|
454
|
-
|
|
504
|
+
convert_to_snake_case(key): (
|
|
505
|
+
convert_dict_to_snake_case(value)
|
|
506
|
+
if isinstance(value, (dict, list))
|
|
507
|
+
else value
|
|
508
|
+
)
|
|
455
509
|
for key, value in input_dict.items()
|
|
456
510
|
}
|
|
457
511
|
|
|
@@ -665,7 +719,7 @@ def open_cachefile(cache_file, append=False):
|
|
|
665
719
|
logger.info("OpenCacheFile: %s created", cache_file)
|
|
666
720
|
f = open(cache_file, "a+" if append else "r+")
|
|
667
721
|
return f
|
|
668
|
-
|
|
722
|
+
logger.error("OpenCacheFile: %s directory or does not exist", cache_dir)
|
|
669
723
|
|
|
670
724
|
|
|
671
725
|
def remove_cachefile(cache_file):
|
|
@@ -748,11 +802,11 @@ def open_url(url, timeout=None, verify=True, **kwargs):
|
|
|
748
802
|
urlobj = request.urlopen(req, timeout=timeout, context=ctx)
|
|
749
803
|
return urlobj
|
|
750
804
|
except request.HTTPError as e:
|
|
751
|
-
|
|
805
|
+
logger.error("HTTPError: %s for %s", str(e.code), url)
|
|
752
806
|
if e.code == 304:
|
|
753
807
|
return None
|
|
754
808
|
except request.URLError as e:
|
|
755
|
-
|
|
809
|
+
logger.error("URLError: %s for %s", str(e.reason), url)
|
|
756
810
|
raise Exception("GetURL Failed")
|
|
757
811
|
|
|
758
812
|
|
|
@@ -780,17 +834,17 @@ def check_url(url):
|
|
|
780
834
|
return True
|
|
781
835
|
|
|
782
836
|
except requests.exceptions.HTTPError:
|
|
783
|
-
|
|
837
|
+
logger.error(
|
|
784
838
|
"Requests: HTTPError: status code %s for %s",
|
|
785
839
|
str(resp.status_code) if resp else None,
|
|
786
840
|
url,
|
|
787
841
|
)
|
|
788
842
|
except requests.exceptions.Timeout:
|
|
789
|
-
|
|
843
|
+
logger.error("Requests: Timeout for %s", url)
|
|
790
844
|
except requests.exceptions.TooManyRedirects:
|
|
791
|
-
|
|
845
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
792
846
|
except requests.ConnectionError:
|
|
793
|
-
|
|
847
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
794
848
|
return False
|
|
795
849
|
|
|
796
850
|
|
|
@@ -810,7 +864,7 @@ def exec_command(command):
|
|
|
810
864
|
rcode = p.poll()
|
|
811
865
|
return {"stdout": stdout, "stderr": stderr, "rcode": rcode}
|
|
812
866
|
except OSError as e:
|
|
813
|
-
|
|
867
|
+
logger.error(
|
|
814
868
|
"SubprocessError: %s %s: %s", str(e.filename), str(e.strerror), str(e.errno)
|
|
815
869
|
)
|
|
816
870
|
raise Exception("ExecCommand")
|
|
@@ -836,7 +890,7 @@ def encode_file(file_path):
|
|
|
836
890
|
return efile
|
|
837
891
|
return None
|
|
838
892
|
except (IOError, OSError) as e:
|
|
839
|
-
|
|
893
|
+
logger.error("EncodeFile: Failed to open file: %s", e.strerror)
|
|
840
894
|
raise Exception("EncodeFile")
|
|
841
895
|
finally:
|
|
842
896
|
if isinstance(f, IOBase):
|
|
@@ -859,17 +913,17 @@ def check_url_modified(url):
|
|
|
859
913
|
if resp.status_code == 200:
|
|
860
914
|
return True
|
|
861
915
|
except requests.exceptions.HTTPError:
|
|
862
|
-
|
|
916
|
+
logger.error(
|
|
863
917
|
"Requests: HTTPError: status code %s for %s",
|
|
864
918
|
str(resp.status_code) if resp else None,
|
|
865
919
|
url,
|
|
866
920
|
)
|
|
867
921
|
except requests.exceptions.Timeout:
|
|
868
|
-
|
|
922
|
+
logger.error("Requests: Timeout for %s", url)
|
|
869
923
|
except requests.exceptions.TooManyRedirects:
|
|
870
|
-
|
|
924
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
871
925
|
except requests.ConnectionError:
|
|
872
|
-
|
|
926
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
873
927
|
return False
|
|
874
928
|
|
|
875
929
|
|
|
@@ -895,7 +949,7 @@ def get_url_path_filename(url):
|
|
|
895
949
|
if name[n].endswith("."):
|
|
896
950
|
return name
|
|
897
951
|
except IndexError:
|
|
898
|
-
|
|
952
|
+
logger.error("Range: IndexError: URL basename is short: %s of %s", name, url)
|
|
899
953
|
return None
|
|
900
954
|
return None
|
|
901
955
|
|
|
@@ -915,16 +969,16 @@ def get_url_filename(url):
|
|
|
915
969
|
return name
|
|
916
970
|
return None
|
|
917
971
|
except requests.exceptions.MissingSchema:
|
|
918
|
-
|
|
972
|
+
logger.error("Requests: MissingSchema: Requires ftp|http(s):// for %s", url)
|
|
919
973
|
except requests.exceptions.HTTPError:
|
|
920
|
-
|
|
974
|
+
logger.error(
|
|
921
975
|
"Requests: HTTPError: status code %s for %s",
|
|
922
976
|
str(resp.status_code) if resp else None,
|
|
923
977
|
url,
|
|
924
978
|
)
|
|
925
979
|
except requests.exceptions.Timeout:
|
|
926
|
-
|
|
980
|
+
logger.error("Requests: Timeout for %s", url)
|
|
927
981
|
except requests.exceptions.TooManyRedirects:
|
|
928
|
-
|
|
982
|
+
logger.error("Requests: TooManyRedirects for %s", url)
|
|
929
983
|
except requests.ConnectionError:
|
|
930
|
-
|
|
984
|
+
logger.error("Requests: ConnectionError for %s", url)
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
|
-
import pkg_resources
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
def load_schema(file_name):
|
|
5
|
+
def load_schema(file_name: str) -> dict:
|
|
7
6
|
"""
|
|
8
7
|
Loads a json schema from the packages data folder.
|
|
9
8
|
:param file_name: name of the file
|
|
10
9
|
:return: JSON object as a dictionary
|
|
11
10
|
"""
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
schema = json.loads(schema)
|
|
18
|
-
return schema
|
|
12
|
+
with open(
|
|
13
|
+
Path(__file__).parent / "data" / file_name, "r", encoding="utf-8"
|
|
14
|
+
) as schema_file:
|
|
15
|
+
return json.loads(schema_file.read())
|
|
19
16
|
|
|
20
17
|
|
|
21
18
|
input_message_schema = load_schema("input_message_schema.json")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: insightconnect-plugin-runtime
|
|
3
|
-
Version: 6.2.
|
|
3
|
+
Version: 6.2.4
|
|
4
4
|
Summary: InsightConnect Plugin Runtime
|
|
5
5
|
Home-page: https://github.com/rapid7/komand-plugin-sdk-python
|
|
6
6
|
Author: Rapid7 Integrations Alliance
|
|
@@ -12,20 +12,28 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
12
12
|
Classifier: Natural Language :: English
|
|
13
13
|
Classifier: Topic :: Software Development :: Build Tools
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
|
-
Requires-Dist: requests==2.32.
|
|
15
|
+
Requires-Dist: requests==2.32.3
|
|
16
16
|
Requires-Dist: python_jsonschema_objects==0.5.2
|
|
17
|
-
Requires-Dist: jsonschema==4.
|
|
18
|
-
Requires-Dist: certifi==2024.
|
|
19
|
-
Requires-Dist: Flask==3.0
|
|
20
|
-
Requires-Dist: gunicorn==
|
|
17
|
+
Requires-Dist: jsonschema==4.22.0
|
|
18
|
+
Requires-Dist: certifi==2024.12.14
|
|
19
|
+
Requires-Dist: Flask==3.1.0
|
|
20
|
+
Requires-Dist: gunicorn==23.0.0
|
|
21
21
|
Requires-Dist: greenlet==3.1.1
|
|
22
|
-
Requires-Dist: gevent==24.
|
|
22
|
+
Requires-Dist: gevent==24.11.1
|
|
23
23
|
Requires-Dist: marshmallow==3.21.0
|
|
24
24
|
Requires-Dist: apispec==6.5.0
|
|
25
25
|
Requires-Dist: apispec-webframeworks==1.0.0
|
|
26
|
-
Requires-Dist: blinker==1.
|
|
27
|
-
Requires-Dist: structlog==24.
|
|
26
|
+
Requires-Dist: blinker==1.9.0
|
|
27
|
+
Requires-Dist: structlog==24.4.0
|
|
28
28
|
Requires-Dist: python-json-logger==2.0.7
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: author-email
|
|
31
|
+
Dynamic: classifier
|
|
32
|
+
Dynamic: description
|
|
33
|
+
Dynamic: description-content-type
|
|
34
|
+
Dynamic: home-page
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: summary
|
|
29
37
|
|
|
30
38
|
|
|
31
39
|
# InsightConnect Python Plugin Runtime 
|
|
@@ -48,10 +56,10 @@ to get started.
|
|
|
48
56
|
|
|
49
57
|
## Development of the InsightConnect Plugin Runtime
|
|
50
58
|
|
|
51
|
-
The Python Runtime codebase is built to support Python 3.11.
|
|
59
|
+
The Python Runtime codebase is built to support Python 3.11.11 as of version 6.2.3. The following dependencies will need
|
|
52
60
|
to be installed when developing or testing the Plugin Runtime:
|
|
53
61
|
|
|
54
|
-
- Python 3.11.
|
|
62
|
+
- Python 3.11.11
|
|
55
63
|
- Docker
|
|
56
64
|
- make
|
|
57
65
|
- tox
|
|
@@ -67,7 +75,7 @@ version and activate it. Then build, install, and confirm the package has been i
|
|
|
67
75
|
> source venv/bin/activate
|
|
68
76
|
> pip install -e ./
|
|
69
77
|
> pip list | grep insightconnect-plugin-runtime
|
|
70
|
-
insightconnect-plugin-runtime
|
|
78
|
+
insightconnect-plugin-runtime 6.2.3
|
|
71
79
|
```
|
|
72
80
|
|
|
73
81
|
#### Building the InsightConnect Plugin Runtime Docker Images
|
|
@@ -123,7 +131,7 @@ name as a parameter:
|
|
|
123
131
|
|
|
124
132
|
The plugin will be started in `http` mode and listening at `http:0.0.0.0:10001`:
|
|
125
133
|
```
|
|
126
|
-
[2020-02-13 23:21:13 -0500] [56567] [INFO] Starting gunicorn
|
|
134
|
+
[2020-02-13 23:21:13 -0500] [56567] [INFO] Starting gunicorn 23.0.0
|
|
127
135
|
[2020-02-13 23:21:13 -0500] [56567] [INFO] Listening at: http://0.0.0.0:10001 (56567)
|
|
128
136
|
[2020-02-13 23:21:13 -0500] [56567] [INFO] Using worker: threads
|
|
129
137
|
[2020-02-13 23:21:13 -0500] [56571] [INFO] Booting worker with pid: 56571
|
|
@@ -184,7 +192,7 @@ Running a specific test file:
|
|
|
184
192
|
|
|
185
193
|
| | Plugin | Slim Plugin |
|
|
186
194
|
|:------------------|:-------:|:-----------:|
|
|
187
|
-
| Python Version | 3.11.
|
|
195
|
+
| Python Version | 3.11.11 | 3.11.11 |
|
|
188
196
|
| OS | Alpine | Bullseye |
|
|
189
197
|
| Package installer | apk | apt |
|
|
190
198
|
| Shell | /bin/sh | /bin/bash |
|
|
@@ -211,6 +219,9 @@ contributed. Black is installed as a test dependency and the hook can be initial
|
|
|
211
219
|
after cloning this repository.
|
|
212
220
|
|
|
213
221
|
## Changelog
|
|
222
|
+
* 6.2.4 - Update `make_request` helper to support extra parameter of `max_response_size` to cap the response
|
|
223
|
+
* 6.2.3 - Updated dockerfiles for both `slim` and `full` SDK types to use Python 3.11.11 | Updated dependencies | Removed `pkg_resources` usage due to deprecation
|
|
224
|
+
* 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
225
|
* 6.2.1 - Fix instances where logging would lead to duplicate entries being output
|
|
215
226
|
* 6.2.0 - Update base images to pull Python 3.11.10 | changed the pep-8 check in tox to `pycodestyle`
|
|
216
227
|
* 6.1.4 - Address vulnerabilities within local development requirements.txt and vulnerabilities in slim image.
|
|
@@ -4,10 +4,10 @@ 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=B0XqAXmn8CT1KQ6i5IoWLQrQ_HVOvuKrIKFuj_npQ-g,33770
|
|
8
8
|
insightconnect_plugin_runtime/metrics.py,sha256=hf_Aoufip_s4k4o8Gtzz90ymZthkaT2e5sXh5B4LcF0,3186
|
|
9
9
|
insightconnect_plugin_runtime/plugin.py,sha256=Yf4LNczykDVc31F9G8uuJ9gxEsgmxmAr0n4pcZzichM,26393
|
|
10
|
-
insightconnect_plugin_runtime/schema.py,sha256=
|
|
10
|
+
insightconnect_plugin_runtime/schema.py,sha256=6MVw5hqGATU1VLgwfOWfPsP3hy1OnsugCTsgX8sknes,521
|
|
11
11
|
insightconnect_plugin_runtime/server.py,sha256=09fxsbKf2ZZvSqRP2Bv9e9-fspDyEFR8_YgIFeMnXqQ,12578
|
|
12
12
|
insightconnect_plugin_runtime/step.py,sha256=KdERg-789-s99IEKN61DR08naz-YPxyinPT0C_T81C4,855
|
|
13
13
|
insightconnect_plugin_runtime/task.py,sha256=d-H1EAzVnmSdDEJtXyIK5JySprxpF9cetVoFGtWlHrg,123
|
|
@@ -15,7 +15,7 @@ insightconnect_plugin_runtime/trigger.py,sha256=Zq3cy68N3QxAGbNZKCID6CZF05Zi7YD2
|
|
|
15
15
|
insightconnect_plugin_runtime/util.py,sha256=qPkZ3LA55nYuNYdansEbnCnBccQkpzIpp9NA1B64Kvw,8444
|
|
16
16
|
insightconnect_plugin_runtime/variables.py,sha256=7FjJGnU7KUR7m9o-_tRq7Q3KiaB1Pp0Apj1NGgOwrJk,3056
|
|
17
17
|
insightconnect_plugin_runtime/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
insightconnect_plugin_runtime/api/endpoints.py,sha256=
|
|
18
|
+
insightconnect_plugin_runtime/api/endpoints.py,sha256=8QQrxzW8jmQIkalud8fqYwB05uUw8sTiDNgO5ZekOCA,33353
|
|
19
19
|
insightconnect_plugin_runtime/api/schemas.py,sha256=jRmDrwLJTBl-iQOnyZkSwyJlCWg4eNjAnKfD9Eko4z0,2754
|
|
20
20
|
insightconnect_plugin_runtime/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
insightconnect_plugin_runtime/clients/aws_client.py,sha256=bgSK3Txr1YonDiUN5JFEZ5cAyCXFM14JjfMus6lRV8o,23017
|
|
@@ -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=ym1tFi1VSKmdPaHEAlMEl1S7Ibu9-LrqZ2oqJv7bfbE,18685
|
|
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
|
|
@@ -77,8 +77,8 @@ tests/unit/test_server_cloud_plugins.py,sha256=PuMDHTz3af6lR9QK1BtPScr7_cRbWheto
|
|
|
77
77
|
tests/unit/test_server_spec.py,sha256=je97BaktgK0Fiz3AwFPkcmHzYtOJJNqJV_Fw5hrvqX4,644
|
|
78
78
|
tests/unit/test_trigger.py,sha256=E53mAUoVyponWu_4IQZ0IC1gQ9lakBnTn_9vKN2IZfg,1692
|
|
79
79
|
tests/unit/test_variables.py,sha256=OUEOqGYZA3Nd5oKk5GVY3hcrWKHpZpxysBJcO_v5gzs,291
|
|
80
|
-
tests/unit/utils.py,sha256=
|
|
81
|
-
insightconnect_plugin_runtime-6.2.
|
|
82
|
-
insightconnect_plugin_runtime-6.2.
|
|
83
|
-
insightconnect_plugin_runtime-6.2.
|
|
84
|
-
insightconnect_plugin_runtime-6.2.
|
|
80
|
+
tests/unit/utils.py,sha256=hcY0A2H_DMgCDXUTvDtCXMdMvRjLQgTaGcTpATb8YG0,2236
|
|
81
|
+
insightconnect_plugin_runtime-6.2.4.dist-info/METADATA,sha256=zprsS3dKV5_7yPthb5aUPyXTAItb-fJt-8NQn9M98bA,15719
|
|
82
|
+
insightconnect_plugin_runtime-6.2.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
83
|
+
insightconnect_plugin_runtime-6.2.4.dist-info/top_level.txt,sha256=AJtyJOpiFzHxsbHUICTcUKXyrGQ3tZxhrEHsPjJBvEA,36
|
|
84
|
+
insightconnect_plugin_runtime-6.2.4.dist-info/RECORD,,
|
tests/unit/test_helpers.py
CHANGED
|
@@ -4,6 +4,7 @@ from insightconnect_plugin_runtime.exceptions import (
|
|
|
4
4
|
HTTPStatusCodes,
|
|
5
5
|
ResponseExceptionData,
|
|
6
6
|
PluginException,
|
|
7
|
+
APIException
|
|
7
8
|
)
|
|
8
9
|
import requests
|
|
9
10
|
import os
|
|
@@ -378,16 +379,39 @@ class TestRequestsHelpers(TestCase):
|
|
|
378
379
|
json={"sample": "value"},
|
|
379
380
|
headers={"Content-Type": "application/json"},
|
|
380
381
|
)
|
|
381
|
-
response = helper.make_request(_request=request)
|
|
382
|
+
response = helper.make_request(_request=request, stream=True, max_response_size=100)
|
|
382
383
|
expected = {
|
|
383
384
|
"json": {"example": "sample"},
|
|
384
385
|
"status_code": 200,
|
|
385
386
|
"content": b"example",
|
|
386
387
|
"url": "https://example.com/success",
|
|
388
|
+
"content-length": "1",
|
|
387
389
|
}
|
|
388
390
|
self.assertEqual(response.content, expected.get("content"))
|
|
389
391
|
self.assertEqual(response.status_code, expected.get("status_code"))
|
|
390
392
|
self.assertEqual(response.url, expected.get("url"))
|
|
393
|
+
self.assertEqual(response.headers.get("content-length"), expected.get("content-length"))
|
|
394
|
+
|
|
395
|
+
@patch("requests.Session.send")
|
|
396
|
+
def test_make_request_enforces_max_response_size(self, mocked_request):
|
|
397
|
+
returned_max_size, test_max_size = "5000", 1527
|
|
398
|
+
|
|
399
|
+
response = requests.Response()
|
|
400
|
+
response.headers = {"content-length": returned_max_size, "content-type": "application/json"}
|
|
401
|
+
mocked_request.return_value = response
|
|
402
|
+
|
|
403
|
+
test_request = requests.Request(method="GET", url="https://event_source.com/api/v1/logs")
|
|
404
|
+
|
|
405
|
+
with self.assertRaises(APIException) as api_err:
|
|
406
|
+
helper.make_request(_request=test_request, stream=True, max_response_size=test_max_size)
|
|
407
|
+
|
|
408
|
+
self.assertEqual(400, api_err.exception.status_code)
|
|
409
|
+
exp_err_cause = f"API response is exceeding allowed limit of {test_max_size} bytes."
|
|
410
|
+
exp_err_data = f"Content length returned was {returned_max_size} and max allowed is {test_max_size}"
|
|
411
|
+
self.assertEqual(400, api_err.exception.status_code)
|
|
412
|
+
self.assertEqual(exp_err_cause, api_err.exception.cause)
|
|
413
|
+
self.assertEqual(exp_err_data, api_err.exception.data)
|
|
414
|
+
|
|
391
415
|
|
|
392
416
|
@parameterized.expand(
|
|
393
417
|
[
|
|
@@ -510,20 +534,46 @@ class TestRequestsHelpers(TestCase):
|
|
|
510
534
|
)
|
|
511
535
|
|
|
512
536
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
537
|
+
class TestHashing(TestCase):
|
|
538
|
+
def setUp(self) -> None:
|
|
539
|
+
self.log = {"example": "value", "sample": "value"}
|
|
516
540
|
|
|
541
|
+
def test_hash_sha1_no_keys(self):
|
|
542
|
+
# Test hash with no keys provided
|
|
543
|
+
expected_hash = "2e1ccc1a95e9b2044f13546c25fe380bbd039293"
|
|
544
|
+
self.assertEqual(helper.hash_sha1(self.log), expected_hash)
|
|
517
545
|
|
|
518
|
-
def
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
546
|
+
def test_hash_sha1_keys(self):
|
|
547
|
+
# Test hash with valid key provided
|
|
548
|
+
expected_hash = "61c908e52d66a763ceed0798b8e5f4b7f0328a21"
|
|
549
|
+
self.assertEqual(helper.hash_sha1(self.log, keys=["example"]), expected_hash)
|
|
550
|
+
|
|
551
|
+
def test_hash_sha1_keys_wrong_type(self):
|
|
552
|
+
# Test hash with wrong type for keys
|
|
553
|
+
with self.assertRaises(TypeError) as context:
|
|
554
|
+
helper.hash_sha1(self.log, keys="test")
|
|
555
|
+
|
|
556
|
+
self.assertEqual(
|
|
557
|
+
str(context.exception),
|
|
558
|
+
"The 'keys' parameter must be a list or None in the 'hash_sha1' function, not str"
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def test_hash_sha1_keys_not_found(self):
|
|
562
|
+
# Test hash with key not found
|
|
563
|
+
with self.assertRaises(KeyError) as context:
|
|
564
|
+
helper.hash_sha1(self.log, keys=["example", "test"])
|
|
565
|
+
|
|
566
|
+
self.assertEqual(str(context.exception), "\"Key 'test' not found in the provided log.\"")
|
|
567
|
+
|
|
568
|
+
def test_compare_and_dedupe_hashes(self):
|
|
569
|
+
hashes = ["2e1ccc1a95e9b2044f13546c25fe380bbd039293"]
|
|
570
|
+
logs = [
|
|
571
|
+
{
|
|
572
|
+
"example": "value",
|
|
573
|
+
"sample": "value",
|
|
574
|
+
},
|
|
575
|
+
{"specimen": "new_value"},
|
|
576
|
+
]
|
|
577
|
+
assert [{"specimen": "new_value"}], [
|
|
578
|
+
"ad6ae80c0356e02b1561cb58408ee678eb1070bb"
|
|
579
|
+
] == helper.compare_and_dedupe_hashes(hashes, logs)
|
tests/unit/utils.py
CHANGED
|
File without changes
|