howler-api 3.4.0.dev912__tar.gz → 3.4.0.dev929__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 (203) hide show
  1. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/PKG-INFO +1 -1
  2. howler_api-3.4.0.dev929/howler/cronjobs/retention.py +129 -0
  3. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/collection.py +107 -4
  4. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/types.py +4 -0
  5. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/pyproject.toml +1 -1
  6. howler_api-3.4.0.dev912/howler/cronjobs/retention.py +0 -61
  7. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/README.md +0 -0
  8. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/__init__.py +0 -0
  9. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/__init__.py +0 -0
  10. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/add_label.py +0 -0
  11. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/add_to_bundle.py +0 -0
  12. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/change_field.py +0 -0
  13. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/demote.py +0 -0
  14. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/example_plugin.py +0 -0
  15. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/prioritization.py +0 -0
  16. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/promote.py +0 -0
  17. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/remove_from_bundle.py +0 -0
  18. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/remove_label.py +0 -0
  19. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/actions/transition.py +0 -0
  20. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/__init__.py +0 -0
  21. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/base.py +0 -0
  22. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/socket.py +0 -0
  23. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/__init__.py +0 -0
  24. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/action.py +0 -0
  25. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/analytic.py +0 -0
  26. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/auth.py +0 -0
  27. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/clue.py +0 -0
  28. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/configs.py +0 -0
  29. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/dossier.py +0 -0
  30. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/help.py +0 -0
  31. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/hit.py +0 -0
  32. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/notebook.py +0 -0
  33. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/overview.py +0 -0
  34. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/search.py +0 -0
  35. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/template.py +0 -0
  36. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/tool.py +0 -0
  37. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/user.py +0 -0
  38. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/utils/__init__.py +0 -0
  39. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/utils/etag.py +0 -0
  40. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/api/v1/view.py +0 -0
  41. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/app.py +0 -0
  42. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/README.md +0 -0
  43. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/__init__.py +0 -0
  44. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/classification.py +0 -0
  45. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/classification.yml +0 -0
  46. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/exceptions.py +0 -0
  47. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/loader.py +0 -0
  48. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/logging/__init__.py +0 -0
  49. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/logging/audit.py +0 -0
  50. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/logging/format.py +0 -0
  51. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/net.py +0 -0
  52. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/net_static.py +0 -0
  53. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/random_user.py +0 -0
  54. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/common/swagger.py +0 -0
  55. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/config.py +0 -0
  56. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/cronjobs/__init__.py +0 -0
  57. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/cronjobs/rules.py +0 -0
  58. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/cronjobs/view_cleanup.py +0 -0
  59. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/README.md +0 -0
  60. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/__init__.py +0 -0
  61. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/bulk.py +0 -0
  62. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/constants.py +0 -0
  63. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/exceptions.py +0 -0
  64. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/howler_store.py +0 -0
  65. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/migrations/fix_process.py +0 -0
  66. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/operations.py +0 -0
  67. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/schemas.py +0 -0
  68. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/store.py +0 -0
  69. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/support/__init__.py +0 -0
  70. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/support/build.py +0 -0
  71. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/datastore/support/schemas.py +0 -0
  72. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/error.py +0 -0
  73. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/README.md +0 -0
  74. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/__init__.py +0 -0
  75. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/generate_mitre.py +0 -0
  76. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/generate_sigma_rules.py +0 -0
  77. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/generate_tlds.py +0 -0
  78. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/reindex_data.py +0 -0
  79. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/external/wipe_databases.py +0 -0
  80. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/gunicorn_config.py +0 -0
  81. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/healthz.py +0 -0
  82. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/__init__.py +0 -0
  83. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/azure.py +0 -0
  84. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/discover.py +0 -0
  85. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/hit.py +0 -0
  86. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/oauth.py +0 -0
  87. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/search.py +0 -0
  88. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/workflow.py +0 -0
  89. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/helper/ws.py +0 -0
  90. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/README.md +0 -0
  91. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/__init__.py +0 -0
  92. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/base.py +0 -0
  93. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/charter.txt +0 -0
  94. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/helper.py +0 -0
  95. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/howler_enum.py +0 -0
  96. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/__init__.py +0 -0
  97. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/action.py +0 -0
  98. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/analytic.py +0 -0
  99. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/assemblyline.py +0 -0
  100. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/aws.py +0 -0
  101. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/azure.py +0 -0
  102. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/cbs.py +0 -0
  103. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/clue.py +0 -0
  104. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/config.py +0 -0
  105. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/dossier.py +0 -0
  106. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/__init__.py +0 -0
  107. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/agent.py +0 -0
  108. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/autonomous_system.py +0 -0
  109. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/client.py +0 -0
  110. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/cloud.py +0 -0
  111. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/code_signature.py +0 -0
  112. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/container.py +0 -0
  113. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/dns.py +0 -0
  114. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/egress.py +0 -0
  115. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/elf.py +0 -0
  116. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/email.py +0 -0
  117. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/error.py +0 -0
  118. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/event.py +0 -0
  119. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/faas.py +0 -0
  120. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/file.py +0 -0
  121. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/geo.py +0 -0
  122. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/group.py +0 -0
  123. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/hash.py +0 -0
  124. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/host.py +0 -0
  125. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/http.py +0 -0
  126. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/ingress.py +0 -0
  127. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/interface.py +0 -0
  128. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/network.py +0 -0
  129. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/observer.py +0 -0
  130. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/organization.py +0 -0
  131. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/os.py +0 -0
  132. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/pe.py +0 -0
  133. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/process.py +0 -0
  134. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/registry.py +0 -0
  135. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/related.py +0 -0
  136. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/rule.py +0 -0
  137. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/server.py +0 -0
  138. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/threat.py +0 -0
  139. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/tls.py +0 -0
  140. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/url.py +0 -0
  141. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/user.py +0 -0
  142. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/user_agent.py +0 -0
  143. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/ecs/vulnerability.py +0 -0
  144. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/gcp.py +0 -0
  145. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/hit.py +0 -0
  146. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/howler_data.py +0 -0
  147. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/lead.py +0 -0
  148. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/localized_label.py +0 -0
  149. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/overview.py +0 -0
  150. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/pivot.py +0 -0
  151. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/template.py +0 -0
  152. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/user.py +0 -0
  153. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/models/view.py +0 -0
  154. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/random_data.py +0 -0
  155. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/odm/randomizer.py +0 -0
  156. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/patched.py +0 -0
  157. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/plugins/__init__.py +0 -0
  158. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/plugins/config.py +0 -0
  159. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/__init__.py +0 -0
  160. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/README.md +0 -0
  161. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/__init__.py +0 -0
  162. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/counters.py +0 -0
  163. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/events.py +0 -0
  164. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/hash.py +0 -0
  165. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/lock.py +0 -0
  166. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/queues/__init__.py +0 -0
  167. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/queues/comms.py +0 -0
  168. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/queues/multi.py +0 -0
  169. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/queues/named.py +0 -0
  170. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/queues/priority.py +0 -0
  171. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/set.py +0 -0
  172. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  173. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/security/__init__.py +0 -0
  174. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/security/socket.py +0 -0
  175. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/security/utils.py +0 -0
  176. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/__init__.py +0 -0
  177. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/action_service.py +0 -0
  178. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/analytic_service.py +0 -0
  179. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/auth_service.py +0 -0
  180. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/config_service.py +0 -0
  181. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/dossier_service.py +0 -0
  182. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/event_service.py +0 -0
  183. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/hit_service.py +0 -0
  184. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/jwt_service.py +0 -0
  185. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/lucene_service.py +0 -0
  186. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/notebook_service.py +0 -0
  187. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/overview_service.py +0 -0
  188. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/template_service.py +0 -0
  189. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/services/user_service.py +0 -0
  190. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/telemetry.py +0 -0
  191. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/__init__.py +0 -0
  192. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/annotations.py +0 -0
  193. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/chunk.py +0 -0
  194. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/compat.py +0 -0
  195. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/constants.py +0 -0
  196. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/dict_utils.py +0 -0
  197. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/isotime.py +0 -0
  198. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/list_utils.py +0 -0
  199. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/lucene.py +0 -0
  200. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/path.py +0 -0
  201. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/socket_utils.py +0 -0
  202. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/str_utils.py +0 -0
  203. {howler_api-3.4.0.dev912 → howler_api-3.4.0.dev929}/howler/utils/uid.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howler-api
