regscale-cli 6.16.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.
- regscale/__init__.py +1 -0
- regscale/airflow/__init__.py +9 -0
- regscale/airflow/azure/__init__.py +9 -0
- regscale/airflow/azure/cli.py +89 -0
- regscale/airflow/azure/upload_dags.py +116 -0
- regscale/airflow/click_dags.py +127 -0
- regscale/airflow/click_mixins.py +82 -0
- regscale/airflow/config.py +25 -0
- regscale/airflow/factories/__init__.py +0 -0
- regscale/airflow/factories/connections.py +58 -0
- regscale/airflow/factories/workflows.py +78 -0
- regscale/airflow/hierarchy.py +88 -0
- regscale/airflow/operators/__init__.py +0 -0
- regscale/airflow/operators/click.py +36 -0
- regscale/airflow/sensors/__init__.py +0 -0
- regscale/airflow/sensors/sql.py +107 -0
- regscale/airflow/sessions/__init__.py +0 -0
- regscale/airflow/sessions/sql/__init__.py +3 -0
- regscale/airflow/sessions/sql/queries.py +64 -0
- regscale/airflow/sessions/sql/sql_server_queries.py +248 -0
- regscale/airflow/tasks/__init__.py +0 -0
- regscale/airflow/tasks/branches.py +22 -0
- regscale/airflow/tasks/cli.py +116 -0
- regscale/airflow/tasks/click.py +73 -0
- regscale/airflow/tasks/debugging.py +9 -0
- regscale/airflow/tasks/groups.py +116 -0
- regscale/airflow/tasks/init.py +60 -0
- regscale/airflow/tasks/states.py +47 -0
- regscale/airflow/tasks/workflows.py +36 -0
- regscale/ansible/__init__.py +9 -0
- regscale/core/__init__.py +0 -0
- regscale/core/app/__init__.py +3 -0
- regscale/core/app/api.py +571 -0
- regscale/core/app/application.py +665 -0
- regscale/core/app/internal/__init__.py +136 -0
- regscale/core/app/internal/admin_actions.py +230 -0
- regscale/core/app/internal/assessments_editor.py +873 -0
- regscale/core/app/internal/catalog.py +316 -0
- regscale/core/app/internal/comparison.py +459 -0
- regscale/core/app/internal/control_editor.py +571 -0
- regscale/core/app/internal/encrypt.py +79 -0
- regscale/core/app/internal/evidence.py +1240 -0
- regscale/core/app/internal/file_uploads.py +151 -0
- regscale/core/app/internal/healthcheck.py +66 -0
- regscale/core/app/internal/login.py +305 -0
- regscale/core/app/internal/migrations.py +240 -0
- regscale/core/app/internal/model_editor.py +1701 -0
- regscale/core/app/internal/poam_editor.py +632 -0
- regscale/core/app/internal/workflow.py +105 -0
- regscale/core/app/logz.py +74 -0
- regscale/core/app/utils/XMLIR.py +258 -0
- regscale/core/app/utils/__init__.py +0 -0
- regscale/core/app/utils/api_handler.py +358 -0
- regscale/core/app/utils/app_utils.py +1110 -0
- regscale/core/app/utils/catalog_utils/__init__.py +0 -0
- regscale/core/app/utils/catalog_utils/common.py +91 -0
- regscale/core/app/utils/catalog_utils/compare_catalog.py +193 -0
- regscale/core/app/utils/catalog_utils/diagnostic_catalog.py +97 -0
- regscale/core/app/utils/catalog_utils/download_catalog.py +103 -0
- regscale/core/app/utils/catalog_utils/update_catalog.py +718 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v2.py +1378 -0
- regscale/core/app/utils/catalog_utils/update_catalog_v3.py +1272 -0
- regscale/core/app/utils/catalog_utils/update_plans.py +334 -0
- regscale/core/app/utils/file_utils.py +238 -0
- regscale/core/app/utils/parser_utils.py +81 -0
- regscale/core/app/utils/pickle_file_handler.py +57 -0
- regscale/core/app/utils/regscale_utils.py +319 -0
- regscale/core/app/utils/report_utils.py +119 -0
- regscale/core/app/utils/variables.py +226 -0
- regscale/core/decorators.py +31 -0
- regscale/core/lazy_group.py +65 -0
- regscale/core/login.py +63 -0
- regscale/core/server/__init__.py +0 -0
- regscale/core/server/flask_api.py +473 -0
- regscale/core/server/helpers.py +373 -0
- regscale/core/server/rest.py +64 -0
- regscale/core/server/static/css/bootstrap.css +6030 -0
- regscale/core/server/static/css/bootstrap.min.css +6 -0
- regscale/core/server/static/css/main.css +176 -0
- regscale/core/server/static/images/regscale-cli.svg +49 -0
- regscale/core/server/static/images/regscale.svg +38 -0
- regscale/core/server/templates/base.html +74 -0
- regscale/core/server/templates/index.html +43 -0
- regscale/core/server/templates/login.html +28 -0
- regscale/core/server/templates/make_base64.html +22 -0
- regscale/core/server/templates/upload_STIG.html +109 -0
- regscale/core/server/templates/upload_STIG_result.html +26 -0
- regscale/core/server/templates/upload_ssp.html +144 -0
- regscale/core/server/templates/upload_ssp_result.html +128 -0
- regscale/core/static/__init__.py +0 -0
- regscale/core/static/regex.py +14 -0
- regscale/core/utils/__init__.py +117 -0
- regscale/core/utils/click_utils.py +13 -0
- regscale/core/utils/date.py +238 -0
- regscale/core/utils/graphql.py +254 -0
- regscale/core/utils/urls.py +23 -0
- regscale/dev/__init__.py +6 -0
- regscale/dev/analysis.py +454 -0
- regscale/dev/cli.py +235 -0
- regscale/dev/code_gen.py +492 -0
- regscale/dev/dirs.py +69 -0
- regscale/dev/docs.py +384 -0
- regscale/dev/monitoring.py +26 -0
- regscale/dev/profiling.py +216 -0
- regscale/exceptions/__init__.py +4 -0
- regscale/exceptions/license_exception.py +7 -0
- regscale/exceptions/validation_exception.py +9 -0
- regscale/integrations/__init__.py +1 -0
- regscale/integrations/commercial/__init__.py +486 -0
- regscale/integrations/commercial/ad.py +433 -0
- regscale/integrations/commercial/amazon/__init__.py +0 -0
- regscale/integrations/commercial/amazon/common.py +106 -0
- regscale/integrations/commercial/aqua/__init__.py +0 -0
- regscale/integrations/commercial/aqua/aqua.py +91 -0
- regscale/integrations/commercial/aws/__init__.py +6 -0
- regscale/integrations/commercial/aws/cli.py +322 -0
- regscale/integrations/commercial/aws/inventory/__init__.py +110 -0
- regscale/integrations/commercial/aws/inventory/base.py +64 -0
- regscale/integrations/commercial/aws/inventory/resources/__init__.py +19 -0
- regscale/integrations/commercial/aws/inventory/resources/compute.py +234 -0
- regscale/integrations/commercial/aws/inventory/resources/containers.py +113 -0
- regscale/integrations/commercial/aws/inventory/resources/database.py +101 -0
- regscale/integrations/commercial/aws/inventory/resources/integration.py +237 -0
- regscale/integrations/commercial/aws/inventory/resources/networking.py +253 -0
- regscale/integrations/commercial/aws/inventory/resources/security.py +240 -0
- regscale/integrations/commercial/aws/inventory/resources/storage.py +91 -0
- regscale/integrations/commercial/aws/scanner.py +823 -0
- regscale/integrations/commercial/azure/__init__.py +0 -0
- regscale/integrations/commercial/azure/common.py +32 -0
- regscale/integrations/commercial/azure/intune.py +488 -0
- regscale/integrations/commercial/azure/scanner.py +49 -0
- regscale/integrations/commercial/burp.py +78 -0
- regscale/integrations/commercial/cpe.py +144 -0
- regscale/integrations/commercial/crowdstrike.py +1117 -0
- regscale/integrations/commercial/defender.py +1511 -0
- regscale/integrations/commercial/dependabot.py +210 -0
- regscale/integrations/commercial/durosuite/__init__.py +0 -0
- regscale/integrations/commercial/durosuite/api.py +1546 -0
- regscale/integrations/commercial/durosuite/process_devices.py +101 -0
- regscale/integrations/commercial/durosuite/scanner.py +637 -0
- regscale/integrations/commercial/durosuite/variables.py +21 -0
- regscale/integrations/commercial/ecr.py +90 -0
- regscale/integrations/commercial/gcp/__init__.py +237 -0
- regscale/integrations/commercial/gcp/auth.py +96 -0
- regscale/integrations/commercial/gcp/control_tests.py +238 -0
- regscale/integrations/commercial/gcp/variables.py +18 -0
- regscale/integrations/commercial/gitlab.py +332 -0
- regscale/integrations/commercial/grype.py +165 -0
- regscale/integrations/commercial/ibm.py +90 -0
- regscale/integrations/commercial/import_all/__init__.py +0 -0
- regscale/integrations/commercial/import_all/import_all_cmd.py +467 -0
- regscale/integrations/commercial/import_all/scan_file_fingerprints.json +27 -0
- regscale/integrations/commercial/jira.py +1046 -0
- regscale/integrations/commercial/mappings/__init__.py +0 -0
- regscale/integrations/commercial/mappings/csf_controls.json +713 -0
- regscale/integrations/commercial/mappings/nist_800_53_r5_controls.json +1516 -0
- regscale/integrations/commercial/nessus/__init__.py +0 -0
- regscale/integrations/commercial/nessus/nessus_utils.py +429 -0
- regscale/integrations/commercial/nessus/scanner.py +416 -0
- regscale/integrations/commercial/nexpose.py +90 -0
- regscale/integrations/commercial/okta.py +798 -0
- regscale/integrations/commercial/opentext/__init__.py +0 -0
- regscale/integrations/commercial/opentext/click.py +99 -0
- regscale/integrations/commercial/opentext/scanner.py +143 -0
- regscale/integrations/commercial/prisma.py +91 -0
- regscale/integrations/commercial/qualys.py +1462 -0
- regscale/integrations/commercial/salesforce.py +980 -0
- regscale/integrations/commercial/sap/__init__.py +0 -0
- regscale/integrations/commercial/sap/click.py +31 -0
- regscale/integrations/commercial/sap/sysdig/__init__.py +0 -0
- regscale/integrations/commercial/sap/sysdig/click.py +57 -0
- regscale/integrations/commercial/sap/sysdig/sysdig_scanner.py +190 -0
- regscale/integrations/commercial/sap/tenable/__init__.py +0 -0
- regscale/integrations/commercial/sap/tenable/click.py +49 -0
- regscale/integrations/commercial/sap/tenable/scanner.py +196 -0
- regscale/integrations/commercial/servicenow.py +1756 -0
- regscale/integrations/commercial/sicura/__init__.py +0 -0
- regscale/integrations/commercial/sicura/api.py +855 -0
- regscale/integrations/commercial/sicura/commands.py +73 -0
- regscale/integrations/commercial/sicura/scanner.py +481 -0
- regscale/integrations/commercial/sicura/variables.py +16 -0
- regscale/integrations/commercial/snyk.py +90 -0
- regscale/integrations/commercial/sonarcloud.py +260 -0
- regscale/integrations/commercial/sqlserver.py +369 -0
- regscale/integrations/commercial/stig_mapper_integration/__init__.py +0 -0
- regscale/integrations/commercial/stig_mapper_integration/click_commands.py +38 -0
- regscale/integrations/commercial/stig_mapper_integration/mapping_engine.py +353 -0
- regscale/integrations/commercial/stigv2/__init__.py +0 -0
- regscale/integrations/commercial/stigv2/ckl_parser.py +349 -0
- regscale/integrations/commercial/stigv2/click_commands.py +95 -0
- regscale/integrations/commercial/stigv2/stig_integration.py +202 -0
- regscale/integrations/commercial/synqly/__init__.py +0 -0
- regscale/integrations/commercial/synqly/assets.py +46 -0
- regscale/integrations/commercial/synqly/ticketing.py +132 -0
- regscale/integrations/commercial/synqly/vulnerabilities.py +223 -0
- regscale/integrations/commercial/synqly_jira.py +840 -0
- regscale/integrations/commercial/tenablev2/__init__.py +0 -0
- regscale/integrations/commercial/tenablev2/authenticate.py +31 -0
- regscale/integrations/commercial/tenablev2/click.py +1584 -0
- regscale/integrations/commercial/tenablev2/scanner.py +504 -0
- regscale/integrations/commercial/tenablev2/stig_parsers.py +140 -0
- regscale/integrations/commercial/tenablev2/utils.py +78 -0
- regscale/integrations/commercial/tenablev2/variables.py +17 -0
- regscale/integrations/commercial/trivy.py +162 -0
- regscale/integrations/commercial/veracode.py +96 -0
- regscale/integrations/commercial/wizv2/WizDataMixin.py +97 -0
- regscale/integrations/commercial/wizv2/__init__.py +0 -0
- regscale/integrations/commercial/wizv2/click.py +429 -0
- regscale/integrations/commercial/wizv2/constants.py +1001 -0
- regscale/integrations/commercial/wizv2/issue.py +361 -0
- regscale/integrations/commercial/wizv2/models.py +112 -0
- regscale/integrations/commercial/wizv2/parsers.py +339 -0
- regscale/integrations/commercial/wizv2/sbom.py +115 -0
- regscale/integrations/commercial/wizv2/scanner.py +416 -0
- regscale/integrations/commercial/wizv2/utils.py +796 -0
- regscale/integrations/commercial/wizv2/variables.py +39 -0
- regscale/integrations/commercial/wizv2/wiz_auth.py +159 -0
- regscale/integrations/commercial/xray.py +91 -0
- regscale/integrations/integration/__init__.py +2 -0
- regscale/integrations/integration/integration.py +26 -0
- regscale/integrations/integration/inventory.py +17 -0
- regscale/integrations/integration/issue.py +100 -0
- regscale/integrations/integration_override.py +149 -0
- regscale/integrations/public/__init__.py +103 -0
- regscale/integrations/public/cisa.py +641 -0
- regscale/integrations/public/criticality_updater.py +70 -0
- regscale/integrations/public/emass.py +411 -0
- regscale/integrations/public/emass_slcm_import.py +697 -0
- regscale/integrations/public/fedramp/__init__.py +0 -0
- regscale/integrations/public/fedramp/appendix_parser.py +548 -0
- regscale/integrations/public/fedramp/click.py +479 -0
- regscale/integrations/public/fedramp/components.py +714 -0
- regscale/integrations/public/fedramp/docx_parser.py +259 -0
- regscale/integrations/public/fedramp/fedramp_cis_crm.py +1124 -0
- regscale/integrations/public/fedramp/fedramp_common.py +3181 -0
- regscale/integrations/public/fedramp/fedramp_docx.py +388 -0
- regscale/integrations/public/fedramp/fedramp_five.py +2343 -0
- regscale/integrations/public/fedramp/fedramp_traversal.py +138 -0
- regscale/integrations/public/fedramp/import_fedramp_r4_ssp.py +279 -0
- regscale/integrations/public/fedramp/import_workbook.py +495 -0
- regscale/integrations/public/fedramp/inventory_items.py +244 -0
- regscale/integrations/public/fedramp/mappings/__init__.py +0 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r4_parts.json +7388 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_params.json +8636 -0
- regscale/integrations/public/fedramp/mappings/fedramp_r5_parts.json +9605 -0
- regscale/integrations/public/fedramp/mappings/system_roles.py +34 -0
- regscale/integrations/public/fedramp/mappings/user.py +175 -0
- regscale/integrations/public/fedramp/mappings/values.py +141 -0
- regscale/integrations/public/fedramp/markdown_parser.py +150 -0
- regscale/integrations/public/fedramp/metadata.py +689 -0
- regscale/integrations/public/fedramp/models/__init__.py +59 -0
- regscale/integrations/public/fedramp/models/leveraged_auth_new.py +168 -0
- regscale/integrations/public/fedramp/models/poam_importer.py +522 -0
- regscale/integrations/public/fedramp/parts_mapper.py +107 -0
- regscale/integrations/public/fedramp/poam/__init__.py +0 -0
- regscale/integrations/public/fedramp/poam/scanner.py +851 -0
- regscale/integrations/public/fedramp/properties.py +201 -0
- regscale/integrations/public/fedramp/reporting.py +84 -0
- regscale/integrations/public/fedramp/resources.py +496 -0
- regscale/integrations/public/fedramp/rosetta.py +110 -0
- regscale/integrations/public/fedramp/ssp_logger.py +87 -0
- regscale/integrations/public/fedramp/system_characteristics.py +922 -0
- regscale/integrations/public/fedramp/system_control_implementations.py +582 -0
- regscale/integrations/public/fedramp/system_implementation.py +190 -0
- regscale/integrations/public/fedramp/xml_utils.py +87 -0
- regscale/integrations/public/nist_catalog.py +275 -0
- regscale/integrations/public/oscal.py +1946 -0
- regscale/integrations/public/otx.py +169 -0
- regscale/integrations/scanner_integration.py +2692 -0
- regscale/integrations/variables.py +25 -0
- regscale/models/__init__.py +7 -0
- regscale/models/app_models/__init__.py +5 -0
- regscale/models/app_models/catalog_compare.py +213 -0
- regscale/models/app_models/click.py +252 -0
- regscale/models/app_models/datetime_encoder.py +21 -0
- regscale/models/app_models/import_validater.py +321 -0
- regscale/models/app_models/mapping.py +260 -0
- regscale/models/app_models/pipeline.py +37 -0
- regscale/models/click_models.py +413 -0
- regscale/models/config.py +154 -0
- regscale/models/email_style.css +67 -0
- regscale/models/hierarchy.py +8 -0
- regscale/models/inspect_models.py +79 -0
- regscale/models/integration_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/__init__.py +0 -0
- regscale/models/integration_models/amazon_models/inspector.py +262 -0
- regscale/models/integration_models/amazon_models/inspector_scan.py +206 -0
- regscale/models/integration_models/aqua.py +247 -0
- regscale/models/integration_models/azure_alerts.py +255 -0
- regscale/models/integration_models/base64.py +23 -0
- regscale/models/integration_models/burp.py +433 -0
- regscale/models/integration_models/burp_models.py +128 -0
- regscale/models/integration_models/cisa_kev_data.json +19333 -0
- regscale/models/integration_models/defender_data.py +93 -0
- regscale/models/integration_models/defenderimport.py +143 -0
- regscale/models/integration_models/drf.py +443 -0
- regscale/models/integration_models/ecr_models/__init__.py +0 -0
- regscale/models/integration_models/ecr_models/data.py +69 -0
- regscale/models/integration_models/ecr_models/ecr.py +239 -0
- regscale/models/integration_models/flat_file_importer.py +1079 -0
- regscale/models/integration_models/grype_import.py +247 -0
- regscale/models/integration_models/ibm.py +126 -0
- regscale/models/integration_models/implementation_results.py +85 -0
- regscale/models/integration_models/nexpose.py +140 -0
- regscale/models/integration_models/prisma.py +202 -0
- regscale/models/integration_models/qualys.py +720 -0
- regscale/models/integration_models/qualys_scanner.py +160 -0
- regscale/models/integration_models/sbom/__init__.py +0 -0
- regscale/models/integration_models/sbom/cyclone_dx.py +139 -0
- regscale/models/integration_models/send_reminders.py +620 -0
- regscale/models/integration_models/snyk.py +155 -0
- regscale/models/integration_models/synqly_models/__init__.py +0 -0
- regscale/models/integration_models/synqly_models/capabilities.json +1 -0
- regscale/models/integration_models/synqly_models/connector_types.py +22 -0
- regscale/models/integration_models/synqly_models/connectors/__init__.py +7 -0
- regscale/models/integration_models/synqly_models/connectors/assets.py +97 -0
- regscale/models/integration_models/synqly_models/connectors/ticketing.py +583 -0
- regscale/models/integration_models/synqly_models/connectors/vulnerabilities.py +169 -0
- regscale/models/integration_models/synqly_models/ocsf_mapper.py +331 -0
- regscale/models/integration_models/synqly_models/param.py +72 -0
- regscale/models/integration_models/synqly_models/synqly_model.py +733 -0
- regscale/models/integration_models/synqly_models/tenants.py +39 -0
- regscale/models/integration_models/tenable_models/__init__.py +0 -0
- regscale/models/integration_models/tenable_models/integration.py +187 -0
- regscale/models/integration_models/tenable_models/models.py +513 -0
- regscale/models/integration_models/trivy_import.py +231 -0
- regscale/models/integration_models/veracode.py +217 -0
- regscale/models/integration_models/xray.py +135 -0
- regscale/models/locking.py +100 -0
- regscale/models/platform.py +110 -0
- regscale/models/regscale_models/__init__.py +67 -0
- regscale/models/regscale_models/assessment.py +570 -0
- regscale/models/regscale_models/assessment_plan.py +52 -0
- regscale/models/regscale_models/asset.py +567 -0
- regscale/models/regscale_models/asset_mapping.py +190 -0
- regscale/models/regscale_models/case.py +42 -0
- regscale/models/regscale_models/catalog.py +261 -0
- regscale/models/regscale_models/cci.py +46 -0
- regscale/models/regscale_models/change.py +167 -0
- regscale/models/regscale_models/checklist.py +372 -0
- regscale/models/regscale_models/comment.py +49 -0
- regscale/models/regscale_models/compliance_settings.py +112 -0
- regscale/models/regscale_models/component.py +412 -0
- regscale/models/regscale_models/component_mapping.py +65 -0
- regscale/models/regscale_models/control.py +38 -0
- regscale/models/regscale_models/control_implementation.py +1128 -0
- regscale/models/regscale_models/control_objective.py +261 -0
- regscale/models/regscale_models/control_parameter.py +100 -0
- regscale/models/regscale_models/control_test.py +34 -0
- regscale/models/regscale_models/control_test_plan.py +75 -0
- regscale/models/regscale_models/control_test_result.py +52 -0
- regscale/models/regscale_models/custom_field.py +245 -0
- regscale/models/regscale_models/data.py +109 -0
- regscale/models/regscale_models/data_center.py +40 -0
- regscale/models/regscale_models/deviation.py +203 -0
- regscale/models/regscale_models/email.py +97 -0
- regscale/models/regscale_models/evidence.py +47 -0
- regscale/models/regscale_models/evidence_mapping.py +40 -0
- regscale/models/regscale_models/facility.py +59 -0
- regscale/models/regscale_models/file.py +382 -0
- regscale/models/regscale_models/filetag.py +37 -0
- regscale/models/regscale_models/form_field_value.py +94 -0
- regscale/models/regscale_models/group.py +169 -0
- regscale/models/regscale_models/implementation_objective.py +335 -0
- regscale/models/regscale_models/implementation_option.py +275 -0
- regscale/models/regscale_models/implementation_role.py +33 -0
- regscale/models/regscale_models/incident.py +177 -0
- regscale/models/regscale_models/interconnection.py +43 -0
- regscale/models/regscale_models/issue.py +1176 -0
- regscale/models/regscale_models/leveraged_authorization.py +125 -0
- regscale/models/regscale_models/line_of_inquiry.py +52 -0
- regscale/models/regscale_models/link.py +205 -0
- regscale/models/regscale_models/meta_data.py +64 -0
- regscale/models/regscale_models/mixins/__init__.py +0 -0
- regscale/models/regscale_models/mixins/parent_cache.py +124 -0
- regscale/models/regscale_models/module.py +224 -0
- regscale/models/regscale_models/modules.py +191 -0
- regscale/models/regscale_models/objective.py +14 -0
- regscale/models/regscale_models/parameter.py +87 -0
- regscale/models/regscale_models/ports_protocol.py +81 -0
- regscale/models/regscale_models/privacy.py +89 -0
- regscale/models/regscale_models/profile.py +50 -0
- regscale/models/regscale_models/profile_link.py +68 -0
- regscale/models/regscale_models/profile_mapping.py +124 -0
- regscale/models/regscale_models/project.py +63 -0
- regscale/models/regscale_models/property.py +278 -0
- regscale/models/regscale_models/question.py +85 -0
- regscale/models/regscale_models/questionnaire.py +87 -0
- regscale/models/regscale_models/questionnaire_instance.py +177 -0
- regscale/models/regscale_models/rbac.py +132 -0
- regscale/models/regscale_models/reference.py +86 -0
- regscale/models/regscale_models/regscale_model.py +1643 -0
- regscale/models/regscale_models/requirement.py +29 -0
- regscale/models/regscale_models/risk.py +274 -0
- regscale/models/regscale_models/sbom.py +54 -0
- regscale/models/regscale_models/scan_history.py +436 -0
- regscale/models/regscale_models/search.py +53 -0
- regscale/models/regscale_models/security_control.py +132 -0
- regscale/models/regscale_models/security_plan.py +204 -0
- regscale/models/regscale_models/software_inventory.py +159 -0
- regscale/models/regscale_models/stake_holder.py +64 -0
- regscale/models/regscale_models/stig.py +647 -0
- regscale/models/regscale_models/supply_chain.py +152 -0
- regscale/models/regscale_models/system_role.py +188 -0
- regscale/models/regscale_models/system_role_external_assignment.py +40 -0
- regscale/models/regscale_models/tag.py +37 -0
- regscale/models/regscale_models/tag_mapping.py +19 -0
- regscale/models/regscale_models/task.py +133 -0
- regscale/models/regscale_models/threat.py +196 -0
- regscale/models/regscale_models/user.py +175 -0
- regscale/models/regscale_models/user_group.py +55 -0
- regscale/models/regscale_models/vulnerability.py +242 -0
- regscale/models/regscale_models/vulnerability_mapping.py +162 -0
- regscale/models/regscale_models/workflow.py +55 -0
- regscale/models/regscale_models/workflow_action.py +34 -0
- regscale/models/regscale_models/workflow_instance.py +269 -0
- regscale/models/regscale_models/workflow_instance_step.py +114 -0
- regscale/models/regscale_models/workflow_template.py +58 -0
- regscale/models/regscale_models/workflow_template_step.py +45 -0
- regscale/regscale.py +815 -0
- regscale/utils/__init__.py +7 -0
- regscale/utils/b64conversion.py +14 -0
- regscale/utils/click_utils.py +118 -0
- regscale/utils/decorators.py +48 -0
- regscale/utils/dict_utils.py +59 -0
- regscale/utils/files.py +79 -0
- regscale/utils/fxns.py +30 -0
- regscale/utils/graphql_client.py +113 -0
- regscale/utils/lists.py +16 -0
- regscale/utils/numbers.py +12 -0
- regscale/utils/shell.py +148 -0
- regscale/utils/string.py +121 -0
- regscale/utils/synqly_utils.py +165 -0
- regscale/utils/threading/__init__.py +8 -0
- regscale/utils/threading/threadhandler.py +131 -0
- regscale/utils/threading/threadsafe_counter.py +47 -0
- regscale/utils/threading/threadsafe_dict.py +242 -0
- regscale/utils/threading/threadsafe_list.py +83 -0
- regscale/utils/version.py +104 -0
- regscale/validation/__init__.py +0 -0
- regscale/validation/address.py +37 -0
- regscale/validation/record.py +48 -0
- regscale/visualization/__init__.py +5 -0
- regscale/visualization/click.py +34 -0
- regscale_cli-6.16.0.0.dist-info/LICENSE +21 -0
- regscale_cli-6.16.0.0.dist-info/METADATA +659 -0
- regscale_cli-6.16.0.0.dist-info/RECORD +481 -0
- regscale_cli-6.16.0.0.dist-info/WHEEL +5 -0
- regscale_cli-6.16.0.0.dist-info/entry_points.txt +6 -0
- regscale_cli-6.16.0.0.dist-info/top_level.txt +2 -0
- tests/fixtures/__init__.py +2 -0
- tests/fixtures/api.py +87 -0
- tests/fixtures/models.py +91 -0
- tests/fixtures/test_fixture.py +144 -0
- tests/mocks/__init__.py +0 -0
- tests/mocks/objects.py +3 -0
- tests/mocks/response.py +32 -0
- tests/mocks/xml.py +13 -0
- tests/regscale/__init__.py +0 -0
- tests/regscale/core/__init__.py +0 -0
- tests/regscale/core/test_api.py +232 -0
- tests/regscale/core/test_app.py +406 -0
- tests/regscale/core/test_login.py +37 -0
- tests/regscale/core/test_logz.py +66 -0
- tests/regscale/core/test_sbom_generator.py +87 -0
- tests/regscale/core/test_validation_utils.py +163 -0
- tests/regscale/core/test_version.py +78 -0
- tests/regscale/models/__init__.py +0 -0
- tests/regscale/models/test_asset.py +71 -0
- tests/regscale/models/test_config.py +26 -0
- tests/regscale/models/test_control_implementation.py +27 -0
- tests/regscale/models/test_import.py +97 -0
- tests/regscale/models/test_issue.py +36 -0
- tests/regscale/models/test_mapping.py +52 -0
- tests/regscale/models/test_platform.py +31 -0
- tests/regscale/models/test_regscale_model.py +346 -0
- tests/regscale/models/test_report.py +32 -0
- tests/regscale/models/test_tenable_integrations.py +118 -0
- tests/regscale/models/test_user_model.py +121 -0
- tests/regscale/test_about.py +19 -0
- tests/regscale/test_authorization.py +65 -0
|
@@ -0,0 +1,1546 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import random
|
|
6
|
+
import time
|
|
7
|
+
from functools import wraps
|
|
8
|
+
from typing import Optional, List, Union, Dict, Any, Type, TypeVar
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
|
|
11
|
+
import requests
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
from requests.exceptions import Timeout, ConnectionError as RequestsConnectionError
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("regscale")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def retry_with_backoff(retries=3, backoff_in_seconds=1):
|
|
19
|
+
"""
|
|
20
|
+
Decorator for retrying a function with exponential backoff.
|
|
21
|
+
|
|
22
|
+
:param int retries: Number of retries
|
|
23
|
+
:param int backoff_in_seconds: Initial backoff time in seconds
|
|
24
|
+
:return: Decorated function
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def decorator(func):
|
|
28
|
+
@wraps(func)
|
|
29
|
+
def wrapper(*args, **kwargs):
|
|
30
|
+
x = 0
|
|
31
|
+
while True:
|
|
32
|
+
try:
|
|
33
|
+
return func(*args, **kwargs)
|
|
34
|
+
except (Timeout, RequestsConnectionError) as e:
|
|
35
|
+
if x == retries:
|
|
36
|
+
raise e
|
|
37
|
+
sleep = backoff_in_seconds * 2**x + random.uniform(0, 1)
|
|
38
|
+
time.sleep(sleep)
|
|
39
|
+
x += 1
|
|
40
|
+
|
|
41
|
+
return wrapper
|
|
42
|
+
|
|
43
|
+
return decorator
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DuroSuiteModel(BaseModel):
|
|
47
|
+
"""Base model for DuroSuite API responses."""
|
|
48
|
+
|
|
49
|
+
class Config:
|
|
50
|
+
populate_by_name = True
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class AuditResponse(DuroSuiteModel):
|
|
54
|
+
"""Model for audit response."""
|
|
55
|
+
|
|
56
|
+
device_id: int
|
|
57
|
+
group_id: int
|
|
58
|
+
template_id: int
|
|
59
|
+
audit_id: int
|
|
60
|
+
job_id: str
|
|
61
|
+
status: Optional[str] = None
|
|
62
|
+
error_message: Optional[str] = None
|
|
63
|
+
|
|
64
|
+
class Config:
|
|
65
|
+
populate_by_name = True
|
|
66
|
+
extra = "allow"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Group(DuroSuiteModel):
|
|
70
|
+
"""Model for group information."""
|
|
71
|
+
|
|
72
|
+
id: Optional[int] = Field(None, alias="group_id")
|
|
73
|
+
name: Optional[str] = Field(None, alias="name")
|
|
74
|
+
os_id: Optional[int] = None
|
|
75
|
+
host_group_vars: Optional[List[Dict[str, Any]]] = Field(default_factory=list)
|
|
76
|
+
children: Optional[List[Dict[str, Any]]] = Field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
class Config:
|
|
79
|
+
populate_by_name = True
|
|
80
|
+
extra = "allow"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Var(DuroSuiteModel):
|
|
84
|
+
"""Model for variable information."""
|
|
85
|
+
|
|
86
|
+
id: int = Field(default=None, alias="var_id")
|
|
87
|
+
device_id: Optional[int] = None
|
|
88
|
+
name: str = Field(..., alias="var_name")
|
|
89
|
+
value: str = Field(..., alias="var_value")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class Device(DuroSuiteModel):
|
|
93
|
+
"""DuroSuite Device model."""
|
|
94
|
+
|
|
95
|
+
id: Optional[int] = Field(None, alias="device_id")
|
|
96
|
+
name: str = Field(..., alias="name")
|
|
97
|
+
os_id: int = Field(..., alias="os_id")
|
|
98
|
+
group_id: Optional[int] = Field(None, alias="group_id")
|
|
99
|
+
device_vars: Optional[List[Var]] = Field(default_factory=list, alias="device_vars")
|
|
100
|
+
groups: Optional[List[Group]] = Field(default_factory=list, alias="groups")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class STIG(DuroSuiteModel):
|
|
104
|
+
"""Model for STIG information."""
|
|
105
|
+
|
|
106
|
+
id: int
|
|
107
|
+
file_name: str
|
|
108
|
+
version: str
|
|
109
|
+
os_id: int
|
|
110
|
+
releaseinfo: str
|
|
111
|
+
playbook: str
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Template(DuroSuiteModel):
|
|
115
|
+
"""Template model."""
|
|
116
|
+
|
|
117
|
+
id: Optional[int] = Field(None)
|
|
118
|
+
name: str
|
|
119
|
+
os_id: int
|
|
120
|
+
playbook_id: Optional[int] = None
|
|
121
|
+
group_id: Optional[int] = None
|
|
122
|
+
template_vars: Optional[List[Dict[str, Any]]] = Field(default_factory=list)
|
|
123
|
+
|
|
124
|
+
class Config:
|
|
125
|
+
populate_by_name = True
|
|
126
|
+
extra = "allow"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
DuroSuiteModelType = TypeVar("DuroSuiteModelType", bound=DuroSuiteModel)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DuroSuite:
|
|
133
|
+
"""Methods to interact with DuroSuite API"""
|
|
134
|
+
|
|
135
|
+
# API Endpoints
|
|
136
|
+
TEMPLATE_VAR_ENDPOINT = "/api/stigs/templates/var"
|
|
137
|
+
TEMPLATES_ENDPOINT = "/api/stigs/templates"
|
|
138
|
+
DEVICE_VARS_ENDPOINT = "/api/devices/vars"
|
|
139
|
+
|
|
140
|
+
def __init__(self, base_url: str, username: Optional[str] = None, password: Optional[str] = None):
|
|
141
|
+
"""
|
|
142
|
+
Initialize DuroSuite API client.
|
|
143
|
+
|
|
144
|
+
:param str base_url: Base URL for the API
|
|
145
|
+
:param Optional[str] username: Username for authentication
|
|
146
|
+
:param Optional[str] password: Password for authentication
|
|
147
|
+
"""
|
|
148
|
+
self.base_url = base_url
|
|
149
|
+
self.api_key = None
|
|
150
|
+
if username and password:
|
|
151
|
+
self.username = username
|
|
152
|
+
self.password = password
|
|
153
|
+
self.login(username, password)
|
|
154
|
+
|
|
155
|
+
def login(self, username: str, password: str) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Log in to the DuroSuite API.
|
|
158
|
+
|
|
159
|
+
:param str username: Username for authentication
|
|
160
|
+
:param str password: Password for authentication
|
|
161
|
+
:raises ValueError: If login fails
|
|
162
|
+
"""
|
|
163
|
+
self.username = username
|
|
164
|
+
self.password = password
|
|
165
|
+
data = {"username": username, "password": password}
|
|
166
|
+
response_data = self._make_request("POST", "/api/login", data=data)
|
|
167
|
+
if response_data:
|
|
168
|
+
self.api_key = response_data.get("access_token")
|
|
169
|
+
if not self.api_key:
|
|
170
|
+
raise ValueError("Login failed: No access token received")
|
|
171
|
+
|
|
172
|
+
def _handle_403_error(self) -> bool:
|
|
173
|
+
"""
|
|
174
|
+
Handle 403 error by attempting to refresh token or log in again.
|
|
175
|
+
|
|
176
|
+
:return: True if handled successfully, False otherwise
|
|
177
|
+
:rtype: bool
|
|
178
|
+
"""
|
|
179
|
+
if hasattr(self, "username") and hasattr(self, "password"):
|
|
180
|
+
self.login(self.username, self.password)
|
|
181
|
+
return True
|
|
182
|
+
return False
|
|
183
|
+
|
|
184
|
+
@retry_with_backoff(retries=3, backoff_in_seconds=1)
|
|
185
|
+
def _make_request(
|
|
186
|
+
self,
|
|
187
|
+
method: str,
|
|
188
|
+
endpoint: str,
|
|
189
|
+
data: Optional[Dict[str, Any]] = None,
|
|
190
|
+
params: Optional[Dict[str, Any]] = None,
|
|
191
|
+
files: Optional[Dict[str, Any]] = None,
|
|
192
|
+
) -> Optional[Union[dict, str]]:
|
|
193
|
+
"""
|
|
194
|
+
Make a request to the DuroSuite API.
|
|
195
|
+
|
|
196
|
+
:param str method: HTTP method
|
|
197
|
+
:param str endpoint: API endpoint
|
|
198
|
+
:param Optional[Dict[str, Any]] data: Request data
|
|
199
|
+
:param Optional[Dict[str, Any]] params: Query parameters
|
|
200
|
+
:param Optional[Dict[str, Any]] files: Files to upload
|
|
201
|
+
:return: Response data or None if request failed
|
|
202
|
+
:rtype: Optional[Union[dict, str]]
|
|
203
|
+
"""
|
|
204
|
+
url = urljoin(self.base_url, endpoint)
|
|
205
|
+
headers = {}
|
|
206
|
+
if self.api_key:
|
|
207
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
208
|
+
else:
|
|
209
|
+
logger.warning("No API key provided, proceeding without authentication")
|
|
210
|
+
|
|
211
|
+
if data:
|
|
212
|
+
headers["Content-Type"] = "application/json"
|
|
213
|
+
|
|
214
|
+
try:
|
|
215
|
+
response = requests.request(
|
|
216
|
+
method, url, headers=headers, json=data, params=params, verify=True, timeout=60, files=files
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if response.status_code == 403:
|
|
220
|
+
if self._handle_403_error():
|
|
221
|
+
# Retry the request with the new token
|
|
222
|
+
return self._make_request(method, endpoint, data, params, files)
|
|
223
|
+
else:
|
|
224
|
+
logger.error("Authentication failed and no credentials provided for retry")
|
|
225
|
+
raise requests.exceptions.HTTPError("Authentication failed after retry", response=response)
|
|
226
|
+
|
|
227
|
+
# Special handling for 404 with JSON response
|
|
228
|
+
if response.status_code == 404:
|
|
229
|
+
logger.error(f"Resource not found: {url}")
|
|
230
|
+
try:
|
|
231
|
+
return response.json()
|
|
232
|
+
except: # noqa: E722
|
|
233
|
+
response.raise_for_status()
|
|
234
|
+
else:
|
|
235
|
+
response.raise_for_status()
|
|
236
|
+
|
|
237
|
+
# Try to parse as JSON, if it fails, return the text content
|
|
238
|
+
try:
|
|
239
|
+
r = response.json()
|
|
240
|
+
return r
|
|
241
|
+
except requests.exceptions.JSONDecodeError:
|
|
242
|
+
return response.text
|
|
243
|
+
|
|
244
|
+
except Exception as e:
|
|
245
|
+
logger.error(f"Method: {method}, Endpoint: {endpoint}, Data: {data}, Params: {params}, Files: {files}")
|
|
246
|
+
logger.error(f"Request failed: {e}", exc_info=True)
|
|
247
|
+
raise
|
|
248
|
+
|
|
249
|
+
@staticmethod
|
|
250
|
+
def handle_response(
|
|
251
|
+
response: Optional[Dict[str, Any]], model: Type[DuroSuiteModelType]
|
|
252
|
+
) -> Optional[DuroSuiteModelType]:
|
|
253
|
+
"""
|
|
254
|
+
Handle API response and convert to appropriate DuroSuite model.
|
|
255
|
+
|
|
256
|
+
:param Optional[Dict[str, Any]] response: API response
|
|
257
|
+
:param Type[DuroSuiteModelType] model: DuroSuite model to validate response against
|
|
258
|
+
:return: Validated DuroSuite model instance or None if validation fails
|
|
259
|
+
:rtype: Optional[DuroSuiteModelType]
|
|
260
|
+
"""
|
|
261
|
+
if response is None:
|
|
262
|
+
return None
|
|
263
|
+
logger.debug(f"Handling Response: {response}")
|
|
264
|
+
try:
|
|
265
|
+
return model.model_validate(response)
|
|
266
|
+
except ValueError as e:
|
|
267
|
+
logging.error(f"Error validating response: {e}", exc_info=True)
|
|
268
|
+
return None
|
|
269
|
+
|
|
270
|
+
def _handle_list_response(
|
|
271
|
+
self, response: Optional[List[Dict[str, Any]]], model: Type[DuroSuiteModelType]
|
|
272
|
+
) -> List[DuroSuiteModelType]:
|
|
273
|
+
"""
|
|
274
|
+
Handle API list response and convert to a list of appropriate DuroSuite models.
|
|
275
|
+
|
|
276
|
+
:param Optional[List[Dict[str, Any]]] response: API response
|
|
277
|
+
:param Type[DuroSuiteModelType] model: DuroSuite model to validate response against
|
|
278
|
+
:return: List of validated DuroSuite model instances
|
|
279
|
+
:rtype: List[DuroSuiteModelType]
|
|
280
|
+
"""
|
|
281
|
+
if response is None:
|
|
282
|
+
return []
|
|
283
|
+
return [
|
|
284
|
+
item
|
|
285
|
+
for item in (self.handle_response(item, model) for item in response if item is not None)
|
|
286
|
+
if item is not None
|
|
287
|
+
]
|
|
288
|
+
|
|
289
|
+
def revoke_access_token(self) -> Optional[Dict[str, Any]]:
|
|
290
|
+
"""
|
|
291
|
+
Revoke Access Token.
|
|
292
|
+
|
|
293
|
+
:return: API response
|
|
294
|
+
:rtype: Optional[Dict[str, Any]]
|
|
295
|
+
"""
|
|
296
|
+
return self._make_request("DELETE", "/api/access-revoke")
|
|
297
|
+
|
|
298
|
+
def revoke_refresh_token(self) -> Optional[Dict[str, Any]]:
|
|
299
|
+
"""
|
|
300
|
+
Revoke Refresh Token.
|
|
301
|
+
|
|
302
|
+
:return: API response
|
|
303
|
+
:rtype: Optional[Dict[str, Any]]
|
|
304
|
+
"""
|
|
305
|
+
return self._make_request("DELETE", "/api/refresh-revoke")
|
|
306
|
+
|
|
307
|
+
def refresh_token(self) -> Optional[Dict[str, Any]]:
|
|
308
|
+
"""
|
|
309
|
+
Refresh Token.
|
|
310
|
+
|
|
311
|
+
:return: API response
|
|
312
|
+
:rtype: Optional[Dict[str, Any]]
|
|
313
|
+
"""
|
|
314
|
+
return self._make_request("POST", "/api/refresh-token")
|
|
315
|
+
|
|
316
|
+
def change_current_user_password(self, old_password: str, new_password: str) -> Optional[Dict[str, Any]]:
|
|
317
|
+
"""
|
|
318
|
+
Change Current User Password.
|
|
319
|
+
|
|
320
|
+
:param str old_password: Current password
|
|
321
|
+
:param str new_password: New password
|
|
322
|
+
:return: API response
|
|
323
|
+
:rtype: Optional[Dict[str, Any]]
|
|
324
|
+
"""
|
|
325
|
+
data = {"old_password": old_password, "new_password": new_password}
|
|
326
|
+
return self._make_request("POST", "/api/users/change_password", data=json.dumps(data))
|
|
327
|
+
|
|
328
|
+
def reset_user_password(self, admin_password: str, user_id: int, new_password: str) -> Optional[Dict[str, Any]]:
|
|
329
|
+
"""
|
|
330
|
+
Reset User Password.
|
|
331
|
+
|
|
332
|
+
:param str admin_password: Admin password
|
|
333
|
+
:param int user_id: ID of the user whose password is being reset
|
|
334
|
+
:param str new_password: New password
|
|
335
|
+
:return: API response
|
|
336
|
+
:rtype: Optional[Dict[str, Any]]
|
|
337
|
+
"""
|
|
338
|
+
data = {"admin_password": admin_password, "user_id": user_id, "new_password": new_password}
|
|
339
|
+
return self._make_request("POST", "/api/users/reset_password", data=json.dumps(data))
|
|
340
|
+
|
|
341
|
+
# 2. Audit Records
|
|
342
|
+
def get_all_audits(self, skip: int = 0, limit: int = 10, **params) -> Optional[Dict[str, Any]]:
|
|
343
|
+
"""
|
|
344
|
+
Get All Audits.
|
|
345
|
+
|
|
346
|
+
:param int skip: Number of records to skip
|
|
347
|
+
:param int limit: Number of records to return
|
|
348
|
+
:param params: Additional query parameters
|
|
349
|
+
:return: List of audits
|
|
350
|
+
:rtype: Optional[Dict[str, Any]]
|
|
351
|
+
"""
|
|
352
|
+
params.update({"skip": skip, "limit": limit})
|
|
353
|
+
return self._make_request("GET", "/api/audits", params=params)
|
|
354
|
+
|
|
355
|
+
def get_audit_record(self, audit_id: int) -> Optional[Dict[str, Any]]:
|
|
356
|
+
"""
|
|
357
|
+
Get audit record.
|
|
358
|
+
|
|
359
|
+
:param int audit_id: ID of the audit
|
|
360
|
+
:return: Audit record
|
|
361
|
+
:rtype: Optional[Dict[str, Any]]
|
|
362
|
+
"""
|
|
363
|
+
return self._make_request("GET", f"/api/audits/{audit_id}")
|
|
364
|
+
|
|
365
|
+
def delete_audit(self, audit_id: int) -> Optional[Dict[str, Any]]:
|
|
366
|
+
"""
|
|
367
|
+
Delete Audit.
|
|
368
|
+
|
|
369
|
+
:param int audit_id: ID of the audit to delete
|
|
370
|
+
:return: API response
|
|
371
|
+
:rtype: Optional[Dict[str, Any]]
|
|
372
|
+
"""
|
|
373
|
+
return self._make_request("DELETE", f"/api/audits/{audit_id}")
|
|
374
|
+
|
|
375
|
+
def get_checklist_file_by_audit_id(self, audit_id: int) -> Optional[str]:
|
|
376
|
+
"""
|
|
377
|
+
Get checklist file by audit ID.
|
|
378
|
+
|
|
379
|
+
:param int audit_id: The ID of the audit
|
|
380
|
+
:return: Checklist file content or None if not found
|
|
381
|
+
:rtype: Optional[str]
|
|
382
|
+
"""
|
|
383
|
+
return self._make_request("GET", f"/api/audits/checklist/{audit_id}")
|
|
384
|
+
|
|
385
|
+
def combine_checklist_files(self, audit_ids: List[int]) -> Optional[Dict[str, Any]]:
|
|
386
|
+
"""
|
|
387
|
+
Combine Checklist Files.
|
|
388
|
+
|
|
389
|
+
:param List[int] audit_ids: List of audit IDs to combine
|
|
390
|
+
:return: Combined checklist file
|
|
391
|
+
:rtype: Optional[Dict[str, Any]]
|
|
392
|
+
"""
|
|
393
|
+
return self._make_request("GET", "/api/combine-ckl", params={"audit_ids": audit_ids})
|
|
394
|
+
|
|
395
|
+
def get_vulnerabilities_for_audit(self, audit_id: int) -> Optional[Dict[str, Any]]:
|
|
396
|
+
"""
|
|
397
|
+
Get Vulnerabilities For Audit.
|
|
398
|
+
|
|
399
|
+
:param int audit_id: ID of the audit
|
|
400
|
+
:return: List of vulnerabilities
|
|
401
|
+
:rtype: Optional[Dict[str, Any]]
|
|
402
|
+
"""
|
|
403
|
+
return self._make_request("GET", "/api/vulnerabilities", params={"audit_id": audit_id})
|
|
404
|
+
|
|
405
|
+
def get_single_vulnerability(self, vuln_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
406
|
+
"""
|
|
407
|
+
Get Single Vulnerability.
|
|
408
|
+
|
|
409
|
+
:param Dict[str, Any] vuln_data: Vulnerability data
|
|
410
|
+
:return: Vulnerability details
|
|
411
|
+
:rtype: Optional[Dict[str, Any]]
|
|
412
|
+
"""
|
|
413
|
+
return self._make_request("POST", "/api/single-vuln", data=json.dumps(vuln_data))
|
|
414
|
+
|
|
415
|
+
# 3. Remediate Records
|
|
416
|
+
def get_all_remediations(self, skip: int = 0, limit: int = 10, **params) -> Optional[Dict[str, Any]]:
|
|
417
|
+
"""
|
|
418
|
+
Get All Remediations.
|
|
419
|
+
|
|
420
|
+
:param int skip: Number of records to skip
|
|
421
|
+
:param int limit: Number of records to return
|
|
422
|
+
:param params: Additional query parameters
|
|
423
|
+
:return: List of remediations
|
|
424
|
+
:rtype: Optional[Dict[str, Any]]
|
|
425
|
+
"""
|
|
426
|
+
params.update({"skip": skip, "limit": limit})
|
|
427
|
+
return self._make_request("GET", "/api/remediations", params=params)
|
|
428
|
+
|
|
429
|
+
def get_remediation_record(self, remediation_id: int) -> Optional[Dict[str, Any]]:
|
|
430
|
+
"""
|
|
431
|
+
Get Remediation Record.
|
|
432
|
+
|
|
433
|
+
:param int remediation_id: ID of the remediation
|
|
434
|
+
:return: Remediation record
|
|
435
|
+
:rtype: Optional[Dict[str, Any]]
|
|
436
|
+
"""
|
|
437
|
+
return self._make_request("GET", f"/api/remediations/{remediation_id}")
|
|
438
|
+
|
|
439
|
+
def delete_remediation(self, remediation_id: int) -> Optional[Dict[str, Any]]:
|
|
440
|
+
"""
|
|
441
|
+
Delete Remediation.
|
|
442
|
+
|
|
443
|
+
:param int remediation_id: ID of the remediation to delete
|
|
444
|
+
:return: API response
|
|
445
|
+
:rtype: Optional[Dict[str, Any]]
|
|
446
|
+
"""
|
|
447
|
+
return self._make_request("DELETE", f"/api/remediations/{remediation_id}")
|
|
448
|
+
|
|
449
|
+
# 4. Devices
|
|
450
|
+
def get_devices(self) -> List[Device]:
|
|
451
|
+
"""
|
|
452
|
+
Get all devices.
|
|
453
|
+
|
|
454
|
+
:return: List of all devices
|
|
455
|
+
:rtype: List[Device]
|
|
456
|
+
"""
|
|
457
|
+
try:
|
|
458
|
+
response = self._make_request("GET", "/api/devices")
|
|
459
|
+
# Handle 404 "No devices available" response
|
|
460
|
+
if not response or (isinstance(response, dict) and "detail" in response):
|
|
461
|
+
logger.debug(f"No devices found: {response}")
|
|
462
|
+
return []
|
|
463
|
+
return self._handle_list_response(response, Device)
|
|
464
|
+
except Exception as e:
|
|
465
|
+
logger.error(f"Failed to get devices: {e}", exc_info=True)
|
|
466
|
+
return []
|
|
467
|
+
|
|
468
|
+
def update_device(self, device_data: Device) -> Optional[Dict[str, Any]]:
|
|
469
|
+
"""
|
|
470
|
+
Update Device.
|
|
471
|
+
|
|
472
|
+
:param Device device_data: Updated device data
|
|
473
|
+
:return: API response
|
|
474
|
+
:rtype: Optional[Dict[str, Any]]
|
|
475
|
+
"""
|
|
476
|
+
return self._make_request("PUT", "/api/devices", params=device_data.model_dump())
|
|
477
|
+
|
|
478
|
+
def add_new_device(self, device_data: Dict[str, Any]) -> Optional[Device]:
|
|
479
|
+
"""
|
|
480
|
+
Add a new device.
|
|
481
|
+
|
|
482
|
+
:param Dict[str, Any] device_data: Device data to add
|
|
483
|
+
:return: Added device
|
|
484
|
+
:rtype: Optional[Device]
|
|
485
|
+
"""
|
|
486
|
+
try:
|
|
487
|
+
# Format device data according to API spec
|
|
488
|
+
request_data = {
|
|
489
|
+
"device_name": device_data["name"], # API expects device_name
|
|
490
|
+
"os_id": int(device_data["os_id"]),
|
|
491
|
+
"group_id": int(device_data["group_id"]),
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
logger.debug(f"Creating device with data: {request_data}")
|
|
495
|
+
|
|
496
|
+
# Create device first
|
|
497
|
+
response = self._make_request("POST", "/api/devices", data=request_data)
|
|
498
|
+
|
|
499
|
+
if not response:
|
|
500
|
+
logger.error("Failed to create device - empty response")
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
logger.debug(f"Device creation response: {response}")
|
|
504
|
+
|
|
505
|
+
device_id = response.get("device_id")
|
|
506
|
+
if not device_id:
|
|
507
|
+
logger.error("Failed to get device ID from response")
|
|
508
|
+
return None
|
|
509
|
+
|
|
510
|
+
# Add device variables if provided
|
|
511
|
+
if "device_vars" in device_data:
|
|
512
|
+
for var in device_data["device_vars"]:
|
|
513
|
+
var_data = {"device_id": device_id, "var_name": var["var_name"], "var_value": var["var_value"]}
|
|
514
|
+
|
|
515
|
+
logger.debug(f"Adding device variable: {var_data}")
|
|
516
|
+
self._make_request("POST", "/api/devices/vars", data=var_data)
|
|
517
|
+
|
|
518
|
+
# Get all devices and find our newly created one
|
|
519
|
+
all_devices = self._make_request("GET", "/api/devices")
|
|
520
|
+
if not all_devices:
|
|
521
|
+
logger.error("Failed to get devices list")
|
|
522
|
+
return None
|
|
523
|
+
|
|
524
|
+
# Find our device in the list
|
|
525
|
+
device_response = next((d for d in all_devices if d.get("device_id") == device_id), None)
|
|
526
|
+
|
|
527
|
+
if not device_response:
|
|
528
|
+
logger.error(f"Could not find device {device_id} in devices list")
|
|
529
|
+
# Return a basic device object with what we know
|
|
530
|
+
device_response = {
|
|
531
|
+
"device_id": device_id,
|
|
532
|
+
"name": device_data["name"],
|
|
533
|
+
"os_id": device_data["os_id"],
|
|
534
|
+
"group_id": device_data["group_id"],
|
|
535
|
+
"device_vars": device_data.get("device_vars", []),
|
|
536
|
+
"groups": [{"id": device_data["group_id"]}],
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
return Device(**device_response)
|
|
540
|
+
except Exception as e:
|
|
541
|
+
logger.error(f"Failed to create device: {e}", exc_info=True)
|
|
542
|
+
return None
|
|
543
|
+
|
|
544
|
+
def get_csv_for_batch_upload(self, os_id: int) -> Optional[str]:
|
|
545
|
+
"""
|
|
546
|
+
Get CSV For Batch Upload.
|
|
547
|
+
|
|
548
|
+
:param int os_id: Operating system ID
|
|
549
|
+
:return: CSV content
|
|
550
|
+
:rtype: Optional[str]
|
|
551
|
+
"""
|
|
552
|
+
return self._make_request("GET", "/api/devices/batch-upload", params={"os_id": os_id})
|
|
553
|
+
|
|
554
|
+
def batch_upload_devices(self, os_id: int, csv_file: Any) -> Optional[Dict[str, Any]]:
|
|
555
|
+
"""
|
|
556
|
+
Batch Upload Devices.
|
|
557
|
+
|
|
558
|
+
:param int os_id: Operating system ID
|
|
559
|
+
:param Any csv_file: CSV file containing device information
|
|
560
|
+
:return: API response
|
|
561
|
+
:rtype: Optional[Dict[str, Any]]
|
|
562
|
+
"""
|
|
563
|
+
files = {"devices_file": csv_file}
|
|
564
|
+
return self._make_request("POST", f"/api/devices/batch-upload?os_id={os_id}", files=files)
|
|
565
|
+
|
|
566
|
+
def get_devices_by_group_id(self, group_id: int) -> Optional[List[Device]]:
|
|
567
|
+
"""
|
|
568
|
+
Get Devices By Group ID.
|
|
569
|
+
|
|
570
|
+
:param int group_id: Group ID
|
|
571
|
+
:return: List of devices in the group
|
|
572
|
+
:rtype: Optional[List[Device]]
|
|
573
|
+
"""
|
|
574
|
+
return self._make_request("GET", f"/api/devices/{group_id}")
|
|
575
|
+
|
|
576
|
+
def delete_device(self, device_id: int) -> Optional[Dict[str, Any]]:
|
|
577
|
+
"""
|
|
578
|
+
Delete Device.
|
|
579
|
+
|
|
580
|
+
:param int device_id: Device ID to delete
|
|
581
|
+
:return: API response
|
|
582
|
+
:rtype: Optional[Dict[str, Any]]
|
|
583
|
+
"""
|
|
584
|
+
return self._make_request("DELETE", f"/api/devices/{device_id}")
|
|
585
|
+
|
|
586
|
+
def update_device_variable(self, var_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
587
|
+
"""
|
|
588
|
+
Update Device Variable.
|
|
589
|
+
|
|
590
|
+
:param Dict[str, Any] var_data: Variable data to update
|
|
591
|
+
:return: API response
|
|
592
|
+
:rtype: Optional[Dict[str, Any]]
|
|
593
|
+
"""
|
|
594
|
+
return self._make_request("PUT", self.DEVICE_VARS_ENDPOINT, params=var_data)
|
|
595
|
+
|
|
596
|
+
def add_new_device_variable(self, var: Var) -> Optional[Var]:
|
|
597
|
+
"""
|
|
598
|
+
Add New Device Variable.
|
|
599
|
+
|
|
600
|
+
:param Var var: New variable data
|
|
601
|
+
:return: Added variable
|
|
602
|
+
:rtype: Optional[Var]
|
|
603
|
+
"""
|
|
604
|
+
data = var.model_dump(by_alias=True, exclude_none=True)
|
|
605
|
+
response = self._make_request("POST", self.DEVICE_VARS_ENDPOINT, data=json.dumps(data))
|
|
606
|
+
return self.handle_response(response, Var)
|
|
607
|
+
|
|
608
|
+
def delete_device_variable(self, var_id: int) -> Optional[Dict[str, Any]]:
|
|
609
|
+
"""
|
|
610
|
+
Delete Device Variable.
|
|
611
|
+
|
|
612
|
+
:param int var_id: Variable ID to delete
|
|
613
|
+
:return: API response
|
|
614
|
+
:rtype: Optional[Dict[str, Any]]
|
|
615
|
+
"""
|
|
616
|
+
return self._make_request("DELETE", f"{self.DEVICE_VARS_ENDPOINT}/{var_id}")
|
|
617
|
+
|
|
618
|
+
def test_connection(self, device_id: int, group_id: int) -> Optional[Dict[str, Any]]:
|
|
619
|
+
"""
|
|
620
|
+
Test Connection.
|
|
621
|
+
|
|
622
|
+
:param int device_id: Device ID
|
|
623
|
+
:param int group_id: Group ID
|
|
624
|
+
:return: Connection test results
|
|
625
|
+
:rtype: Optional[Dict[str, Any]]
|
|
626
|
+
"""
|
|
627
|
+
params = {"device_id": device_id, "group_id": group_id}
|
|
628
|
+
return self._make_request("GET", "/api/connection-test", params=params)
|
|
629
|
+
|
|
630
|
+
# 5. Groups
|
|
631
|
+
def get_groups(self) -> List[Group]:
|
|
632
|
+
"""
|
|
633
|
+
Get all groups.
|
|
634
|
+
|
|
635
|
+
:return: List of all groups
|
|
636
|
+
:rtype: List[Group]
|
|
637
|
+
"""
|
|
638
|
+
try:
|
|
639
|
+
response = self._make_request("GET", "/api/groups")
|
|
640
|
+
if not response:
|
|
641
|
+
return []
|
|
642
|
+
|
|
643
|
+
# Log response for debugging
|
|
644
|
+
logger.debug(f"Get groups response: {response}")
|
|
645
|
+
|
|
646
|
+
# Handle both single group and list responses
|
|
647
|
+
if isinstance(response, dict):
|
|
648
|
+
response = [response]
|
|
649
|
+
|
|
650
|
+
groups = []
|
|
651
|
+
for group_data in response:
|
|
652
|
+
try:
|
|
653
|
+
# Ensure group_id is properly set
|
|
654
|
+
if "group_id" not in group_data and "id" in group_data:
|
|
655
|
+
group_data["group_id"] = group_data["id"]
|
|
656
|
+
groups.append(Group(**group_data))
|
|
657
|
+
except Exception as e:
|
|
658
|
+
logger.error(f"Failed to parse group data: {e}", exc_info=True)
|
|
659
|
+
|
|
660
|
+
return groups
|
|
661
|
+
except Exception as e:
|
|
662
|
+
logger.error(f"Failed to get groups: {e}", exc_info=True)
|
|
663
|
+
return []
|
|
664
|
+
|
|
665
|
+
def update_group(self, group_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
666
|
+
"""
|
|
667
|
+
Update Group.
|
|
668
|
+
|
|
669
|
+
:param Dict[str, Any] group_data: Updated group data
|
|
670
|
+
:return: API response
|
|
671
|
+
:rtype: Optional[Dict[str, Any]]
|
|
672
|
+
"""
|
|
673
|
+
return self._make_request("PUT", "/api/groups", params=group_data)
|
|
674
|
+
|
|
675
|
+
def add_new_group(self, group_data: Dict[str, Any]) -> Optional[Group]:
|
|
676
|
+
"""
|
|
677
|
+
Add a new group.
|
|
678
|
+
|
|
679
|
+
:param Dict[str, Any] group_data: Group data to add
|
|
680
|
+
:return: Added group
|
|
681
|
+
:rtype: Optional[Group]
|
|
682
|
+
"""
|
|
683
|
+
try:
|
|
684
|
+
# Format group data according to API spec
|
|
685
|
+
request_data = {
|
|
686
|
+
"group_name": group_data["group_name"], # API expects group_name
|
|
687
|
+
"os_id": int(group_data["os_id"]),
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
logger.debug(f"Creating group with data: {request_data}")
|
|
691
|
+
|
|
692
|
+
response = self._make_request("POST", "/api/groups", data=request_data)
|
|
693
|
+
|
|
694
|
+
if not response:
|
|
695
|
+
logger.error("Failed to create group - empty response")
|
|
696
|
+
return None
|
|
697
|
+
|
|
698
|
+
logger.debug(f"Group creation response: {response}")
|
|
699
|
+
|
|
700
|
+
# Map response fields to Group model fields
|
|
701
|
+
group_data = {
|
|
702
|
+
"group_id": response.get("group_id"),
|
|
703
|
+
"name": response.get("group_name"),
|
|
704
|
+
"os_id": response.get("group_os_id"), # Changed from os_id to group_os_id
|
|
705
|
+
"host_group_vars": [],
|
|
706
|
+
"children": [],
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
# Create the group first
|
|
710
|
+
group = Group(**group_data)
|
|
711
|
+
if not group.id:
|
|
712
|
+
logger.error("Failed to parse group response - missing id")
|
|
713
|
+
return None
|
|
714
|
+
|
|
715
|
+
# Step 2: Create template for the group
|
|
716
|
+
template_data = {"name": "Baseline", "os_id": int(group_data["os_id"]), "group_id": group.id}
|
|
717
|
+
|
|
718
|
+
template_response = self._make_request("POST", "/api/stigs/templates", data=template_data)
|
|
719
|
+
|
|
720
|
+
if not template_response:
|
|
721
|
+
logger.error("Failed to create template")
|
|
722
|
+
return None
|
|
723
|
+
|
|
724
|
+
template_id = template_response.get("id")
|
|
725
|
+
|
|
726
|
+
# Step 3: Add template variables
|
|
727
|
+
if "variables" in group_data and template_id:
|
|
728
|
+
for var_name, var_value in group_data["variables"].items():
|
|
729
|
+
var_data = {"template_id": template_id, "var_name": var_name, "var_value": str(var_value)}
|
|
730
|
+
|
|
731
|
+
logger.debug(f"Adding template variable: {var_data}")
|
|
732
|
+
self._make_request("POST", "/api/stigs/templates/var", data=var_data)
|
|
733
|
+
|
|
734
|
+
return group
|
|
735
|
+
except Exception as e:
|
|
736
|
+
logger.error(f"Failed to create group: {e}", exc_info=True)
|
|
737
|
+
return None
|
|
738
|
+
|
|
739
|
+
def get_groups_by_device_id(self, device_id: int) -> Optional[List[Group]]:
|
|
740
|
+
"""
|
|
741
|
+
Get Groups By Device ID.
|
|
742
|
+
|
|
743
|
+
:param int device_id: Device ID
|
|
744
|
+
:return: List of groups associated with the device
|
|
745
|
+
:rtype: Optional[List[Group]]
|
|
746
|
+
"""
|
|
747
|
+
return self._make_request("GET", f"/api/groups/{device_id}")
|
|
748
|
+
|
|
749
|
+
def delete_group(self, group_id: int) -> Optional[Dict[str, Any]]:
|
|
750
|
+
"""
|
|
751
|
+
Delete Group.
|
|
752
|
+
|
|
753
|
+
:param int group_id: Group ID to delete
|
|
754
|
+
:return: API response
|
|
755
|
+
:rtype: Optional[Dict[str, Any]]
|
|
756
|
+
"""
|
|
757
|
+
return self._make_request("DELETE", f"/api/groups/{group_id}")
|
|
758
|
+
|
|
759
|
+
def add_device_to_group(self, group_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
760
|
+
"""
|
|
761
|
+
Add Device To Group.
|
|
762
|
+
|
|
763
|
+
:param Dict[str, Any] group_data: Data for adding device to group
|
|
764
|
+
:return: API response
|
|
765
|
+
:rtype: Optional[Dict[str, Any]]
|
|
766
|
+
"""
|
|
767
|
+
return self._make_request("POST", "/api/groups/device", data=json.dumps(group_data))
|
|
768
|
+
|
|
769
|
+
def remove_device_from_group(self, group_id: int, device_id: int) -> Optional[Dict[str, Any]]:
|
|
770
|
+
"""
|
|
771
|
+
Remove Device From Group.
|
|
772
|
+
|
|
773
|
+
:param int group_id: Group ID
|
|
774
|
+
:param int device_id: Device ID to remove
|
|
775
|
+
:return: API response
|
|
776
|
+
:rtype: Optional[Dict[str, Any]]
|
|
777
|
+
"""
|
|
778
|
+
return self._make_request("DELETE", f"/api/groups/device/{group_id}/{device_id}")
|
|
779
|
+
|
|
780
|
+
def add_child_to_group(self, group_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
781
|
+
"""
|
|
782
|
+
Add Child To Group.
|
|
783
|
+
|
|
784
|
+
:param Dict[str, Any] group_data: Data for adding child to group
|
|
785
|
+
:return: API response
|
|
786
|
+
:rtype: Optional[Dict[str, Any]]
|
|
787
|
+
"""
|
|
788
|
+
return self._make_request("POST", "/api/groups/child", data=json.dumps(group_data))
|
|
789
|
+
|
|
790
|
+
def remove_child_from_group(self, parent_id: int, child_id: int) -> Optional[Dict[str, Any]]:
|
|
791
|
+
"""
|
|
792
|
+
Remove Child From Group.
|
|
793
|
+
|
|
794
|
+
:param int parent_id: Parent group ID
|
|
795
|
+
:param int child_id: Child group ID to remove
|
|
796
|
+
:return: API response
|
|
797
|
+
:rtype: Optional[Dict[str, Any]]
|
|
798
|
+
"""
|
|
799
|
+
return self._make_request("DELETE", f"/api/groups/child/{parent_id}/{child_id}")
|
|
800
|
+
|
|
801
|
+
def get_roles_by_group_id(self, group_id: int) -> Optional[List[Dict[str, Any]]]:
|
|
802
|
+
"""
|
|
803
|
+
Get Roles By Group ID.
|
|
804
|
+
|
|
805
|
+
:param int group_id: Group ID
|
|
806
|
+
:return: List of roles associated with the group
|
|
807
|
+
:rtype: Optional[List[Dict[str, Any]]
|
|
808
|
+
"""
|
|
809
|
+
return self._make_request("GET", f"/api/groups/roles/{group_id}")
|
|
810
|
+
|
|
811
|
+
def add_user_to_group(self, group_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
812
|
+
"""
|
|
813
|
+
Add User To Group.
|
|
814
|
+
|
|
815
|
+
:param Dict[str, Any] group_data: Data for adding user to group
|
|
816
|
+
:return: API response
|
|
817
|
+
:rtype: Optional[Dict[str, Any]]
|
|
818
|
+
"""
|
|
819
|
+
return self._make_request("POST", "/api/groups/user", data=json.dumps(group_data))
|
|
820
|
+
|
|
821
|
+
def remove_user_from_group(self, user_id: int, perm_id: int) -> Optional[Dict[str, Any]]:
|
|
822
|
+
"""
|
|
823
|
+
Remove User From Group.
|
|
824
|
+
|
|
825
|
+
:param int user_id: User ID to remove
|
|
826
|
+
:param int perm_id: Permission ID
|
|
827
|
+
:return: API response
|
|
828
|
+
:rtype: Optional[Dict[str, Any]]
|
|
829
|
+
"""
|
|
830
|
+
return self._make_request("DELETE", f"/api/groups/user/{user_id}/{perm_id}")
|
|
831
|
+
|
|
832
|
+
def get_current_users_group_perms(self, group_id: int) -> Optional[Dict[str, Any]]:
|
|
833
|
+
"""
|
|
834
|
+
Get Current Users Group Permissions.
|
|
835
|
+
|
|
836
|
+
:param int group_id: The ID of the group
|
|
837
|
+
:return: Group permissions for the current user
|
|
838
|
+
:rtype: Optional[Dict[str, Any]]
|
|
839
|
+
"""
|
|
840
|
+
return self._make_request("GET", f"/api/groups/perms/{group_id}")
|
|
841
|
+
|
|
842
|
+
# 6. Settings
|
|
843
|
+
def get_ansible_cfg_settings(self) -> Optional[Dict[str, Any]]:
|
|
844
|
+
"""
|
|
845
|
+
Get Ansible Configuration Settings.
|
|
846
|
+
|
|
847
|
+
:return: Ansible configuration settings
|
|
848
|
+
:rtype: Optional[Dict[str, Any]]
|
|
849
|
+
"""
|
|
850
|
+
return self._make_request("GET", "/api/ansible-cfg")
|
|
851
|
+
|
|
852
|
+
def update_ansible_cfg(self, ansible_cfg_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
853
|
+
"""
|
|
854
|
+
Update Ansible Configuration.
|
|
855
|
+
|
|
856
|
+
:param Dict[str, Any] ansible_cfg_data: Updated Ansible configuration data
|
|
857
|
+
:return: API response
|
|
858
|
+
:rtype: Optional[Dict[str, Any]]
|
|
859
|
+
"""
|
|
860
|
+
return self._make_request("PUT", "/api/ansible-cfg", data=json.dumps(ansible_cfg_data))
|
|
861
|
+
|
|
862
|
+
def get_site_theme(self) -> Optional[Dict[str, Any]]:
|
|
863
|
+
"""
|
|
864
|
+
Get Site Theme.
|
|
865
|
+
|
|
866
|
+
:return: Site theme information
|
|
867
|
+
:rtype: Optional[Dict[str, Any]]
|
|
868
|
+
"""
|
|
869
|
+
return self._make_request("GET", "/api/settings/theme")
|
|
870
|
+
|
|
871
|
+
def update_site_theme(self, theme_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
872
|
+
"""
|
|
873
|
+
Update Site Theme.
|
|
874
|
+
|
|
875
|
+
:param Dict[str, Any] theme_data: Updated theme data
|
|
876
|
+
:return: API response
|
|
877
|
+
:rtype: Optional[Dict[str, Any]]
|
|
878
|
+
"""
|
|
879
|
+
return self._make_request("PUT", "/api/settings/theme", data=json.dumps(theme_data))
|
|
880
|
+
|
|
881
|
+
def get_database_backup(self) -> Optional[Dict[str, Any]]:
|
|
882
|
+
"""
|
|
883
|
+
Get Database Backup.
|
|
884
|
+
|
|
885
|
+
:return: Database backup information
|
|
886
|
+
:rtype: Optional[Dict[str, Any]]
|
|
887
|
+
"""
|
|
888
|
+
return self._make_request("GET", "/api/settings/backup")
|
|
889
|
+
|
|
890
|
+
def get_dns_conf(self) -> Optional[Dict[str, Any]]:
|
|
891
|
+
"""
|
|
892
|
+
Get DNS Configuration.
|
|
893
|
+
|
|
894
|
+
:return: DNS configuration information
|
|
895
|
+
:rtype: Optional[Dict[str, Any]]
|
|
896
|
+
"""
|
|
897
|
+
return self._make_request("GET", "/api/settings/dns")
|
|
898
|
+
|
|
899
|
+
def set_dns_conf(self, dns_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
900
|
+
"""
|
|
901
|
+
Set DNS Configuration.
|
|
902
|
+
|
|
903
|
+
:param Dict[str, Any] dns_data: DNS configuration data
|
|
904
|
+
:return: API response
|
|
905
|
+
:rtype: Optional[Dict[str, Any]]
|
|
906
|
+
"""
|
|
907
|
+
return self._make_request("POST", "/api/settings/dns", data=json.dumps(dns_data))
|
|
908
|
+
|
|
909
|
+
def get_license_info(self) -> Optional[Dict[str, Any]]:
|
|
910
|
+
"""
|
|
911
|
+
Get License Info.
|
|
912
|
+
|
|
913
|
+
:return: License information
|
|
914
|
+
:rtype: Optional[Dict[str, Any]]
|
|
915
|
+
"""
|
|
916
|
+
return self._make_request("GET", "/api/settings/license")
|
|
917
|
+
|
|
918
|
+
def license_key_ingest(self, license_file: Any) -> Optional[Dict[str, Any]]:
|
|
919
|
+
"""
|
|
920
|
+
License Key Ingest.
|
|
921
|
+
|
|
922
|
+
:param Any license_file: License file to ingest
|
|
923
|
+
:return: API response
|
|
924
|
+
:rtype: Optional[Dict[str, Any]]
|
|
925
|
+
"""
|
|
926
|
+
files = {"license_file": license_file}
|
|
927
|
+
return self._make_request("POST", "/api/settings/license", files=files)
|
|
928
|
+
|
|
929
|
+
def get_version_info(self) -> Optional[Dict[str, Any]]:
|
|
930
|
+
"""
|
|
931
|
+
Get Version Info.
|
|
932
|
+
|
|
933
|
+
:return: Version information
|
|
934
|
+
:rtype: Optional[Dict[str, Any]]
|
|
935
|
+
"""
|
|
936
|
+
return self._make_request("GET", "/api/settings/version")
|
|
937
|
+
|
|
938
|
+
def get_license_status(self) -> Optional[Dict[str, Any]]:
|
|
939
|
+
"""
|
|
940
|
+
Get License Status.
|
|
941
|
+
|
|
942
|
+
:return: License status information
|
|
943
|
+
:rtype: Optional[Dict[str, Any]]
|
|
944
|
+
"""
|
|
945
|
+
return self._make_request("GET", "/api/settings/license/status")
|
|
946
|
+
|
|
947
|
+
# 7. Supported Systems
|
|
948
|
+
def get_supported_operating_systems(self) -> Optional[List[Dict[str, Any]]]:
|
|
949
|
+
"""
|
|
950
|
+
Get Supported Operating Systems.
|
|
951
|
+
|
|
952
|
+
:return: List of supported operating systems
|
|
953
|
+
:rtype: Optional[List[Dict[str, Any]]]
|
|
954
|
+
"""
|
|
955
|
+
return self._make_request("GET", "/api/operating-systems")
|
|
956
|
+
|
|
957
|
+
def get_operating_system_default_variables(self, os_id: int) -> Optional[Dict[str, Any]]:
|
|
958
|
+
"""
|
|
959
|
+
Get Operating System Default Variables.
|
|
960
|
+
|
|
961
|
+
:param int os_id: Operating system ID
|
|
962
|
+
:return: Default variables for the specified operating system
|
|
963
|
+
:rtype: Optional[Dict[str, Any]]
|
|
964
|
+
"""
|
|
965
|
+
return self._make_request("GET", f"/api/operating-systems/defaults/{os_id}")
|
|
966
|
+
|
|
967
|
+
def get_os_connection_vars(self, os_id: int) -> Optional[Dict[str, Any]]:
|
|
968
|
+
"""
|
|
969
|
+
Get OS Connection Variables.
|
|
970
|
+
|
|
971
|
+
:param int os_id: Operating system ID
|
|
972
|
+
:return: Connection variables for the specified operating system
|
|
973
|
+
:rtype: Optional[Dict[str, Any]]
|
|
974
|
+
"""
|
|
975
|
+
return self._make_request("GET", f"/api/operating-systems/connection-vars/{os_id}")
|
|
976
|
+
|
|
977
|
+
def get_available_stigs(self) -> List[STIG]:
|
|
978
|
+
"""
|
|
979
|
+
Get Available STIGs.
|
|
980
|
+
|
|
981
|
+
:return: List of available STIGs
|
|
982
|
+
:rtype: List[STIG]
|
|
983
|
+
"""
|
|
984
|
+
response = self._make_request("GET", "/api/stigs")
|
|
985
|
+
return self._handle_list_response(response, STIG)
|
|
986
|
+
|
|
987
|
+
def get_stigs_by_os_id(self, os_id: int) -> List[STIG]:
|
|
988
|
+
"""
|
|
989
|
+
Get STIGs By OS ID.
|
|
990
|
+
|
|
991
|
+
:param int os_id: Operating system ID
|
|
992
|
+
:return: List of STIGs for the specified operating system
|
|
993
|
+
:rtype: List[STIG]
|
|
994
|
+
"""
|
|
995
|
+
response = self._make_request("GET", f"/api/stigs/{os_id}")
|
|
996
|
+
return self._handle_list_response(response, STIG)
|
|
997
|
+
|
|
998
|
+
def get_stig_defaults_by_stig_id(self, stig_id: int) -> Optional[STIG]:
|
|
999
|
+
"""
|
|
1000
|
+
Get STIG Defaults By STIG ID.
|
|
1001
|
+
|
|
1002
|
+
:param int stig_id: STIG ID
|
|
1003
|
+
:return: Default STIG information
|
|
1004
|
+
:rtype: Optional[STIG]
|
|
1005
|
+
"""
|
|
1006
|
+
response = self._make_request("GET", f"/api/stigs/defaults/{stig_id}")
|
|
1007
|
+
return self.handle_response(response, STIG)
|
|
1008
|
+
|
|
1009
|
+
def create_template(self, name: str, os_id: int, playbook_id: int, group_id: int) -> Template:
|
|
1010
|
+
"""
|
|
1011
|
+
Create a new template.
|
|
1012
|
+
|
|
1013
|
+
:param str name: The name of the template.
|
|
1014
|
+
:param int os_id: The ID of the operating system.
|
|
1015
|
+
:param int playbook_id: The ID of the playbook.
|
|
1016
|
+
:param int group_id: The ID of the group.
|
|
1017
|
+
|
|
1018
|
+
:return: The created template.
|
|
1019
|
+
:rtype: Template
|
|
1020
|
+
"""
|
|
1021
|
+
try:
|
|
1022
|
+
# Log input parameters
|
|
1023
|
+
logger.debug(
|
|
1024
|
+
f"Creating template with params: name={name}, os_id={os_id}, playbook_id={playbook_id}, group_id={group_id}"
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
# Create request data according to swagger spec for TemplateRequest
|
|
1028
|
+
template_request = {
|
|
1029
|
+
"name": name,
|
|
1030
|
+
"os_id": int(os_id), # Ensure integer type
|
|
1031
|
+
"group_id": int(group_id), # Ensure integer type
|
|
1032
|
+
"playbook_id": int(playbook_id), # Ensure integer type
|
|
1033
|
+
"template_vars": [], # Added empty template_vars array
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
# Log request data
|
|
1037
|
+
logger.debug(f"Template request data: {template_request}")
|
|
1038
|
+
|
|
1039
|
+
# No need to set headers here as _make_request handles authorization
|
|
1040
|
+
response = self._make_request(
|
|
1041
|
+
"POST", self.TEMPLATES_ENDPOINT, data=template_request # _make_request will handle JSON serialization
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1044
|
+
if not response:
|
|
1045
|
+
error_msg = "Failed to create template - empty response"
|
|
1046
|
+
logger.error(error_msg)
|
|
1047
|
+
raise ValueError(error_msg)
|
|
1048
|
+
|
|
1049
|
+
response_data = response
|
|
1050
|
+
|
|
1051
|
+
# Log the response for debugging
|
|
1052
|
+
logger.debug(f"Template creation response: {response_data}")
|
|
1053
|
+
|
|
1054
|
+
# Ensure the response has all required fields
|
|
1055
|
+
response_data.update({"os_id": os_id, "playbook_id": playbook_id, "group_id": group_id})
|
|
1056
|
+
|
|
1057
|
+
# Log the final template data
|
|
1058
|
+
logger.debug(f"Final template data: {response_data}")
|
|
1059
|
+
|
|
1060
|
+
return Template(**response_data)
|
|
1061
|
+
except Exception as e:
|
|
1062
|
+
logger.error(f"Failed to create template: {str(e)}", exc_info=True) # Added full stack trace
|
|
1063
|
+
raise ValueError(f"Failed to create template: {str(e)}")
|
|
1064
|
+
|
|
1065
|
+
def update_template(self, template: Template) -> Optional[Template]:
|
|
1066
|
+
"""
|
|
1067
|
+
Update Template.
|
|
1068
|
+
|
|
1069
|
+
:param Template template: Template to update
|
|
1070
|
+
:return: Updated template
|
|
1071
|
+
:rtype: Optional[Template]
|
|
1072
|
+
"""
|
|
1073
|
+
data = template.model_dump(by_alias=True, exclude_none=True)
|
|
1074
|
+
response = self._make_request("PUT", self.TEMPLATES_ENDPOINT, data=json.dumps(data))
|
|
1075
|
+
return self.handle_response(response, Template)
|
|
1076
|
+
|
|
1077
|
+
def add_new_template(self, template: Template) -> Optional[Template]:
|
|
1078
|
+
"""
|
|
1079
|
+
Add New Template.
|
|
1080
|
+
|
|
1081
|
+
:param Template template: New template to add
|
|
1082
|
+
:return: Added template
|
|
1083
|
+
:rtype: Optional[Template]
|
|
1084
|
+
"""
|
|
1085
|
+
data = template.model_dump(by_alias=True, exclude_none=True)
|
|
1086
|
+
response = self._make_request("POST", self.TEMPLATES_ENDPOINT, data=json.dumps(data))
|
|
1087
|
+
return self.handle_response(response, Template)
|
|
1088
|
+
|
|
1089
|
+
def update_template_variable(self, var_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1090
|
+
"""
|
|
1091
|
+
Update Template Variable.
|
|
1092
|
+
|
|
1093
|
+
:param Dict[str, Any] var_data: Variable data to update
|
|
1094
|
+
:return: API response
|
|
1095
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1096
|
+
"""
|
|
1097
|
+
return self._make_request("PUT", self.TEMPLATE_VAR_ENDPOINT, data=json.dumps(var_data))
|
|
1098
|
+
|
|
1099
|
+
def add_template_var(self, var_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1100
|
+
"""
|
|
1101
|
+
Add Template Variable.
|
|
1102
|
+
|
|
1103
|
+
:param Dict[str, Any] var_data: Variable data to add
|
|
1104
|
+
:return: API response
|
|
1105
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1106
|
+
"""
|
|
1107
|
+
return self._make_request("POST", self.TEMPLATE_VAR_ENDPOINT, data=json.dumps(var_data))
|
|
1108
|
+
|
|
1109
|
+
def get_available_stig_templates(self, stig_id: int) -> List[Template]:
|
|
1110
|
+
"""
|
|
1111
|
+
Get Available STIG Templates.
|
|
1112
|
+
|
|
1113
|
+
:param int stig_id: STIG ID
|
|
1114
|
+
:return: List of available templates for the specified STIG
|
|
1115
|
+
:rtype: List[Template]
|
|
1116
|
+
"""
|
|
1117
|
+
response = self._make_request("GET", f"/api/stigs/templates/stig/{stig_id}")
|
|
1118
|
+
return self._handle_list_response(response, Template)
|
|
1119
|
+
|
|
1120
|
+
def get_template(self, template_id: int) -> Optional[Template]:
|
|
1121
|
+
"""
|
|
1122
|
+
Get Template.
|
|
1123
|
+
|
|
1124
|
+
:param int template_id: Template ID
|
|
1125
|
+
:return: Template information
|
|
1126
|
+
:rtype: Optional[Template]
|
|
1127
|
+
"""
|
|
1128
|
+
response = self._make_request("GET", f"{self.TEMPLATES_ENDPOINT}/{template_id}")
|
|
1129
|
+
return self.handle_response(response, Template)
|
|
1130
|
+
|
|
1131
|
+
def delete_template(self, template_id: int) -> bool:
|
|
1132
|
+
"""
|
|
1133
|
+
Delete Template.
|
|
1134
|
+
|
|
1135
|
+
:param int template_id: Template ID to delete
|
|
1136
|
+
:return: True if deletion was successful, False otherwise
|
|
1137
|
+
:rtype: bool
|
|
1138
|
+
"""
|
|
1139
|
+
response = self._make_request("DELETE", f"{self.TEMPLATES_ENDPOINT}/{template_id}")
|
|
1140
|
+
return response is not None
|
|
1141
|
+
|
|
1142
|
+
def get_template_ids_by_group(self, group_id: int) -> List[Template]:
|
|
1143
|
+
"""
|
|
1144
|
+
Get Template IDs By Group.
|
|
1145
|
+
|
|
1146
|
+
:param int group_id: The ID of the group
|
|
1147
|
+
:return: List of templates associated with the group
|
|
1148
|
+
:rtype: List[Template]
|
|
1149
|
+
"""
|
|
1150
|
+
try:
|
|
1151
|
+
response = self._make_request("GET", f"/api/stigs/templates/group/{group_id}")
|
|
1152
|
+
if not response:
|
|
1153
|
+
return []
|
|
1154
|
+
|
|
1155
|
+
# Add group_id to each template response
|
|
1156
|
+
if isinstance(response, list):
|
|
1157
|
+
for template_data in response:
|
|
1158
|
+
template_data.update(
|
|
1159
|
+
{
|
|
1160
|
+
"group_id": group_id,
|
|
1161
|
+
"playbook_id": template_data.get("playbook_id", None), # Handle missing playbook_id
|
|
1162
|
+
}
|
|
1163
|
+
)
|
|
1164
|
+
|
|
1165
|
+
templates = []
|
|
1166
|
+
for template_data in response:
|
|
1167
|
+
try:
|
|
1168
|
+
templates.append(Template(**template_data))
|
|
1169
|
+
except Exception as e:
|
|
1170
|
+
logger.error(f"Failed to parse template data: {e}", exc_info=True)
|
|
1171
|
+
|
|
1172
|
+
return templates
|
|
1173
|
+
except Exception as e:
|
|
1174
|
+
logger.error(f"Failed to get templates for group {group_id}: {e}", exc_info=True)
|
|
1175
|
+
return []
|
|
1176
|
+
|
|
1177
|
+
def delete_template_variable(self, var_id: int) -> Optional[Dict[str, Any]]:
|
|
1178
|
+
"""
|
|
1179
|
+
Delete Template Variable.
|
|
1180
|
+
|
|
1181
|
+
:param int var_id: The ID of the variable to delete
|
|
1182
|
+
:return: API response
|
|
1183
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1184
|
+
"""
|
|
1185
|
+
return self._make_request("DELETE", f"{self.TEMPLATE_VAR_ENDPOINT}/{var_id}")
|
|
1186
|
+
|
|
1187
|
+
# 8. Perform Audit/Remediate
|
|
1188
|
+
def audit_device(self, device_id: int, group_id: int, playbook_id: int, template_id: int) -> AuditResponse:
|
|
1189
|
+
"""
|
|
1190
|
+
Audit Device.
|
|
1191
|
+
|
|
1192
|
+
:param int device_id: The ID of the device to audit
|
|
1193
|
+
:param int group_id: The ID of the group
|
|
1194
|
+
:param int playbook_id: The ID of the playbook
|
|
1195
|
+
:param int template_id: The ID of the template
|
|
1196
|
+
:return: Audit response
|
|
1197
|
+
:rtype: AuditResponse
|
|
1198
|
+
:raises ValueError: If the API response cannot be parsed into an AuditResponse
|
|
1199
|
+
"""
|
|
1200
|
+
params = {"device_id": device_id, "group_id": group_id, "playbook_id": playbook_id, "template_id": template_id}
|
|
1201
|
+
response = self._make_request("GET", "/api/audit/device", params=params)
|
|
1202
|
+
audit_response = self.handle_response(response, AuditResponse)
|
|
1203
|
+
if audit_response is None:
|
|
1204
|
+
raise ValueError("Failed to parse API response into AuditResponse")
|
|
1205
|
+
return audit_response
|
|
1206
|
+
|
|
1207
|
+
def audit_group(self, group_id: int, playbook_id: int, template_id: int) -> Optional[Dict[str, Any]]:
|
|
1208
|
+
"""
|
|
1209
|
+
Audit Group.
|
|
1210
|
+
|
|
1211
|
+
:param int group_id: The ID of the group to audit
|
|
1212
|
+
:param int playbook_id: The ID of the playbook
|
|
1213
|
+
:param int template_id: The ID of the template
|
|
1214
|
+
:return: API response
|
|
1215
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1216
|
+
"""
|
|
1217
|
+
params = {"group_id": group_id, "playbook_id": playbook_id, "template_id": template_id}
|
|
1218
|
+
return self._make_request("GET", "/api/audit/group", params=params)
|
|
1219
|
+
|
|
1220
|
+
def cancel_audit(self, audit_id: int) -> Optional[Dict[str, Any]]:
|
|
1221
|
+
"""
|
|
1222
|
+
Cancel Audit.
|
|
1223
|
+
|
|
1224
|
+
:param int audit_id: The ID of the audit to cancel
|
|
1225
|
+
:return: API response
|
|
1226
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1227
|
+
"""
|
|
1228
|
+
return self._make_request("GET", "/api/audit/cancel", params={"audit_id": audit_id})
|
|
1229
|
+
|
|
1230
|
+
def remediate_device(
|
|
1231
|
+
self, device_id: int, group_id: int, playbook_id: int, template_id: int
|
|
1232
|
+
) -> Optional[Dict[str, Any]]:
|
|
1233
|
+
"""
|
|
1234
|
+
Remediate Device.
|
|
1235
|
+
|
|
1236
|
+
:param int device_id: The ID of the device to remediate
|
|
1237
|
+
:param int group_id: The ID of the group
|
|
1238
|
+
:param int playbook_id: The ID of the playbook
|
|
1239
|
+
:param int template_id: The ID of the template
|
|
1240
|
+
:return: API response
|
|
1241
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1242
|
+
"""
|
|
1243
|
+
params = {"device_id": device_id, "group_id": group_id, "playbook_id": playbook_id, "template_id": template_id}
|
|
1244
|
+
return self._make_request("GET", "/api/remediate/device", params=params)
|
|
1245
|
+
|
|
1246
|
+
def remediate_group(self, group_id: int, playbook_id: int, template_id: int) -> Optional[Dict[str, Any]]:
|
|
1247
|
+
"""
|
|
1248
|
+
Remediate Group.
|
|
1249
|
+
|
|
1250
|
+
:param int group_id: The ID of the group to remediate
|
|
1251
|
+
:param int playbook_id: The ID of the playbook
|
|
1252
|
+
:param int template_id: The ID of the template
|
|
1253
|
+
:return: API response
|
|
1254
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1255
|
+
"""
|
|
1256
|
+
params = {"group_id": group_id, "playbook_id": playbook_id, "template_id": template_id}
|
|
1257
|
+
return self._make_request("GET", "/api/remediate/group", params=params)
|
|
1258
|
+
|
|
1259
|
+
def cancel_remediation(self, rem_id: int) -> Optional[Dict[str, Any]]:
|
|
1260
|
+
"""
|
|
1261
|
+
Cancel Remediation.
|
|
1262
|
+
|
|
1263
|
+
:param int rem_id: The ID of the remediation to cancel
|
|
1264
|
+
:return: API response
|
|
1265
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1266
|
+
"""
|
|
1267
|
+
return self._make_request("GET", "/api/remediate/cancel", params={"rem_id": rem_id})
|
|
1268
|
+
|
|
1269
|
+
def stream_audit(self, audit_id: int) -> Optional[Dict[str, Any]]:
|
|
1270
|
+
"""
|
|
1271
|
+
Stream Audit.
|
|
1272
|
+
|
|
1273
|
+
:param int audit_id: The ID of the audit to stream
|
|
1274
|
+
:return: API response
|
|
1275
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1276
|
+
"""
|
|
1277
|
+
return self._make_request("GET", "/api/stream-audit", params={"audit_id": audit_id})
|
|
1278
|
+
|
|
1279
|
+
def get_scheduled_tasks(
|
|
1280
|
+
self, group_id: Optional[int] = None, device_id: Optional[int] = None, repeat_type: Optional[str] = None
|
|
1281
|
+
) -> Optional[Dict[str, Any]]:
|
|
1282
|
+
"""
|
|
1283
|
+
Get Scheduled Tasks.
|
|
1284
|
+
|
|
1285
|
+
:param Optional[int] group_id: The ID of the group (default: None)
|
|
1286
|
+
:param Optional[int] device_id: The ID of the device (default: None)
|
|
1287
|
+
:param Optional[str] repeat_type: The type of repeat for the task (default: None)
|
|
1288
|
+
:return: API response
|
|
1289
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1290
|
+
"""
|
|
1291
|
+
params = {}
|
|
1292
|
+
if group_id is not None:
|
|
1293
|
+
params["group_id"] = str(group_id)
|
|
1294
|
+
if device_id is not None:
|
|
1295
|
+
params["device_id"] = str(device_id)
|
|
1296
|
+
if repeat_type:
|
|
1297
|
+
params["repeat_type"] = repeat_type
|
|
1298
|
+
return self._make_request("GET", "/api/schedule", params=params)
|
|
1299
|
+
|
|
1300
|
+
def delete_scheduled_task(self, scheduled_task_id: int) -> Optional[Dict[str, Any]]:
|
|
1301
|
+
"""
|
|
1302
|
+
Delete Scheduled Task.
|
|
1303
|
+
|
|
1304
|
+
:param int scheduled_task_id: The ID of the scheduled task to delete
|
|
1305
|
+
:return: API response
|
|
1306
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1307
|
+
"""
|
|
1308
|
+
return self._make_request("DELETE", f"/api/schedule/{scheduled_task_id}")
|
|
1309
|
+
|
|
1310
|
+
def schedule_single(self, schedule_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1311
|
+
"""
|
|
1312
|
+
Schedule Single Task.
|
|
1313
|
+
|
|
1314
|
+
:param Dict[str, Any] schedule_data: The data for scheduling the task
|
|
1315
|
+
:return: API response
|
|
1316
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1317
|
+
"""
|
|
1318
|
+
return self._make_request("POST", "/api/schedule/single", data=json.dumps(schedule_data))
|
|
1319
|
+
|
|
1320
|
+
def schedule_repeat(self, schedule_data: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
1321
|
+
"""
|
|
1322
|
+
Schedule Repeat Task.
|
|
1323
|
+
|
|
1324
|
+
:param Dict[str, Any] schedule_data: The data for scheduling the repeating task
|
|
1325
|
+
:return: API response
|
|
1326
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1327
|
+
"""
|
|
1328
|
+
return self._make_request("POST", "/api/schedule/repeat", data=json.dumps(schedule_data))
|
|
1329
|
+
|
|
1330
|
+
# 9. User Actions
|
|
1331
|
+
def read_current_user(self) -> Optional[Dict[str, Any]]:
|
|
1332
|
+
"""
|
|
1333
|
+
Read Current User.
|
|
1334
|
+
|
|
1335
|
+
:return: Current user information
|
|
1336
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1337
|
+
"""
|
|
1338
|
+
return self._make_request("GET", "/api/users/me")
|
|
1339
|
+
|
|
1340
|
+
def read_all_users(self) -> Optional[List[Dict[str, Any]]]:
|
|
1341
|
+
"""
|
|
1342
|
+
Read All Users.
|
|
1343
|
+
|
|
1344
|
+
:return: List of all users
|
|
1345
|
+
:rtype: Optional[List[Dict[str, Any]]]
|
|
1346
|
+
"""
|
|
1347
|
+
return self._make_request("GET", "/api/users/all")
|
|
1348
|
+
|
|
1349
|
+
def view_all_roles(self) -> Optional[List[Dict[str, Any]]]:
|
|
1350
|
+
"""
|
|
1351
|
+
View All Roles.
|
|
1352
|
+
|
|
1353
|
+
:return: List of all roles
|
|
1354
|
+
:rtype: Optional[List[Dict[str, Any]]]
|
|
1355
|
+
"""
|
|
1356
|
+
return self._make_request("GET", "/api/users/roles/all")
|
|
1357
|
+
|
|
1358
|
+
def create_new_user(self, email: str, password: str) -> Optional[Dict[str, Any]]:
|
|
1359
|
+
"""
|
|
1360
|
+
Create New User.
|
|
1361
|
+
|
|
1362
|
+
:param str email: The email of the new user
|
|
1363
|
+
:param str password: The password for the new user
|
|
1364
|
+
:return: API response
|
|
1365
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1366
|
+
"""
|
|
1367
|
+
data = {"email": email, "password": password}
|
|
1368
|
+
return self._make_request("POST", "/api/users", data=json.dumps(data))
|
|
1369
|
+
|
|
1370
|
+
def delete_user(self, user_id: int) -> Optional[Dict[str, Any]]:
|
|
1371
|
+
"""
|
|
1372
|
+
Delete User.
|
|
1373
|
+
|
|
1374
|
+
:param int user_id: The ID of the user to delete
|
|
1375
|
+
:return: API response
|
|
1376
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1377
|
+
"""
|
|
1378
|
+
return self._make_request("DELETE", f"/api/users/{user_id}")
|
|
1379
|
+
|
|
1380
|
+
def enable_user(self, user_id: int) -> Optional[Dict[str, Any]]:
|
|
1381
|
+
"""
|
|
1382
|
+
Enable User.
|
|
1383
|
+
|
|
1384
|
+
:param int user_id: The ID of the user to enable
|
|
1385
|
+
:return: API response
|
|
1386
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1387
|
+
"""
|
|
1388
|
+
return self._make_request("PUT", "/api/users/enable", params={"user_id": user_id})
|
|
1389
|
+
|
|
1390
|
+
def disable_user(self, user_id: int) -> Optional[Dict[str, Any]]:
|
|
1391
|
+
"""
|
|
1392
|
+
Disable User.
|
|
1393
|
+
|
|
1394
|
+
:param int user_id: The ID of the user to disable
|
|
1395
|
+
:return: API response
|
|
1396
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1397
|
+
"""
|
|
1398
|
+
return self._make_request("PUT", "/api/users/disable", params={"user_id": user_id})
|
|
1399
|
+
|
|
1400
|
+
def add_role_to_user(self, user_id: int, role_id: int) -> Optional[Dict[str, Any]]:
|
|
1401
|
+
"""
|
|
1402
|
+
Add Role To User.
|
|
1403
|
+
|
|
1404
|
+
:param int user_id: The ID of the user
|
|
1405
|
+
:param int role_id: The ID of the role to add
|
|
1406
|
+
:return: API response
|
|
1407
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1408
|
+
"""
|
|
1409
|
+
data = {"user_id": user_id, "role_id": role_id}
|
|
1410
|
+
return self._make_request("PUT", "/api/users/roles/add", data=json.dumps(data))
|
|
1411
|
+
|
|
1412
|
+
def remove_role_from_user(self, user_id: int, role_id: int) -> Optional[Dict[str, Any]]:
|
|
1413
|
+
"""
|
|
1414
|
+
Remove Role From User.
|
|
1415
|
+
|
|
1416
|
+
:param int user_id: The ID of the user
|
|
1417
|
+
:param int role_id: The ID of the role to remove
|
|
1418
|
+
:return: API response
|
|
1419
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1420
|
+
"""
|
|
1421
|
+
data = {"user_id": user_id, "role_id": role_id}
|
|
1422
|
+
return self._make_request("PUT", "/api/users/roles/remove", data=json.dumps(data))
|
|
1423
|
+
|
|
1424
|
+
def edit_user_settings(self, user_id: int, dark_mode: bool) -> Optional[Dict[str, Any]]:
|
|
1425
|
+
"""
|
|
1426
|
+
Edit User Settings.
|
|
1427
|
+
|
|
1428
|
+
:param int user_id: The ID of the user
|
|
1429
|
+
:param bool dark_mode: Whether to enable dark mode
|
|
1430
|
+
:return: API response
|
|
1431
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1432
|
+
"""
|
|
1433
|
+
data = {"user_id": user_id, "dark_mode": dark_mode}
|
|
1434
|
+
return self._make_request("PUT", "/api/users/settings", data=json.dumps(data))
|
|
1435
|
+
|
|
1436
|
+
# 10. Logs
|
|
1437
|
+
def get_all_logs(self, skip: int = 0, limit: int = 10, **params) -> Optional[Dict[str, Any]]:
|
|
1438
|
+
"""
|
|
1439
|
+
Get All Logs.
|
|
1440
|
+
|
|
1441
|
+
:param int skip: Number of logs to skip (default: 0)
|
|
1442
|
+
:param int limit: Maximum number of logs to return (default: 10)
|
|
1443
|
+
:param params: Additional parameters for filtering logs
|
|
1444
|
+
:return: API response
|
|
1445
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1446
|
+
"""
|
|
1447
|
+
return self._make_request("GET", "/api/logs", params={"skip": skip, "limit": limit, **params})
|
|
1448
|
+
|
|
1449
|
+
# 11. Notifications
|
|
1450
|
+
def get_notification(self, id: int) -> Optional[Dict[str, Any]]:
|
|
1451
|
+
"""
|
|
1452
|
+
Get Notification.
|
|
1453
|
+
|
|
1454
|
+
:param int id: The ID of the notification
|
|
1455
|
+
:return: API response
|
|
1456
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1457
|
+
"""
|
|
1458
|
+
return self._make_request("GET", "/api/notification", params={"id": id})
|
|
1459
|
+
|
|
1460
|
+
def delete_notification(self, id: int) -> Optional[Dict[str, Any]]:
|
|
1461
|
+
"""
|
|
1462
|
+
Delete Notification.
|
|
1463
|
+
|
|
1464
|
+
:param int id: The ID of the notification to delete
|
|
1465
|
+
:return: API response
|
|
1466
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1467
|
+
"""
|
|
1468
|
+
return self._make_request("DELETE", "/api/notification", params={"id": id})
|
|
1469
|
+
|
|
1470
|
+
def acknowledge_notification(self, id: int) -> Optional[Dict[str, Any]]:
|
|
1471
|
+
"""
|
|
1472
|
+
Acknowledge Notification.
|
|
1473
|
+
|
|
1474
|
+
:param int id: The ID of the notification to acknowledge
|
|
1475
|
+
:return: API response
|
|
1476
|
+
:rtype: Optional[Dict[str, Any]]
|
|
1477
|
+
"""
|
|
1478
|
+
return self._make_request("PUT", "/api/notification/acknowledge", params={"id": id})
|
|
1479
|
+
|
|
1480
|
+
def get_audit_status(self, audit_id: int) -> Optional[AuditResponse]:
|
|
1481
|
+
"""
|
|
1482
|
+
Get audit status.
|
|
1483
|
+
|
|
1484
|
+
:param int audit_id: The ID of the audit
|
|
1485
|
+
:return: Audit status response
|
|
1486
|
+
:rtype: Optional[AuditResponse]
|
|
1487
|
+
"""
|
|
1488
|
+
try:
|
|
1489
|
+
# According to swagger spec, the endpoint is /api/audits/{audit_id}
|
|
1490
|
+
response = self._make_request("GET", f"/api/audits/{audit_id}")
|
|
1491
|
+
|
|
1492
|
+
if not response:
|
|
1493
|
+
logger.error(f"Failed to get audit status for audit {audit_id} - empty response")
|
|
1494
|
+
return None
|
|
1495
|
+
|
|
1496
|
+
# Log the full response for debugging
|
|
1497
|
+
logger.debug(f"Audit status response: {response}")
|
|
1498
|
+
|
|
1499
|
+
# Add required fields if missing
|
|
1500
|
+
response.update(
|
|
1501
|
+
{
|
|
1502
|
+
"audit_id": audit_id,
|
|
1503
|
+
"status": response.get("status", "unknown"),
|
|
1504
|
+
"error_message": response.get("error_message", response.get("detail", "Unknown error")),
|
|
1505
|
+
"device_id": response.get("device_id"),
|
|
1506
|
+
"group_id": response.get("group_id"),
|
|
1507
|
+
"template_id": response.get("template_id"),
|
|
1508
|
+
"job_id": response.get("job_id"),
|
|
1509
|
+
}
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
# Create AuditResponse object
|
|
1513
|
+
audit_response = AuditResponse(**response)
|
|
1514
|
+
|
|
1515
|
+
# Log status for debugging
|
|
1516
|
+
logger.debug(f"Audit {audit_id} status: {audit_response.status}")
|
|
1517
|
+
|
|
1518
|
+
return audit_response
|
|
1519
|
+
except Exception as e:
|
|
1520
|
+
logger.error(f"Failed to get audit status for audit {audit_id}: {e}", exc_info=True)
|
|
1521
|
+
return None
|
|
1522
|
+
|
|
1523
|
+
def update_device_vars(self, device_id: int, vars_data: List[Dict[str, str]]) -> bool:
|
|
1524
|
+
"""
|
|
1525
|
+
Update device variables.
|
|
1526
|
+
|
|
1527
|
+
:param int device_id: Device ID
|
|
1528
|
+
:param List[Dict[str, str]] vars_data: List of variable data to update
|
|
1529
|
+
:return: True if successful, False otherwise
|
|
1530
|
+
:rtype: bool
|
|
1531
|
+
"""
|
|
1532
|
+
try:
|
|
1533
|
+
for var in vars_data:
|
|
1534
|
+
var_data = {"device_id": device_id, "var_name": var["var_name"], "var_value": var["var_value"]}
|
|
1535
|
+
|
|
1536
|
+
logger.debug(f"Updating device variable: {var_data}")
|
|
1537
|
+
response = self._make_request("POST", self.DEVICE_VARS_ENDPOINT, data=var_data)
|
|
1538
|
+
|
|
1539
|
+
if not response:
|
|
1540
|
+
logger.error(f"Failed to update variable {var['var_name']}")
|
|
1541
|
+
return False
|
|
1542
|
+
|
|
1543
|
+
return True
|
|
1544
|
+
except Exception as e:
|
|
1545
|
+
logger.error(f"Failed to update device variables: {e}", exc_info=True)
|
|
1546
|
+
return False
|