howler-api 4.0.0.dev803__tar.gz → 4.0.0.dev846__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 (220) hide show
  1. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/PKG-INFO +5 -5
  2. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/__init__.py +91 -21
  3. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/add_label.py +3 -1
  4. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/add_to_bundle.py +15 -3
  5. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/change_field.py +1 -1
  6. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/demote.py +3 -1
  7. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/example_plugin.py +1 -1
  8. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/prioritization.py +3 -1
  9. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/promote.py +3 -1
  10. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/remove_from_bundle.py +15 -3
  11. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/remove_label.py +3 -1
  12. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/transition.py +16 -3
  13. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/socket.py +6 -1
  14. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/action.py +23 -7
  15. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/user.py +2 -4
  16. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v2/case.py +103 -1
  17. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v2/ingest.py +27 -0
  18. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/app.py +5 -0
  19. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/classification.py +162 -82
  20. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/loader.py +1 -1
  21. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/config.py +1 -1
  22. howler_api-4.0.0.dev846/howler/cronjobs/correlation.py +36 -0
  23. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/collection.py +27 -0
  24. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/azure.py +7 -2
  25. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/oauth.py +40 -16
  26. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/search.py +1 -1
  27. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/case.py +25 -0
  28. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/config.py +15 -1
  29. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/record.py +7 -0
  30. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/user.py +2 -1
  31. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/random_data.py +57 -7
  32. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/randomizer.py +6 -4
  33. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/case_service.py +242 -60
  34. howler_api-4.0.0.dev846/howler/services/correlation_service.py +168 -0
  35. howler_api-4.0.0.dev846/howler/services/event_service.py +134 -0
  36. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/jwt_service.py +2 -2
  37. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/user_service.py +38 -14
  38. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/pyproject.toml +9 -9
  39. howler_api-4.0.0.dev803/howler/services/event_service.py +0 -96
  40. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/README.md +0 -0
  41. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/__init__.py +0 -0
  42. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/actions/add_to_case.py +0 -0
  43. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/__init__.py +0 -0
  44. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/base.py +0 -0
  45. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/__init__.py +0 -0
  46. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/analytic.py +0 -0
  47. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/auth.py +0 -0
  48. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/clue.py +0 -0
  49. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/configs.py +0 -0
  50. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/dossier.py +0 -0
  51. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/help.py +0 -0
  52. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/hit.py +0 -0
  53. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/notebook.py +0 -0
  54. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/overview.py +0 -0
  55. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/search.py +0 -0
  56. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/template.py +0 -0
  57. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/tool.py +0 -0
  58. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/utils/__init__.py +0 -0
  59. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/utils/etag.py +0 -0
  60. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v1/view.py +0 -0
  61. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v2/__init__.py +0 -0
  62. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/api/v2/search.py +0 -0
  63. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/README.md +0 -0
  64. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/__init__.py +0 -0
  65. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/classification.yml +0 -0
  66. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/exceptions.py +0 -0
  67. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/logging/__init__.py +0 -0
  68. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/logging/audit.py +0 -0
  69. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/logging/format.py +0 -0
  70. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/net.py +0 -0
  71. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/net_static.py +0 -0
  72. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/random_user.py +0 -0
  73. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/common/swagger.py +0 -0
  74. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/cronjobs/__init__.py +0 -0
  75. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/cronjobs/retention.py +0 -0
  76. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/cronjobs/view_cleanup.py +0 -0
  77. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/README.md +0 -0
  78. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/__init__.py +0 -0
  79. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/bulk.py +0 -0
  80. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/constants.py +0 -0
  81. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/exceptions.py +0 -0
  82. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/howler_store.py +0 -0
  83. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/migrations/fix_process.py +0 -0
  84. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/operations.py +0 -0
  85. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/schemas.py +0 -0
  86. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/store.py +0 -0
  87. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/support/__init__.py +0 -0
  88. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/support/build.py +0 -0
  89. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/support/schemas.py +0 -0
  90. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/datastore/types.py +0 -0
  91. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/error.py +0 -0
  92. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/README.md +0 -0
  93. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/__init__.py +0 -0
  94. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/generate_mitre.py +0 -0
  95. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/generate_sigma_rules.py +0 -0
  96. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/generate_tlds.py +0 -0
  97. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/reindex_data.py +0 -0
  98. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/external/wipe_databases.py +0 -0
  99. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/gunicorn_config.py +0 -0
  100. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/healthz.py +0 -0
  101. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/__init__.py +0 -0
  102. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/discover.py +0 -0
  103. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/hit.py +0 -0
  104. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/workflow.py +0 -0
  105. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/helper/ws.py +0 -0
  106. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/README.md +0 -0
  107. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/__init__.py +0 -0
  108. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/base.py +0 -0
  109. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/charter.txt +0 -0
  110. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/constants.py +0 -0
  111. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/helper.py +0 -0
  112. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/howler_enum.py +0 -0
  113. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/mixins.py +0 -0
  114. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/__init__.py +0 -0
  115. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/action.py +0 -0
  116. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/analytic.py +0 -0
  117. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/assemblyline.py +0 -0
  118. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/aws.py +0 -0
  119. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/azure.py +0 -0
  120. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/cbs.py +0 -0
  121. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/clue.py +0 -0
  122. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/dossier.py +0 -0
  123. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/__init__.py +0 -0
  124. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/agent.py +0 -0
  125. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/autonomous_system.py +0 -0
  126. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/client.py +0 -0
  127. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/cloud.py +0 -0
  128. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/code_signature.py +0 -0
  129. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/container.py +0 -0
  130. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/dns.py +0 -0
  131. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/egress.py +0 -0
  132. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/elf.py +0 -0
  133. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/email.py +0 -0
  134. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/error.py +0 -0
  135. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/event.py +0 -0
  136. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/faas.py +0 -0
  137. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/file.py +0 -0
  138. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/geo.py +0 -0
  139. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/group.py +0 -0
  140. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/hash.py +0 -0
  141. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/host.py +0 -0
  142. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/http.py +0 -0
  143. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/ingress.py +0 -0
  144. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/interface.py +0 -0
  145. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/network.py +0 -0
  146. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/observer.py +0 -0
  147. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/organization.py +0 -0
  148. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/os.py +0 -0
  149. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/pe.py +0 -0
  150. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/process.py +0 -0
  151. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/registry.py +0 -0
  152. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/related.py +0 -0
  153. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/rule.py +0 -0
  154. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/server.py +0 -0
  155. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/threat.py +0 -0
  156. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/tls.py +0 -0
  157. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/url.py +0 -0
  158. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/user.py +0 -0
  159. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/user_agent.py +0 -0
  160. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/ecs/vulnerability.py +0 -0
  161. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/gcp.py +0 -0
  162. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/hit.py +0 -0
  163. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/howler_data.py +0 -0
  164. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/lead.py +0 -0
  165. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/localized_label.py +0 -0
  166. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/observable.py +0 -0
  167. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/overview.py +0 -0
  168. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/pivot.py +0 -0
  169. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/template.py +0 -0
  170. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/odm/models/view.py +0 -0
  171. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/patched.py +0 -0
  172. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/plugins/__init__.py +0 -0
  173. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/plugins/config.py +0 -0
  174. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/__init__.py +0 -0
  175. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/README.md +0 -0
  176. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/__init__.py +0 -0
  177. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/counters.py +0 -0
  178. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/events.py +0 -0
  179. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/hash.py +0 -0
  180. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/lock.py +0 -0
  181. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/queues/__init__.py +0 -0
  182. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/queues/comms.py +0 -0
  183. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/queues/multi.py +0 -0
  184. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/queues/named.py +0 -0
  185. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/queues/priority.py +0 -0
  186. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/set.py +0 -0
  187. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  188. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/security/__init__.py +0 -0
  189. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/security/socket.py +0 -0
  190. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/security/utils.py +0 -0
  191. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/__init__.py +0 -0
  192. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/action_service.py +0 -0
  193. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/analytic_service.py +0 -0
  194. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/auth_service.py +0 -0
  195. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/bundle_compat_service.py +0 -0
  196. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/config_service.py +0 -0
  197. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/docs_service.py +0 -0
  198. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/dossier_service.py +0 -0
  199. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/hit_service.py +0 -0
  200. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/lucene_service.py +0 -0
  201. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/notebook_service.py +0 -0
  202. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/observable_service.py +0 -0
  203. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/overview_service.py +0 -0
  204. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/search_service.py +0 -0
  205. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/template_service.py +0 -0
  206. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/services/viewer_service.py +0 -0
  207. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/telemetry.py +0 -0
  208. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/__init__.py +0 -0
  209. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/annotations.py +0 -0
  210. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/chunk.py +0 -0
  211. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/compat.py +0 -0
  212. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/constants.py +0 -0
  213. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/dict_utils.py +0 -0
  214. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/isotime.py +0 -0
  215. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/list_utils.py +0 -0
  216. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/lucene.py +0 -0
  217. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/path.py +0 -0
  218. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/socket_utils.py +0 -0
  219. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/howler/utils/str_utils.py +0 -0
  220. {howler_api-4.0.0.dev803 → howler_api-4.0.0.dev846}/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.dev803
