tcex 4.0.11.dev2__tar.gz → 4.0.11.dev4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {tcex-4.0.11.dev2/tcex.egg-info → tcex-4.0.11.dev4}/PKG-INFO +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/pyproject.toml +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/__metadata__.py +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/__init__.py +2 -1
- tcex-4.0.11.dev4/tcex/api/tc/v2/batch/batch_cleaner.py +451 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch_submit.py +50 -131
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_object_abc.py +47 -4
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type.py +10 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case.py +10 -2
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group_filter.py +10 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement.py +3 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role.py +10 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner.py +8 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role.py +10 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group.py +10 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user.py +10 -0
- tcex-4.0.11.dev4/tcex/api/tc/v3/tags/mitre_tags.py +160 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/naics_tags.py +11 -3
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task.py +10 -2
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/threat_intelligence/threat_intelligence.py +6 -18
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/app_spec_yml.py +6 -2
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json.py +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json_update.py +3 -3
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/install_json_validate.py +2 -2
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/app_spec_yml_model.py +13 -7
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/install_json_model.py +18 -18
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/job_json_model.py +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/permutation.py +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/mqtt_message_broker.py +4 -6
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/input.py +19 -0
- tcex-4.0.11.dev4/tcex/pleb/cached_property_filesystem.py +276 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/code_operation.py +18 -5
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4/tcex.egg-info}/PKG-INFO +1 -1
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/SOURCES.txt +2 -0
- tcex-4.0.11.dev2/tcex/api/tc/v3/tags/mitre_tags.py +0 -79
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/LICENSE +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/README.md +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/setup.cfg +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/api.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/tc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/model/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/model/transform_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/ti_predefined_functions.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/ti_transform.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/ti_transform/transform_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/util/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/util/threat_intel_util.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/association.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/attribute.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/batch_writer.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/group.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/indicator.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/security_label.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/batch/tag.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/cache.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/datastore/datastore.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/metric/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/metric/metric.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/notification/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/notification/notification.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/filters.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/adversary.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/attack_pattern.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/campaign.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/course_of_action.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/document.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/email.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/event.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/incident.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/intrusion_set.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/malware.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/report.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/signature.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tactic.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/threat.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/tool.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/group/group_type/vulnerability.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/address.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/email_address.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/file.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/host.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/indicator/indicator_type/url.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/mapping.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/owner.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/security_label.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/tag.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/tags.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/task.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/mapping/victim.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/tcex_ti_tc_request.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/threat_intelligence/threat_intelligence.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v2/v2.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_args_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_filter_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_gen_model_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_data_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/_options_data_object.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/_filter_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/_gen/model/_property_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/adversary_assets/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/api_endpoints.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifact_types/artifact_type_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/artifacts/artifact_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attribute_types/attribute_type_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attributes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/attributes/attribute_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_attributes/case_attribute_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_management/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/case_management/case_management.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/cases/case_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_actions/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_actions/file_action_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_occurrences/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/file_occurrences/file_occurrence_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/filter_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/group_attributes/group_attribute_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/groups/group_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicator_attributes/indicator_attribute_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/indicators/indicator_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirement/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirement/ir.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/categories/category_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_req_type_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/intel_requirement_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/keyword_sections/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/keyword_sections/keyword_section_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/results/result_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/intel_requirements/subtypes/subtype_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/notes/note_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/object_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/object_collection_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_user_group_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/assignee_user_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/exclusion_lists/exclusion_list_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owner_roles/owner_role_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/owners/owner_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/security.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/system_roles/system_role_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/task_assignee_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/user_groups/user_group_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security/users/user_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/security_labels/security_label_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/default_naics_tags.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tags/tag_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tasks/task_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/threat_intelligence/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql_operator.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/tql/tql_type.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3_model_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/v3_types.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_assets/victim_asset_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victim_attributes/victim_attribute_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/victims/victim_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_events/workflow_event_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/api/tc/v3/workflow_templates/workflow_template_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/app.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/job_json.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/layout_json.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/layout_json_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/model/tcex_json_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/tcex_json.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/config/tcex_json_update.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/benchmark.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/debug.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/fail_on_output.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/on_exception.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/on_success.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/decorator/output.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_abc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_api.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_mock.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_redis.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/key_value_store.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/redis_client.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/key_value_store/redis_client_ssl.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/advanced_request.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_create.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_delete.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_output.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/playbook/playbook_read.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/api_service.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/common_service.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/common_service_trigger.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/service/webhook_trigger_service.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/token/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/app/token/token.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/error_code.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/exit/exit.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/binary.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/case_management_entity.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/choice.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/datetime.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/edit_choice.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/exception.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/group_entity.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/indicator_entity.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/integer.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/ip_address.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/key_value.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/sensitive.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/string.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/tc_entity.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/field_type/validator.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/advanced_request_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/api_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_api_service_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_external_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_feed_api_service_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_organization_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_playbook_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_system_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_trigger_service_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/app_webhook_trigger_service_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/batch_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/cal_setting_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/cert_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/common_advanced_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/common_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/create_config_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/logging_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/model_map.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/module_app_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/module_requests_session_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/organization_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/path_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/playbook_common_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/playbook_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/proxy_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/service_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/input/model/smtp_setting_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/api_handler.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/cache_handler.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/logger.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/rotating_file_handler_custom.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/sensitive_filter.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/logger/trace_logger.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/cached_property.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/event.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/exception_thread.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/jmespath_custom.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/none_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/proxies.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/scoped_property.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/pleb/singleton.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/registry.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/external_session.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/rate_limit_handler.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_external/requests_external.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/hmac_auth.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/tc_auth.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/auth/token_auth.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/requests_tc.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/requests_tc/tc_session.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/tcex.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/aes_operation.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/datetime_operation.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/file_operation.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/model/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/model/playbook_variable_model.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/__init__.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_panel.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_prompt.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/render/render_table.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/requests_to_curl.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/string_operation.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/util.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex/util/variable.py +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/dependency_links.txt +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/requires.txt +0 -0
- {tcex-4.0.11.dev2 → tcex-4.0.11.dev4}/tcex.egg-info/top_level.txt +0 -0
|
@@ -108,7 +108,7 @@ commit = false
|
|
|
108
108
|
# Additional arguments to pass to the git commit command
|
|
109
109
|
commit_args = "--no-verify"
|
|
110
110
|
# The current version of the project
|
|
111
|
-
current_version = "4.0.11-
|
|
111
|
+
current_version = "4.0.11-dev4"
|
|
112
112
|
# The commit message template used when committing version bumps
|
|
113
113
|
message = "Version updated from {current_version} to {new_version}"
|
|
114
114
|
# Regular expression for parsing version strings
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
# first-party
|
|
4
4
|
from tcex.api.tc.v2.batch.batch import Batch
|
|
5
|
+
from tcex.api.tc.v2.batch.batch_cleaner import BatchCleaner
|
|
5
6
|
from tcex.api.tc.v2.batch.batch_submit import BatchSubmit
|
|
6
7
|
from tcex.api.tc.v2.batch.batch_writer import BatchWriter, GroupType, IndicatorType
|
|
7
8
|
|
|
8
|
-
__all__ = ['Batch', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
|
|
9
|
+
__all__ = ['Batch', 'BatchCleaner', 'BatchSubmit', 'BatchWriter', 'GroupType', 'IndicatorType']
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""TcEx Framework Module"""
|
|
2
|
+
|
|
3
|
+
# standard library
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from itertools import chain
|
|
8
|
+
|
|
9
|
+
# first-party
|
|
10
|
+
from tcex.api.tc.v3.tags.mitre_tags import MitreTags
|
|
11
|
+
from tcex.api.tc.v3.tags.naics_tags import NAICSTags
|
|
12
|
+
from tcex.logger.trace_logger import TraceLogger
|
|
13
|
+
from tcex.pleb.cached_property import cached_property
|
|
14
|
+
from tcex.util.datetime_operation import DatetimeOperation
|
|
15
|
+
|
|
16
|
+
# get tcex logger
|
|
17
|
+
_logger: TraceLogger = logging.getLogger(__name__.split('.', maxsplit=1)[0]) # type: ignore
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BatchCleaner:
|
|
21
|
+
"""Batch content cleaning configuration and logic.
|
|
22
|
+
|
|
23
|
+
All flags default to False. Enable the desired cleaning steps before calling clean().
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
attribute_types: dict[str, dict],
|
|
29
|
+
mitre_tags: MitreTags,
|
|
30
|
+
*,
|
|
31
|
+
combine_on_filename: bool = False,
|
|
32
|
+
convert_to_mitre_tags: bool = False,
|
|
33
|
+
convert_to_naics_tags: bool = False,
|
|
34
|
+
deduplicate_indicators: bool = False,
|
|
35
|
+
deduplicate_groups: bool = False,
|
|
36
|
+
deduplicate_attributes: bool = False,
|
|
37
|
+
truncate_attributes: bool = False,
|
|
38
|
+
):
|
|
39
|
+
"""Initialize instance properties.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
attribute_types: A dictionary of attribute type config.
|
|
43
|
+
mitre_tags: A MitreTags instance.
|
|
44
|
+
combine_on_filename: Whether to combine items based on filename.
|
|
45
|
+
convert_to_mitre_tags: Whether to convert Mitre tags.
|
|
46
|
+
convert_to_naics_tags: Whether to convert NAICS tags.
|
|
47
|
+
deduplicate_indicators: Whether to deduplicate indicators.
|
|
48
|
+
deduplicate_groups: Whether to deduplicate groups.
|
|
49
|
+
deduplicate_attributes: Whether to deduplicate attributes.
|
|
50
|
+
truncate_attributes: Whether to truncate attributes.
|
|
51
|
+
"""
|
|
52
|
+
self.attribute_types = attribute_types
|
|
53
|
+
self.mitre_tags = mitre_tags
|
|
54
|
+
|
|
55
|
+
self.combine_on_filename = combine_on_filename
|
|
56
|
+
self.convert_to_mitre_tags = convert_to_mitre_tags
|
|
57
|
+
self.convert_to_naics_tags = convert_to_naics_tags
|
|
58
|
+
self.deduplicate_indicators = deduplicate_indicators
|
|
59
|
+
self.deduplicate_groups = deduplicate_groups
|
|
60
|
+
self.deduplicate_attributes = deduplicate_attributes
|
|
61
|
+
self.truncate_attributes = truncate_attributes
|
|
62
|
+
|
|
63
|
+
def clean(self, content: dict | str) -> dict:
|
|
64
|
+
"""Run enabled cleaning steps on content.
|
|
65
|
+
|
|
66
|
+
Pass 1: Deduplicate and combine indicators/groups.
|
|
67
|
+
Pass 2: Clean attributes (truncate/deduplicate) on the result.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
content: The batch content dictionary or JSON string.
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
The cleaned content dictionary.
|
|
74
|
+
"""
|
|
75
|
+
content_dict = json.loads(content) if isinstance(content, str) else content
|
|
76
|
+
|
|
77
|
+
# Pass 1: dedup/combine
|
|
78
|
+
content_dict['indicator'] = self._dedup_indicators(content_dict.get('indicator', []))
|
|
79
|
+
if self.deduplicate_groups:
|
|
80
|
+
content_dict['group'] = self._dedup(content_dict.get('group', []), self._group_keys)
|
|
81
|
+
|
|
82
|
+
# Pass 2: attribute cleaning
|
|
83
|
+
if self.deduplicate_attributes or self.truncate_attributes:
|
|
84
|
+
truncated_types: set[str] = set()
|
|
85
|
+
for item in chain(
|
|
86
|
+
content_dict.get('indicator') or [],
|
|
87
|
+
content_dict.get('group') or [],
|
|
88
|
+
):
|
|
89
|
+
self.clean_attributes(
|
|
90
|
+
item,
|
|
91
|
+
self.attribute_types,
|
|
92
|
+
deduplicate=self.deduplicate_attributes,
|
|
93
|
+
truncate=self.truncate_attributes,
|
|
94
|
+
truncated_types=truncated_types,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return content_dict
|
|
98
|
+
|
|
99
|
+
def _dedup_indicators(self, indicators: list[dict]) -> list[dict]:
|
|
100
|
+
"""Deduplicate and combine indicators.
|
|
101
|
+
|
|
102
|
+
When deduplicate_indicators is enabled, indicators are matched by
|
|
103
|
+
summary (hash-overlap for File indicators, exact match for others).
|
|
104
|
+
|
|
105
|
+
When combine_on_filename is enabled, File indicators with matching
|
|
106
|
+
fileOccurrence fileName values are also merged.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
indicators: The list of indicator dictionaries.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The deduplicated list of indicator dictionaries.
|
|
113
|
+
"""
|
|
114
|
+
if not self.deduplicate_indicators and not self.combine_on_filename:
|
|
115
|
+
return indicators
|
|
116
|
+
|
|
117
|
+
return self._dedup(indicators, self._indicator_keys)
|
|
118
|
+
|
|
119
|
+
def _dedup(
|
|
120
|
+
self,
|
|
121
|
+
items: list[dict],
|
|
122
|
+
key_fn: Callable[[dict], set[str]],
|
|
123
|
+
) -> list[dict]:
|
|
124
|
+
"""Deduplicate items using a key function that returns a set of lookup keys.
|
|
125
|
+
|
|
126
|
+
Each item is matched against previously seen keys. If any key from the
|
|
127
|
+
incoming item is already in the index, the item is merged into the
|
|
128
|
+
existing one. After a merge, keys are re-indexed to account for new
|
|
129
|
+
keys added during the merge (e.g. File hash combination).
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
items: The list of items to deduplicate.
|
|
133
|
+
key_fn: A callable that takes an item dict and returns a set of
|
|
134
|
+
strings to use as lookup keys. Return an empty set for items
|
|
135
|
+
that should never be deduplicated.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
The deduplicated list of items.
|
|
139
|
+
"""
|
|
140
|
+
result: list[dict] = []
|
|
141
|
+
seen: dict[str, int] = {}
|
|
142
|
+
|
|
143
|
+
for item in items:
|
|
144
|
+
keys = key_fn(item)
|
|
145
|
+
match_idx = next((seen[k] for k in keys if k in seen), None)
|
|
146
|
+
if match_idx is not None:
|
|
147
|
+
self.deduplicate(item, result[match_idx])
|
|
148
|
+
# re-index: merged item may have new keys (e.g. combined file hashes)
|
|
149
|
+
for k in key_fn(result[match_idx]):
|
|
150
|
+
seen[k] = match_idx
|
|
151
|
+
else:
|
|
152
|
+
idx = len(result)
|
|
153
|
+
for k in keys:
|
|
154
|
+
seen[k] = idx
|
|
155
|
+
result.append(item)
|
|
156
|
+
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
def _indicator_keys(self, item: dict) -> set[str]:
|
|
160
|
+
"""Return the set of dedup keys for an indicator.
|
|
161
|
+
|
|
162
|
+
File indicators return individual hashes so that any hash overlap
|
|
163
|
+
triggers a merge. When combine_on_filename is enabled, a prefixed
|
|
164
|
+
filename key is also included for File indicators with a fileOccurrence.
|
|
165
|
+
|
|
166
|
+
All other indicators return the summary as a single-element set.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
item: An indicator dictionary.
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
A set of key strings for dedup lookup.
|
|
173
|
+
"""
|
|
174
|
+
if item.get('type') == 'File':
|
|
175
|
+
keys: set[str] = set()
|
|
176
|
+
if self.deduplicate_indicators:
|
|
177
|
+
keys = BatchCleaner._file_hash_set(item.get('summary', ''))
|
|
178
|
+
if self.combine_on_filename:
|
|
179
|
+
file_occ = item.get('fileOccurrence') or []
|
|
180
|
+
if file_occ:
|
|
181
|
+
fname = file_occ[0].get('fileName')
|
|
182
|
+
if fname:
|
|
183
|
+
keys.add(f'filename:{fname.lower()}')
|
|
184
|
+
return keys
|
|
185
|
+
if self.deduplicate_indicators:
|
|
186
|
+
summary = item.get('summary')
|
|
187
|
+
return {summary} if summary else set()
|
|
188
|
+
return set()
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _group_keys(item: dict) -> set[str]:
|
|
192
|
+
"""Return the set of dedup keys for a group.
|
|
193
|
+
|
|
194
|
+
Groups are deduplicated by xid.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
item: A group dictionary.
|
|
198
|
+
|
|
199
|
+
Returns:
|
|
200
|
+
A single-element set containing the xid, or empty set if no xid.
|
|
201
|
+
"""
|
|
202
|
+
xid = item.get('xid')
|
|
203
|
+
return {xid} if xid else set()
|
|
204
|
+
|
|
205
|
+
def _normalize_tags(self, tags: list) -> None:
|
|
206
|
+
"""Convert tag names to formatted MITRE/NAICS format where applicable.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
tags: A list of tags
|
|
210
|
+
"""
|
|
211
|
+
for tag in tags:
|
|
212
|
+
name = tag.get('name')
|
|
213
|
+
if not name:
|
|
214
|
+
continue
|
|
215
|
+
|
|
216
|
+
# try MITRE conversion first
|
|
217
|
+
if self.convert_to_mitre_tags:
|
|
218
|
+
formatted = self.mitre_tags.get_by_id_regex(name)
|
|
219
|
+
if formatted:
|
|
220
|
+
tag['name'] = formatted
|
|
221
|
+
continue
|
|
222
|
+
|
|
223
|
+
# try NAICS conversion
|
|
224
|
+
if self.convert_to_naics_tags:
|
|
225
|
+
formatted = self.naics_tags.get_by_id(name)
|
|
226
|
+
if formatted:
|
|
227
|
+
tag['name'] = formatted
|
|
228
|
+
|
|
229
|
+
@staticmethod
|
|
230
|
+
def auto_truncate_attribute(
|
|
231
|
+
attribute_type: str,
|
|
232
|
+
attribute_value: str,
|
|
233
|
+
attribute_config: dict,
|
|
234
|
+
ellipsis: str = '...',
|
|
235
|
+
) -> str:
|
|
236
|
+
"""Truncate attribute value if it exceeds the maximum length for its type.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
attribute_type: The name of the attribute type.
|
|
240
|
+
attribute_value: The attribute value to potentially truncate.
|
|
241
|
+
attribute_config: Attribute type config dict keyed by name.
|
|
242
|
+
ellipsis: The string to append when truncating. Defaults to '...'.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
The original value if within limits, or the truncated value with ellipsis.
|
|
246
|
+
"""
|
|
247
|
+
config = attribute_config.get(attribute_type)
|
|
248
|
+
if not config:
|
|
249
|
+
return attribute_value
|
|
250
|
+
|
|
251
|
+
max_length = config.get('maxSize')
|
|
252
|
+
if not max_length:
|
|
253
|
+
return attribute_value
|
|
254
|
+
|
|
255
|
+
if not isinstance(attribute_value, str):
|
|
256
|
+
return attribute_value
|
|
257
|
+
|
|
258
|
+
if len(attribute_value) <= max_length:
|
|
259
|
+
return attribute_value
|
|
260
|
+
|
|
261
|
+
if max_length <= len(ellipsis):
|
|
262
|
+
return attribute_value[:max_length]
|
|
263
|
+
|
|
264
|
+
return attribute_value[: max_length - len(ellipsis)] + ellipsis
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def clean_attributes(
|
|
268
|
+
item: dict,
|
|
269
|
+
attribute_types: dict,
|
|
270
|
+
deduplicate: bool = False,
|
|
271
|
+
truncate: bool = False,
|
|
272
|
+
truncated_types: set[str] | None = None,
|
|
273
|
+
) -> dict:
|
|
274
|
+
"""Truncate and/or deduplicate attributes on a single item.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
item: A single group or indicator dictionary.
|
|
278
|
+
attribute_types: Attribute type config dict keyed by name.
|
|
279
|
+
deduplicate: Whether to remove duplicate attributes.
|
|
280
|
+
truncate: Whether to truncate attributes based on maxSize.
|
|
281
|
+
truncated_types: A shared set tracking attribute types that have already
|
|
282
|
+
been logged as truncated, to avoid duplicate log warnings. If None,
|
|
283
|
+
warnings are logged for every truncation.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
The item with cleaned attributes.
|
|
287
|
+
"""
|
|
288
|
+
original_attrs = item.get('attribute') or []
|
|
289
|
+
cleaned_attrs = []
|
|
290
|
+
seen: set[tuple] = set()
|
|
291
|
+
|
|
292
|
+
for attr in original_attrs:
|
|
293
|
+
type_ = attr.get('type')
|
|
294
|
+
value = attr.get('value')
|
|
295
|
+
|
|
296
|
+
if not type_ or value is None or value == '':
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
if truncate:
|
|
300
|
+
truncated = BatchCleaner.auto_truncate_attribute(type_, value, attribute_types)
|
|
301
|
+
if truncated != value and (truncated_types is None or type_ not in truncated_types):
|
|
302
|
+
_logger.warning(
|
|
303
|
+
f'feature=batch, event=attribute-truncated, attribute-type={type_}'
|
|
304
|
+
)
|
|
305
|
+
if truncated_types is not None:
|
|
306
|
+
truncated_types.add(type_)
|
|
307
|
+
else:
|
|
308
|
+
truncated = value
|
|
309
|
+
|
|
310
|
+
if deduplicate:
|
|
311
|
+
normalized = tuple(
|
|
312
|
+
(k, truncated if k == 'value' else attr.get(k)) for k in sorted(attr.keys())
|
|
313
|
+
)
|
|
314
|
+
if normalized in seen:
|
|
315
|
+
continue
|
|
316
|
+
seen.add(normalized)
|
|
317
|
+
|
|
318
|
+
new_attr = dict(attr)
|
|
319
|
+
new_attr['value'] = truncated
|
|
320
|
+
cleaned_attrs.append(new_attr)
|
|
321
|
+
|
|
322
|
+
item['attribute'] = cleaned_attrs
|
|
323
|
+
return item
|
|
324
|
+
|
|
325
|
+
@staticmethod
|
|
326
|
+
def merge_list_field(existing: dict, incoming: dict, field: str) -> None:
|
|
327
|
+
"""Merge a list-of-dicts field from incoming into existing, removing duplicates.
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
existing: The target item (modified in place).
|
|
331
|
+
incoming: The source item to merge from.
|
|
332
|
+
field: The field name (e.g. 'tag', 'securityLabel').
|
|
333
|
+
"""
|
|
334
|
+
incoming_items = incoming.get(field) or []
|
|
335
|
+
if not incoming_items:
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
existing_items = existing.setdefault(field, [])
|
|
339
|
+
seen = {frozenset(item.items()) for item in existing_items}
|
|
340
|
+
for item in incoming_items:
|
|
341
|
+
key = frozenset(item.items())
|
|
342
|
+
if key not in seen:
|
|
343
|
+
seen.add(key)
|
|
344
|
+
existing_items.append(item)
|
|
345
|
+
|
|
346
|
+
def deduplicate(self, incoming: dict, existing: dict) -> dict:
|
|
347
|
+
"""Merge incoming item into existing.
|
|
348
|
+
|
|
349
|
+
For File indicators, summaries are combined via hash merging.
|
|
350
|
+
|
|
351
|
+
Args:
|
|
352
|
+
incoming: The new item.
|
|
353
|
+
existing: The previously seen item to merge into.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
The merged item (same reference as existing).
|
|
357
|
+
"""
|
|
358
|
+
# merge File hash summaries
|
|
359
|
+
if existing.get('type') == 'File' and incoming.get('type') == 'File':
|
|
360
|
+
existing['summary'] = BatchCleaner.combine_file_hashes(
|
|
361
|
+
existing.get('summary', ''), incoming.get('summary', '')
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
# merge associated group xids
|
|
365
|
+
incoming_xids = incoming.get('associatedGroupXid') or []
|
|
366
|
+
if incoming_xids:
|
|
367
|
+
existing_xids = existing.get('associatedGroupXid') or []
|
|
368
|
+
existing['associatedGroupXid'] = list(set(existing_xids + incoming_xids))
|
|
369
|
+
|
|
370
|
+
# normalize existing tags
|
|
371
|
+
existing_tags = existing.get('tag') or []
|
|
372
|
+
if existing_tags:
|
|
373
|
+
self._normalize_tags(existing_tags)
|
|
374
|
+
|
|
375
|
+
# normalize incoming tags
|
|
376
|
+
incoming_tags = incoming.get('tag') or []
|
|
377
|
+
if incoming_tags:
|
|
378
|
+
self._normalize_tags(incoming_tags)
|
|
379
|
+
|
|
380
|
+
# merge metadata
|
|
381
|
+
BatchCleaner.merge_list_field(existing, incoming, 'associatedGroups')
|
|
382
|
+
BatchCleaner.merge_list_field(existing, incoming, 'fileOccurrence')
|
|
383
|
+
BatchCleaner.merge_list_field(existing, incoming, 'securityLabel')
|
|
384
|
+
BatchCleaner.merge_list_field(existing, incoming, 'tag')
|
|
385
|
+
|
|
386
|
+
# keep highest rating/confidence
|
|
387
|
+
for field in ('rating', 'confidence'):
|
|
388
|
+
if field in incoming:
|
|
389
|
+
existing[field] = max(existing.get(field, 0), incoming.get(field, 0))
|
|
390
|
+
|
|
391
|
+
# keep earliest externalDateAdded and latest externalLastModified
|
|
392
|
+
to_dt = DatetimeOperation.any_to_datetime
|
|
393
|
+
date_merge_rules = (
|
|
394
|
+
('externalDateAdded', min),
|
|
395
|
+
('externalDateLastModified', max),
|
|
396
|
+
)
|
|
397
|
+
for field, resolve in date_merge_rules:
|
|
398
|
+
if field in incoming:
|
|
399
|
+
existing_val = existing.get(field, incoming[field])
|
|
400
|
+
try:
|
|
401
|
+
existing[field] = resolve(existing_val, incoming[field], key=to_dt)
|
|
402
|
+
except Exception:
|
|
403
|
+
_logger.exception(f'Invalid date specified for {field}')
|
|
404
|
+
|
|
405
|
+
# combine attributes and later the cleaning pass will dedup/truncate
|
|
406
|
+
incoming_attrs = incoming.get('attribute') or []
|
|
407
|
+
if incoming_attrs:
|
|
408
|
+
existing['attribute'] = (existing.get('attribute') or []) + incoming_attrs
|
|
409
|
+
|
|
410
|
+
return existing
|
|
411
|
+
|
|
412
|
+
@staticmethod
|
|
413
|
+
def combine_file_hashes(existing_summary: str, new_summary: str) -> str:
|
|
414
|
+
"""Combine two File indicator summaries into a single multi-hash summary.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
existing_summary: The summary from the already-collected indicator.
|
|
418
|
+
new_summary: The summary from the incoming indicator.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
A combined summary with hashes in md5 : sha1 : sha256 order.
|
|
422
|
+
"""
|
|
423
|
+
hash_type_by_len = {32: 'md5', 40: 'sha1', 64: 'sha256'}
|
|
424
|
+
|
|
425
|
+
all_hashes = [h.strip() for h in new_summary.split(' : ')]
|
|
426
|
+
all_hashes.extend(h.strip() for h in existing_summary.split(' : '))
|
|
427
|
+
|
|
428
|
+
hash_values: dict[str, str] = {}
|
|
429
|
+
for hash_value in all_hashes:
|
|
430
|
+
hash_type = hash_type_by_len.get(len(hash_value))
|
|
431
|
+
if hash_type:
|
|
432
|
+
hash_values[hash_type] = hash_value
|
|
433
|
+
|
|
434
|
+
return ' : '.join(hash_values[ht] for ht in ['md5', 'sha1', 'sha256'] if ht in hash_values)
|
|
435
|
+
|
|
436
|
+
@staticmethod
|
|
437
|
+
def _file_hash_set(summary: str) -> set[str]:
|
|
438
|
+
"""Extract individual hashes from a File indicator summary.
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
summary: A File indicator summary string (e.g. 'md5 : sha1 : sha256').
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
A set of individual hash strings with whitespace stripped.
|
|
445
|
+
"""
|
|
446
|
+
return {h.strip() for h in summary.split(' : ') if h.strip()}
|
|
447
|
+
|
|
448
|
+
@cached_property
|
|
449
|
+
def naics_tags(self) -> NAICSTags:
|
|
450
|
+
"""NAICS Tags"""
|
|
451
|
+
return NAICSTags()
|