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,620 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""RegScale Email Reminders Class used in admin_actions.py"""
|
|
4
|
+
|
|
5
|
+
# standard python imports
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
9
|
+
from datetime import datetime, timedelta
|
|
10
|
+
from typing import Tuple, Optional
|
|
11
|
+
from urllib.parse import urljoin
|
|
12
|
+
|
|
13
|
+
from requests import JSONDecodeError
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
|
|
16
|
+
from regscale.core.app.api import Api
|
|
17
|
+
from regscale.core.app.application import Application
|
|
18
|
+
from regscale.core.app.utils.app_utils import (
|
|
19
|
+
create_progress_object,
|
|
20
|
+
error_and_exit,
|
|
21
|
+
flatten_dict,
|
|
22
|
+
get_css,
|
|
23
|
+
reformat_str_date,
|
|
24
|
+
uncamel_case,
|
|
25
|
+
)
|
|
26
|
+
from regscale.models import Email, User
|
|
27
|
+
from regscale.models.app_models.pipeline import Pipeline
|
|
28
|
+
from regscale.utils.threading.threadhandler import create_threads, thread_assignment
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SendReminders:
|
|
32
|
+
"""
|
|
33
|
+
Class to send reminders to users with upcoming and/or outstanding Tasks, Assessments,
|
|
34
|
+
Data Calls, Issues, Security Plans, and Workflows
|
|
35
|
+
|
|
36
|
+
:param Application app: CLI Application
|
|
37
|
+
:param int days: # of days to look for upcoming and/or outstanding items, default is 30 days
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, app: Application, days: int = 30):
|
|
41
|
+
self.app = app
|
|
42
|
+
self.logger = self.app.logger
|
|
43
|
+
self.api = Api()
|
|
44
|
+
self.config = self.app.config
|
|
45
|
+
self.days = days
|
|
46
|
+
self.base_url = urljoin(self.config["domain"], "/api/")
|
|
47
|
+
self.users = []
|
|
48
|
+
self.activated_users = []
|
|
49
|
+
self.email_data = None
|
|
50
|
+
self.tenant_pipeline = []
|
|
51
|
+
self.final_pipeline = []
|
|
52
|
+
self.emails = []
|
|
53
|
+
self.job_progress = create_progress_object()
|
|
54
|
+
|
|
55
|
+
def get_and_send_reminders(self) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Function to get and send reminders for users in RegScale that have email notifications
|
|
58
|
+
enabled and have upcoming or outstanding Tasks, Assessments, Data Calls, Issues, Security Plans,
|
|
59
|
+
and Workflows
|
|
60
|
+
|
|
61
|
+
:rtype: None
|
|
62
|
+
"""
|
|
63
|
+
import pandas as pd # Optimize import performance
|
|
64
|
+
|
|
65
|
+
# make sure config is set before processing
|
|
66
|
+
if self.config["domain"] == "":
|
|
67
|
+
error_and_exit("The domain is blank in the initialization file.")
|
|
68
|
+
if self.config["token"] == "":
|
|
69
|
+
error_and_exit("The token has not been set in the initialization file.")
|
|
70
|
+
|
|
71
|
+
# get the user's tenant id, used to get all active
|
|
72
|
+
# users for that instance of the application
|
|
73
|
+
url = urljoin(self.base_url, f"accounts/find/{self.config['userId']}")
|
|
74
|
+
self.logger.debug("Fetching tenant information from %s.", url)
|
|
75
|
+
try:
|
|
76
|
+
res = self.api.get(url=url)
|
|
77
|
+
self.logger.debug("Response: %i: %s=%s", res.status_code, res.reason, res.text)
|
|
78
|
+
self.logger.debug(res.json())
|
|
79
|
+
res = res.json()
|
|
80
|
+
except JSONDecodeError as ex:
|
|
81
|
+
error_and_exit(f"Unable to retrieve tenant information from RegScale.\n{ex}")
|
|
82
|
+
ten_id = res["tenantId"]
|
|
83
|
+
|
|
84
|
+
# Use the api to get a list of all active users
|
|
85
|
+
# with emailNotifications set to True for
|
|
86
|
+
# the tenant id of the current user
|
|
87
|
+
response = self.api.get(url=urljoin(self.base_url, f"accounts/{ten_id}/True"))
|
|
88
|
+
activated_users_response = self.api.get(url=urljoin(self.base_url, "accounts"))
|
|
89
|
+
# try to convert the response to a json file, exit if it errors
|
|
90
|
+
try:
|
|
91
|
+
self.users = response.json()
|
|
92
|
+
self.activated_users = activated_users_response.json()
|
|
93
|
+
# if error encountered, exit the application
|
|
94
|
+
except JSONDecodeError as ex:
|
|
95
|
+
error_and_exit(f"Unable to retrieve active users from RegScale.\n{ex}")
|
|
96
|
+
|
|
97
|
+
# start a console progress bar and threads for the given task
|
|
98
|
+
# create the threads with the given function, arguments and thread count
|
|
99
|
+
with self.job_progress:
|
|
100
|
+
if len(self.users) == 0:
|
|
101
|
+
self.logger.warning("No users have email notifications enabled!")
|
|
102
|
+
return
|
|
103
|
+
self.logger.info("Fetching pipeline for %s user(s).", len(self.users))
|
|
104
|
+
getting_items = self.job_progress.add_task(
|
|
105
|
+
f"[#f8b737]Fetching pipeline for {len(self.users)} user(s)...",
|
|
106
|
+
total=len(self.users),
|
|
107
|
+
)
|
|
108
|
+
max_workers = self.config.get("maxThreads", 30)
|
|
109
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
110
|
+
futures = [
|
|
111
|
+
executor.submit(
|
|
112
|
+
self.get_upcoming_or_expired_items,
|
|
113
|
+
user,
|
|
114
|
+
)
|
|
115
|
+
for user in self.users
|
|
116
|
+
]
|
|
117
|
+
for future in as_completed(futures):
|
|
118
|
+
pipeline = future.result()
|
|
119
|
+
self.tenant_pipeline.append(pipeline)
|
|
120
|
+
self.job_progress.update(getting_items, advance=1)
|
|
121
|
+
|
|
122
|
+
if len(self.tenant_pipeline) > 0:
|
|
123
|
+
self.logger.info("Analyzing pipeline for %s user(s).", len(self.tenant_pipeline))
|
|
124
|
+
# start a console progress bar and threads for the given task
|
|
125
|
+
analyze_items = self.job_progress.add_task(
|
|
126
|
+
f"[#ef5d23]Analyzing pipeline for {len(self.tenant_pipeline)} user(s)...",
|
|
127
|
+
total=len(self.tenant_pipeline),
|
|
128
|
+
)
|
|
129
|
+
# convert user list into a dictionary using ID as the key for each user dictionary
|
|
130
|
+
dict_users = {
|
|
131
|
+
self.activated_users[i]["id"]: self.activated_users[i] for i in range(len(self.activated_users))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
create_threads(
|
|
135
|
+
process=self.analyze_pipeline,
|
|
136
|
+
args=(self.config, analyze_items, dict_users),
|
|
137
|
+
thread_count=len(self.tenant_pipeline),
|
|
138
|
+
)
|
|
139
|
+
self.logger.info("Sending an email to %s user(s).", len(self.final_pipeline))
|
|
140
|
+
# start a console progress bar and threads for the given task
|
|
141
|
+
emailing_users = self.job_progress.add_task(
|
|
142
|
+
f"[#21a5bb]Sending an email to {len(self.final_pipeline)} user(s)...",
|
|
143
|
+
total=len(self.final_pipeline),
|
|
144
|
+
)
|
|
145
|
+
create_threads(
|
|
146
|
+
process=self.format_and_email,
|
|
147
|
+
args=(self.api, self.config, emailing_users),
|
|
148
|
+
thread_count=len(self.final_pipeline),
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
self.logger.info("No outstanding or upcoming items!")
|
|
152
|
+
sys.exit()
|
|
153
|
+
|
|
154
|
+
# create one data table from all pandas data tables in emails
|
|
155
|
+
self.email_data = pd.concat(self.emails)
|
|
156
|
+
|
|
157
|
+
# create console variable and print # of emails sent successfully
|
|
158
|
+
self.logger.info("Successfully sent an email to %s user(s)...", self.email_data.Emailed.sum())
|
|
159
|
+
console = Console()
|
|
160
|
+
console.print(f"[green]Successfully sent an email to {self.email_data.Emailed.sum()} user(s)...")
|
|
161
|
+
|
|
162
|
+
# format email to notify person that called the command of the outcome
|
|
163
|
+
email = Email(
|
|
164
|
+
fromEmail="Support@RegScale.com",
|
|
165
|
+
emailSenderId=self.config["userId"],
|
|
166
|
+
to=res["email"],
|
|
167
|
+
subject=f"RegScale Reminders Sent to {self.email_data.Emailed.sum()} User(s)",
|
|
168
|
+
body=get_css("email_style.css")
|
|
169
|
+
+ self.email_data.to_html(justify="left", index=False)
|
|
170
|
+
.replace('border="1"', 'border="0"')
|
|
171
|
+
.replace("&", "&")
|
|
172
|
+
.replace(">", ">")
|
|
173
|
+
.replace("<", "<")
|
|
174
|
+
.replace("’", "'"),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
# send the email to the user
|
|
178
|
+
email.send()
|
|
179
|
+
|
|
180
|
+
def get_upcoming_or_expired_items(self, user: dict) -> Optional[Pipeline]:
|
|
181
|
+
"""
|
|
182
|
+
Function used by threads to send emails to users with upcoming and/or outstanding
|
|
183
|
+
Tasks, Assessments, Data Calls, Issues, Security Plans, and Workflows
|
|
184
|
+
|
|
185
|
+
:param dict user: Dictionary of user to get upcoming and/or outstanding items for
|
|
186
|
+
:return: Pipeline object for the provided user, if they have one or more items
|
|
187
|
+
:rtype: Optional[Pipeline]
|
|
188
|
+
"""
|
|
189
|
+
# calculate date with the # of days provided
|
|
190
|
+
# have to explicitly convert the days to an int because of Airflow
|
|
191
|
+
before_date = datetime.now() + timedelta(days=int(self.days))
|
|
192
|
+
after_date = datetime.now() - timedelta(days=int(self.days))
|
|
193
|
+
|
|
194
|
+
# format the date to a string the server will recognize
|
|
195
|
+
before_date = before_date.strftime("%Y-%m-%dT%H:%M:%S")
|
|
196
|
+
after_date = after_date.strftime("%Y-%m-%dT%H:%M:%S")
|
|
197
|
+
|
|
198
|
+
# get all the assessments, issues, tasks, data calls, security plans and workflows
|
|
199
|
+
# for the user we can email, using the # of days entered by the user using graphql,
|
|
200
|
+
# if no days were entered, the default is 30 days
|
|
201
|
+
query = f"""
|
|
202
|
+
query {{
|
|
203
|
+
assessments(
|
|
204
|
+
take: 50
|
|
205
|
+
skip: 0
|
|
206
|
+
order: {{ plannedFinish: DESC }}
|
|
207
|
+
where: {{
|
|
208
|
+
leadAssessorId: {{ eq: "{user["id"]}" }}
|
|
209
|
+
plannedFinish: {{ lte: "{before_date}" }}
|
|
210
|
+
status: {{ nin: ["Complete", "Cancelled"] }}
|
|
211
|
+
}}
|
|
212
|
+
) {{
|
|
213
|
+
items {{
|
|
214
|
+
uuid
|
|
215
|
+
id
|
|
216
|
+
title
|
|
217
|
+
leadAssessorId
|
|
218
|
+
assessmentType
|
|
219
|
+
plannedFinish
|
|
220
|
+
createdById
|
|
221
|
+
dateCreated
|
|
222
|
+
status
|
|
223
|
+
assessmentResult
|
|
224
|
+
actualFinish
|
|
225
|
+
}}
|
|
226
|
+
totalCount
|
|
227
|
+
pageInfo {{
|
|
228
|
+
hasNextPage
|
|
229
|
+
}}
|
|
230
|
+
}}
|
|
231
|
+
dataCalls(
|
|
232
|
+
take: 50
|
|
233
|
+
skip: 0
|
|
234
|
+
order: {{ dateDue: DESC }}
|
|
235
|
+
where: {{
|
|
236
|
+
createdById: {{ eq: "{user["id"]}" }}
|
|
237
|
+
dateDue: {{ lte: "{before_date}" }}
|
|
238
|
+
status: {{ nin: ["Completed", "Cancelled"] }}
|
|
239
|
+
}}
|
|
240
|
+
) {{
|
|
241
|
+
items {{
|
|
242
|
+
uuid
|
|
243
|
+
id
|
|
244
|
+
title
|
|
245
|
+
dataCallLeadId
|
|
246
|
+
dateDue
|
|
247
|
+
createdById
|
|
248
|
+
dateCreated
|
|
249
|
+
status
|
|
250
|
+
}}
|
|
251
|
+
totalCount
|
|
252
|
+
pageInfo {{
|
|
253
|
+
hasNextPage
|
|
254
|
+
}}
|
|
255
|
+
}}
|
|
256
|
+
securityPlans(
|
|
257
|
+
take: 50
|
|
258
|
+
skip: 0
|
|
259
|
+
order: {{ expirationDate: DESC }}
|
|
260
|
+
where: {{
|
|
261
|
+
systemOwnerId: {{ eq: "{user["id"]}" }}
|
|
262
|
+
expirationDate: {{ lte: "{before_date}" }}
|
|
263
|
+
}}
|
|
264
|
+
) {{
|
|
265
|
+
items {{
|
|
266
|
+
uuid
|
|
267
|
+
id
|
|
268
|
+
systemName
|
|
269
|
+
systemOwnerId
|
|
270
|
+
status
|
|
271
|
+
systemType
|
|
272
|
+
expirationDate
|
|
273
|
+
overallCategorization
|
|
274
|
+
createdById
|
|
275
|
+
dateCreated
|
|
276
|
+
}}
|
|
277
|
+
totalCount
|
|
278
|
+
pageInfo {{
|
|
279
|
+
hasNextPage
|
|
280
|
+
}}
|
|
281
|
+
}}
|
|
282
|
+
workflowInstances(
|
|
283
|
+
take: 50
|
|
284
|
+
skip: 0
|
|
285
|
+
order: {{ startDate: DESC }}
|
|
286
|
+
where: {{
|
|
287
|
+
ownerId: {{ eq: "{user["id"]}" }}
|
|
288
|
+
status: {{ neq: "Complete" }}
|
|
289
|
+
startDate: {{ gte: "{after_date}" }}
|
|
290
|
+
endDate: {{ eq: null }}
|
|
291
|
+
}}
|
|
292
|
+
) {{
|
|
293
|
+
items {{
|
|
294
|
+
id
|
|
295
|
+
name
|
|
296
|
+
status
|
|
297
|
+
startDate
|
|
298
|
+
endDate
|
|
299
|
+
comments
|
|
300
|
+
currentStep
|
|
301
|
+
createdById
|
|
302
|
+
dateCreated
|
|
303
|
+
lastUpdatedById
|
|
304
|
+
ownerId
|
|
305
|
+
atlasModule
|
|
306
|
+
parentId
|
|
307
|
+
}}
|
|
308
|
+
totalCount
|
|
309
|
+
pageInfo {{
|
|
310
|
+
hasNextPage
|
|
311
|
+
}}
|
|
312
|
+
}}
|
|
313
|
+
tasks(
|
|
314
|
+
take: 50
|
|
315
|
+
skip: 0
|
|
316
|
+
order: {{ dueDate: DESC }}
|
|
317
|
+
where: {{
|
|
318
|
+
assignedToId: {{ eq: "{user["id"]}" }}
|
|
319
|
+
dueDate: {{ lte: "{before_date}" }}
|
|
320
|
+
status: {{ nin: ["Closed", "Cancelled"] }}
|
|
321
|
+
}}
|
|
322
|
+
) {{
|
|
323
|
+
items {{
|
|
324
|
+
uuid
|
|
325
|
+
id
|
|
326
|
+
title
|
|
327
|
+
assignedToId
|
|
328
|
+
dueDate
|
|
329
|
+
createdById
|
|
330
|
+
status
|
|
331
|
+
percentComplete
|
|
332
|
+
}}
|
|
333
|
+
totalCount
|
|
334
|
+
pageInfo {{
|
|
335
|
+
hasNextPage
|
|
336
|
+
}}
|
|
337
|
+
}}
|
|
338
|
+
issues(
|
|
339
|
+
take: 50
|
|
340
|
+
skip: 0
|
|
341
|
+
order: {{ dueDate: DESC }}
|
|
342
|
+
where: {{
|
|
343
|
+
issueOwnerId: {{ eq: "{user["id"]}" }}
|
|
344
|
+
dueDate: {{ lte: "{before_date}" }}
|
|
345
|
+
status: {{ nin: ["Closed", "Cancelled"] }}
|
|
346
|
+
}}
|
|
347
|
+
) {{
|
|
348
|
+
items {{
|
|
349
|
+
uuid
|
|
350
|
+
id
|
|
351
|
+
title
|
|
352
|
+
issueOwnerId
|
|
353
|
+
severityLevel
|
|
354
|
+
createdById
|
|
355
|
+
dateCreated
|
|
356
|
+
status
|
|
357
|
+
dueDate
|
|
358
|
+
}}
|
|
359
|
+
totalCount
|
|
360
|
+
pageInfo {{
|
|
361
|
+
hasNextPage
|
|
362
|
+
}}
|
|
363
|
+
}}
|
|
364
|
+
}}
|
|
365
|
+
"""
|
|
366
|
+
# get the data from GraphQL
|
|
367
|
+
res_data = self.api.graph(query=query)
|
|
368
|
+
|
|
369
|
+
# create list that has dictionaries of the user's pipeline and categories
|
|
370
|
+
pipelines = {
|
|
371
|
+
"Assessments": {"Pipeline": res_data["assessments"]["items"]},
|
|
372
|
+
"Issues": {"Pipeline": res_data["issues"]["items"]},
|
|
373
|
+
"Tasks": {"Pipeline": res_data["tasks"]["items"]},
|
|
374
|
+
"Data Calls": {"Pipeline": res_data["dataCalls"]["items"]},
|
|
375
|
+
"Security Plans": {"Pipeline": res_data["securityPlans"]["items"]},
|
|
376
|
+
"Workflow": {"Pipeline": res_data["workflowInstances"]["items"]},
|
|
377
|
+
}
|
|
378
|
+
# iterate through the user's pipeline tallying their items and check the amount
|
|
379
|
+
total_tasks = sum(len(pipeline["Pipeline"]) for pipeline in pipelines.values())
|
|
380
|
+
if total_tasks > 0:
|
|
381
|
+
# map and add the data to a self variable
|
|
382
|
+
return Pipeline(
|
|
383
|
+
email=user["email"],
|
|
384
|
+
fullName=f'{user["firstName"]} {user["lastName"]}',
|
|
385
|
+
pipelines=pipelines,
|
|
386
|
+
totalTasks=total_tasks,
|
|
387
|
+
)
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
# flake8: noqa: C901
|
|
391
|
+
def analyze_pipeline(self, args: Tuple, thread: int) -> None:
|
|
392
|
+
"""
|
|
393
|
+
Function to set up data tables from the user's pipeline while using threading
|
|
394
|
+
|
|
395
|
+
:param Tuple args: Tuple of args to use during the process
|
|
396
|
+
:param int thread: Thread number of current thread
|
|
397
|
+
:rtype: None
|
|
398
|
+
"""
|
|
399
|
+
import pandas as pd # Optimize import performance
|
|
400
|
+
|
|
401
|
+
config, task, users = args
|
|
402
|
+
|
|
403
|
+
id_fields = ["leadassessorid", "assignedtoid", "datacallleadid"]
|
|
404
|
+
|
|
405
|
+
# get the assigned threads
|
|
406
|
+
threads = thread_assignment(
|
|
407
|
+
thread=thread,
|
|
408
|
+
total_items=len(self.tenant_pipeline),
|
|
409
|
+
)
|
|
410
|
+
for i in range(len(threads)):
|
|
411
|
+
# get the pipeline from the self.tenant_pipeline
|
|
412
|
+
pipelines = self.tenant_pipeline[threads[i]].pipelines
|
|
413
|
+
|
|
414
|
+
# set up local variable for user pipeline
|
|
415
|
+
user_pipeline = []
|
|
416
|
+
|
|
417
|
+
# check if the user has already been analyzed
|
|
418
|
+
if not self.tenant_pipeline[threads[i]].analyzed:
|
|
419
|
+
# change the user's status to analyzed
|
|
420
|
+
self.tenant_pipeline[threads[i]].analyzed = True
|
|
421
|
+
|
|
422
|
+
# start out in the beginning of the pipelines
|
|
423
|
+
# and iterate through all of their items
|
|
424
|
+
for pipe in pipelines:
|
|
425
|
+
# creating variable to store html table for the user's email
|
|
426
|
+
prelim_pipeline = []
|
|
427
|
+
|
|
428
|
+
# iterate through the items in the pipeline category while
|
|
429
|
+
# creating legible table headers
|
|
430
|
+
for item in pipelines[pipe]["Pipeline"]:
|
|
431
|
+
# flatten the dict to remove nested dictionaries
|
|
432
|
+
item = flatten_dict(item)
|
|
433
|
+
|
|
434
|
+
# create list variable to store the renamed column names
|
|
435
|
+
headers = []
|
|
436
|
+
# iterate through all columns for the item and see if the header
|
|
437
|
+
# has to be changed to Title Case and if the data has to revalued
|
|
438
|
+
for key in item.keys():
|
|
439
|
+
# change the camelcase header to a Title Case Header
|
|
440
|
+
fixed_key = uncamel_case(key)
|
|
441
|
+
|
|
442
|
+
# check the keys to revalue the data accordingly
|
|
443
|
+
if key.lower() == "uuid" or (pipe.lower() == "workflow" and key.lower() == "id"):
|
|
444
|
+
# create html url using data for the html table
|
|
445
|
+
href = f'{config["domain"]}/form/{pipe.lower().replace(" ", "")}/{item["id"]}'
|
|
446
|
+
# have to add an if clause for mso to display the view button correctly
|
|
447
|
+
url = (
|
|
448
|
+
'<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"'
|
|
449
|
+
f'xmlns:w="urn:schemas-microsoft-com:office:word" href="{href}" '
|
|
450
|
+
'style="height:40px;v-text-anchor:middle;width:60px;" arcsize="5%" '
|
|
451
|
+
'strokecolor="#22C2DC" fillcolor="#1DC3EB"><w:anchorlock/><center'
|
|
452
|
+
' style="color:#ffffff;font-family:Roboto, Arial, sans-serif;font'
|
|
453
|
+
'-size:14px;">View</center></v:roundrect><![endif]-->'
|
|
454
|
+
)
|
|
455
|
+
url += f'<a href="{href}" style="mso-hide:all;">View</a>'
|
|
456
|
+
|
|
457
|
+
headers.append("Action")
|
|
458
|
+
if pipe.lower() == "workflow":
|
|
459
|
+
update_dict = {"UUID": url}
|
|
460
|
+
item = {**update_dict, **item}
|
|
461
|
+
headers.append("ID")
|
|
462
|
+
else:
|
|
463
|
+
# replace the UUID with the HTML url
|
|
464
|
+
item[key] = url
|
|
465
|
+
elif ("ById" in key or "ownerid" in key.lower() or key.lower() in id_fields) and item[key]:
|
|
466
|
+
# remove ById from the key
|
|
467
|
+
new_key = key.replace("Id", "")
|
|
468
|
+
|
|
469
|
+
# uncamel_case() the key
|
|
470
|
+
new_key = uncamel_case(new_key)
|
|
471
|
+
|
|
472
|
+
# replace the user id string with a user's name
|
|
473
|
+
user_id = item[key]
|
|
474
|
+
try:
|
|
475
|
+
# try to replace the ID with a user from all active users
|
|
476
|
+
item[key] = f'{users[user_id]["firstName"]} {users[user_id]["lastName"]}'
|
|
477
|
+
except KeyError:
|
|
478
|
+
# means the user is not activated, fetch them via API
|
|
479
|
+
user = User.get_user_by_id(user_id)
|
|
480
|
+
item[key] = f"{user.firstName} {user.lastName}"
|
|
481
|
+
# add the updated key to the table headers
|
|
482
|
+
headers.append(new_key)
|
|
483
|
+
elif key.lower() == "atlasmodule":
|
|
484
|
+
headers.append("Parent Module")
|
|
485
|
+
elif ("date" in key.lower() or "finish" in key.lower()) and item[key]:
|
|
486
|
+
try:
|
|
487
|
+
# convert string to a date & reformat the date to a legible string
|
|
488
|
+
item[key] = reformat_str_date(item[key], "%b %d, %Y")
|
|
489
|
+
except ValueError:
|
|
490
|
+
headers.append(fixed_key)
|
|
491
|
+
continue
|
|
492
|
+
# append the Title Case header to the headers list
|
|
493
|
+
headers.append(fixed_key)
|
|
494
|
+
elif key == "id":
|
|
495
|
+
# change the key to all uppercase
|
|
496
|
+
headers.append(key.upper())
|
|
497
|
+
elif isinstance(item[key], str) and "<" in item[key]:
|
|
498
|
+
# replace </br> with \n
|
|
499
|
+
text = item[key].replace("</br>", "\n")
|
|
500
|
+
|
|
501
|
+
# strip other html codes from string values
|
|
502
|
+
item[key] = re.sub("<[^<]+?>", "", text)
|
|
503
|
+
|
|
504
|
+
# append the Title Case header to headers
|
|
505
|
+
headers.append(fixed_key)
|
|
506
|
+
elif key.lower() == "currentstep":
|
|
507
|
+
item[key] += 1
|
|
508
|
+
headers.append(fixed_key)
|
|
509
|
+
elif key.lower() == "workflowinstancesteps":
|
|
510
|
+
del item[key]
|
|
511
|
+
else:
|
|
512
|
+
headers.append(fixed_key)
|
|
513
|
+
# add it to the final pipeline for the user
|
|
514
|
+
prelim_pipeline.append(item)
|
|
515
|
+
# check to see if there is an item for the bucket before
|
|
516
|
+
# appending it to the self.final_pipeline for the email
|
|
517
|
+
if len(prelim_pipeline) > 0:
|
|
518
|
+
# convert the item to a pandas data table
|
|
519
|
+
data = pd.DataFrame(prelim_pipeline)
|
|
520
|
+
|
|
521
|
+
# replace the columns with our legible data headers
|
|
522
|
+
data.columns = headers
|
|
523
|
+
|
|
524
|
+
# append the data item and bucket to our local user_pipeline list
|
|
525
|
+
user_pipeline.append({"bucket": pipe, "items": data})
|
|
526
|
+
# add the user's pipeline data to the self.pipeline for the emails
|
|
527
|
+
self.final_pipeline.append(
|
|
528
|
+
Pipeline(
|
|
529
|
+
email=self.tenant_pipeline[threads[i]].email,
|
|
530
|
+
fullName=self.tenant_pipeline[threads[i]].fullName,
|
|
531
|
+
pipelines=user_pipeline,
|
|
532
|
+
totalTasks=self.tenant_pipeline[threads[i]].totalTasks,
|
|
533
|
+
analyzed=True,
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
self.job_progress.update(task, advance=1)
|
|
537
|
+
|
|
538
|
+
def format_and_email(self, args: Tuple, thread: int) -> None:
|
|
539
|
+
"""
|
|
540
|
+
Function to email all users with an HTML formatted email
|
|
541
|
+
|
|
542
|
+
:param Tuple args: Tuple of args to use during the process
|
|
543
|
+
:param int thread: Thread number of current thread
|
|
544
|
+
:rtype: None
|
|
545
|
+
"""
|
|
546
|
+
# set up my args from the args tuple
|
|
547
|
+
import pandas as pd # Optimize import performance
|
|
548
|
+
|
|
549
|
+
api, config, task = args
|
|
550
|
+
|
|
551
|
+
threads = thread_assignment(
|
|
552
|
+
thread=thread,
|
|
553
|
+
total_items=len(self.final_pipeline),
|
|
554
|
+
)
|
|
555
|
+
|
|
556
|
+
# update api pool limits to max_thread count from init.yaml
|
|
557
|
+
api.pool_connections = config["maxThreads"]
|
|
558
|
+
api.pool_maxsize = config["maxThreads"]
|
|
559
|
+
|
|
560
|
+
# get assigned threads
|
|
561
|
+
for i in range(len(threads)):
|
|
562
|
+
# get the user's pipeline details
|
|
563
|
+
email = self.final_pipeline[threads[i]].email
|
|
564
|
+
total_tasks = self.final_pipeline[threads[i]].totalTasks
|
|
565
|
+
|
|
566
|
+
# create list to store the html tables
|
|
567
|
+
tables = []
|
|
568
|
+
|
|
569
|
+
# see if the user has been emailed already
|
|
570
|
+
if not self.final_pipeline[threads[i]].emailed:
|
|
571
|
+
# set the emailed flag to true
|
|
572
|
+
self.final_pipeline[threads[i]].emailed = True
|
|
573
|
+
|
|
574
|
+
# iterate through all items in self.final_pipeline to
|
|
575
|
+
# set up data tables as a html tables using pandas
|
|
576
|
+
for item in self.final_pipeline[threads[i]].pipelines:
|
|
577
|
+
tables.extend(
|
|
578
|
+
(
|
|
579
|
+
f'<h1>{item["bucket"]}</h1>',
|
|
580
|
+
item["items"].to_html(justify="left", index=False).replace('border="1"', 'border="0"'),
|
|
581
|
+
)
|
|
582
|
+
)
|
|
583
|
+
# join all the items in tables and separate them all with a </br> tag
|
|
584
|
+
tables = "</br>".join(tables)
|
|
585
|
+
|
|
586
|
+
# fix any broken html tags
|
|
587
|
+
tables = tables.replace("&", "&").replace(">", ">").replace("<", "<").replace("’", "'")
|
|
588
|
+
|
|
589
|
+
# create email payload
|
|
590
|
+
email_notification = Email(
|
|
591
|
+
fromEmail="Support@RegScale.com",
|
|
592
|
+
emailSenderId=config["userId"],
|
|
593
|
+
to=email,
|
|
594
|
+
subject=f"RegScale Reminder: {total_tasks} Upcoming Items",
|
|
595
|
+
body=get_css("email_style.css") + tables,
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
# send the email and get the response
|
|
599
|
+
if email_notification.send():
|
|
600
|
+
emailed = True
|
|
601
|
+
else:
|
|
602
|
+
emailed = False
|
|
603
|
+
|
|
604
|
+
# set up dict to use for pandas data
|
|
605
|
+
data = {
|
|
606
|
+
"Email Address": '<!--[if mso]><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml"'
|
|
607
|
+
'xmlns:w="urn:schemas-microsoft-com:office:word" href="mailto:'
|
|
608
|
+
f'{email}"style="height:auto;v-text-anchor:middle;mso-width-'
|
|
609
|
+
'percent:150;" arcsize="5%" strokecolor="#22C2DC" fillcolor='
|
|
610
|
+
'"#1DC3EB"><w:anchorlock/><center style="color:#ffffff;font-'
|
|
611
|
+
f'family:Roboto, Arial, sans-serif;font-size:14px;">{email}'
|
|
612
|
+
'</center></v:roundrect><![endif]--><a href="mailto:'
|
|
613
|
+
f'{email}" style="mso-hide:all;">{email}</a>',
|
|
614
|
+
"User Name": self.final_pipeline[threads[i]].fullName,
|
|
615
|
+
"Total Tasks": total_tasks,
|
|
616
|
+
"Emailed": emailed,
|
|
617
|
+
}
|
|
618
|
+
table = pd.DataFrame([data])
|
|
619
|
+
self.emails.append(table)
|
|
620
|
+
self.job_progress.update(task, advance=1)
|