stoobly-agent 1.3.0__py3-none-any.whl → 1.4.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.
Files changed (51) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/application_http_request_handler.py +3 -3
  3. stoobly_agent/app/api/proxy_controller.py +8 -7
  4. stoobly_agent/app/cli/config_cli.py +1 -1
  5. stoobly_agent/app/cli/helpers/certificate_authority.py +6 -1
  6. stoobly_agent/app/cli/helpers/print_service.py +17 -0
  7. stoobly_agent/app/cli/scaffold/app.py +2 -2
  8. stoobly_agent/app/cli/scaffold/constants.py +0 -2
  9. stoobly_agent/app/cli/scaffold/hosts_file_manager.py +185 -0
  10. stoobly_agent/app/cli/scaffold/service.py +0 -1
  11. stoobly_agent/app/cli/scaffold/service_config.py +10 -14
  12. stoobly_agent/app/cli/scaffold/service_workflow_validate_command.py +3 -3
  13. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
  14. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +77 -53
  15. stoobly_agent/app/cli/scaffold/templates/app/stoobly-ui/exec/bin/.services +9 -0
  16. stoobly_agent/app/cli/scaffold/workflow_create_command.py +0 -1
  17. stoobly_agent/app/cli/scaffold/workflow_run_command.py +6 -9
  18. stoobly_agent/app/cli/scaffold_cli.py +212 -69
  19. stoobly_agent/app/cli/snapshot_cli.py +1 -1
  20. stoobly_agent/app/proxy/handle_mock_service.py +2 -0
  21. stoobly_agent/app/proxy/handle_replay_service.py +2 -0
  22. stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
  23. stoobly_agent/app/proxy/mitmproxy/response_body_facade.py +19 -0
  24. stoobly_agent/app/proxy/mitmproxy/response_facade.py +90 -18
  25. stoobly_agent/app/proxy/record/join_request_service.py +1 -1
  26. stoobly_agent/app/settings/constants/request_component.py +2 -1
  27. stoobly_agent/config/constants/custom_headers.py +13 -13
  28. stoobly_agent/config/constants/headers.py +0 -2
  29. stoobly_agent/public/18-es2015.583f191cc7ad512ee262.js +1 -0
  30. stoobly_agent/public/18-es5.583f191cc7ad512ee262.js +1 -0
  31. stoobly_agent/public/35-es2015.8f79ff8748d4ff06ab03.js +1 -0
  32. stoobly_agent/public/35-es5.8f79ff8748d4ff06ab03.js +1 -0
  33. stoobly_agent/public/index.html +1 -1
  34. stoobly_agent/public/main-es2015.2cc16523aa3fcaba51e5.js +1 -0
  35. stoobly_agent/public/main-es5.2cc16523aa3fcaba51e5.js +1 -0
  36. stoobly_agent/public/{runtime-es2015.9addf49b79aca951b7e2.js → runtime-es2015.b914470164e4d6e75d96.js} +1 -1
  37. stoobly_agent/public/{runtime-es5.9addf49b79aca951b7e2.js → runtime-es5.b914470164e4d6e75d96.js} +1 -1
  38. stoobly_agent/test/app/cli/scaffold/{hosts_file_reader_test.py → hosts_file_manager_test.py} +20 -20
  39. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  40. {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.1.dist-info}/METADATA +1 -1
  41. {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.1.dist-info}/RECORD +44 -42
  42. stoobly_agent/app/cli/scaffold/hosts_file_reader.py +0 -65
  43. stoobly_agent/public/18-es2015.d3b430636a4d6f544d92.js +0 -1
  44. stoobly_agent/public/18-es5.d3b430636a4d6f544d92.js +0 -1
  45. stoobly_agent/public/35-es2015.f741ebce0bfc25f0ec99.js +0 -1
  46. stoobly_agent/public/35-es5.f741ebce0bfc25f0ec99.js +0 -1
  47. stoobly_agent/public/main-es2015.ccd46ac1b6638ddf2066.js +0 -1
  48. stoobly_agent/public/main-es5.ccd46ac1b6638ddf2066.js +0 -1
  49. {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.1.dist-info}/LICENSE +0 -0
  50. {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.1.dist-info}/WHEEL +0 -0
  51. {stoobly_agent-1.3.0.dist-info → stoobly_agent-1.4.1.dist-info}/entry_points.txt +0 -0
@@ -1,33 +1,45 @@
1
- from mitmproxy.http import Response as MitmproxyResponse
1
+ import pdb
2
2
 
3
- from stoobly_agent.app.settings.rewrite_rule import RewriteRule
3
+ from mitmproxy.http import Headers, Response as MitmproxyResponse
4
+ from mitmproxy.coretypes import multidict
5
+ from typing import Callable, List
4
6
 
7
+ from stoobly_agent.app.settings.constants import request_component
8
+ from stoobly_agent.app.settings.rewrite_rule import ParameterRule, RewriteRule
9
+ from stoobly_agent.config.constants import custom_headers
10
+ from stoobly_agent.lib.logger import Logger, bcolors
11
+ from stoobly_agent.lib.utils import jmespath
12
+ from stoobly_agent.lib.utils.decode import decode
13
+
14
+ from .request_facade import MitmproxyRequestFacade
15
+ from .response_body_facade import MitmproxyResponseBodyFacade
5
16
  from .response import Response
