howler-api 4.1.0.dev1435__tar.gz → 4.1.0.dev1452__tar.gz

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