stoobly-agent 1.8.4__py3-none-any.whl → 1.9.0__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.
- stoobly_agent/__init__.py +1 -1
- stoobly_agent/app/api/configs_controller.py +3 -3
- stoobly_agent/app/api/headers_controller.py +1 -1
- stoobly_agent/app/api/query_params_controller.py +4 -4
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +12 -12
- stoobly_agent/app/cli/intercept_cli.py +30 -7
- stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
- stoobly_agent/app/cli/scaffold/templates/app/.Makefile +6 -0
- stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/.overwrite +13 -0
- stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +16 -0
- stoobly_agent/app/cli/snapshot_cli.py +57 -0
- stoobly_agent/app/cli/types/snapshot_migration.py +61 -0
- stoobly_agent/app/models/adapters/joined_request_adapter.py +1 -1
- stoobly_agent/app/models/adapters/orm/request/mitmproxy_adapter.py +1 -1
- stoobly_agent/app/models/adapters/python/request/__init__.py +2 -2
- stoobly_agent/app/models/adapters/python/request/mitmproxy_adapter.py +1 -1
- stoobly_agent/app/models/adapters/python/request/raw_adapter.py +2 -2
- stoobly_agent/app/models/factories/resource/local_db/helpers/log.py +7 -0
- stoobly_agent/app/models/factories/resource/local_db/helpers/log_event.py +10 -0
- stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py +20 -0
- stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +8 -1
- stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot.py +1 -1
- stoobly_agent/app/models/factories/resource/local_db/query_param_adapter.py +2 -2
- stoobly_agent/app/models/helpers/apply.py +7 -6
- stoobly_agent/app/models/helpers/create_request_params_service.py +4 -2
- stoobly_agent/app/proxy/handle_mock_service.py +19 -6
- stoobly_agent/app/proxy/handle_record_service.py +16 -7
- stoobly_agent/app/proxy/intercept_settings.py +12 -1
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
- stoobly_agent/app/proxy/record/join_request_service.py +22 -10
- stoobly_agent/app/proxy/record/proxy_request.py +9 -0
- stoobly_agent/app/proxy/record/request_string.py +19 -10
- stoobly_agent/app/proxy/record/upload_request_service.py +7 -5
- stoobly_agent/app/proxy/replay/body_parser_service.py +15 -4
- stoobly_agent/app/proxy/replay/replay_request_service.py +1 -0
- stoobly_agent/app/proxy/test/helpers/upload_test_service.py +1 -1
- stoobly_agent/app/settings/data_rules.py +16 -1
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/config/constants/lifecycle_hooks.py +2 -0
- stoobly_agent/config/constants/record_order.py +6 -0
- stoobly_agent/config/constants/record_policy.py +2 -2
- stoobly_agent/lib/orm/transformers/orm_to_request_transformer.py +1 -1
- stoobly_agent/public/18-es2015.beb31fe4a4dee3007cb2.js +1 -0
- stoobly_agent/public/18-es5.beb31fe4a4dee3007cb2.js +1 -0
- stoobly_agent/public/index.html +2 -2
- stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +1 -0
- stoobly_agent/public/main-es5.089b46f303768fbe864f.js +1 -0
- stoobly_agent/public/{runtime-es2015.b13c22b834b51724d30a.js → runtime-es2015.f8c814b38b27708e91c1.js} +1 -1
- stoobly_agent/public/{runtime-es5.b13c22b834b51724d30a.js → runtime-es5.f8c814b38b27708e91c1.js} +1 -1
- stoobly_agent/public/{styles.ab281309cf423b2cdcb0.css → styles.817f011ab81b18b0e5c2.css} +1 -1
- stoobly_agent/test/app/cli/config/scenario/config_scenario_set_test.py +3 -3
- stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +5 -7
- stoobly_agent/test/app/cli/intercept/intercept_enable_test.py +3 -5
- stoobly_agent/test/app/cli/scenario/scenario_snapshot_test.py +1 -1
- stoobly_agent/test/app/cli/snapshot/lifecycle_hooks_migrate.py +10 -0
- stoobly_agent/test/app/cli/snapshot/snapshot_migrate_test.py +181 -0
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/cli/record_test.py +3 -3
- {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/METADATA +1 -1
- {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/RECORD +63 -58
- stoobly_agent/public/18-es2015.d07dd29def7e2574c5b7.js +0 -1
- stoobly_agent/public/18-es5.d07dd29def7e2574c5b7.js +0 -1
- stoobly_agent/public/main-es2015.ce00115b0520fa030f01.js +0 -1
- stoobly_agent/public/main-es5.ce00115b0520fa030f01.js +0 -1
- {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/WHEEL +0 -0
- {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '1.
|
2
|
+
VERSION = '1.9.0'
|
@@ -4,7 +4,7 @@ from mergedeep import merge
|
|
4
4
|
|
5
5
|
from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
|
6
6
|
from stoobly_agent.app.cli.helpers.handle_config_update_service import (
|
7
|
-
context as handle_context, handle_intercept_active_update,
|
7
|
+
context as handle_context, handle_intercept_active_update, handle_order_update, handle_project_update, handle_scenario_update
|
8
8
|
)
|
9
9
|
from stoobly_agent.app.models.scenario_model import ScenarioModel
|
10
10
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
@@ -41,7 +41,7 @@ class ConfigsController:
|
|
41
41
|
)
|
42
42
|
elif active_mode == mode.RECORD:
|
43
43
|
context.render(
|
44
|
-
json = [record_policy.ALL, record_policy.
|
44
|
+
json = [record_policy.ALL, record_policy.API, record_policy.FOUND, record_policy.NOT_FOUND],
|
45
45
|
status = 200
|
46
46
|
)
|
47
47
|
elif active_mode == mode.REPLAY:
|
@@ -112,7 +112,7 @@ class ConfigsController:
|
|
112
112
|
_handle_context = handle_context()
|
113
113
|
|
114
114
|
handle_intercept_active_update(settings, _handle_context)
|
115
|
-
|
115
|
+
handle_order_update(settings, _handle_context)
|
116
116
|
handle_project_update(settings, _handle_context)
|
117
117
|
handle_scenario_update(settings, _handle_context)
|
118
118
|
|
@@ -103,13 +103,13 @@ class QueryParamsController:
|
|
103
103
|
|
104
104
|
if context.filter_response(query_param, status):
|
105
105
|
return
|
106
|
-
|
106
|
+
|
107
107
|
context.render(
|
108
|
-
plain =
|
109
|
-
status =
|
108
|
+
plain = '',
|
109
|
+
status = 204
|
110
110
|
)
|
111
111
|
|
112
112
|
def __query_param_model(self, context: SimpleHTTPRequestHandler):
|
113
113
|
access_token = context.headers.get('access-token')
|
114
114
|
query_param_model = QueryParamModel(Settings.instance(), access_token=access_token)
|
115
|
-
return query_param_model
|
115
|
+
return query_param_model
|
@@ -6,7 +6,7 @@ from stoobly_agent.app.models.scenario_model import ScenarioModel
|
|
6
6
|
from stoobly_agent.app.settings.constants import intercept_mode
|
7
7
|
from stoobly_agent.app.settings.constants.intercept_mode import Mode
|
8
8
|
from stoobly_agent.app.settings import ProxySettings, Settings
|
9
|
-
from stoobly_agent.config.constants import
|
9
|
+
from stoobly_agent.config.constants import record_order
|
10
10
|
from stoobly_agent.lib.api.keys.project_key import ProjectKey
|
11
11
|
from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
|
12
12
|
|
@@ -28,15 +28,15 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
|
|
28
28
|
|
29
29
|
if not was_active and is_active:
|
30
30
|
if _mode == intercept_mode.RECORD:
|
31
|
-
|
31
|
+
new_order = data_rule.record_order
|
32
32
|
scenario_key = data_rule.scenario_key
|
33
33
|
_scenario_key = __parse_scenario_key(scenario_key)
|
34
34
|
|
35
35
|
if _scenario_key:
|
36
36
|
scenario_model = ScenarioModel(new_settings)
|
37
37
|
|
38
|
-
if
|
39
|
-
# If
|
38
|
+
if new_order == record_order.OVERWRITE:
|
39
|
+
# If order is overwrite when recording, whenever intercept is enabled,
|
40
40
|
# set active scenario to be overwritable
|
41
41
|
scenario_model.update(_scenario_key.id, **{ 'overwritable': True })[1]
|
42
42
|
else:
|
@@ -44,16 +44,16 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
|
|
44
44
|
elif was_active and not is_active:
|
45
45
|
if _mode == intercept_mode.RECORD:
|
46
46
|
old_proxy_settings = __current_proxy_settings(context)
|
47
|
-
|
47
|
+
old_order = __data_rule(old_proxy_settings).record_order
|
48
48
|
|
49
|
-
if
|
49
|
+
if old_order == record_order.OVERWRITE:
|
50
50
|
scenario_key = data_rule.scenario_key
|
51
51
|
_scenario_key = __parse_scenario_key(scenario_key)
|
52
52
|
|
53
53
|
if _scenario_key:
|
54
54
|
scenario_model = ScenarioModel(new_settings)
|
55
55
|
|
56
|
-
# If
|
56
|
+
# If order is overwrite when recording, whenever intercept is disabled,
|
57
57
|
# set active scenario to not be overwritable
|
58
58
|
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
59
59
|
elif _mode == intercept_mode.MOCK:
|
@@ -73,7 +73,7 @@ def handle_scenario_update(new_settings: Settings, context = None):
|
|
73
73
|
if old_scenario_key != new_scenario_key:
|
74
74
|
data_rule = __data_rule(new_settings.proxy)
|
75
75
|
|
76
|
-
if data_rule.
|
76
|
+
if data_rule.record_order == record_order.OVERWRITE:
|
77
77
|
scenario_model = ScenarioModel(new_settings)
|
78
78
|
|
79
79
|
_old_scenario_key = __parse_scenario_key(old_scenario_key)
|
@@ -108,7 +108,7 @@ def handle_project_update(new_settings: Settings, context: Context = None):
|
|
108
108
|
if _project_key.id != _new_scenario_key.project_id:
|
109
109
|
data_rule.scenario_key = None
|
110
110
|
|
111
|
-
def
|
111
|
+
def handle_order_update(new_settings: Settings, context: Context = None):
|
112
112
|
data_rule = __data_rule(new_settings.proxy)
|
113
113
|
_mode = context['mode'] if context and context.get('mode') else new_settings.proxy.intercept.mode
|
114
114
|
|
@@ -116,10 +116,10 @@ def handle_policy_update(new_settings: Settings, context: Context = None):
|
|
116
116
|
old_data_rule = __data_rule(old_proxy_settings)
|
117
117
|
|
118
118
|
if _mode == intercept_mode.RECORD:
|
119
|
-
|
120
|
-
|
119
|
+
new_order = data_rule.record_order
|
120
|
+
old_order = old_data_rule.record_order
|
121
121
|
|
122
|
-
if
|
122
|
+
if new_order != old_order and old_order == record_order.OVERWRITE:
|
123
123
|
scenario_key = data_rule.scenario_key
|
124
124
|
_scenario_key = __parse_scenario_key(scenario_key)
|
125
125
|
|
@@ -3,10 +3,10 @@ import pdb
|
|
3
3
|
import sys
|
4
4
|
|
5
5
|
from stoobly_agent.app.settings import Settings
|
6
|
-
from stoobly_agent.config.constants import mode, mock_policy, record_policy, replay_policy
|
6
|
+
from stoobly_agent.config.constants import mode, mock_policy, record_order, record_policy, replay_policy
|
7
7
|
from stoobly_agent.lib.api.keys.project_key import ProjectKey
|
8
8
|
|
9
|
-
from .helpers.handle_config_update_service import handle_intercept_active_update,
|
9
|
+
from .helpers.handle_config_update_service import handle_intercept_active_update, handle_order_update
|
10
10
|
|
11
11
|
settings = Settings.instance()
|
12
12
|
|
@@ -17,16 +17,25 @@ if settings.cli.features.remote:
|
|
17
17
|
|
18
18
|
active_mode = settings.proxy.intercept.mode
|
19
19
|
|
20
|
+
def __get_order_options(active_mode):
|
21
|
+
if active_mode == mode.RECORD:
|
22
|
+
return [record_order.APPEND, record_order.OVERWRITE]
|
23
|
+
else:
|
24
|
+
return []
|
25
|
+
|
20
26
|
def __get_policy_options(active_mode):
|
21
27
|
if active_mode == mode.MOCK:
|
22
28
|
return [mock_policy.ALL, mock_policy.FOUND]
|
23
29
|
elif active_mode == mode.RECORD:
|
24
|
-
return [record_policy.ALL, record_policy.
|
30
|
+
return [record_policy.ALL, record_policy.API, record_policy.FOUND, record_policy.NOT_FOUND]
|
25
31
|
elif active_mode == mode.REPLAY:
|
26
32
|
return [replay_policy.ALL]
|
27
33
|
elif active_mode == mode.TEST:
|
28
34
|
return [mock_policy.FOUND]
|
35
|
+
else:
|
36
|
+
return []
|
29
37
|
|
38
|
+
order_options = __get_order_options(active_mode)
|
30
39
|
policy_options = __get_policy_options(active_mode)
|
31
40
|
|
32
41
|
@click.group(
|
@@ -69,11 +78,12 @@ def disable(**kwargs):
|
|
69
78
|
help="Configure intercept"
|
70
79
|
)
|
71
80
|
@click.option('--mode', type=click.Choice(mode_options))
|
81
|
+
@click.option('--order', type=click.Choice(order_options))
|
72
82
|
@click.option('--policy', type=click.Choice(policy_options))
|
73
83
|
def configure(**kwargs):
|
74
|
-
settings = Settings.instance()
|
84
|
+
settings: Settings = Settings.instance()
|
75
85
|
|
76
|
-
if not kwargs['mode'] and not kwargs['policy']:
|
86
|
+
if not kwargs['mode'] and not kwargs['order'] and not kwargs['policy']:
|
77
87
|
print("Error: Missing an option")
|
78
88
|
sys.exit(1)
|
79
89
|
|
@@ -89,6 +99,21 @@ def configure(**kwargs):
|
|
89
99
|
|
90
100
|
_mode = kwargs['mode'] or settings.proxy.intercept.mode
|
91
101
|
|
102
|
+
if kwargs['order']:
|
103
|
+
active_mode = settings.proxy.intercept.mode
|
104
|
+
|
105
|
+
if active_mode == mode.RECORD:
|
106
|
+
project_key = ProjectKey(settings.proxy.intercept.project_key)
|
107
|
+
data_rule = settings.proxy.data.data_rules(project_key.id)
|
108
|
+
data_rule.record_order = kwargs['order']
|
109
|
+
else:
|
110
|
+
print("Error: set --mode to a intercept mode that supports the order option", file=sys.stderr)
|
111
|
+
sys.exit(1)
|
112
|
+
|
113
|
+
handle_order_update(settings)
|
114
|
+
|
115
|
+
print(f"Updating {_mode} order to {kwargs['order']}")
|
116
|
+
|
92
117
|
if kwargs['policy']:
|
93
118
|
active_mode = settings.proxy.intercept.mode
|
94
119
|
valid_policies = __get_policy_options(active_mode)
|
@@ -109,8 +134,6 @@ def configure(**kwargs):
|
|
109
134
|
elif active_mode == mode.TEST:
|
110
135
|
data_rule.test_policy = kwargs['policy']
|
111
136
|
|
112
|
-
handle_policy_update(settings)
|
113
|
-
|
114
137
|
print(f"Updating {_mode} policy to {kwargs['policy']}")
|
115
138
|
|
116
139
|
settings.commit()
|
@@ -143,6 +143,12 @@ scenario/list:
|
|
143
143
|
@export EXEC_COMMAND=.list && \
|
144
144
|
export EXEC_OPTIONS="$(options)" && \
|
145
145
|
$(stoobly_exec)
|
146
|
+
scenario/overwrite:
|
147
|
+
# Overwrite a scenario
|
148
|
+
@export EXEC_COMMAND=.overwrite && \
|
149
|
+
export EXEC_OPTIONS="$(options)" && \
|
150
|
+
export EXEC_ARGS="$(key)" && \
|
151
|
+
$(stoobly_exec)
|
146
152
|
scenario/reset:
|
147
153
|
# Resets a scenario to its last snapshot
|
148
154
|
@export EXEC_COMMAND=.reset && \:
|
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
scenario_key=$1
|
4
|
+
|
5
|
+
if [ ! "$scenario_key" ]; then
|
6
|
+
echo "Missing argument 'scenario_key'"
|
7
|
+
stoobly-agent scenario list
|
8
|
+
exit
|
9
|
+
fi
|
10
|
+
|
11
|
+
stoobly-agent config scenario set "$scenario_key"
|
12
|
+
stoobly-agent intercept configure --mode record --order overwrite
|
13
|
+
stoobly-agent intercept disable
|
@@ -22,6 +22,14 @@ stoobly-agent config firewall set \
|
|
22
22
|
|
23
23
|
# Rewrite
|
24
24
|
echo "Configuring rewrite rules..."
|
25
|
+
stoobly-agent config rewrite set \
|
26
|
+
--method GET --method POST --method OPTIONS --method PUT --method DELETE \
|
27
|
+
--mode record \
|
28
|
+
--name authorization \
|
29
|
+
--pattern "$origin/?.*?" \
|
30
|
+
--type Header \
|
31
|
+
--value '' \
|
32
|
+
|
25
33
|
stoobly-agent config rewrite set \
|
26
34
|
--method GET --method POST --method OPTIONS --method PUT --method DELETE \
|
27
35
|
--mode record \
|
@@ -29,3 +37,11 @@ stoobly-agent config rewrite set \
|
|
29
37
|
--pattern "$origin/?.*?" \
|
30
38
|
--type Header \
|
31
39
|
--value '' \
|
40
|
+
|
41
|
+
stoobly-agent config rewrite set \
|
42
|
+
--method GET --method POST --method OPTIONS --method PUT --method DELETE \
|
43
|
+
--mode record \
|
44
|
+
--name set-cookie \
|
45
|
+
--pattern "$origin/?.*?" \
|
46
|
+
--type 'Response Header' \
|
47
|
+
--value '' \
|
@@ -20,6 +20,7 @@ from stoobly_agent.lib.api.keys import RequestKey, ScenarioKey
|
|
20
20
|
|
21
21
|
from .helpers.print_service import FORMATS, print_snapshots, select_print_options
|
22
22
|
from .helpers.verify_raw_request_service import verify_raw_request
|
23
|
+
from .types.snapshot_migration import SnapshotMigration
|
23
24
|
|
24
25
|
@click.group(
|
25
26
|
epilog="Run 'stoobly-agent project COMMAND --help' for more information on a command.",
|
@@ -91,6 +92,62 @@ def _list(**kwargs):
|
|
91
92
|
if len(formatted_events):
|
92
93
|
print_snapshots(formatted_events, **print_options)
|
93
94
|
|
95
|
+
@snapshot.command(
|
96
|
+
help="Migrate snapshots."
|
97
|
+
)
|
98
|
+
@click.option('--scenario-key', help='Apply migration to specific scenario.')
|
99
|
+
@click.argument('lifecycle_hooks_path')
|
100
|
+
def migrate(**kwargs):
|
101
|
+
from runpy import run_path
|
102
|
+
from stoobly_agent.config.constants.lifecycle_hooks import BEFORE_MIGRATE
|
103
|
+
|
104
|
+
scenario_key = None
|
105
|
+
if kwargs['scenario_key']:
|
106
|
+
scenario_key = ScenarioKey(kwargs['scenario_key'])
|
107
|
+
|
108
|
+
try:
|
109
|
+
lifecycle_hooks = run_path(kwargs['lifecycle_hooks_path'])
|
110
|
+
except Exception as e:
|
111
|
+
lifecycle_hooks = {}
|
112
|
+
|
113
|
+
before_migrate_hook = None
|
114
|
+
if BEFORE_MIGRATE not in lifecycle_hooks:
|
115
|
+
sys.exit(1)
|
116
|
+
else:
|
117
|
+
before_migrate_hook = lifecycle_hooks[BEFORE_MIGRATE]
|
118
|
+
|
119
|
+
log = Log()
|
120
|
+
|
121
|
+
request_snapshots = []
|
122
|
+
for event in log.resource_events:
|
123
|
+
if event.is_scenario():
|
124
|
+
# If scenario_key is set, only consider that scenario
|
125
|
+
if scenario_key and event.resource_uuid != scenario_key.id:
|
126
|
+
continue
|
127
|
+
|
128
|
+
scenario_snapshot: ScenarioSnapshot = event.snapshot()
|
129
|
+
_request_snapshots = scenario_snapshot.request_snapshots
|
130
|
+
|
131
|
+
for request_snapshot in _request_snapshots:
|
132
|
+
# Scenario requests will have the same log event
|
133
|
+
request_snapshots.append((request_snapshot, event))
|
134
|
+
elif event.is_request():
|
135
|
+
# If scenario_key is set, only consider that scenario
|
136
|
+
if scenario_key:
|
137
|
+
continue
|
138
|
+
|
139
|
+
request_snapshot: RequestSnapshot = event.snapshot()
|
140
|
+
request_snapshots.append((request_snapshot, event))
|
141
|
+
|
142
|
+
snapshot_migrations = {}
|
143
|
+
for request_snapshot, event in request_snapshots:
|
144
|
+
if request_snapshot.uuid not in snapshot_migrations:
|
145
|
+
snapshot_migration = SnapshotMigration(request_snapshot, event)
|
146
|
+
snapshot_migrations[request_snapshot.uuid] = snapshot_migration
|
147
|
+
|
148
|
+
if before_migrate_hook(snapshot_migration):
|
149
|
+
break
|
150
|
+
|
94
151
|
@snapshot.command(
|
95
152
|
help="Prune deleted snapshots."
|
96
153
|
)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
from mitmproxy.http import Request, Response
|
2
|
+
|
3
|
+
from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
|
4
|
+
from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import LogEvent
|
5
|
+
from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
|
6
|
+
from stoobly_agent.app.proxy.record.join_request_service import join_request_from_request_response
|
7
|
+
|
8
|
+
class SnapshotMigration():
|
9
|
+
_request: Request
|
10
|
+
_response: Response
|
11
|
+
_snapshot: RequestSnapshot
|
12
|
+
|
13
|
+
def __init__(self, snapshot: RequestSnapshot, log_event: LogEvent, log: Log = None):
|
14
|
+
self._event = log_event
|
15
|
+
self._log = log or Log()
|
16
|
+
self._request = snapshot.mitmproxy_request
|
17
|
+
self._response = snapshot.mitmproxy_response
|
18
|
+
self._snapshot = snapshot
|
19
|
+
|
20
|
+
@property
|
21
|
+
def request(self):
|
22
|
+
return self._request
|
23
|
+
|
24
|
+
@request.setter
|
25
|
+
def request(self, v: Request):
|
26
|
+
if not isinstance(v, Request):
|
27
|
+
raise TypeError('Invalid type.')
|
28
|
+
self._request = v
|
29
|
+
|
30
|
+
@property
|
31
|
+
def response(self):
|
32
|
+
return self._response
|
33
|
+
|
34
|
+
@response.setter
|
35
|
+
def response(self, v: Response):
|
36
|
+
if not isinstance(v, Response):
|
37
|
+
raise TypeError('Invalid type.')
|
38
|
+
self._response = v
|
39
|
+
|
40
|
+
@property
|
41
|
+
def snapshot(self):
|
42
|
+
return self._snapshot
|
43
|
+
|
44
|
+
@property
|
45
|
+
def uuid(self):
|
46
|
+
return self.snapshot.uuid
|
47
|
+
|
48
|
+
def delete(self, log: Log = None):
|
49
|
+
log = log or self._log
|
50
|
+
new_event = self._event.duplicate_as_delete()
|
51
|
+
log.append(str(new_event))
|
52
|
+
|
53
|
+
def save(self, log: Log = None):
|
54
|
+
log = log or self._log
|
55
|
+
request_uuid = self.snapshot.uuid
|
56
|
+
joined_request = join_request_from_request_response(self.request, self.response, id=request_uuid)
|
57
|
+
raw_request = joined_request.build()
|
58
|
+
self.snapshot.write_raw(raw_request)
|
59
|
+
new_event = self._event.duplicate()
|
60
|
+
log.append(str(new_event))
|
61
|
+
return new_event
|
@@ -46,7 +46,7 @@ class JoinedRequestAdapter():
|
|
46
46
|
def build_request_string(self):
|
47
47
|
request_string = RequestString(None)
|
48
48
|
|
49
|
-
delimitter = RequestStringCLRF
|
49
|
+
delimitter = RequestStringCLRF
|
50
50
|
request_string_toks = self.__split_joined_request_string[0].split(delimitter)
|
51
51
|
request_string.set(self.raw_request_string or delimitter.join(request_string_toks[1:]))
|
52
52
|
request_string.control = request_string_toks[0]
|
@@ -15,4 +15,4 @@ class MitmproxyRequestAdapter():
|
|
15
15
|
protocol = raw_http_request_adapter.protocol
|
16
16
|
http_version = protocol.split('/')[1]
|
17
17
|
|
18
|
-
return PythonRequestMitmproxyRequestAdapter(
|
18
|
+
return PythonRequestMitmproxyRequestAdapter(python_request, http_version).adapt()
|
@@ -13,10 +13,10 @@ class PythonRequestAdapterFactory():
|
|
13
13
|
self.__request = request
|
14
14
|
|
15
15
|
def mitmproxy_request(self, http_version: str = 'HTTP/1.1'):
|
16
|
-
return MitmproxyRequestAdapter(
|
16
|
+
return MitmproxyRequestAdapter(self.__request, http_version).adapt()
|
17
17
|
|
18
18
|
def raw_request(self, http_version: str = 'HTTP/1.1'):
|
19
|
-
return RawRequestAdapter(
|
19
|
+
return RawRequestAdapter(self.__request, http_version).adapt()
|
20
20
|
|
21
21
|
def stoobly_request(self):
|
22
22
|
return StooblyRequestAdapter(self.__request).adapt()
|
@@ -8,7 +8,7 @@ from stoobly_agent.lib.utils.decode import decode
|
|
8
8
|
|
9
9
|
class MitmproxyRequestAdapter():
|
10
10
|
|
11
|
-
def __init__(self,
|
11
|
+
def __init__(self, request: requests.Request, http_version: str = 'HTTP/1.1'):
|
12
12
|
self.__http_version = http_version
|
13
13
|
self.__request = request
|
14
14
|
|
@@ -9,12 +9,12 @@ from .mitmproxy_adapter import MitmproxyRequestAdapter
|
|
9
9
|
|
10
10
|
class RawRequestAdapter():
|
11
11
|
|
12
|
-
def __init__(self,
|
12
|
+
def __init__(self, request: requests.Request, http_version: str):
|
13
13
|
self.__http_version = http_version
|
14
14
|
self.__request = request
|
15
15
|
|
16
16
|
def adapt(self):
|
17
|
-
mitmproxy_request = MitmproxyRequestAdapter(self.
|
17
|
+
mitmproxy_request = MitmproxyRequestAdapter(self.__request, self.__http_version).adapt()
|
18
18
|
adapted_request = MitmproxyRequestFacade(mitmproxy_request)
|
19
19
|
proxy_request = ProxyRequest(adapted_request)
|
20
20
|
request_string = RequestString(proxy_request)
|
@@ -82,6 +82,13 @@ class Log():
|
|
82
82
|
|
83
83
|
return self.remove_processed_events(events, version_uuids)
|
84
84
|
|
85
|
+
@property
|
86
|
+
def resource_events(self) -> List[LogEvent]:
|
87
|
+
events = self.target_events
|
88
|
+
events = self.remove_dangling_events(events, events)
|
89
|
+
|
90
|
+
return list(filter(lambda e: e.action != DELETE_ACTION, events))
|
91
|
+
|
85
92
|
@property
|
86
93
|
def version(self):
|
87
94
|
version_file_path = DataDir.instance().snapshosts_version_path
|
@@ -84,6 +84,16 @@ class LogEvent():
|
|
84
84
|
toks[4] = str(int(time.time() * 1000))
|
85
85
|
return LogEvent(COLUMN_DELIMITTER.join(toks))
|
86
86
|
|
87
|
+
def duplicate_as_delete(self):
|
88
|
+
event = self.duplicate()
|
89
|
+
event.action = DELETE_ACTION
|
90
|
+
return event
|
91
|
+
|
92
|
+
def duplicate_as_put(self):
|
93
|
+
event = self.duplicate()
|
94
|
+
event.action = PUT_ACTION
|
95
|
+
return event
|
96
|
+
|
87
97
|
def is_request(self):
|
88
98
|
return self.resource == REQUEST_RESOURCE
|
89
99
|
|
@@ -34,6 +34,26 @@ class RequestSnapshot(Snapshot):
|
|
34
34
|
with open(request_file_path, 'rb') as fp:
|
35
35
|
return fp.read()
|
36
36
|
|
37
|
+
@property
|
38
|
+
def mitmproxy_request(self):
|
39
|
+
from stoobly_agent.app.models.adapters.python.request.mitmproxy_adapter import MitmproxyRequestAdapter
|
40
|
+
return MitmproxyRequestAdapter(self.python_request).adapt()
|
41
|
+
|
42
|
+
@property
|
43
|
+
def mitmproxy_response(self):
|
44
|
+
from stoobly_agent.app.models.adapters.python.response.mitmproxy_adapter import MitmproxyResponseAdapter
|
45
|
+
return MitmproxyResponseAdapter(self.python_response).adapt()
|
46
|
+
|
47
|
+
@property
|
48
|
+
def python_request(self):
|
49
|
+
from stoobly_agent.app.models.adapters.raw_joined.request.python_adapter import PythonRequestAdapter
|
50
|
+
return PythonRequestAdapter(self.request).adapt()
|
51
|
+
|
52
|
+
@property
|
53
|
+
def python_response(self):
|
54
|
+
from stoobly_agent.app.models.adapters.raw_joined.response.python_adapter import PythonResponseAdapter
|
55
|
+
return PythonResponseAdapter(self.request).adapt()
|
56
|
+
|
37
57
|
@property
|
38
58
|
def path(self):
|
39
59
|
dir_path = os.path.join(self.__requests_dir_path, self.uuid[0:2])
|
@@ -3,7 +3,7 @@ import os
|
|
3
3
|
import pdb
|
4
4
|
import shutil
|
5
5
|
|
6
|
-
from typing import Callable
|
6
|
+
from typing import Callable, List
|
7
7
|
|
8
8
|
from stoobly_agent.lib.orm.scenario import Scenario
|
9
9
|
|
@@ -46,6 +46,13 @@ class ScenarioSnapshot(Snapshot):
|
|
46
46
|
def metadata_path(self):
|
47
47
|
return os.path.join(self.__scenarios_dir_path, self.uuid)
|
48
48
|
|
49
|
+
@property
|
50
|
+
def request_snapshots(self) -> List[RequestSnapshot]:
|
51
|
+
snapshots = []
|
52
|
+
handler = lambda request_snapshot: snapshots.append(request_snapshot)
|
53
|
+
self.iter_request_snapshots(handler)
|
54
|
+
return snapshots
|
55
|
+
|
49
56
|
@property
|
50
57
|
def requests(self):
|
51
58
|
requests_file_path = self.requests_path
|
@@ -122,10 +122,10 @@ class LocalDBQueryParamAdapter(LocalDBAdapter):
|
|
122
122
|
parsed_url = parsed_url._replace(query=urlencode(_query_params, True))
|
123
123
|
request = LocalDBRequestAdapter(self.__request_orm).update(request_id, url=parsed_url.geturl())
|
124
124
|
|
125
|
-
return {
|
125
|
+
return self.success({
|
126
126
|
'name': name,
|
127
127
|
'value': value,
|
128
|
-
}
|
128
|
+
})
|
129
129
|
|
130
130
|
def __decode_id(self, id: str):
|
131
131
|
id = base64.b64decode(id)
|
@@ -3,7 +3,7 @@ import pdb
|
|
3
3
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
|
4
4
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
|
5
5
|
from stoobly_agent.app.models.factories.resource.local_db.helpers.scenario_snapshot import ScenarioSnapshot
|
6
|
-
from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF
|
6
|
+
from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF
|
7
7
|
from stoobly_agent.app.settings import Settings
|
8
8
|
from stoobly_agent.lib.logger import bcolors
|
9
9
|
|
@@ -218,15 +218,16 @@ class Apply():
|
|
218
218
|
|
219
219
|
snapshot_requests = {}
|
220
220
|
|
221
|
-
|
222
|
-
for
|
223
|
-
|
221
|
+
request_snapshots = snapshot.request_snapshots
|
222
|
+
for request_snapshot in request_snapshots:
|
223
|
+
raw_request = request_snapshot.request
|
224
|
+
|
225
|
+
toks = raw_request.split(REQUEST_STRING_CLRF, 1)
|
224
226
|
|
225
227
|
if len(toks) != 2:
|
226
228
|
return f"{snapshot.requests_path} contains an invalid request", 400
|
227
229
|
|
228
|
-
|
229
|
-
uuid = control.id
|
230
|
+
uuid = request_snapshot.uuid
|
230
231
|
res, status = self.__put_request(uuid, raw_request, scenario_id=scenario['id'])
|
231
232
|
|
232
233
|
if status != 200:
|
@@ -4,7 +4,7 @@ import requests
|
|
4
4
|
from stoobly_agent.app.models.adapters import JoinedRequestAdapter, RawHttpRequestAdapter, RawHttpResponseAdapter
|
5
5
|
from stoobly_agent.app.models.adapters.python import PythonRequestAdapterFactory, PythonResponseAdapterFactory
|
6
6
|
|
7
|
-
from stoobly_agent.app.proxy.record.join_request_service import InterceptSettings,
|
7
|
+
from stoobly_agent.app.proxy.record.join_request_service import InterceptSettings, join_request_from_request_response
|
8
8
|
from stoobly_agent.app.settings import Settings
|
9
9
|
from stoobly_agent.lib.logger import Logger
|
10
10
|
|
@@ -40,7 +40,9 @@ def build_params_from_python(request: requests.Request, response: requests.Respo
|
|
40
40
|
mitmproxy_response = PythonResponseAdapterFactory(response).mitmproxy_response()
|
41
41
|
|
42
42
|
intercept_settings = InterceptSettings(Settings.instance(), mitmproxy_request)
|
43
|
-
joined_request =
|
43
|
+
joined_request = join_request_from_request_response(
|
44
|
+
mitmproxy_request, mitmproxy_response, intercept_settings=intercept_settings
|
45
|
+
)
|
44
46
|
|
45
47
|
mitmproxy_flow_mock = MitmproxyFlowMock(mitmproxy_request, mitmproxy_response)
|
46
48
|
|