regscale-cli 6.26.0.0__py3-none-any.whl → 6.27.0.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.

Files changed (95) hide show
  1. regscale/_version.py +1 -1
  2. regscale/core/app/application.py +1 -1
  3. regscale/core/app/internal/evidence.py +419 -2
  4. regscale/dev/code_gen.py +24 -20
  5. regscale/integrations/commercial/jira.py +367 -126
  6. regscale/integrations/commercial/qualys/__init__.py +7 -8
  7. regscale/integrations/commercial/qualys/scanner.py +8 -3
  8. regscale/integrations/commercial/synqly/assets.py +17 -0
  9. regscale/integrations/commercial/synqly/vulnerabilities.py +45 -28
  10. regscale/integrations/commercial/tenablev2/cis_parsers.py +453 -0
  11. regscale/integrations/commercial/tenablev2/cis_scanner.py +447 -0
  12. regscale/integrations/commercial/tenablev2/commands.py +142 -1
  13. regscale/integrations/commercial/tenablev2/scanner.py +0 -1
  14. regscale/integrations/commercial/tenablev2/stig_parsers.py +113 -57
  15. regscale/integrations/commercial/wizv2/WizDataMixin.py +1 -1
  16. regscale/integrations/commercial/wizv2/click.py +44 -59
  17. regscale/integrations/commercial/wizv2/compliance/__init__.py +15 -0
  18. regscale/integrations/commercial/wizv2/{policy_compliance_helpers.py → compliance/helpers.py} +78 -60
  19. regscale/integrations/commercial/wizv2/compliance_report.py +10 -9
  20. regscale/integrations/commercial/wizv2/core/__init__.py +133 -0
  21. regscale/integrations/commercial/wizv2/{async_client.py → core/client.py} +3 -3
  22. regscale/integrations/commercial/wizv2/{constants.py → core/constants.py} +1 -17
  23. regscale/integrations/commercial/wizv2/core/file_operations.py +237 -0
  24. regscale/integrations/commercial/wizv2/fetchers/__init__.py +11 -0
  25. regscale/integrations/commercial/wizv2/{data_fetcher.py → fetchers/policy_assessment.py} +5 -9
  26. regscale/integrations/commercial/wizv2/issue.py +1 -1
  27. regscale/integrations/commercial/wizv2/models/__init__.py +0 -0
  28. regscale/integrations/commercial/wizv2/parsers/__init__.py +34 -0
  29. regscale/integrations/commercial/wizv2/{parsers.py → parsers/main.py} +1 -1
  30. regscale/integrations/commercial/wizv2/processors/__init__.py +11 -0
  31. regscale/integrations/commercial/wizv2/{finding_processor.py → processors/finding.py} +1 -1
  32. regscale/integrations/commercial/wizv2/reports.py +1 -1
  33. regscale/integrations/commercial/wizv2/sbom.py +1 -1
  34. regscale/integrations/commercial/wizv2/scanner.py +40 -100
  35. regscale/integrations/commercial/wizv2/utils/__init__.py +48 -0
  36. regscale/integrations/commercial/wizv2/{utils.py → utils/main.py} +116 -61
  37. regscale/integrations/commercial/wizv2/variables.py +89 -3
  38. regscale/integrations/compliance_integration.py +0 -46
  39. regscale/integrations/control_matcher.py +22 -3
  40. regscale/integrations/due_date_handler.py +14 -8
  41. regscale/integrations/public/fedramp/docx_parser.py +10 -1
  42. regscale/integrations/public/fedramp/fedramp_cis_crm.py +393 -340
  43. regscale/integrations/public/fedramp/fedramp_five.py +1 -1
  44. regscale/integrations/scanner_integration.py +127 -57
  45. regscale/models/integration_models/cisa_kev_data.json +132 -9
  46. regscale/models/integration_models/qualys.py +3 -4
  47. regscale/models/integration_models/synqly_models/capabilities.json +1 -1
  48. regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +24 -7
  49. regscale/models/integration_models/synqly_models/synqly_model.py +8 -1
  50. regscale/models/regscale_models/control_implementation.py +1 -1
  51. regscale/models/regscale_models/issue.py +0 -1
  52. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/METADATA +1 -17
  53. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/RECORD +93 -60
  54. tests/regscale/integrations/commercial/test_jira.py +481 -91
  55. tests/regscale/integrations/commercial/test_wiz.py +96 -200
  56. tests/regscale/integrations/commercial/wizv2/__init__.py +1 -1
  57. tests/regscale/integrations/commercial/wizv2/compliance/__init__.py +1 -0
  58. tests/regscale/integrations/commercial/wizv2/compliance/test_helpers.py +903 -0
  59. tests/regscale/integrations/commercial/wizv2/core/__init__.py +1 -0
  60. tests/regscale/integrations/commercial/wizv2/core/test_auth.py +701 -0
  61. tests/regscale/integrations/commercial/wizv2/core/test_client.py +1037 -0
  62. tests/regscale/integrations/commercial/wizv2/core/test_file_operations.py +989 -0
  63. tests/regscale/integrations/commercial/wizv2/fetchers/__init__.py +1 -0
  64. tests/regscale/integrations/commercial/wizv2/fetchers/test_policy_assessment.py +805 -0
  65. tests/regscale/integrations/commercial/wizv2/parsers/__init__.py +1 -0
  66. tests/regscale/integrations/commercial/wizv2/parsers/test_main.py +1153 -0
  67. tests/regscale/integrations/commercial/wizv2/processors/__init__.py +1 -0
  68. tests/regscale/integrations/commercial/wizv2/processors/test_finding.py +671 -0
  69. tests/regscale/integrations/commercial/wizv2/test_WizDataMixin.py +537 -0
  70. tests/regscale/integrations/commercial/wizv2/test_click_comprehensive.py +851 -0
  71. tests/regscale/integrations/commercial/wizv2/test_compliance_report_comprehensive.py +910 -0
  72. tests/regscale/integrations/commercial/wizv2/test_file_cleanup.py +283 -0
  73. tests/regscale/integrations/commercial/wizv2/test_file_operations.py +260 -0
  74. tests/regscale/integrations/commercial/wizv2/test_issue.py +1 -1
  75. tests/regscale/integrations/commercial/wizv2/test_issue_comprehensive.py +1203 -0
  76. tests/regscale/integrations/commercial/wizv2/test_reports.py +497 -0
  77. tests/regscale/integrations/commercial/wizv2/test_sbom.py +643 -0
  78. tests/regscale/integrations/commercial/wizv2/test_scanner_comprehensive.py +805 -0
  79. tests/regscale/integrations/commercial/wizv2/test_wiz_click_client_id.py +1 -1
  80. tests/regscale/integrations/commercial/wizv2/test_wiz_compliance_report.py +72 -29
  81. tests/regscale/integrations/commercial/wizv2/test_wiz_findings_comprehensive.py +364 -0
  82. tests/regscale/integrations/commercial/wizv2/test_wiz_inventory_comprehensive.py +644 -0
  83. tests/regscale/integrations/commercial/wizv2/test_wizv2.py +946 -78
  84. tests/regscale/integrations/commercial/wizv2/test_wizv2_utils.py +97 -202
  85. tests/regscale/integrations/commercial/wizv2/utils/__init__.py +1 -0
  86. tests/regscale/integrations/commercial/wizv2/utils/test_main.py +1523 -0
  87. tests/regscale/integrations/public/test_fedramp.py +301 -0
  88. tests/regscale/integrations/test_control_matcher.py +83 -0
  89. regscale/integrations/commercial/wizv2/policy_compliance.py +0 -3543
  90. tests/regscale/integrations/commercial/wizv2/test_wiz_policy_compliance.py +0 -750
  91. /regscale/integrations/commercial/wizv2/{wiz_auth.py → core/auth.py} +0 -0
  92. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/LICENSE +0 -0
  93. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/WHEEL +0 -0
  94. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/entry_points.txt +0 -0
  95. {regscale_cli-6.26.0.0.dist-info → regscale_cli-6.27.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1 @@
1
+ """Tests for Wiz core modules"""
@@ -0,0 +1,701 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Comprehensive unit tests for Wiz Authentication Module"""
4
+
5
+ import pytest
6
+ from unittest.mock import patch, MagicMock, Mock
7
+ import requests
8
+
9
+ from regscale.integrations.commercial.wizv2.core.auth import (
10
+ wiz_authenticate,
11
+ get_token,
12
+ generate_authentication_params,
13
+ AUTH0_URLS,
14
+ COGNITO_URLS,
15
+ )
16
+
17
+ PATH = "regscale.integrations.commercial.wizv2.core.auth"
18
+
19
+
20
+ class TestGenerateAuthenticationParams:
21
+ """Test the generate_authentication_params function"""
22
+
23
+ def test_auth0_url_generates_correct_params(self):
24
+ """Test that Auth0 URLs generate correct authentication parameters"""
25
+ client_id = "test_client_id"
26
+ client_secret = "test_client_secret"
27
+
28
+ for auth0_url in AUTH0_URLS:
29
+ params = generate_authentication_params(client_id, client_secret, auth0_url)
30
+ assert params == {
31
+ "grant_type": "client_credentials",
32
+ "audience": "beyond-api",
33
+ "client_id": client_id,
34
+ "client_secret": client_secret,
35
+ }
36
+
37
+ def test_cognito_url_generates_correct_params(self):
38
+ """Test that Cognito URLs generate correct authentication parameters"""
39
+ client_id = "test_client_id"
40
+ client_secret = "test_client_secret"
41
+
42
+ for cognito_url in COGNITO_URLS:
43
+ params = generate_authentication_params(client_id, client_secret, cognito_url)
44
+ assert params == {
45
+ "grant_type": "client_credentials",
46
+ "audience": "wiz-api",
47
+ "client_id": client_id,
48
+ "client_secret": client_secret,
49
+ }
50
+
51
+ def test_invalid_url_raises_error(self):
52
+ """Test that an invalid URL raises ValueError"""
53
+ client_id = "test_client_id"
54
+ client_secret = "test_client_secret"
55
+ invalid_url = "https://invalid.example.com/oauth/token"
56
+
57
+ with pytest.raises(ValueError, match="Invalid Token URL"):
58
+ generate_authentication_params(client_id, client_secret, invalid_url)
59
+
60
+ def test_empty_credentials_still_processed(self):
61
+ """Test that empty credentials are still processed correctly"""
62
+ client_id = ""
63
+ client_secret = ""
64
+
65
+ params = generate_authentication_params(client_id, client_secret, AUTH0_URLS[0])
66
+ assert params["client_id"] == ""
67
+ assert params["client_secret"] == ""
68
+
69
+
70
+ class TestGetToken:
71
+ """Test the get_token function"""
72
+
73
+ @patch(f"{PATH}.Api")
74
+ def test_successful_token_retrieval_first_url(self, mock_api_class):
75
+ """Test successful token retrieval on first URL attempt"""
76
+ mock_api = MagicMock()
77
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
78
+ mock_api.app = MagicMock()
79
+ mock_api.app.save_config = MagicMock()
80
+
81
+ mock_response = MagicMock()
82
+ mock_response.ok = True
83
+ mock_response.status_code = 200
84
+ mock_response.reason = "OK"
85
+ mock_response.json.return_value = {
86
+ "access_token": "test_token_123",
87
+ "scope": "read:all write:all",
88
+ }
89
+ mock_api.post.return_value = mock_response
90
+
91
+ token, scope = get_token(
92
+ api=mock_api,
93
+ client_id="test_client",
94
+ client_secret="test_secret",
95
+ token_url=AUTH0_URLS[0],
96
+ )
97
+
98
+ assert token == "test_token_123"
99
+ assert scope == "read:all write:all"
100
+ mock_api.post.assert_called_once()
101
+
102
+ @patch(f"{PATH}.error_and_exit")
103
+ @patch(f"{PATH}.Api")
104
+ def test_unauthorized_fallback_to_cognito(self, mock_api_class, mock_error_exit):
105
+ """Test fallback to Cognito URL on unauthorized response"""
106
+ mock_api = MagicMock()
107
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
108
+ mock_api.app = MagicMock()
109
+ mock_api.app.save_config = MagicMock()
110
+
111
+ # First response: Unauthorized
112
+ mock_response_unauthorized = MagicMock()
113
+ mock_response_unauthorized.ok = False
114
+ mock_response_unauthorized.status_code = requests.codes.unauthorized
115
+ mock_response_unauthorized.reason = "Unauthorized"
116
+
117
+ # Second response: Success with Cognito
118
+ mock_response_success = MagicMock()
119
+ mock_response_success.ok = True
120
+ mock_response_success.status_code = 200
121
+ mock_response_success.reason = "OK"
122
+ mock_response_success.json.return_value = {
123
+ "access_token": "cognito_token_456",
124
+ "scope": "wiz-api",
125
+ }
126
+
127
+ mock_api.post.side_effect = [mock_response_unauthorized, mock_response_success]
128
+
129
+ token, scope = get_token(
130
+ api=mock_api,
131
+ client_id="test_client",
132
+ client_secret="test_secret",
133
+ token_url=AUTH0_URLS[0],
134
+ )
135
+
136
+ assert token == "cognito_token_456"
137
+ assert scope == "wiz-api"
138
+ assert mock_api.post.call_count == 2
139
+ mock_api.app.save_config.assert_called_once()
140
+ assert mock_api.config["wizAuthUrl"] == COGNITO_URLS[0]
141
+
142
+ @patch(f"{PATH}.error_and_exit")
143
+ @patch(f"{PATH}.Api")
144
+ def test_unauthorized_fallback_fails_with_request_exception(self, mock_api_class, mock_error_exit):
145
+ """Test that RequestException during fallback is handled properly"""
146
+ mock_error_exit.side_effect = SystemExit(1)
147
+ mock_api = MagicMock()
148
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
149
+ mock_api.app = MagicMock()
150
+
151
+ # First response: Unauthorized
152
+ mock_response_unauthorized = MagicMock()
153
+ mock_response_unauthorized.ok = False
154
+ mock_response_unauthorized.status_code = requests.codes.unauthorized
155
+ mock_response_unauthorized.reason = "Unauthorized"
156
+ mock_response_unauthorized.text = "Invalid credentials"
157
+
158
+ # Second call raises RequestException
159
+ mock_api.post.side_effect = [mock_response_unauthorized, requests.RequestException("Network error")]
160
+
161
+ with pytest.raises(SystemExit):
162
+ get_token(
163
+ api=mock_api,
164
+ client_id="test_client",
165
+ client_secret="test_secret",
166
+ token_url=AUTH0_URLS[0],
167
+ )
168
+
169
+ mock_error_exit.assert_called_once()
170
+
171
+ @patch(f"{PATH}.error_and_exit")
172
+ @patch(f"{PATH}.Api")
173
+ def test_non_200_status_code_error(self, mock_api_class, mock_error_exit):
174
+ """Test that non-200 status codes trigger error_and_exit"""
175
+ mock_error_exit.side_effect = SystemExit(1)
176
+ mock_api = MagicMock()
177
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
178
+ mock_api.app = MagicMock()
179
+
180
+ mock_response = MagicMock()
181
+ mock_response.ok = False
182
+ mock_response.status_code = 500
183
+ mock_response.reason = "Internal Server Error"
184
+ mock_response.text = "Server error occurred"
185
+ mock_api.post.return_value = mock_response
186
+
187
+ with pytest.raises(SystemExit):
188
+ get_token(
189
+ api=mock_api,
190
+ client_id="test_client",
191
+ client_secret="test_secret",
192
+ token_url=AUTH0_URLS[0],
193
+ )
194
+
195
+ mock_error_exit.assert_called_once()
196
+ error_message = mock_error_exit.call_args[0][0]
197
+ assert "Error authenticating to Wiz" in error_message
198
+ assert "500" in error_message
199
+
200
+ @patch(f"{PATH}.error_and_exit")
201
+ @patch(f"{PATH}.Api")
202
+ def test_missing_access_token_in_response(self, mock_api_class, mock_error_exit):
203
+ """Test error when access_token is missing from response"""
204
+ mock_error_exit.side_effect = SystemExit(1)
205
+ mock_api = MagicMock()
206
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
207
+ mock_api.app = MagicMock()
208
+
209
+ mock_response = MagicMock()
210
+ mock_response.ok = True
211
+ mock_response.status_code = 200
212
+ mock_response.reason = "OK"
213
+ mock_response.json.return_value = {
214
+ "scope": "read:all",
215
+ "message": "Token generation failed",
216
+ }
217
+ mock_api.post.return_value = mock_response
218
+
219
+ with pytest.raises(SystemExit):
220
+ get_token(
221
+ api=mock_api,
222
+ client_id="test_client",
223
+ client_secret="test_secret",
224
+ token_url=AUTH0_URLS[0],
225
+ )
226
+
227
+ mock_error_exit.assert_called_once()
228
+ error_message = mock_error_exit.call_args[0][0]
229
+ assert "Could not retrieve token from Wiz" in error_message
230
+
231
+ @patch(f"{PATH}.Api")
232
+ def test_response_with_null_scope(self, mock_api_class):
233
+ """Test successful token retrieval when scope is None"""
234
+ mock_api = MagicMock()
235
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
236
+ mock_api.app = MagicMock()
237
+
238
+ mock_response = MagicMock()
239
+ mock_response.ok = True
240
+ mock_response.status_code = 200
241
+ mock_response.reason = "OK"
242
+ mock_response.json.return_value = {
243
+ "access_token": "test_token_123",
244
+ "scope": None,
245
+ }
246
+ mock_api.post.return_value = mock_response
247
+
248
+ token, scope = get_token(
249
+ api=mock_api,
250
+ client_id="test_client",
251
+ client_secret="test_secret",
252
+ token_url=AUTH0_URLS[0],
253
+ )
254
+
255
+ assert token == "test_token_123"
256
+ assert scope is None
257
+
258
+ @patch(f"{PATH}.Api")
259
+ def test_cognito_url_direct_success(self, mock_api_class):
260
+ """Test successful authentication directly with Cognito URL"""
261
+ mock_api = MagicMock()
262
+ mock_api.config = {"wizAuthUrl": COGNITO_URLS[0]}
263
+ mock_api.app = MagicMock()
264
+
265
+ mock_response = MagicMock()
266
+ mock_response.ok = True
267
+ mock_response.status_code = 200
268
+ mock_response.reason = "OK"
269
+ mock_response.json.return_value = {
270
+ "access_token": "cognito_direct_token",
271
+ "scope": "wiz-api full",
272
+ }
273
+ mock_api.post.return_value = mock_response
274
+
275
+ token, scope = get_token(
276
+ api=mock_api,
277
+ client_id="cognito_client",
278
+ client_secret="cognito_secret",
279
+ token_url=COGNITO_URLS[0],
280
+ )
281
+
282
+ assert token == "cognito_direct_token"
283
+ assert scope == "wiz-api full"
284
+
285
+
286
+ class TestWizAuthenticate:
287
+ """Test the wiz_authenticate function"""
288
+
289
+ @patch(f"{PATH}.get_token")
290
+ @patch(f"{PATH}.WizVariables")
291
+ @patch(f"{PATH}.Api")
292
+ @patch(f"{PATH}.check_license")
293
+ def test_successful_authentication_with_env_variables(
294
+ self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
295
+ ):
296
+ """Test successful authentication using environment variables"""
297
+ mock_app = MagicMock()
298
+ mock_app.config = {
299
+ "wizAuthUrl": AUTH0_URLS[0],
300
+ }
301
+ mock_app.save_config = MagicMock()
302
+ mock_check_license.return_value = mock_app
303
+
304
+ mock_api = MagicMock()
305
+ mock_api_class.return_value = mock_api
306
+
307
+ mock_wiz_vars.wizClientId = "env_client_id"
308
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
309
+
310
+ mock_get_token.return_value = ("test_token_789", "read:all write:all")
311
+
312
+ token = wiz_authenticate()
313
+
314
+ assert token == "test_token_789"
315
+ mock_get_token.assert_called_once_with(
316
+ api=mock_api,
317
+ client_id="env_client_id",
318
+ client_secret="env_client_secret",
319
+ token_url=AUTH0_URLS[0],
320
+ )
321
+ mock_app.save_config.assert_called_once()
322
+ saved_config = mock_app.save_config.call_args[0][0]
323
+ assert saved_config["wizAccessToken"] == "test_token_789"
324
+ assert saved_config["wizScope"] == "read:all write:all"
325
+
326
+ @patch(f"{PATH}.get_token")
327
+ @patch(f"{PATH}.WizVariables")
328
+ @patch(f"{PATH}.Api")
329
+ @patch(f"{PATH}.check_license")
330
+ def test_successful_authentication_with_cli_parameters(
331
+ self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
332
+ ):
333
+ """Test successful authentication using CLI-provided parameters"""
334
+ mock_app = MagicMock()
335
+ mock_app.config = {
336
+ "wizAuthUrl": COGNITO_URLS[0],
337
+ }
338
+ mock_app.save_config = MagicMock()
339
+ mock_check_license.return_value = mock_app
340
+
341
+ mock_api = MagicMock()
342
+ mock_api_class.return_value = mock_api
343
+
344
+ mock_wiz_vars.wizClientId = "env_client_id"
345
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
346
+
347
+ mock_get_token.return_value = ("cli_token_999", "wiz-api")
348
+
349
+ token = wiz_authenticate(client_id="cli_client_id", client_secret="cli_client_secret")
350
+
351
+ assert token == "cli_token_999"
352
+ mock_get_token.assert_called_once_with(
353
+ api=mock_api,
354
+ client_id="cli_client_id",
355
+ client_secret="cli_client_secret",
356
+ token_url=COGNITO_URLS[0],
357
+ )
358
+
359
+ @patch(f"{PATH}.error_and_exit")
360
+ @patch(f"{PATH}.WizVariables")
361
+ @patch(f"{PATH}.Api")
362
+ @patch(f"{PATH}.check_license")
363
+ def test_missing_client_id_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
364
+ """Test error when client ID is missing"""
365
+ mock_error_exit.side_effect = SystemExit(1)
366
+ mock_app = MagicMock()
367
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
368
+ mock_check_license.return_value = mock_app
369
+
370
+ mock_api = MagicMock()
371
+ mock_api_class.return_value = mock_api
372
+
373
+ mock_wiz_vars.wizClientId = None
374
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
375
+
376
+ with pytest.raises(SystemExit):
377
+ wiz_authenticate()
378
+
379
+ mock_error_exit.assert_called_once()
380
+ error_message = mock_error_exit.call_args[0][0]
381
+ assert "No Wiz Client ID provided" in error_message
382
+
383
+ @patch(f"{PATH}.error_and_exit")
384
+ @patch(f"{PATH}.WizVariables")
385
+ @patch(f"{PATH}.Api")
386
+ @patch(f"{PATH}.check_license")
387
+ def test_missing_client_secret_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
388
+ """Test error when client secret is missing"""
389
+ mock_error_exit.side_effect = SystemExit(1)
390
+ mock_app = MagicMock()
391
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
392
+ mock_check_license.return_value = mock_app
393
+
394
+ mock_api = MagicMock()
395
+ mock_api_class.return_value = mock_api
396
+
397
+ mock_wiz_vars.wizClientId = "env_client_id"
398
+ mock_wiz_vars.wizClientSecret = None
399
+
400
+ with pytest.raises(SystemExit):
401
+ wiz_authenticate()
402
+
403
+ mock_error_exit.assert_called_once()
404
+ error_message = mock_error_exit.call_args[0][0]
405
+ assert "No Wiz Client Secret provided" in error_message
406
+
407
+ @patch(f"{PATH}.error_and_exit")
408
+ @patch(f"{PATH}.WizVariables")
409
+ @patch(f"{PATH}.Api")
410
+ @patch(f"{PATH}.check_license")
411
+ def test_missing_auth_url_error(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_error_exit):
412
+ """Test error when wizAuthUrl is missing from config"""
413
+ mock_error_exit.side_effect = SystemExit(1)
414
+ mock_app = MagicMock()
415
+ mock_app.config = {}
416
+ mock_check_license.return_value = mock_app
417
+
418
+ mock_api = MagicMock()
419
+ mock_api_class.return_value = mock_api
420
+
421
+ mock_wiz_vars.wizClientId = "env_client_id"
422
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
423
+
424
+ with pytest.raises(SystemExit):
425
+ wiz_authenticate()
426
+
427
+ mock_error_exit.assert_called_once()
428
+ error_message = mock_error_exit.call_args[0][0]
429
+ assert "No Wiz Authentication URL provided" in error_message
430
+
431
+ @patch(f"{PATH}.get_token")
432
+ @patch(f"{PATH}.WizVariables")
433
+ @patch(f"{PATH}.Api")
434
+ @patch(f"{PATH}.check_license")
435
+ def test_empty_string_credentials_treated_as_missing(
436
+ self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
437
+ ):
438
+ """Test that empty string credentials are treated as missing"""
439
+ mock_app = MagicMock()
440
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
441
+ mock_check_license.return_value = mock_app
442
+
443
+ mock_api = MagicMock()
444
+ mock_api_class.return_value = mock_api
445
+
446
+ # Empty strings should be treated as falsy
447
+ mock_wiz_vars.wizClientId = ""
448
+ mock_wiz_vars.wizClientSecret = "valid_secret"
449
+
450
+ # Since empty string client_id is provided via CLI, it should override env
451
+ # But empty strings are falsy, so error should trigger
452
+ from regscale.core.app.utils.app_utils import error_and_exit
453
+
454
+ with patch(f"{PATH}.error_and_exit") as mock_error_exit:
455
+ mock_error_exit.side_effect = SystemExit(1)
456
+
457
+ with pytest.raises(SystemExit):
458
+ wiz_authenticate(client_id="", client_secret="valid_secret")
459
+
460
+ mock_error_exit.assert_called()
461
+
462
+ @patch(f"{PATH}.get_token")
463
+ @patch(f"{PATH}.WizVariables")
464
+ @patch(f"{PATH}.Api")
465
+ @patch(f"{PATH}.check_license")
466
+ def test_cli_parameters_override_environment_variables(
467
+ self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token
468
+ ):
469
+ """Test that CLI parameters override environment variables"""
470
+ mock_app = MagicMock()
471
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
472
+ mock_app.save_config = MagicMock()
473
+ mock_check_license.return_value = mock_app
474
+
475
+ mock_api = MagicMock()
476
+ mock_api_class.return_value = mock_api
477
+
478
+ mock_wiz_vars.wizClientId = "env_client_id"
479
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
480
+
481
+ mock_get_token.return_value = ("override_token", "override_scope")
482
+
483
+ wiz_authenticate(client_id="cli_client_id", client_secret="cli_client_secret")
484
+
485
+ # Verify CLI parameters were used, not env variables
486
+ mock_get_token.assert_called_once_with(
487
+ api=mock_api,
488
+ client_id="cli_client_id",
489
+ client_secret="cli_client_secret",
490
+ token_url=AUTH0_URLS[0],
491
+ )
492
+
493
+ @patch(f"{PATH}.get_token")
494
+ @patch(f"{PATH}.WizVariables")
495
+ @patch(f"{PATH}.Api")
496
+ @patch(f"{PATH}.check_license")
497
+ def test_token_and_scope_saved_to_config(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
498
+ """Test that token and scope are properly saved to config"""
499
+ mock_app = MagicMock()
500
+ mock_app.config = {
501
+ "wizAuthUrl": AUTH0_URLS[0],
502
+ "existingKey": "existingValue",
503
+ }
504
+ mock_app.save_config = MagicMock()
505
+ mock_check_license.return_value = mock_app
506
+
507
+ mock_api = MagicMock()
508
+ mock_api_class.return_value = mock_api
509
+
510
+ mock_wiz_vars.wizClientId = "env_client_id"
511
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
512
+
513
+ mock_get_token.return_value = ("new_token_123", "full_scope")
514
+
515
+ wiz_authenticate()
516
+
517
+ mock_app.save_config.assert_called_once()
518
+ saved_config = mock_app.save_config.call_args[0][0]
519
+ assert saved_config["wizAccessToken"] == "new_token_123"
520
+ assert saved_config["wizScope"] == "full_scope"
521
+ assert saved_config["existingKey"] == "existingValue"
522
+ assert saved_config["wizAuthUrl"] == AUTH0_URLS[0]
523
+
524
+ @patch(f"{PATH}.get_token")
525
+ @patch(f"{PATH}.WizVariables")
526
+ @patch(f"{PATH}.Api")
527
+ @patch(f"{PATH}.check_license")
528
+ def test_partial_cli_parameters(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
529
+ """Test authentication with partial CLI parameters (only client_id)"""
530
+ mock_app = MagicMock()
531
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
532
+ mock_app.save_config = MagicMock()
533
+ mock_check_license.return_value = mock_app
534
+
535
+ mock_api = MagicMock()
536
+ mock_api_class.return_value = mock_api
537
+
538
+ mock_wiz_vars.wizClientId = "env_client_id"
539
+ mock_wiz_vars.wizClientSecret = "env_client_secret"
540
+
541
+ mock_get_token.return_value = ("partial_token", "partial_scope")
542
+
543
+ wiz_authenticate(client_id="cli_client_id")
544
+
545
+ # CLI client_id should be used, env client_secret should be used
546
+ mock_get_token.assert_called_once_with(
547
+ api=mock_api,
548
+ client_id="cli_client_id",
549
+ client_secret="env_client_secret",
550
+ token_url=AUTH0_URLS[0],
551
+ )
552
+
553
+
554
+ class TestAuthenticationIntegration:
555
+ """Integration tests for authentication flow"""
556
+
557
+ @patch(f"{PATH}.get_token")
558
+ @patch(f"{PATH}.WizVariables")
559
+ @patch(f"{PATH}.Api")
560
+ @patch(f"{PATH}.check_license")
561
+ def test_complete_auth_flow_with_all_urls(self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token):
562
+ """Test complete authentication flow with all supported URLs"""
563
+ all_urls = AUTH0_URLS + COGNITO_URLS
564
+
565
+ for url in all_urls:
566
+ mock_app = MagicMock()
567
+ mock_app.config = {"wizAuthUrl": url}
568
+ mock_app.save_config = MagicMock()
569
+ mock_check_license.return_value = mock_app
570
+
571
+ mock_api = MagicMock()
572
+ mock_api_class.return_value = mock_api
573
+
574
+ mock_wiz_vars.wizClientId = "test_client"
575
+ mock_wiz_vars.wizClientSecret = "test_secret"
576
+
577
+ mock_get_token.return_value = (f"token_for_{url}", "scope")
578
+
579
+ token = wiz_authenticate()
580
+
581
+ assert token == f"token_for_{url}"
582
+ mock_get_token.assert_called_with(
583
+ api=mock_api,
584
+ client_id="test_client",
585
+ client_secret="test_secret",
586
+ token_url=url,
587
+ )
588
+
589
+ @patch(f"{PATH}.error_and_exit")
590
+ @patch(f"{PATH}.get_token")
591
+ @patch(f"{PATH}.WizVariables")
592
+ @patch(f"{PATH}.Api")
593
+ @patch(f"{PATH}.check_license")
594
+ def test_get_token_failure_propagates_to_wiz_authenticate(
595
+ self, mock_check_license, mock_api_class, mock_wiz_vars, mock_get_token, mock_error_exit
596
+ ):
597
+ """Test that get_token failures properly propagate to wiz_authenticate"""
598
+ mock_error_exit.side_effect = SystemExit(1)
599
+ mock_app = MagicMock()
600
+ mock_app.config = {"wizAuthUrl": AUTH0_URLS[0]}
601
+ mock_check_license.return_value = mock_app
602
+
603
+ mock_api = MagicMock()
604
+ mock_api_class.return_value = mock_api
605
+
606
+ mock_wiz_vars.wizClientId = "test_client"
607
+ mock_wiz_vars.wizClientSecret = "test_secret"
608
+
609
+ mock_get_token.side_effect = SystemExit(1)
610
+
611
+ with pytest.raises(SystemExit):
612
+ wiz_authenticate()
613
+
614
+
615
+ class TestEdgeCases:
616
+ """Test edge cases and boundary conditions"""
617
+
618
+ def test_auth0_urls_list_not_empty(self):
619
+ """Test that AUTH0_URLS is not empty"""
620
+ assert len(AUTH0_URLS) > 0
621
+
622
+ def test_cognito_urls_list_not_empty(self):
623
+ """Test that COGNITO_URLS is not empty"""
624
+ assert len(COGNITO_URLS) > 0
625
+
626
+ def test_all_auth0_urls_are_unique(self):
627
+ """Test that all Auth0 URLs are unique"""
628
+ assert len(AUTH0_URLS) == len(set(AUTH0_URLS))
629
+
630
+ def test_all_cognito_urls_are_unique(self):
631
+ """Test that all Cognito URLs are unique"""
632
+ assert len(COGNITO_URLS) == len(set(COGNITO_URLS))
633
+
634
+ def test_no_url_overlap_between_auth0_and_cognito(self):
635
+ """Test that Auth0 and Cognito URLs don't overlap"""
636
+ auth0_set = set(AUTH0_URLS)
637
+ cognito_set = set(COGNITO_URLS)
638
+ assert len(auth0_set.intersection(cognito_set)) == 0
639
+
640
+ def test_all_urls_are_strings(self):
641
+ """Test that all URLs are strings"""
642
+ for url in AUTH0_URLS + COGNITO_URLS:
643
+ assert isinstance(url, str)
644
+
645
+ def test_all_urls_are_https(self):
646
+ """Test that all URLs use HTTPS"""
647
+ for url in AUTH0_URLS + COGNITO_URLS:
648
+ assert url.startswith("https://")
649
+
650
+ @patch(f"{PATH}.Api")
651
+ def test_get_token_handles_response_without_scope(self, mock_api_class):
652
+ """Test get_token when response doesn't include scope key"""
653
+ mock_api = MagicMock()
654
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
655
+ mock_api.app = MagicMock()
656
+
657
+ mock_response = MagicMock()
658
+ mock_response.ok = True
659
+ mock_response.status_code = 200
660
+ mock_response.reason = "OK"
661
+ # Response without scope key
662
+ mock_response.json.return_value = {
663
+ "access_token": "token_without_scope",
664
+ }
665
+ mock_api.post.return_value = mock_response
666
+
667
+ token, scope = get_token(
668
+ api=mock_api,
669
+ client_id="test_client",
670
+ client_secret="test_secret",
671
+ token_url=AUTH0_URLS[0],
672
+ )
673
+
674
+ assert token == "token_without_scope"
675
+ assert scope is None
676
+
677
+ @patch(f"{PATH}.error_and_exit")
678
+ @patch(f"{PATH}.Api")
679
+ def test_get_token_handles_empty_json_response(self, mock_api_class, mock_error_exit):
680
+ """Test get_token when response JSON is empty"""
681
+ mock_error_exit.side_effect = SystemExit(1)
682
+ mock_api = MagicMock()
683
+ mock_api.config = {"wizAuthUrl": AUTH0_URLS[0]}
684
+ mock_api.app = MagicMock()
685
+
686
+ mock_response = MagicMock()
687
+ mock_response.ok = True
688
+ mock_response.status_code = 200
689
+ mock_response.reason = "OK"
690
+ mock_response.json.return_value = {}
691
+ mock_api.post.return_value = mock_response
692
+
693
+ with pytest.raises(SystemExit):
694
+ get_token(
695
+ api=mock_api,
696
+ client_id="test_client",
697
+ client_secret="test_secret",
698
+ token_url=AUTH0_URLS[0],
699
+ )
700
+
701
+ mock_error_exit.assert_called_once()