howler-api 3.4.0.dev830__tar.gz → 3.4.0.dev845__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 (202) hide show
  1. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/PKG-INFO +1 -1
  2. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/__init__.py +91 -21
  3. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/add_label.py +3 -1
  4. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/add_to_bundle.py +15 -3
  5. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/change_field.py +1 -1
  6. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/demote.py +3 -1
  7. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/example_plugin.py +1 -1
  8. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/prioritization.py +3 -1
  9. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/promote.py +3 -1
  10. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/remove_from_bundle.py +16 -5
  11. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/remove_label.py +3 -1
  12. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/actions/transition.py +16 -3
  13. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/action.py +23 -7
  14. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/loader.py +1 -1
  15. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/base.py +80 -21
  16. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/user.py +1 -1
  17. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/random_data.py +13 -1
  18. howler_api-3.4.0.dev845/howler/utils/compat.py +27 -0
  19. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/pyproject.toml +1 -1
  20. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/README.md +0 -0
  21. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/__init__.py +0 -0
  22. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/__init__.py +0 -0
  23. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/base.py +0 -0
  24. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/socket.py +0 -0
  25. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/__init__.py +0 -0
  26. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/analytic.py +0 -0
  27. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/auth.py +0 -0
  28. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/clue.py +0 -0
  29. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/configs.py +0 -0
  30. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/dossier.py +0 -0
  31. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/help.py +0 -0
  32. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/hit.py +0 -0
  33. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/notebook.py +0 -0
  34. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/overview.py +0 -0
  35. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/search.py +0 -0
  36. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/template.py +0 -0
  37. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/tool.py +0 -0
  38. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/user.py +0 -0
  39. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/utils/__init__.py +0 -0
  40. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/utils/etag.py +0 -0
  41. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/api/v1/view.py +0 -0
  42. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/app.py +0 -0
  43. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/README.md +0 -0
  44. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/__init__.py +0 -0
  45. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/classification.py +0 -0
  46. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/classification.yml +0 -0
  47. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/exceptions.py +0 -0
  48. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/logging/__init__.py +0 -0
  49. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/logging/audit.py +0 -0
  50. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/logging/format.py +0 -0
  51. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/net.py +0 -0
  52. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/net_static.py +0 -0
  53. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/random_user.py +0 -0
  54. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/common/swagger.py +0 -0
  55. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/config.py +0 -0
  56. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/cronjobs/__init__.py +0 -0
  57. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/cronjobs/retention.py +0 -0
  58. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/cronjobs/rules.py +0 -0
  59. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/cronjobs/view_cleanup.py +0 -0
  60. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/README.md +0 -0
  61. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/__init__.py +0 -0
  62. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/bulk.py +0 -0
  63. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/collection.py +0 -0
  64. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/constants.py +0 -0
  65. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/exceptions.py +0 -0
  66. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/howler_store.py +0 -0
  67. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/migrations/fix_process.py +0 -0
  68. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/operations.py +0 -0
  69. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/schemas.py +0 -0
  70. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/store.py +0 -0
  71. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/support/__init__.py +0 -0
  72. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/support/build.py +0 -0
  73. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/support/schemas.py +0 -0
  74. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/datastore/types.py +0 -0
  75. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/error.py +0 -0
  76. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/README.md +0 -0
  77. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/__init__.py +0 -0
  78. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/generate_mitre.py +0 -0
  79. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/generate_sigma_rules.py +0 -0
  80. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/generate_tlds.py +0 -0
  81. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/reindex_data.py +0 -0
  82. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/external/wipe_databases.py +0 -0
  83. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/gunicorn_config.py +0 -0
  84. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/healthz.py +0 -0
  85. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/__init__.py +0 -0
  86. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/azure.py +0 -0
  87. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/discover.py +0 -0
  88. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/hit.py +0 -0
  89. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/oauth.py +0 -0
  90. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/search.py +0 -0
  91. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/workflow.py +0 -0
  92. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/helper/ws.py +0 -0
  93. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/README.md +0 -0
  94. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/__init__.py +0 -0
  95. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/charter.txt +0 -0
  96. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/helper.py +0 -0
  97. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/howler_enum.py +0 -0
  98. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/__init__.py +0 -0
  99. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/action.py +0 -0
  100. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/analytic.py +0 -0
  101. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/assemblyline.py +0 -0
  102. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/aws.py +0 -0
  103. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/azure.py +0 -0
  104. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/cbs.py +0 -0
  105. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/clue.py +0 -0
  106. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/config.py +0 -0
  107. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/dossier.py +0 -0
  108. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/__init__.py +0 -0
  109. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/agent.py +0 -0
  110. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/autonomous_system.py +0 -0
  111. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/client.py +0 -0
  112. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/cloud.py +0 -0
  113. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/code_signature.py +0 -0
  114. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/container.py +0 -0
  115. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/dns.py +0 -0
  116. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/egress.py +0 -0
  117. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/elf.py +0 -0
  118. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/email.py +0 -0
  119. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/error.py +0 -0
  120. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/event.py +0 -0
  121. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/faas.py +0 -0
  122. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/file.py +0 -0
  123. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/geo.py +0 -0
  124. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/group.py +0 -0
  125. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/hash.py +0 -0
  126. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/host.py +0 -0
  127. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/http.py +0 -0
  128. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/ingress.py +0 -0
  129. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/interface.py +0 -0
  130. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/network.py +0 -0
  131. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/observer.py +0 -0
  132. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/organization.py +0 -0
  133. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/os.py +0 -0
  134. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/pe.py +0 -0
  135. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/process.py +0 -0
  136. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/registry.py +0 -0
  137. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/related.py +0 -0
  138. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/rule.py +0 -0
  139. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/server.py +0 -0
  140. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/threat.py +0 -0
  141. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/tls.py +0 -0
  142. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/url.py +0 -0
  143. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/user.py +0 -0
  144. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/user_agent.py +0 -0
  145. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/ecs/vulnerability.py +0 -0
  146. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/gcp.py +0 -0
  147. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/hit.py +0 -0
  148. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/howler_data.py +0 -0
  149. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/lead.py +0 -0
  150. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/localized_label.py +0 -0
  151. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/overview.py +0 -0
  152. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/pivot.py +0 -0
  153. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/template.py +0 -0
  154. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/models/view.py +0 -0
  155. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/odm/randomizer.py +0 -0
  156. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/patched.py +0 -0
  157. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/plugins/__init__.py +0 -0
  158. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/plugins/config.py +0 -0
  159. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/__init__.py +0 -0
  160. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/README.md +0 -0
  161. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/__init__.py +0 -0
  162. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/counters.py +0 -0
  163. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/events.py +0 -0
  164. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/hash.py +0 -0
  165. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/lock.py +0 -0
  166. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/queues/__init__.py +0 -0
  167. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/queues/comms.py +0 -0
  168. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/queues/multi.py +0 -0
  169. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/queues/named.py +0 -0
  170. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/queues/priority.py +0 -0
  171. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/set.py +0 -0
  172. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/remote/datatypes/user_quota_tracker.py +0 -0
  173. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/security/__init__.py +0 -0
  174. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/security/socket.py +0 -0
  175. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/security/utils.py +0 -0
  176. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/__init__.py +0 -0
  177. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/action_service.py +0 -0
  178. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/analytic_service.py +0 -0
  179. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/auth_service.py +0 -0
  180. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/config_service.py +0 -0
  181. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/dossier_service.py +0 -0
  182. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/event_service.py +0 -0
  183. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/hit_service.py +0 -0
  184. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/jwt_service.py +0 -0
  185. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/lucene_service.py +0 -0
  186. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/notebook_service.py +0 -0
  187. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/overview_service.py +0 -0
  188. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/template_service.py +0 -0
  189. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/services/user_service.py +0 -0
  190. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/telemetry.py +0 -0
  191. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/__init__.py +0 -0
  192. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/annotations.py +0 -0
  193. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/chunk.py +0 -0
  194. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/constants.py +0 -0
  195. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/dict_utils.py +0 -0
  196. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/isotime.py +0 -0
  197. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/list_utils.py +0 -0
  198. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/lucene.py +0 -0
  199. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/path.py +0 -0
  200. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/socket_utils.py +0 -0
  201. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/howler/utils/str_utils.py +0 -0
  202. {howler_api-3.4.0.dev830 → howler_api-3.4.0.dev845}/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.dev830
