howler-api 4.0.0.dev850__tar.gz → 4.0.0.dev871__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.dev850 → howler_api-4.0.0.dev871}/PKG-INFO +1 -1
- howler_api-4.0.0.dev871/howler/api/v1/utils/etag.py +106 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v2/ingest.py +4 -2
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v2/search.py +8 -11
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/collection.py +1 -1
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/search_service.py +4 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/pyproject.toml +1 -1
- howler_api-4.0.0.dev850/howler/api/v1/utils/etag.py +0 -84
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/add_label.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/add_to_bundle.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/add_to_case.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/change_field.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/demote.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/prioritization.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/promote.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/remove_from_bundle.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/remove_label.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/actions/transition.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/base.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/socket.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/action.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/analytic.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/auth.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/clue.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/configs.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/help.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/hit.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/overview.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/search.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/template.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/tool.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/user.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v1/view.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v2/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/api/v2/case.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/app.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/classification.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/classification.yml +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/exceptions.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/loader.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/logging/audit.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/logging/format.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/net.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/net_static.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/random_user.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/common/swagger.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/config.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/cronjobs/correlation.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/cronjobs/retention.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/bulk.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/constants.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/howler_store.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/operations.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/schemas.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/store.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/support/build.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/datastore/types.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/error.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/reindex_data.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/gunicorn_config.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/healthz.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/azure.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/discover.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/hit.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/oauth.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/search.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/workflow.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/helper/ws.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/base.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/charter.txt +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/constants.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/helper.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/mixins.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/action.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/aws.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/azure.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/case.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/clue.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/config.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/hit.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/howler_data.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/lead.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/observable.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/overview.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/record.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/template.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/user.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/view.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/random_data.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/randomizer.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/patched.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/plugins/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/plugins/config.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/security/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/security/socket.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/security/utils.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/action_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/analytic_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/auth_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/bundle_compat_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/case_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/config_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/correlation_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/docs_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/dossier_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/event_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/hit_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/jwt_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/lucene_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/notebook_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/observable_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/overview_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/template_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/user_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/services/viewer_service.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/telemetry.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/annotations.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/chunk.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/compat.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/constants.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/isotime.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/list_utils.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/lucene.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/path.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/socket_utils.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/str_utils.py +0 -0
- {howler_api-4.0.0.dev850 → howler_api-4.0.0.dev871}/howler/utils/uid.py +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""ETag utility module for handling HTTP ETags in Flask responses.
|
|
2
|
+
|
|
3
|
+
ETags (Entity Tags) are HTTP headers used for web cache validation and conditional requests.
|
|
4
|
+
They help optimize performance by allowing clients to cache responses and only fetch
|
|
5
|
+
new data when the resource has actually changed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from flask import Response, request
|
|
12
|
+
|
|
13
|
+
from howler.api import not_modified
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def add_etag(getter=None, check_if_match=True):
|
|
17
|
+
"""Decorator to add ETag handling to a Flask response.
|
|
18
|
+
|
|
19
|
+
This decorator implements HTTP ETag functionality for API endpoints, enabling:
|
|
20
|
+
- Conditional requests using If-Match headers
|
|
21
|
+
- Cache validation to prevent unnecessary data transfers
|
|
22
|
+
- Version tracking for resources
|
|
23
|
+
|
|
24
|
+
When ``getter`` is provided, the decorator pre-fetches the object and its
|
|
25
|
+
version, injects ``server_version`` into kwargs, caches the object, and
|
|
26
|
+
supports ``If-Match`` conditional requests.
|
|
27
|
+
|
|
28
|
+
When ``getter`` is ``None``, the decorator only handles converting
|
|
29
|
+
``(Response, version)`` return tuples into a single Response with the
|
|
30
|
+
``ETag`` header set. This is useful for endpoints that manage their own
|
|
31
|
+
version retrieval (e.g. v2 endpoints).
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
getter: Optional function that retrieves the object and its version.
|
|
35
|
+
When None, the decorator only handles ETag header setting.
|
|
36
|
+
check_if_match (bool): Whether to check If-Match headers for conditional requests.
|
|
37
|
+
Only used when getter is provided.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Decorated function with ETag support
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def wrapper(f):
|
|
44
|
+
"""Inner wrapper function that applies ETag functionality to the decorated function."""
|
|
45
|
+
|
|
46
|
+
@functools.wraps(f)
|
|
47
|
+
def generate_etag(*args, **kwargs):
|
|
48
|
+
"""Generate and handle ETags for the HTTP response."""
|
|
49
|
+
if getter is not None:
|
|
50
|
+
# Retrieve the object and its version using the provided getter function
|
|
51
|
+
# The getter should return (object, version) tuple
|
|
52
|
+
obj, version = getter(
|
|
53
|
+
kwargs.get("id", kwargs.get("username", None)),
|
|
54
|
+
as_odm=True,
|
|
55
|
+
version=True,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Handle conditional requests with If-Match header
|
|
59
|
+
# If the client's version matches the current version and it's a GET request
|
|
60
|
+
# without metadata parameter, return 304 Not Modified to save bandwidth
|
|
61
|
+
if (
|
|
62
|
+
check_if_match
|
|
63
|
+
and "If-Match" in request.headers
|
|
64
|
+
and request.headers["If-Match"] == version
|
|
65
|
+
and request.method == "GET"
|
|
66
|
+
and "metadata" not in request.args
|
|
67
|
+
):
|
|
68
|
+
return not_modified()
|
|
69
|
+
|
|
70
|
+
# Extract the resource type from the API path and create a cache key
|
|
71
|
+
# e.g., "/api/v1/users/123" becomes "cached_users"
|
|
72
|
+
key = re.sub(r"^\/api\/v\d+\/(\w+)\/.+$", r"cached_\1", request.path)
|
|
73
|
+
kwargs[key] = obj
|
|
74
|
+
|
|
75
|
+
# Call the original function with the cached object and version
|
|
76
|
+
values = f(*args, server_version=version, **kwargs)
|
|
77
|
+
|
|
78
|
+
# Handle different return value formats from the decorated function
|
|
79
|
+
# If there is only one return, it's just the response
|
|
80
|
+
if isinstance(values, Response):
|
|
81
|
+
# Only add ETag header for successful responses (not 409 Conflict or 400 Bad Request)
|
|
82
|
+
if values.status_code != 409 and values.status_code != 400:
|
|
83
|
+
values.headers["ETag"] = version
|
|
84
|
+
return values
|
|
85
|
+
|
|
86
|
+
# If there are two returns, it's the response and the new version
|
|
87
|
+
# This happens when the function modifies the resource and returns an updated version
|
|
88
|
+
else:
|
|
89
|
+
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
90
|
+
# Add the new ETag version to successful responses
|
|
91
|
+
values[0].headers["ETag"] = values[1]
|
|
92
|
+
return values[0]
|
|
93
|
+
|
|
94
|
+
# No getter: just call the function and handle (Response, version) tuples
|
|
95
|
+
values = f(*args, **kwargs)
|
|
96
|
+
|
|
97
|
+
if isinstance(values, Response):
|
|
98
|
+
return values
|
|
99
|
+
|
|
100
|
+
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
101
|
+
values[0].headers["ETag"] = values[1]
|
|
102
|
+
return values[0]
|
|
103
|
+
|
|
104
|
+
return generate_etag
|
|
105
|
+
|
|
106
|
+
return wrapper
|
|
@@ -5,6 +5,7 @@ from flask import request
|
|
|
5
5
|
from mergedeep import Strategy, merge
|
|
6
6
|
|
|
7
7
|
from howler.api import bad_request, created, forbidden, internal_error, make_subapi_blueprint, no_content, not_found, ok
|
|
8
|
+
from howler.api.v1.utils.etag import add_etag
|
|
8
9
|
from howler.common.exceptions import HowlerException, HowlerValueError
|
|
9
10
|
from howler.common.loader import datastore
|
|
10
11
|
from howler.common.logging import get_logger
|
|
@@ -254,7 +255,8 @@ def validate(index: str, **kwargs):
|
|
|
254
255
|
@generate_swagger_docs()
|
|
255
256
|
@ingest_api.route("/<index>/<id>/overwrite", methods=["PATCH"])
|
|
256
257
|
@api_login(audit=False, required_priv=["W"])
|
|
257
|
-
|
|
258
|
+
@add_etag()
|
|
259
|
+
def overwrite(index: str, id: str, **kwargs):
|
|
258
260
|
"""Overwrite a record.
|
|
259
261
|
|
|
260
262
|
Variables:
|
|
@@ -301,7 +303,7 @@ def overwrite(index: str, id: str, server_version: str, **kwargs):
|
|
|
301
303
|
),
|
|
302
304
|
)
|
|
303
305
|
|
|
304
|
-
ds[index].save(id, odm(new_record) if odm else new_record, version=server_version)
|
|
306
|
+
ds[index].save(id, odm(new_record) if odm else new_record, version=kwargs.get("server_version"))
|
|
305
307
|
|
|
306
308
|
new_record, new_version = ds[index].get(id, as_obj=False, version=True)
|
|
307
309
|
|
|
@@ -5,7 +5,6 @@ from typing import Any
|
|
|
5
5
|
from elasticsearch import BadRequestError
|
|
6
6
|
from elasticsearch._sync.client.indices import IndicesClient
|
|
7
7
|
from flask import Request, request
|
|
8
|
-
from werkzeug.exceptions import BadRequest
|
|
9
8
|
|
|
10
9
|
from howler.api import bad_request, make_subapi_blueprint, ok
|
|
11
10
|
from howler.common.loader import datastore
|
|
@@ -30,10 +29,7 @@ def generate_params(request: Request, fields: list[str], multi_fields: list[str]
|
|
|
30
29
|
params = {}
|
|
31
30
|
|
|
32
31
|
if request.method == "POST":
|
|
33
|
-
|
|
34
|
-
req_data = request.json
|
|
35
|
-
except BadRequest:
|
|
36
|
-
req_data = {"query": "*:*"}
|
|
32
|
+
req_data = request.get_json(silent=True) or {"query": "*:*"}
|
|
37
33
|
|
|
38
34
|
params = {
|
|
39
35
|
**params,
|
|
@@ -131,7 +127,8 @@ def search(indexes: str, **kwargs):
|
|
|
131
127
|
return bad_request(err="There was no search query.")
|
|
132
128
|
|
|
133
129
|
metadata = params.pop("metadata", [])
|
|
134
|
-
|
|
130
|
+
access_control = params.pop("access_control", None)
|
|
131
|
+
result = search_service.search(indexes, query, access_control=access_control, **params)
|
|
135
132
|
|
|
136
133
|
if metadata and any(idx in index_list for idx in ["hit"]):
|
|
137
134
|
hit_service.augment_metadata(result["items"], metadata, user)
|
|
@@ -236,7 +233,7 @@ def count(index, **kwargs):
|
|
|
236
233
|
|
|
237
234
|
Result Example:
|
|
238
235
|
{
|
|
239
|
-
"
|
|
236
|
+
"count": 201, # Total results found
|
|
240
237
|
}
|
|
241
238
|
"""
|
|
242
239
|
user = kwargs["user"]
|
|
@@ -245,7 +242,7 @@ def count(index, **kwargs):
|
|
|
245
242
|
if collection is None:
|
|
246
243
|
return bad_request(err=f"Not a valid index to search in: {index}")
|
|
247
244
|
|
|
248
|
-
params, req_data = generate_params(request, [], [])
|
|
245
|
+
params, req_data = generate_params(request, ["timeout"], ["filters"])
|
|
249
246
|
|
|
250
247
|
boolean_fields = ["use_archive"]
|
|
251
248
|
params.update(
|
|
@@ -256,15 +253,15 @@ def count(index, **kwargs):
|
|
|
256
253
|
}
|
|
257
254
|
)
|
|
258
255
|
|
|
259
|
-
if has_access_control(index)
|
|
260
|
-
params.update({"access_control": user["access_control"]})
|
|
256
|
+
access_control = user["access_control"] if has_access_control(index) else None
|
|
261
257
|
|
|
262
258
|
query = req_data.get("query", None)
|
|
263
259
|
if not query:
|
|
264
260
|
return bad_request(err="There was no search query.")
|
|
265
261
|
|
|
262
|
+
filters = params.pop("filters", [])
|
|
266
263
|
try:
|
|
267
|
-
return ok(collection().count(query,
|
|
264
|
+
return ok(collection().count(query, filters, access_control=access_control))
|
|
268
265
|
except (SearchException, BadRequestError) as e:
|
|
269
266
|
return bad_request(err=f"SearchException: {e}")
|
|
270
267
|
|
|
@@ -1922,7 +1922,7 @@ class ESCollection(Generic[ModelType]):
|
|
|
1922
1922
|
search result object that consists of the following:
|
|
1923
1923
|
|
|
1924
1924
|
{
|
|
1925
|
-
"
|
|
1925
|
+
"count": 123456, # Total number of documents matching the query
|
|
1926
1926
|
}
|
|
1927
1927
|
|
|
1928
1928
|
:param query: lucene query to search for
|
|
@@ -99,6 +99,7 @@ def search( # noqa: C901
|
|
|
99
99
|
timeout: int | None = None,
|
|
100
100
|
track_total_hits: bool = False,
|
|
101
101
|
metadata: list[str] | None = None,
|
|
102
|
+
access_control: str | None = None,
|
|
102
103
|
) -> SearchResult[dict[str, Any]]:
|
|
103
104
|
"""Search through specified index for a given query. Uses lucene search syntax for query.
|
|
104
105
|
|
|
@@ -136,6 +137,9 @@ def search( # noqa: C901
|
|
|
136
137
|
else:
|
|
137
138
|
parsed_filters = filters
|
|
138
139
|
|
|
140
|
+
if access_control:
|
|
141
|
+
parsed_filters.append(access_control)
|
|
142
|
+
|
|
139
143
|
if query is None:
|
|
140
144
|
query = "id:*"
|
|
141
145
|
|
|
@@ -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.0.0.
|
|
155
|
+
version = "4.0.0.dev871"
|
|
156
156
|
description = "Howler - API server"
|
|
157
157
|
authors = [
|
|
158
158
|
"Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
"""ETag utility module for handling HTTP ETags in Flask responses.
|
|
2
|
-
|
|
3
|
-
ETags (Entity Tags) are HTTP headers used for web cache validation and conditional requests.
|
|
4
|
-
They help optimize performance by allowing clients to cache responses and only fetch
|
|
5
|
-
new data when the resource has actually changed.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import functools
|
|
9
|
-
import re
|
|
10
|
-
|
|
11
|
-
from flask import Response, request
|
|
12
|
-
|
|
13
|
-
from howler.api import not_modified
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def add_etag(getter, check_if_match=True):
|
|
17
|
-
"""Decorator to add ETag handling to a Flask response.
|
|
18
|
-
|
|
19
|
-
This decorator implements HTTP ETag functionality for API endpoints, enabling:
|
|
20
|
-
- Conditional requests using If-Match headers
|
|
21
|
-
- Cache validation to prevent unnecessary data transfers
|
|
22
|
-
- Version tracking for resources
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
getter: Function that retrieves the object and its version
|
|
26
|
-
check_if_match (bool): Whether to check If-Match headers for conditional requests
|
|
27
|
-
|
|
28
|
-
Returns:
|
|
29
|
-
Decorated function with ETag support
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def wrapper(f):
|
|
33
|
-
"""Inner wrapper function that applies ETag functionality to the decorated function."""
|
|
34
|
-
|
|
35
|
-
@functools.wraps(f)
|
|
36
|
-
def generate_etag(*args, **kwargs):
|
|
37
|
-
"""Generate and handle ETags for the HTTP response."""
|
|
38
|
-
# Retrieve the object and its version using the provided getter function
|
|
39
|
-
# The getter should return (object, version) tuple
|
|
40
|
-
obj, version = getter(
|
|
41
|
-
kwargs.get("id", kwargs.get("username", None)),
|
|
42
|
-
as_odm=True,
|
|
43
|
-
version=True,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
# Handle conditional requests with If-Match header
|
|
47
|
-
# If the client's version matches the current version and it's a GET request
|
|
48
|
-
# without metadata parameter, return 304 Not Modified to save bandwidth
|
|
49
|
-
if (
|
|
50
|
-
check_if_match
|
|
51
|
-
and "If-Match" in request.headers
|
|
52
|
-
and request.headers["If-Match"] == version
|
|
53
|
-
and request.method == "GET"
|
|
54
|
-
and "metadata" not in request.args
|
|
55
|
-
):
|
|
56
|
-
return not_modified()
|
|
57
|
-
|
|
58
|
-
# Extract the resource type from the API path and create a cache key
|
|
59
|
-
# e.g., "/api/v1/users/123" becomes "cached_users"
|
|
60
|
-
key = re.sub(r"^\/api\/v\d+\/(\w+)\/.+$", r"cached_\1", request.path)
|
|
61
|
-
kwargs[key] = obj
|
|
62
|
-
|
|
63
|
-
# Call the original function with the cached object and version
|
|
64
|
-
values = f(*args, server_version=version, **kwargs)
|
|
65
|
-
|
|
66
|
-
# Handle different return value formats from the decorated function
|
|
67
|
-
# If there is only one return, it's just the response
|
|
68
|
-
if isinstance(values, Response):
|
|
69
|
-
# Only add ETag header for successful responses (not 409 Conflict or 400 Bad Request)
|
|
70
|
-
if values.status_code != 409 and values.status_code != 400:
|
|
71
|
-
values.headers["ETag"] = version
|
|
72
|
-
return values
|
|
73
|
-
|
|
74
|
-
# If there are two returns, it's the response and the new version
|
|
75
|
-
# This happens when the function modifies the resource and returns an updated version
|
|
76
|
-
else:
|
|
77
|
-
if values[0].status_code != 409 and values[0].status_code != 400:
|
|
78
|
-
# Add the new ETag version to successful responses
|
|
79
|
-
values[0].headers["ETag"] = values[1]
|
|
80
|
-
return values[0]
|
|
81
|
-
|
|
82
|
-
return generate_etag
|
|
83
|
-
|
|
84
|
-
return wrapper
|
|
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.0.0.dev850 → howler_api-4.0.0.dev871}/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
|
|
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.0.0.dev850 → howler_api-4.0.0.dev871}/howler/odm/models/ecs/autonomous_system.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|