contentctl 4.4.7__tar.gz → 5.0.0a2__tar.gz

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.
Files changed (183) hide show
  1. {contentctl-4.4.7 → contentctl-5.0.0a2}/PKG-INFO +5 -4
  2. contentctl-5.0.0a2/contentctl/actions/build.py +103 -0
  3. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/DetectionTestingManager.py +0 -1
  4. contentctl-5.0.0a2/contentctl/actions/detection_testing/GitService.py +247 -0
  5. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructure.py +32 -26
  6. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/progress_bar.py +6 -6
  7. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingView.py +4 -4
  8. contentctl-5.0.0a2/contentctl/actions/new_content.py +150 -0
  9. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/test.py +4 -5
  10. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/validate.py +2 -1
  11. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/contentctl.py +114 -80
  12. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/helper/utils.py +0 -14
  13. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/input/director.py +5 -5
  14. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/input/new_content_questions.py +2 -2
  15. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/input/yml_reader.py +11 -6
  16. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/abstract_security_content_objects/detection_abstract.py +228 -120
  17. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/abstract_security_content_objects/security_content_object_abstract.py +5 -7
  18. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/alert_action.py +2 -1
  19. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/atomic.py +1 -0
  20. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/base_test.py +4 -3
  21. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/base_test_result.py +3 -3
  22. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/baseline.py +26 -6
  23. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/baseline_tags.py +2 -3
  24. contentctl-5.0.0a2/contentctl/objects/config.py +1238 -0
  25. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/constants.py +4 -1
  26. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/correlation_search.py +89 -95
  27. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/data_source.py +5 -6
  28. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/deployment.py +2 -10
  29. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/deployment_email.py +2 -1
  30. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/deployment_notable.py +2 -1
  31. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/deployment_phantom.py +2 -1
  32. contentctl-5.0.0a2/contentctl/objects/deployment_rba.py +7 -0
  33. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/deployment_scheduling.py +2 -1
  34. contentctl-5.0.0a2/contentctl/objects/deployment_slack.py +8 -0
  35. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/detection_tags.py +7 -42
  36. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/drilldown.py +1 -0
  37. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/enums.py +21 -58
  38. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/investigation.py +6 -5
  39. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/investigation_tags.py +2 -3
  40. contentctl-5.0.0a2/contentctl/objects/lookup.py +235 -0
  41. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/macro.py +2 -3
  42. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/mitre_attack_enrichment.py +2 -2
  43. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/observable.py +3 -1
  44. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/playbook_tags.py +5 -1
  45. contentctl-5.0.0a2/contentctl/objects/rba.py +90 -0
  46. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/risk_event.py +87 -144
  47. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/story_tags.py +1 -2
  48. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/test_attack_data.py +2 -1
  49. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/unit_test_baseline.py +2 -1
  50. contentctl-5.0.0a2/contentctl/output/api_json_output.py +259 -0
  51. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/conf_output.py +51 -44
  52. contentctl-5.0.0a2/contentctl/output/conf_writer.py +413 -0
  53. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/data_source_writer.py +0 -1
  54. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/json_writer.py +2 -4
  55. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/svg_output.py +1 -1
  56. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_detections.j2 +1 -1
  57. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/collections.j2 +1 -1
  58. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_detections.j2 +0 -5
  59. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_detections.j2 +8 -3
  60. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/transforms.j2 +4 -4
  61. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/yml_writer.py +15 -0
  62. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/detections/endpoint/anomalous_usage_of_7zip.yml +16 -34
  63. contentctl-5.0.0a2/pyproject.toml +118 -0
  64. contentctl-4.4.7/contentctl/actions/build.py +0 -91
  65. contentctl-4.4.7/contentctl/actions/detection_testing/GitService.py +0 -187
  66. contentctl-4.4.7/contentctl/actions/new_content.py +0 -133
  67. contentctl-4.4.7/contentctl/objects/config.py +0 -1045
  68. contentctl-4.4.7/contentctl/objects/deployment_rba.py +0 -6
  69. contentctl-4.4.7/contentctl/objects/deployment_slack.py +0 -7
  70. contentctl-4.4.7/contentctl/objects/event_source.py +0 -11
  71. contentctl-4.4.7/contentctl/objects/lookup.py +0 -153
  72. contentctl-4.4.7/contentctl/output/api_json_output.py +0 -246
  73. contentctl-4.4.7/contentctl/output/conf_writer.py +0 -337
  74. contentctl-4.4.7/contentctl/output/detection_writer.py +0 -28
  75. contentctl-4.4.7/contentctl/output/new_content_yml_output.py +0 -56
  76. contentctl-4.4.7/contentctl/output/yml_output.py +0 -66
  77. contentctl-4.4.7/pyproject.toml +0 -36
  78. {contentctl-4.4.7 → contentctl-5.0.0a2}/LICENSE.md +0 -0
  79. {contentctl-4.4.7 → contentctl-5.0.0a2}/README.md +0 -0
  80. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/__init__.py +0 -0
  81. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/deploy_acs.py +0 -0
  82. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/generate_detection_coverage_badge.py +0 -0
  83. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureContainer.py +0 -0
  84. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/infrastructures/DetectionTestingInfrastructureServer.py +0 -0
  85. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewCLI.py +0 -0
  86. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewFile.py +0 -0
  87. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/detection_testing/views/DetectionTestingViewWeb.py +0 -0
  88. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/doc_gen.py +0 -0
  89. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/initialize.py +0 -0
  90. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/initialize_old.py +0 -0
  91. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/inspect.py +0 -0
  92. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/release_notes.py +0 -0
  93. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/actions/reporting.py +0 -0
  94. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/api.py +0 -0
  95. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/enrichments/attack_enrichment.py +0 -0
  96. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/enrichments/cve_enrichment.py +0 -0
  97. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/enrichments/splunk_app_enrichment.py +0 -0
  98. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/helper/link_validator.py +0 -0
  99. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/helper/logger.py +0 -0
  100. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/helper/splunk_app.py +0 -0
  101. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/annotated_types.py +0 -0
  102. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/dashboard.py +0 -0
  103. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/detection.py +0 -0
  104. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/detection_metadata.py +0 -0
  105. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/detection_stanza.py +0 -0
  106. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/errors.py +0 -0
  107. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/integration_test.py +0 -0
  108. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/integration_test_result.py +0 -0
  109. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/manual_test.py +0 -0
  110. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/manual_test_result.py +0 -0
  111. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/notable_action.py +0 -0
  112. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/notable_event.py +0 -0
  113. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/playbook.py +0 -0
  114. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/risk_analysis_action.py +0 -0
  115. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/risk_object.py +0 -0
  116. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/savedsearches_conf.py +0 -0
  117. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/security_content_object.py +0 -0
  118. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/story.py +0 -0
  119. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/test_group.py +0 -0
  120. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/threat_object.py +0 -0
  121. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/throttling.py +0 -0
  122. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/unit_test.py +0 -0
  123. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/objects/unit_test_result.py +0 -0
  124. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/attack_nav_output.py +0 -0
  125. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/attack_nav_writer.py +0 -0
  126. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/doc_md_output.py +0 -0
  127. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/jinja_writer.py +0 -0
  128. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_investigations.j2 +0 -0
  129. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/analyticstories_stories.j2 +0 -0
  130. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/app.conf.j2 +0 -0
  131. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/app.manifest.j2 +0 -0
  132. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/content-version.j2 +0 -0
  133. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/detection_count.j2 +0 -0
  134. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/detection_coverage.j2 +0 -0
  135. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_detection_page.j2 +0 -0
  136. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_navigation.j2 +0 -0
  137. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_navigation_pages.j2 +0 -0
  138. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_playbooks.j2 +0 -0
  139. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_playbooks_page.j2 +0 -0
  140. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_stories.j2 +0 -0
  141. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/doc_story_page.j2 +0 -0
  142. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/es_investigations_investigations.j2 +0 -0
  143. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/es_investigations_stories.j2 +0 -0
  144. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/header.j2 +0 -0
  145. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/macros.j2 +0 -0
  146. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/panel.j2 +0 -0
  147. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_baselines.j2 +0 -0
  148. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/savedsearches_investigations.j2 +0 -0
  149. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/server.conf.j2 +0 -0
  150. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/output/templates/workflow_actions.j2 +0 -0
  151. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/README.md +0 -0
  152. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_default.yml +0 -0
  153. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_story_detail.txt +0 -0
  154. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_summary.txt +0 -0
  155. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/README/essoc_usage_dashboard.txt +0 -0
  156. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/README.md +0 -0
  157. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/analytic_stories.conf +0 -0
  158. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/commands.conf +0 -0
  159. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/nav/default.xml +0 -0
  160. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/views/escu_summary.xml +0 -0
  161. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/data/ui/views/feedback.xml +0 -0
  162. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/default/use_case_library.conf +0 -0
  163. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/lookups/mitre_enrichment.csv +0 -0
  164. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/metadata/default.meta +0 -0
  165. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIcon.png +0 -0
  166. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIconAlt.png +0 -0
  167. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIconAlt_2x.png +0 -0
  168. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/app_template/static/appIcon_2x.png +0 -0
  169. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/data_sources/sysmon_eventid_1.yml +0 -0
  170. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/datamodels_cim.conf +0 -0
  171. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/datamodels_custom.conf +0 -0
  172. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_anomaly.yml +0 -0
  173. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_baseline.yml +0 -0
  174. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_correlation.yml +0 -0
  175. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_hunting.yml +0 -0
  176. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/deployments/escu_default_configuration_ttp.yml +0 -0
  177. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/detections/application/.gitkeep +0 -0
  178. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/detections/cloud/.gitkeep +0 -0
  179. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/detections/network/.gitkeep +0 -0
  180. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/detections/web/.gitkeep +0 -0
  181. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/macros/security_content_ctime.yml +0 -0
  182. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/macros/security_content_summariesonly.yml +0 -0
  183. {contentctl-4.4.7 → contentctl-5.0.0a2}/contentctl/templates/stories/cobalt_strike.yml +0 -0