3
- Version: 3.4.0.dev912
3
+ Version: 3.4.0.dev929
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -0,0 +1,129 @@
1
+ import os
2
+ from datetime import datetime, timedelta
3
+ from typing import Any
4
+
5
+ from apscheduler.schedulers.base import BaseScheduler
6
+ from apscheduler.triggers.cron import CronTrigger
7
+ from pytz import timezone
8
+
9
+ from howler.common.logging import get_logger
10
+ from howler.config import DEBUG, config
11
+ from howler.datastore.howler_store import HowlerDatastore
12
+
13
+ logger = get_logger(__file__)
14
+
15
+
16
+ def execute():
17
+ """Delete any hits older than the configured time"""
18
+ from howler.common.loader import datastore
19
+
20
+ delta_kwargs = {str(config.system.retention.limit_unit): config.system.retention.limit_amount}
21
+
22
+ cutoff = (datetime.now() - timedelta(**delta_kwargs)).strftime("%Y-%m-%d")
23
+
24
+ logger.debug("Removing hits older than %s", cutoff)
25
+
26
+ ds = datastore()
27
+
28
+ ds.hit.delete_by_query(f"event.created:{{* TO {cutoff}}} OR howler.expiry:{{* TO now}}")
29
+
30
+ ds.hit.commit()
31
+
32
+ logger.debug("Deletion complete")
33
+
34
+ logger.debug("Cleaning analytics with no matching hits")
35
+ _remove_analytics_without_hits(ds)
36
+
37
+
38
+ def _remove_analytics_without_hits(ds: HowlerDatastore):
39
+
40
+ matched_analytics = _find_analytics_with_hits(ds)
41
+
42
+ if matched_analytics is not None:
43
+ ds.analytic.delete_by_search_object(
44
+ {
45
+ "bool": {
46
+ "filter": [
47
+ {
48
+ "bool": {
49
+ "must_not": [
50
+ {"exists": {"field": "rule"}},
51
+ {"exists": {"field": "rule_type"}},
52
+ ]
53
+ }
54
+ }
55
+ ],
56
+ "must_not": [{"terms": {"name": matched_analytics}}],
57
+ }
58
+ }
59
+ )
60
+ ds.analytic.commit()
61
+ else:
62
+ logger.warning(
63
+ "Aggregation search for matched analytics did not run or returned no results. "
64
+ "There is likely an issue with the query. Skipping cleanup."
65
+ )
66
+
67
+
68
+ def _find_analytics_with_hits(ds: HowlerDatastore) -> list[str] | None:
69
+
70
+ total_analytics = ds.analytic.count("id:*", filters=None)["count"]
71
+
72
+ if total_analytics:
73
+ matched_analytics = ds.hit.search(
74
+ "howler.id:*",
75
+ aggregations=[
76
+ (
77
+ "matched_analytics",
78
+ {
79
+ "terms": {
80
+ "field": "howler.analytic",
81
+ "size": total_analytics,
82
+ }
83
+ },
84
+ )
85
+ ],
86
+ rows=0,
87
+ )
88
+
89
+ if "matched_analytics" in matched_analytics["aggregations"]:
90
+ matched_analytic_names = [
91
+ bucket["key"] for bucket in matched_analytics["aggregations"]["matched_analytics"]["buckets"]
92
+ ]
93
+ else:
94
+ return None
95
+
96
+ else:
97
+ matched_analytic_names = []
98
+
99
+ return matched_analytic_names
100
+
101
+
102
+ def setup_job(sched: BaseScheduler):
103
+ """Initialize the retention job"""
104
+ if not config.system.retention.enabled:
105
+ if not DEBUG or config.system.type == "production":
106
+ logger.warning("Retention cronjob disabled! This is not recommended for a production settings.")
107
+
108
+ return
109
+
110
+ logger.debug("Initializing retention cronjob with cron %s", config.system.retention.crontab)
111
+
112
+ if DEBUG:
113
+ _kwargs: dict[str, Any] = {"next_run_time": datetime.now()}
114
+ else:
115
+ _kwargs = {}
116
+
117
+ if sched.get_job("retention"):
118
+ logger.debug("Retention job already running!")
119
+ return
120
+
121
+ sched.add_job(
122
+ id="retention",
123
+ func=execute,
124
+ trigger=CronTrigger.from_crontab(
125
+ config.system.retention.crontab, timezone=timezone(os.getenv("SCHEDULER_TZ", "America/Toronto"))
126
+ ),
127
+ **_kwargs,
128
+ )
129
+ logger.debug("Initialization complete")
@@ -38,7 +38,7 @@ from howler.datastore.support.schemas import (
38
38
  default_index,
39
39
  default_mapping,
40
40
  )