3
+ Version: 3.4.0.dev845
4
4
  Summary: Howler - API server
5
5
  License: MIT
6
6
  Keywords: howler,alerting,gc,canada,cse-cst,cse,cst,cyber,cccs
@@ -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": []},
@@ -1,18 +1,23 @@
1
1
  from typing import Optional
2
2
 
3
+ from howler.actions import check_hit_limit
3
4
  from howler.common.loader import datastore
4
5
  from howler.datastore.operations import OdmHelper
5
6
  from howler.odm.models.action import VALID_TRIGGERS
6
7
  from howler.odm.models.hit import Hit
8
+ from howler.odm.models.user import User
7
9
  from howler.services import hit_service
8
10
  from howler.utils.str_utils import sanitize_lucene_query
9
11
 
10
12
  hit_helper = OdmHelper(Hit)
11
13
 
12
14
  OPERATION_ID = "add_to_bundle"
15
+ MAX_HITS_BASIC = 10
16
+ MAX_HITS_ADVANCED = 1000
17
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
13
18
 
14
19
 
15
- def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
20
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
16
21
  """Add a set of hits matching the query to the specified bundle.
17
22
 
18
23
  Args:
@@ -77,7 +82,14 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
77
82
  )
78
83
 
79
84
  safe_query = f"({query}) AND (-howler.bundles:({sanitize_lucene_query(bundle_id)}) AND howler.is_bundle:false)"
80
- matching_hits = ds.hit.search(safe_query)["items"]
85
+
86
+ # Check hit limit against the effective query (not raw query)
87
+ if user:
88
+ limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
89
+ if limit_error:
90
+ return [limit_error]
91
+
92
+ matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
81
93
  if len(matching_hits) < 1:
82
94
  report.append(
83
95
  {
@@ -141,7 +153,7 @@ def specification():
141
153
  "short": "Add a set of hits to a bundle",
142
154
  "long": execute.__doc__,
143
155
  },
144
- "roles": ["automation_basic"],
156
+ "roles": ["automation_basic", "actionrunner_basic"],
145
157
  "steps": [
146
158
  {
147
159
  "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": []},
@@ -1,19 +1,24 @@
1
1
  from typing import Optional
2
2
 
3
+ from howler.actions import check_hit_limit
3
4
  from howler.common.exceptions import HowlerException
4
5
  from howler.common.loader import datastore
5
6
  from howler.datastore.operations import OdmHelper
6
7
  from howler.odm.models.action import VALID_TRIGGERS
7
8
  from howler.odm.models.hit import Hit
9
+ from howler.odm.models.user import User
8
10
  from howler.services import hit_service
9
11
  from howler.utils.str_utils import sanitize_lucene_query
10
12
 
11
13
  hit_helper = OdmHelper(Hit)
12
14
 
13
15
  OPERATION_ID = "remove_from_bundle"
16
+ MAX_HITS_BASIC = 10
17
+ MAX_HITS_ADVANCED = 1000
18
+ SKIP_CENTRAL_LIMIT = True # This operation transforms the query, handles limit check locally
14
19
 
15
20
 
16
- def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
21
+ def execute(query: str, bundle_id: Optional[str] = None, user: Optional[User] = None, **kwargs):
17
22
  """Remove a set of hits matching the query from the specified bundle.
