howler-api 4.0.0.dev871__tar.gz → 4.0.0.dev973__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.dev871 → howler_api-4.0.0.dev973}/PKG-INFO +4 -4
- howler_api-4.0.0.dev973/howler/actions/add_to_bundle.py +171 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/demote.py +2 -2
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/remove_from_bundle.py +46 -64
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/transition.py +5 -3
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/__init__.py +1 -2
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/socket.py +6 -36
- howler_api-4.0.0.dev871/howler/services/docs_service.py → howler_api-4.0.0.dev973/howler/api/v1/__init__.py +45 -37
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/action.py +2 -1
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/analytic.py +102 -17
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/hit.py +123 -60
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/tool.py +23 -46
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/user.py +1 -1
- howler_api-4.0.0.dev973/howler/api/v1/utils/etag.py +84 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/app.py +0 -15
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/loader.py +5 -4
- howler_api-4.0.0.dev973/howler/cronjobs/action_queue_worker.py +82 -0
- howler_api-4.0.0.dev973/howler/cronjobs/retention.py +129 -0
- howler_api-4.0.0.dev973/howler/cronjobs/rules.py +279 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/collection.py +177 -46
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/howler_store.py +14 -29
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/store.py +120 -34
- howler_api-4.0.0.dev973/howler/datastore/types.py +22 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/discover.py +11 -12
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/hit.py +4 -4
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/search.py +2 -12
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/helper.py +8 -114
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/config.py +23 -8
- howler_api-4.0.0.dev973/howler/odm/models/ecs/file.py +83 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/host.py +6 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/process.py +3 -3
- howler_api-4.0.0.dev871/howler/odm/models/record.py → howler_api-4.0.0.dev973/howler/odm/models/hit.py +25 -12
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/howler_data.py +35 -3
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/view.py +0 -8
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/random_data.py +50 -372
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/__init__.py +2 -3
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/queues/comms.py +1 -2
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/security/utils.py +2 -2
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/action_service.py +95 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/config_service.py +3 -3
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/dossier_service.py +2 -1
- howler_api-4.0.0.dev973/howler/services/event_service.py +96 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/hit_service.py +174 -85
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/socket_utils.py +25 -4
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/pyproject.toml +11 -10
- howler_api-4.0.0.dev871/howler/actions/add_to_bundle.py +0 -148
- howler_api-4.0.0.dev871/howler/actions/add_to_case.py +0 -136
- howler_api-4.0.0.dev871/howler/api/v1/__init__.py +0 -44
- howler_api-4.0.0.dev871/howler/api/v1/utils/etag.py +0 -106
- howler_api-4.0.0.dev871/howler/api/v2/__init__.py +0 -44
- howler_api-4.0.0.dev871/howler/api/v2/case.py +0 -435
- howler_api-4.0.0.dev871/howler/api/v2/ingest.py +0 -371
- howler_api-4.0.0.dev871/howler/api/v2/search.py +0 -338
- howler_api-4.0.0.dev871/howler/cronjobs/correlation.py +0 -36
- howler_api-4.0.0.dev871/howler/cronjobs/retention.py +0 -61
- howler_api-4.0.0.dev871/howler/datastore/types.py +0 -13
- howler_api-4.0.0.dev871/howler/odm/constants.py +0 -20
- howler_api-4.0.0.dev871/howler/odm/mixins.py +0 -97
- howler_api-4.0.0.dev871/howler/odm/models/case.py +0 -211
- howler_api-4.0.0.dev871/howler/odm/models/ecs/file.py +0 -83
- howler_api-4.0.0.dev871/howler/odm/models/hit.py +0 -31
- howler_api-4.0.0.dev871/howler/odm/models/observable.py +0 -126
- howler_api-4.0.0.dev871/howler/services/bundle_compat_service.py +0 -273
- howler_api-4.0.0.dev871/howler/services/case_service.py +0 -905
- howler_api-4.0.0.dev871/howler/services/correlation_service.py +0 -168
- howler_api-4.0.0.dev871/howler/services/event_service.py +0 -134
- howler_api-4.0.0.dev871/howler/services/observable_service.py +0 -142
- howler_api-4.0.0.dev871/howler/services/search_service.py +0 -229
- howler_api-4.0.0.dev871/howler/services/viewer_service.py +0 -43
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/add_label.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/change_field.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/example_plugin.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/prioritization.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/promote.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/actions/remove_label.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/base.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/auth.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/clue.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/configs.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/dossier.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/help.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/notebook.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/overview.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/search.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/template.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/api/v1/view.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/classification.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/classification.yml +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/exceptions.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/logging/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/logging/audit.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/logging/format.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/net.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/net_static.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/random_user.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/common/swagger.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/config.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/cronjobs/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/cronjobs/view_cleanup.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/bulk.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/constants.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/exceptions.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/migrations/fix_process.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/operations.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/schemas.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/support/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/support/build.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/datastore/support/schemas.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/error.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/generate_mitre.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/generate_sigma_rules.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/generate_tlds.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/reindex_data.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/external/wipe_databases.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/gunicorn_config.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/healthz.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/azure.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/oauth.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/workflow.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/helper/ws.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/base.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/charter.txt +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/howler_enum.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/action.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/analytic.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/assemblyline.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/aws.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/azure.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/cbs.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/clue.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/dossier.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/agent.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/autonomous_system.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/client.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/cloud.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/code_signature.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/container.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/dns.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/egress.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/elf.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/email.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/error.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/event.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/faas.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/geo.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/group.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/hash.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/http.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/ingress.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/interface.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/network.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/observer.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/organization.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/os.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/pe.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/registry.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/related.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/rule.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/server.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/threat.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/tls.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/url.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/user.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/user_agent.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/ecs/vulnerability.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/gcp.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/lead.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/localized_label.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/overview.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/pivot.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/template.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/models/user.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/odm/randomizer.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/patched.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/plugins/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/plugins/config.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/README.md +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/counters.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/events.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/hash.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/lock.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/queues/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/queues/multi.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/queues/named.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/queues/priority.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/set.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/remote/datatypes/user_quota_tracker.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/security/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/security/socket.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/analytic_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/auth_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/jwt_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/lucene_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/notebook_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/overview_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/template_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/services/user_service.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/telemetry.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/__init__.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/annotations.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/chunk.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/compat.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/constants.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/dict_utils.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/isotime.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/list_utils.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/lucene.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/path.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/howler/utils/str_utils.py +0 -0
- {howler_api-4.0.0.dev871 → howler_api-4.0.0.dev973}/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.dev973
|
|
4
4
|
Summary: Howler - API server
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
|
|
@@ -29,7 +29,7 @@ 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)
|
|
31
31
|
Requires-Dist: flask-caching (==2.4.0)
|
|
32
|
-
Requires-Dist: gevent (
|
|
32
|
+
Requires-Dist: gevent (==23.9.1)
|
|
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)
|
|
@@ -42,7 +42,7 @@ Requires-Dist: prometheus-client (==0.25.0)
|
|
|
42
42
|
Requires-Dist: pydantic (>=2.11.4,<3.0.0)
|
|
43
43
|
Requires-Dist: pydantic-settings[yaml] (>=2.9.1,<3.0.0)
|
|
44
44
|
Requires-Dist: pydash (>=8.0.5,<9.0.0)
|
|
45
|
-
Requires-Dist: pyjwt (==2.
|
|
45
|
+
Requires-Dist: pyjwt (==2.13.0)
|
|
46
46
|
Requires-Dist: pysigma (==0.11.23)
|
|
47
47
|
Requires-Dist: pysigma-backend-elasticsearch (>=1.1.2,<2.0.0)
|
|
48
48
|
Requires-Dist: python-baseconv (==1.2.2)
|
|
@@ -51,7 +51,7 @@ Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
|
|
|
51
51
|
Requires-Dist: pytz (>=2025.2,<2026.0)
|
|
52
52
|
Requires-Dist: pyyaml (==6.0.3)
|
|
53
53
|
Requires-Dist: redis (==4.6.0)
|
|
54
|
-
Requires-Dist: requests (==2.
|
|
54
|
+
Requires-Dist: requests (==2.34.2)
|
|
55
55
|
Requires-Dist: tzdata (>=2026.1,<2027.0)
|
|
56
56
|
Requires-Dist: validators (>=0.34,<0.36)
|
|
57
57
|
Requires-Dist: wsproto (==1.3.2)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
|
|
3
|
+
from howler.actions import check_hit_limit
|
|
4
|
+
from howler.common.loader import datastore
|
|
5
|
+
from howler.datastore.operations import OdmHelper
|
|
6
|
+
from howler.odm.models.action import VALID_TRIGGERS
|
|
7
|
+
from howler.odm.models.hit import Hit
|
|
8
|
+
from howler.odm.models.user import User
|
|
9
|
+
from howler.services import hit_service
|
|
10
|
+
from howler.utils.str_utils import sanitize_lucene_query
|
|
11
|
+
|
|
12
|
+
hit_helper = OdmHelper(Hit)
|
|
13
|
+
|
|
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
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
|
|
21
|
+
"""Add a set of hits matching the query to the specified bundle.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
query (str): The query containing the matching hits
|
|
25
|
+
bundle_id (str): The `howler.id` of the bundle to add the hits to.
|
|
26
|
+
"""
|
|
27
|
+
report = []
|
|
28
|
+
|
|
29
|
+
if not bundle_id:
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
"query": query,
|
|
33
|
+
"outcome": "error",
|
|
34
|
+
"title": "Invalid Bundle ID",
|
|
35
|
+
"message": "Bundle ID cannot be empty.",
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
|
|
41
|
+
if not bundle_hit or not bundle_hit.howler.is_bundle:
|
|
42
|
+
report.append(
|
|
43
|
+
{
|
|
44
|
+
"query": query,
|
|
45
|
+
"outcome": "error",
|
|
46
|
+
"title": "Invalid Bundle",
|
|
47
|
+
"message": f"Either a hit with ID {bundle_id} does not exist, or it is not a bundle.",
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
return report
|
|
51
|
+
|
|
52
|
+
ds = datastore()
|
|
53
|
+
|
|
54
|
+
skipped_hits_bundles = ds.hit.search(
|
|
55
|
+
f"({query}) AND howler.is_bundle:true",
|
|
56
|
+
fl="howler.id",
|
|
57
|
+
)["items"]
|
|
58
|
+
|
|
59
|
+
if len(skipped_hits_bundles) > 0:
|
|
60
|
+
report.append(
|
|
61
|
+
{
|
|
62
|
+
"query": f"({query}) AND howler.is_bundle:true",
|
|
63
|
+
"outcome": "skipped",
|
|
64
|
+
"title": "Skipped Bundles",
|
|
65
|
+
"message": "Bundles cannot be added to a bundle.",
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
skipped_hits_already_added = ds.hit.search(
|
|
70
|
+
f"({query}) AND (howler.bundles:{sanitize_lucene_query(bundle_id)})",
|
|
71
|
+
fl="howler.id",
|
|
72
|
+
)["items"]
|
|
73
|
+
|
|
74
|
+
if len(skipped_hits_already_added) > 0:
|
|
75
|
+
report.append(
|
|
76
|
+
{
|
|
77
|
+
"query": f"({query}) AND (howler.bundles:{sanitize_lucene_query(bundle_id)})",
|
|
78
|
+
"outcome": "skipped",
|
|
79
|
+
"title": "Skipped Hits",
|
|
80
|
+
"message": "These hits have already been added to the specified bundle.",
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
safe_query = f"({query}) AND (-howler.bundles:({sanitize_lucene_query(bundle_id)}) AND howler.is_bundle:false)"
|
|
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"]
|
|
93
|
+
if len(matching_hits) < 1:
|
|
94
|
+
report.append(
|
|
95
|
+
{
|
|
96
|
+
"query": safe_query,
|
|
97
|
+
"outcome": "skipped",
|
|
98
|
+
"title": "No Matching Hits",
|
|
99
|
+
"message": "There were no hits matching this query.",
|
|
100
|
+
}
|
|
101
|
+
)
|
|
102
|
+
return report
|
|
103
|
+
|
|
104
|
+
ds.hit.update_by_query(
|
|
105
|
+
safe_query,
|
|
106
|
+
[hit_helper.list_add("howler.bundles", sanitize_lucene_query(bundle_id), if_missing=True)],
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
operations = [
|
|
110
|
+
hit_helper.list_add(
|
|
111
|
+
"howler.hits",
|
|
112
|
+
hit["howler"]["id"],
|
|
113
|
+
if_missing=True,
|
|
114
|
+
)
|
|
115
|
+
for hit in matching_hits
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
operations.append(hit_helper.update("howler.bundle_size", len(operations)))
|
|
119
|
+
hit_service.update_hit(
|
|
120
|
+
bundle_id,
|
|
121
|
+
operations,
|
|
122
|
+
)
|
|
123
|
+
bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
|
|
124
|
+
report.append(
|
|
125
|
+
{
|
|
126
|
+
"query": safe_query.replace("-howler.bundles", "howler.bundles"),
|
|
127
|
+
"outcome": "success",
|
|
128
|
+
"title": "Executed Successfully",
|
|
129
|
+
"message": "The specified bundle has had all matching hits added.",
|
|
130
|
+
}
|
|
131
|
+
)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
report.append(
|
|
134
|
+
{
|
|
135
|
+
"query": query,
|
|
136
|
+
"outcome": "error",
|
|
137
|
+
"title": "Failed to Execute",
|
|
138
|
+
"message": f"Unknown exception occurred: {str(e)}",
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return report
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def specification():
|
|
146
|
+
"""Specify various properties of the action, such as title, descriptions, permissions and input steps."""
|
|
147
|
+
return {
|
|
148
|
+
"id": OPERATION_ID,
|
|
149
|
+
"title": "Add to Bundle",
|
|
150
|
+
"priority": 6,
|
|
151
|
+
"i18nKey": f"operations.{OPERATION_ID}",
|
|
152
|
+
"description": {
|
|
153
|
+
"short": "Add a set of hits to a bundle",
|
|
154
|
+
"long": execute.__doc__,
|
|
155
|
+
},
|
|
156
|
+
"roles": ["automation_basic", "actionrunner_basic"],
|
|
157
|
+
"steps": [
|
|
158
|
+
{
|
|
159
|
+
"args": {"bundle_id": []},
|
|
160
|
+
"options": {},
|
|
161
|
+
"validation": {
|
|
162
|
+
"warn": {"query": "howler.bundles:($bundle_id) OR howler.is_bundle:true"},
|
|
163
|
+
"error": {
|
|
164
|
+
"query": "howler.id:$bundle_id AND howler.is_bundle:false",
|
|
165
|
+
"message": "The bundle id given must be a bundle.",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
}
|
|
169
|
+
],
|
|
170
|
+
"triggers": VALID_TRIGGERS,
|
|
171
|
+
}
|
|
@@ -9,7 +9,7 @@ from howler.odm.models.howler_data import (
|
|
|
9
9
|
Assessment,
|
|
10
10
|
AssessmentEscalationMap,
|
|
11
11
|
Escalation,
|
|
12
|
-
|
|
12
|
+
HitStatus,
|
|
13
13
|
)
|
|
14
14
|
from howler.odm.models.user import User
|
|
15
15
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
@@ -98,7 +98,7 @@ def execute(
|
|
|
98
98
|
"howler.assignment",
|
|
99
99
|
user.get("uname", "automation") if user else "automation",
|
|
100
100
|
),
|
|
101
|
-
odm_helper.update("howler.status",
|
|
101
|
+
odm_helper.update("howler.status", HitStatus.RESOLVED),
|
|
102
102
|
],
|
|
103
103
|
)
|
|
104
104
|
|
|
@@ -1,27 +1,29 @@
|
|
|
1
|
-
"""Deprecated remove_from_bundle action — delegates to case_service for item removal."""
|
|
2
|
-
|
|
3
1
|
from typing import Optional
|
|
4
2
|
|
|
5
3
|
from howler.actions import check_hit_limit
|
|
6
|
-
from howler.common.exceptions import
|
|
4
|
+
from howler.common.exceptions import HowlerException
|
|
7
5
|
from howler.common.loader import datastore
|
|
6
|
+
from howler.datastore.operations import OdmHelper
|
|
8
7
|
from howler.odm.models.action import VALID_TRIGGERS
|
|
8
|
+
from howler.odm.models.hit import Hit
|
|
9
9
|
from howler.odm.models.user import User
|
|
10
|
-
from howler.services import
|
|
10
|
+
from howler.services import hit_service
|
|
11
11
|
from howler.utils.str_utils import sanitize_lucene_query
|
|
12
12
|
|
|
13
|
+
hit_helper = OdmHelper(Hit)
|
|
14
|
+
|
|
13
15
|
OPERATION_ID = "remove_from_bundle"
|
|
14
16
|
MAX_HITS_BASIC = 10
|
|
15
17
|
MAX_HITS_ADVANCED = 1000
|
|
16
18
|
SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
|
|
17
19
|
|
|
18
20
|
|
|
19
|
-
def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
|
|
20
|
-
"""Remove a set of hits matching the query from the specified bundle
|
|
21
|
+
def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
|
|
22
|
+
"""Remove a set of hits matching the query from the specified bundle.
|
|
21
23
|
|
|
22
24
|
Args:
|
|
23
25
|
query (str): The query containing the matching hits
|
|
24
|
-
bundle_id (str): The
|
|
26
|
+
bundle_id (str): The `howler.id` of the bundle to remove the hits from.
|
|
25
27
|
"""
|
|
26
28
|
report = []
|
|
27
29
|
|
|
@@ -36,78 +38,67 @@ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] =
|
|
|
36
38
|
]
|
|
37
39
|
|
|
38
40
|
try:
|
|
39
|
-
|
|
40
|
-
if
|
|
41
|
+
bundle_hit = hit_service.get_hit(bundle_id, as_odm=True)
|
|
42
|
+
if not bundle_hit or not bundle_hit.howler.is_bundle:
|
|
41
43
|
report.append(
|
|
42
44
|
{
|
|
43
45
|
"query": query,
|
|
44
46
|
"outcome": "error",
|
|
45
47
|
"title": "Invalid Bundle",
|
|
46
|
-
"message": f"Either a hit with ID {bundle_id} does not exist, or it
|
|
48
|
+
"message": f"Either a hit with ID {bundle_id} does not exist, or it is not a bundle.",
|
|
47
49
|
}
|
|
48
50
|
)
|
|
49
51
|
return report
|
|
50
52
|
|
|
51
53
|
ds = datastore()
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return [limit_error]
|
|
58
|
-
|
|
59
|
-
matching_hits = ds.hit.search(query, rows=MAX_HITS_ADVANCED)["items"]
|
|
55
|
+
skipped_hits = ds.hit.search(
|
|
56
|
+
f"({query}) AND -howler.bundles:{sanitize_lucene_query(bundle_id)}",
|
|
57
|
+
fl="howler.id",
|
|
58
|
+
)["items"]
|
|
60
59
|
|
|
61
|
-
if
|
|
60
|
+
if len(skipped_hits) > 0:
|
|
62
61
|
report.append(
|
|
63
62
|
{
|
|
64
|
-
"query":
|
|
63
|
+
"query": f"howler.id:({' OR '.join(h.howler.id for h in skipped_hits)})",
|
|
65
64
|
"outcome": "skipped",
|
|
66
|
-
"title": "
|
|
67
|
-
"message": "
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
return report
|
|
71
|
-
|
|
72
|
-
# Get the case to check which hits are actually in it
|
|
73
|
-
case = ds.case.get(case_id)
|
|
74
|
-
if case is None:
|
|
75
|
-
report.append(
|
|
76
|
-
{
|
|
77
|
-
"query": query,
|
|
78
|
-
"outcome": "error",
|
|
79
|
-
"title": "Case Not Found",
|
|
80
|
-
"message": f"Associated case {case_id} no longer exists.",
|
|
65
|
+
"title": "Skipped Hit not in Bundle",
|
|
66
|
+
"message": "These hits already are not in the bundle.",
|
|
81
67
|
}
|
|
82
68
|
)
|
|
83
|
-
return report
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
values_to_remove = [h.howler.id for h in matching_hits if h.howler.id in case_item_values]
|
|
87
|
-
skipped_ids = [h.howler.id for h in matching_hits if h.howler.id not in case_item_values]
|
|
70
|
+
safe_query = f"{query} AND (howler.bundles:{sanitize_lucene_query(bundle_id)})"
|
|
88
71
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"title": "Skipped Hits Not in Bundle",
|
|
95
|
-
"message": "These hits are not in the bundle.",
|
|
96
|
-
}
|
|
97
|
-
)
|
|
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]
|
|
98
77
|
|
|
99
|
-
|
|
78
|
+
matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
|
|
79
|
+
if len(matching_hits) < 1:
|
|
100
80
|
report.append(
|
|
101
81
|
{
|
|
102
|
-
"query":
|
|
82
|
+
"query": safe_query,
|
|
103
83
|
"outcome": "skipped",
|
|
104
84
|
"title": "No Matching Hits",
|
|
105
|
-
"message": "
|
|
85
|
+
"message": "There were no hits matching this query.",
|
|
106
86
|
}
|
|
107
87
|
)
|
|
108
88
|
return report
|
|
109
89
|
|
|
110
|
-
|
|
90
|
+
ds.hit.update_by_query(
|
|
91
|
+
safe_query,
|
|
92
|
+
[hit_helper.list_remove("howler.bundles", bundle_id)],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
hit_service.update_hit(
|
|
96
|
+
bundle_id,
|
|
97
|
+
[hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in matching_hits],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if len(ds.hit.get(bundle_id).howler.hits) < 1:
|
|
101
|
+
hit_service.update_hit(bundle_id, [hit_helper.update("howler.is_bundle", False)])
|
|
111
102
|
|
|
112
103
|
report.append(
|
|
113
104
|
{
|
|
@@ -117,17 +108,7 @@ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] =
|
|
|
117
108
|
"message": f"Matching hits removed from bundle with id {bundle_id}",
|
|
118
109
|
}
|
|
119
110
|
)
|
|
120
|
-
|
|
121
|
-
except NotFoundException as e:
|
|
122
|
-
report.append(
|
|
123
|
-
{
|
|
124
|
-
"query": query,
|
|
125
|
-
"outcome": "error",
|
|
126
|
-
"title": "Failed to Execute",
|
|
127
|
-
"message": str(e),
|
|
128
|
-
}
|
|
129
|
-
)
|
|
130
|
-
except Exception as e:
|
|
111
|
+
except HowlerException as e:
|
|
131
112
|
report.append(
|
|
132
113
|
{
|
|
133
114
|
"query": query,
|
|
@@ -144,11 +125,11 @@ def specification():
|
|
|
144
125
|
"""Specify various properties of the action, such as title, descriptions, permissions and input steps."""
|
|
145
126
|
return {
|
|
146
127
|
"id": OPERATION_ID,
|
|
147
|
-
"title": "Remove from Bundle
|
|
128
|
+
"title": "Remove from Bundle",
|
|
148
129
|
"priority": 5,
|
|
149
130
|
"i18nKey": f"operations.{OPERATION_ID}",
|
|
150
131
|
"description": {
|
|
151
|
-
"short": "Remove a set of hits from a bundle
|
|
132
|
+
"short": "Remove a set of hits from a bundle",
|
|
152
133
|
"long": execute.__doc__,
|
|
153
134
|
},
|
|
154
135
|
"roles": ["automation_basic", "actionrunner_basic"],
|
|
@@ -156,6 +137,7 @@ def specification():
|
|
|
156
137
|
{
|
|
157
138
|
"args": {"bundle_id": []},
|
|
158
139
|
"options": {},
|
|
140
|
+
"validation": {"error": {"query": "-howler.bundles:$bundle_id"}},
|
|
159
141
|
}
|
|
160
142
|
],
|
|
161
143
|
"triggers": VALID_TRIGGERS,
|
|
@@ -9,8 +9,8 @@ from howler.helper.workflow import Workflow, WorkflowException
|
|
|
9
9
|
from howler.odm.models.action import VALID_TRIGGERS
|
|
10
10
|
from howler.odm.models.howler_data import (
|
|
11
11
|
Assessment,
|
|
12
|
+
HitStatus,
|
|
12
13
|
HitStatusTransition,
|
|
13
|
-
Status,
|
|
14
14
|
Vote,
|
|
15
15
|
)
|
|
16
16
|
from howler.odm.models.user import User
|
|
@@ -193,13 +193,15 @@ def specification():
|
|
|
193
193
|
"steps": [
|
|
194
194
|
{
|
|
195
195
|
"args": {"status": []},
|
|
196
|
-
"options": {"status":
|
|
196
|
+
"options": {"status": HitStatus.list()},
|
|
197
197
|
"validation": {"error": {"query": "-howler.status:$status"}},
|
|
198
198
|
},
|
|
199
199
|
{
|
|
200
200
|
"args": {"transition": []},
|
|
201
201
|
"options": {
|
|
202
|
-
"transition": {
|
|
202
|
+
"transition": {
|
|
203
|
+
f"status:{status}": hit_service.get_transitions(status) for status in HitStatus.list()
|
|
204
|
+
},
|
|
203
205
|
},
|
|
204
206
|
},
|
|
205
207
|
{
|
|
@@ -25,8 +25,7 @@ logger = get_logger(__file__)
|
|
|
25
25
|
|
|
26
26
|
def make_subapi_blueprint(name, api_version=1):
|
|
27
27
|
"""Create a flask Blueprint for a subapi in a standard way."""
|
|
28
|
-
|
|
29
|
-
return Blueprint(full_name, full_name, url_prefix="/".join([API_PREFIX, f"v{api_version}", name]))
|
|
28
|
+
return Blueprint(name, name, url_prefix="/".join([API_PREFIX, f"v{api_version}", name]))
|
|
30
29
|
|
|
31
30
|
|
|
32
31
|
def _make_api_response(
|
|
@@ -7,11 +7,11 @@ from flask import Blueprint, request
|
|
|
7
7
|
from opentelemetry import trace
|
|
8
8
|
|
|
9
9
|
import howler.services.event_service as event_service
|
|
10
|
-
import howler.services.viewer_service as viewer_service
|
|
11
10
|
from howler.api import ok, unauthorized
|
|
12
11
|
from howler.common.logging import get_logger
|
|
12
|
+
from howler.datastore.operations import OdmHelper
|
|
13
13
|
from howler.helper.ws import ConnectionClosed, Server
|
|
14
|
-
from howler.
|
|
14
|
+
from howler.odm.models.hit import Hit
|
|
15
15
|
from howler.security.socket import websocket_auth, ws_response
|
|
16
16
|
from howler.utils.socket_utils import check_action
|
|
17
17
|
|
|
@@ -24,16 +24,13 @@ socket_api._doc = "Endpoints concerning websocket connectivity between the clien
|
|
|
24
24
|
logger = get_logger(__file__)
|
|
25
25
|
tracer = trace.get_tracer(__name__)
|
|
26
26
|
|
|
27
|
+
hit_helper = OdmHelper(Hit)
|
|
28
|
+
|
|
27
29
|
|
|
28
30
|
@tracer.start_as_current_span(f"{__name__}.emit")
|
|
29
31
|
@socket_api.route("/emit/<event>", methods=["POST"])
|
|
30
32
|
def emit(event: str):
|
|
31
|
-
"""Emit an event to all listening websockets
|
|
32
|
-
|
|
33
|
-
.. deprecated::
|
|
34
|
-
This endpoint is deprecated. Events are now propagated via Redis pubsub
|
|
35
|
-
and no longer require a dedicated websocket pod.
|
|
36
|
-
"""
|
|
33
|
+
"""Emit an event to all listening websockets"""
|
|
37
34
|
if "Authorization" not in request.headers:
|
|
38
35
|
return unauthorized(err="Missing authorization header")
|
|
39
36
|
|
|
@@ -52,25 +49,10 @@ def emit(event: str):
|
|
|
52
49
|
return ok()
|
|
53
50
|
|
|
54
51
|
|
|
55
|
-
@tracer.start_as_current_span(f"{__name__}.get_viewers")
|
|
56
|
-
@socket_api.route("/viewers/<entity_id>", methods=["GET"])
|
|
57
|
-
@api_login(audit=False, required_priv=["R"])
|
|
58
|
-
def get_viewers(entity_id: str, **kwargs):
|
|
59
|
-
"""Get the list of users currently viewing the specified entity
|
|
60
|
-
|
|
61
|
-
Variables:
|
|
62
|
-
entity_id => The ID of the entity to get viewers for
|
|
63
|
-
|
|
64
|
-
Result Example:
|
|
65
|
-
["user1", "user2"]
|
|
66
|
-
"""
|
|
67
|
-
return ok(viewer_service.get_viewers(entity_id))
|
|
68
|
-
|
|
69
|
-
|
|
70
52
|
@tracer.start_as_current_span(f"{__name__}.connect")
|
|
71
53
|
@socket_api.route("/connect", websocket=True) # type: ignore
|
|
72
54
|
@websocket_auth(required_priv=["R"])
|
|
73
|
-
def connect(ws: Server, *args: Any, ws_id: str, **kwargs):
|
|
55
|
+
def connect(ws: Server, *args: Any, ws_id: str, **kwargs):
|
|
74
56
|
"""Connect to the server to monitor for updates via websocket
|
|
75
57
|
|
|
76
58
|
Variables:
|
|
@@ -96,20 +78,10 @@ def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
|
|
|
96
78
|
logger.debug("Sending action: %s", data)
|
|
97
79
|
ws.send(ws_response("action", data))
|
|
98
80
|
|
|
99
|
-
def send_case(data: dict[str, Any]):
|
|
100
|
-
logger.debug("Sending case update: %s", data.get("case", {}).get("case_id", "unknown"))
|
|
101
|
-
ws.send(ws_response("cases", data))
|
|
102
|
-
|
|
103
|
-
def send_viewers_update(data: dict[str, Any]):
|
|
104
|
-
logger.debug("Sending viewers update: %s", data.get("id", "unknown"))
|
|
105
|
-
ws.send(ws_response("viewers_update", data))
|
|
106
|
-
|
|
107
81
|
try:
|
|
108
82
|
event_service.on("hits", send_hit)
|
|
109
83
|
event_service.on("broadcast", send_broadcast)
|
|
110
84
|
event_service.on("action", send_action)
|
|
111
|
-
event_service.on("cases", send_case)
|
|
112
|
-
event_service.on("viewers_update", send_viewers_update)
|
|
113
85
|
while ws.connected:
|
|
114
86
|
data = ws.receive(10)
|
|
115
87
|
if data:
|
|
@@ -141,8 +113,6 @@ def connect(ws: Server, *args: Any, ws_id: str, **kwargs): # noqa: C901
|
|
|
141
113
|
event_service.off("hits", send_hit)
|
|
142
114
|
event_service.off("broadcast", send_broadcast)
|
|
143
115
|
event_service.off("action", send_action)
|
|
144
|
-
event_service.off("cases", send_case)
|
|
145
|
-
event_service.off("viewers_update", send_viewers_update)
|
|
146
116
|
|
|
147
117
|
for id, action, broadcast in outstanding_actions:
|
|
148
118
|
outstanding_actions = check_action(id, action, broadcast, outstanding_actions=outstanding_actions, **kwargs)
|
|
@@ -1,39 +1,47 @@
|
|
|
1
1
|
from textwrap import dedent
|
|
2
2
|
|
|
3
|
-
from flask import current_app, request
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
3
|
+
from flask import Blueprint, current_app, request
|
|
4
|
+
|
|
5
|
+
from howler.api import ok
|
|
6
|
+
from howler.security import api_login
|
|
7
|
+
|
|
8
|
+
API_PREFIX = "/api/v1"
|
|
9
|
+
apiv1 = Blueprint("apiv1", __name__, url_prefix=API_PREFIX)
|
|
10
|
+
apiv1._doc = "Api Documentation Version 1" # type: ignore[attr-defined] # type: ignore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@apiv1.route("/")
|
|
14
|
+
@api_login(audit=False, required_priv=["R", "W"], required_type=["user", "admin"])
|
|
15
|
+
def get_api_documentation(**kwargs):
|
|
16
|
+
"""Full API doc.
|
|
17
|
+
|
|
18
|
+
Loop through all registered API paths and display their documentation.
|
|
19
|
+
Returns a list of API definition.
|
|
20
|
+
|
|
21
|
+
Variables:
|
|
22
|
+
None
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
None
|
|
26
|
+
|
|
27
|
+
Result Example:
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
'name': "Api Doc", # Name of the api
|
|
31
|
+
'path': "/api/path/<variable>/", # API path
|
|
32
|
+
'ui_only': false, # Is UI only API
|
|
33
|
+
'methods': ["GET", "POST"], # Allowed HTTP methods
|
|
34
|
+
'description': "API doc.", # API documentation
|
|
35
|
+
'id': "api_doc", # Unique ID for the API
|
|
36
|
+
'function': "apiv1.api_doc", # Function called in the code
|
|
37
|
+
'protected': False, # Does the API require login?
|
|
38
|
+
'required_type': ['user'], # Type of users allowed to use API
|
|
39
|
+
'complete' : True # Is the API stable?
|
|
40
|
+
},
|
|
41
|
+
]
|
|
36
42
|
"""
|
|
43
|
+
user_types = kwargs["user"]["type"]
|
|
44
|
+
|
|
37
45
|
api_blueprints = {}
|
|
38
46
|
api_list = []
|
|
39
47
|
for rule in current_app.url_map.iter_rules():
|
|
@@ -50,7 +58,7 @@ def build_route_docs(version: str, user_types: list[str]):
|
|
|
50
58
|
[x.capitalize() for x in rule.endpoint[rule.endpoint.rindex(".") + 1 :].split("_")]
|
|
51
59
|
)
|
|
52
60
|
blueprint = rule.endpoint[: rule.endpoint.rindex(".")]
|
|
53
|
-
if blueprint ==
|
|
61
|
+
if blueprint == "apiv1":
|
|
54
62
|
blueprint = "documentation"
|
|
55
63
|
|
|
56
64
|
if blueprint not in api_blueprints:
|
|
@@ -66,7 +74,7 @@ def build_route_docs(version: str, user_types: list[str]):
|
|
|
66
74
|
else:
|
|
67
75
|
description = "[INCOMPLETE]\n\nTHIS API HAS NOT BEEN DOCUMENTED YET!"
|
|
68
76
|
|
|
69
|
-
api_id = rule.endpoint.replace(
|
|
77
|
+
api_id = rule.endpoint.replace("apiv1.", "").replace(".", "_")
|
|
70
78
|
|
|
71
79
|
api_list.append(
|
|
72
80
|
{
|
|
@@ -74,7 +82,7 @@ def build_route_docs(version: str, user_types: list[str]):
|
|
|
74
82
|
"required_type": sorted(required_type),
|
|
75
83
|
"name": func_title,
|
|
76
84
|
"id": api_id,
|
|
77
|
-
"function": f"api.
|
|
85
|
+
"function": f"api.v1.{rule.endpoint}",
|
|
78
86
|
"path": rule.rule,
|
|
79
87
|
"ui_only": rule.rule.startswith("%sui/" % request.path),
|
|
80
88
|
"methods": sorted(methods),
|
|
@@ -86,4 +94,4 @@ def build_route_docs(version: str, user_types: list[str]):
|
|
|
86
94
|
|
|
87
95
|
break
|
|
88
96
|
|
|
89
|
-
return {"apis": api_list, "blueprints": api_blueprints}
|
|
97
|
+
return ok({"apis": api_list, "blueprints": api_blueprints})
|
|
@@ -239,7 +239,8 @@ def execute_action(id: str, **kwargs) -> Response:
|
|
|
239
239
|
if not isinstance(execute_req, dict):
|
|
240
240
|
return bad_request(err="Incorrect data structure!")
|
|
241
241
|
|
|
242
|
-
action = datastore().action.get(id)
|
|
242
|
+
action: Action = datastore().action.get(id)
|
|
243
|
+
|
|
243
244
|
if not action:
|
|
244
245
|
return not_found(err="The specified action does not exist")
|
|
245
246
|
|