regscale-cli 6.20.8.0__py3-none-any.whl → 6.20.9.0__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/models/integration_models/synqly_models/capabilities.json +1 -1
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.dist-info}/METADATA +1 -1
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.dist-info}/RECORD +10 -10
- tests/regscale/core/test_app.py +402 -16
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.dist-info}/LICENSE +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.dist-info}/WHEEL +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.dist-info}/entry_points.txt +0 -0
- {regscale_cli-6.20.8.0.dist-info → regscale_cli-6.20.9.0.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=gKT5YQ9lUTUBh7pTS4c88lS7iYu1716siI0f5SPm0uc,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=6EHkTkx9u9Y1CBz6HYJMGbui4S2Nq8kqIngZfnWkZfg,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
|
|
@@ -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=kpHT6EWkFvjSmCOj1UErmPPIPuLhh-AjUjtvAewX36s,356796
|
|
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=naFMgYRXWSHmYghhd8zsRJf1MsZwu0F1DClujmp-gtU,40593
|
|
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.0.dist-info/LICENSE,sha256=ytNhYQ9Rmhj_m-EX2pPq9Ld6tH5wrqqDYg-fCf46WDU,1076
|
|
522
|
+
regscale_cli-6.20.9.0.dist-info/METADATA,sha256=JMEea5DFwZ0PLNWKrLxGzq6yKwrMCWTrp5qPxYCydR4,34955
|
|
523
|
+
regscale_cli-6.20.9.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
524
|
+
regscale_cli-6.20.9.0.dist-info/entry_points.txt,sha256=cLOaIP1eRv1yZ2u7BvpE3aB4x3kDrDwkpeisKOu33z8,269
|
|
525
|
+
regscale_cli-6.20.9.0.dist-info/top_level.txt,sha256=Uv8VUCAdxRm70bgrD4YNEJUmDhBThad_1aaEFGwRByc,15
|
|
526
|
+
regscale_cli-6.20.9.0.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|