41
- from howler.datastore.types import SearchResult
41
+ from howler.datastore.types import AggSearchResult, SearchResult
42
42
  from howler.odm.base import (
43
43
  BANNED_FIELDS,
44
44
  IP,
@@ -211,9 +211,11 @@ class ESCollection(Generic[ModelType]):
211
211
  "sort": DEFAULT_SORT,
212
212
  "df": None,
213
213
  "script_fields": [],
214
+ "aggregations": None,
214
215
  }
215
216
  IGNORE_ENSURE_COLLECTION: bool = False
216
217
  ENSURE_COLLECTION_WARNED: bool = False
218
+ CUSTOM_AGG_PREFIX: str = "_custom_agg__"
217
219
 
218
220
  def __init__(self, datastore: ESStore, name, model_class=None, validate=True, max_attempts=10):
219
221
  self.replicas = int(
@@ -1119,7 +1121,7 @@ class ESCollection(Generic[ModelType]):
1119
1121
  except elasticsearch.NotFoundError:
1120
1122
  return False
1121
1123
 
1122
- def delete_by_query(self, query, workers=20, sort=None, max_docs=None):
1124
+ def delete_by_query(self, query: str, sort=None, max_docs=None):
1123
1125
  """This function should delete the underlying documents referenced by the query.
1124
1126
  It should return true if the documents were in fact properly deleted.
1125
1127
 
@@ -1127,7 +1129,18 @@ class ESCollection(Generic[ModelType]):
1127
1129
  :param workers: Number of workers used for deletion if basic currency delete is used
1128
1130
  :return: True is delete successful
1129
1131
  """
1130
- query = {"bool": {"must": {"query_string": {"query": query}}}}
1132
+ query_obj = {"bool": {"must": {"query_string": {"query": query}}}}
1133
+ success = self.delete_by_search_object(query=query_obj, sort=sort, max_docs=max_docs)
1134
+ return success
1135
+
1136
+ def delete_by_search_object(self, query: dict, sort=None, max_docs=None):
1137
+ """Delete the underlying documents matching the query object.
1138
+ Returns true if the documents were in fact properly deleted.
1139
+
1140
+ :param query: Query object following elasticsearch request structure
1141
+ :param workers: Number of workers used for deletion if basic currency delete is used
1142
+ :return: True is delete successful
1143
+ """
1131
1144
  info = self._delete_async(self.name, query=query, sort=sort_str(parse_sort(sort)), max_docs=max_docs)
1132
1145
  return info.get("deleted", 0) != 0
1133
1146
 
@@ -1503,6 +1516,26 @@ class ESCollection(Generic[ModelType]):
1503
1516
  },