@@ -1,15 +1,16 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: contentctl
3
- Version: 4.4.7
3
+ Version: 5.0.0a2
4
4
  Summary: Splunk Content Control Tool
5
5
  License: Apache 2.0
6
6
  Author: STRT
7
7
  Author-email: research@splunk.com
8
- Requires-Python: >=3.11,<3.13
8
+ Requires-Python: >=3.11,<3.14
9
9
  Classifier: License :: Other/Proprietary License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
13
14
  Requires-Dist: Jinja2 (>=3.1.4,<4.0.0)
14
15
  Requires-Dist: PyYAML (>=6.0.2,<7.0.0)
15
16
  Requires-Dist: attackcti (>=0.4.0,<0.5.0)
@@ -25,7 +26,7 @@ Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
25
26
  Requires-Dist: setuptools (>=69.5.1,<76.0.0)
26
27
  Requires-Dist: splunk-sdk (>=2.0.2,<3.0.0)
27
28
  Requires-Dist: tqdm (>=4.66.5,<5.0.0)
28
- Requires-Dist: tyro (>=0.8.3,<0.9.0)
29
+ Requires-Dist: tyro (>=0.9.2,<0.10.0)
29
30
  Requires-Dist: xmltodict (>=0.13,<0.15)
30
31
  Description-Content-Type: text/markdown