3
+ Version: 4.0.0.dev846
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -28,7 +28,7 @@ Requires-Dist: chevron (==0.14.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)
31
- Requires-Dist: flask-caching (==2.3.1)
31
+ Requires-Dist: flask-caching (==2.4.0)
32
32
  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)
@@ -38,7 +38,7 @@ Requires-Dist: opentelemetry-instrumentation-flask (==0.61b0)
38
38
  Requires-Dist: opentelemetry-sdk (==1.40.0)
39
39
  Requires-Dist: packaging (<25.0)
40
40
  Requires-Dist: passlib (==1.7.4)
41
- Requires-Dist: prometheus-client (==0.24.1)
41
+ Requires-Dist: prometheus-client (==0.25.0)
42
42
  Requires-Dist: pydantic (>=2.11.4,<3.0.0)
43
43
  Requires-Dist: pydantic-settings[yaml] (>=2.9.1,<3.0.0)
44
44
  Requires-Dist: pydash (>=8.0.5,<9.0.0)
@@ -51,10 +51,10 @@ Requires-Dist: python-dotenv (>=1.1.0,<2.0.0)
51
51
  Requires-Dist: pytz (>=2025.2,<2026.0)
52
52
  Requires-Dist: pyyaml (==6.0.3)
53
53
  Requires-Dist: redis (==4.6.0)