1504
1517
  }
1505
1518
 
1519
+ # Add any arbitrary aggregations
1520
+ if parsed_values["aggregations"]:
1521
+ query_body.setdefault("aggregations", {})
1522
+ cluster_settings = self.datastore.client.cluster.get_settings(include_defaults=True, flat_settings=True)
1523
+ flattened_settings = {
1524
+ **cluster_settings["defaults"],
1525
+ **cluster_settings["transient"],
1526
+ **cluster_settings["persistent"],
1527
+ }
1528
+ max_buckets = int(flattened_settings["search.max_buckets"])
1529
+ for agg_name, agg_args in parsed_values["aggregations"]:
1530
+ if any("size" in agg_def and agg_def["size"] > max_buckets for agg_def in agg_args.values()):
1531
+ # verify the size of the agg query doesn't exceed the max
1532
+ warnings.warn(
1533
+ f"Aggregation {agg_name} has a size argument higher than the maximum allowed "
1534
+ f"buckets of the cluster ({max_buckets}). Skipping aggregation."
1535
+ )
1536
+ continue
1537
+ query_body["aggregations"][f"{self.CUSTOM_AGG_PREFIX}{agg_name}"] = agg_args
1538
+
1506
1539
  try:
1507
1540
  if deep_paging_id is not None and not deep_paging_id == "*":
1508
1541
  # Get the next page
