regscale-cli 6.20.8.0__py3-none-any.whl → 6.20.9.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.
Potentially problematic release.
This version of regscale-cli might be problematic. Click here for more details.
- regscale/_version.py +1 -1
- regscale/core/app/application.py +81 -17
- regscale/integrations/commercial/synqly/vulnerabilities.py +33 -0
- regscale/models/integration_models/cisa_kev_data.json +8 -8
- regscale/models/integration_models/synqly_models/capabilities.json +1 -1
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/RECORD +12 -12
- tests/regscale/core/test_app.py +419 -16
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.1.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
regscale/__init__.py,sha256=ZygAIkX6Nbjag1czWdQa-yP-GM1mBE_9ss21Xh__JFc,34
|
|
2
|
-
regscale/_version.py,sha256=
|
|
2
|
+
regscale/_version.py,sha256=WscC-yFJAjZatYGLMOYhguSuiAfPUuRtOOaPN3OZazg,1198
|
|
3
3
|
regscale/regscale.py,sha256=xcxnTwEwWgfO3Fnp0LVo32SZCJzAswq3WDZgm21nHnI,30914
|
|
4
4
|
regscale/airflow/__init__.py,sha256=yMwN0Bz4JbM0nl5qY_hPegxo_O2ilhTOL9PY5Njhn-s,270
|
|
5
5
|
regscale/airflow/click_dags.py,sha256=H3SUR5jkvInNMv1gu-VG-Ja_H-kH145CpQYNalWNAbE,4520
|
|
@@ -36,7 +36,7 @@ regscale/core/lazy_group.py,sha256=S2-nA5tzm47A929NOTqGkzrzKuZQDlq2OAPbNnG1W1Q,2
|
|
|
36
36
|
regscale/core/login.py,sha256=-8vy1HVAtv1iARnZh6uzYtwmx8VFYPwLYR0QAf1ttCk,2714
|
|
37
37
|
regscale/core/app/__init__.py,sha256=nGcCN1vWBAnZzoccIlt0jwWQdegCOrBWOB7LPhQkQSs,96
|
|
38
38
|
regscale/core/app/api.py,sha256=CSyUCV6haBAQ9IyE1FViJcAfTcoS5GJRaULwnRoAV9U,23499
|
|
39
|
-
regscale/core/app/application.py,sha256=
|
|
39
|
+
regscale/core/app/application.py,sha256=8s18603Q8DR0xni2c5Lv-NWzhmFynjD49tPlWkol2Lw,31407
|
|
40
40
|
regscale/core/app/logz.py,sha256=8AdBKmquv45JGi5fCMc38JqQg6-FpUONGmqfd5EGwrI,2583
|
|
41
41
|
regscale/core/app/internal/__init__.py,sha256=rod4nmE7rrYDdbuYF4mCWz49TK_2r-v4tWy1UHW63r0,4808
|
|
42
42
|
regscale/core/app/internal/admin_actions.py,sha256=hOdma7QGwFblmxEj9UTjiWWmZGUS5_ZFtxL2qZdhf-Q,7443
|
|
@@ -212,7 +212,7 @@ regscale/integrations/commercial/synqly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5J
|
|
|
212
212
|
regscale/integrations/commercial/synqly/assets.py,sha256=wAyoMaR5MizBifnKUnNrx6ZlnmneP07LEBjJuf1POZs,3329
|
|
213
213
|
regscale/integrations/commercial/synqly/edr.py,sha256=IvObVzz5Y2a8iNjCP9dTfKUEv2ZAboCzcFqxI00Y6Do,2978
|
|
214
214
|
regscale/integrations/commercial/synqly/ticketing.py,sha256=RoteG9YUvnArlB5Kvmysc3hPTbzVrvuI_f4CQQSfAy8,6890
|
|
215
|
-
regscale/integrations/commercial/synqly/vulnerabilities.py,sha256=
|
|
215
|
+
regscale/integrations/commercial/synqly/vulnerabilities.py,sha256=qPiw5oWOGQwZadkUIicgLZHwPSTNJpkDA39hooMCkGo,8868
|
|
216
216
|
regscale/integrations/commercial/tenablev2/__init__.py,sha256=UpSY_oww83kz9c7amdbptJKwDB1gAOBQDS-Q9WFp588,295
|
|
217
217
|
regscale/integrations/commercial/tenablev2/authenticate.py,sha256=VPTmxaVCaah2gJYNeU9P1KoQ734ohGQ-wcVy6JfqDTE,1247
|
|
218
218
|
regscale/integrations/commercial/tenablev2/commands.py,sha256=4pUfHv_a3ddbKiS_nQ0W6u86rKGzm9PQbEF67OfsE-4,27862
|
|
@@ -311,7 +311,7 @@ regscale/models/integration_models/azure_alerts.py,sha256=2etrpvcxa7jVQrc98bJlVG
|
|
|
311
311
|
regscale/models/integration_models/base64.py,sha256=sxV6O5qY1_TstJENX5jBPsSdQwmA83-NNhgJFunXiZE,570
|
|
312
312
|
regscale/models/integration_models/burp.py,sha256=FBEBkH3U0Q8vq71FFoWnvgLRF5Hkr9GYmQFmNNHFrVk,16932
|
|
313
313
|
regscale/models/integration_models/burp_models.py,sha256=UytDTAcCaxyu-knFkm_mEUH6UmWK3OTXKSC9Sc6OjVs,3669
|
|
314
|
-
regscale/models/integration_models/cisa_kev_data.json,sha256=
|
|
314
|
+
regscale/models/integration_models/cisa_kev_data.json,sha256=jrZaPTEY6JkF7QinRoRT91C7nohLCCM_hm68lOWjWyE,1243393
|
|
315
315
|
regscale/models/integration_models/defender_data.py,sha256=jsAcjKxiGmumGerj7xSWkFd6r__YpuKDnYX5o7xHDiE,2844
|
|
316
316
|
regscale/models/integration_models/defenderimport.py,sha256=OFwEH0Xu-HFLIZJZ8hP60Ov3lS8RR7KHEsw4wI8QnoE,5766
|
|
317
317
|
regscale/models/integration_models/drf.py,sha256=Aq7AdLa_CH97NrnR-CxaFI22JjVN9uCxVN7Z-BBUaNU,18896
|
|
@@ -341,7 +341,7 @@ regscale/models/integration_models/flat_file_importer/__init__.py,sha256=Z-Gzk6v
|
|
|
341
341
|
regscale/models/integration_models/sbom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
342
342
|
regscale/models/integration_models/sbom/cyclone_dx.py,sha256=0pFR0BWBrF5c8_cC_8mj2MXvNOMHOdHbBYXvTVfFAh8,4058
|
|
343
343
|
regscale/models/integration_models/synqly_models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
344
|
-
regscale/models/integration_models/synqly_models/capabilities.json,sha256=
|
|
344
|
+
regscale/models/integration_models/synqly_models/capabilities.json,sha256=N2aC05wJ8r--zES6HtrBUHAy-4VTUwoMd-FBoOCkEpQ,361465
|
|
345
345
|
regscale/models/integration_models/synqly_models/connector_types.py,sha256=8nxptkTexpskySnmL0obNAff_iu_fx6tJ7i1-4hJvao,461
|
|
346
346
|
regscale/models/integration_models/synqly_models/ocsf_mapper.py,sha256=e2kTOhWSNRnzbgMchMx-7c21pCgSv2DqWnxvajKEKJM,16960
|
|
347
347
|
regscale/models/integration_models/synqly_models/param.py,sha256=Xt5Zm6lC_VkLj7LF2qXo72TJZHysqttsp5ai0NCf1po,2643
|
|
@@ -492,7 +492,7 @@ tests/regscale/test_authorization.py,sha256=fls5ODCYiu0DdkwXFepO_GM-BP6tRaPmMCZW
|
|
|
492
492
|
tests/regscale/test_init.py,sha256=O_3Dh7s7ObycIZyd0-Y10NCTKdGnrnotcpU6CUB5pno,4180
|
|
493
493
|
tests/regscale/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
494
494
|
tests/regscale/core/test_api.py,sha256=25AsIT-jg8SrlkPeynUwwm4PvTOrHcRO2Ba_omnNLUY,7512
|
|
495
|
-
tests/regscale/core/test_app.py,sha256=
|
|
495
|
+
tests/regscale/core/test_app.py,sha256=4uoFFGRbyBMJCbxE9Ykevg6AUC8Vcu7qu1q-LR8KiN8,42228
|
|
496
496
|
tests/regscale/core/test_login.py,sha256=Kl7ySS8JU7SzhmupaDexeUH8VOMjtMRJvW8-CimxHqU,1166
|
|
497
497
|
tests/regscale/core/test_logz.py,sha256=Yf6tAthETLlYOEp3hee3ovDw-WnZ_6fTw3e1rjx4xSw,2621
|
|
498
498
|
tests/regscale/core/test_sbom_generator.py,sha256=lgzo1HRbkNIIDZIeKiM2JbbIYQsak0BpU0GlvbrcexM,2935
|
|
@@ -518,9 +518,9 @@ tests/regscale/models/test_regscale_model.py,sha256=ZsrEZkC4EtdIsoQuayn1xv2gEGcV
|
|
|
518
518
|
tests/regscale/models/test_report.py,sha256=eiSvS_zS0aVeL0HBvtmHVvEzcfF9ZFVn2twj5g8KttY,970
|
|
519
519
|
tests/regscale/models/test_tenable_integrations.py,sha256=PNJC2Zu6lv1xj7y6e1yOsz5FktSU3PRKb5x3n5YG3w0,4072
|
|
520
520
|
tests/regscale/models/test_user_model.py,sha256=e9olv28qBApgnvK6hFHOgXjUC-pkaV8aGDirEIWASL4,4427
|
|
521
|
-
regscale_cli-6.20.
|
|
522
|
-
regscale_cli-6.20.
|
|
523
|
-
regscale_cli-6.20.
|
|
524
|
-
regscale_cli-6.20.
|
|
525
|
-
regscale_cli-6.20.
|
|
526
|
-
regscale_cli-6.20.
|
|
521
|
+
regscale_cli-6.20.9.1.dist-info/LICENSE,sha256=ytNhYQ9Rmhj_m-EX2pPq9Ld6tH5wrqqDYg-fCf46WDU,1076
|
|
522
|
+
regscale_cli-6.20.9.1.dist-info/METADATA,sha256=-2HH25ZiR3MzFNGrsmNc2s_XFJD14yx4LhxxiNz0JbQ,34955
|
|
523
|
+
regscale_cli-6.20.9.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
524
|
+
regscale_cli-6.20.9.1.dist-info/entry_points.txt,sha256=cLOaIP1eRv1yZ2u7BvpE3aB4x3kDrDwkpeisKOu33z8,269
|
|
525
|
+
regscale_cli-6.20.9.1.dist-info/top_level.txt,sha256=Uv8VUCAdxRm70bgrD4YNEJUmDhBThad_1aaEFGwRByc,15
|
|
526
|
+
regscale_cli-6.20.9.1.dist-info/RECORD,,
|
tests/regscale/core/test_app.py
CHANGED
|
@@ -54,29 +54,31 @@ class TestApplication:
|
|
|
54
54
|
def test_fetch_config_from_regscale_success(self, mock_get, mock_parse_user_id):
|
|
55
55
|
mock_parse_user_id.return_value = "test_user_id"
|
|
56
56
|
mock_response = MagicMock()
|
|
57
|
-
mock_response.json.return_value = {"
|
|
57
|
+
mock_response.json.return_value = '{"key": "value"}'
|
|
58
58
|
mock_get.return_value = mock_response
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
with patch.object(self.app, "_decrypt_config", return_value='{"key": "value"}'):
|
|
60
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
61
|
+
assert "domain" in config
|
|
62
|
+
assert config["userId"] == "test_user_id"
|
|
63
|
+
assert config["key"] == "value"
|
|
63
64
|
|
|
64
65
|
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
65
66
|
@patch("requests.get")
|
|
66
67
|
def test_fetch_config_from_regscale_success_with_envars(self, mock_get, mock_parse_user_id):
|
|
67
68
|
mock_parse_user_id.return_value = "test_user_id"
|
|
68
69
|
mock_response = MagicMock()
|
|
69
|
-
mock_response.json.return_value = {"
|
|
70
|
+
mock_response.json.return_value = '{"key": "value"}'
|
|
70
71
|
mock_get.return_value = mock_response
|
|
71
72
|
envars = os.environ.copy()
|
|
72
73
|
envars["REGSCALE_DOMAIN"] = self.test_domain
|
|
73
74
|
envars["REGSCALE_TOKEN"] = self.test_token
|
|
74
75
|
with patch.dict(os.environ, envars, clear=True):
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
with patch.object(self.app, "_decrypt_config", return_value='{"key": "value"}'):
|
|
77
|
+
config = self.app._fetch_config_from_regscale(config={})
|
|
78
|
+
assert config["domain"] == self.test_domain
|
|
79
|
+
assert config["token"] == self.test_token
|
|
80
|
+
assert config["userId"] == "test_user_id"
|
|
81
|
+
assert config["key"] == "value"
|
|
80
82
|
|
|
81
83
|
@patch("requests.get")
|
|
82
84
|
def test_fetch_config_from_regscale_failure(self, mock_get):
|
|
@@ -88,12 +90,396 @@ class TestApplication:
|
|
|
88
90
|
envars["REGSCALE_DOMAIN"] = self.test_domain
|
|
89
91
|
envars["REGSCALE_TOKEN"] = self.test_token
|
|
90
92
|
with patch.dict(os.environ, envars, clear=True):
|
|
91
|
-
empty_config = self.app._fetch_config_from_regscale()
|
|
92
|
-
assert empty_config == {}
|
|
93
93
|
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
94
|
+
empty_config = self.app._fetch_config_from_regscale()
|
|
95
|
+
assert empty_config == {}
|
|
96
|
+
mock_logger_error.assert_called()
|
|
97
|
+
|
|
98
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
99
|
+
@patch("requests.get")
|
|
100
|
+
def test_fetch_config_from_regscale_unencrypted_dict(self, mock_get, mock_parse_user_id):
|
|
101
|
+
"""Test the case where the response is already a dictionary (not encrypted)"""
|
|
102
|
+
mock_parse_user_id.return_value = "test_user_id"
|
|
103
|
+
mock_response = MagicMock()
|
|
104
|
+
mock_response.json.return_value = {"key": "value", "userId": "test_user_id"}
|
|
105
|
+
mock_get.return_value = mock_response
|
|
106
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
107
|
+
assert "domain" in config
|
|
108
|
+
assert config["userId"] == "test_user_id"
|
|
109
|
+
assert config["key"] == "value"
|
|
110
|
+
|
|
111
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
112
|
+
@patch("requests.get")
|
|
113
|
+
def test_fetch_config_from_regscale_no_cli_config(self, mock_get, mock_parse_user_id):
|
|
114
|
+
"""Test error handling when the cliConfig field is missing from the response"""
|
|
115
|
+
mock_parse_user_id.return_value = "test_user_id"
|
|
116
|
+
mock_response = MagicMock()
|
|
117
|
+
mock_response.json.return_value = ""
|
|
118
|
+
mock_get.return_value = mock_response
|
|
119
|
+
with patch.object(self.app.logger, "warning") as mock_logger_warning:
|
|
120
|
+
config = self.app._fetch_config_from_regscale(config={})
|
|
121
|
+
mock_logger_warning.assert_called_once()
|
|
122
|
+
assert config == {}
|
|
123
|
+
|
|
124
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
125
|
+
@patch("requests.get")
|
|
126
|
+
def test_fetch_config_from_regscale_with_existing_user_id(self, mock_get, mock_parse_user_id):
|
|
127
|
+
"""Test that existing userId in response is preserved"""
|
|
128
|
+
mock_response = MagicMock()
|
|
129
|
+
mock_response.json.return_value = '{"key": "value", "userId": "existing_user"}'
|
|
130
|
+
mock_get.return_value = mock_response
|
|
131
|
+
|
|
132
|
+
with patch.object(self.app, "_decrypt_config", return_value='{"key": "value", "userId": "existing_user"}'):
|
|
133
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
134
|
+
assert config["userId"] == "existing_user"
|
|
135
|
+
# parse_user_id_from_jwt should not be called since userId already exists
|
|
136
|
+
mock_parse_user_id.assert_not_called()
|
|
137
|
+
|
|
138
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
139
|
+
@patch("requests.get")
|
|
140
|
+
def test_fetch_config_from_regscale_empty_response(self, mock_get, mock_parse_user_id):
|
|
141
|
+
"""Test handling of empty response"""
|
|
142
|
+
mock_response = MagicMock()
|
|
143
|
+
mock_response.json.return_value = {}
|
|
144
|
+
mock_get.return_value = mock_response
|
|
145
|
+
|
|
146
|
+
with patch.object(self.app.logger, "warning") as mock_logger_warning:
|
|
147
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
148
|
+
assert config == {}
|
|
149
|
+
mock_logger_warning.assert_called_once()
|
|
150
|
+
|
|
151
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
152
|
+
@patch("requests.get")
|
|
153
|
+
def test_fetch_config_from_regscale_no_token(self, mock_get, mock_parse_user_id):
|
|
154
|
+
"""Test handling when no token is provided"""
|
|
155
|
+
envars = os.environ.copy()
|
|
156
|
+
envars.pop("REGSCALE_TOKEN", None)
|
|
157
|
+
envars.pop("REGSCALE_DOMAIN", None)
|
|
158
|
+
|
|
159
|
+
with patch.dict(os.environ, envars, clear=True):
|
|
160
|
+
config = self.app._fetch_config_from_regscale(config={})
|
|
161
|
+
assert config == {}
|
|
162
|
+
mock_get.assert_not_called()
|
|
163
|
+
|
|
164
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
165
|
+
@patch("requests.get")
|
|
166
|
+
def test_fetch_config_from_regscale_no_domain(self, mock_get, mock_parse_user_id):
|
|
167
|
+
"""Test handling when no domain is provided"""
|
|
168
|
+
envars = os.environ.copy()
|
|
169
|
+
envars["REGSCALE_TOKEN"] = self.test_token
|
|
170
|
+
envars.pop("REGSCALE_DOMAIN", None)
|
|
171
|
+
|
|
172
|
+
with patch.dict(os.environ, envars, clear=True):
|
|
173
|
+
with patch.object(self.app, "retrieve_domain", return_value="https://default.com"):
|
|
174
|
+
_ = self.app._fetch_config_from_regscale(config={})
|
|
175
|
+
# Should still attempt to make the request with default domain
|
|
176
|
+
mock_get.assert_called_once()
|
|
177
|
+
|
|
178
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
179
|
+
@patch("requests.get")
|
|
180
|
+
def test_fetch_config_from_regscale_request_exception(self, mock_get, mock_parse_user_id):
|
|
181
|
+
"""Test handling of request exceptions"""
|
|
182
|
+
mock_get.side_effect = Exception("Network error")
|
|
183
|
+
|
|
184
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
185
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
186
|
+
assert config == {}
|
|
187
|
+
mock_logger_error.assert_called_once()
|
|
188
|
+
|
|
189
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
190
|
+
@patch("requests.get")
|
|
191
|
+
def test_fetch_config_from_regscale_correct_endpoint(self, mock_get, mock_parse_user_id):
|
|
192
|
+
"""Test that the correct API endpoint is called"""
|
|
193
|
+
mock_parse_user_id.return_value = "test_user_id"
|
|
194
|
+
mock_response = MagicMock()
|
|
195
|
+
mock_response.json.return_value = {"cliConfig": '{"key": "value"}'}
|
|
196
|
+
mock_get.return_value = mock_response
|
|
197
|
+
|
|
198
|
+
with patch.object(self.app, "_decrypt_config", return_value='{"key": "value"}'):
|
|
199
|
+
self.app._fetch_config_from_regscale(config=self.app.config)
|
|
200
|
+
|
|
201
|
+
# Verify the correct endpoint was called
|
|
202
|
+
mock_get.assert_called_once()
|
|
203
|
+
call_args = mock_get.call_args
|
|
204
|
+
assert "/api/tenants/getDetailedCliConfig" in call_args[1]["url"]
|
|
205
|
+
|
|
206
|
+
@patch("regscale.core.app.internal.login.parse_user_id_from_jwt")
|
|
207
|
+
@patch("requests.get")
|
|
208
|
+
def test_fetch_config_from_regscale_headers_verification(self, mock_get, mock_parse_user_id):
|
|
209
|
+
"""Test that the correct headers are sent with the request"""
|
|
210
|
+
mock_parse_user_id.return_value = "test_user_id"
|
|
211
|
+
mock_response = MagicMock()
|
|
212
|
+
mock_response.json.return_value = {"cliConfig": '{"key": "value"}'}
|
|
213
|
+
mock_get.return_value = mock_response
|
|
214
|
+
|
|
215
|
+
with patch.object(self.app, "_decrypt_config", return_value='{"key": "value"}'):
|
|
216
|
+
self.app._fetch_config_from_regscale(config=self.app.config)
|
|
217
|
+
|
|
218
|
+
# Verify the correct headers were sent
|
|
219
|
+
mock_get.assert_called_once()
|
|
220
|
+
call_args = mock_get.call_args
|
|
221
|
+
headers = call_args[1]["headers"]
|
|
222
|
+
assert headers["Content-Type"] == "application/json"
|
|
223
|
+
assert headers["Accept"] == "application/json"
|
|
224
|
+
assert headers["Authorization"] == self.app.config.get("token")
|
|
225
|
+
|
|
226
|
+
def test_fetch_config_from_regscale_json_decode_error(self):
|
|
227
|
+
"""Test handling of JSON decode errors in decrypted config"""
|
|
228
|
+
mock_response = MagicMock()
|
|
229
|
+
mock_response.json.return_value = {"cliConfig": "encrypted_data"}
|
|
230
|
+
|
|
231
|
+
with patch("requests.get", return_value=mock_response):
|
|
232
|
+
with patch.object(self.app, "_decrypt_config", return_value="invalid json"):
|
|
233
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
234
|
+
config = self.app._fetch_config_from_regscale(config=self.app.config)
|
|
235
|
+
assert config == {}
|
|
236
|
+
mock_logger_error.assert_called_once()
|
|
237
|
+
|
|
238
|
+
def test_decrypt_config(self):
|
|
239
|
+
"""Test the _decrypt_config method with a mock encrypted string"""
|
|
240
|
+
# Mock the cryptography imports
|
|
241
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
242
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
243
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
244
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
245
|
+
# Setup mocks
|
|
246
|
+
mock_backend.return_value = "backend"
|
|
247
|
+
mock_b64decode.side_effect = [
|
|
248
|
+
b"iv_data_cipher_text", # First call for combined data
|
|
249
|
+
b"key_data", # Second call for token
|
|
250
|
+
]
|
|
251
|
+
mock_sha256_instance = MagicMock()
|
|
252
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
253
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
254
|
+
|
|
255
|
+
mock_decryptor = MagicMock()
|
|
256
|
+
mock_decryptor.update.return_value = b'{"key": "value"}'
|
|
257
|
+
mock_decryptor.finalize.return_value = b""
|
|
258
|
+
|
|
259
|
+
mock_cipher_instance = MagicMock()
|
|
260
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
261
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
262
|
+
|
|
263
|
+
# Test the method
|
|
264
|
+
result = self.app._decrypt_config(
|
|
265
|
+
"encrypted_string", os.getenv("REGSCALE_TOKEN", self.app.config["token"])
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Verify the result
|
|
269
|
+
assert result == '{"key": "value"}'
|
|
270
|
+
|
|
271
|
+
# Verify the mocks were called correctly
|
|
272
|
+
mock_b64decode.assert_called()
|
|
273
|
+
mock_sha256.assert_called_once()
|
|
274
|
+
mock_cipher.assert_called_once()
|
|
275
|
+
|
|
276
|
+
def test_decrypt_config_with_trailing_characters(self):
|
|
277
|
+
"""Test the _decrypt_config method handles trailing control characters"""
|
|
278
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
279
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
280
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
281
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
282
|
+
# Setup mocks
|
|
283
|
+
mock_backend.return_value = "backend"
|
|
284
|
+
mock_b64decode.side_effect = [
|
|
285
|
+
b"iv_data_cipher_text", # First call for combined data
|
|
286
|
+
b"key_data", # Second call for token
|
|
287
|
+
]
|
|
288
|
+
mock_sha256_instance = MagicMock()
|
|
289
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
290
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
291
|
+
|
|
292
|
+
mock_decryptor = MagicMock()
|
|
293
|
+
# Simulate decrypted data with trailing control characters
|
|
294
|
+
mock_decryptor.update.return_value = b'{"key": "value"}\x00\x08\x07'
|
|
295
|
+
mock_decryptor.finalize.return_value = b""
|
|
296
|
+
|
|
297
|
+
mock_cipher_instance = MagicMock()
|
|
298
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
299
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
300
|
+
|
|
301
|
+
# Test the method
|
|
302
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
303
|
+
|
|
304
|
+
# Verify the result is cleaned
|
|
305
|
+
assert result == '{"key": "value"}'
|
|
306
|
+
|
|
307
|
+
def test_decrypt_config_invalid_base64(self):
|
|
308
|
+
"""Test _decrypt_config with invalid base64 input"""
|
|
309
|
+
with patch("base64.b64decode", side_effect=Exception("Invalid base64")):
|
|
310
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
311
|
+
result = self.app._decrypt_config("invalid_base64", "Bearer test_token")
|
|
312
|
+
# Should raise an exception that gets caught by the calling method
|
|
313
|
+
assert result is None or mock_logger_error.called
|
|
314
|
+
assert mock_logger_error.call_count == 1
|
|
315
|
+
|
|
316
|
+
def test_decrypt_config_short_data(self):
|
|
317
|
+
"""Test _decrypt_config with data too short for IV extraction"""
|
|
318
|
+
with patch("base64.b64decode", return_value=b"short"):
|
|
319
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
320
|
+
result = self.app._decrypt_config("short_data", "Bearer test_token")
|
|
321
|
+
# Should handle the short data gracefully
|
|
322
|
+
assert result is None or mock_logger_error.called
|
|
323
|
+
|
|
324
|
+
def test_decrypt_config_decryption_failure(self):
|
|
325
|
+
"""Test _decrypt_config when decryption fails"""
|
|
326
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
327
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
328
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
329
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
330
|
+
# Setup mocks
|
|
331
|
+
mock_backend.return_value = "backend"
|
|
332
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
333
|
+
mock_sha256_instance = MagicMock()
|
|
334
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
335
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
336
|
+
|
|
337
|
+
mock_cipher.side_effect = Exception("Decryption failed")
|
|
338
|
+
|
|
339
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
340
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
341
|
+
# Should handle decryption failure gracefully
|
|
342
|
+
assert result is None or mock_logger_error.called
|
|
343
|
+
assert mock_logger_error.call_count == 1
|
|
344
|
+
|
|
345
|
+
def test_decrypt_config_unicode_decode_error(self):
|
|
346
|
+
"""Test _decrypt_config when UTF-8 decode fails"""
|
|
347
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
348
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
349
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
350
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
351
|
+
# Setup mocks
|
|
352
|
+
mock_backend.return_value = "backend"
|
|
353
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
354
|
+
mock_sha256_instance = MagicMock()
|
|
355
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
356
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
357
|
+
|
|
358
|
+
mock_decryptor = MagicMock()
|
|
359
|
+
# Return bytes that can't be decoded as UTF-8
|
|
360
|
+
mock_decryptor.update.return_value = b"\xff\xfe\xfd"
|
|
361
|
+
mock_decryptor.finalize.return_value = b""
|
|
362
|
+
|
|
363
|
+
mock_cipher_instance = MagicMock()
|
|
364
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
365
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
366
|
+
|
|
367
|
+
with patch.object(self.app.logger, "error") as mock_logger_error:
|
|
368
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
369
|
+
# Should handle decode error gracefully
|
|
370
|
+
assert result is None or mock_logger_error.called
|
|
371
|
+
|
|
372
|
+
def test_decrypt_config_multiple_null_bytes(self):
|
|
373
|
+
"""Test _decrypt_config with multiple trailing null bytes"""
|
|
374
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
375
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
376
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
377
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
378
|
+
# Setup mocks
|
|
379
|
+
mock_backend.return_value = "backend"
|
|
380
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
381
|
+
mock_sha256_instance = MagicMock()
|
|
382
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
383
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
384
|
+
|
|
385
|
+
mock_decryptor = MagicMock()
|
|
386
|
+
# Simulate decrypted data with multiple null bytes
|
|
387
|
+
mock_decryptor.update.return_value = b'{"key": "value"}\x00\x08\x07\x0f\x1b\x1c\x1d'
|
|
388
|
+
mock_decryptor.finalize.return_value = b""
|
|
389
|
+
|
|
390
|
+
mock_cipher_instance = MagicMock()
|
|
391
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
392
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
393
|
+
|
|
394
|
+
# Test the method
|
|
395
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
396
|
+
|
|
397
|
+
# Verify the result is cleaned
|
|
398
|
+
assert result == '{"key": "value"}'
|
|
399
|
+
|
|
400
|
+
def test_decrypt_config_mixed_trailing_characters(self):
|
|
401
|
+
"""Test _decrypt_config with mixed trailing characters"""
|
|
402
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
403
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
404
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
405
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
406
|
+
# Setup mocks
|
|
407
|
+
mock_backend.return_value = "backend"
|
|
408
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
409
|
+
mock_sha256_instance = MagicMock()
|
|
410
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
411
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
412
|
+
|
|
413
|
+
mock_decryptor = MagicMock()
|
|
414
|
+
# Simulate decrypted data with mixed trailing characters
|
|
415
|
+
mock_decryptor.update.return_value = b'{"key": "value"} \t\n\r\x00\x08\x07\x0f'
|
|
416
|
+
mock_decryptor.finalize.return_value = b""
|
|
417
|
+
|
|
418
|
+
mock_cipher_instance = MagicMock()
|
|
419
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
420
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
421
|
+
|
|
422
|
+
# Test the method
|
|
423
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
424
|
+
|
|
425
|
+
# Verify the result is cleaned
|
|
426
|
+
assert result == '{"key": "value"} '
|
|
427
|
+
|
|
428
|
+
def test_decrypt_config_with_trailing_backslash(self):
|
|
429
|
+
"""Test _decrypt_config with trailing backslash and characters after it"""
|
|
430
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
431
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
432
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
433
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
434
|
+
# Setup mocks
|
|
435
|
+
mock_backend.return_value = "backend"
|
|
436
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
437
|
+
mock_sha256_instance = MagicMock()
|
|
438
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
439
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
440
|
+
|
|
441
|
+
mock_decryptor = MagicMock()
|
|
442
|
+
# Simulate decrypted data with trailing backslash and characters
|
|
443
|
+
mock_decryptor.update.return_value = b'{"key": "value"}\\abc123'
|
|
444
|
+
mock_decryptor.finalize.return_value = b""
|
|
445
|
+
|
|
446
|
+
mock_cipher_instance = MagicMock()
|
|
447
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
448
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
449
|
+
|
|
450
|
+
# Test the method
|
|
451
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
452
|
+
|
|
453
|
+
# Verify the result is cleaned - should remove \abc123
|
|
454
|
+
assert result == '{"key": "value"}'
|
|
455
|
+
|
|
456
|
+
def test_decrypt_config_with_multiple_backslashes(self):
|
|
457
|
+
"""Test _decrypt_config with multiple backslashes in the string"""
|
|
458
|
+
with patch("cryptography.hazmat.primitives.ciphers.Cipher") as mock_cipher:
|
|
459
|
+
with patch("cryptography.hazmat.backends.default_backend") as mock_backend:
|
|
460
|
+
with patch("base64.b64decode") as mock_b64decode:
|
|
461
|
+
with patch("hashlib.sha256") as mock_sha256:
|
|
462
|
+
# Setup mocks
|
|
463
|
+
mock_backend.return_value = "backend"
|
|
464
|
+
mock_b64decode.return_value = b"iv_data_cipher_text"
|
|
465
|
+
mock_sha256_instance = MagicMock()
|
|
466
|
+
mock_sha256_instance.digest.return_value = b"key" * 8 # 32 bytes for AES-256
|
|
467
|
+
mock_sha256.return_value = mock_sha256_instance
|
|
468
|
+
|
|
469
|
+
mock_decryptor = MagicMock()
|
|
470
|
+
# Simulate decrypted data with multiple backslashes but only remove trailing one
|
|
471
|
+
mock_decryptor.update.return_value = b'{"key": "value\\nested"}\\trailing'
|
|
472
|
+
mock_decryptor.finalize.return_value = b""
|
|
473
|
+
|
|
474
|
+
mock_cipher_instance = MagicMock()
|
|
475
|
+
mock_cipher_instance.decryptor.return_value = mock_decryptor
|
|
476
|
+
mock_cipher.return_value = mock_cipher_instance
|
|
477
|
+
|
|
478
|
+
# Test the method
|
|
479
|
+
result = self.app._decrypt_config("encrypted_string", "Bearer test_token")
|
|
480
|
+
|
|
481
|
+
# Verify the result - should keep nested backslash but remove trailing one
|
|
482
|
+
assert result == '{"key": "value\\nested"}'
|
|
97
483
|
|
|
98
484
|
def test_gen_config(self):
|
|
99
485
|
self.app.local_config = True
|
|
@@ -125,6 +511,23 @@ class TestApplication:
|
|
|
125
511
|
assert result == {"key": "value", "env_key": "env_value"}
|
|
126
512
|
mock_save_config.assert_called_once()
|
|
127
513
|
|
|
514
|
+
def test_decrypting_config(self):
|
|
515
|
+
"""
|
|
516
|
+
Test to decrypt an actual encrypted config string and verify the output.
|
|
517
|
+
"""
|
|
518
|
+
import json
|
|
519
|
+
|
|
520
|
+
token = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjpbImFiZWxhcmRvIiwiYWJlbGFyZG8iXSwiaWQiOiIzOGJhZWJkYS03ZTI5LTRlZDYtYThiZi1mNzA0NjYyYzUwOGYiLCJyb2wiOiJhcGlfYWNjZXNzIiwic3ViIjoiYWJlbGFyZG8iLCJqdGkiOiJhODlmNTVmYy04YzU3LTRjNTktYmMzNy00ZmNhZjdkYjMxOGUiLCJpYXQiOjE3NTM5ODM0MjEsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWVpZGVudGlmaWVyIjoiMzhiYWViZGEtN2UyOS00ZWQ2LWE4YmYtZjcwNDY2MmM1MDhmIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9wcmltYXJ5Z3JvdXBzaWQiOiIxIiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5pc3RyYXRvciIsIm5iZiI6MTc1Mzk4MzQyMCwiZXhwIjoxNzU0MDY5ODIwLCJpc3MiOiJSZWdTY2FsZSIsImF1ZCI6Imh0dHBzOi8vd3d3LnJlZ3NjYWxlLmlvLyJ9.WG-xraZXNpmxs1x_LmoHFKY2RyLDOrLhZJSlZyYQb3U"
|
|
521
|
+
encrypted_string = "RPslDQj8r2NE5zwFnXRPHwJdgU8T6CPOWg+pQ+HEk+emPF7BV1jLKR3RHvqXO3LM8X+TTAMbfj+AoW6rV+unK0XVU4a9lI5aGYBQ7PbO4zEal7gMO+u88QM9fY0u3GqB"
|
|
522
|
+
decrypted_string = self.app._decrypt_config(encrypted_string, token)
|
|
523
|
+
parsed_config = json.loads(decrypted_string)
|
|
524
|
+
assert isinstance(decrypted_string, str)
|
|
525
|
+
assert isinstance(parsed_config, dict)
|
|
526
|
+
assert len(parsed_config) == 3
|
|
527
|
+
assert parsed_config["maxThreads"] == "20"
|
|
528
|
+
assert parsed_config["assessmentDays"] == "10"
|
|
529
|
+
assert parsed_config["test"] == "Just a test though."
|
|
530
|
+
|
|
128
531
|
def test_gen_config_without_local_config(self):
|
|
129
532
|
self.app.local_config = False
|
|
130
533
|
with patch.object(self.app, "_get_env", return_value={"env_key": "env_value"}):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|