6
17
 
18
+ LOG_ID = 'Response'
19
+
7
20
  class MitmproxyResponseFacade(Response):
8
21
 
9
22
  def __init__(self, response: MitmproxyResponse):
10
23
  self.response = response
11
- self.content = response.raw_content
12
24
 
13
- self.rewrite_rules = []
25
+ self.__body = MitmproxyResponseBodyFacade(response)
26
+ self.__parameter_rules: List[ParameterRule] = []
14
27
 
15
28
  @property
16
29
  def code(self):
17
30
  return self.response.status_code
18
31
 
32
+ @property
33
+ def content_type(self):
34
+ return self.headers.get('content-type')
35
+
19
36
  @property
20
37
  def headers(self):
21
- return self.response.headers
38
+ return self.__filter_custom_headers(self.response.headers)
22
39
 
23
40
  @property
24
41
  def body(self):
25
- content = self.content
26
-
27
- if not content:
28
- return b''
29
-
30
- return content
42
+ return self.response.raw_content or ''
31
43
 
32
44
  @property
33
45
  def http_verison(self):
@@ -40,13 +52,73 @@ class MitmproxyResponseFacade(Response):
40
52
  # Update Content-Lenght header to decoded content length
41
53
  self.response.headers['content-length'] = str(len(self.content))
42
54
 
43
-
44
- def with_rewrite_rules(self, rules: RewriteRule):
55
+ def with_parameter_rules(self, rules: List[RewriteRule], request_facade: MitmproxyRequestFacade):
45
56
  if type(rules) == list:
46
- self.rewrite_rules = rules
57
+ self.__parameter_rules = request_facade.select_parameter_rules(rules)
58
+ return self
47
59
 
48
- return self
49
-
50
- # TODO
51
60
  def rewrite(self):