@@ -1554,6 +1587,8 @@ class ESCollection(Generic[ModelType]):
1554
1587
  use_archive: bool = False,
1555
1588
  track_total_hits: bool = False,
1556
1589
  script_fields: list[str] = [],
1590
+ *,
1591
+ aggregations: None = None,
1557
1592
  ) -> SearchResult[ModelType]: ...
1558
1593
 
1559
1594
  @overload
@@ -1572,8 +1607,50 @@ class ESCollection(Generic[ModelType]):
1572
1607
  use_archive: bool = False,
1573
1608
  track_total_hits: bool = False,
1574
1609
  script_fields: list[str] = [],
1610
+ *,
1611
+ aggregations: None = None,
1575
1612
  ) -> SearchResult[dict[str, typing.Any]]: ...
1576
1613
 
1614
+ @overload
1615
+ def search(
1616
+ self,
1617
+ query: str | None,
1618
+ as_obj: Literal[True] = True,
1619
+ offset: int = 0,
1620
+ rows: int | None = None,
1621
+ sort: typing.Any = None,
1622
+ fl: str | None = None,
1623
+ timeout: int | None = None,
1624
+ filters: list[str] | str | None = None,
1625
+ access_control: typing.Any = None,
1626
+ deep_paging_id: str | None = None,
1627
+ use_archive: bool = False,
1628
+ track_total_hits: bool = False,
1629
+ script_fields: list[str] = [],
1630
+ *,
1631
+ aggregations: list[tuple[str, dict]],
1632
+ ) -> AggSearchResult[ModelType]: ...
1633
+
1634
+ @overload
1635
+ def search(
1636
+ self,
1637
+ query: str | None,
1638
+ as_obj: Literal[False],
1639
+ offset: int = 0,
1640
+ rows: int | None = None,
1641
+ sort: typing.Any = None,
1642
+ fl: str | None = None,
1643
+ timeout: int | None = None,
1644
+ filters: list[str] | str | None = None,
1645
+ access_control: typing.Any = None,
1646
+ deep_paging_id: str | None = None,
1647
+ use_archive: bool = False,
1648
+ track_total_hits: bool = False,
1649
+ script_fields: list[str] = [],
1650
+ *,
1651
+ aggregations: list[tuple[str, dict]],
1652
+ ) -> AggSearchResult[dict[str, typing.Any]]: ...
1653
+
1577
1654
  def search(
1578
1655
  self,
1579
1656
  query,
@@ -1589,6 +1666,8 @@ class ESCollection(Generic[ModelType]):
1589
1666
  use_archive=False,
1590
1667
  track_total_hits=None,
1591
1668
  script_fields=[],
1669
+ *,
1670
+ aggregations=None,
1592
1671
  ):
1593
1672
  """This function should perform a search through the datastore and return a
1594
1673
  search result object that consist on the following::
@@ -1605,6 +1684,14 @@ class ESCollection(Generic[ModelType]):
1605
1684
  }, ...]
1606
1685
  }
1607
1686
 
1687
+ If aggregations are provided the search result will include an additional field::
1688
+
1689
+ {
1690
+ "aggregations": { # Dictionary where the keys are the keys of the `aggregations` parameter
1691
+ "agg_name": {...} # and the values are the results of the aggregations
1692
+ }
1693
+ }
1694
+
1608
1695
  :param script_fields: List of name/script tuple of fields to be evaluated at runtime
1609
1696
  :param track_total_hits: Return to total matching document count
1610
1697
  :param use_archive: Query also the archive
@@ -1618,6 +1705,8 @@ class ESCollection(Generic[ModelType]):
1618
1705
  :param timeout: maximum time of execution
1619
1706
  :param filters: additional queries to run on the original query to reduce the scope
1620
1707
  :param access_control: access control parameters to limiti the scope of the query
1708
+ :param aggregations: optional list of arbitrary aggregations to run alongside the query
1709
+ structured the same way as the es rest query aggs field
1621
1710
  :return: a search result object
1622
1711
  """
1623
1712
  if offset is None:
@@ -1660,6 +1749,9 @@ class ESCollection(Generic[ModelType]):
1660
1749
  if script_fields:
1661
1750
  args.append(("script_fields", script_fields))
1662
1751
 