18
23
 
19
24
  Args:
@@ -62,9 +67,15 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
62
67
  }
63
68
  )
64
69
 
65
- safe_query = f"{query} AND (howler.bundles:{bundle_id})"
70
+ safe_query = f"{query} AND (howler.bundles:{sanitize_lucene_query(bundle_id)})"
66
71
 
67
- matching_hits = ds.hit.search(safe_query)["items"]
72
+ # Check hit limit against the effective query (not raw query)
73
+ if user:
74
+ limit_error = check_hit_limit(safe_query, user, MAX_HITS_BASIC, MAX_HITS_ADVANCED)
75
+ if limit_error:
76
+ return [limit_error]
77
+
78
+ matching_hits = ds.hit.search(safe_query, rows=MAX_HITS_ADVANCED, fl="howler.id")["items"]
68
79
  if len(matching_hits) < 1:
69
80
  report.append(
70
81
  {
@@ -83,7 +94,7 @@ def execute(query: str, bundle_id: Optional[str] = None, **kwargs):
83
94
 
84
95
  hit_service.update_hit(
85
96
  bundle_id,
86
- [hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in ds.hit.search(safe_query)["items"]],
97
+ [hit_helper.list_remove("howler.hits", h["howler"]["id"]) for h in matching_hits],
87
98
  )
88
99
 
89
100
  if len(ds.hit.get(bundle_id).howler.hits) < 1:
@@ -121,7 +132,7 @@ def specification():
121
132
  "short": "Remove a set of hits from a bundle",
122
133
  "long": execute.__doc__,
123
134
  },
124
- "roles": ["automation_basic"],
135
+ "roles": ["automation_basic", "actionrunner_basic"],
125
136
  "steps": [
126
137
  {
127
138
  "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": []},
@@ -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
 
@@ -275,7 +283,11 @@ def execute_action(id: str, **kwargs) -> Response:
275
283
 
276
284
  @generate_swagger_docs()
277
285
  @action_api.route("/operations")
278
- @api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
286
+ @api_login(
287
+ audit=False,
288
+ check_xsrf_token=False,
289
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
290
+ )
279
291
  def get_operations(**_) -> Response:
280
292
  """Get a list of operations the user can run on a query
281
293
 
@@ -295,7 +307,11 @@ def get_operations(**_) -> Response:
295
307
 
296
308
  @generate_swagger_docs()
297
309
  @action_api.route("/execute", methods=["POST"])
298
- @api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
310
+ @api_login(
311
+ audit=True,
312
+ check_xsrf_token=False,
313
+ required_type=["admin", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"],
314
+ )
299
315
  def execute_operations(**kwargs) -> Response:
300
316
  """Execute one or more operations on a given query
301
317
 
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
13
13
 
14
14
  APP_NAME = os.environ.get("APP_NAME", "howler")
15
15
  APP_PREFIX = os.environ.get("APP_PREFIX", "hwl")
16
- USER_TYPES = {"admin", "user", "automation_basic", "automation_advanced"}
16
+ USER_TYPES = {"admin", "user", "automation_basic", "automation_advanced", "actionrunner_basic", "actionrunner_advanced"}
17
17
 
18
18
 
19
19
  def env_substitute(buffer):