stoobly-agent 0.34.8__py3-none-any.whl → 0.34.10__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/requests_controller.py +1 -1
- stoobly_agent/app/cli/helpers/endpoints_import_service.py +70 -11
- stoobly_agent/app/cli/helpers/handle_config_update_service.py +5 -0
- stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +108 -207
- stoobly_agent/app/cli/helpers/schema_builder.py +11 -88
- stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py +6 -1
- stoobly_agent/app/models/factories/resource/local_db/request_adapter.py +1 -0
- stoobly_agent/app/proxy/handle_mock_service.py +6 -1
- stoobly_agent/app/proxy/replay/body_parser_service.py +1 -1
- stoobly_agent/app/proxy/run.py +0 -1
- stoobly_agent/cli.py +11 -2
- stoobly_agent/config/constants/custom_headers.py +1 -0
- stoobly_agent/lib/cache.py +26 -9
- stoobly_agent/public/{13-es2015.220b4a1adf4cacb294e5.js → 13-es2015.343b0261a8b3b3f4a1fc.js} +1 -1
- stoobly_agent/public/{13-es5.220b4a1adf4cacb294e5.js → 13-es5.343b0261a8b3b3f4a1fc.js} +1 -1
- stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +1 -0
- stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +1 -0
- stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +1 -0
- stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +1 -0
- stoobly_agent/public/7-es2015.19ccb84e62e2ea874f53.js +1 -0
- stoobly_agent/public/7-es5.19ccb84e62e2ea874f53.js +1 -0
- stoobly_agent/public/9-es2015.b7bcad8238f58e214f03.js +1 -0
- stoobly_agent/public/9-es5.b7bcad8238f58e214f03.js +1 -0
- stoobly_agent/public/index.html +1 -1
- stoobly_agent/public/runtime-es2015.9addf49b79aca951b7e2.js +1 -0
- stoobly_agent/public/runtime-es5.9addf49b79aca951b7e2.js +1 -0
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_info_test.py +25 -15
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_oauth2_scopes_test.py +26 -16
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_servers_test.py +25 -15
- stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_test.py +79 -9
- stoobly_agent/test/app/cli/helpers/schema_builder_test.py +22 -7
- stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py +20 -2
- stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
- stoobly_agent/test/app/proxy/replay/body_parser_service_test.py +3 -3
- {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/METADATA +1 -1
- {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/RECORD +40 -40
- stoobly_agent/public/18-es2015.10cdd5c608b10d90d19a.js +0 -1
- stoobly_agent/public/18-es5.10cdd5c608b10d90d19a.js +0 -1
- stoobly_agent/public/35-es2015.61a7ae8da93df94fab06.js +0 -1
- stoobly_agent/public/35-es5.61a7ae8da93df94fab06.js +0 -1
- stoobly_agent/public/7-es2015.c359dbb640e2af507221.js +0 -1
- stoobly_agent/public/7-es5.c359dbb640e2af507221.js +0 -1
- stoobly_agent/public/9-es2015.cfc1101139d6ae75731b.js +0 -1
- stoobly_agent/public/9-es5.cfc1101139d6ae75731b.js +0 -1
- stoobly_agent/public/runtime-es2015.08e65883d390cd16c15b.js +0 -1
- stoobly_agent/public/runtime-es5.08e65883d390cd16c15b.js +0 -1
- {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/LICENSE +0 -0
- {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/WHEEL +0 -0
- {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/entry_points.txt +0 -0
@@ -16,97 +16,20 @@ class SchemaBuilder:
|
|
16
16
|
self.param_column_name: str = param_column_name
|
17
17
|
|
18
18
|
def build(self, params):
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
###
|
24
|
-
#
|
25
|
-
# @param name [String] name of current param
|
26
|
-
# @param value [Object] value of current param
|
27
|
-
# @param param [QueryParamName, BodyParamName, ResponseParamName] parent param record
|
28
|
-
#
|
29
|
-
def __traverse(self, name: str, value, param: RequestComponentName):
|
30
|
-
if type(value) is list:
|
31
|
-
self.__traverse_array(name, value, param)
|
32
|
-
elif type(value) is dict:
|
33
|
-
self.__traverse_hash(name, value, param)
|
34
|
-
|
35
|
-
def __traverse_array(self, name: str, value, parent_param: RequestComponentName):
|
36
|
-
columns = {
|
37
|
-
'endpoint_id': self.endpoint_id,
|
38
|
-
'name': f"{name.capitalize()}Element",
|
39
|
-
'query': f"{parent_param.get('query')}[*]" if parent_param else '[*]'
|
40
|
-
}
|
41
|
-
columns[self.param_column_name + '_id'] = parent_param['id'] if parent_param else None
|
42
|
-
|
43
|
-
# Iterate
|
44
|
-
types = {}
|
45
|
-
|
46
|
-
for e in value:
|
47
|
-
# Example of e = {'id': {'value': 0, 'required': False}}
|
48
|
-
type_value = None
|
49
|
-
if e.get('value') is not None:
|
50
|
-
type_value = e.get('value')
|
51
|
-
else:
|
52
|
-
type_value = e
|
53
|
-
|
54
|
-
_type = self.__infer_type(type_value)
|
55
|
-
|
56
|
-
if types.get(_type) is None:
|
57
|
-
columns['inferred_type'] = convert(_type)
|
58
|
-
types[_type] = self.__find_or_create_by(columns)
|
59
|
-
|
60
|
-
self.__traverse('', type_value, types[_type])
|
61
|
-
|
62
|
-
def __traverse_hash(self, name, value, parent_param: RequestComponentName):
|
63
|
-
# Iterate
|
64
|
-
for k, v in value.items():
|
65
|
-
columns = {
|
19
|
+
params_list = []
|
20
|
+
for literal_param in params:
|
21
|
+
param: RequestComponentName = {
|
66
22
|
'endpoint_id': self.endpoint_id,
|
67
|
-
'
|
68
|
-
'
|
69
|
-
|
23
|
+
'name': literal_param['name'],
|
24
|
+
'query': literal_param['query'],
|
25
|
+
'is_required': literal_param['required'],
|
26
|
+
'inferred_type': convert(self.__infer_type(literal_param['value'])),
|
70
27
|
'is_deterministic': True,
|
71
|
-
'
|
72
|
-
|
28
|
+
'id': literal_param['id'],
|
29
|
+
f"{self.param_column_name}_id": literal_param['parent_id']
|
73
30
|
}
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
self.__traverse(k, v['value'], param)
|
78
|
-
|
79
|
-
def __find_or_create_by(self, columns):
|
80
|
-
param = self.__find_by(columns)
|
81
|
-
|
82
|
-
if param is None:
|
83
|
-
param = self.__create(columns)
|
84
|
-
|
85
|
-
return param
|
86
|
-
|
87
|
-
def __find_by(self, columns) -> Union[RequestComponentName, None]:
|
88
|
-
for id, param_name in self.param_names_created.items():
|
89
|
-
matches = True
|
90
|
-
|
91
|
-
for key in columns:
|
92
|
-
if key not in param_name:
|
93
|
-
matches = False
|
94
|
-
break
|
95
|
-
|
96
|
-
if param_name[key] != columns[key]:
|
97
|
-
matches = False
|
98
|
-
break
|
99
|
-
|
100
|
-
if matches:
|
101
|
-
return param_name
|
102
|
-
|
103
|
-
def __create(self, columns):
|
104
|
-
param: RequestComponentName = columns.copy()
|
105
|
-
param['id'] = len(self.param_names_created.keys()) + 1
|
106
|
-
|
107
|
-
self.param_names_created[param['id']] = param
|
108
|
-
|
109
|
-
return param
|
31
|
+
params_list.append(param)
|
32
|
+
return params_list
|
110
33
|
|
111
34
|
def __infer_type(self, val) -> str:
|
112
35
|
return str(val.__class__)
|
@@ -5,6 +5,8 @@ from typing import List
|
|
5
5
|
from stoobly_agent.lib.cache import Cache
|
6
6
|
from stoobly_agent.lib.orm.request import Request
|
7
7
|
|
8
|
+
PREFIX = 'last_request_id'
|
9
|
+
|
8
10
|
def access_request(session_id: str, request_id: int, timeout = None):
|
9
11
|
cache = Cache.instance()
|
10
12
|
_last_request_id_key = __last_request_id_key(session_id)
|
@@ -18,6 +20,9 @@ def generate_session_id(query: dict):
|
|
18
20
|
|
19
21
|
return hashlib.md5(b'.'.join(toks)).hexdigest()
|
20
22
|
|
23
|
+
def reset():
|
24
|
+
Cache.instance().clear(f".+\.{PREFIX}")
|
25
|
+
|
21
26
|
def tiebreak_scenario_request(session_id: str, requests: List[Request]):
|
22
27
|
if len(requests) == 0:
|
23
28
|
return None
|
@@ -43,4 +48,4 @@ def tiebreak_scenario_request(session_id: str, requests: List[Request]):
|
|
43
48
|
|
44
49
|
def __last_request_id_key(_key = None):
|
45
50
|
_key = _key or generate_session_id()
|
46
|
-
return f"{_key}.
|
51
|
+
return f"{_key}.{PREFIX}"
|
@@ -112,6 +112,7 @@ class LocalDBRequestAdapter(LocalDBAdapter):
|
|
112
112
|
|
113
113
|
headers = {}
|
114
114
|
headers[custom_headers.MOCK_REQUEST_ID] = str(request.id)
|
115
|
+
headers[custom_headers.MOCK_REQUEST_KEY] = request.key()
|
115
116
|
headers[custom_headers.RESPONSE_LATENCY] = str(request.latency)
|
116
117
|
|
117
118
|
return (
|
@@ -10,7 +10,7 @@ from stoobly_agent.app.models.request_model import RequestModel
|
|
10
10
|
from stoobly_agent.app.proxy.mitmproxy.request_facade import MitmproxyRequestFacade
|
11
11
|
from stoobly_agent.app.proxy.utils.rewrite_rules_to_ignored_components_service import rewrite_rules_to_ignored_components
|
12
12
|
from stoobly_agent.config.constants import custom_headers, env_vars, lifecycle_hooks, mock_policy, request_origin
|
13
|
-
from stoobly_agent.lib.logger import Logger
|
13
|
+
from stoobly_agent.lib.logger import bcolors, Logger
|
14
14
|
|
15
15
|
from .constants import custom_response_codes
|
16
16
|
from .mock.context import MockContext
|
@@ -131,6 +131,11 @@ def __handle_mock_success(context: MockContext) -> None:
|
|
131
131
|
if os.environ.get(env_vars.AGENT_SIMULATE_LATENCY):
|
132
132
|
__simulate_latency(response.headers.get(custom_headers.RESPONSE_LATENCY), start_time)
|
133
133
|
|
134
|
+
request_key = response.headers.get(custom_headers.MOCK_REQUEST_KEY)
|
135
|
+
if request_key:
|
136
|
+
request = context.flow.request
|
137
|
+
Logger.instance().info(f"{bcolors.OKCYAN}Mocked{bcolors.ENDC} {request.url} -> {request_key}")
|
138
|
+
|
134
139
|
def __handle_mock_failure(context: MockContext) -> None:
|
135
140
|
req = context.flow.request
|
136
141
|
intercept_settings = context.intercept_settings
|
@@ -76,7 +76,7 @@ def parse_multipart_form_data(content, content_type) -> Dict[bytes, bytes]:
|
|
76
76
|
|
77
77
|
params_array = []
|
78
78
|
for ele in decoded_multipart:
|
79
|
-
params_array.append((decode(ele[0]),
|
79
|
+
params_array.append((decode(ele[0]), ele[1]))
|
80
80
|
|
81
81
|
return MultiDict(params_array)
|
82
82
|
|
stoobly_agent/app/proxy/run.py
CHANGED
stoobly_agent/cli.py
CHANGED
@@ -86,7 +86,16 @@ def init(**kwargs):
|
|
86
86
|
Passphrase for decrypting the private key provided in the --cert option. Note that passing cert_passphrase on the command line makes your passphrase visible in your system's process list. Specify it in
|
87
87
|
config.yaml to avoid this.
|
88
88
|
''')
|
89
|
+
@click.option('--confdir', default=os.path.join(os.path.expanduser('~'), '.mitmproxy'), help='Location of the default mitmproxy configuration files.')
|
89
90
|
@click.option('--connection-strategy', help=', '.join(CONNECTION_STRATEGIES), type=click.Choice(CONNECTION_STRATEGIES))
|
91
|
+
@click.option('--flow-detail', default='1', type=click.Choice(['1', '2', '3', '4']), help='''
|
92
|
+
The display detail level for flows in mitmdump: 0 (quiet) to 4 (very verbose).
|
93
|
+
0: no output
|
94
|
+
1: shortened request URL with response status code
|
95
|
+
2: full request URL with response status code and HTTP headers
|
96
|
+
3: 2 + truncated response content, content of WebSocket and TCP messages (content_view_lines_cutoff: 512)
|
97
|
+
4: 3 + nothing is truncated
|
98
|
+
''')
|
90
99
|
@click.option('--headless', is_flag=True, default=False, help='Disable starting UI.')
|
91
100
|
@click.option('--intercept', is_flag=True, default=False, help='Enable intercept on run.')
|
92
101
|
@click.option('--log-level', default=logger.INFO, type=click.Choice([logger.DEBUG, logger.INFO, logger.WARNING, logger.ERROR]), help='''
|
@@ -218,7 +227,7 @@ def __build_request_from_curl(**kwargs):
|
|
218
227
|
|
219
228
|
if len(toks) != 2:
|
220
229
|
continue
|
221
|
-
|
230
|
+
|
222
231
|
headers[toks[0].strip()] = toks[1].strip()
|
223
232
|
|
224
233
|
return requests.Request(
|
@@ -226,4 +235,4 @@ def __build_request_from_curl(**kwargs):
|
|
226
235
|
headers=headers,
|
227
236
|
method=kwargs['request'],
|
228
237
|
url=kwargs['url']
|
229
|
-
)
|
238
|
+
)
|
@@ -5,6 +5,7 @@ REMOTE_PROJECT_KEY = 'X-Stoobly-Endpoints-Project-Id'
|
|
5
5
|
MOCK_POLICY = 'X-Mock-Policy'
|
6
6
|
MOCK_REQUEST_ID = 'X-Stoobly-Request-Id'
|
7
7
|
MOCK_REQUEST_ENDPOINT_ID = 'X-Stoobly-Request-Endpoint-Id'
|
8
|
+
MOCK_REQUEST_KEY = 'X-Stoobly-Request-Key'
|
8
9
|
DO_PROXY = 'X-Do-Proxy'
|
9
10
|
LIFECYCLE_HOOKS_PATH = 'X-Stoobly-Lifecycle-Hooks-Path'
|
10
11
|
PROJECT_KEY = 'X-Project-Key'
|
stoobly_agent/lib/cache.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import pdb
|
2
2
|
import random
|
3
|
+
import re
|
3
4
|
|
4
5
|
from time import time
|
5
6
|
|
@@ -23,10 +24,19 @@ class Cache:
|
|
23
24
|
|
24
25
|
return cls._instance
|
25
26
|
|
26
|
-
def clear(self):
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
def clear(self, pattern: str = None):
|
28
|
+
if not pattern:
|
29
|
+
self.data = {}
|
30
|
+
self.dataVersions = {}
|
31
|
+
self.version += 1
|
32
|
+
else:
|
33
|
+
delete_list = []
|
34
|
+
for key in self.data.keys():
|
35
|
+
if re.match(pattern, key):
|
36
|
+
delete_list.append(key)
|
37
|
+
|
38
|
+
for key in delete_list:
|
39
|
+
self.delete(key)
|
30
40
|
|
31
41
|
def read(self, key):
|
32
42
|
if key in self.timeouts:
|
@@ -48,9 +58,10 @@ class Cache:
|
|
48
58
|
|
49
59
|
def read_all(self):
|
50
60
|
data = []
|
51
|
-
|
52
|
-
for key in self.data.keys():
|
61
|
+
|
62
|
+
for key in list(self.data.keys()):
|
53
63
|
datum = self.read(key)
|
64
|
+
|
54
65
|
if datum:
|
55
66
|
data.append(datum)
|
56
67
|
|
@@ -73,7 +84,13 @@ class Cache:
|
|
73
84
|
self.version += 1
|
74
85
|
|
75
86
|
def delete(self, key):
|
76
|
-
|
77
|
-
|
78
|
-
|
87
|
+
if key in self.data:
|
88
|
+
del self.data[key]
|
89
|
+
|
90
|
+
if key in self.dataVersions:
|
91
|
+
del self.dataVersions[key]
|
92
|
+
|
93
|
+
if key in self.timeouts:
|
94
|
+
del self.timeouts[key]
|
95
|
+
|
79
96
|
self.version += 1
|