1752
+ if aggregations:
1753
+ args.append(("aggregations", aggregations))
1754
+
1663
1755
  result = self._search(
1664
1756
  args,
1665
1757
  deep_paging_id=deep_paging_id,
@@ -1667,13 +1759,24 @@ class ESCollection(Generic[ModelType]):
1667
1759
  track_total_hits=track_total_hits,
1668
1760
  )
1669
1761
 
1670
- ret_data: SearchResult = {
1762
+ ret_data: SearchResult | AggSearchResult
1763
+ ret_data = {
1671
1764
  "offset": int(offset),
1672
1765
  "rows": int(rows),
1673
1766
  "total": int(result["hits"]["total"]["value"]),
1674
1767
  "items": [self._format_output(doc, field_list, as_obj=as_obj) for doc in result["hits"]["hits"]],
1675
1768
  }
1676
1769
 
1770
+ if aggregations:
1771
+ ret_data = {
1772
+ **ret_data,
1773
+ "aggregations": {
1774
+ k[len(self.CUSTOM_AGG_PREFIX) :]: v
1775
+ for k, v in result.get("aggregations", {}).items()
1776
+ if k.startswith(self.CUSTOM_AGG_PREFIX)
1777
+ },
1778
+ }
1779
+
1677
1780
  new_deep_paging_id = result.get("_scroll_id", None)
1678
1781
 
1679
1782
  # Check if the scroll is finished and close it
@@ -16,3 +16,7 @@ class SearchResult(TypedDict, Generic[SearchResultType]):
16
16
  total: int
17
17
  items: list[SearchResultType]
18
18
  next_deep_paging_id: NotRequired[str | None]
19
+
20
+
21
+ class AggSearchResult(SearchResult[SearchResultType]):
22
+ aggregations: dict[str, dict]
@@ -152,7 +152,7 @@ suppress-none-returning = true
152
152
  [tool.poetry]
153
153
  package-mode = true
154
154
  name = "howler-api"
155
- version = "3.4.0.dev912"
155
+ version = "3.4.0.dev929"
156
156
  description = "Howler - API server"
157
157
  authors = [
158
158
  "Canadian Centre for Cyber Security <howler@cyber.gc.ca>",
@@ -1,61 +0,0 @@
1
- import os
2
- from datetime import datetime, timedelta
3
- from typing import Any
4
-
5
- from apscheduler.schedulers.base import BaseScheduler
6
- from apscheduler.triggers.cron import CronTrigger
7
- from pytz import timezone
8
-
9
- from howler.common.logging import get_logger
10
- from howler.config import DEBUG, config
11
-
12
- logger = get_logger(__file__)
13
-
14
-
15
- def execute():
16
- """Delete any hits older than the configured time"""
17
- from howler.common.loader import datastore
18
-
19
- delta_kwargs = {str(config.system.retention.limit_unit): config.system.retention.limit_amount}
20
-
21
- cutoff = (datetime.now() - timedelta(**delta_kwargs)).strftime("%Y-%m-%d")
22
-
23
- logger.debug("Removing hits older than %s", cutoff)
24
-
25
- ds = datastore()
26
-
27
- ds.hit.delete_by_query(f"event.created:{{* TO {cutoff}}} OR howler.expiry:{{* TO now}}")
28
-
29
- ds.hit.commit()
30
-
31
- logger.debug("Deletion complete")
32
-
33
-
34
- def setup_job(sched: BaseScheduler):
35
- """Initialize the retention job"""
36
- if not config.system.retention.enabled:
37
- if not DEBUG or config.system.type == "production":
38
- logger.warning("Retention cronjob disabled! This is not recommended for a production settings.")
39
-
40
- return
41
-
42
- logger.debug("Initializing retention cronjob with cron %s", config.system.retention.crontab)
43
-
44
- if DEBUG:
45
- _kwargs: dict[str, Any] = {"next_run_time": datetime.now()}
46
- else:
47
- _kwargs = {}
48
-
49
- if sched.get_job("retention"):
50
- logger.debug("Retention job already running!")
51
- return
52
-
53
- sched.add_job(
54
- id="retention",
55
- func=execute,
56
- trigger=CronTrigger.from_crontab(
57
- config.system.retention.crontab, timezone=timezone(os.getenv("SCHEDULER_TZ", "America/Toronto"))
58
- ),
59
- **_kwargs,
60
- )
61
- logger.debug("Initialization complete")