31
32
 
@@ -0,0 +1,103 @@
1
+ import sys
2
+ import shutil
3
+ import os
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from contentctl.objects.enums import SecurityContentType
8
+ from contentctl.input.director import Director, DirectorOutputDto
9
+ from contentctl.output.conf_output import ConfOutput
10
+ from contentctl.output.conf_writer import ConfWriter
11
+ from contentctl.output.api_json_output import ApiJsonOutput
12
+ from contentctl.output.data_source_writer import DataSourceWriter
13
+ from contentctl.objects.lookup import CSVLookup, Lookup_Type
14
+ import pathlib
15
+ import json
16
+ import datetime
17
+ import uuid
18
+
19
+ from contentctl.objects.config import build
20
+
21
+ @dataclass(frozen=True)
22
+ class BuildInputDto:
23
+ director_output_dto: DirectorOutputDto
24
+ config:build
25
+
26
+
27
+ class Build:
28
+
29
+
30
+
31
+ def execute(self, input_dto: BuildInputDto) -> DirectorOutputDto:
32
+ if input_dto.config.build_app:
33
+
34
+ updated_conf_files:set[pathlib.Path] = set()
35
+ conf_output = ConfOutput(input_dto.config)
36
+
37
+
38
+ # Construct a path to a YML that does not actually exist.
39
+ # We mock this "fake" path since the YML does not exist.
40
+ # This ensures the checking for the existence of the CSV is correct
41
+ data_sources_fake_yml_path = input_dto.config.getPackageDirectoryPath() / "lookups" / "data_sources.yml"
42
+
43
+ # Construct a special lookup whose CSV is created at runtime and
44
+ # written directly into the lookups folder. We will delete this after a build,
45
+ # assuming that it is successful.
46
+ data_sources_lookup_csv_path = input_dto.config.getPackageDirectoryPath() / "lookups" / "data_sources.csv"
47
+
48
+
49
+
50
+ DataSourceWriter.writeDataSourceCsv(input_dto.director_output_dto.data_sources, data_sources_lookup_csv_path)
51
+ input_dto.director_output_dto.addContentToDictMappings(CSVLookup.model_construct(name="data_sources",
52
+ id=uuid.UUID("b45c1403-6e09-47b0-824f-cf6e44f15ac8"),
53
+ version=1,
54
+ author=input_dto.config.app.author_name,
55
+ date = datetime.date.today(),
56
+ description= "A lookup file that will contain the data source objects for detections.",
57
+ lookup_type=Lookup_Type.csv,
58
+ file_path=data_sources_fake_yml_path))
59
+ updated_conf_files.update(conf_output.writeHeaders())
60
+ updated_conf_files.update(conf_output.writeLookups(input_dto.director_output_dto.lookups))
61
+ updated_conf_files.update(conf_output.writeDetections(input_dto.director_output_dto.detections))
62
+ updated_conf_files.update(conf_output.writeStories(input_dto.director_output_dto.stories))
63
+ updated_conf_files.update(conf_output.writeBaselines(input_dto.director_output_dto.baselines))
64
+ updated_conf_files.update(conf_output.writeInvestigations(input_dto.director_output_dto.investigations))
65
+ updated_conf_files.update(conf_output.writeMacros(input_dto.director_output_dto.macros))
66
+ updated_conf_files.update(conf_output.writeDashboards(input_dto.director_output_dto.dashboards))
67
+ updated_conf_files.update(conf_output.writeMiscellaneousAppFiles())
68
+
69
+
70
+
71
+
72
+ #Ensure that the conf file we just generated/update is syntactically valid
73
+ for conf_file in updated_conf_files:
74
+ ConfWriter.validateConfFile(conf_file)
75
+
76
+ conf_output.packageApp()
77
+
78
+ print(f"Build of '{input_dto.config.app.title}' APP successful to {input_dto.config.getPackageFilePath()}")
79
+
80
+
81
+ if input_dto.config.build_api:
82
+ shutil.rmtree(input_dto.config.getAPIPath(), ignore_errors=True)
83
+ input_dto.config.getAPIPath().mkdir(parents=True)
84
+ api_json_output = ApiJsonOutput(input_dto.config.getAPIPath(), input_dto.config.app.label)
85
+ api_json_output.writeDetections(input_dto.director_output_dto.detections)
86
+ api_json_output.writeStories(input_dto.director_output_dto.stories)
87
+ api_json_output.writeBaselines(input_dto.director_output_dto.baselines)
88
+ api_json_output.writeInvestigations(input_dto.director_output_dto.investigations)
89
+ api_json_output.writeLookups(input_dto.director_output_dto.lookups)
90
+ api_json_output.writeMacros(input_dto.director_output_dto.macros)
91
+ api_json_output.writeDeployments(input_dto.director_output_dto.deployments)
92
+
93
+
94
+ #create version file for sse api
95
+ version_file = input_dto.config.getAPIPath()/"version.json"
96
+ utc_time = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0,tzinfo=None).isoformat()
97
+ version_dict = {"version":{"name":f"v{input_dto.config.app.version}","published_at": f"{utc_time}Z" }}
98
+ with open(version_file,"w") as version_f:
99
+ json.dump(version_dict,version_f)
100
+
101
+ print(f"Build of '{input_dto.config.app.title}' API successful to {input_dto.config.getAPIPath()}")
102
+
103
+ return input_dto.director_output_dto
@@ -5,7 +5,6 @@ from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfras
5
5
  from contentctl.actions.detection_testing.infrastructures.DetectionTestingInfrastructureServer import DetectionTestingInfrastructureServer