54
- Requires-Dist: requests (==2.32.5)
54
+ Requires-Dist: requests (==2.33.1)
55
55
  Requires-Dist: tzdata (>=2026.1,<2027.0)
56
56
  Requires-Dist: validators (>=0.34,<0.36)
57
- Requires-Dist: wsproto (==1.2.0)
57
+ Requires-Dist: wsproto (==1.3.2)
58
58
  Project-URL: Documentation, https://cybercentrecanada.github.io/howler/developer/backend/
59
59
  Project-URL: Homepage, https://cybercentrecanada.github.io/howler/
60
60
  Project-URL: Repository, https://github.com/CybercentreCanada/howler-api
@@ -2,8 +2,10 @@ import importlib
2
2
  import os
3
3
  import re
4
4
  from pathlib import Path
5
+ from types import ModuleType
5
6
  from typing import Any, Optional
6
7
 
8
+ from howler.common.loader import datastore
7
9
  from howler.common.logging import get_logger
8
10
  from howler.odm.models.user import User
9
11
  from howler.plugins import get_plugins
@@ -12,6 +14,9 @@ logger = get_logger(__file__)
12
14
 
13
15
  PLUGIN_PATH = Path(os.environ.get("HWL_PLUGIN_DIRECTORY", "/etc/howler/plugins"))
14
16
 
17
+ # Roles that grant advanced hit limits
18
+ ADVANCED_ROLES = {"automation_advanced", "actionrunner_advanced", "admin"}
19
+
15
20
 
16
21
  def __sanitize_specification(spec: dict[str, Any]) -> dict[str, Any]:
