stoobly-agent 0.29.1__py3-none-any.whl → 0.30.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 +5 -2
- stoobly_agent/app/cli/config_cli.py +64 -14
- stoobly_agent/app/cli/endpoint_cli.py +1 -1
- stoobly_agent/app/cli/helpers/endpoint_facade.py +1 -1
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +44 -5
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +19 -3
- stoobly_agent/app/cli/helpers/replay_facade.py +2 -2
- stoobly_agent/app/cli/intercept_cli.py +14 -5
- stoobly_agent/app/cli/request_cli.py +2 -2
- stoobly_agent/app/cli/scenario_cli.py +2 -2
- stoobly_agent/app/cli/types/endpoint.py +1 -1
- stoobly_agent/app/models/adapters/raw_http_request_adapter.py +1 -1
- stoobly_agent/app/models/adapters/raw_http_response_adapter.py +1 -1
- stoobly_agent/app/proxy/context.py +18 -0
- stoobly_agent/app/proxy/handle_mock_service.py +1 -1
- stoobly_agent/app/proxy/handle_replay_service.py +2 -1
- stoobly_agent/app/proxy/intercept_handler.py +14 -3
- stoobly_agent/app/proxy/intercept_settings.py +28 -21
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +41 -35
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +0 -11
- stoobly_agent/app/proxy/mock/context.py +4 -11
- stoobly_agent/app/proxy/record/context.py +4 -12
- stoobly_agent/app/proxy/record/join_request_service.py +1 -1
- stoobly_agent/app/proxy/replay/context.py +4 -11
- stoobly_agent/app/proxy/replay/replay_request_service.py +5 -5
- stoobly_agent/app/proxy/replay/rewrite_params_service.py +1 -1
- stoobly_agent/app/proxy/run.py +3 -0
- stoobly_agent/app/proxy/test/context.py +2 -2
- stoobly_agent/app/proxy/test/context_abc.py +1 -1
- stoobly_agent/app/proxy/test/matchers/custom.py +1 -1
- stoobly_agent/app/settings/rewrite_rule.py +13 -0
- stoobly_agent/app/settings/types/__init__.py +1 -6
- stoobly_agent/app/settings/types/proxy_settings.py +6 -0
- stoobly_agent/app/settings/url_rule.py +33 -0
- stoobly_agent/cli.py +4 -0
- stoobly_agent/config/constants/custom_headers.py +1 -1
- stoobly_agent/config/constants/env_vars.py +1 -0
- stoobly_agent/config/constants/lifecycle_hooks.py +2 -0
- stoobly_agent/config/schema.yml +7 -1
- stoobly_agent/lib/orm/request.py +8 -4
- stoobly_agent/public/18-es2015.46d337c47cb41abec8ad.js +1 -0
- stoobly_agent/public/18-es5.46d337c47cb41abec8ad.js +1 -0
- stoobly_agent/public/dashboard.agent-alpha-0.30.0.tar.gz +0 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/runtime-es2015.3a15c6ce90c8f8cce796.js +1 -0
- stoobly_agent/public/runtime-es5.3a15c6ce90c8f8cce796.js +1 -0
- stoobly_agent-0.30.0.dist-info/METADATA +26 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/RECORD +53 -51
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/WHEEL +1 -1
- stoobly_agent/public/18-es2015.750ef954bde422debcb6.js +0 -1
- stoobly_agent/public/18-es5.750ef954bde422debcb6.js +0 -1
- stoobly_agent/public/dashboard.agent-alpha-0.29.0.tar.gz +0 -0
- stoobly_agent/public/runtime-es2015.b392473c492ce4cf1cb5.js +0 -1
- stoobly_agent/public/runtime-es5.b392473c492ce4cf1cb5.js +0 -1
- stoobly_agent-0.29.1.dist-info/METADATA +0 -26
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-0.29.1.dist-info → stoobly_agent-0.30.0.dist-info}/top_level.txt +0 -0
stoobly_agent/__init__.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
COMMAND = 'stoobly-agent'
|
2
|
-
VERSION = '0.
|
2
|
+
VERSION = '0.30.0'
|
@@ -3,7 +3,9 @@ import pdb
|
|
3
3
|
from mergedeep import merge
|
4
4
|
|
5
5
|
from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
|
6
|
-
from stoobly_agent.app.cli.helpers.handle_config_update_service import
|
6
|
+
from stoobly_agent.app.cli.helpers.handle_config_update_service import (
|
7
|
+
context as handle_context, handle_intercept_active_update, handle_policy_update, handle_project_update, handle_scenario_update
|
8
|
+
)
|
7
9
|
from stoobly_agent.app.models.scenario_model import ScenarioModel
|
8
10
|
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
9
11
|
from stoobly_agent.app.settings import Settings
|
@@ -106,8 +108,9 @@ class ConfigsController:
|
|
106
108
|
|
107
109
|
_handle_context = handle_context()
|
108
110
|
|
109
|
-
|
111
|
+
handle_intercept_active_update(settings, _handle_context)
|
110
112
|
handle_policy_update(settings, _handle_context)
|
113
|
+
handle_project_update(settings, _handle_context)
|
111
114
|
handle_scenario_update(settings, _handle_context)
|
112
115
|
|
113
116
|
settings.write(settings.to_dict())
|
@@ -8,7 +8,7 @@ from stoobly_agent.app.settings import Settings
|
|
8
8
|
from stoobly_agent.app.settings.constants import firewall_action, request_component
|
9
9
|
from stoobly_agent.app.settings.firewall_rule import FirewallRule
|
10
10
|
from stoobly_agent.app.settings.match_rule import MatchRule
|
11
|
-
from stoobly_agent.app.settings.rewrite_rule import ParameterRule, RewriteRule
|
11
|
+
from stoobly_agent.app.settings.rewrite_rule import ParameterRule, RewriteRule, UrlRule
|
12
12
|
from stoobly_agent.config.constants import mode
|
13
13
|
from stoobly_agent.config.data_dir import DataDir
|
14
14
|
from stoobly_agent.lib.api.keys import ProjectKey, ScenarioKey
|
@@ -146,6 +146,7 @@ def rewrite(ctx):
|
|
146
146
|
@rewrite.command(
|
147
147
|
help="Set rewrite rule."
|
148
148
|
)
|
149
|
+
@click.option('--host', help='Request URL host.')
|
149
150
|
@click.option(
|
150
151
|
'--method',
|
151
152
|
multiple=True,
|
@@ -159,17 +160,30 @@ def rewrite(ctx):
|
|
159
160
|
required=True,
|
160
161
|
type=click.Choice([mode.MOCK, mode.RECORD, mode.REPLAY] + ([mode.TEST] if is_remote else []))
|
161
162
|
)
|
162
|
-
@click.option('--name',
|
163
|
+
@click.option('--name', help='Name of the request component.')
|
163
164
|
@click.option('--pattern', required=True, help='URLs pattern.')
|
165
|
+
@click.option('--port', help='Request URL port.')
|
164
166
|
@click.option('--project-key', help='Project to add rewrite rule to.')
|
165
167
|
@click.option(
|
166
168
|
'--type',
|
167
|
-
required=True,
|
168
169
|
type=click.Choice([request_component.BODY_PARAM, request_component.HEADER, request_component.QUERY_PARAM]),
|
169
170
|
help='Request component type.'
|
170
171
|
)
|
171
|
-
@click.option('--value',
|
172
|
+
@click.option('--value', help='Rewrite value.')
|
172
173
|
def set(**kwargs):
|
174
|
+
if kwargs['name'] or kwargs['value'] or kwargs['type']:
|
175
|
+
if kwargs['name'] == None:
|
176
|
+
print("Error: missing option '--name'", file=sys.stderr)
|
177
|
+
sys.exit(1)
|
178
|
+
|
179
|
+
if kwargs['value'] == None:
|
180
|
+
print("Error: missing option '--value'", file=sys.stderr)
|
181
|
+
sys.exit(1)
|
182
|
+
|
183
|
+
if kwargs['type'] == None:
|
184
|
+
print("Error: missing option '--type'", file=sys.stderr)
|
185
|
+
sys.exit(1)
|
186
|
+
|
173
187
|
settings = Settings.instance()
|
174
188
|
project_key_str = resolve_project_key_and_validate(kwargs, settings)
|
175
189
|
project_key = ProjectKey(project_key_str)
|
@@ -183,27 +197,50 @@ def set(**kwargs):
|
|
183
197
|
filtered_rewrite_rules: List[RewriteRule] = list(filter(rewrite_rule_filter, rewrite_rules))
|
184
198
|
|
185
199
|
if len(filtered_rewrite_rules) == 0:
|
200
|
+
parameter_rules = list(filter(lambda r: r != None, [__select_parameter_rule(kwargs)]))
|
201
|
+
url_rules = list(filter(lambda r: r != None , [__select_url_rule(kwargs)]))
|
186
202
|
rewrite_rule = RewriteRule({
|
187
203
|
'methods': methods,
|
188
204
|
'pattern': kwargs['pattern'],
|
189
|
-
'parameter_rules':
|
205
|
+
'parameter_rules': parameter_rules,
|
206
|
+
'url_rules': url_rules,
|
190
207
|
})
|
191
208
|
rewrite_rules.append(rewrite_rule)
|
192
209
|
settings.proxy.rewrite.set_rewrite_rules(project_key.id, rewrite_rules)
|
193
210
|
else:
|
194
211
|
parameter_rule_filter = lambda rule: rule.name == kwargs['name'] and rule.type == kwargs['type'] and rule.modes == modes
|
212
|
+
url_rule_filter = lambda rule: rule.host == kwargs['host'] and rule.modes == modes
|
213
|
+
|
195
214
|
for rewrite_rule in filtered_rewrite_rules:
|
196
|
-
|
197
|
-
filtered_parameter_rules: List[ParameterRule] = list(filter(parameter_rule_filter, parameter_rules))
|
215
|
+
# Parameter rules
|
198
216
|
parameter_rule_dict = __select_parameter_rule(kwargs)
|
199
217
|
|
200
|
-
if
|
201
|
-
|
202
|
-
parameter_rules
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
218
|
+
if parameter_rule_dict:
|
219
|
+
parameter_rules = rewrite_rule.parameter_rules
|
220
|
+
filtered_parameter_rules: List[ParameterRule] = list(filter(parameter_rule_filter, parameter_rules))
|
221
|
+
|
222
|
+
if len(filtered_parameter_rules) == 0:
|
223
|
+
parameter_rule = ParameterRule(parameter_rule_dict)
|
224
|
+
parameter_rules.append(parameter_rule)
|
225
|
+
rewrite_rule.parameter_rules = parameter_rules
|
226
|
+
else:
|
227
|
+
for parameter_rule in filtered_parameter_rules:
|
228
|
+
parameter_rule.update(parameter_rule_dict)
|
229
|
+
|
230
|
+
# URL rules
|
231
|
+
url_rule_dict = __select_url_rule(kwargs)
|
232
|
+
|
233
|
+
if url_rule_dict:
|
234
|
+
url_rules = rewrite_rule.url_rules
|
235
|
+
filtered_url_rules: List[UrlRule] = list(filter(url_rule_filter, url_rules))
|
236
|
+
|
237
|
+
if len(filtered_url_rules) == 0:
|
238
|
+
url_rule = UrlRule(url_rule_dict)
|
239
|
+
url_rules.append(url_rule)
|
240
|
+
rewrite_rule.url_rules = url_rules
|
241
|
+
else:
|
242
|
+
for url_rule in filtered_url_rules:
|
243
|
+
url_rule.update(url_rule_dict)
|
207
244
|
|
208
245
|
settings.commit()
|
209
246
|
|
@@ -437,6 +474,9 @@ config.add_command(rewrite)
|
|
437
474
|
config.add_command(scenario)
|
438
475
|
|
439
476
|
def __select_parameter_rule(kwargs):
|
477
|
+
if kwargs['name'] == None or kwargs['value'] == None or kwargs['type'] == None:
|
478
|
+
return
|
479
|
+
|
440
480
|
return {
|
441
481
|
'modes': list(kwargs['mode']),
|
442
482
|
'name': kwargs['name'],
|
@@ -444,6 +484,16 @@ def __select_parameter_rule(kwargs):
|
|
444
484
|
'type': kwargs['type'],
|
445
485
|
}
|
446
486
|
|
487
|
+
def __select_url_rule(kwargs):
|
488
|
+
if kwargs['host'] == None or kwargs['port'] == None:
|
489
|
+
return
|
490
|
+
|
491
|
+
return {
|
492
|
+
'host': kwargs['host'],
|
493
|
+
'modes': list(kwargs['mode']),
|
494
|
+
'port': kwargs['port'],
|
495
|
+
}
|
496
|
+
|
447
497
|
def __project_key(settings):
|
448
498
|
project_key = settings.proxy.intercept.project_key
|
449
499
|
validate_project_key(project_key)
|
@@ -26,7 +26,7 @@ def endpoint(ctx):
|
|
26
26
|
)
|
27
27
|
@click.option('--format', required=True, type=click.Choice([OPENAPI_FORMAT]), help='File format')
|
28
28
|
@click.option('--scenario-key', help='Which scenario to import to. If none then all requests will be imported to.')
|
29
|
-
@click.option('--lifecycle-hooks-
|
29
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
30
30
|
@ConditionalDecorator(lambda f: click.option('--project-key', help='Project to create endpoint in.')(f), is_remote)
|
31
31
|
@click.argument('path')
|
32
32
|
def _import(**kwargs: EndpointCreateCliOptions):
|
@@ -19,6 +19,44 @@ def context() -> Context:
|
|
19
19
|
'current_proxy_settings': __current_proxy_settings()
|
20
20
|
}
|
21
21
|
|
22
|
+
def handle_intercept_active_update(new_settings: Settings, context: Context = None):
|
23
|
+
data_rule = __data_rule(new_settings.proxy)
|
24
|
+
is_active = new_settings.proxy.intercept.active
|
25
|
+
old_proxy_settings = __current_proxy_settings(context)
|
26
|
+
was_active = old_proxy_settings.intercept.active
|
27
|
+
_mode = context['mode'] if context and context.get('mode') else new_settings.proxy.intercept.mode
|
28
|
+
|
29
|
+
if not was_active and is_active:
|
30
|
+
if _mode == intercept_mode.RECORD:
|
31
|
+
new_policy = data_rule.record_policy
|
32
|
+
scenario_key = data_rule.scenario_key
|
33
|
+
_scenario_key = __parse_scenario_key(scenario_key)
|
34
|
+
|
35
|
+
if _scenario_key:
|
36
|
+
scenario_model = ScenarioModel(new_settings)
|
37
|
+
|
38
|
+
if new_policy == record_policy.OVERWRITE:
|
39
|
+
# If policy is overwrite when recording, whenever intercept is enabled,
|
40
|
+
# set active scenario to be overwritable
|
41
|
+
scenario_model.update(_scenario_key.id, **{ 'overwritable': True })[1]
|
42
|
+
else:
|
43
|
+
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
44
|
+
elif was_active and not is_active:
|
45
|
+
if _mode == intercept_mode.RECORD:
|
46
|
+
old_proxy_settings = __current_proxy_settings(context)
|
47
|
+
old_policy = __data_rule(old_proxy_settings).record_policy
|
48
|
+
|
49
|
+
if old_policy == record_policy.OVERWRITE:
|
50
|
+
scenario_key = data_rule.scenario_key
|
51
|
+
_scenario_key = __parse_scenario_key(scenario_key)
|
52
|
+
|
53
|
+
if _scenario_key:
|
54
|
+
scenario_model = ScenarioModel(new_settings)
|
55
|
+
|
56
|
+
# If policy is overwrite when recording, whenever intercept is disabled,
|
57
|
+
# set active scenario to not be overwritable
|
58
|
+
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
59
|
+
|
22
60
|
def handle_scenario_update(new_settings: Settings, context = None):
|
23
61
|
new_scenario_key = __scenario_key(new_settings.proxy)
|
24
62
|
if not new_scenario_key:
|
@@ -27,9 +65,10 @@ def handle_scenario_update(new_settings: Settings, context = None):
|
|
27
65
|
old_proxy_settings = __current_proxy_settings(context)
|
28
66
|
old_scenario_key = __scenario_key(old_proxy_settings)
|
29
67
|
|
30
|
-
|
31
|
-
|
32
|
-
|
68
|
+
if old_scenario_key != new_scenario_key:
|
69
|
+
data_rule = __data_rule(new_settings.proxy)
|
70
|
+
|
71
|
+
if data_rule.record_policy == record_policy.OVERWRITE:
|
33
72
|
scenario_model = ScenarioModel(new_settings)
|
34
73
|
|
35
74
|
_old_scenario_key = __parse_scenario_key(old_scenario_key)
|
@@ -75,13 +114,13 @@ def handle_policy_update(new_settings: Settings, context: Context = None):
|
|
75
114
|
new_policy = data_rule.record_policy
|
76
115
|
old_policy = old_data_rule.record_policy
|
77
116
|
|
78
|
-
if new_policy != old_policy and
|
117
|
+
if new_policy != old_policy and old_policy == record_policy.OVERWRITE:
|
79
118
|
scenario_key = data_rule.scenario_key
|
80
119
|
_scenario_key = __parse_scenario_key(scenario_key)
|
81
120
|
|
82
121
|
if _scenario_key:
|
83
122
|
scenario_model = ScenarioModel(new_settings)
|
84
|
-
scenario_model.update(_scenario_key.id, **{ 'overwritable':
|
123
|
+
scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
|
85
124
|
|
86
125
|
def __current_proxy_settings(context: Context = None):
|
87
126
|
if context and context.get('current_proxy_settings'):
|
@@ -1,13 +1,13 @@
|
|
1
1
|
import copy
|
2
2
|
from functools import reduce
|
3
3
|
import itertools
|
4
|
-
import pdb
|
5
4
|
from pprint import pprint
|
6
5
|
import re
|
7
6
|
from typing import Dict, List, Union
|
8
7
|
from urllib.parse import urlparse
|
9
8
|
|
10
9
|
from openapi_core import Spec
|
10
|
+
import yaml
|
11
11
|
|
12
12
|
from stoobly_agent.lib.api.interfaces.endpoints import (
|
13
13
|
Alias,
|
@@ -23,7 +23,14 @@ class OpenApiEndpointAdapter():
|
|
23
23
|
return
|
24
24
|
|
25
25
|
def adapt_from_file(self, file_path) -> List[EndpointShowResponse]:
|
26
|
-
spec =
|
26
|
+
spec = {}
|
27
|
+
|
28
|
+
with open(file_path, "r") as stream:
|
29
|
+
file_data: Dict = yaml.safe_load(stream)
|
30
|
+
if 'info' not in file_data:
|
31
|
+
self.__add_info(file_data)
|
32
|
+
spec = Spec.from_dict(file_data)
|
33
|
+
|
27
34
|
return self.adapt(spec)
|
28
35
|
|
29
36
|
def adapt(self, spec: Spec) -> List[EndpointShowResponse]:
|
@@ -33,7 +40,7 @@ class OpenApiEndpointAdapter():
|
|
33
40
|
schemas = components.get("schemas", {})
|
34
41
|
paths = spec.getkey('paths')
|
35
42
|
|
36
|
-
servers_spec = spec
|
43
|
+
servers_spec = spec.get("servers")
|
37
44
|
servers = self.__evaluate_servers(servers_spec)
|
38
45
|
|
39
46
|
for _, server in enumerate(servers):
|
@@ -231,6 +238,15 @@ class OpenApiEndpointAdapter():
|
|
231
238
|
|
232
239
|
return endpoints
|
233
240
|
|
241
|
+
def __add_info(self, file_data: Dict):
|
242
|
+
if 'info' in file_data:
|
243
|
+
return
|
244
|
+
|
245
|
+
file_data['info'] = {
|
246
|
+
'version': '0.0.1',
|
247
|
+
'title': ''
|
248
|
+
}
|
249
|
+
|
234
250
|
def __get_most_recent_param(self, literal_params: dict):
|
235
251
|
return list(literal_params)[-1] if literal_params else None
|
236
252
|
|
@@ -14,7 +14,7 @@ class ReplayCliOptions(TypedDict):
|
|
14
14
|
assign: List[str]
|
15
15
|
group_by: str
|
16
16
|
host: str
|
17
|
-
|
17
|
+
lifecycle_hooks_path: str
|
18
18
|
on_response: Callable
|
19
19
|
project_key: str
|
20
20
|
record: bool
|
@@ -54,7 +54,7 @@ class ReplayFacade():
|
|
54
54
|
'before_replay': cli_options.get('before_replay'),
|
55
55
|
'group_by': cli_options.get('group_by'),
|
56
56
|
'host': cli_options.get('host'),
|
57
|
-
'
|
57
|
+
'lifecycle_hooks_path': cli_options.get('lifecycle_hooks_path'),
|
58
58
|
'overwrite': cli_options.get('overwrite'),
|
59
59
|
'request_origin': request_origin.CLI,
|
60
60
|
'save': cli_options.get('save'),
|
@@ -6,7 +6,7 @@ from stoobly_agent.app.settings import Settings
|
|
6
6
|
from stoobly_agent.config.constants import mode, mock_policy, 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_policy_update
|
9
|
+
from .helpers.handle_config_update_service import handle_intercept_active_update, handle_policy_update
|
10
10
|
|
11
11
|
settings = Settings.instance()
|
12
12
|
|
@@ -43,6 +43,8 @@ def intercept(ctx):
|
|
43
43
|
def enable(**kwargs):
|
44
44
|
settings.proxy.intercept.active = True
|
45
45
|
|
46
|
+
handle_intercept_active_update(settings)
|
47
|
+
|
46
48
|
settings.commit()
|
47
49
|
|
48
50
|
print("Intercept enabled!")
|
@@ -55,6 +57,8 @@ def disable(**kwargs):
|
|
55
57
|
|
56
58
|
settings.proxy.intercept.active = False
|
57
59
|
|
60
|
+
handle_intercept_active_update(settings)
|
61
|
+
|
58
62
|
settings.commit()
|
59
63
|
|
60
64
|
print("Intercept disabled!")
|
@@ -72,9 +76,14 @@ def configure(**kwargs):
|
|
72
76
|
sys.exit(1)
|
73
77
|
|
74
78
|
if kwargs['mode']:
|
75
|
-
settings.proxy.intercept.mode
|
79
|
+
if settings.proxy.intercept.mode != kwargs['mode']:
|
80
|
+
if settings.proxy.intercept.active:
|
81
|
+
settings.proxy.intercept.active = False
|
82
|
+
handle_intercept_active_update(settings)
|
76
83
|
|
77
|
-
|
84
|
+
settings.proxy.intercept.mode = kwargs['mode']
|
85
|
+
|
86
|
+
print(f"Updating intercept mode to {kwargs['mode']}")
|
78
87
|
|
79
88
|
_mode = kwargs['mode'] or settings.proxy.intercept.mode
|
80
89
|
|
@@ -98,9 +107,9 @@ def configure(**kwargs):
|
|
98
107
|
elif active_mode == mode.TEST:
|
99
108
|
data_rule.test_policy = kwargs['policy']
|
100
109
|
|
110
|
+
handle_policy_update(settings)
|
111
|
+
|
101
112
|
print(f"Updating {_mode} policy to {kwargs['policy']}")
|
102
|
-
|
103
|
-
handle_policy_update(settings)
|
104
113
|
|
105
114
|
settings.commit()
|
106
115
|
|
@@ -116,7 +116,7 @@ def list(**kwargs):
|
|
116
116
|
@click.option('--format', type=click.Choice([BODY_FORMAT, JSON_FORMAT]), help='Format replay response.')
|
117
117
|
@click.option('--host', help='Rewrite request host.')
|
118
118
|
@ConditionalDecorator(lambda f: click.option('--group-by', help='Repeat for each alias name.')(f), is_remote)
|
119
|
-
@click.option('--lifecycle-hooks-
|
119
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
120
120
|
@click.option(
|
121
121
|
'--log-level', default=logger.WARNING, type=click.Choice(log_levels),
|
122
122
|
help='''
|
@@ -215,7 +215,7 @@ if is_remote:
|
|
215
215
|
@click.option('--format', type=click.Choice([BODY_FORMAT, JSON_FORMAT]), help='Format replay response.')
|
216
216
|
@click.option('--group-by', help='Repeat for each alias name.')
|
217
217
|
@click.option('--host', help='Rewrite request host.')
|
218
|
-
@click.option('--lifecycle-hooks-
|
218
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
219
219
|
@click.option(
|
220
220
|
'--log-level', default=logger.WARNING, type=click.Choice(log_levels),
|
221
221
|
help='''
|
@@ -65,7 +65,7 @@ def create(**kwargs):
|
|
65
65
|
@click.option('--format', type=click.Choice([JSON_FORMAT]), help='Format replay response.')
|
66
66
|
@ConditionalDecorator(lambda f: click.option('--group-by', help='Repeat for each alias name.')(f), is_remote)
|
67
67
|
@click.option('--host', help='Rewrite request host.')
|
68
|
-
@click.option('--lifecycle-hooks-
|
68
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
69
69
|
@click.option(
|
70
70
|
'--log-level', default=logger.WARNING, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]),
|
71
71
|
help='''
|
@@ -261,7 +261,7 @@ if is_remote:
|
|
261
261
|
@click.option('--format', type=click.Choice([JSON_FORMAT]), help='Format replay response.')
|
262
262
|
@click.option('--group-by', help='Repeat for each alias name.')
|
263
263
|
@click.option('--host', help='Rewrite request host.')
|
264
|
-
@click.option('--lifecycle-hooks-
|
264
|
+
@click.option('--lifecycle-hooks-path', help='Path to lifecycle hooks script.')
|
265
265
|
@click.option(
|
266
266
|
'--log-level', default=logger.WARNING, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]),
|
267
267
|
help='''
|
@@ -4,7 +4,7 @@ from stoobly_agent.app.models.types import OPENAPI_FORMAT
|
|
4
4
|
|
5
5
|
class EndpointCreateCliOptions(TypedDict):
|
6
6
|
format: Optional[Literal[f"{OPENAPI_FORMAT}"]]
|
7
|
-
|
7
|
+
lifecycle_hooks_path: Optional[str]
|
8
8
|
path: str
|
9
9
|
project_key: Optional[str]
|
10
10
|
scenario_key: Optional[str]
|
@@ -28,7 +28,7 @@ class RawHttpRequestAdapter():
|
|
28
28
|
|
29
29
|
if decoded_key in self.headers:
|
30
30
|
separator = HEADER_COOKIE_SEPARATOR if decoded_key.lower() == HEADER_COOKIE else HEADER_SEPARATOR
|
31
|
-
self.headers[decoded_key] = separator.join(self.headers[decoded_key], decoded_value)
|
31
|
+
self.headers[decoded_key] = separator.join([self.headers[decoded_key], decoded_value])
|
32
32
|
else:
|
33
33
|
self.headers[decoded_key] = decoded_value
|
34
34
|
|
@@ -35,7 +35,7 @@ class RawHttpResponseAdapter():
|
|
35
35
|
decoded_key = decode(header_key)
|
36
36
|
decoded_value = decode(header_value).strip()
|
37
37
|
if decoded_key in self.headers:
|
38
|
-
self.headers[decoded_key] = HEADER_SEPARATOR.join(self.headers[decoded_key], decoded_value)
|
38
|
+
self.headers[decoded_key] = HEADER_SEPARATOR.join([self.headers[decoded_key], decoded_value])
|
39
39
|
else:
|
40
40
|
self.headers[decoded_key] = decoded_value
|
41
41
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow
|
2
|
+
|
3
|
+
from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
|
4
|
+
|
5
|
+
class InterceptContext():
|
6
|
+
|
7
|
+
def __init__(self, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
8
|
+
self.__flow = flow
|
9
|
+
self.__intercept_settings = intercept_settings
|
10
|
+
|
11
|
+
@property
|
12
|
+
def flow(self):
|
13
|
+
return self.__flow
|
14
|
+
|
15
|
+
@property
|
16
|
+
def intercept_settings(self):
|
17
|
+
return self.__intercept_settings
|
18
|
+
|
@@ -41,7 +41,7 @@ def handle_request_mock_generic(context: MockContext, **options: MockOptions):
|
|
41
41
|
# Rewrite request with paramter rules for mock
|
42
42
|
request: MitmproxyRequest = context.flow.request
|
43
43
|
request_facade = MitmproxyRequestFacade(request)
|
44
|
-
request_facade.
|
44
|
+
request_facade.with_parameter_rules(rewrite_rules).rewrite()
|
45
45
|
|
46
46
|
__mock_hook(lifecycle_hooks.BEFORE_MOCK, context)
|
47
47
|
|
@@ -28,10 +28,11 @@ def __replay_request(replay_context: ReplayContext):
|
|
28
28
|
"""
|
29
29
|
intercept_settings: InterceptSettings = replay_context.intercept_settings
|
30
30
|
rewrite_rules = intercept_settings.rewrite_rules
|
31
|
+
|
31
32
|
if len(rewrite_rules) > 0:
|
32
33
|
request: MitmproxyRequest = replay_context.flow.request
|
33
34
|
request_facade = MitmproxyRequestFacade(request)
|
34
|
-
request_facade.
|
35
|
+
request_facade.with_parameter_rules(rewrite_rules).with_url_rules(rewrite_rules).rewrite()
|
35
36
|
|
36
37
|
__replay_hook(lifecycle_hooks.BEFORE_REPLAY, replay_context)
|
37
38
|
|
@@ -2,8 +2,9 @@ import os
|
|
2
2
|
import pdb
|
3
3
|
|
4
4
|
from mitmproxy.http import HTTPFlow as MitmproxyHTTPFlow
|
5
|
-
from mitmproxy.http import Headers, Request as MitmproxyRequest
|
5
|
+
from mitmproxy.http import Headers, Request as MitmproxyRequest
|
6
6
|
|
7
|
+
from stoobly_agent.app.proxy.context import InterceptContext
|
7
8
|
from stoobly_agent.app.proxy.handle_mock_service import handle_request_mock
|
8
9
|
from stoobly_agent.app.proxy.handle_replay_service import handle_request_replay, handle_response_replay
|
9
10
|
from stoobly_agent.app.proxy.handle_record_service import handle_response_record
|
@@ -14,7 +15,7 @@ from stoobly_agent.app.proxy.replay.context import ReplayContext
|
|
14
15
|
from stoobly_agent.app.proxy.record.context import RecordContext
|
15
16
|
from stoobly_agent.app.proxy.utils.response_handler import bad_request
|
16
17
|
from stoobly_agent.app.settings import Settings
|
17
|
-
from stoobly_agent.config.constants import mode
|
18
|
+
from stoobly_agent.config.constants import lifecycle_hooks, mode
|
18
19
|
from stoobly_agent.lib.logger import Logger
|
19
20
|
|
20
21
|
# Disable proxy settings in urllib
|
@@ -29,6 +30,8 @@ def request(flow: MitmproxyHTTPFlow):
|
|
29
30
|
|
30
31
|
intercept_settings = InterceptSettings(Settings.instance(), request)
|
31
32
|
|
33
|
+
__intercept_hook(lifecycle_hooks.BEFORE_REQUEST, flow, intercept_settings)
|
34
|
+
|
32
35
|
active_mode = intercept_settings.mode
|
33
36
|
Logger.instance().debug(f"{LOG_ID}:ProxyMode: {active_mode}")
|
34
37
|
|
@@ -56,6 +59,8 @@ def response(flow: MitmproxyHTTPFlow):
|
|
56
59
|
intercept_settings = InterceptSettings(Settings.instance(), request)
|
57
60
|
intercept_settings.for_response()
|
58
61
|
|
62
|
+
__intercept_hook(lifecycle_hooks.BEFORE_RESPONSE, flow, intercept_settings)
|
63
|
+
|
59
64
|
active_mode = intercept_settings.mode
|
60
65
|
|
61
66
|
if active_mode == mode.RECORD:
|
@@ -90,4 +95,10 @@ def __patch_cookie(request: MitmproxyRequest):
|
|
90
95
|
|
91
96
|
def __combine_header(headers: Headers, header_name: str, delimitter: str):
|
92
97
|
values = headers.get_all(header_name)
|
93
|
-
headers[header_name] = delimitter.join(values)
|
98
|
+
headers[header_name] = delimitter.join(values)
|
99
|
+
|
100
|
+
def __intercept_hook(hook: str, flow: MitmproxyHTTPFlow, intercept_settings: InterceptSettings):
|
101
|
+
lifecycle_hooks_module = intercept_settings.lifecycle_hooks
|
102
|
+
|
103
|
+
if hook in lifecycle_hooks_module:
|
104
|
+
lifecycle_hooks_module[hook](InterceptContext(flow, intercept_settings))
|
@@ -10,8 +10,8 @@ from stoobly_agent.app.settings.constants import firewall_action, intercept_mode
|
|
10
10
|
from stoobly_agent.app.settings.rewrite_rule import RewriteRule
|
11
11
|
from stoobly_agent.app.settings.firewall_rule import FirewallRule
|
12
12
|
from stoobly_agent.app.settings import Settings
|
13
|
-
from stoobly_agent.app.settings.types import IgnoreRule, MatchRule
|
14
|
-
from stoobly_agent.config.constants import custom_headers, mode, request_origin, test_filter
|
13
|
+
from stoobly_agent.app.settings.types import IgnoreRule, MatchRule
|
14
|
+
from stoobly_agent.config.constants import custom_headers, env_vars, mode, request_origin, test_filter
|
15
15
|
from stoobly_agent.lib.api.keys.project_key import InvalidProjectKey, ProjectKey
|
16
16
|
from stoobly_agent.lib.logger import Logger
|
17
17
|
|
@@ -62,9 +62,12 @@ class InterceptSettings:
|
|
62
62
|
return custom_headers.PROXY_MODE in self.__headers
|
63
63
|
|
64
64
|
@property
|
65
|
-
def
|
66
|
-
if self.__headers and custom_headers.
|
67
|
-
return self.__headers[custom_headers.
|
65
|
+
def lifecycle_hooks_path(self):
|
66
|
+
if self.__headers and custom_headers.LIFECYCLE_HOOKS_PATH in self.__headers:
|
67
|
+
return self.__headers[custom_headers.LIFECYCLE_HOOKS_PATH]
|
68
|
+
|
69
|
+
if os.environ.get(env_vars.AGENT_LIFECYCLE_HOOKS_PATH):
|
70
|
+
return os.environ[env_vars.AGENT_LIFECYCLE_HOOKS_PATH]
|
68
71
|
|
69
72
|
@property
|
70
73
|
def lifecycle_hooks(self):
|
@@ -151,11 +154,11 @@ class InterceptSettings:
|
|
151
154
|
return self.__select_rewrite_rules(_mode)
|
152
155
|
|
153
156
|
@property
|
154
|
-
def record_rewrite_rules(self) -> List[
|
157
|
+
def record_rewrite_rules(self) -> List[RewriteRule]:
|
155
158
|
return self.__select_rewrite_rules(mode.RECORD)
|
156
159
|
|
157
160
|
@property
|
158
|
-
def mock_rewrite_rules(self) -> List[
|
161
|
+
def mock_rewrite_rules(self) -> List[RewriteRule]:
|
159
162
|
return self.__select_rewrite_rules(mode.MOCK)
|
160
163
|
|
161
164
|
@property
|
@@ -207,21 +210,18 @@ class InterceptSettings:
|
|
207
210
|
|
208
211
|
# Filter only parameters matching active intercept mode
|
209
212
|
for rewrite_rule in self.__rewrite_rules:
|
210
|
-
|
213
|
+
# If url rule applies, then update .url_rules with url_rule
|
214
|
+
url_rules = self.__select_url_rules(rewrite_rule)
|
211
215
|
|
212
|
-
# If
|
213
|
-
|
214
|
-
continue
|
215
|
-
|
216
|
-
# Build a new RewriteRule object contain only parameter rules matching intercept mode
|
217
|
-
rewrite_rule = RewriteRule({
|
218
|
-
'methods': rewrite_rule.methods,
|
219
|
-
'pattern': rewrite_rule.pattern,
|
220
|
-
'parameters_rules': [], # Has to be dict form, manually set it
|
221
|
-
})
|
222
|
-
rewrite_rule.parameter_rules = parameter_rules
|
216
|
+
# If parameters rules apply, then update .parameter_rules with applicable parameter_rules
|
217
|
+
parameter_rules = self.__select_parameter_rules(rewrite_rule, mode)
|
223
218
|
|
224
|
-
|
219
|
+
if len(url_rules) > 0 or len(parameter_rules) > 0:
|
220
|
+
# Build a new RewriteRule object contain only parameter rules matching intercept mode
|
221
|
+
rewrite_rule = RewriteRule(rewrite_rule.to_dict())
|
222
|
+
rewrite_rule.url_rules = url_rules
|
223
|
+
rewrite_rule.parameter_rules = parameter_rules
|
224
|
+
rules.append(rewrite_rule)
|
225
225
|
|
226
226
|
return rules
|
227
227
|
|
@@ -232,8 +232,15 @@ class InterceptSettings:
|
|
232
232
|
rewrite_rule.parameter_rules or []
|
233
233
|
))
|
234
234
|
|
235
|
+
def __select_url_rules(self, rewrite_rule: RewriteRule, mode = None):
|
236
|
+
mode = mode or self.mode
|
237
|
+
return list(filter(
|
238
|
+
lambda url: mode in url.modes,
|
239
|
+
rewrite_rule.url_rules or []
|
240
|
+
))
|
241
|
+
|
235
242
|
def __initialize_lifecycle_hooks(self):
|
236
|
-
script_path = self.
|
243
|
+
script_path = self.lifecycle_hooks_path
|
237
244
|
|
238
245
|
if not script_path:
|
239
246
|
return
|