howler-api 4.1.0.dev1435__tar.gz → 4.1.0.dev1452__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.
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/PKG-INFO +1 -1
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/utils/params.py +13 -8
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/action_queue_worker.py +3 -3
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/base.py +92 -20
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/case.py +10 -2
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/howler_data.py +8 -1
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/record.py +11 -3
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/action_service.py +22 -16
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/pyproject.toml +1 -1
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/add_label.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/add_to_bundle.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/add_to_case.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/change_field.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/demote.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/prioritization.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/promote.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/remove_from_bundle.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/remove_label.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/actions/transition.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/base.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/socket.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/action.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/analytic.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/auth.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/clue.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/configs.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/help.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/hit.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/overview.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/search.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/template.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/tool.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/user.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/utils/etag.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v1/view.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v2/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v2/case.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v2/fuzzy.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v2/ingest.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/api/v2/search.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/app.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/classification.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/classification.yml +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/exceptions.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/loader.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/logging/audit.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/logging/format.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/net.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/net_static.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/random_user.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/common/swagger.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/config.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/correlation.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/retention.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/bulk.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/collection.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/constants.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/howler_store.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/operations.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/schemas.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/store.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/support/build.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/types.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/error.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/check_duplicates.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/reindex_data.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/gunicorn_config.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/healthz.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/azure.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/discover.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/hit.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/oauth.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/search.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/workflow.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/helper/ws.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/charter.txt +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/constants.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/helper.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/mixins.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/action.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/aws.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/azure.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/clue.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/config.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/event.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/hit.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/lead.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/overview.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/template.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/user.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/models/view.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/random_data.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/odm/randomizer.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/patched.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/plugins/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/plugins/config.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/security/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/security/socket.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/security/utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/analytic_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/auth_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/bundle_compat_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/case_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/comms_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/config_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/correlation_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/docs_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/dossier_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/event_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/fuzzy_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/hit_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/jwt_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/lucene_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/notebook_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/overview_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/search_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/template_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/user_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/services/viewer_service.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/telemetry.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/__init__.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/annotations.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/chunk.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/compat.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/constants.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/indexes.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/isotime.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/list_utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/lucene.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/net_utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/path.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/socket_utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/str_utils.py +0 -0
- {howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/utils/uid.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Parameter parsing and error handling reused across multiple endpoints."""
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
|
-
from typing import Callable, Literal
|
|
4
|
+
from typing import Any, Callable, Literal
|
|
5
5
|
|
|
6
6
|
from flask import request
|
|
7
7
|
|
|
@@ -10,8 +10,10 @@ from howler.common.exceptions import HowlerInvalidParameterException
|
|
|
10
10
|
|
|
11
11
|
REFRESH_ARG_OPTIONS = ["true", "false", "wait_for"]
|
|
12
12
|
|
|
13
|
+
parser_t = Callable[[str | None], Any]
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
|
|
16
|
+
def parse_parameters(**requested_params: parser_t | Literal["required"] | tuple[parser_t, Literal["required"]] | None):
|
|
15
17
|
"""A decorator to parse required parameters from the request args.
|
|
16
18
|
|
|
17
19
|
Usage:
|
|
@@ -32,6 +34,9 @@ def parse_parameters(**requested_params: Callable[[str | None], str | None] | Li
|
|
|
32
34
|
and the raw string value from the request args will be passed to the endpoint function.
|
|
33
35
|
Alternatively, if the parameter is required but does not need parsing,
|
|
34
36
|
pass "required" as the value for the parameter.
|
|
37
|
+
|
|
38
|
+
Parser functions which normally accept None can also be marked as required
|
|
39
|
+
by passing a tuple of (parser_func, "required") as the value for the parameter.
|
|
35
40
|
"""
|
|
36
41
|
|
|
37
42
|
def decorator(func):
|
|
@@ -40,17 +45,17 @@ def parse_parameters(**requested_params: Callable[[str | None], str | None] | Li
|
|
|
40
45
|
def wrapper(*args, **kwargs):
|
|
41
46
|
try:
|
|
42
47
|
for param_name, parser in requested_params.items():
|
|
48
|
+
required_flag = None
|
|
49
|
+
if isinstance(parser, tuple):
|
|
50
|
+
parser, required_flag = parser
|
|
51
|
+
|
|
43
52
|
raw_value = request.args.get(param_name, None)
|
|
44
53
|
|
|
45
|
-
|
|
46
|
-
if parser == "required":
|
|
54
|
+
if parser == "required" or required_flag == "required":
|
|
47
55
|
if raw_value is None:
|
|
48
56
|
return bad_request(err=f"Missing required parameter: [{param_name}]")
|
|
49
|
-
parsed_value = raw_value
|
|
50
|
-
else:
|
|
51
|
-
parsed_value = parser(raw_value) if parser else raw_value
|
|
52
57
|
|
|
53
|
-
kwargs[param_name] =
|
|
58
|
+
kwargs[param_name] = parser(raw_value) if callable(parser) else raw_value
|
|
54
59
|
|
|
55
60
|
except HowlerInvalidParameterException as e:
|
|
56
61
|
return bad_request(err=e.message)
|
{howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/cronjobs/action_queue_worker.py
RENAMED
|
@@ -13,7 +13,7 @@ from apscheduler.schedulers.base import BaseScheduler
|
|
|
13
13
|
from howler.common.logging import get_logger
|
|
14
14
|
from howler.config import config
|
|
15
15
|
from howler.odm.models.action import VALID_TRIGGERS
|
|
16
|
-
from howler.services.action_service import
|
|
16
|
+
from howler.services.action_service import TriggeredAction, get_action_queue, process_action_batch
|
|
17
17
|
|
|
18
18
|
logger = get_logger(__file__)
|
|
19
19
|
|
|
@@ -33,7 +33,7 @@ def run_worker(trigger: str) -> None: # pragma: no cover – long-running loop,
|
|
|
33
33
|
logger.info("Action queue worker disabled by configuration, not starting for trigger=%s", trigger)
|
|
34
34
|
return
|
|
35
35
|
|
|
36
|
-
queue =
|
|
36
|
+
queue = get_action_queue(trigger)
|
|
37
37
|
logger.info(
|
|
38
38
|
"Action queue worker started for trigger=%s (batch_size=%d, timeout=%ds)",
|
|
39
39
|
trigger,
|
|
@@ -41,7 +41,7 @@ def run_worker(trigger: str) -> None: # pragma: no cover – long-running loop,
|
|
|
41
41
|
BATCH_TIMEOUT,
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
batch: list[
|
|
44
|
+
batch: list[TriggeredAction] = []
|
|
45
45
|
|
|
46
46
|
while True:
|
|
47
47
|
try:
|
|
@@ -11,14 +11,16 @@ independent data models in python. This gives us:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
+
import base64
|
|
14
15
|
import copy
|
|
15
16
|
import json
|
|
16
17
|
import re
|
|
17
18
|
import typing
|
|
18
|
-
from datetime import datetime
|
|
19
|
+
from datetime import datetime, timezone
|
|
19
20
|
from enum import Enum as PyEnum
|
|
20
21
|
from enum import EnumMeta
|
|
21
|
-
from
|
|
22
|
+
from ipaddress import ip_address
|
|
23
|
+
from typing import TYPE_CHECKING, Callable, Literal
|
|
22
24
|
from typing import Any as _Any
|
|
23
25
|
from venv import logger
|
|
24
26
|
|
|
@@ -125,6 +127,7 @@ class _Field:
|
|
|
125
127
|
reference=None,
|
|
126
128
|
optional=False,
|
|
127
129
|
deprecated=False,
|
|
130
|
+
sync=True,
|
|
128
131
|
):
|
|
129
132
|
self.index = index
|
|
130
133
|
self.store = store
|
|
@@ -134,6 +137,7 @@ class _Field:
|
|
|
134
137
|
self.copyto.append(copyto)
|
|
135
138
|
elif copyto:
|
|
136
139
|
self.copyto.extend(copyto)
|
|
140
|
+
self.sync = sync
|
|
137
141
|
|
|
138
142
|
self.name = name
|
|
139
143
|
self.parent_name = None
|
|
@@ -1427,34 +1431,36 @@ class Model:
|
|
|
1427
1431
|
# attribute assignment
|
|
1428
1432
|
self.__frozen = True
|
|
1429
1433
|
|
|
1430
|
-
def as_primitives(
|
|
1434
|
+
def as_primitives(
|
|
1435
|
+
self,
|
|
1436
|
+
hidden_fields=False,
|
|
1437
|
+
strip_null=True,
|
|
1438
|
+
ip_format: Literal["str", "int", "encoded_bytes"] | None = None,
|
|
1439
|
+
timestamp_format: Literal["iso", "posix"] | None = None,
|
|
1440
|
+
) -> dict[str, typing.Any]:
|
|
1431
1441
|
"""Convert the object back into primitives that can be json serialized."""
|
|
1432
1442
|
out = {}
|
|
1433
1443
|
|
|
1434
1444
|
fields = self.fields()
|
|
1435
1445
|
for key, value in self._odm_py_obj.items():
|
|
1436
1446
|
field_type = fields.get(key, Any)
|
|
1447
|
+
if isinstance(field_type, Optional):
|
|
1448
|
+
field_type = field_type.child_type
|
|
1449
|
+
|
|
1437
1450
|
if value is not None or (value is None and field_type.default_set):
|
|
1438
1451
|
if strip_null and value is None:
|
|
1439
1452
|
continue
|
|
1440
1453
|
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
out[key] = [(v.as_primitives(strip_null=strip_null) if isinstance(v, Model) else v) for v in value]
|
|
1452
|
-
elif isinstance(value, ClassificationObject):
|
|
1453
|
-
out[key] = str(value)
|
|
1454
|
-
if hidden_fields:
|
|
1455
|
-
out.update(value.get_access_control_parts())
|
|
1456
|
-
else:
|
|
1457
|
-
out[key] = value
|
|
1454
|
+
out[key] = self._as_primitive(
|
|
1455
|
+
value,
|
|
1456
|
+
field_type,
|
|
1457
|
+
strip_null=strip_null,
|
|
1458
|
+
ip_format=ip_format,
|
|
1459
|
+
timestamp_format=timestamp_format,
|
|
1460
|
+
)
|
|
1461
|
+
|
|
1462
|
+
if hidden_fields and isinstance(value, ClassificationObject):
|
|
1463
|
+
out.update(value.get_access_control_parts())
|
|
1458
1464
|
return out
|
|
1459
1465
|
|
|
1460
1466
|
def json(self):
|
|
@@ -1520,6 +1526,72 @@ class Model:
|
|
|
1520
1526
|
def __contains__(self, name):
|
|
1521
1527
|
return name.rstrip("_") in self.fields()
|
|
1522
1528
|
|
|
1529
|
+
@staticmethod
|
|
1530
|
+
def _as_primitive(value, field_type, strip_null=True, ip_format=None, timestamp_format=None):
|
|
1531
|
+
if isinstance(value, Model):
|
|
1532
|
+
return value.as_primitives(strip_null=strip_null, ip_format=ip_format, timestamp_format=timestamp_format)
|
|
1533
|
+
|
|
1534
|
+
if isinstance(value, datetime):
|
|
1535
|
+
if timestamp_format == "iso":
|
|
1536
|
+
return value.isoformat()
|
|
1537
|
+
|
|
1538
|
+
if timestamp_format == "posix":
|
|
1539
|
+
return int(value.timestamp())
|
|
1540
|
+
|
|
1541
|
+
if value.tzinfo is not None:
|
|
1542
|
+
value = value.astimezone(timezone.utc)
|
|
1543
|
+
return value.strftime(DATEFORMAT)
|
|
1544
|
+
|
|
1545
|
+
if value is not None and isinstance(field_type, IP):
|
|
1546
|
+
if ip_format == "encoded_bytes":
|
|
1547
|
+
ip_bytes = ip_address(value).packed
|
|
1548
|
+
return base64.b64encode(ip_bytes).decode("utf-8")
|
|
1549
|
+
|
|
1550
|
+
if ip_format == "int":
|
|
1551
|
+
return int(ip_address(value))
|
|
1552
|
+
|
|
1553
|
+
if ip_format == "str":
|
|
1554
|
+
return str(ip_address(value))
|
|
1555
|
+
|
|
1556
|
+
return value
|
|
1557
|
+
|
|
1558
|
+
if isinstance(value, TypedMapping):
|
|
1559
|
+
return {
|
|
1560
|
+
k: (
|
|
1561
|
+
v.as_primitives(strip_null=strip_null, ip_format=ip_format, timestamp_format=timestamp_format)
|
|
1562
|
+
if isinstance(v, Model)
|
|
1563
|
+
else Model._as_primitive(
|
|
1564
|
+
v,
|
|
1565
|
+
value.type,
|
|
1566
|
+
strip_null=strip_null,
|
|
1567
|
+
ip_format=ip_format,
|
|
1568
|
+
timestamp_format=timestamp_format,
|
|
1569
|
+
)
|
|
1570
|
+
)
|
|
1571
|
+
for k, v in value.items()
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if isinstance(value, TypedList):
|
|
1575
|
+
return [
|
|
1576
|
+
(
|
|
1577
|
+
v.as_primitives(strip_null=strip_null, ip_format=ip_format, timestamp_format=timestamp_format)
|
|
1578
|
+
if isinstance(v, Model)
|
|
1579
|
+
else Model._as_primitive(
|
|
1580
|
+
v,
|
|
1581
|
+
value.type,
|
|
1582
|
+
strip_null=strip_null,
|
|
1583
|
+
ip_format=ip_format,
|
|
1584
|
+
timestamp_format=timestamp_format,
|
|
1585
|
+
)
|
|
1586
|
+
)
|
|
1587
|
+
for v in value
|
|
1588
|
+
]
|
|
1589
|
+
|
|
1590
|
+
if isinstance(value, ClassificationObject):
|
|
1591
|
+
return str(value)
|
|
1592
|
+
|
|
1593
|
+
return value
|
|
1594
|
+
|
|
1523
1595
|
|
|
1524
1596
|
def recursive_set_name(field, name, to_parent=False):
|
|
1525
1597
|
if not to_parent:
|
|
@@ -257,8 +257,16 @@ class Case(DatastoreMixin["Case"], odm.Model):
|
|
|
257
257
|
)
|
|
258
258
|
)
|
|
259
259
|
|
|
260
|
-
def as_primitives(
|
|
261
|
-
|
|
260
|
+
def as_primitives(
|
|
261
|
+
self,
|
|
262
|
+
hidden_fields=False,
|
|
263
|
+
strip_null=True,
|
|
264
|
+
ip_format: Literal["int", "str", "encoded_bytes"] | None = None,
|
|
265
|
+
timestamp_format: Literal["iso", "posix"] | None = None,
|
|
266
|
+
) -> dict[str, Any]:
|
|
267
|
+
result = super().as_primitives(
|
|
268
|
+
hidden_fields, strip_null, ip_format=ip_format, timestamp_format=timestamp_format
|
|
269
|
+
)
|
|
262
270
|
|
|
263
271
|
result["__index"] = self.__class__.__name__.lower()
|
|
264
272
|
|
|
@@ -185,11 +185,13 @@ class HowlerData(odm.Model):
|
|
|
185
185
|
odm.Keyword(description="Raw telemetry records associated with this hit."),
|
|
186
186
|
default=[],
|
|
187
187
|
store=False,
|
|
188
|
+
sync=False,
|
|
188
189
|
)
|
|
189
190
|
links: list[Link] = odm.List(
|
|
190
191
|
odm.Compound(Link),
|
|
191
192
|
default=[],
|
|
192
193
|
description="A list of links associated with this hit.",
|
|
194
|
+
sync=False,
|
|
193
195
|
)
|
|
194
196
|
detection: str | None = odm.Optional(
|
|
195
197
|
odm.CaseInsensitiveKeyword(description="The detection that produced this hit.")
|
|
@@ -248,11 +250,13 @@ class HowlerData(odm.Model):
|
|
|
248
250
|
odm.Compound(Comment),
|
|
249
251
|
default=[],
|
|
250
252
|
description="A list of comments with timestamps and attribution.",
|
|
253
|
+
sync=False,
|
|
251
254
|
)
|
|
252
255
|
log: list[Log] = odm.List(
|
|
253
256
|
odm.Compound(Log),
|
|
254
257
|
default=[],
|
|
255
258
|
description="A list of changes to the hit with timestamps and attribution.",
|
|
259
|
+
sync=False,
|
|
256
260
|
)
|
|
257
261
|
monitored: str | None = odm.Optional(odm.Keyword(description="Link to the incident monitoring dashboard."))
|
|
258
262
|
reported: str | None = odm.Optional(odm.Keyword(description="Link to the incident report."))
|
|
@@ -272,5 +276,8 @@ class HowlerData(odm.Model):
|
|
|
272
276
|
description="Votes relating to the hit",
|
|
273
277
|
)
|
|
274
278
|
dossier: list[Lead] = odm.List(
|
|
275
|
-
odm.Compound(Lead),
|
|
279
|
+
odm.Compound(Lead),
|
|
280
|
+
default=[],
|
|
281
|
+
description="A list of leads forming the dossier associated with this hit",
|
|
282
|
+
sync=False,
|
|
276
283
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# mypy: ignore-errors
|
|
2
|
-
from typing import Any, Optional
|
|
2
|
+
from typing import Any, Literal, Optional
|
|
3
3
|
|
|
4
4
|
from howler import odm
|
|
5
5
|
from howler.common.logging import get_logger
|
|
@@ -342,8 +342,16 @@ class Record(odm.Model):
|
|
|
342
342
|
)
|
|
343
343
|
)
|
|
344
344
|
|
|
345
|
-
def as_primitives(
|
|
346
|
-
|
|
345
|
+
def as_primitives(
|
|
346
|
+
self,
|
|
347
|
+
hidden_fields=False,
|
|
348
|
+
strip_null=True,
|
|
349
|
+
ip_format: Literal["int", "str", "encoded_bytes"] | None = None,
|
|
350
|
+
timestamp_format: Literal["iso", "posix"] | None = None,
|
|
351
|
+
) -> dict[str, Any]:
|
|
352
|
+
result = super().as_primitives(
|
|
353
|
+
hidden_fields, strip_null, ip_format=ip_format, timestamp_format=timestamp_format
|
|
354
|
+
)
|
|
347
355
|
|
|
348
356
|
result["__index"] = self.__class__.__name__.lower()
|
|
349
357
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
from typing import Any, Optional, TypedDict
|
|
3
4
|
|
|
4
5
|
from flask import Response
|
|
5
6
|
|
|
@@ -18,11 +19,19 @@ from howler.utils.str_utils import sanitize_lucene_query
|
|
|
18
19
|
|
|
19
20
|
logger = get_logger(__file__)
|
|
20
21
|
|
|
22
|
+
|
|
23
|
+
class TriggeredAction(TypedDict):
|
|
24
|
+
"""Type for triggered actions"""
|
|
25
|
+
|
|
26
|
+
hit_ids: list[str]
|
|
27
|
+
uname: str | None
|
|
28
|
+
|
|
29
|
+
|
|
21
30
|
# Per-trigger persistent queues for buffering action execution requests.
|
|
22
|
-
_action_queues: dict[str, NamedQueue[
|
|
31
|
+
_action_queues: dict[str, NamedQueue[TriggeredAction]] = {}
|
|
23
32
|
|
|
24
33
|
|
|
25
|
-
def
|
|
34
|
+
def get_action_queue(trigger: str) -> NamedQueue[TriggeredAction]:
|
|
26
35
|
"""Return the action queue for *trigger*, creating it on first use.
|
|
27
36
|
|
|
28
37
|
Raises:
|
|
@@ -65,10 +74,10 @@ def enqueue_action_execution(hit_ids: list[str], trigger: str = "create", user:
|
|
|
65
74
|
return
|
|
66
75
|
|
|
67
76
|
try:
|
|
68
|
-
|
|
77
|
+
get_action_queue(trigger).push(
|
|
69
78
|
{
|
|
70
79
|
"hit_ids": hit_ids,
|
|
71
|
-
"
|
|
80
|
+
"uname": user.uname if user else None,
|
|
72
81
|
}
|
|
73
82
|
)
|
|
74
83
|
except Exception:
|
|
@@ -77,7 +86,7 @@ def enqueue_action_execution(hit_ids: list[str], trigger: str = "create", user:
|
|
|
77
86
|
bulk_execute_on_query(query, trigger=trigger, user=user)
|
|
78
87
|
|
|
79
88
|
|
|
80
|
-
def process_action_batch(trigger: str, items: list[
|
|
89
|
+
def process_action_batch(trigger: str, items: list[TriggeredAction]) -> None:
|
|
81
90
|
"""Process a batch of queued action execution requests for a single trigger.
|
|
82
91
|
|
|
83
92
|
Groups items by user and issues a single coalesced
|
|
@@ -90,26 +99,23 @@ def process_action_batch(trigger: str, items: list[dict]) -> None:
|
|
|
90
99
|
if not items:
|
|
91
100
|
return
|
|
92
101
|
|
|
93
|
-
groups: dict[str | None,
|
|
102
|
+
groups: dict[str | None, list[str]] = defaultdict(list)
|
|
94
103
|
|
|
95
104
|
for item in items:
|
|
96
|
-
|
|
97
|
-
user_key = user_data["uname"] if isinstance(user_data, dict) and "uname" in user_data else None
|
|
98
|
-
|
|
99
|
-
if user_key not in groups:
|
|
100
|
-
groups[user_key] = ([], user_data)
|
|
105
|
+
uname = item.get("uname")
|
|
101
106
|
|
|
102
|
-
groups[
|
|
107
|
+
groups[uname].extend(item["hit_ids"])
|
|
103
108
|
|
|
104
|
-
for
|
|
109
|
+
for uname, hit_ids in groups.items():
|
|
105
110
|
# Deduplicate IDs within a batch group
|
|
106
111
|
unique_ids = list(dict.fromkeys(hit_ids))
|
|
107
112
|
query = f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in unique_ids)})"
|
|
108
113
|
|
|
109
114
|
try:
|
|
110
|
-
|
|
115
|
+
user = datastore().user.get(uname)
|
|
116
|
+
bulk_execute_on_query(query, trigger=trigger, user=user)
|
|
111
117
|
except Exception:
|
|
112
|
-
logger.exception("Error processing action batch for trigger=%s user=%s", trigger,
|
|
118
|
+
logger.exception("Error processing action batch for trigger=%s user=%s", trigger, uname)
|
|
113
119
|
|
|
114
120
|
|
|
115
121
|
def validate_action(new_action: Any) -> Optional[Response]: # noqa: C901
|
|
@@ -152,7 +152,7 @@ suppress-none-returning = true
|
|
|
152
152
|
[tool.poetry]
|
|
153
153
|
package-mode = true
|
|
154
154
|
name = "howler-api"
|
|
155
|
-
version = "4.1.0.
|
|
155
|
+
version = "4.1.0.dev1452"
|
|
156
156
|
description = "Howler - API server"
|
|
157
157
|
authors = [
|
|
158
158
|
"Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{howler_api-4.1.0.dev1435 → howler_api-4.1.0.dev1452}/howler/datastore/migrations/fix_process.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|