howler-api 4.0.0.dev724__tar.gz → 4.0.0.dev799__tar.gz

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