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.
Files changed (50) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/requests_controller.py +1 -1
  3. stoobly_agent/app/cli/helpers/endpoints_import_service.py +70 -11
  4. stoobly_agent/app/cli/helpers/handle_config_update_service.py +5 -0
  5. stoobly_agent/app/cli/helpers/openapi_endpoint_adapter.py +108 -207
  6. stoobly_agent/app/cli/helpers/schema_builder.py +11 -88
  7. stoobly_agent/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request.py +6 -1
  8. stoobly_agent/app/models/factories/resource/local_db/request_adapter.py +1 -0
  9. stoobly_agent/app/proxy/handle_mock_service.py +6 -1
  10. stoobly_agent/app/proxy/replay/body_parser_service.py +1 -1
  11. stoobly_agent/app/proxy/run.py +0 -1
  12. stoobly_agent/cli.py +11 -2
  13. stoobly_agent/config/constants/custom_headers.py +1 -0
  14. stoobly_agent/lib/cache.py +26 -9
  15. stoobly_agent/public/{13-es2015.220b4a1adf4cacb294e5.js → 13-es2015.343b0261a8b3b3f4a1fc.js} +1 -1
  16. stoobly_agent/public/{13-es5.220b4a1adf4cacb294e5.js → 13-es5.343b0261a8b3b3f4a1fc.js} +1 -1
  17. stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +1 -0
  18. stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +1 -0
  19. stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +1 -0
  20. stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +1 -0
  21. stoobly_agent/public/7-es2015.19ccb84e62e2ea874f53.js +1 -0
  22. stoobly_agent/public/7-es5.19ccb84e62e2ea874f53.js +1 -0
  23. stoobly_agent/public/9-es2015.b7bcad8238f58e214f03.js +1 -0
  24. stoobly_agent/public/9-es5.b7bcad8238f58e214f03.js +1 -0
  25. stoobly_agent/public/index.html +1 -1
  26. stoobly_agent/public/runtime-es2015.9addf49b79aca951b7e2.js +1 -0
  27. stoobly_agent/public/runtime-es5.9addf49b79aca951b7e2.js +1 -0
  28. stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_info_test.py +25 -15
  29. stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_oauth2_scopes_test.py +26 -16
  30. stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_missing_servers_test.py +25 -15
  31. stoobly_agent/test/app/cli/helpers/openapi_endpoint_adapter_test.py +79 -9
  32. stoobly_agent/test/app/cli/helpers/schema_builder_test.py +22 -7
  33. stoobly_agent/test/app/models/factories/resource/local_db/helpers/tiebreak_scenario_request_test.py +20 -2
  34. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  35. stoobly_agent/test/app/proxy/replay/body_parser_service_test.py +3 -3
  36. {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/METADATA +1 -1
  37. {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/RECORD +40 -40
  38. stoobly_agent/public/18-es2015.10cdd5c608b10d90d19a.js +0 -1
  39. stoobly_agent/public/18-es5.10cdd5c608b10d90d19a.js +0 -1
  40. stoobly_agent/public/35-es2015.61a7ae8da93df94fab06.js +0 -1
  41. stoobly_agent/public/35-es5.61a7ae8da93df94fab06.js +0 -1
  42. stoobly_agent/public/7-es2015.c359dbb640e2af507221.js +0 -1
  43. stoobly_agent/public/7-es5.c359dbb640e2af507221.js +0 -1
  44. stoobly_agent/public/9-es2015.cfc1101139d6ae75731b.js +0 -1
  45. stoobly_agent/public/9-es5.cfc1101139d6ae75731b.js +0 -1
  46. stoobly_agent/public/runtime-es2015.08e65883d390cd16c15b.js +0 -1
  47. stoobly_agent/public/runtime-es5.08e65883d390cd16c15b.js +0 -1
  48. {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/LICENSE +0 -0
  49. {stoobly_agent-0.34.8.dist-info → stoobly_agent-0.34.10.dist-info}/WHEEL +0 -0
  50. {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
- self.param_names_created = {}
20
- self.__traverse('', params, None)
21
- return self.param_names_created.values()
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
- 'inferred_type': convert(self.__infer_type(v['value'])),
68
- 'is_required': v.get('required') is True,
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
- 'name': k,
72
- 'query': f"{parent_param.get('query')}.{k}" if parent_param else k,
28
+ 'id': literal_param['id'],
29
+ f"{self.param_column_name}_id": literal_param['parent_id']
73
30
  }
74
- columns[self.param_column_name + '_id'] = parent_param['id'] if parent_param else None
75
- param = self.__find_or_create_by(columns)
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}.last_request_id"
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]), decode(ele[1])))
79
+ params_array.append((decode(ele[0]), ele[1]))
80
80
 
81
81
  return MultiDict(params_array)
82
82
 
@@ -58,7 +58,6 @@ def __get_intercept_handler_path():
58
58
  def __with_static_options(config: MitmproxyConfig, cli_options):
59
59
  options = (
60
60
  'block_global=false',
61
- 'flow_detail=1',
62
61
  f"scripts={__get_intercept_handler_path()}",
63
62
  'upstream_cert=false'
64
63
  )
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'
@@ -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
- self.data = {}
28
- self.dataVersions = {}
29
- self.version += 1
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
- del self.data[key]
77
- del self.dataVersions[key]
78
- del self.timeouts[key]
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