17
22
  """Adapt the specification for use in the UI
@@ -71,6 +76,68 @@ def __sanitize_report(report: list[dict[str, Any]]) -> list[dict[str, Any]]:
71
76
  return sanitized
72
77
 
73
78
 
79
+ def _get_operation(operation_id: str) -> ModuleType | None:
80
+ """Find and return an operation module by ID."""
81
+ operation = None
82
+ try:
83
+ operation = importlib.import_module(f"howler.actions.{operation_id}")
84
+ except ImportError:
85
+ pass
86
+
87
+ if not operation:
88
+ for plugin in get_plugins():
89
+ if not plugin.modules.operations:
90
+ continue
91
+
92
+ operation = next(
93
+ (operation for operation in plugin.modules.operations if operation.OPERATION_ID == operation_id), None
94
+ )
95
+
96
+ if operation:
97
+ break
98
+
99
+ return operation
100
+
101
+
102
+ def check_hit_limit(
103
+ query: str, user: User, max_hits_basic: int | None, max_hits_advanced: int | None
104
+ ) -> dict[str, Any] | None:
105
+ """Check if the user exceeds hit count limits. Returns error dict if exceeded, None otherwise.
106
+
107
+ Args:
108
+ query: The query to check hit count for (should be the effective query the operation will execute).
109
+ user: The user executing the action.
110
+ max_hits_basic: Maximum hits allowed for basic users (None for no limit).
111
+ max_hits_advanced: Maximum hits allowed for advanced users (None for no limit).
112
+
113
+ Returns:
114
+ Error dict if limit exceeded, None otherwise.
115
+ """
116
+ is_advanced = bool(ADVANCED_ROLES & set(user["type"]))
117
+ limit = max_hits_advanced if is_advanced else max_hits_basic
118
+
119
+ if limit is not None:
120
+ hit_count = datastore().hit.search(query, rows=0)["total"]
121
+ if hit_count > limit:
122
+ return {
123
+ "query": query,
124
+ "outcome": "error",
125
+ "title": "Hit limit exceeded",
126
+ "message": (
127
+ f"This action affects {hit_count} hits, but you can only process {limit} at a time. "
128
+ "Contact an administrator for bulk operations."
129
+ ),
130
+ }
131
+ return None
132
+
133
+
134
+ def _check_hit_limit(operation: ModuleType, query: str, user: User) -> dict[str, Any] | None:
135
+ """Central hit limit check using raw query. Skipped if operation sets SKIP_CENTRAL_LIMIT."""
136
+ max_hits_basic = getattr(operation, "MAX_HITS_BASIC", None)
137
+ max_hits_advanced = getattr(operation, "MAX_HITS_ADVANCED", None)
138
+ return check_hit_limit(query, user, max_hits_basic, max_hits_advanced)
139
+
140
+
74
141
  def execute(
75
142
  operation_id: str,
76
143
  query: str,
@@ -89,23 +156,7 @@ def execute(
89
156
  Returns:
90
157
  list[dict[str, Any]]: A report on the execution
91
158
  """
92
- operation = None
93
- try:
94
- operation = importlib.import_module(f"howler.actions.{operation_id}")
95
- except ImportError:
96
- pass
97
-
98
- if not operation:
99
- for plugin in get_plugins():
100
- if not plugin.modules.operations:
101
- continue
102
-
103
- operation = next(
104
- (operation for operation in plugin.modules.operations if operation.OPERATION_ID == operation_id), None
105
- )
106
-
107
- if operation:
108
- break
159
+ operation = _get_operation(operation_id)
109
160
 
110
161
  if not operation:
111
162
  return [
@@ -117,9 +168,21 @@ def execute(
117
168
  }
118
169
  ]
119
170
 
