stoobly-agent 0.29.2__py3-none-any.whl → 0.30.1__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/cli/config_cli.py +64 -14
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +79 -8
- 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_settings.py +20 -16
- stoobly_agent/app/proxy/mitmproxy/request_facade.py +41 -35
- stoobly_agent/app/proxy/mitmproxy/response_facade.py +0 -11
- stoobly_agent/app/proxy/record/join_request_service.py +1 -1
- stoobly_agent/app/proxy/replay/rewrite_params_service.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/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.29.2.dist-info → stoobly_agent-0.30.1.dist-info}/METADATA +1 -1
- {stoobly_agent-0.29.2.dist-info → stoobly_agent-0.30.1.dist-info}/RECORD +28 -27
- 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.2.dist-info → stoobly_agent-0.30.1.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.29.2.dist-info → stoobly_agent-0.30.1.dist-info}/WHEEL +0 -0
- {stoobly_agent-0.29.2.dist-info → stoobly_agent-0.30.1.dist-info}/entry_points.txt +0 -0
- {stoobly_agent-0.29.2.dist-info → stoobly_agent-0.30.1.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.1'
|
@@ -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)
|
@@ -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,17 +23,30 @@ 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
|
+
|
31
|
+
if 'info' not in file_data:
|
32
|
+
self.__add_info(file_data)
|
33
|
+
|
34
|
+
missing_oauth2_scopes = self.__is_missing_oauth2_scopes(file_data)
|
35
|
+
if missing_oauth2_scopes:
|
36
|
+
self.__add_oauth2_default_scopes(file_data)
|
37
|
+
|
38
|
+
spec = Spec.from_dict(file_data)
|
39
|
+
|
27
40
|
return self.adapt(spec)
|
28
41
|
|
29
42
|
def adapt(self, spec: Spec) -> List[EndpointShowResponse]:
|
30
43
|
endpoints = []
|
31
44
|
endpoint_counter = 0
|
32
|
-
components = spec.get("components")
|
45
|
+
components = spec.get("components", {})
|
33
46
|
schemas = components.get("schemas", {})
|
34
47
|
paths = spec.getkey('paths')
|
35
48
|
|
36
|
-
servers_spec = spec
|
49
|
+
servers_spec = spec.get("servers")
|
37
50
|
servers = self.__evaluate_servers(servers_spec)
|
38
51
|
|
39
52
|
for _, server in enumerate(servers):
|
@@ -177,12 +190,12 @@ class OpenApiEndpointAdapter():
|
|
177
190
|
request_body = operation.get("requestBody", {})
|
178
191
|
required_request_body = request_body.get("required")
|
179
192
|
required_body_params = []
|
180
|
-
param_properties = {}
|
181
193
|
literal_body_params = {}
|
182
194
|
request_body_array = False
|
183
195
|
|
184
196
|
content = request_body.get("content", {})
|
185
197
|
for mimetype, media_type in content.items():
|
198
|
+
param_properties = {}
|
186
199
|
schema = media_type['schema']
|
187
200
|
|
188
201
|
# If Spec Component reference, look it up in components
|
@@ -195,12 +208,10 @@ class OpenApiEndpointAdapter():
|
|
195
208
|
schema_type = schema.get('type')
|
196
209
|
if schema_type:
|
197
210
|
if schema_type == 'object':
|
198
|
-
param_properties = schema
|
211
|
+
param_properties = schema.get('properties', {})
|
199
212
|
elif schema_type == 'array':
|
200
213
|
request_body_array = True
|
201
214
|
param_properties = {'tmp': schema['items']}
|
202
|
-
else:
|
203
|
-
param_properties = {}
|
204
215
|
|
205
216
|
for property_key, property_value in param_properties.items():
|
206
217
|
if property_key in required_body_params:
|
@@ -231,6 +242,66 @@ class OpenApiEndpointAdapter():
|
|
231
242
|
|
232
243
|
return endpoints
|
233
244
|
|
245
|
+
def __add_info(self, file_data: Dict):
|
246
|
+
if 'info' in file_data:
|
247
|
+
return
|
248
|
+
|
249
|
+
file_data['info'] = {
|
250
|
+
'version': '0.0.1',
|
251
|
+
'title': ''
|
252
|
+
}
|
253
|
+
|
254
|
+
def __is_missing_oauth2_scopes(self, file_data: Dict):
|
255
|
+
is_missing = False
|
256
|
+
if 'components' in file_data:
|
257
|
+
components = file_data['components']
|
258
|
+
|
259
|
+
if 'securitySchemes' in components:
|
260
|
+
security_schemes = components['securitySchemes']
|
261
|
+
|
262
|
+
for scheme_name, scheme in security_schemes.items():
|
263
|
+
if scheme.get('type') == 'oauth2':
|
264
|
+
flows = scheme.get('flows')
|
265
|
+
|
266
|
+
if flows and flows.get('clientCredentials'):
|
267
|
+
scopes = flows.get('clientCredentials').get('scopes')
|
268
|
+
|
269
|
+
if scopes is None:
|
270
|
+
return True
|
271
|
+
|
272
|
+
break
|
273
|
+
|
274
|
+
return is_missing
|
275
|
+
|
276
|
+
def __add_oauth2_default_scopes(self, file_data: Dict):
|
277
|
+
if 'components' not in file_data:
|
278
|
+
return
|
279
|
+
components = file_data['components']
|
280
|
+
|
281
|
+
if 'securitySchemes' not in components:
|
282
|
+
return
|
283
|
+
security_schemes = components['securitySchemes']
|
284
|
+
|
285
|
+
oauth_security_scheme = {}
|
286
|
+
for scheme_name, scheme in security_schemes.items():
|
287
|
+
if scheme.get('type') == 'oauth2':
|
288
|
+
oauth_security_scheme = scheme
|
289
|
+
break
|
290
|
+
|
291
|
+
# If empty dict or None
|
292
|
+
if not oauth_security_scheme:
|
293
|
+
return
|
294
|
+
|
295
|
+
flows = oauth_security_scheme['flows']
|
296
|
+
if not flows:
|
297
|
+
return
|
298
|
+
|
299
|
+
client_credentials = flows['clientCredentials']
|
300
|
+
if not client_credentials:
|
301
|
+
return
|
302
|
+
|
303
|
+
client_credentials['scopes'] = {}
|
304
|
+
|
234
305
|
def __get_most_recent_param(self, literal_params: dict):
|
235
306
|
return list(literal_params)[-1] if literal_params else None
|
236
307
|
|
@@ -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).with_url_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
|
|
@@ -10,7 +10,7 @@ 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
|
13
|
+
from stoobly_agent.app.settings.types import IgnoreRule, MatchRule
|
14
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
|
@@ -154,11 +154,11 @@ class InterceptSettings:
|
|
154
154
|
return self.__select_rewrite_rules(_mode)
|
155
155
|
|
156
156
|
@property
|
157
|
-
def record_rewrite_rules(self) -> List[
|
157
|
+
def record_rewrite_rules(self) -> List[RewriteRule]:
|
158
158
|
return self.__select_rewrite_rules(mode.RECORD)
|
159
159
|
|
160
160
|
@property
|
161
|
-
def mock_rewrite_rules(self) -> List[
|
161
|
+
def mock_rewrite_rules(self) -> List[RewriteRule]:
|
162
162
|
return self.__select_rewrite_rules(mode.MOCK)
|
163
163
|
|
164
164
|
@property
|
@@ -210,21 +210,18 @@ class InterceptSettings:
|
|
210
210
|
|
211
211
|
# Filter only parameters matching active intercept mode
|
212
212
|
for rewrite_rule in self.__rewrite_rules:
|
213
|
-
|
214
|
-
|
215
|
-
# If no parameters rules were found, then this filter rule is not applied
|
216
|
-
if len(parameter_rules) == 0:
|
217
|
-
continue
|
213
|
+
# If url rule applies, then update .url_rules with url_rule
|
214
|
+
url_rules = self.__select_url_rules(rewrite_rule)
|
218
215
|
|
219
|
-
#
|
220
|
-
|
221
|
-
'methods': rewrite_rule.methods,
|
222
|
-
'pattern': rewrite_rule.pattern,
|
223
|
-
'parameters_rules': [], # Has to be dict form, manually set it
|
224
|
-
})
|
225
|
-
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)
|
226
218
|
|
227
|
-
|
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)
|
228
225
|
|
229
226
|
return rules
|
230
227
|
|
@@ -235,6 +232,13 @@ class InterceptSettings:
|
|
235
232
|
rewrite_rule.parameter_rules or []
|
236
233
|
))
|
237
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
|
+
|
238
242
|
def __initialize_lifecycle_hooks(self):
|
239
243
|
script_path = self.lifecycle_hooks_path
|
240
244
|
|
@@ -8,7 +8,7 @@ from typing import Callable, List
|
|
8
8
|
from urllib.parse import urlparse
|
9
9
|
|
10
10
|
from stoobly_agent.app.settings.constants import request_component
|
11
|
-
from stoobly_agent.app.settings.rewrite_rule import RewriteRule,
|
11
|
+
from stoobly_agent.app.settings.rewrite_rule import ParameterRule, RewriteRule, UrlRule
|
12
12
|
from stoobly_agent.config.constants import custom_headers
|
13
13
|
from stoobly_agent.lib.logger import Logger, bcolors
|
14
14
|
from stoobly_agent.lib.utils import jmespath
|
@@ -29,8 +29,8 @@ class MitmproxyRequestFacade(Request):
|
|
29
29
|
self.request = request
|
30
30
|
self.uri = urlparse(self.request.url)
|
31
31
|
|
32
|
-
self.
|
33
|
-
self.
|
32
|
+
self.__url_rules: List[UrlRule] = []
|
33
|
+
self.__parameter_rules: List[ParameterRule] = []
|
34
34
|
|
35
35
|
self.__body = MitmproxyRequestBodyFacade(request)
|
36
36
|
|
@@ -99,41 +99,42 @@ class MitmproxyRequestFacade(Request):
|
|
99
99
|
return self.request.port
|
100
100
|
|
101
101
|
@property
|
102
|
-
def
|
103
|
-
return self.
|
102
|
+
def url_rules(self) -> List[ParameterRule]:
|
103
|
+
return self.__url_rules
|
104
104
|
|
105
105
|
@property
|
106
|
-
def
|
107
|
-
return self.
|
106
|
+
def parameter_rules(self) -> List[ParameterRule]:
|
107
|
+
return self.__parameter_rules
|
108
108
|
|
109
|
-
def
|
109
|
+
def with_parameter_rules(self, rules: List[RewriteRule]):
|
110
110
|
if type(rules) == list:
|
111
|
-
self.
|
111
|
+
self.__parameter_rules = self.select_parameter_rules(rules)
|
112
112
|
return self
|
113
113
|
|
114
|
-
def
|
114
|
+
def with_url_rules(self, rules: List[RewriteRule]):
|
115
115
|
if type(rules) == list:
|
116
|
-
self.
|
116
|
+
self.__url_rules = self.select_url_rules(rules)
|
117
117
|
return self
|
118
118
|
|
119
|
-
def redact(self):
|
120
|
-
redacts = self.__redact_rules
|
121
|
-
if len(redacts) != 0:
|
122
|
-
self.__redact_headers(redacts)
|
123
|
-
self.__redact_query(redacts)
|
124
|
-
self.__redact_content(redacts)
|
125
|
-
|
126
119
|
def rewrite(self):
|
127
|
-
rewrites = self.
|
120
|
+
rewrites = self.__parameter_rules
|
128
121
|
|
129
122
|
if len(rewrites) != 0:
|
130
123
|
self.__rewrite_headers(rewrites)
|
131
124
|
self.__rewrite_query(rewrites)
|
132
125
|
self.__rewrite_content(rewrites)
|
133
126
|
|
127
|
+
rewrites = self.__url_rules
|
128
|
+
|
129
|
+
if len(rewrites):
|
130
|
+
self.__rewrite_url(rewrites)
|
131
|
+
|
132
|
+
# Find all the rules that match request url and method
|
133
|
+
def select_rewrite_rules(self, rules: List[RewriteRule]) -> List[RewriteRule]:
|
134
|
+
return list(filter(self.__is_rewrite_rule_selected, rules or []))
|
135
|
+
|
134
136
|
def select_parameter_rules(self, rules: List[RewriteRule]) -> List[ParameterRule]:
|
135
|
-
|
136
|
-
_rules = list(filter(self.__is_parameter_rule_selected, rules or []))
|
137
|
+
_rules = self.select_rewrite_rules(rules)
|
137
138
|
|
138
139
|
if len(_rules) == 0:
|
139
140
|
return []
|
@@ -142,7 +143,17 @@ class MitmproxyRequestFacade(Request):
|
|
142
143
|
|
143
144
|
return [item for sublist in parameter_rules for item in sublist] # flatten list
|
144
145
|
|
145
|
-
def
|
146
|
+
def select_url_rules(self, rules: List[RewriteRule]) -> List[UrlRule]:
|
147
|
+
_rules = self.select_rewrite_rules(rules)
|
148
|
+
|
149
|
+
if len(_rules) == 0:
|
150
|
+
return []
|
151
|
+
|
152
|
+
url_rules = list(map(lambda rule: rule.url_rules, _rules))
|
153
|
+
|
154
|
+
return [item for sublist in url_rules for item in sublist] # flatten list
|
155
|
+
|
156
|
+
def __is_rewrite_rule_selected(self, rewrite_rule: RewriteRule):
|
146
157
|
pattern = rewrite_rule.pattern
|
147
158
|
|
148
159
|
try:
|
@@ -168,6 +179,14 @@ class MitmproxyRequestFacade(Request):
|
|
168
179
|
Logger.instance().info(f"{bcolors.OKCYAN}Rewriting {rewrite.type.lower()}{bcolors.ENDC} {rewrite.name} => {rewrite.value}")
|
169
180
|
return rewrite.value
|
170
181
|
|
182
|
+
def __rewrite_url(self, rewrites: List[UrlRule]):
|
183
|
+
for rewrite in rewrites:
|
184
|
+
if rewrite.host:
|
185
|
+
self.request.host = rewrite.host
|
186
|
+
|
187
|
+
if rewrite.port:
|
188
|
+
self.request.port = int(rewrite.port)
|
189
|
+
|
171
190
|
def __rewrite_headers(self, rewrites: List[ParameterRule]):
|
172
191
|
self.__apply_headers(rewrites, self.__rewrite_handler)
|
173
192
|
|
@@ -177,19 +196,6 @@ class MitmproxyRequestFacade(Request):
|
|
177
196
|
def __rewrite_content(self, rewrites: List[ParameterRule]):
|
178
197
|
self.__apply_content(rewrites, self.__rewrite_handler)
|
179
198
|
|
180
|
-
def __redact_handler(self, rewrite: ParameterRule) -> str:
|
181
|
-
Logger.instance().debug(f"{bcolors.OKCYAN}Redacting{bcolors.ENDC} {rewrite.name}")
|
182
|
-
return '[REDACTED]'
|
183
|
-
|
184
|
-
def __redact_headers(self, redacts: List[ParameterRule]):
|
185
|
-
self.__apply_headers(redacts, self.__redact_handler)
|
186
|
-
|
187
|
-
def __redact_query(self, redacts: List[ParameterRule]):
|
188
|
-
self.__apply_queries(redacts, self.__redact_handler)
|
189
|
-
|
190
|
-
def __redact_content(self, redacts: List[ParameterRule]):
|
191
|
-
self.__apply_content(redacts, self.__redact_handler)
|
192
|
-
|
193
199
|
def __apply_headers(self, rewrites: List[ParameterRule], handler: Callable):
|
194
200
|
rewrites = list(filter(lambda rewrite: rewrite.type == request_component.HEADER, rewrites))
|
195
201
|
self.__apply_rewrites(self.request.headers, rewrites, handler)
|
@@ -11,7 +11,6 @@ class MitmproxyResponseFacade(Response):
|
|
11
11
|
self.content = response.raw_content
|
12
12
|
|
13
13
|
self.rewrite_rules = []
|
14
|
-
self.rewrite_rules = []
|
15
14
|
|
16
15
|
@property
|
17
16
|
def code(self):
|
@@ -42,22 +41,12 @@ class MitmproxyResponseFacade(Response):
|
|
42
41
|
self.response.headers['content-length'] = str(len(self.content))
|
43
42
|
|
44
43
|
|
45
|
-
def with_redact_rules(self, rules: RewriteRule):
|
46
|
-
if type(rules) == list:
|
47
|
-
self.rewrite_rules = rules
|
48
|
-
|
49
|
-
return self
|
50
|
-
|
51
44
|
def with_rewrite_rules(self, rules: RewriteRule):
|
52
45
|
if type(rules) == list:
|
53
46
|
self.rewrite_rules = rules
|
54
47
|
|
55
48
|
return self
|
56
49
|
|
57
|
-
# TODO
|
58
|
-
def redact(self):
|
59
|
-
pass
|
60
|
-
|
61
50
|
# TODO
|
62
51
|
def rewrite(self):
|
63
52
|
pass
|
@@ -26,7 +26,7 @@ def join_rewritten_request(flow: MitmproxyHTTPFlow, intercept_settings: Intercep
|
|
26
26
|
response = MitmproxyResponseFacade(flow.response)
|
27
27
|
rewrite_rules = intercept_settings.record_rewrite_rules
|
28
28
|
|
29
|
-
request.
|
29
|
+
request.with_parameter_rules(rewrite_rules).with_url_rules(rewrite_rules).rewrite()
|
30
30
|
response.with_rewrite_rules(rewrite_rules).rewrite()
|
31
31
|
|
32
32
|
return join_request(request, response, intercept_settings)
|
@@ -2,6 +2,7 @@ import pdb
|
|
2
2
|
|
3
3
|
from .parameter_rule import ParameterRule
|
4
4
|
from .types.proxy_settings import RewriteRule as IRewriteRule
|
5
|
+
from .url_rule import UrlRule
|
5
6
|
|
6
7
|
class RewriteRule:
|
7
8
|
|
@@ -14,6 +15,9 @@ class RewriteRule:
|
|
14
15
|
self.__raw_parameter_rules = self.__rewrite_rule.get('parameter_rules') or []
|
15
16
|
self.__parameter_rules = list(map(lambda rule: ParameterRule(rule), self.__raw_parameter_rules))
|
16
17
|
|
18
|
+
self.__raw_url_rules = self.__rewrite_rule.get('url_rules') or []
|
19
|
+
self.__url_rules = list(map(lambda rule: UrlRule(rule), self.__raw_url_rules))
|
20
|
+
|
17
21
|
@property
|
18
22
|
def methods(self):
|
19
23
|
return self.__methods
|
@@ -30,9 +34,18 @@ class RewriteRule:
|
|
30
34
|
def parameter_rules(self, v):
|
31
35
|
self.__parameter_rules = v
|
32
36
|
|
37
|
+
@property
|
38
|
+
def url_rules(self):
|
39
|
+
return self.__url_rules
|
40
|
+
|
41
|
+
@url_rules.setter
|
42
|
+
def url_rules(self, v):
|
43
|
+
self.__url_rules = v
|
44
|
+
|
33
45
|
def to_dict(self):
|
34
46
|
return {
|
35
47
|
'methods': self.__methods,
|
36
48
|
'pattern': self.__pattern,
|
37
49
|
'parameter_rules': list(map(lambda parameter: parameter.to_dict(), self.__parameter_rules)),
|
50
|
+
'url_rules': list(map(lambda url: url.to_dict(), self.__url_rules)),
|
38
51
|
}
|
@@ -23,11 +23,6 @@ class IgnoreRule(TypedDict):
|
|
23
23
|
method: str
|
24
24
|
pattern: str
|
25
25
|
|
26
|
-
class RedactRule(TypedDict):
|
27
|
-
redacts: List[Redact]
|
28
|
-
method: str
|
29
|
-
pattern: str
|
30
|
-
|
31
26
|
class RewriteRule(TypedDict):
|
32
27
|
rewrites: List[Rewrite]
|
33
28
|
method: str
|
@@ -97,4 +92,4 @@ Component = {
|
|
97
92
|
'QueryParam': 'Query Param',
|
98
93
|
}
|
99
94
|
IProjectModeSettings = Union[IProjectMockSettings, IProjectRecordSettings, IProjectTestSettings]
|
100
|
-
Rule = Union[IgnoreRule,
|
95
|
+
Rule = Union[IgnoreRule, RewriteRule]
|
@@ -15,6 +15,11 @@ class ParameterRule(TypedDict):
|
|
15
15
|
type: str
|
16
16
|
value: str
|
17
17
|
|
18
|
+
class UrlRule(TypedDict):
|
19
|
+
host: str
|
20
|
+
modes: List[Mode]
|
21
|
+
port: str
|
22
|
+
|
18
23
|
class DataRules(TypedDict):
|
19
24
|
mock_policy: MockPolicy
|
20
25
|
record_policy: RecordPolicy
|
@@ -30,6 +35,7 @@ class RewriteRule(TypedDict):
|
|
30
35
|
methods: List[Method]
|
31
36
|
pattern: str
|
32
37
|
parameter_rules: List[ParameterRule]
|
38
|
+
url_rule: List[UrlRule]
|
33
39
|
|
34
40
|
class FirewallRule(TypedDict):
|
35
41
|
action: List[FirewallAction]
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from typing import List
|
2
|
+
|
3
|
+
from .types.proxy_settings import UrlRule as IUrlRule
|
4
|
+
|
5
|
+
class UrlRule:
|
6
|
+
|
7
|
+
def __init__(self, url_rule: IUrlRule):
|
8
|
+
self.update(url_rule)
|
9
|
+
|
10
|
+
@property
|
11
|
+
def host(self):
|
12
|
+
return self.__host
|
13
|
+
|
14
|
+
@property
|
15
|
+
def modes(self):
|
16
|
+
return self.__modes
|
17
|
+
|
18
|
+
@property
|
19
|
+
def port(self):
|
20
|
+
return self.__port
|
21
|
+
|
22
|
+
def update(self, url_rule: IUrlRule):
|
23
|
+
self.__url_rule = url_rule
|
24
|
+
self.__host = self.__url_rule.get('host')
|
25
|
+
self.__modes = self.__url_rule.get('modes')
|
26
|
+
self.__port = self.__url_rule.get('port')
|
27
|
+
|
28
|
+
def to_dict(self):
|
29
|
+
return {
|
30
|
+
'host': self.__host,
|
31
|
+
'modes': self.__modes,
|
32
|
+
'port': self.__port,
|
33
|
+
}
|