52
- pass
61
+ rewrites = self.__parameter_rules
62
+
63
+ if len(rewrites) != 0:
64
+ self.__rewrite_headers(rewrites)
65
+ self.__rewrite_content(rewrites)
66
+
67
+ def __apply_rewrites(self, params: dict, rewrites: List[ParameterRule], handler: Callable):
68
+ if len(rewrites) == 0:
69
+ return
70
+
71
+ for rewrite in rewrites:
72
+ jmespath.search(rewrite.name, params, {
73
+ 'replacements': [handler(rewrite) if handler else rewrite.value],
74
+ })
75
+
76
+ def __rewrite_handler(self, rewrite: ParameterRule) -> str:
77
+ Logger.instance(LOG_ID).info(f"{bcolors.OKCYAN}Rewriting{bcolors.ENDC} {rewrite.type.lower()} {rewrite.name} => {rewrite.value}")
78
+ return rewrite.value
79
+
80
+ def __rewrite_headers(self, rewrites: List[ParameterRule]):
81
+ self.__apply_headers(rewrites, self.__rewrite_handler)
82
+
83
+ def __rewrite_content(self, rewrites: List[ParameterRule]):
84
+ self.__apply_content(rewrites, self.__rewrite_handler)
85
+
86
+ def __apply_headers(self, rewrites: List[ParameterRule], handler: Callable):
87
+ rewrites = list(filter(lambda rewrite: rewrite.type == request_component.RESPONSE_HEADER, rewrites))
88
+ self.__apply_rewrites(self.response.headers, rewrites, handler)
89
+
90
+ def __apply_content(self, rewrites: List[ParameterRule], handler: Callable):
91
+ rewrites = list(filter(lambda rewrite: rewrite.type == request_component.RESPONSE_PARAM, rewrites))
92
+ if len(rewrites) == 0:
93
+ return
94
+
95
+ content_type = self.content_type
96
+ parsed_content = self.__body.get(content_type)
97
+
98
+ if not isinstance(parsed_content, dict) and not isinstance(parsed_content, multidict.MultiDictView):
99
+ content_type = 'application/json'
100
+ self.response.headers['content-type'] = content_type
101
+ parsed_content = {}
102
+
103
+ self.__apply_rewrites(parsed_content, rewrites, handler)
104
+ self.__body.set(parsed_content, content_type)
105
+
106
+ def __filter_custom_headers(self, response_headers: Headers):
107
+ '''
108
+ Remove custom headers
109
+ '''
110
+ _response_headers = Headers(**response_headers)
111
+
112
+ headers = custom_headers.__dict__
113
+ for key in headers:
114
+ if key[0:2] == '__' and key[-2:] == '__':
115
+ continue
116
+
117
+ name = headers[key]
118
+
119
+ if name not in response_headers:
120
+ continue
121
+
122
+ _response_headers.pop(name)
123
+
124
+ return _response_headers
@@ -27,6 +27,6 @@ def join_rewritten_request(flow: MitmproxyHTTPFlow, intercept_settings: Intercep
27
27
  rewrite_rules = intercept_settings.record_rewrite_rules
28
28
 
29
29
  request.with_parameter_rules(rewrite_rules).with_url_rules(rewrite_rules).rewrite()
30
- response.with_rewrite_rules(rewrite_rules).rewrite()
30
+ response.with_parameter_rules(rewrite_rules, request).rewrite()
31
31
 
32
32
  return join_request(request, response, intercept_settings)
@@ -3,6 +3,7 @@ from typing import Literal
3
3
  HEADER = 'Header'
4
4
  BODY_PARAM = 'Body Param'
5
5
  QUERY_PARAM = 'Query Param'
6
+ RESPONSE_HEADER = 'Response Header'
6
7
  RESPONSE_PARAM = 'Response Param'
7
8
 
8
- RequestComponent = Literal[BODY_PARAM, HEADER, QUERY_PARAM, RESPONSE_PARAM]
9
+ RequestComponent = Literal[BODY_PARAM, HEADER, QUERY_PARAM, RESPONSE_HEADER, RESPONSE_PARAM]
@@ -2,28 +2,28 @@ ALIAS_RESOLVE_STRATEGY = 'X-Stoobly-Alias-Resolve-Strategy'
2
2
  CONTENT_TYPE = 'X-Stoobly-Content-Type'
3
3
  CONTENT_TYPE_TEST_RESULTS = 'test/results'
4
4
  REMOTE_PROJECT_KEY = 'X-Stoobly-Endpoints-Project-Id'
5
- MOCK_POLICY = 'X-Mock-Policy'
5
+ MOCK_POLICY = 'X-Stoobly-Mock-Policy'
6
6
  MOCK_REQUEST_ID = 'X-Stoobly-Request-Id'
7
7
  MOCK_REQUEST_ENDPOINT_ID = 'X-Stoobly-Request-Endpoint-Id'
8
8
  MOCK_REQUEST_KEY = 'X-Stoobly-Request-Key'
9
- DO_PROXY = 'X-Do-Proxy'
9
+ DO_PROXY = 'X-Stoobly-Do-Proxy'
10
10
  LIFECYCLE_HOOKS_PATH = 'X-Stoobly-Lifecycle-Hooks-Path'
11
- PROJECT_KEY = 'X-Project-Key'
12
- PROXY_MODE = 'X-Proxy-Mode'
11
+ PROJECT_KEY = 'X-Stoobly-Project-Key'
12
+ PROXY_MODE = 'X-Stoobly-Proxy-Mode'
13
13
  PUBLIC_DIRECTORY_PATH = 'X-Stoobly-Public-Directory-Path'
14
- RECORD_POLICY = 'X-Record-Policy'
15
- REPORT_KEY = 'X-Report-Key'
16
- REQUEST_ORIGIN = 'X-Proxy-Request-Origin'
14
+ RECORD_POLICY = 'X-Stoobly-Record-Policy'
15
+ REPORT_KEY = 'X-Stoobly-Report-Key'
16
+ REQUEST_ORIGIN = 'X-Stoobly-Request-Origin'
17
17
  RESPONSE_FIXTURES_PATH = 'X-Stoobly-Response-Fixtures-Path'
18
- RESPONSE_ID = 'X-Response-Id'
18
+ RESPONSE_ID = 'X-Stoobly-Response-Id'
19
19
  RESPONSE_LATENCY = 'X-Stoobly-Request-Response-Latency'
20
- RESPONSE_PROXY_MODE = 'X-Response-Proxy-Mode'
21
- SCENARIO_KEY = 'X-Scenario-Key'
22
- SERVICE_URL = 'X-Service-Url'
23
- TEST_FILTER = 'X-Test-Filter'
20
+ RESPONSE_PROXY_MODE = 'X-Stoobly-Response-Proxy-Mode'
21
+ SCENARIO_KEY = 'X-Stoobly-Scenario-Key'
22
+ SERVICE_URL = 'X-Stoobly-Service-Url'
23
+ TEST_FILTER = 'X-Stoobly-Test-Filter'
24
24
  TEST_ID = 'X-Stoobly-Test-Id'
25
25
  TEST_SAVE_RESULTS = 'X-Stoobly-Test-Save-Results'
26
26
  TEST_SKIP = 'X-Stoobly-Test-Skip'
27
- TEST_STRATEGY = 'X-Test-Strategy'
27
+ TEST_STRATEGY = 'X-Stoobly-Test-Strategy'
28
28
  TRACE_ID = 'X-Stoobly-Trace-Id'
29
29
  TRACE_REQUEST_ID = 'X-Stoobly-Trace-Request-Id'
@@ -1,9 +1,7 @@
1
1
  ACCESS_TOKEN = 'ACCESS-TOKEN'
2
2
  CLIENT = 'CLIENT'
3
- DO_PROXY = 'X-DO-PROXY'
4
3
  EXPIRY = 'EXPIRY'
5
4
  PROXY_HEADERS = 'X-PROXY-HEADERS'
6
5
  REQUEST_PATH = 'X-REQUEST-PATH'
7
- SERVICE_URL = 'X-SERVICE-URL'
8
6
  TOKEN_TYPE = 'TOKEN-TYPE'
9
7
  UID = 'UID'