howler-api 2.13.0.dev329__py3-none-any.whl
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.
Potentially problematic release.
This version of howler-api might be problematic. Click here for more details.
- howler/__init__.py +0 -0
- howler/actions/__init__.py +167 -0
- howler/actions/add_label.py +111 -0
- howler/actions/add_to_bundle.py +159 -0
- howler/actions/change_field.py +76 -0
- howler/actions/demote.py +160 -0
- howler/actions/example_plugin.py +104 -0
- howler/actions/prioritization.py +93 -0
- howler/actions/promote.py +147 -0
- howler/actions/remove_from_bundle.py +133 -0
- howler/actions/remove_label.py +111 -0
- howler/actions/transition.py +200 -0
- howler/api/__init__.py +249 -0
- howler/api/base.py +88 -0
- howler/api/socket.py +114 -0
- howler/api/v1/__init__.py +97 -0
- howler/api/v1/action.py +372 -0
- howler/api/v1/analytic.py +748 -0
- howler/api/v1/auth.py +382 -0
- howler/api/v1/borealis.py +101 -0
- howler/api/v1/configs.py +55 -0
- howler/api/v1/dossier.py +222 -0
- howler/api/v1/help.py +28 -0
- howler/api/v1/hit.py +1181 -0
- howler/api/v1/notebook.py +82 -0
- howler/api/v1/overview.py +191 -0
- howler/api/v1/search.py +715 -0
- howler/api/v1/template.py +206 -0
- howler/api/v1/tool.py +183 -0
- howler/api/v1/user.py +414 -0
- howler/api/v1/utils/__init__.py +0 -0
- howler/api/v1/utils/etag.py +84 -0
- howler/api/v1/view.py +288 -0
- howler/app.py +235 -0
- howler/common/README.md +144 -0
- howler/common/__init__.py +0 -0
- howler/common/classification.py +979 -0
- howler/common/classification.yml +107 -0
- howler/common/exceptions.py +167 -0
- howler/common/hexdump.py +48 -0
- howler/common/iprange.py +171 -0
- howler/common/loader.py +154 -0
- howler/common/logging/__init__.py +241 -0
- howler/common/logging/audit.py +138 -0
- howler/common/logging/format.py +38 -0
- howler/common/net.py +79 -0
- howler/common/net_static.py +1494 -0
- howler/common/random_user.py +316 -0
- howler/common/swagger.py +117 -0
- howler/config.py +64 -0
- howler/cronjobs/__init__.py +29 -0
- howler/cronjobs/retention.py +61 -0
- howler/cronjobs/rules.py +274 -0
- howler/cronjobs/view_cleanup.py +88 -0
- howler/datastore/README.md +112 -0
- howler/datastore/__init__.py +0 -0
- howler/datastore/bulk.py +72 -0
- howler/datastore/collection.py +2327 -0
- howler/datastore/constants.py +117 -0
- howler/datastore/exceptions.py +41 -0
- howler/datastore/howler_store.py +105 -0
- howler/datastore/migrations/fix_process.py +41 -0
- howler/datastore/operations.py +130 -0
- howler/datastore/schemas.py +90 -0
- howler/datastore/store.py +231 -0
- howler/datastore/support/__init__.py +0 -0
- howler/datastore/support/build.py +214 -0
- howler/datastore/support/schemas.py +90 -0
- howler/datastore/types.py +22 -0
- howler/error.py +91 -0
- howler/external/__init__.py +0 -0
- howler/external/generate_mitre.py +96 -0
- howler/external/generate_sigma_rules.py +31 -0
- howler/external/generate_tlds.py +47 -0
- howler/external/reindex_data.py +46 -0
- howler/external/wipe_databases.py +58 -0
- howler/gunicorn_config.py +25 -0
- howler/healthz.py +47 -0
- howler/helper/__init__.py +0 -0
- howler/helper/azure.py +50 -0
- howler/helper/discover.py +59 -0
- howler/helper/hit.py +236 -0
- howler/helper/oauth.py +247 -0
- howler/helper/search.py +92 -0
- howler/helper/workflow.py +110 -0
- howler/helper/ws.py +378 -0
- howler/odm/README.md +102 -0
- howler/odm/__init__.py +1 -0
- howler/odm/base.py +1504 -0
- howler/odm/charter.txt +146 -0
- howler/odm/helper.py +416 -0
- howler/odm/howler_enum.py +25 -0
- howler/odm/models/__init__.py +0 -0
- howler/odm/models/action.py +33 -0
- howler/odm/models/analytic.py +90 -0
- howler/odm/models/assemblyline.py +48 -0
- howler/odm/models/aws.py +23 -0
- howler/odm/models/azure.py +16 -0
- howler/odm/models/cbs.py +44 -0
- howler/odm/models/config.py +558 -0
- howler/odm/models/dossier.py +33 -0
- howler/odm/models/ecs/__init__.py +0 -0
- howler/odm/models/ecs/agent.py +17 -0
- howler/odm/models/ecs/autonomous_system.py +16 -0
- howler/odm/models/ecs/client.py +149 -0
- howler/odm/models/ecs/cloud.py +141 -0
- howler/odm/models/ecs/code_signature.py +27 -0
- howler/odm/models/ecs/container.py +32 -0
- howler/odm/models/ecs/dns.py +62 -0
- howler/odm/models/ecs/egress.py +10 -0
- howler/odm/models/ecs/elf.py +74 -0
- howler/odm/models/ecs/email.py +122 -0
- howler/odm/models/ecs/error.py +14 -0
- howler/odm/models/ecs/event.py +140 -0
- howler/odm/models/ecs/faas.py +24 -0
- howler/odm/models/ecs/file.py +84 -0
- howler/odm/models/ecs/geo.py +30 -0
- howler/odm/models/ecs/group.py +18 -0
- howler/odm/models/ecs/hash.py +16 -0
- howler/odm/models/ecs/host.py +17 -0
- howler/odm/models/ecs/http.py +37 -0
- howler/odm/models/ecs/ingress.py +12 -0
- howler/odm/models/ecs/interface.py +21 -0
- howler/odm/models/ecs/network.py +30 -0
- howler/odm/models/ecs/observer.py +45 -0
- howler/odm/models/ecs/organization.py +12 -0
- howler/odm/models/ecs/os.py +21 -0
- howler/odm/models/ecs/pe.py +17 -0
- howler/odm/models/ecs/process.py +216 -0
- howler/odm/models/ecs/registry.py +26 -0
- howler/odm/models/ecs/related.py +45 -0
- howler/odm/models/ecs/rule.py +51 -0
- howler/odm/models/ecs/server.py +24 -0
- howler/odm/models/ecs/threat.py +247 -0
- howler/odm/models/ecs/tls.py +58 -0
- howler/odm/models/ecs/url.py +51 -0
- howler/odm/models/ecs/user.py +57 -0
- howler/odm/models/ecs/user_agent.py +20 -0
- howler/odm/models/ecs/vulnerability.py +41 -0
- howler/odm/models/gcp.py +16 -0
- howler/odm/models/hit.py +356 -0
- howler/odm/models/howler_data.py +328 -0
- howler/odm/models/lead.py +33 -0
- howler/odm/models/localized_label.py +13 -0
- howler/odm/models/overview.py +16 -0
- howler/odm/models/pivot.py +40 -0
- howler/odm/models/template.py +24 -0
- howler/odm/models/user.py +83 -0
- howler/odm/models/view.py +34 -0
- howler/odm/random_data.py +888 -0
- howler/odm/randomizer.py +606 -0
- howler/patched.py +5 -0
- howler/plugins/__init__.py +25 -0
- howler/plugins/config.py +123 -0
- howler/remote/__init__.py +0 -0
- howler/remote/datatypes/README.md +355 -0
- howler/remote/datatypes/__init__.py +98 -0
- howler/remote/datatypes/counters.py +63 -0
- howler/remote/datatypes/events.py +66 -0
- howler/remote/datatypes/hash.py +206 -0
- howler/remote/datatypes/lock.py +42 -0
- howler/remote/datatypes/queues/__init__.py +0 -0
- howler/remote/datatypes/queues/comms.py +59 -0
- howler/remote/datatypes/queues/multi.py +32 -0
- howler/remote/datatypes/queues/named.py +93 -0
- howler/remote/datatypes/queues/priority.py +215 -0
- howler/remote/datatypes/set.py +118 -0
- howler/remote/datatypes/user_quota_tracker.py +54 -0
- howler/security/__init__.py +253 -0
- howler/security/socket.py +108 -0
- howler/security/utils.py +185 -0
- howler/services/__init__.py +0 -0
- howler/services/action_service.py +111 -0
- howler/services/analytic_service.py +128 -0
- howler/services/auth_service.py +323 -0
- howler/services/config_service.py +128 -0
- howler/services/dossier_service.py +252 -0
- howler/services/event_service.py +93 -0
- howler/services/hit_service.py +893 -0
- howler/services/jwt_service.py +158 -0
- howler/services/lucene_service.py +286 -0
- howler/services/notebook_service.py +119 -0
- howler/services/overview_service.py +44 -0
- howler/services/template_service.py +45 -0
- howler/services/user_service.py +330 -0
- howler/utils/__init__.py +0 -0
- howler/utils/annotations.py +28 -0
- howler/utils/chunk.py +38 -0
- howler/utils/dict_utils.py +200 -0
- howler/utils/isotime.py +17 -0
- howler/utils/list_utils.py +11 -0
- howler/utils/lucene.py +77 -0
- howler/utils/path.py +27 -0
- howler/utils/socket_utils.py +61 -0
- howler/utils/str_utils.py +256 -0
- howler/utils/uid.py +47 -0
- howler_api-2.13.0.dev329.dist-info/METADATA +71 -0
- howler_api-2.13.0.dev329.dist-info/RECORD +200 -0
- howler_api-2.13.0.dev329.dist-info/WHEEL +4 -0
- howler_api-2.13.0.dev329.dist-info/entry_points.txt +8 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from textwrap import dedent
|
|
2
|
+
|
|
3
|
+
from flask import Blueprint, current_app, request
|
|
4
|
+
|
|
5
|
+
from howler.api import ok
|
|
6
|
+
from howler.security import api_login
|
|
7
|
+
|
|
8
|
+
API_PREFIX = "/api/v1"
|
|
9
|
+
apiv1 = Blueprint("apiv1", __name__, url_prefix=API_PREFIX)
|
|
10
|
+
apiv1._doc = "Api Documentation Version 1" # type: ignore[attr-defined]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@apiv1.route("/")
|
|
14
|
+
@api_login(audit=False, required_priv=["R", "W"], required_type=["user", "admin"])
|
|
15
|
+
def get_api_documentation(**kwargs):
|
|
16
|
+
"""Full API doc.
|
|
17
|
+
|
|
18
|
+
Loop through all registered API paths and display their documentation.
|
|
19
|
+
Returns a list of API definition.
|
|
20
|
+
|
|
21
|
+
Variables:
|
|
22
|
+
None
|
|
23
|
+
|
|
24
|
+
Arguments:
|
|
25
|
+
None
|
|
26
|
+
|
|
27
|
+
Result Example:
|
|
28
|
+
[
|
|
29
|
+
{
|
|
30
|
+
'name': "Api Doc", # Name of the api
|
|
31
|
+
'path': "/api/path/<variable>/", # API path
|
|
32
|
+
'ui_only': false, # Is UI only API
|
|
33
|
+
'methods': ["GET", "POST"], # Allowed HTTP methods
|
|
34
|
+
'description': "API doc.", # API documentation
|
|
35
|
+
'id': "api_doc", # Unique ID for the API
|
|
36
|
+
'function': "apiv1.api_doc", # Function called in the code
|
|
37
|
+
'protected': False, # Does the API require login?
|
|
38
|
+
'required_type': ['user'], # Type of users allowed to use API
|
|
39
|
+
'complete' : True # Is the API stable?
|
|
40
|
+
},
|
|
41
|
+
]
|
|
42
|
+
"""
|
|
43
|
+
user_types = kwargs["user"]["type"]
|
|
44
|
+
|
|
45
|
+
api_blueprints = {}
|
|
46
|
+
api_list = []
|
|
47
|
+
for rule in current_app.url_map.iter_rules():
|
|
48
|
+
if rule.rule.startswith(request.path):
|
|
49
|
+
methods = [item for item in (rule.methods or []) if item != "OPTIONS" and item != "HEAD"]
|
|
50
|
+
|
|
51
|
+
func = current_app.view_functions[rule.endpoint]
|
|
52
|
+
required_type = func.__dict__.get("required_type", ["user"])
|
|
53
|
+
|
|
54
|
+
for u_type in user_types:
|
|
55
|
+
if u_type in required_type:
|
|
56
|
+
doc_string = func.__doc__
|
|
57
|
+
func_title = " ".join(
|
|
58
|
+
[x.capitalize() for x in rule.endpoint[rule.endpoint.rindex(".") + 1 :].split("_")]
|
|
59
|
+
)
|
|
60
|
+
blueprint = rule.endpoint[: rule.endpoint.rindex(".")]
|
|
61
|
+
if blueprint == "apiv1":
|
|
62
|
+
blueprint = "documentation"
|
|
63
|
+
|
|
64
|
+
if blueprint not in api_blueprints:
|
|
65
|
+
try:
|
|
66
|
+
doc = current_app.blueprints[rule.endpoint[: rule.endpoint.rindex(".")]]._doc # type: ignore[attr-defined]
|
|
67
|
+
except Exception:
|
|
68
|
+
doc = ""
|
|
69
|
+
|
|
70
|
+
api_blueprints[blueprint] = doc
|
|
71
|
+
|
|
72
|
+
if doc_string:
|
|
73
|
+
description = dedent(doc_string)
|
|
74
|
+
else:
|
|
75
|
+
description = "[INCOMPLETE]\n\nTHIS API HAS NOT BEEN DOCUMENTED YET!"
|
|
76
|
+
|
|
77
|
+
api_id = rule.endpoint.replace("apiv1.", "").replace(".", "_")
|
|
78
|
+
|
|
79
|
+
api_list.append(
|
|
80
|
+
{
|
|
81
|
+
"protected": func.__dict__.get("protected", False),
|
|
82
|
+
"required_type": sorted(required_type),
|
|
83
|
+
"name": func_title,
|
|
84
|
+
"id": api_id,
|
|
85
|
+
"function": f"api.v1.{rule.endpoint}",
|
|
86
|
+
"path": rule.rule,
|
|
87
|
+
"ui_only": rule.rule.startswith("%sui/" % request.path),
|
|
88
|
+
"methods": sorted(methods),
|
|
89
|
+
"description": description,
|
|
90
|
+
"complete": "[INCOMPLETE]" not in description,
|
|
91
|
+
"required_priv": func.__dict__.get("required_priv", []),
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
break
|
|
96
|
+
|
|
97
|
+
return ok({"apis": api_list, "blueprints": api_blueprints})
|
howler/api/v1/action.py
ADDED
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from flask import Response, request
|
|
4
|
+
|
|
5
|
+
import howler.actions as actions
|
|
6
|
+
from howler.api import bad_request, created, forbidden, internal_error, make_subapi_blueprint, no_content, not_found, ok
|
|
7
|
+
from howler.common.exceptions import HowlerException
|
|
8
|
+
from howler.common.loader import datastore
|
|
9
|
+
from howler.common.logging.audit import audit
|
|
10
|
+
from howler.common.swagger import generate_swagger_docs
|
|
11
|
+
from howler.config import CLASSIFICATION
|
|
12
|
+
from howler.odm.models.action import Action
|
|
13
|
+
from howler.odm.models.user import User
|
|
14
|
+
from howler.security import api_login
|
|
15
|
+
from howler.services import action_service
|
|
16
|
+
|
|
17
|
+
SUB_API = "action"
|
|
18
|
+
classification_definition = CLASSIFICATION.get_parsed_classification_definition()
|
|
19
|
+
|
|
20
|
+
action_api = make_subapi_blueprint(SUB_API, api_version=1)
|
|
21
|
+
action_api._doc = "Endpoints relating to bulk actions and automation"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@generate_swagger_docs()
|
|
25
|
+
@action_api.route("/")
|
|
26
|
+
@api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
|
|
27
|
+
def get_actions(**_) -> Response:
|
|
28
|
+
"""Get a list of existing actions
|
|
29
|
+
|
|
30
|
+
Variables:
|
|
31
|
+
None
|
|
32
|
+
|
|
33
|
+
Optional Arguments:
|
|
34
|
+
None
|
|
35
|
+
|
|
36
|
+
Result Example:
|
|
37
|
+
[
|
|
38
|
+
...actions # A list of actions the user can see
|
|
39
|
+
]
|
|
40
|
+
"""
|
|
41
|
+
return ok(datastore().action.search("*:*", as_obj=False)["items"])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@generate_swagger_docs()
|
|
45
|
+
@action_api.route("/", methods=["POST"])
|
|
46
|
+
@api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
|
|
47
|
+
def add_action(user: User, **_) -> Response:
|
|
48
|
+
"""Create a new action
|
|
49
|
+
|
|
50
|
+
Variables:
|
|
51
|
+
None
|
|
52
|
+
|
|
53
|
+
Optional Arguments:
|
|
54
|
+
None
|
|
55
|
+
|
|
56
|
+
Data Block:
|
|
57
|
+
{
|
|
58
|
+
"name": "New Action", # An action name (human readable)
|
|
59
|
+
"query": "howler.id:*", # The query to execute when triggering this action
|
|
60
|
+
"operations": [ # A list of operations to execute
|
|
61
|
+
{
|
|
62
|
+
"operation_id": "add_label", # The id of the operation to run
|
|
63
|
+
"data_json": "{'category': 'generic', 'label': 'assigned'}" # Various requisite values for the operation
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
Result Example:
|
|
69
|
+
{
|
|
70
|
+
...action # The saved action data
|
|
71
|
+
}
|
|
72
|
+
"""
|
|
73
|
+
new_action = request.json
|
|
74
|
+
|
|
75
|
+
if new_action is None:
|
|
76
|
+
return bad_request(err="You must specify an action")
|
|
77
|
+
|
|
78
|
+
if error := action_service.validate_action(new_action):
|
|
79
|
+
return error
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
new_action["owner_id"] = user.uname
|
|
83
|
+
|
|
84
|
+
action_obj = Action(new_action)
|
|
85
|
+
|
|
86
|
+
ds = datastore()
|
|
87
|
+
ds.action.save(action_obj.action_id, action_obj)
|
|
88
|
+
ds.action.commit()
|
|
89
|
+
except HowlerException as e:
|
|
90
|
+
return bad_request(err=str(e))
|
|
91
|
+
|
|
92
|
+
return created(action_obj)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@generate_swagger_docs()
|
|
96
|
+
@action_api.route("/<id>", methods=["PUT", "PATCH"])
|
|
97
|
+
@api_login(
|
|
98
|
+
audit=False,
|
|
99
|
+
check_xsrf_token=False,
|
|
100
|
+
required_type=["automation_basic"],
|
|
101
|
+
)
|
|
102
|
+
def update_action(id: str, user: User, **_) -> Response:
|
|
103
|
+
"""Update an existing action
|
|
104
|
+
|
|
105
|
+
Variables:
|
|
106
|
+
id => id of the aciton to update
|
|
107
|
+
|
|
108
|
+
Optional Arguments:
|
|
109
|
+
None
|
|
110
|
+
|
|
111
|
+
Data Block:
|
|
112
|
+
{
|
|
113
|
+
"name": "New Action", # An action name (human readable)
|
|
114
|
+
"query": "howler.id:*", # The query to execute when triggering this action
|
|
115
|
+
"actions": [ # A list of actions to execute
|
|
116
|
+
{
|
|
117
|
+
"operation_id": "add_label", # The id of the action to run
|
|
118
|
+
"data_json": "{ 'category': 'generic', 'label': 'assigned' }" # Various requisite values for the action
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
Result Example:
|
|
124
|
+
{
|
|
125
|
+
...action # The saved action data
|
|
126
|
+
}
|
|
127
|
+
"""
|
|
128
|
+
updated_action = request.json
|
|
129
|
+
if not isinstance(updated_action, dict):
|
|
130
|
+
return bad_request(err="Incorrect data structure!")
|
|
131
|
+
|
|
132
|
+
ds = datastore()
|
|
133
|
+
|
|
134
|
+
existing_action = ds.action.get(id, as_obj=False)
|
|
135
|
+
|
|
136
|
+
if not existing_action:
|
|
137
|
+
return not_found(err="The specified automation does not exist")
|
|
138
|
+
|
|
139
|
+
if "automation_advanced" not in user.type and updated_action.get("triggers", []) != existing_action.get(
|
|
140
|
+
"triggers", []
|
|
141
|
+
):
|
|
142
|
+
return forbidden(err="Updating triggers requires the role 'automation_advanced'.")
|
|
143
|
+
|
|
144
|
+
updated_action = {
|
|
145
|
+
**existing_action,
|
|
146
|
+
**updated_action,
|
|
147
|
+
"action_id": existing_action["action_id"],
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if error := action_service.validate_action(updated_action):
|
|
151
|
+
return error
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
action_obj = Action(updated_action)
|
|
155
|
+
action_obj.action_id = id
|
|
156
|
+
|
|
157
|
+
ds.action.save(action_obj.action_id, action_obj)
|
|
158
|
+
ds.action.commit()
|
|
159
|
+
except HowlerException as e:
|
|
160
|
+
return bad_request(err=str(e))
|
|
161
|
+
|
|
162
|
+
return ok(action_obj)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@generate_swagger_docs()
|
|
166
|
+
@action_api.route("/<id>", methods=["DELETE"])
|
|
167
|
+
@api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
|
|
168
|
+
def delete_action(id: str, user: User, **kwargs) -> Response:
|
|
169
|
+
"""Delete an existing action
|
|
170
|
+
|
|
171
|
+
Variables:
|
|
172
|
+
id => The id of the action to delete
|
|
173
|
+
|
|
174
|
+
Optional Arguments:
|
|
175
|
+
None
|
|
176
|
+
|
|
177
|
+
Result Example:
|
|
178
|
+
None
|
|
179
|
+
"""
|
|
180
|
+
ds = datastore()
|
|
181
|
+
|
|
182
|
+
result = ds.action.search(f"action_id:{id}", rows=1)
|
|
183
|
+
|
|
184
|
+
if not result["total"]:
|
|
185
|
+
return not_found(err="Action does not exist")
|
|
186
|
+
|
|
187
|
+
action: Action = result["items"][0]
|
|
188
|
+
|
|
189
|
+
if action.owner_id != user.uname and "admin" not in user.type:
|
|
190
|
+
return forbidden(err="You do not have the permissions necessary to delete this action.")
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
ds.action.delete(id)
|
|
194
|
+
ds.action.commit()
|
|
195
|
+
|
|
196
|
+
return no_content()
|
|
197
|
+
except HowlerException as e:
|
|
198
|
+
return internal_error(err=str(e))
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@generate_swagger_docs()
|
|
202
|
+
@action_api.route("/<id>/execute", methods=["POST"])
|
|
203
|
+
@api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
|
|
204
|
+
def execute_action(id: str, **kwargs) -> Response:
|
|
205
|
+
"""Execute one or more actions on a given query
|
|
206
|
+
|
|
207
|
+
Variables:
|
|
208
|
+
id => The id of the action to execute
|
|
209
|
+
|
|
210
|
+
Optional Arguments:
|
|
211
|
+
None
|
|
212
|
+
|
|
213
|
+
Data Block:
|
|
214
|
+
{
|
|
215
|
+
"request_id": "abc123", # An id used to identify the request in websocket updates
|
|
216
|
+
"query": "howler.id:*" # An optional override query
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
Result Example:
|
|
220
|
+
{
|
|
221
|
+
"add_label": [ # Each entry corresponds to a given action ID
|
|
222
|
+
{
|
|
223
|
+
"query": "howler.id:*", # The query this portion of the report applies to
|
|
224
|
+
"title": "Execution Succeeeded", # The title of this section of the report
|
|
225
|
+
"message": "Label successfully added to 42 hits" # A longer explanation of this portion
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
"""
|
|
230
|
+
execute_req = request.json
|
|
231
|
+
if not isinstance(execute_req, dict):
|
|
232
|
+
return bad_request(err="Incorrect data structure!")
|
|
233
|
+
|
|
234
|
+
action: Action = datastore().action.get(id)
|
|
235
|
+
|
|
236
|
+
if not action:
|
|
237
|
+
return not_found(err="The specified action does not exist")
|
|
238
|
+
|
|
239
|
+
reports: dict[str, list[dict]] = {}
|
|
240
|
+
current_user = kwargs.get("user", None)
|
|
241
|
+
|
|
242
|
+
for operation in action.operations:
|
|
243
|
+
op_data = json.loads(operation["data_json"])
|
|
244
|
+
|
|
245
|
+
query = execute_req.get("query", action.query) or action.query
|
|
246
|
+
|
|
247
|
+
audit(
|
|
248
|
+
[],
|
|
249
|
+
{
|
|
250
|
+
**kwargs,
|
|
251
|
+
"query": query,
|
|
252
|
+
"operation_id": operation.operation_id,
|
|
253
|
+
**op_data,
|
|
254
|
+
},
|
|
255
|
+
current_user["uname"] if current_user is not None else "unknown",
|
|
256
|
+
current_user,
|
|
257
|
+
execute_action,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
report = actions.execute(
|
|
261
|
+
operation_id=operation.operation_id,
|
|
262
|
+
request_id=execute_req["request_id"],
|
|
263
|
+
query=query,
|
|
264
|
+
user=current_user,
|
|
265
|
+
**op_data,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
if operation.operation_id not in reports:
|
|
269
|
+
reports[operation.operation_id] = []
|
|
270
|
+
|
|
271
|
+
reports[operation.operation_id].extend(report)
|
|
272
|
+
|
|
273
|
+
return ok(reports)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@generate_swagger_docs()
|
|
277
|
+
@action_api.route("/operations")
|
|
278
|
+
@api_login(audit=False, check_xsrf_token=False, required_type=["automation_basic"])
|
|
279
|
+
def get_operations(**_) -> Response:
|
|
280
|
+
"""Get a list of operations the user can run on a query
|
|
281
|
+
|
|
282
|
+
Variables:
|
|
283
|
+
None
|
|
284
|
+
|
|
285
|
+
Optional Arguments:
|
|
286
|
+
None
|
|
287
|
+
|
|
288
|
+
Result Example:
|
|
289
|
+
[
|
|
290
|
+
...operations # A list of specifications for the operations the user can use
|
|
291
|
+
]
|
|
292
|
+
"""
|
|
293
|
+
return ok(actions.specifications())
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@generate_swagger_docs()
|
|
297
|
+
@action_api.route("/execute", methods=["POST"])
|
|
298
|
+
@api_login(audit=True, check_xsrf_token=False, required_type=["automation_basic"])
|
|
299
|
+
def execute_operations(**kwargs) -> Response:
|
|
300
|
+
"""Execute one or more operations on a given query
|
|
301
|
+
|
|
302
|
+
Variables:
|
|
303
|
+
None
|
|
304
|
+
|
|
305
|
+
Optional Arguments:
|
|
306
|
+
None
|
|
307
|
+
|
|
308
|
+
Data Block:
|
|
309
|
+
{
|
|
310
|
+
"query": "howler.id:*", # The query to run
|
|
311
|
+
"request_id": "abc123", # An id used to identify the request in websocket updates
|
|
312
|
+
"operations": [ # A list of operations to execute
|
|
313
|
+
{
|
|
314
|
+
"operation_id": "add_label", # The id of the action to run
|
|
315
|
+
"data_json": { "category": "generic", "label": "assigned" } # Various requisite values for the action
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
Result Example:
|
|
321
|
+
{
|
|
322
|
+
"add_label": [ # Each entry corresponds to a given operation ID
|
|
323
|
+
{
|
|
324
|
+
"query": "howler.id:*", # The query this portion of the report applies to
|
|
325
|
+
"title": "Execution Succeeeded", # The title of this section of the report
|
|
326
|
+
"message": "Label successfully added to 42 hits" # A longer explanation of this portion
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
"""
|
|
331
|
+
execute_req = request.json
|
|
332
|
+
if not isinstance(execute_req, dict):
|
|
333
|
+
return bad_request(err="Incorrect data structure!")
|
|
334
|
+
|
|
335
|
+
reports: dict[str, list[dict]] = {}
|
|
336
|
+
current_user = kwargs.get("user", None)
|
|
337
|
+
operations = execute_req["operations"]
|
|
338
|
+
|
|
339
|
+
operation_ids = [o["operation_id"] for o in operations]
|
|
340
|
+
if len(operation_ids) != len(set(operation_ids)):
|
|
341
|
+
return bad_request(err="You must have a maximum of one operation of each type in request.")
|
|
342
|
+
|
|
343
|
+
for operation in operations:
|
|
344
|
+
op_data = json.loads(operation["data_json"])
|
|
345
|
+
|
|
346
|
+
audit(
|
|
347
|
+
[],
|
|
348
|
+
{
|
|
349
|
+
**kwargs,
|
|
350
|
+
"query": execute_req["query"],
|
|
351
|
+
"operation_id": operation["operation_id"],
|
|
352
|
+
**op_data,
|
|
353
|
+
},
|
|
354
|
+
current_user["uname"] if current_user is not None else "unknown",
|
|
355
|
+
current_user,
|
|
356
|
+
execute_operations,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
report = actions.execute(
|
|
360
|
+
operation_id=operation["operation_id"],
|
|
361
|
+
request_id=execute_req["request_id"],
|
|
362
|
+
query=execute_req["query"],
|
|
363
|
+
user=current_user,
|
|
364
|
+
**op_data,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
if operation["operation_id"] not in reports:
|
|
368
|
+
reports[operation["operation_id"]] = []
|
|
369
|
+
|
|
370
|
+
reports[operation["operation_id"]].extend(report)
|
|
371
|
+
|
|
372
|
+
return ok(reports)
|