6
6
  from urllib.parse import urlparse
7
7
  from copy import deepcopy
8
- from contentctl.objects.enums import DetectionTestingTargetInfrastructure
9
8
  import signal
10
9
  import datetime
11
10
  # from queue import Queue
@@ -0,0 +1,247 @@
1
+ import logging
2
+ import os
3
+ import pathlib
4
+ from typing import TYPE_CHECKING, List, Optional
5
+
6
+ import pygit2
7
+ from pydantic import BaseModel, FilePath
8
+ from pygit2.enums import DeltaStatus
9
+
10
+ if TYPE_CHECKING:
11
+ from contentctl.input.director import DirectorOutputDto
12
+
13
+ from contentctl.objects.config import All, Changes, Selected, test_common
14
+ from contentctl.objects.data_source import DataSource
15
+ from contentctl.objects.detection import Detection
16
+ from contentctl.objects.lookup import CSVLookup, Lookup
17
+ from contentctl.objects.macro import Macro
18
+ from contentctl.objects.security_content_object import SecurityContentObject
19
+
20
+ # Logger
21
+ logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
22
+ LOGGER = logging.getLogger(__name__)
23
+
24
+
25
+ from contentctl.input.director import DirectorOutputDto
26
+
27
+
28
+ class GitService(BaseModel):
29
+ director: DirectorOutputDto
30
+ config: test_common
31
+ gitHash: Optional[str] = None
32
+
33
+ def getHash(self) -> str:
34
+ if self.gitHash is None:
35
+ raise Exception("Cannot get hash of repo, it was not set")
36
+ return self.gitHash
37
+
38
+ def getContent(self) -> List[Detection]:
39
+ if isinstance(self.config.mode, Selected):
40
+ return self.getSelected(self.config.mode.files)
41
+ elif isinstance(self.config.mode, Changes):
42
+ return self.getChanges(self.config.mode.target_branch)
43
+ if isinstance(self.config.mode, All):
44
+ return self.getAll()
45
+ else:
46
+ raise Exception(
47
+ f"Could not get content to test. Unsupported test mode '{self.config.mode}'"
48
+ )
49
+
50
+ def getAll(self) -> List[Detection]:
51
+ return self.director.detections
52
+
53
+ def getChanges(self, target_branch: str) -> List[Detection]:
54
+ repo = pygit2.Repository(path=str(self.config.path))
55
+
56
+ try:
57
+ target_tree = repo.revparse_single(target_branch).tree
58
+ self.gitHash = target_tree.id
59
+ diffs = repo.index.diff_to_tree(target_tree)
60
+ except Exception:
61
+ raise Exception(
62
+ f"Error parsing diff target_branch '{target_branch}'. Are you certain that it exists?"
63
+ )
64
+
65
+ # Get the uncommitted changes in the current directory
66
+ diffs2 = repo.index.diff_to_workdir()
67
+
68
+ # Combine the uncommitted changes with the committed changes
69
+ all_diffs = list(diffs) + list(diffs2)
70
+
71
+ # Make a filename to content map
72
+ filepath_to_content_map = {
73
+ obj.file_path: obj for (_, obj) in self.director.name_to_content_map.items()
74
+ }
75
+
76
+ updated_detections: set[Detection] = set()
77
+ updated_macros: set[Macro] = set()
78
+ updated_lookups: set[Lookup] = set()
79
+ updated_datasources: set[DataSource] = set()
80
+
81
+ for diff in all_diffs:
82
+ if type(diff) == pygit2.Patch:
83
+ if diff.delta.status in (
84
+ DeltaStatus.ADDED,
85
+ DeltaStatus.MODIFIED,
86
+ DeltaStatus.RENAMED,
87
+ ):
88
+ # print(f"{DeltaStatus(diff.delta.status).name:<8}:{diff.delta.new_file.raw_path}")
89
+ decoded_path = pathlib.Path(
90
+ diff.delta.new_file.raw_path.decode("utf-8")
91
+ )
92
+ # Note that we only handle updates to detections, lookups, and macros at this time. All other changes are ignored.
93
+ if (
94
+ decoded_path.is_relative_to(self.config.path / "detections")
95
+ and decoded_path.suffix == ".yml"
96
+ ):
97
+ detectionObject = filepath_to_content_map.get(
98
+ decoded_path, None
99
+ )
100
+ if isinstance(detectionObject, Detection):
101
+ updated_detections.add(detectionObject)
102
+ else:
103
+ raise Exception(
104
+ f"Error getting detection object for file {str(decoded_path)}"
105
+ )
106
+
107
+ elif (
108
+ decoded_path.is_relative_to(self.config.path / "macros")
109
+ and decoded_path.suffix == ".yml"
110
+ ):
111
+ macroObject = filepath_to_content_map.get(decoded_path, None)
112
+ if isinstance(macroObject, Macro):
113
+ updated_macros.add(macroObject)
114
+ else:
115
+ raise Exception(
116
+ f"Error getting macro object for file {str(decoded_path)}"
117
+ )
118
+
119
+ elif (
120
+ decoded_path.is_relative_to(self.config.path / "data_sources")
121
+ and decoded_path.suffix == ".yml"
122
+ ):
123
+ datasourceObject = filepath_to_content_map.get(
124
+ decoded_path, None
125
+ )
126
+ if isinstance(datasourceObject, DataSource):
127
+ updated_datasources.add(datasourceObject)
128
+ else:
129
+ raise Exception(
130
+ f"Error getting data source object for file {str(decoded_path)}"
131
+ )
132
+
133
+ elif decoded_path.is_relative_to(self.config.path / "lookups"):
134
+ # We need to convert this to a yml. This means we will catch
135
+ # both changes to a csv AND changes to the YML that uses it
136
+ if decoded_path.suffix == ".yml":
137
+ updatedLookup = filepath_to_content_map.get(
138
+ decoded_path, None
139
+ )
140
+ if not isinstance(updatedLookup, Lookup):
141
+ raise Exception(
142
+ f"Expected {decoded_path} to be type {type(Lookup)}, but instead if was {(type(updatedLookup))}"
143
+ )
144
+ updated_lookups.add(updatedLookup)
145
+
146
+ elif decoded_path.suffix == ".csv":
147
+ # If the CSV was updated, we want to make sure that we
148
+ # add the correct corresponding Lookup object.
149
+ # Filter to find the Lookup Object the references this CSV
150
+ matched = list(
151
+ filter(
152
+ lambda x: isinstance(x, CSVLookup)
153
+ and x.filename == decoded_path,
154
+ self.director.lookups,
155
+ )
156
+ )
157
+ if len(matched) == 0:
158
+ raise Exception(
159
+ f"Failed to find any lookups that reference the modified CSV file '{decoded_path}'"
160
+ )
161
+ elif len(matched) > 1:
162
+ raise Exception(
163
+ f"More than 1 Lookup reference the modified CSV file '{decoded_path}': {[match.file_path for match in matched]}"
164
+ )
165
+ else:
166
+ updatedLookup = matched[0]
167
+ elif decoded_path.suffix == ".mlmodel":
168
+ # Detected a changed .mlmodel file. However, since we do not have testing for these detections at
169
+ # this time, we will ignore this change.
170
+ updatedLookup = None
171
+
172
+ else:
173
+ raise Exception(
174
+ f"Detected a changed file in the lookups/ directory '{str(decoded_path)}'.\n"
175
+ "Only files ending in .csv, .yml, or .mlmodel are supported in this "
176
+ "directory. This file must be removed from the lookups/ directory."
177
+ )
178
+
179
+ if (
180
+ updatedLookup is not None
181
+ and updatedLookup not in updated_lookups
182
+ ):
183
+ # It is possible that both the CSV and YML have been modified for the same lookup,
184
+ # and we do not want to add it twice.
185
+ updated_lookups.add(updatedLookup)
186
+
187
+ else:
188
+ pass
189
+ # print(f"Ignore changes to file {decoded_path} since it is not a detection, macro, or lookup.")
190
+ else:
191
+ raise Exception(f"Unrecognized diff type {type(diff)}")
192
+
193
+ # If a detection has at least one dependency on changed content,
194
+ # then we must test it again
195
+
196
+ changed_macros_and_lookups_and_datasources: set[Macro | Lookup | DataSource] = (
197
+ updated_macros.union(updated_lookups, updated_datasources)
198
+ )
199
+
200
+ for detection in self.director.detections:
201
+ if detection in updated_detections:
202
+ # we are already planning to test it, don't need
203
+ # to add it again
204
+ continue
205
+
206
+ for obj in changed_macros_and_lookups_and_datasources:
207
+ if obj in detection.get_content_dependencies():
208
+ updated_detections.add(detection)
209
+ break
210
+
211
+ # Print out the names of all modified/new content
212
+ modifiedAndNewContentString = "\n - ".join(
213
+ sorted([d.name for d in updated_detections])
214
+ )
215
+
216
+ print(
217
+ f"[{len(updated_detections)}] Pieces of modifed and new content (this may include experimental/deprecated/manual_test content):\n - {modifiedAndNewContentString}"
218
+ )
219
+ return sorted(list(updated_detections))
220
+
221
+ def getSelected(self, detectionFilenames: List[FilePath]) -> List[Detection]:
222
+ filepath_to_content_map: dict[FilePath, SecurityContentObject] = {
223
+ obj.file_path: obj
224
+ for (_, obj) in self.director.name_to_content_map.items()
225
+ if obj.file_path is not None
226
+ }
227
+ errors = []
228
+ detections: List[Detection] = []
229
+ for name in detectionFilenames:
230
+ obj = filepath_to_content_map.get(name, None)
231
+ if obj is None:
232
+ errors.append(
233
+ f"There is no detection file or security_content_object at '{name}'"
234
+ )
235
+ elif not isinstance(obj, Detection):
236
+ errors.append(
237
+ f"The security_content_object at '{name}' is of type '{type(obj).__name__}', NOT '{Detection.__name__}'"
238
+ )
239
+ else:
240
+ detections.append(obj)
241
+
242
+ if errors:
243
+ errorsString = "\n - ".join(errors)
244
+ raise Exception(
245
+ f"The following errors were encountered while getting selected detections to test:\n - {errorsString}"
246
+ )
247
+ return detections
@@ -442,7 +442,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
442
442
  self.format_pbar_string(
443
443
  TestReportingType.GROUP,
444
444
  test_group.name,
445
- FinalTestingStates.SKIP.value,
445
+ FinalTestingStates.SKIP,
446
446
  start_time=time.time(),
447
447
  set_pbar=False,
448
448
  )