120
- user_roles: set[str] = set(user["type"] if user else [])
121
- missing_roles = set(operation.specification()["roles"]) - user_roles
122
- if missing_roles:
171
+ if not user:
172
+ return [
173
+ {
174
+ "query": query,
175
+ "outcome": "error",
176
+ "title": "Authentication required",
177
+ "message": "You must be logged in to execute actions.",
178
+ }
179
+ ]
180
+
181
+ user_roles = set(user["type"])
182
+ is_admin = "admin" in user_roles
183
+ required_roles = set(operation.specification()["roles"])
184
+ has_roles = required_roles & user_roles
185
+ if not is_admin and not has_roles:
123
186
  return [
124
187
  {
125
188
  "query": query,
@@ -127,11 +190,18 @@ def execute(
127
190
  "title": "Insufficient permissions",
128
191
  "message": (
129
192
  f"The operation ID provided ({operation_id}) requires permissions you do not have "
130
- f"({', '.join(missing_roles)}). Contact HOWLER Support for more information."
193
+ f"(missing one of: {', '.join(sorted(required_roles))}). "
194
+ "Contact HOWLER Support for more information."
131
195
  ),
132
196
  }
133
197
  ]
134
198
 
199
+ # Skip central limit check if operation handles it locally with transformed query
200
+ if not getattr(operation, "SKIP_CENTRAL_LIMIT", False):
201
+ limit_error = _check_hit_limit(operation, query, user)
202
+ if limit_error:
203
+ return [limit_error]
204
+
135
205
  report = operation.execute(query=query, request_id=request_id, user=user, **kwargs)
136
206
 
137
207
  return __sanitize_report(report)
@@ -10,6 +10,8 @@ from howler.utils.str_utils import sanitize_lucene_query
10
10
  hit_helper = OdmHelper(Hit)
11
11
 
12
12
  OPERATION_ID = "add_label"
13
+ MAX_HITS_BASIC = 20
14
+ MAX_HITS_ADVANCED = 1000
13
15
 
14
16
  CATEGORIES = list(Label.fields().keys())
15
17
 
@@ -99,7 +101,7 @@ def specification():
99
101
  "short": "Add a label to a hit",
100
102
  "long": execute.__doc__,
101
103
  },
