howler-api 3.4.0.dev830__tar.gz → 3.4.0.dev834__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-3.4.0.dev830 → howler_api-3.4.0.dev834}/PKG-INFO +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/__init__.py +91 -21
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/add_label.py +3 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/add_to_bundle.py +15 -3
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/change_field.py +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/demote.py +3 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/example_plugin.py +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/prioritization.py +3 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/promote.py +3 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/remove_from_bundle.py +16 -5
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/remove_label.py +3 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/actions/transition.py +16 -3
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/action.py +23 -7
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/loader.py +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/user.py +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/random_data.py +13 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/pyproject.toml +1 -1
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/base.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/socket.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/analytic.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/auth.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/clue.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/configs.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/dossier.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/help.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/hit.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/notebook.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/overview.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/search.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/template.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/tool.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/user.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/utils/etag.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/api/v1/view.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/app.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/classification.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/classification.yml +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/exceptions.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/logging/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/logging/audit.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/logging/format.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/net.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/net_static.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/random_user.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/common/swagger.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/config.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/cronjobs/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/cronjobs/retention.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/cronjobs/rules.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/bulk.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/collection.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/constants.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/exceptions.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/howler_store.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/operations.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/schemas.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/store.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/support/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/support/build.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/support/schemas.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/datastore/types.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/error.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/generate_mitre.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/generate_tlds.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/reindex_data.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/external/wipe_databases.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/gunicorn_config.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/healthz.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/azure.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/discover.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/hit.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/oauth.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/search.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/workflow.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/helper/ws.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/base.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/charter.txt +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/helper.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/howler_enum.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/action.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/analytic.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/aws.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/azure.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/cbs.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/clue.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/config.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/dossier.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/file.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/host.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/process.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/gcp.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/hit.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/howler_data.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/lead.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/localized_label.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/overview.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/pivot.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/template.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/models/view.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/odm/randomizer.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/patched.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/plugins/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/plugins/config.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/README.md +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/events.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/queues/comms.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/set.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/security/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/security/socket.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/security/utils.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/action_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/analytic_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/auth_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/config_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/dossier_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/event_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/hit_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/jwt_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/lucene_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/notebook_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/overview_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/template_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/services/user_service.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/telemetry.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/__init__.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/annotations.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/chunk.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/constants.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/dict_utils.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/isotime.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/list_utils.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/lucene.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/path.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/socket_utils.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/str_utils.py +0 -0
- {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev834}/howler/utils/uid.py +0 -0
|
@@ -2,8 +2,10 @@ import importlib
|
|
|
2
2
|
import os
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from types import ModuleType
|
|
5
6
|
from typing import Any, Optional
|
|
6
7
|
|
|
8
|
+
from howler.common.loader import datastore
|
|
7
9
|
from howler.common.logging import get_logger
|
|
8
10
|
from howler.odm.models.user import User
|
|
9
11
|
from howler.plugins import get_plugins
|
|
@@ -12,6 +14,9 @@ logger = get_logger(__file__)
|
|
|
12
14
|
|
|
13
15
|
PLUGIN_PATH = Path(os.environ.get("HWL_PLUGIN_DIRECTORY", "/etc/howler/plugins"))
|
|
14
16
|
|
|
17
|
+
# Roles that grant advanced hit limits
|
|
18
|
+
ADVANCED_ROLES = {"automation_advanced", "actionrunner_advanced", "admin"}
|
|
19
|
+
|
|
15
20
|
|
|
16
21
|
def __sanitize_specification(spec: dict[str, Any]) -> dict[str, Any]:
|
|
17
22
|
"""Adapt the specification for use in the UI
|
|
@@ -71,6 +76,68 @@ def __sanitize_report(report: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
|
71
76
|
return sanitized
|
|
72
77
|
|
|
73
78
|
|
|
79
|
+
def _get_operation(operation_id: str) -> ModuleType | None:
|
|
80
|
+
"""Find and return an operation module by ID."""
|
|
81
|
+
operation = None
|
|
82
|
+
try:
|
|
83
|
+
operation = importlib.import_module(f"howler.actions.{operation_id}")
|
|
84
|
+
except ImportError:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
if not operation:
|
|
88
|
+
for plugin in get_plugins():
|
|
89
|
+
if not plugin.modules.operations:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
operation = next(
|
|
93
|
+
(operation for operation in plugin.modules.operations if operation.OPERATION_ID == operation_id), None
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if operation:
|
|
97
|
+
break
|
|
98
|
+
|
|
99
|
+
return operation
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def check_hit_limit(
|
|
103
|
+
query: str, user: User, max_hits_basic: int | None, max_hits_advanced: int | None
|
|
104
|
+
) -> dict[str, Any] | None:
|
|
105
|
+
"""Check if the user exceeds hit count limits. Returns error dict if exceeded, None otherwise.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
query: The query to check hit count for (should be the effective query the operation will execute).
|
|
109
|
+
user: The user executing the action.
|
|
110
|
+
max_hits_basic: Maximum hits allowed for basic users (None for no limit).
|
|
111
|
+
max_hits_advanced: Maximum hits allowed for advanced users (None for no limit).
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Error dict if limit exceeded, None otherwise.
|
|
115
|
+
"""
|
|
116
|
+
is_advanced = bool(ADVANCED_ROLES & set(user["type"]))
|
|
117
|
+
limit = max_hits_advanced if is_advanced else max_hits_basic
|
|
118
|
+
|
|
119
|
+
if limit is not None:
|
|
120
|
+
hit_count = datastore().hit.search(query, rows=0)["total"]
|
|
121
|
+
if hit_count > limit:
|
|
122
|
+
return {
|
|
123
|
+
"query": query,
|
|
124
|
+
"outcome": "error",
|
|
125
|
+
"title": "Hit limit exceeded",
|
|
126
|
+
"message": (
|
|
127
|
+
f"This action affects {hit_count} hits, but you can only process {limit} at a time. "
|
|
128
|
+
"Contact an administrator for bulk operations."
|
|
129
|
+
),
|
|
130
|
+
}
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _check_hit_limit(operation: ModuleType, query: str, user: User) -> dict[str, Any] | None:
|
|
135
|
+
"""Central hit limit check using raw query. Skipped if operation sets SKIP_CENTRAL_LIMIT."""
|
|
136
|
+
max_hits_basic = getattr(operation, "MAX_HITS_BASIC", None)
|
|
137
|
+
max_hits_advanced = getattr(operation, "MAX_HITS_ADVANCED", None)
|
|
138
|
+
return check_hit_limit(query, user, max_hits_basic, max_hits_advanced)
|
|
139
|
+
|
|
140
|
+
|
|
74
141
|
def execute(
|
|
75
142
|
operation_id: str,
|
|
76
143
|
query: str,
|
|
@@ -89,23 +156,7 @@ def execute(
|
|
|
89
156
|
Returns:
|
|
90
157
|
list[dict[str, Any]]: A report on the execution
|
|
91
158
|
"""
|
|
92
|
-
operation =
|
|
93
|
-
try:
|
|
94
|
-
operation = importlib.import_module(f"howler.actions.{operation_id}")
|
|
95
|
-
except ImportError:
|
|
96
|
-
pass
|
|
97
|
-
|
|
98
|
-
if not operation:
|
|
99
|
-
for plugin in get_plugins():
|
|
100
|
-
if not plugin.modules.operations:
|
|
101
|
-
continue
|
|
102
|
-
|
|
103
|
-
operation = next(
|
|
104
|
-
(operation for operation in plugin.modules.operations if operation.OPERATION_ID == operation_id), None
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
if operation:
|
|
108
|
-
break
|
|
159
|
+
operation = _get_operation(operation_id)
|
|
109
160
|
|
|
110
161
|
if not operation:
|
|
111
162
|
return [
|
|
@@ -117,9 +168,21 @@ def execute(
|
|
|
117
168
|
}
|
|
118
169
|
]
|
|
119
170
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
171
|
+
if not user:
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
"query": query,
|
|
175
|
+
"outcome": "error",
|
|
176
|
+
"title": "Authentication required",
|
|
177
|
+
"message": "You must be logged in to execute actions.",
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
user_roles = set(user["type"])
|
|
182
|
+
is_admin = "admin" in user_roles
|
|
183
|
+
required_roles = set(operation.specification()["roles"])
|
|
184
|
+
has_roles = required_roles & user_roles
|
|
185
|
+
if not is_admin and not has_roles:
|
|
123
186
|
return [
|
|
124
187
|
{
|
|
125
188
|
"query": query,
|
|
@@ -127,11 +190,18 @@ def execute(
|
|
|
127
190
|
"title": "Insufficient permissions",
|
|
128
191
|
"message": (
|
|
129
192
|
f"The operation ID provided ({operation_id}) requires permissions you do not have "
|
|
130
|
-
f"({', '.join(
|
|
193
|
+
f"(missing one of: {', '.join(sorted(required_roles))}). "
|
|
194
|
+
"Contact HOWLER Support for more information."
|
|
131
195
|
),
|
|
132
196
|
}
|
|
133
197
|
]
|
|
134
198
|
|
|
199
|
+
# Skip central limit check if operation handles it locally with transformed query
|
|
200
|
+
if not getattr(operation, "SKIP_CENTRAL_LIMIT", False):
|
|
201
|
+
limit_error = _check_hit_limit(operation, query, user)
|
|
202
|
+
if limit_error:
|
|
203
|
+
return [limit_error]
|
|
204
|
+
|
|
135
205
|
report = operation.execute(query=query, request_id=request_id, user=user, **kwargs)
|
|
136
206
|
|
|
137
207
|
return __sanitize_report(report)
|
|
@@ -10,6 +10,8 @@ from howler.utils.str_utils import sanitize_lucene_query
|
|
|
10
10
|
hit_helper = OdmHelper(Hit)
|
|
11
11
|
|
|
12
12
|
OPERATION_ID = "add_label"
|
|
13
|
+
MAX_HITS_BASIC = 20
|
|
14
|
+
MAX_HITS_ADVANCED = 1000
|
|
13
15
|
|
|
14
16
|
CATEGORIES = list(Label.fields().keys())
|
|
15
17
|
|
|
@@ -99,7 +101,7 @@ def specification():
|
|
|
99
101
|
"short": "Add a label to a hit",
|
|
100
102
|
"long": execute.__doc__,
|
|
101
103
|
},
|
|
102
|
-
"roles": ["automation_basic"],
|
|
104
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
103
105
|
"steps": [
|
|
104
106
|
{
|
|
105
107
|
"args": {"category": [], "label": []},
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from howler.actions import check_hit_limit
|
|
3
4
|
from howler.common.loader import datastore
|
|
4
5
|
from howler.datastore.operations import OdmHelper
|
|
5
6
|
from howler.odm.models.action import VALID_TRIGGERS
|
|
6
7
|
from howler.odm.models.hit import Hit
|
|
8
|
+
from howler.odm.models.user import User
|
|
7
9
|
from howler.services import hit_service
|
|
8
10
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
9
11
|
|
|
10
12
|
hit_helper = OdmHelper(Hit)
|
|
11
13
|
|
|
12
14
|
OPERATION_ID = "add_to_bundle"
|
|
15
|
+
MAX_HITS_BASIC = 10
|
|
16
|
+
MAX_HITS_ADVANCED = 1000
|
|
17
|
+
SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
|
|
13
18
|
|
|
14
19
|
|
|
15
|
-
def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
20
|
+
def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
|
|
16
21
|
"""Add a set of hits matching the query to the specified bundle.
|
|
17
22
|
|
|
18
23
|
Args:
|
|
@@ -77,7 +82,14 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
|
77
82
|
)
|
|
78
83
|
|
|
79
84
|
safe_query = f"({query}) AND (-howler.bundles:({sanitize_lucene_query(bundle_id)}) AND howler.is_bundle:false)"
|
|
80
|
-
|
|
85
|
+
|
|
86
|
+
# Check hit limit against the effective query (not raw query)
|
|
87
|
+
if user:
|
|
88
|
+
limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
|
|
89
|
+
if limit_error:
|
|
90
|
+
return [limit_error]
|
|
91
|
+
|
|
92
|
+
matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
|
|
81
93
|
if len(matching_hits) < 1:
|
|
82
94
|
report.append(
|
|
83
95
|
{
|
|
@@ -141,7 +153,7 @@ def specification():
|
|
|
141
153
|
"short": "Add a set of hits to a bundle",
|
|
142
154
|
"long": execute.__doc__,
|
|
143
155
|
},
|
|
144
|
-
"roles": ["automation_basic"],
|
|
156
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
145
157
|
"steps": [
|
|
146
158
|
{
|
|
147
159
|
"args": {"bundle_id": []},
|
|
@@ -65,7 +65,7 @@ def specification():
|
|
|
65
65
|
"short": "Change one of the fields of a hit",
|
|
66
66
|
"long": execute.__doc__,
|
|
67
67
|
},
|
|
68
|
-
"roles": ["automation_advanced", "admin"],
|
|
68
|
+
"roles": ["automation_advanced", "actionrunner_advanced", "admin"],
|
|
69
69
|
"steps": [
|
|
70
70
|
{
|
|
71
71
|
"args": {"field": [], "value": []},
|
|
@@ -15,6 +15,8 @@ from howler.odm.models.user import User
|
|
|
15
15
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
16
16
|
|
|
17
17
|
OPERATION_ID = "demote"
|
|
18
|
+
MAX_HITS_BASIC = 10
|
|
19
|
+
MAX_HITS_ADVANCED = 1000
|
|
18
20
|
|
|
19
21
|
ESCALATIONS = [esc for esc in Escalation.list() if esc != Escalation.EVIDENCE]
|
|
20
22
|
|
|
@@ -131,7 +133,7 @@ def specification():
|
|
|
131
133
|
"short": "Demote a hit",
|
|
132
134
|
"long": execute.__doc__,
|
|
133
135
|
},
|
|
134
|
-
"roles": ["automation_basic"],
|
|
136
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
135
137
|
"steps": [
|
|
136
138
|
{
|
|
137
139
|
"args": {"escalation": []},
|
|
@@ -67,7 +67,7 @@ def specification():
|
|
|
67
67
|
},
|
|
68
68
|
# What roles should be necessary to run this action? In general, automation_basic should always be required,
|
|
69
69
|
# while automation_advanced should be set when this action could be dangerous or costly in terms of resources.
|
|
70
|
-
"roles": ["automation_basic"],
|
|
70
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
71
71
|
# What data should the user be required to provide? This is split intop steps, so arguments can depend on each
|
|
72
72
|
# other, giving basic control flow for specifying arguments.
|
|
73
73
|
"steps": [
|
|
@@ -6,6 +6,8 @@ from howler.odm.models.hit import Hit
|
|
|
6
6
|
hit_helper = OdmHelper(Hit)
|
|
7
7
|
|
|
8
8
|
OPERATION_ID = "prioritization"
|
|
9
|
+
MAX_HITS_BASIC = 10
|
|
10
|
+
MAX_HITS_ADVANCED = 1000
|
|
9
11
|
|
|
10
12
|
VALID_FIELDS = ["reliability", "severity", "volume", "confidence", "score"]
|
|
11
13
|
|
|
@@ -82,7 +84,7 @@ def specification():
|
|
|
82
84
|
"short": "Change one of the prioritization fields of a hit",
|
|
83
85
|
"long": execute.__doc__,
|
|
84
86
|
},
|
|
85
|
-
"roles": ["automation_basic"],
|
|
87
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
86
88
|
"steps": [
|
|
87
89
|
{
|
|
88
90
|
"args": {"field": [], "value": []},
|
|
@@ -13,6 +13,8 @@ from howler.odm.models.howler_data import (
|
|
|
13
13
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
14
14
|
|
|
15
15
|
OPERATION_ID = "promote"
|
|
16
|
+
MAX_HITS_BASIC = 10
|
|
17
|
+
MAX_HITS_ADVANCED = 1000
|
|
16
18
|
|
|
17
19
|
ESCALATIONS = [esc for esc in Escalation.list() if esc != Escalation.MISS]
|
|
18
20
|
|
|
@@ -118,7 +120,7 @@ def specification():
|
|
|
118
120
|
"short": "Promote a hit",
|
|
119
121
|
"long": execute.__doc__,
|
|
120
122
|
},
|
|
121
|
-
"roles": ["automation_basic"],
|
|
123
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
122
124
|
"steps": [
|
|
123
125
|
{
|
|
124
126
|
"args": {"escalation": []},
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
from typing import Optional
|
|
2
2
|
|
|
3
|
+
from howler.actions import check_hit_limit
|
|
3
4
|
from howler.common.exceptions import HowlerException
|
|
4
5
|
from howler.common.loader import datastore
|
|
5
6
|
from howler.datastore.operations import OdmHelper
|
|
6
7
|
from howler.odm.models.action import VALID_TRIGGERS
|
|
7
8
|
from howler.odm.models.hit import Hit
|
|
9
|
+
from howler.odm.models.user import User
|
|
8
10
|
from howler.services import hit_service
|
|
9
11
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
10
12
|
|
|
11
13
|
hit_helper = OdmHelper(Hit)
|
|
12
14
|
|
|
13
15
|
OPERATION_ID = "remove_from_bundle"
|
|
16
|
+
MAX_HITS_BASIC = 10
|
|
17
|
+
MAX_HITS_ADVANCED = 1000
|
|
18
|
+
SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
|
|
14
19
|
|
|
15
20
|
|
|
16
|
-
def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
21
|
+
def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
|
|
17
22
|
"""Remove a set of hits matching the query from the specified bundle.
|
|
18
23
|
|
|
19
24
|
Args:
|
|
@@ -62,9 +67,15 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
|
62
67
|
}
|
|
63
68
|
)
|
|
64
69
|
|
|
65
|
-
safe_query = f"{query} AND (howler.bundles:{bundle_id})"
|
|
70
|
+
safe_query = f"{query} AND (howler.bundles:{sanitize_lucene_query(bundle_id)})"
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
# Check hit limit against the effective query (not raw query)
|
|
73
|
+
if user:
|
|
74
|
+
limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
|
|
75
|
+
if limit_error:
|
|
76
|
+
return [limit_error]
|
|
77
|
+
|
|
78
|
+
matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
|
|
68
79
|
if len(matching_hits) < 1:
|
|
69
80
|
report.append(
|
|
70
81
|
{
|
|
@@ -83,7 +94,7 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
|
|
|
83
94
|
|
|
84
95
|
hit_service.update_hit(
|
|
85
96
|
bundle_id,
|
|
86
|
-
[hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in
|
|
97
|
+
[hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in matching_hits],
|
|
87
98
|
)
|
|
88
99
|
|
|
89
100
|
if len(ds.hit.get(bundle_id).howler.hits) < 1:
|
|
@@ -121,7 +132,7 @@ def specification():
|
|
|
121
132
|
"short": "Remove a set of hits from a bundle",
|
|
122
133
|
"long": execute.__doc__,
|
|
123
134
|
},
|
|
124
|
-
"roles": ["automation_basic"],
|
|
135
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
125
136
|
"steps": [
|
|
126
137
|
{
|
|
127
138
|
"args": {"bundle_id": []},
|
|
@@ -10,6 +10,8 @@ from howler.utils.str_utils import sanitize_lucene_query
|
|
|
10
10
|
hit_helper = OdmHelper(Hit)
|
|
11
11
|
|
|
12
12
|
OPERATION_ID = "remove_label"
|
|
13
|
+
MAX_HITS_BASIC = 20
|
|
14
|
+
MAX_HITS_ADVANCED = 1000
|
|
13
15
|
|
|
14
16
|
CATEGORIES = list(Label.fields().keys())
|
|
15
17
|
|
|
@@ -99,7 +101,7 @@ def specification():
|
|
|
99
101
|
"short": "Remove a label from a hit",
|
|
100
102
|
"long": execute.__doc__,
|
|
101
103
|
},
|
|
102
|
-
"roles": ["automation_basic"],
|
|
104
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
103
105
|
"steps": [
|
|
104
106
|
{
|
|
105
107
|
"args": {"category": [], "label": []},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
from typing import Optional, cast
|
|
3
3
|
|
|
4
|
+
from howler.actions import check_hit_limit
|
|
4
5
|
from howler.common.exceptions import InvalidDataException, NotFoundException
|
|
5
6
|
from howler.common.loader import datastore
|
|
6
7
|
from howler.common.logging import get_logger
|
|
@@ -17,6 +18,9 @@ from howler.services import event_service, hit_service
|
|
|
17
18
|
from howler.utils.list_utils import flatten_list
|
|
18
19
|
|
|
19
20
|
OPERATION_ID = "transition"
|
|
21
|
+
MAX_HITS_BASIC = 10
|
|
22
|
+
MAX_HITS_ADVANCED = 1000
|
|
23
|
+
SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
|
|
20
24
|
|
|
21
25
|
log = get_logger(__file__)
|
|
22
26
|
|
|
@@ -70,8 +74,17 @@ def execute(
|
|
|
70
74
|
status (str): The status from which to transition.
|
|
71
75
|
transition (str): The transition to attempt to execute.
|
|
72
76
|
"""
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
# Build effective query with status filter
|
|
78
|
+
effective_query = f"({query}) AND howler.status:{status}"
|
|
79
|
+
|
|
80
|
+
# Check hit limit against the effective query (not raw query)
|
|
81
|
+
limit_error = check_hit_limit(effective_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
|
|
82
|
+
if limit_error:
|
|
83
|
+
return [limit_error]
|
|
84
|
+
|
|
85
|
+
is_advanced = "automation_advanced" in user.type or "actionrunner_advanced" in user.type or "admin" in user.type
|
|
86
|
+
rows = MAX_HITS_ADVANCED if is_advanced else MAX_HITS_BASIC
|
|
87
|
+
hits = datastore().hit.search(effective_query, rows=rows, fl="howler.id")
|
|
75
88
|
|
|
76
89
|
ids = [hit.howler.id for hit in hits["items"]]
|
|
77
90
|
|
|
@@ -176,7 +189,7 @@ def specification():
|
|
|
176
189
|
"short": "Transition a hit",
|
|
177
190
|
"long": execute.__doc__,
|
|
178
191
|
},
|
|
179
|
-
"roles": ["automation_basic"],
|
|
192
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
180
193
|
"steps": [
|
|
181
194
|
{
|
|
182
195
|
"args": {"status": []},
|
|
@@ -23,7 +23,11 @@ action_api._doc = "Endpoints relating to bulk actions and automation" # type: i
|
|
|
23
23
|
|
|
24
24
|
@generate_swagger_docs()
|
|
25
25
|
@action_api.route("/")
|
|
26
|
-
@api_login(
|
|
26
|
+
@api_login(
|
|
27
|
+
audit=False,
|
|
28
|
+
check_xsrf_token=False,
|
|
29
|
+
required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
|
|
30
|
+
)
|
|
27
31
|
def get_actions(**_) -> Response:
|
|
28
32
|
"""Get a list of existing actions
|
|
29
33
|
|
|
@@ -43,7 +47,7 @@ def get_actions(**_) -> Response:
|
|
|
43
47
|
|
|
44
48
|
@generate_swagger_docs()
|
|
45
49
|
@action_api.route("/", methods=["POST"])
|
|
46
|
-
@api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
|
|
50
|
+
@api_login(audit=False, check_xsrf_token=False, required_type=["admin", "automation_basic", "automation_advanced"])
|
|
47
51
|
def add_action(user: User, **_) -> Response:
|
|
48
52
|
"""Create a new action
|
|
49
53
|
|
|
@@ -97,7 +101,7 @@ def add_action(user: User, **_) -> Response:
|
|
|
97
101
|
@api_login(
|
|
98
102
|
audit=False,
|
|
99
103
|
check_xsrf_token=False,
|
|
100
|
-
required_type=["automation_basic"],
|
|
104
|
+
required_type=["admin", "automation_basic", "automation_advanced"],
|
|
101
105
|
)
|
|
102
106
|
def update_action(id: str, user: User, **_) -> Response:
|
|
103
107
|
"""Update an existing action
|
|
@@ -164,7 +168,7 @@ def update_action(id: str, user: User, **_) -> Response:
|
|
|
164
168
|
|
|
165
169
|
@generate_swagger_docs()
|
|
166
170
|
@action_api.route("/<id>", methods=["DELETE"])
|
|
167
|
-
@api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
|
|
171
|
+
@api_login(audit=True, check_xsrf_token=False, required_type=["admin", "automation_basic", "automation_advanced"])
|
|
168
172
|
def delete_action(id: str, user: User, **kwargs) -> Response:
|
|
169
173
|
"""Delete an existing action
|
|
170
174
|
|
|
@@ -200,7 +204,11 @@ def delete_action(id: str, user: User, **kwargs) -> Response:
|
|
|
200
204
|
|
|
201
205
|
@generate_swagger_docs()
|
|
202
206
|
@action_api.route("/<id>/execute", methods=["POST"])
|
|
203
|
-
@api_login(
|
|
207
|
+
@api_login(
|
|
208
|
+
audit=True,
|
|
209
|
+
check_xsrf_token=False,
|
|
210
|
+
required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
|
|
211
|
+
)
|
|
204
212
|
def execute_action(id: str, **kwargs) -> Response:
|
|
205
213
|
"""Execute one or more actions on a given query
|
|
206
214
|
|
|
@@ -275,7 +283,11 @@ def execute_action(id: str, **kwargs) -> Response:
|
|
|
275
283
|
|
|
276
284
|
@generate_swagger_docs()
|
|
277
285
|
@action_api.route("/operations")
|
|
278
|
-
@api_login(
|
|
286
|
+
@api_login(
|
|
287
|
+
audit=False,
|
|
288
|
+
check_xsrf_token=False,
|
|
289
|
+
required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
|
|
290
|
+
)
|
|
279
291
|
def get_operations(**_) -> Response:
|
|
280
292
|
"""Get a list of operations the user can run on a query
|
|
281
293
|
|
|
@@ -295,7 +307,11 @@ def get_operations(**_) -> Response:
|
|
|
295
307
|
|
|
296
308
|
@generate_swagger_docs()
|
|
297
309
|
@action_api.route("/execute", methods=["POST"])
|
|
298
|
-
@api_login(
|
|
310
|
+
@api_login(
|
|
311
|
+
audit=True,
|
|
312
|
+
check_xsrf_token=False,
|
|
313
|
+
required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
|
|
314
|
+
)
|
|
299
315
|
def execute_operations(**kwargs) -> Response:
|
|
300
316
|
"""Execute one or more operations on a given query
|
|
301
317
|
|
|
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
|
|
|
13
13
|
|
|
14
14
|
APP_NAME = os.environ.get("APP_NAME", "howler")
|
|
15
15
|
APP_PREFIX = os.environ.get("APP_PREFIX", "hwl")
|
|
16
|
-
USER_TYPES = {"admin", "user", "automation_basic", "automation_advanced"}
|
|
16
|
+
USER_TYPES = {"admin", "user", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"}
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def env_substitute(buffer):
|
|
@@ -63,7 +63,7 @@ class User(odm.Model):
|
|
|
63
63
|
access_control: str = odm.Keyword(index=False, store=False, optional=True, description="Access control filter")
|
|
64
64
|
type: list[str] = odm.List(
|
|
65
65
|
odm.Enum(values=loader.USER_TYPES),
|
|
66
|
-
default=["user", "
|
|
66
|
+
default=["user", "actionrunner_basic"],
|
|
67
67
|
description="Type of user",
|
|
68
68
|
)
|
|
69
69
|
uname: str = odm.Keyword(copyto="__text__", description="Username")
|
|
@@ -119,7 +119,14 @@ def create_users(ds):
|
|
|
119
119
|
"email": "admin@howler.cyber.gc.ca",
|
|
120
120
|
"password": admin_hash,
|
|
121
121
|
"uname": "admin",
|
|
122
|
-
"type": [
|
|
122
|
+
"type": [
|
|
123
|
+
"admin",
|
|
124
|
+
"user",
|
|
125
|
+
"automation_basic",
|
|
126
|
+
"automation_advanced",
|
|
127
|
+
"actionrunner_basic",
|
|
128
|
+
"actionrunner_advanced",
|
|
129
|
+
],
|
|
123
130
|
"groups": [
|
|
124
131
|
"group1",
|
|
125
132
|
"group2",
|
|
@@ -169,6 +176,11 @@ def create_users(ds):
|
|
|
169
176
|
"password": user_hash,
|
|
170
177
|
},
|
|
171
178
|
},
|
|
179
|
+
"type": [
|
|
180
|
+
"user",
|
|
181
|
+
"automation_basic",
|
|
182
|
+
"actionrunner_basic",
|
|
183
|
+
],
|
|
172
184
|
"password": user_hash,
|
|
173
185
|
"uname": "user",
|
|
174
186
|
"favourite_views": [user_view.view_id],
|
|
@@ -152,7 +152,7 @@ suppress-none-returning = true
|
|
|
152
152
|
[tool.poetry]
|
|
153
153
|
package-mode = true
|
|
154
154
|
name = "howler-api"
|
|
155
|
-
version = "3.4.0.
|
|
155
|
+
version = "3.4.0.dev834"
|
|
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
|