@@ -483,7 +483,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
483
483
  self.format_pbar_string(
484
484
  TestReportingType.GROUP,
485
485
  test_group.name,
486
- TestingStates.DONE_GROUP.value,
486
+ TestingStates.DONE_GROUP,
487
487
  start_time=setup_results.start_time,
488
488
  set_pbar=False,
489
489
  )
@@ -504,7 +504,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
504
504
  self.format_pbar_string(
505
505
  TestReportingType.GROUP,
506
506
  test_group.name,
507
- TestingStates.BEGINNING_GROUP.value,
507
+ TestingStates.BEGINNING_GROUP,
508
508
  start_time=setup_start_time
509
509
  )
510
510
  # https://github.com/WoLpH/python-progressbar/issues/164
@@ -544,7 +544,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
544
544
  self.format_pbar_string(
545
545
  TestReportingType.GROUP,
546
546
  test_group.name,
547
- TestingStates.DELETING.value,
547
+ TestingStates.DELETING,
548
548
  start_time=test_group_start_time,
549
549
  )
550
550
 
@@ -632,7 +632,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
632
632
  self.format_pbar_string(
633
633
  TestReportingType.UNIT,
634
634
  f"{detection.name}:{test.name}",
635
- FinalTestingStates.SKIP.value,
635
+ FinalTestingStates.SKIP,
636
636
  start_time=test_start_time,
637
637
  set_pbar=False,
638
638
  )
