howler-api 4.0.0.dev724__tar.gz → 4.0.0.dev799__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.0.0.dev724 → howler_api-4.0.0.dev799}/PKG-INFO +6 -3
- howler_api-4.0.0.dev799/howler/actions/add_to_bundle.py +136 -0
- howler_api-4.0.0.dev799/howler/actions/add_to_case.py +136 -0
- howler_api-4.0.0.dev799/howler/actions/remove_from_bundle.py +150 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/__init__.py +3 -1
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/socket.py +4 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/clue.py +17 -19
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/hit.py +134 -1
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/search.py +3 -1
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/tool.py +45 -5
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v2/ingest.py +4 -9
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/app.py +5 -10
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/collection.py +26 -16
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/discover.py +4 -4
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/oauth.py +0 -2
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/helper.py +2 -2
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/case.py +1 -1
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/config.py +21 -24
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/random_data.py +9 -8
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/security/__init__.py +2 -10
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/security/utils.py +4 -2
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/action_service.py +2 -2
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/auth_service.py +6 -4
- howler_api-4.0.0.dev799/howler/services/bundle_compat_service.py +273 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/case_service.py +0 -3
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/config_service.py +4 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/event_service.py +3 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/hit_service.py +20 -13
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/lucene_service.py +2 -1
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/observable_service.py +17 -3
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/user_service.py +3 -2
- howler_api-4.0.0.dev799/howler/telemetry.py +65 -0
- howler_api-4.0.0.dev799/howler/utils/constants.py +4 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/pyproject.toml +7 -4
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/add_label.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/change_field.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/demote.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/prioritization.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/promote.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/remove_label.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/actions/transition.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/base.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/action.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/analytic.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/auth.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/configs.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/help.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/overview.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/template.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/user.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/utils/etag.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v1/view.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v2/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v2/case.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/api/v2/search.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/classification.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/classification.yml +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/exceptions.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/loader.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/logging/audit.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/logging/format.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/net.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/net_static.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/random_user.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/common/swagger.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/config.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/cronjobs/retention.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/bulk.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/constants.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/howler_store.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/operations.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/schemas.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/store.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/support/build.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/datastore/types.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/error.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/reindex_data.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/gunicorn_config.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/healthz.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/azure.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/hit.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/search.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/workflow.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/helper/ws.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/base.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/charter.txt +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/constants.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/mixins.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/action.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/aws.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/azure.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/clue.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/hit.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/howler_data.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/lead.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/observable.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/overview.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/record.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/template.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/user.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/models/view.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/odm/randomizer.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/patched.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/plugins/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/plugins/config.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/security/socket.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/analytic_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/docs_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/dossier_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/jwt_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/notebook_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/overview_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/search_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/services/template_service.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/annotations.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/chunk.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/compat.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/isotime.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/list_utils.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/lucene.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/path.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/socket_utils.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/str_utils.py +0 -0
- {howler_api-4.0.0.dev724 → howler_api-4.0.0.dev799}/howler/utils/uid.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: howler-api
|
|
3
|
-
Version: 4.0.0.
|
|
3
|
+
Version: 4.0.0.dev799
|
|
4
4
|
Summary: Howler - API server
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
|
|
@@ -8,7 +8,7 @@ Author: Canadian Centre for Cyber Security
|
|
|
8
8
|
Author-email: howler@cyber.gc.ca
|
|
9
9
|
Maintainer: Matthew Rafuse
|
|
10
10
|
Maintainer-email: matthew.rafuse@cyber.gc.ca
|
|
11
|
-
Requires-Python: >=3.
|
|
11
|
+
Requires-Python: >=3.10,<4.0
|
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -21,10 +21,10 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
21
21
|
Classifier: Topic :: Software Development :: Libraries
|
|
22
22
|
Requires-Dist: apscheduler (==3.11.2)
|
|
23
23
|
Requires-Dist: authlib (>=1.6.0,<2.0.0)
|
|
24
|
+
Requires-Dist: azure-monitor-opentelemetry (>=1.8.7,<2.0.0)
|
|
24
25
|
Requires-Dist: bcrypt (==4.3.0)
|
|
25
26
|
Requires-Dist: chardet (==5.2.0)
|
|
26
27
|
Requires-Dist: chevron (==0.14.0)
|
|
27
|
-
Requires-Dist: elastic-apm[flask] (>=6.22.0,<7.0.0)
|
|
28
28
|
Requires-Dist: elasticsearch (==8.19.3)
|
|
29
29
|
Requires-Dist: flasgger (>=0.9.7.1,<0.10.0.0)
|
|
30
30
|
Requires-Dist: flask (==3.1.3)
|
|
@@ -33,6 +33,9 @@ Requires-Dist: gevent (>=25.9.1,<26.0.0)
|
|
|
33
33
|
Requires-Dist: gunicorn (==23.0.0)
|
|
34
34
|
Requires-Dist: luqum (>=1.0.0,<2.0.0)
|
|
35
35
|
Requires-Dist: mergedeep (>=1.3.4,<2.0.0)
|
|
36
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http (==1.40.0)
|
|
37
|
+
Requires-Dist: opentelemetry-instrumentation-flask (==0.61b0)
|
|
38
|
+
Requires-Dist: opentelemetry-sdk (==1.40.0)
|
|
36
39
|
Requires-Dist: packaging (<25.0)
|
|
37
40
|
Requires-Dist: passlib (==1.7.4)
|
|
38
41
|
Requires-Dist: prometheus-client (==0.24.1)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Deprecated add_to_bundle action — delegates to add_to_case via bundle_compat_service."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from howler.common.exceptions import NotFoundException
|
|
6
|
+
from howler.common.loader import datastore
|
|
7
|
+
from howler.odm.models.action import VALID_TRIGGERS
|
|
8
|
+
from howler.services import bundle_compat_service, case_service
|
|
9
|
+
from howler.utils.str_utils import sanitize_lucene_query
|
|
10
|
+
|
|
11
|
+
OPERATION_ID = "add_to_bundle"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
15
|
+
"""Add a set of hits matching the query to the specified bundle (deprecated — uses cases).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
query (str): The query containing the matching hits
|
|
19
|
+
bundle_id (str): The ``howler.id`` of the bundle to add the hits to.
|
|
20
|
+
"""
|
|
21
|
+
report = []
|
|
22
|
+
|
|
23
|
+
if not bundle_id:
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
"query": query,
|
|
27
|
+
"outcome": "error",
|
|
28
|
+
"title": "Invalid Bundle ID",
|
|
29
|
+
"message": "Bundle ID cannot be empty.",
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
case_id = bundle_compat_service.find_case_for_bundle(bundle_id)
|
|
35
|
+
if case_id is None:
|
|
36
|
+
report.append(
|
|
37
|
+
{
|
|
38
|
+
"query": query,
|
|
39
|
+
"outcome": "error",
|
|
40
|
+
"title": "Invalid Bundle",
|
|
41
|
+
"message": f"Either a hit with ID {bundle_id} does not exist, or it has no associated case.",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
return report
|
|
45
|
+
|
|
46
|
+
ds = datastore()
|
|
47
|
+
matching_hits = ds.hit.search(query, rows=1000)["items"]
|
|
48
|
+
|
|
49
|
+
if not matching_hits:
|
|
50
|
+
report.append(
|
|
51
|
+
{
|
|
52
|
+
"query": query,
|
|
53
|
+
"outcome": "skipped",
|
|
54
|
+
"title": "No Matching Hits",
|
|
55
|
+
"message": "There were no hits matching this query.",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return report
|
|
59
|
+
|
|
60
|
+
added = []
|
|
61
|
+
skipped = []
|
|
62
|
+
for hit in matching_hits:
|
|
63
|
+
child_label = f"hits/{hit.howler.analytic} ({hit.howler.id})"
|
|
64
|
+
try:
|
|
65
|
+
case_service.append_case_item(
|
|
66
|
+
case_id,
|
|
67
|
+
item_type="hit",
|
|
68
|
+
item_value=hit.howler.id,
|
|
69
|
+
item_path=child_label,
|
|
70
|
+
)
|
|
71
|
+
added.append(hit.howler.id)
|
|
72
|
+
except Exception:
|
|
73
|
+
skipped.append(hit.howler.id)
|
|
74
|
+
|
|
75
|
+
if skipped:
|
|
76
|
+
report.append(
|
|
77
|
+
{
|
|
78
|
+
"query": f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in skipped)})",
|
|
79
|
+
"outcome": "skipped",
|
|
80
|
+
"title": "Skipped Hits",
|
|
81
|
+
"message": "These hits could not be added (already present or invalid).",
|
|
82
|
+
}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if added:
|
|
86
|
+
report.append(
|
|
87
|
+
{
|
|
88
|
+
"query": f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in added)})",
|
|
89
|
+
"outcome": "success",
|
|
90
|
+
"title": "Executed Successfully",
|
|
91
|
+
"message": "The specified bundle has had all matching hits added.",
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
except NotFoundException as e:
|
|
96
|
+
report.append(
|
|
97
|
+
{
|
|
98
|
+
"query": query,
|
|
99
|
+
"outcome": "error",
|
|
100
|
+
"title": "Failed to Execute",
|
|
101
|
+
"message": str(e),
|
|
102
|
+
}
|
|
103
|
+
)
|
|
104
|
+
except Exception as e:
|
|
105
|
+
report.append(
|
|
106
|
+
{
|
|
107
|
+
"query": query,
|
|
108
|
+
"outcome": "error",
|
|
109
|
+
"title": "Failed to Execute",
|
|
110
|
+
"message": f"Unknown exception occurred: {str(e)}",
|
|
111
|
+
}
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return report
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def specification():
|
|
118
|
+
"""Specify various properties of the action, such as title, descriptions, permissions and input steps."""
|
|
119
|
+
return {
|
|
120
|
+
"id": OPERATION_ID,
|
|
121
|
+
"title": "Add to Bundle (Deprecated)",
|
|
122
|
+
"priority": 6,
|
|
123
|
+
"i18nKey": f"operations.{OPERATION_ID}",
|
|
124
|
+
"description": {
|
|
125
|
+
"short": "Add a set of hits to a bundle (deprecated — uses cases)",
|
|
126
|
+
"long": execute.__doc__,
|
|
127
|
+
},
|
|
128
|
+
"roles": ["automation_basic"],
|
|
129
|
+
"steps": [
|
|
130
|
+
{
|
|
131
|
+
"args": {"bundle_id": []},
|
|
132
|
+
"options": {},
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"triggers": VALID_TRIGGERS,
|
|
136
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
import chevron
|
|
4
|
+
|
|
5
|
+
from howler.common.exceptions import InvalidDataException, NotFoundException
|
|
6
|
+
from howler.common.loader import datastore
|
|
7
|
+
from howler.odm.models.action import VALID_TRIGGERS
|
|
8
|
+
from howler.services import case_service
|
|
9
|
+
|
|
10
|
+
OPERATION_ID = "add_to_case"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def execute(
|
|
14
|
+
query: str,
|
|
15
|
+
case_id: Optional[str] = None,
|
|
16
|
+
path: str = "related",
|
|
17
|
+
title_template: str = "{{howler.analytic}} ({{howler.id}})",
|
|
18
|
+
**kwargs,
|
|
19
|
+
):
|
|
20
|
+
"""Add matching alerts to a given case.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
query (str): The query on which to apply this automation.
|
|
24
|
+
case_id (str): The ID of the case to add the alerts to.
|
|
25
|
+
path (str): The path within the case at which to place the alerts. Defaults to "related".
|
|
26
|
+
title_template (str): A Mustache-compatible template string used to generate each item's
|
|
27
|
+
path suffix (title). The hit's fields are available as template variables.
|
|
28
|
+
Defaults to "{{howler.analytic}} ({{howler.id}})".
|
|
29
|
+
"""
|
|
30
|
+
if not case_id:
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
"query": query,
|
|
34
|
+
"outcome": "error",
|
|
35
|
+
"title": "Missing Case ID",
|
|
36
|
+
"message": "A case_id must be provided.",
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
ds = datastore()
|
|
41
|
+
|
|
42
|
+
if ds.case.get(case_id) is None:
|
|
43
|
+
return [
|
|
44
|
+
{
|
|
45
|
+
"query": query,
|
|
46
|
+
"outcome": "error",
|
|
47
|
+
"title": "Case Not Found",
|
|
48
|
+
"message": f"No case with ID '{case_id}' exists.",
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
hits = ds.hit.search(query, rows=1000)["items"]
|
|
53
|
+
|
|
54
|
+
if not hits:
|
|
55
|
+
return [
|
|
56
|
+
{
|
|
57
|
+
"query": query,
|
|
58
|
+
"outcome": "skipped",
|
|
59
|
+
"title": "No Matching Hits",
|
|
60
|
+
"message": "No hits matched the query, so the action was skipped.",
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
report = []
|
|
65
|
+
skipped = []
|
|
66
|
+
added = []
|
|
67
|
+
|
|
68
|
+
normalized_path = path.rstrip("/")
|
|
69
|
+
|
|
70
|
+
for hit in hits:
|
|
71
|
+
hit_data = hit.as_primitives()
|
|
72
|
+
title = chevron.render(title_template, hit_data)
|
|
73
|
+
item_path = f"{normalized_path}/{title}" if normalized_path else title
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
case_service.append_case_item(
|
|
77
|
+
case_id,
|
|
78
|
+
item_type="hit",
|
|
79
|
+
item_value=hit.howler.id,
|
|
80
|
+
item_path=item_path,
|
|
81
|
+
)
|
|
82
|
+
added.append(hit.howler.id)
|
|
83
|
+
except InvalidDataException as e:
|
|
84
|
+
skipped.append(f"{hit.howler.id}: {e}")
|
|
85
|
+
except NotFoundException as e:
|
|
86
|
+
skipped.append(f"{hit.howler.id}: {e}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
skipped.append(f"{hit.howler.id}: {e}")
|
|
89
|
+
|
|
90
|
+
if added:
|
|
91
|
+
report.append(
|
|
92
|
+
{
|
|
93
|
+
"query": f"howler.id:({' OR '.join(added)})",
|
|
94
|
+
"outcome": "success",
|
|
95
|
+
"title": "Added to Case",
|
|
96
|
+
"message": f"{len(added)} alert(s) successfully added to case '{case_id}'.",
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if skipped:
|
|
101
|
+
report.append(
|
|
102
|
+
{
|
|
103
|
+
"query": query,
|
|
104
|
+
"outcome": "skipped",
|
|
105
|
+
"title": "Skipped Alerts",
|
|
106
|
+
"message": f"{len(skipped)} alert(s) could not be added: {'; '.join(skipped)}",
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return report
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def specification():
|
|
114
|
+
"""Specify various properties of the action, such as title, descriptions, permissions and input steps."""
|
|
115
|
+
return {
|
|
116
|
+
"id": OPERATION_ID,
|
|
117
|
+
"title": "Add to Case",
|
|
118
|
+
"priority": 9,
|
|
119
|
+
"i18nKey": f"operations.{OPERATION_ID}",
|
|
120
|
+
"description": {
|
|
121
|
+
"short": "Add matching alerts to a case",
|
|
122
|
+
"long": execute.__doc__,
|
|
123
|
+
},
|
|
124
|
+
"roles": ["automation_basic"],
|
|
125
|
+
"steps": [
|
|
126
|
+
{
|
|
127
|
+
"args": {
|
|
128
|
+
"case_id": [],
|
|
129
|
+
"path": [],
|
|
130
|
+
"title_template": [],
|
|
131
|
+
},
|
|
132
|
+
"options": {},
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"triggers": VALID_TRIGGERS,
|
|
136
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Deprecated remove_from_bundle action — delegates to case_service for item removal."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from howler.common.exceptions import NotFoundException
|
|
6
|
+
from howler.common.loader import datastore
|
|
7
|
+
from howler.odm.models.action import VALID_TRIGGERS
|
|
8
|
+
from howler.services import bundle_compat_service, case_service
|
|
9
|
+
from howler.utils.str_utils import sanitize_lucene_query
|
|
10
|
+
|
|
11
|
+
OPERATION_ID = "remove_from_bundle"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
15
|
+
"""Remove a set of hits matching the query from the specified bundle (deprecated — uses cases).
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
query (str): The query containing the matching hits
|
|
19
|
+
bundle_id (str): The ``howler.id`` of the bundle to remove the hits from.
|
|
20
|
+
"""
|
|
21
|
+
report = []
|
|
22
|
+
|
|
23
|
+
if not bundle_id:
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
"query": query,
|
|
27
|
+
"outcome": "error",
|
|
28
|
+
"title": "Invalid Bundle ID",
|
|
29
|
+
"message": "Bundle ID cannot be empty.",
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
case_id = bundle_compat_service.find_case_for_bundle(bundle_id)
|
|
35
|
+
if case_id is None:
|
|
36
|
+
report.append(
|
|
37
|
+
{
|
|
38
|
+
"query": query,
|
|
39
|
+
"outcome": "error",
|
|
40
|
+
"title": "Invalid Bundle",
|
|
41
|
+
"message": f"Either a hit with ID {bundle_id} does not exist, or it has no associated case.",
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
return report
|
|
45
|
+
|
|
46
|
+
ds = datastore()
|
|
47
|
+
matching_hits = ds.hit.search(query, rows=1000)["items"]
|
|
48
|
+
|
|
49
|
+
if not matching_hits:
|
|
50
|
+
report.append(
|
|
51
|
+
{
|
|
52
|
+
"query": query,
|
|
53
|
+
"outcome": "skipped",
|
|
54
|
+
"title": "No Matching Hits",
|
|
55
|
+
"message": "There were no hits matching this query.",
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return report
|
|
59
|
+
|
|
60
|
+
# Get the case to check which hits are actually in it
|
|
61
|
+
case = ds.case.get(case_id)
|
|
62
|
+
if case is None:
|
|
63
|
+
report.append(
|
|
64
|
+
{
|
|
65
|
+
"query": query,
|
|
66
|
+
"outcome": "error",
|
|
67
|
+
"title": "Case Not Found",
|
|
68
|
+
"message": f"Associated case {case_id} no longer exists.",
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
return report
|
|
72
|
+
|
|
73
|
+
case_item_values = {item.value for item in case.items}
|
|
74
|
+
values_to_remove = [h.howler.id for h in matching_hits if h.howler.id in case_item_values]
|
|
75
|
+
skipped_ids = [h.howler.id for h in matching_hits if h.howler.id not in case_item_values]
|
|
76
|
+
|
|
77
|
+
if skipped_ids:
|
|
78
|
+
report.append(
|
|
79
|
+
{
|
|
80
|
+
"query": f"howler.id:({' OR '.join(sanitize_lucene_query(h) for h in skipped_ids)})",
|
|
81
|
+
"outcome": "skipped",
|
|
82
|
+
"title": "Skipped Hits Not in Bundle",
|
|
83
|
+
"message": "These hits are not in the bundle.",
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if not values_to_remove:
|
|
88
|
+
report.append(
|
|
89
|
+
{
|
|
90
|
+
"query": query,
|
|
91
|
+
"outcome": "skipped",
|
|
92
|
+
"title": "No Matching Hits",
|
|
93
|
+
"message": "None of the matching hits were found in the bundle.",
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
return report
|
|
97
|
+
|
|
98
|
+
case_service.remove_case_items(case_id, values_to_remove)
|
|
99
|
+
|
|
100
|
+
report.append(
|
|
101
|
+
{
|
|
102
|
+
"query": query,
|
|
103
|
+
"outcome": "success",
|
|
104
|
+
"title": "Executed Successfully",
|
|
105
|
+
"message": f"Matching hits removed from bundle with id {bundle_id}",
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except NotFoundException as e:
|
|
110
|
+
report.append(
|
|
111
|
+
{
|
|
112
|
+
"query": query,
|
|
113
|
+
"outcome": "error",
|
|
114
|
+
"title": "Failed to Execute",
|
|
115
|
+
"message": str(e),
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
except Exception as e:
|
|
119
|
+
report.append(
|
|
120
|
+
{
|
|
121
|
+
"query": query,
|
|
122
|
+
"outcome": "error",
|
|
123
|
+
"title": "Failed to Execute",
|
|
124
|
+
"message": f"Unknown exception occurred: {str(e)}",
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return report
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def specification():
|
|
132
|
+
"""Specify various properties of the action, such as title, descriptions, permissions and input steps."""
|
|
133
|
+
return {
|
|
134
|
+
"id": OPERATION_ID,
|
|
135
|
+
"title": "Remove from Bundle (Deprecated)",
|
|
136
|
+
"priority": 5,
|
|
137
|
+
"i18nKey": f"operations.{OPERATION_ID}",
|
|
138
|
+
"description": {
|
|
139
|
+
"short": "Remove a set of hits from a bundle (deprecated — uses cases)",
|
|
140
|
+
"long": execute.__doc__,
|
|
141
|
+
},
|
|
142
|
+
"roles": ["automation_basic"],
|
|
143
|
+
"steps": [
|
|
144
|
+
{
|
|
145
|
+
"args": {"bundle_id": []},
|
|
146
|
+
"options": {},
|
|
147
|
+
}
|
|
148
|
+
],
|
|
149
|
+
"triggers": VALID_TRIGGERS,
|
|
150
|
+
}
|
|
@@ -10,6 +10,7 @@ from howler import odm
|
|
|
10
10
|
from howler.common.loader import APP_NAME
|
|
11
11
|
from howler.common.logging import get_logger, log_with_traceback
|
|
12
12
|
from howler.config import QUOTA_TRACKER, get_version
|
|
13
|
+
from howler.utils.constants import TESTING
|
|
13
14
|
from howler.utils.str_utils import safe_str
|
|
14
15
|
|
|
15
16
|
API_PREFIX = "/api"
|
|
@@ -73,7 +74,8 @@ def _make_api_response(
|
|
|
73
74
|
resp.set_cookie(k, v, secure=True, httponly=True, samesite="Lax")
|
|
74
75
|
|
|
75
76
|
RAW_API_COUNTER.labels(request.method, str(request.url_rule), status_code).inc()
|
|
76
|
-
|
|
77
|
+
if not TESTING:
|
|
78
|
+
logger.info("%s %s - %s", request.method, request.path, status_code)
|
|
77
79
|
|
|
78
80
|
return resp
|
|
79
81
|
|
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from flask import Blueprint, request
|
|
7
|
+
from opentelemetry import trace
|
|
7
8
|
|
|
8
9
|
import howler.services.event_service as event_service
|
|
9
10
|
from howler.api import ok, unauthorized
|
|
@@ -21,10 +22,12 @@ socket_api = Blueprint("socket", "socket", url_prefix="/socket/v1")
|
|
|
21
22
|
socket_api._doc = "Endpoints concerning websocket connectivity between the client and server" # type: ignore
|
|
22
23
|
|
|
23
24
|
logger = get_logger(__file__)
|
|
25
|
+
tracer = trace.get_tracer(__name__)
|
|
24
26
|
|
|
25
27
|
hit_helper = OdmHelper(Hit)
|
|
26
28
|
|
|
27
29
|
|
|
30
|
+
@tracer.start_as_current_span(f"{__name__}.emit")
|
|
28
31
|
@socket_api.route("/emit/<event>", methods=["POST"])
|
|
29
32
|
def emit(event: str):
|
|
30
33
|
"""Emit an event to all listening websockets"""
|
|
@@ -46,6 +49,7 @@ def emit(event: str):
|
|
|
46
49
|
return ok()
|
|
47
50
|
|
|
48
51
|
|
|
52
|
+
@tracer.start_as_current_span(f"{__name__}.connect")
|
|
49
53
|
@socket_api.route("/connect", websocket=True) # type: ignore
|
|
50
54
|
@websocket_auth(required_priv=["R"])
|
|
51
55
|
def connect(ws: Server, *args: Any, ws_id: str, **kwargs):
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
import time
|
|
3
2
|
from typing import Callable, Optional
|
|
4
3
|
|
|
5
4
|
import requests
|
|
6
|
-
from elasticapm.traces import capture_span
|
|
7
5
|
from flask import request
|
|
8
6
|
|
|
9
7
|
from howler.api import bad_gateway, make_subapi_blueprint, ok
|
|
@@ -13,6 +11,7 @@ from howler.common.swagger import generate_swagger_docs
|
|
|
13
11
|
from howler.config import cache, config
|
|
14
12
|
from howler.plugins import get_plugins
|
|
15
13
|
from howler.security import api_login
|
|
14
|
+
from howler.utils.constants import TESTING
|
|
16
15
|
|
|
17
16
|
SUB_API = "clue"
|
|
18
17
|
clue_api = make_subapi_blueprint(SUB_API, api_version=1)
|
|
@@ -23,7 +22,7 @@ logger = get_logger(__file__)
|
|
|
23
22
|
|
|
24
23
|
def skip_cache(*args):
|
|
25
24
|
"Function to skip cache in testing mode"
|
|
26
|
-
return
|
|
25
|
+
return TESTING
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
@cache.memoize(15 * 60, unless=skip_cache)
|
|
@@ -74,22 +73,21 @@ def proxy_to_clue(path, **kwargs):
|
|
|
74
73
|
clue_token = get_token(auth_token)
|
|
75
74
|
|
|
76
75
|
start = time.perf_counter()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
)
|
|
76
|
+
if request.method.lower() == "get":
|
|
77
|
+
response = requests.get(
|
|
78
|
+
f"{config.core.clue.url}/{path}",
|
|
79
|
+
headers={"Authorization": f"Bearer {clue_token}", "Accept": "application/json"},
|
|
80
|
+
params=request.args.to_dict(),
|
|
81
|
+
timeout=5 * 60,
|
|
82
|
+
)
|
|
83
|
+
else:
|
|
84
|
+
response = requests.post(
|
|
85
|
+
f"{config.core.clue.url}/{path}",
|
|
86
|
+
json=request.json,
|
|
87
|
+
headers={"Authorization": f"Bearer {clue_token}", "Accept": "application/json"},
|
|
88
|
+
params=request.args.to_dict(),
|
|
89
|
+
timeout=5 * 60,
|
|
90
|
+
)
|
|
93
91
|
|
|
94
92
|
logger.debug("Request to clue completed in %s ms", round(time.perf_counter() - start))
|
|
95
93
|
|