102
- "roles": ["automation_basic"],
104
+ "roles": ["automation_basic", "actionrunner_basic"],
103
105
  "steps": [
104
106
  {
105
107
  "args": {"category": [], "label": []},
@@ -2,16 +2,21 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
+ from howler.actions import check_hit_limit
5
6
  from howler.common.exceptions import NotFoundException
6
7
  from howler.common.loader import datastore
7
8
  from howler.odm.models.action import VALID_TRIGGERS
9
+ from howler.odm.models.user import User
8
10
  from howler.services import bundle_compat_service, case_service
9
11
  from howler.utils.str_utils import sanitize_lucene_query
10
12
 
11
13
  OPERATION_ID = "add_to_bundle"
14
+ MAX_HITS_BASIC = 10
15
+ MAX_HITS_ADVANCED = 1000
16
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
12
17
 
13
18
 
14
- def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
19
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs): # noqa: C901
15
20
  """Add a set of hits matching the query to the specified bundle (deprecated — uses cases).
16
21
 
17
22
  Args:
@@ -44,7 +49,14 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
44
49
  return report
45
50
 
46
51
  ds = datastore()
47
- matching_hits = ds.hit.search(query, rows=1000)["items"]
52
+
53
+ # Check hit limit against the query before searching
54
+ if user:
55
+ limit_error = check_hit_limit(query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
56
+ if limit_error:
57
+ return [limit_error]
58
+
59
+ matching_hits = ds.hit.search(query, rows=MAX_HITS_ADVANCED)["items"]
48
60
 
49
61
  if not matching_hits:
50
62
  report.append(
@@ -125,7 +137,7 @@ def specification():
125
137
  "short": "Add a set of hits to a bundle (deprecated — uses cases)",
126
138
  "long": execute.__doc__,
127
139
  },
128
- "roles": ["automation_basic"],
140
+ "roles": ["automation_basic", "actionrunner_basic"],
129
141
  "steps": [
130
142
  {
131
143
  "args": {"bundle_id": []},
@@ -65,7 +65,7 @@ def specification():
65
65
  "short": "Change one of the fields of a hit",
66
66
  "long": execute.__doc__,
67
67
  },
68
- "roles": ["automation_advanced", "admin"],
68
+ "roles": ["automation_advanced", "actionrunner_advanced", "admin"],
69
69
  "steps": [
70
70
  {
71
71
  "args": {"field": [], "value": []},
@@ -15,6 +15,8 @@ from howler.odm.models.user import User
15
15
  from howler.utils.str_utils import sanitize_lucene_query
16
16
 
17
17
  OPERATION_ID = "demote"
18
+ MAX_HITS_BASIC = 10
19
+ MAX_HITS_ADVANCED = 1000
18
20
 
19
21
  ESCALATIONS = [esc for esc in Escalation.list() if esc != Escalation.EVIDENCE]
20
22
 
@@ -131,7 +133,7 @@ def specification():
131
133
  "short": "Demote a hit",
132
134
  "long": execute.__doc__,
133
135
  },
134
- "roles": ["automation_basic"],
136
+ "roles": ["automation_basic", "actionrunner_basic"],
135
137
  "steps": [
136
138
  {
137
139
  "args": {"escalation": []},
@@ -67,7 +67,7 @@ def specification():
67
67
  },
68
68
  # What roles should be necessary to run this action? In general, automation_basic should always be required,
69
69
  # while automation_advanced should be set when this action could be dangerous or costly in terms of resources.
70
- "roles": ["automation_basic"],
70
+ "roles": ["automation_basic", "actionrunner_basic"],
71
71
  # What data should the user be required to provide? This is split intop steps, so arguments can depend on each
72
72
  # other, giving basic control flow for specifying arguments.
73
73
  "steps": [
@@ -6,6 +6,8 @@ from howler.odm.models.hit import Hit
6
6
  hit_helper = OdmHelper(Hit)
7
7
 
8
8
  OPERATION_ID = "prioritization"
9
+ MAX_HITS_BASIC = 10
10
+ MAX_HITS_ADVANCED = 1000
9
11
 
10
12
  VALID_FIELDS = ["reliability", "severity", "volume", "confidence", "score"]
11
13
 
@@ -82,7 +84,7 @@ def specification():
82
84
  "short": "Change one of the prioritization fields of a hit",
83
85
  "long": execute.__doc__,
84
86
  },
85
- "roles": ["automation_basic"],
87
+ "roles": ["automation_basic", "actionrunner_basic"],
86
88
  "steps": [
87
89
  {
88
90
  "args": {"field": [], "value": []},
@@ -13,6 +13,8 @@ from howler.odm.models.howler_data import (
13
13
  from howler.utils.str_utils import sanitize_lucene_query
14
14
 
15
15
  OPERATION_ID = "promote"
16
+ MAX_HITS_BASIC = 10
17
+ MAX_HITS_ADVANCED = 1000
16
18
 
17
19
  ESCALATIONS = [esc for esc in Escalation.list() if esc != Escalation.MISS]
18
20
 
@@ -118,7 +120,7 @@ def specification():
118
120
  "short": "Promote a hit",
119
121
  "long": execute.__doc__,
120
122
  },
121
- "roles": ["automation_basic"],
123
+ "roles": ["automation_basic", "actionrunner_basic"],
122
124
  "steps": [
123
125
  {
124
126
  "args": {"escalation": []},
@@ -2,16 +2,21 @@
2
2
 
3
3
  from typing import Optional
4
4
 
5
+ from howler.actions import check_hit_limit
5
6
  from howler.common.exceptions import NotFoundException
6
7
  from howler.common.loader import datastore
7
8
  from howler.odm.models.action import VALID_TRIGGERS
9
+ from howler.odm.models.user import User
8
10
  from howler.services import bundle_compat_service, case_service
9
11
  from howler.utils.str_utils import sanitize_lucene_query
10
12
 
11
13
  OPERATION_ID = "remove_from_bundle"
14
+ MAX_HITS_BASIC = 10
15
+ MAX_HITS_ADVANCED = 1000
16
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
12
17
 
13
18
 
14
- def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
19
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs): # noqa: C901
15
20
  """Remove a set of hits matching the query from the specified bundle (deprecated — uses cases).
16
21
 
17
22
  Args:
@@ -44,7 +49,14 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
44
49
  return report
45
50
 
46
51
  ds = datastore()
47
- matching_hits = ds.hit.search(query, rows=1000)["items"]
52
+
53
+ # Check hit limit against the query before searching
54
+ if user:
55
+ limit_error = check_hit_limit(query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
56
+ if limit_error:
57
+ return [limit_error]
58
+
59
+ matching_hits = ds.hit.search(query, rows=MAX_HITS_ADVANCED)["items"]
48
60
 
49
61
  if not matching_hits:
50
62
  report.append(
@@ -139,7 +151,7 @@ def specification():
139
151
  "short": "Remove a set of hits from a bundle (deprecated — uses cases)",
140
152
  "long": execute.__doc__,
141
153
  },
142
- "roles": ["automation_basic"],
154
+ "roles": ["automation_basic", "actionrunner_basic"],
143
155
  "steps": [
144
156
  {
145
157
  "args": {"bundle_id": []},
@@ -10,6 +10,8 @@ from howler.utils.str_utils import sanitize_lucene_query
10
10
  hit_helper = OdmHelper(Hit)
11
11
 
12
12
  OPERATION_ID = "remove_label"
13
+ MAX_HITS_BASIC = 20
14
+ MAX_HITS_ADVANCED = 1000
13
15
 
14
16
  CATEGORIES = list(Label.fields().keys())
15
17
 
@@ -99,7 +101,7 @@ def specification():
99
101
  "short": "Remove a label from a hit",
100
102
  "long": execute.__doc__,
101
103
  },
102
- "roles": ["automation_basic"],
104
+ "roles": ["automation_basic", "actionrunner_basic"],
103
105
  "steps": [
104
106
  {
105
107
  "args": {"category": [], "label": []},
@@ -1,6 +1,7 @@
1
1
  import inspect
2
2
  from typing import Optional, cast
3
3
 
4
+ from howler.actions import check_hit_limit
4
5
  from howler.common.exceptions import InvalidDataException, NotFoundException
5
6
  from howler.common.loader import datastore
6
7
  from howler.common.logging import get_logger
@@ -17,6 +18,9 @@ from howler.services import event_service, hit_service
17
18
  from howler.utils.list_utils import flatten_list
18
19
 
19
20
  OPERATION_ID = "transition"
21
+ MAX_HITS_BASIC = 10
22
+ MAX_HITS_ADVANCED = 1000
23
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
20
24
 
21
25
  log = get_logger(__file__)
22
26
 
@@ -70,8 +74,17 @@ def execute(
70
74
  status (str): The status from which to transition.
71
75
  transition (str): The transition to attempt to execute.
72
76
  """
73
- rows = 1000 if "automation_advanced" in user.type else 10
74
- hits = datastore().hit.search(f"({query}) AND howler.status:{status}", rows=rows, fl="howler.id")
77
+ # Build effective query with status filter
78
+ effective_query = f"({query}) AND howler.status:{status}"
79
+
80
+ # Check hit limit against the effective query (not raw query)
81
+ limit_error = check_hit_limit(effective_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
82
+ if limit_error:
83
+ return [limit_error]
84
+
85
+ is_advanced = "automation_advanced" in user.type or "actionrunner_advanced" in user.type or "admin" in user.type
86
+ rows = MAX_HITS_ADVANCED if is_advanced else MAX_HITS_BASIC
87
+ hits = datastore().hit.search(effective_query, rows=rows, fl="howler.id")
75
88
 
76
89
  ids = [hit.howler.id for hit in hits["items"]]
77
90
 
@@ -176,7 +189,7 @@ def specification():
176
189
  "short": "Transition a hit",
177
190
  "long": execute.__doc__,
178
191
  },
179
- "roles": ["automation_basic"],
192
+ "roles": ["automation_basic", "actionrunner_basic"],
180
193
  "steps": [
181
194
  {
182
195
  "args": {"status": []},
@@ -28,7 +28,12 @@ tracer = trace.get_tracer(__name__)
28
28
  @tracer.start_as_current_span(f"{__name__}.emit")
29
29
  @socket_api.route("/emit/<event>", methods=["POST"])
30
30
  def emit(event: str):
31
- """Emit an event to all listening websockets"""
31
+ """Emit an event to all listening websockets.
32
+
33
+ .. deprecated::
34
+ This endpoint is deprecated. Events are now propagated via Redis pubsub
35
+ and no longer require a dedicated websocket pod.
36
+ """
32
37
  if "Authorization" not in request.headers:
33
38
  return unauthorized(err="Missing authorization header")
34
39
 
@@ -23,7 +23,11 @@ action_api._doc = "Endpoints relating to bulk actions and automation" # type: i
23
23
 
24
24
  @generate_swagger_docs()
25
25
  @action_api.route("/")
26
- @api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
26
+ @api_login(
27
+ audit=False,
28
+ check_xsrf_token=False,
29
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
30
+ )
27
31
  def get_actions(**_) -> Response:
28
32
  """Get a list of existing actions
29
33
 
@@ -43,7 +47,7 @@ def get_actions(**_) -> Response:
43
47
 
44
48
  @generate_swagger_docs()
45
49
  @action_api.route("/", methods=["POST"])
46
- @api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
50
+ @api_login(audit=False, check_xsrf_token=False, required_type=["admin", "automation_basic", "automation_advanced"])
47
51
  def add_action(user: User, **_) -> Response:
48
52
  """Create a new action
49
53
 
@@ -97,7 +101,7 @@ def add_action(user: User, **_) -> Response:
97
101
  @api_login(
98
102
  audit=False,
99
103
  check_xsrf_token=False,
100
- required_type=["automation_basic"],
104
+ required_type=["admin", "automation_basic", "automation_advanced"],
101
105
  )
102
106
  def update_action(id: str, user: User, **_) -> Response:
103
107
  """Update an existing action
@@ -164,7 +168,7 @@ def update_action(id: str, user: User, **_) -> Response:
164
168
 
165
169
  @generate_swagger_docs()
166
170
  @action_api.route("/<id>", methods=["DELETE"])
167
- @api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
171
+ @api_login(audit=True, check_xsrf_token=False, required_type=["admin", "automation_basic", "automation_advanced"])
168
172
  def delete_action(id: str, user: User, **kwargs) -> Response:
169
173
  """Delete an existing action
170
174
 
@@ -200,7 +204,11 @@ def delete_action(id: str, user: User, **kwargs) -> Response:
200
204
 
201
205
  @generate_swagger_docs()
202
206
  @action_api.route("/<id>/execute", methods=["POST"])
203
- @api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
207
+ @api_login(
208
+ audit=True,
209
+ check_xsrf_token=False,
210
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
211
+ )
204
212
  def execute_action(id: str, **kwargs) -> Response:
205
213
  """Execute one or more actions on a given query
206
214
 
@@ -274,7 +282,11 @@ def execute_action(id: str, **kwargs) -> Response:
274
282
 
275
283
  @generate_swagger_docs()
276
284
  @action_api.route("/operations")
277
- @api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
285
+ @api_login(
286
+ audit=False,
287
+ check_xsrf_token=False,
288
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
289
+ )
278
290
  def get_operations(**_) -> Response:
279
291
  """Get a list of operations the user can run on a query
280
292
 
@@ -294,7 +306,11 @@ def get_operations(**_) -> Response:
294
306
 
295
307
  @generate_swagger_docs()
296
308
  @action_api.route("/execute", methods=["POST"])
297
- @api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
309
+ @api_login(
310
+ audit=True,
311
+ check_xsrf_token=False,
312
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
313
+ )
298
314
  def execute_operations(**kwargs) -> Response:
299
315
  """Execute one or more operations on a given query
300
316
 
@@ -128,9 +128,7 @@ def add_user_account(username, **_):
128
128
  data["name"] = data["uname"]
129
129
 
130
130
  # Add dynamic classification group
131
- data["classification"] = user_service.get_dynamic_classification(
132
- cast(str | None, data.get("classification", None)), data["email"]
133
- )
131
+ data["classification"] = user_service.get_dynamic_classification(data)
134
132
 
135
133
  # Clear non user account data
136
134
  avatar = data.pop("avatar", None)
@@ -290,7 +288,7 @@ def set_user_account(username: str, **kwargs): # noqa: C901
290
288
  data.pop("new_pass_confirm", None)
291
289
 
292
290
  # Apply dynamic classification
293
- data["classification"] = user_service.get_dynamic_classification(data["classification"], data["email"])
291
+ data["classification"] = user_service.get_dynamic_classification(data)
294
292
 
295
293
  ret_val = user_service.save_user_account(username, data, kwargs["user"])
296
294
  return ok({"success": ret_val})