@@ -664,7 +664,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
664
664
  self.format_pbar_string(
665
665
  TestReportingType.UNIT,
666
666
  f"{detection.name}:{test.name}",
667
- FinalTestingStates.ERROR.value,
667
+ FinalTestingStates.ERROR,
668
668
  start_time=test_start_time,
669
669
  set_pbar=False,
670
670
  )
@@ -724,7 +724,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
724
724
  res = "ERROR"
725
725
  link = detection.search
726
726
  else:
727
- res = test.result.status.value.upper() # type: ignore
727
+ res = test.result.status.upper() # type: ignore
728
728
  link = test.result.get_summary_dict()["sid_link"]
729
729
 
730
730
  self.format_pbar_string(
@@ -755,7 +755,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
755
755
  self.format_pbar_string(
756
756
  TestReportingType.UNIT,
757
757
  f"{detection.name}:{test.name}",
758
- FinalTestingStates.PASS.value,
758
+ FinalTestingStates.PASS,
759
759
  start_time=test_start_time,
760
760
  set_pbar=False,
761
761
  )
@@ -766,7 +766,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
766
766
  self.format_pbar_string(
767
767
  TestReportingType.UNIT,
768
768
  f"{detection.name}:{test.name}",
769
- FinalTestingStates.SKIP.value,
769
+ FinalTestingStates.SKIP,
770
770
  start_time=test_start_time,
771
771
  set_pbar=False,
772
772
  )
@@ -777,7 +777,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
777
777
  self.format_pbar_string(
778
778
  TestReportingType.UNIT,
779
779
  f"{detection.name}:{test.name}",
780
- FinalTestingStates.FAIL.value,
780
+ FinalTestingStates.FAIL,
781
781
  start_time=test_start_time,
782
782
  set_pbar=False,
783
783
  )
@@ -788,7 +788,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
788
788
  self.format_pbar_string(
789
789
  TestReportingType.UNIT,
790
790
  f"{detection.name}:{test.name}",
791
- FinalTestingStates.ERROR.value,
791
+ FinalTestingStates.ERROR,
792
792
  start_time=test_start_time,
793
793
  set_pbar=False,
794
794
  )
@@ -821,7 +821,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
821
821
  test_start_time = time.time()
822
822
 
823
823
  # First, check to see if the test should be skipped (Hunting or Correlation)
824
- if detection.type in [AnalyticsType.Hunting.value, AnalyticsType.Correlation.value]:
824
+ if detection.type in [AnalyticsType.Hunting, AnalyticsType.Correlation]:
825
825
  test.skip(
826
826
  f"TEST SKIPPED: detection is type {detection.type} and cannot be integration "
827
827
  "tested at this time"
@@ -843,11 +843,11 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
843
843
  # Determine the reporting state (we should only encounter SKIP/FAIL/ERROR)
844
844
  state: str
845
845
  if test.result.status == TestResultStatus.SKIP:
846
- state = FinalTestingStates.SKIP.value
846
+ state = FinalTestingStates.SKIP
847
847
  elif test.result.status == TestResultStatus.FAIL:
848
- state = FinalTestingStates.FAIL.value
848
+ state = FinalTestingStates.FAIL
849
849
  elif test.result.status == TestResultStatus.ERROR:
850
- state = FinalTestingStates.ERROR.value
850
+ state = FinalTestingStates.ERROR
851
851
  else:
852
852
  raise ValueError(
853
853
  f"Status for (integration) '{detection.name}:{test.name}' was preemptively set"
@@ -891,7 +891,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
891
891
  self.format_pbar_string(
892
892
  TestReportingType.INTEGRATION,
893
893
  f"{detection.name}:{test.name}",
894
- FinalTestingStates.FAIL.value,
894
+ FinalTestingStates.FAIL,
895
895
  start_time=test_start_time,
896
896
  set_pbar=False,
897
897
  )
@@ -935,7 +935,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
935
935
  if test.result is None:
936
936
  res = "ERROR"
937
937
  else:
938
- res = test.result.status.value.upper() # type: ignore
938
+ res = test.result.status.upper() # type: ignore
939
939
 
940
940
  # Get the link to the saved search in this specific instance
941
941
  link = f"https://{self.infrastructure.instance_address}:{self.infrastructure.web_ui_port}"
@@ -968,7 +968,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
968
968
  self.format_pbar_string(
969
969
  TestReportingType.INTEGRATION,
970
970
  f"{detection.name}:{test.name}",
971
- FinalTestingStates.PASS.value,
971
+ FinalTestingStates.PASS,
972
972
  start_time=test_start_time,
973
973
  set_pbar=False,
974
974
  )
@@ -979,7 +979,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
979
979
  self.format_pbar_string(
980
980
  TestReportingType.INTEGRATION,
981
981
  f"{detection.name}:{test.name}",
982
- FinalTestingStates.SKIP.value,
982
+ FinalTestingStates.SKIP,
983
983
  start_time=test_start_time,
984
984
  set_pbar=False,
985
985
  )
@@ -990,7 +990,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
990
990
  self.format_pbar_string(
991
991
  TestReportingType.INTEGRATION,
992
992
  f"{detection.name}:{test.name}",
993
- FinalTestingStates.FAIL.value,
993
+ FinalTestingStates.FAIL,
994
994
  start_time=test_start_time,
995
995
  set_pbar=False,
996
996
  )
@@ -1001,7 +1001,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1001
1001
  self.format_pbar_string(
1002
1002
  TestReportingType.INTEGRATION,
1003
1003
  f"{detection.name}:{test.name}",
1004
- FinalTestingStates.ERROR.value,
1004
+ FinalTestingStates.ERROR,
1005
1005
  start_time=test_start_time,
1006
1006
  set_pbar=False,
1007
1007
  )
@@ -1077,7 +1077,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1077
1077
  self.format_pbar_string(
1078
1078
  TestReportingType.UNIT,
1079
1079
  f"{detection.name}:{test.name}",
1080
- TestingStates.PROCESSING.value,
1080
+ TestingStates.PROCESSING,
1081
1081
  start_time=start_time
1082
1082
  )
1083
1083
 
@@ -1086,7 +1086,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1086
1086
  self.format_pbar_string(
1087
1087
  TestReportingType.UNIT,
1088
1088
  f"{detection.name}:{test.name}",
1089
- TestingStates.SEARCHING.value,
1089
+ TestingStates.SEARCHING,
1090
1090
  start_time=start_time,
1091
1091
  )
1092
1092
 
@@ -1094,6 +1094,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1094
1094
  job = self.get_conn().search(query=search, **kwargs)
1095
1095
  results = JSONResultsReader(job.results(output_mode="json"))
1096
1096
 
1097
+ # TODO (cmcginley): @ljstella you're removing this ultimately, right?
1097
1098
  # Consolidate a set of the distinct observable field names
1098
1099
  observable_fields_set = set([o.name for o in detection.tags.observable]) # keeping this around for later
1099
1100
  risk_object_fields_set = set([o.name for o in detection.tags.observable if "Victim" in o.role ]) # just the "Risk Objects"
@@ -1121,7 +1122,10 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1121
1122
  missing_risk_objects = risk_object_fields_set - results_fields_set
1122
1123
  if len(missing_risk_objects) > 0:
1123
1124
  # Report a failure in such cases
1124
- e = Exception(f"The observable field(s) {missing_risk_objects} are missing in the detection results")
1125
+ e = Exception(
1126
+ f"The risk object field(s) {missing_risk_objects} are missing in the "
1127
+ "detection results"
1128
+ )
1125
1129
  test.result.set_job_content(
1126
1130
  job.content,
1127
1131
  self.infrastructure,
@@ -1137,6 +1141,8 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1137
1141
  # on a field. In this case, the field will appear but will not contain any values
1138
1142
  current_empty_fields: set[str] = set()
1139
1143
 
1144
+ # TODO (cmcginley): @ljstella is this something we're keeping for testing as
1145
+ # well?
1140
1146
  for field in observable_fields_set:
1141
1147
  if result.get(field, 'null') == 'null':
1142
1148
  if field in risk_object_fields_set:
@@ -1289,7 +1295,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1289
1295
  self.format_pbar_string(
1290
1296
  TestReportingType.GROUP,
1291
1297
  test_group.name,
1292
- TestingStates.DOWNLOADING.value,
1298
+ TestingStates.DOWNLOADING,
1293
1299
  start_time=test_group_start_time
1294
1300
  )
1295
1301
 
@@ -1307,7 +1313,7 @@ class DetectionTestingInfrastructure(BaseModel, abc.ABC):
1307
1313
  self.format_pbar_string(
1308
1314
  TestReportingType.GROUP,
1309
1315
  test_group.name,
1310
- TestingStates.REPLAYING.value,
1316
+ TestingStates.REPLAYING,
1311
1317
  start_time=test_group_start_time
1312
1318
  )
1313
1319
 
@@ -1,10 +1,10 @@
1
1
  import time
2
- from enum import Enum
2
+ from enum import StrEnum
3
3
  from tqdm import tqdm
4
4
  import datetime
5
5
 
6
6
 
7
- class TestReportingType(str, Enum):
7
+ class TestReportingType(StrEnum):
8
8
  """
9
9
  5-char identifiers for the type of testing being reported on
10
10
  """
@@ -21,7 +21,7 @@ class TestReportingType(str, Enum):
21
21
  INTEGRATION = "INTEG"
22
22
 
23
23
 
24
- class TestingStates(str, Enum):
24
+ class TestingStates(StrEnum):
25
25
  """
26
26
  Defined testing states
27
27
  """
@@ -40,10 +40,10 @@ class TestingStates(str, Enum):
40
40
 
41
41
 
42
42
  # the longest length of any state
43
- LONGEST_STATE = max(len(w.value) for w in TestingStates)
43
+ LONGEST_STATE = max(len(w) for w in TestingStates)
44
44
 
45
45
 
46
- class FinalTestingStates(str, Enum):
46
+ class FinalTestingStates(StrEnum):
47
47
  """
48
48
  The possible final states for a test (for pbar reporting)
49
49
  """
@@ -82,7 +82,7 @@ def format_pbar_string(
82
82
  :returns: a formatted string for use w/ pbar
83
83
  """
84
84
  # Extract and ljust our various fields
85
- field_one = test_reporting_type.value
85
+ field_one = test_reporting_type
86
86
  field_two = test_name.ljust(MAX_TEST_NAME_LENGTH)
87
87
  field_three = state.ljust(LONGEST_STATE)
88
88
  field_four = datetime.timedelta(seconds=round(time.time() - start_time))