stoobly-agent 0.21.2__py3-none-any.whl → 0.22.3__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 (216) hide show
  1. stoobly_agent/__init__.py +2 -1
  2. stoobly_agent/app/api/bodies_controller.py +2 -2
  3. stoobly_agent/app/api/requests_controller.py +28 -10
  4. stoobly_agent/app/api/simple_http_request_handler.py +1 -1
  5. stoobly_agent/app/cli/ca_cert_installer.py +16 -5
  6. stoobly_agent/app/cli/config_cli.py +32 -20
  7. stoobly_agent/app/cli/helpers/context.py +7 -0
  8. stoobly_agent/app/cli/helpers/handle_mock_service.py +14 -0
  9. stoobly_agent/app/cli/helpers/print_service.py +1 -1
  10. stoobly_agent/app/cli/intercept.py +46 -2
  11. stoobly_agent/app/cli/main_group.py +3 -3
  12. stoobly_agent/app/cli/project_cli.py +3 -1
  13. stoobly_agent/app/cli/request_cli.py +6 -10
  14. stoobly_agent/app/models/adapters/mitmproxy/__init__.py +2 -0
  15. stoobly_agent/app/models/adapters/mitmproxy/request/__init__.py +12 -0
  16. stoobly_agent/app/models/adapters/mitmproxy/request/python_adapter.py +16 -0
  17. stoobly_agent/app/models/adapters/mitmproxy/response/__init__.py +12 -0
  18. stoobly_agent/app/models/adapters/mitmproxy/response/python_adapter.py +24 -0
  19. stoobly_agent/app/models/adapters/python/__init__.py +2 -0
  20. stoobly_agent/app/models/adapters/python/request/__init__.py +18 -0
  21. stoobly_agent/app/models/adapters/python/request/mitmproxy_adapter.py +53 -0
  22. stoobly_agent/app/models/adapters/python/request/stoobly_adapter.py +49 -0
  23. stoobly_agent/app/models/adapters/python/response/__init__.py +13 -0
  24. stoobly_agent/app/models/adapters/{mitmproxy_response_adapter.py → python/response/mitmproxy_adapter.py} +15 -7
  25. stoobly_agent/app/models/adapters/raw_http_response_adapter.py +10 -3
  26. stoobly_agent/app/models/adapters/stoobly/request/__init__.py +11 -0
  27. stoobly_agent/app/models/adapters/stoobly/request/mitmproxy_adapter.py +32 -0
  28. stoobly_agent/app/models/body_model.py +2 -2
  29. stoobly_agent/app/models/factories/__init__.py +0 -0
  30. stoobly_agent/app/models/factories/resource/__init__.py +0 -0
  31. stoobly_agent/app/models/{adapters/body_adapter_factory.py → factories/resource/body.py} +1 -1
  32. stoobly_agent/app/models/{adapters/header_adapter_factory.py → factories/resource/header.py} +1 -1
  33. stoobly_agent/app/models/factories/resource/local_db/__init__.py +0 -0
  34. stoobly_agent/app/models/{adapters → factories/resource}/local_db/replayed_response_adapter.py +1 -2
  35. stoobly_agent/app/models/{adapters → factories/resource}/local_db/request_adapter.py +15 -8
  36. stoobly_agent/app/models/{adapters → factories/resource}/local_db/scenario_adapter.py +13 -7
  37. stoobly_agent/app/models/{adapters/query_param_adapter_factory.py → factories/resource/query_param.py} +1 -1
  38. stoobly_agent/app/models/{adapters/replayed_response_adapter_factory.py → factories/resource/replayed_response.py} +1 -1
  39. stoobly_agent/app/models/{adapters/request_adapter_factory.py → factories/resource/request.py} +2 -2
  40. stoobly_agent/app/models/{adapters/response_adapter_factory.py → factories/resource/response.py} +1 -1
  41. stoobly_agent/app/models/{adapters/response_header_adapter_factory.py → factories/resource/response_header.py} +1 -1
  42. stoobly_agent/app/models/{adapters/scenario_adapter_factory.py → factories/resource/scenario.py} +2 -2
  43. stoobly_agent/app/models/factories/resource/stoobly/__init__.py +0 -0
  44. stoobly_agent/app/models/{adapters/stoobly_request_adapter.py → factories/resource/stoobly/request_adapter.py} +1 -2
  45. stoobly_agent/app/models/{adapters/stoobly_scenario_adapter.py → factories/resource/stoobly/scenario_adapter.py} +1 -2
  46. stoobly_agent/app/models/header_model.py +2 -2
  47. stoobly_agent/app/models/query_param_model.py +2 -2
  48. stoobly_agent/app/models/replayed_response_model.py +2 -2
  49. stoobly_agent/app/models/request_model.py +4 -5
  50. stoobly_agent/app/models/response_header_model.py +2 -2
  51. stoobly_agent/app/models/response_model.py +2 -2
  52. stoobly_agent/app/models/scenario_model.py +4 -4
  53. stoobly_agent/app/models/schemas/request.py +8 -1
  54. stoobly_agent/app/models/types/__init__.py +6 -0
  55. stoobly_agent/app/proxy/__init__.py +0 -5
  56. stoobly_agent/app/proxy/handle_mock_service.py +0 -3
  57. stoobly_agent/app/proxy/handle_test_service.py +1 -1
  58. stoobly_agent/app/proxy/intercept_handler.py +0 -1
  59. stoobly_agent/app/proxy/mitmproxy/flow_mock.py +35 -0
  60. stoobly_agent/app/proxy/mitmproxy/request_facade.py +11 -2
  61. stoobly_agent/app/proxy/replay/replay_request_service.py +57 -40
  62. stoobly_agent/app/proxy/simulate_intercept_service.py +30 -0
  63. stoobly_agent/app/proxy/test/__init__.py +1 -0
  64. stoobly_agent/app/proxy/test/context.py +2 -1
  65. stoobly_agent/app/proxy/test/context_abc.py +155 -0
  66. stoobly_agent/app/proxy/test/matchers/context.py +1 -1
  67. stoobly_agent/app/proxy/test/matchers/contract.py +1 -1
  68. stoobly_agent/app/proxy/test/matchers/custom.py +1 -1
  69. stoobly_agent/app/proxy/test/matchers/diff.py +1 -1
  70. stoobly_agent/app/proxy/test/matchers/fuzzy.py +1 -1
  71. stoobly_agent/app/proxy/test/test_service.py +1 -1
  72. stoobly_agent/app/proxy/upload/joined_request.py +0 -1
  73. stoobly_agent/app/proxy/upload/proxy_request.py +4 -1
  74. stoobly_agent/app/proxy/upload/request_string.py +1 -1
  75. stoobly_agent/app/settings/constants/mode.py +3 -0
  76. stoobly_agent/cli.py +76 -7
  77. stoobly_agent/config/constants/env_vars.py +2 -1
  78. stoobly_agent/config/data_dir.py +11 -8
  79. stoobly_agent/lib/api/api.py +4 -4
  80. stoobly_agent/lib/api/keys/organization_key.py +2 -0
  81. stoobly_agent/lib/api/keys/project_key.py +9 -0
  82. stoobly_agent/lib/api/scenarios_resource.py +1 -1
  83. stoobly_agent/lib/api/stoobly_api.py +9 -5
  84. stoobly_agent/lib/orm/__init__.py +1 -1
  85. stoobly_agent/lib/orm/replayed_response.py +3 -3
  86. stoobly_agent/lib/orm/request.py +25 -5
  87. stoobly_agent/lib/orm/transformers/orm_to_stoobly_request_transformer.py +1 -1
  88. stoobly_agent/mock.py +94 -0
  89. stoobly_agent/public/{2-es2015.3d54569af612a07a2e06.js → 1-es2015.37917aa26708d8f35d36.js} +1 -1
  90. stoobly_agent/public/{2-es5.3d54569af612a07a2e06.js → 1-es5.37917aa26708d8f35d36.js} +1 -1
  91. stoobly_agent/public/10-es2015.e9556b0d0f0e92fb548b.js +1 -0
  92. stoobly_agent/public/10-es5.e9556b0d0f0e92fb548b.js +1 -0
  93. stoobly_agent/public/11-es2015.bc6212fccbe72a623f81.js +1 -0
  94. stoobly_agent/public/11-es5.bc6212fccbe72a623f81.js +1 -0
  95. stoobly_agent/public/{13-es2015.76b6c147b0c46f995cd7.js → 12-es2015.d0768894ddffd6efa5e5.js} +1 -1
  96. stoobly_agent/public/{13-es5.76b6c147b0c46f995cd7.js → 12-es5.d0768894ddffd6efa5e5.js} +1 -1
  97. stoobly_agent/public/13-es2015.8a044490a76fd298162d.js +1 -0
  98. stoobly_agent/public/13-es5.8a044490a76fd298162d.js +1 -0
  99. stoobly_agent/public/{15-es2015.60c3b41c385f5bdedb7b.js → 14-es2015.1cd1a021e51ca0e62e1c.js} +1 -1
  100. stoobly_agent/public/{15-es5.60c3b41c385f5bdedb7b.js → 14-es5.1cd1a021e51ca0e62e1c.js} +1 -1
  101. stoobly_agent/public/{16-es2015.5d395009a77978db4405.js → 15-es2015.587781d19864ff0eb4f5.js} +1 -1
  102. stoobly_agent/public/{16-es5.5d395009a77978db4405.js → 15-es5.587781d19864ff0eb4f5.js} +1 -1
  103. stoobly_agent/public/16-es2015.ec6a175b1f9578203cd8.js +1 -0
  104. stoobly_agent/public/16-es5.ec6a175b1f9578203cd8.js +1 -0
  105. stoobly_agent/public/17-es2015.ad9c4756c96a15bd29d7.js +1 -0
  106. stoobly_agent/public/17-es5.ad9c4756c96a15bd29d7.js +1 -0
  107. stoobly_agent/public/{19-es2015.517f68e08f4c582dae66.js → 18-es2015.8583df0f8eccb3e47c0b.js} +1 -1
  108. stoobly_agent/public/{19-es5.517f68e08f4c582dae66.js → 18-es5.8583df0f8eccb3e47c0b.js} +1 -1
  109. stoobly_agent/public/{20-es2015.473486aabfa4d4a6431b.js → 19-es2015.d0225852a844dc03a09f.js} +1 -1
  110. stoobly_agent/public/{20-es5.473486aabfa4d4a6431b.js → 19-es5.d0225852a844dc03a09f.js} +1 -1
  111. stoobly_agent/public/{3-es5.1dad290844ea619e4c16.js → 2-es2015.8f184ac63348ba447b1f.js} +1 -1
  112. stoobly_agent/public/{3-es2015.1dad290844ea619e4c16.js → 2-es5.8f184ac63348ba447b1f.js} +1 -1
  113. stoobly_agent/public/{21-es2015.56aa10803cc1348a55a3.js → 20-es2015.f7c107847935264d58aa.js} +1 -1
  114. stoobly_agent/public/{21-es5.56aa10803cc1348a55a3.js → 20-es5.f7c107847935264d58aa.js} +1 -1
  115. stoobly_agent/public/{22-es2015.46d81010003b2a50eeab.js → 21-es2015.dd358e1edaf3d32dd2c0.js} +1 -1
  116. stoobly_agent/public/{22-es5.46d81010003b2a50eeab.js → 21-es5.dd358e1edaf3d32dd2c0.js} +1 -1
  117. stoobly_agent/public/26-es2015.6332c32f1b7c8c288f2f.js +1 -0
  118. stoobly_agent/public/26-es5.6332c32f1b7c8c288f2f.js +1 -0
  119. stoobly_agent/public/27-es2015.af505e744b0c869a93d1.js +1 -0
  120. stoobly_agent/public/27-es5.af505e744b0c869a93d1.js +1 -0
  121. stoobly_agent/public/28-es2015.7c7c0f64e4af29d2e4d4.js +1 -0
  122. stoobly_agent/public/28-es5.7c7c0f64e4af29d2e4d4.js +1 -0
  123. stoobly_agent/public/32-es2015.2ab8267be7275dee9059.js +1 -0
  124. stoobly_agent/public/32-es5.2ab8267be7275dee9059.js +1 -0
  125. stoobly_agent/public/{34-es2015.ef24f6f7630620a38b19.js → 33-es2015.5b575f3a87c6c2c6b93b.js} +1 -1
  126. stoobly_agent/public/{34-es5.ef24f6f7630620a38b19.js → 33-es5.5b575f3a87c6c2c6b93b.js} +1 -1
  127. stoobly_agent/public/{35-es2015.0667e742725cc828f59f.js → 34-es2015.6a1160649c718cdb9338.js} +1 -1
  128. stoobly_agent/public/{35-es5.0667e742725cc828f59f.js → 34-es5.6a1160649c718cdb9338.js} +1 -1
  129. stoobly_agent/public/35-es2015.1b9dc7a46a6d5296c3ae.js +1 -0
  130. stoobly_agent/public/35-es5.1b9dc7a46a6d5296c3ae.js +1 -0
  131. stoobly_agent/public/{37-es2015.50d0c2d3fe4d0a74fc8f.js → 36-es2015.2fc9151fe6a5ff2bff31.js} +1 -1
  132. stoobly_agent/public/{37-es5.50d0c2d3fe4d0a74fc8f.js → 36-es5.2fc9151fe6a5ff2bff31.js} +1 -1
  133. stoobly_agent/public/{38-es2015.c14bde0b8d0cc14e915e.js → 37-es2015.ac7108c3625fd6e1d981.js} +1 -1
  134. stoobly_agent/public/{38-es5.c14bde0b8d0cc14e915e.js → 37-es5.ac7108c3625fd6e1d981.js} +1 -1
  135. stoobly_agent/public/38-es2015.9c183b14373c0e449932.js +1 -0
  136. stoobly_agent/public/38-es5.9c183b14373c0e449932.js +1 -0
  137. stoobly_agent/public/39-es2015.4624cdeb29fe9850b216.js +1 -0
  138. stoobly_agent/public/39-es5.4624cdeb29fe9850b216.js +1 -0
  139. stoobly_agent/public/40-es2015.24d981230c0c8f369cde.js +1 -0
  140. stoobly_agent/public/40-es5.24d981230c0c8f369cde.js +1 -0
  141. stoobly_agent/public/{46-es2015.abc7e4fd207d54277fcb.js → 45-es2015.c76937ed45d460bcd36f.js} +1 -1
  142. stoobly_agent/public/{46-es5.abc7e4fd207d54277fcb.js → 45-es5.c76937ed45d460bcd36f.js} +1 -1
  143. stoobly_agent/public/6-es2015.53acc5d2ca7f48ef857f.js +1 -0
  144. stoobly_agent/public/6-es5.53acc5d2ca7f48ef857f.js +1 -0
  145. stoobly_agent/public/7-es2015.1c6b3d315d50ccd228cb.js +1 -0
  146. stoobly_agent/public/7-es5.1c6b3d315d50ccd228cb.js +1 -0
  147. stoobly_agent/public/{9-es2015.ef0f7cb32f5fadb085d0.js → 8-es2015.0fe7492f7b61eb4699b6.js} +1 -1
  148. stoobly_agent/public/{9-es5.ef0f7cb32f5fadb085d0.js → 8-es5.0fe7492f7b61eb4699b6.js} +1 -1
  149. stoobly_agent/public/common-es2015.86f70de6df2c705a87f6.js +1 -0
  150. stoobly_agent/public/common-es5.86f70de6df2c705a87f6.js +1 -0
  151. stoobly_agent/public/dashboard.agent-alpha-0.22.3.tar.gz +0 -0
  152. stoobly_agent/public/index.html +1 -1
  153. stoobly_agent/public/main-es2015.aceb967cb4cccc0bc521.js +1 -0
  154. stoobly_agent/public/main-es5.aceb967cb4cccc0bc521.js +1 -0
  155. stoobly_agent/public/{polyfills-es2015.2b40b2ecdf98a9210572.js → polyfills-es2015.580f7a6e775c2c348c9d.js} +1 -1
  156. stoobly_agent/public/{polyfills-es5.d9fb2eee68607c3f7f64.js → polyfills-es5.4c3461a071d35be3dd81.js} +1 -1
  157. stoobly_agent/public/runtime-es2015.49eaebd2913fa7fe2b97.js +1 -0
  158. stoobly_agent/public/runtime-es5.49eaebd2913fa7fe2b97.js +1 -0
  159. stoobly_agent/test/test_helper.py +4 -5
  160. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/METADATA +1 -1
  161. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/RECORD +175 -157
  162. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/WHEEL +1 -1
  163. stoobly_agent/app/models/adapters/mitmproxy_request_adapter.py +0 -60
  164. stoobly_agent/app/models/adapters/types/__init__.py +0 -3
  165. stoobly_agent/public/11-es2015.b85bdc528bab0ee542fe.js +0 -1
  166. stoobly_agent/public/11-es5.b85bdc528bab0ee542fe.js +0 -1
  167. stoobly_agent/public/12-es2015.72399d40488de533bb97.js +0 -1
  168. stoobly_agent/public/12-es5.72399d40488de533bb97.js +0 -1
  169. stoobly_agent/public/14-es2015.7cedfd0829bd7a64677a.js +0 -1
  170. stoobly_agent/public/14-es5.7cedfd0829bd7a64677a.js +0 -1
  171. stoobly_agent/public/17-es2015.f6f28ba9f681063632a8.js +0 -1
  172. stoobly_agent/public/17-es5.f6f28ba9f681063632a8.js +0 -1
  173. stoobly_agent/public/18-es2015.b0cd6822ebd1090b0d60.js +0 -1
  174. stoobly_agent/public/18-es5.b0cd6822ebd1090b0d60.js +0 -1
  175. stoobly_agent/public/27-es2015.4635450ff709f7868796.js +0 -1
  176. stoobly_agent/public/27-es5.4635450ff709f7868796.js +0 -1
  177. stoobly_agent/public/28-es2015.1b29b35529772ab108f1.js +0 -1
  178. stoobly_agent/public/28-es5.1b29b35529772ab108f1.js +0 -1
  179. stoobly_agent/public/32-es2015.862b67803e6242451976.js +0 -1
  180. stoobly_agent/public/32-es5.862b67803e6242451976.js +0 -1
  181. stoobly_agent/public/33-es2015.4ff4325d1aec37e1b43c.js +0 -1
  182. stoobly_agent/public/33-es5.4ff4325d1aec37e1b43c.js +0 -1
  183. stoobly_agent/public/36-es2015.cd370fdf8990019d0c8e.js +0 -1
  184. stoobly_agent/public/36-es5.cd370fdf8990019d0c8e.js +0 -1
  185. stoobly_agent/public/39-es2015.4ec5fc16202c4759eac4.js +0 -1
  186. stoobly_agent/public/39-es5.4ec5fc16202c4759eac4.js +0 -1
  187. stoobly_agent/public/40-es2015.25287ce77b40050f3471.js +0 -1
  188. stoobly_agent/public/40-es5.25287ce77b40050f3471.js +0 -1
  189. stoobly_agent/public/45-es2015.fd110741ac0fbaada177.js +0 -1
  190. stoobly_agent/public/45-es5.fd110741ac0fbaada177.js +0 -1
  191. stoobly_agent/public/6-es2015.7219d596e3545ebaed3a.js +0 -1
  192. stoobly_agent/public/6-es5.7219d596e3545ebaed3a.js +0 -1
  193. stoobly_agent/public/7-es2015.98b085349ebd9d246060.js +0 -1
  194. stoobly_agent/public/7-es5.98b085349ebd9d246060.js +0 -1
  195. stoobly_agent/public/8-es2015.335fd5c122ad083aec65.js +0 -1
  196. stoobly_agent/public/8-es5.335fd5c122ad083aec65.js +0 -1
  197. stoobly_agent/public/common-es2015.81f870bf87411a04446d.js +0 -1
  198. stoobly_agent/public/common-es5.81f870bf87411a04446d.js +0 -1
  199. stoobly_agent/public/dashboard.agent-alpha-0.21.0.tar.gz +0 -0
  200. stoobly_agent/public/main-es2015.575c1d17fc866d3a6649.js +0 -1
  201. stoobly_agent/public/main-es5.575c1d17fc866d3a6649.js +0 -1
  202. stoobly_agent/public/runtime-es2015.8915d042dc9b55368999.js +0 -1
  203. stoobly_agent/public/runtime-es5.8915d042dc9b55368999.js +0 -1
  204. /stoobly_agent/app/models/adapters/{local_db → stoobly}/__init__.py +0 -0
  205. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/body_adapter.py +0 -0
  206. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/header_adapter.py +0 -0
  207. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/query_param_adapter.py +0 -0
  208. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/response_adapter.py +0 -0
  209. /stoobly_agent/app/models/{adapters → factories/resource}/local_db/response_header_adapter.py +0 -0
  210. /stoobly_agent/app/models/{adapters/types → types}/replayed_response.py +0 -0
  211. /stoobly_agent/app/models/{adapters/types → types}/request_create_params.py +0 -0
  212. /stoobly_agent/app/models/{adapters/types → types}/request_show_params.py +0 -0
  213. /stoobly_agent/app/models/{adapters/types → types}/scenario_create_params.py +0 -0
  214. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/LICENSE +0 -0
  215. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/entry_points.txt +0 -0
  216. {stoobly_agent-0.21.2.dist-info → stoobly_agent-0.22.3.dist-info}/top_level.txt +0 -0
stoobly_agent/__init__.py CHANGED
@@ -1 +1,2 @@
1
- VERSION = '0.21.2'
1
+ COMMAND = 'stoobly-agent'
2
+ VERSION = '0.22.3'
@@ -41,12 +41,12 @@ class BodiesController:
41
41
 
42
42
  accepted_headers = ['content-encoding', 'content-length', 'content-type']
43
43
  for header, val in request.headers.items():
44
- decoded_header = header.decode().lower()
44
+ decoded_header = header.lower()
45
45
 
46
46
  if decoded_header not in accepted_headers:
47
47
  continue
48
48
 
49
- headers[decoded_header] = val.decode()
49
+ headers[decoded_header] = val
50
50
 
51
51
  context.render(
52
52
  data = request.data,
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import pdb
2
3
  import requests
3
4
 
@@ -8,15 +9,15 @@ from urllib.parse import urlparse
8
9
  from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
9
10
  from stoobly_agent.app.cli.helpers.context import ReplayContext
10
11
  from stoobly_agent.app.models.adapters.joined_request_adapter import JoinedRequestAdapter
11
- from stoobly_agent.app.models.adapters.mitmproxy_request_adapter import MitmproxyRequestAdapter
12
- from stoobly_agent.app.models.adapters.mitmproxy_response_adapter import MitmproxyResponseAdapter
12
+ from stoobly_agent.app.models.adapters.python import PythonRequestAdapterFactory, PythonResponseAdapterFactory
13
13
  from stoobly_agent.app.models.adapters.raw_http_request_adapter import RawHttpRequestAdapter
14
14
  from stoobly_agent.app.models.adapters.raw_http_response_adapter import RawHttpResponseAdapter
15
15
  from stoobly_agent.app.models.request_model import RequestModel
16
16
  from stoobly_agent.app.models.schemas.request import Request
17
- from stoobly_agent.app.proxy.replay.replay_request_service import replay_with_rewrite
17
+ from stoobly_agent.app.proxy.replay.replay_request_service import replay
18
18
  from stoobly_agent.app.proxy.upload.upload_request_service import upload_staged_request
19
19
  from stoobly_agent.app.settings import Settings
20
+ from stoobly_agent.config.constants import mode
20
21
  from stoobly_agent.lib.orm.replayed_response import ReplayedResponse
21
22
  from stoobly_agent.lib.orm.request import Request as OrmRequest
22
23
 
@@ -53,8 +54,8 @@ class RequestsController:
53
54
  request_adapter = RawHttpRequestAdapter(joined_request.request_string.get())
54
55
  response_adapter = RawHttpResponseAdapter(joined_request.response_string.get())
55
56
 
56
- mitmproxy_request = MitmproxyRequestAdapter(request_adapter.protocol, request_adapter.to_request()).adapt()
57
- mitmproxy_response = MitmproxyResponseAdapter(response_adapter.protocol, response_adapter.to_response()).adapt()
57
+ mitmproxy_request = PythonRequestAdapterFactory(request_adapter.to_request()).mitmproxy_request(request_adapter.protocol)
58
+ mitmproxy_response = PythonResponseAdapterFactory(response_adapter.to_response()).mitmproxy_response()
58
59
 
59
60
  class MitmproxyFlowMock():
60
61
  def __init__(self, request, response):
@@ -67,6 +68,7 @@ class RequestsController:
67
68
  request = request_model.create(**{
68
69
  'flow': mitmproxy_flow_mock,
69
70
  'joined_request': joined_request,
71
+ 'scenario_id': body_params.get('scenario_id'),
70
72
  })
71
73
 
72
74
  if not request:
@@ -206,10 +208,17 @@ class RequestsController:
206
208
 
207
209
  # PUT /requests/send
208
210
  def send(self, context: SimpleHTTPRequestHandler):
211
+ headers = []
212
+
213
+ try:
214
+ headers = json.load(context.params.get('headers'))
215
+ except Exception as e:
216
+ pass
217
+
209
218
  url = urlparse(context.params.get('url'))
210
219
  request_response = {
211
220
  'body': context.params.get('body'),
212
- 'headers': context.params.get('headers'),
221
+ 'headers': headers,
213
222
  'method': context.params.get('method'),
214
223
  'path': url.path,
215
224
  'password': url.password,
@@ -220,7 +229,13 @@ class RequestsController:
220
229
  }
221
230
 
222
231
  replay_context = ReplayContext(Request(request_response))
223
- self.__send(context, replay_context)
232
+ self.__send(context, replay_context)
233
+
234
+ def export(self, context: SimpleHTTPRequestHandler):
235
+ context.parse_path_params({
236
+ 'id': 1
237
+ })
238
+ request_id = int(context.params.get('id'))
224
239
 
225
240
  def __request_model(self, context: SimpleHTTPRequestHandler):
226
241
  request_model = RequestModel(Settings.instance())
@@ -228,18 +243,21 @@ class RequestsController:
228
243
  return request_model
229
244
 
230
245
  def __replay(self, context: SimpleHTTPRequestHandler, replay_context: ReplayContext):
231
- self.__send(context, replay_context, self.__create_replayed_response)
246
+ save = bool(context.params.get('save'))
247
+ callback = self.__create_replayed_response if save else None
248
+
249
+ self.__send(context, replay_context, callback)
232
250
 
233
251
  def __send(self, context: SimpleHTTPRequestHandler, replay_context: ReplayContext, callback = None):
234
252
  now = time()
235
- res = replay_with_rewrite(replay_context)
253
+ res = replay(replay_context, { 'mode': mode.REPLAY })
236
254
  received_at = time()
237
255
 
238
256
  if callback:
239
257
  callback(context, res, int((received_at - now) * 1000))
240
258
 
241
259
  context.render(
242
- data = res.raw.data,
260
+ data = res.raw.data if hasattr(res, 'raw') else res.content,
243
261
  headers = res.headers,
244
262
  status = res.status_code
245
263
  )
@@ -62,7 +62,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
62
62
  headers = self.filter_headers(kwargs.get('headers'), {
63
63
  'TRANSFER-ENCODING': 'CHUNKED',
64
64
  })
65
- headers['Content-Length'] = len(body)
65
+ headers['Content-Length'] = str(len(body))
66
66
 
67
67
  self.enable_cors(headers)
68
68
  self.render_headers(headers)
@@ -7,19 +7,22 @@ class CACertInstaller():
7
7
  home_dir = os.path.expanduser('~')
8
8
  self.mitmproxy_certs_dir = os.path.join(home_dir, '.mitmproxy')
9
9
  self.pem_file_name = 'mitmproxy-ca-cert.pem'
10
+ self.cer_file_name = 'mitmproxy-ca-cert.cer'
10
11
  self.crt_file_name = 'mitmproxy-ca-cert.crt'
11
12
 
13
+ @property
14
+ def mitm_crt_absolute_path(self):
15
+ return os.path.join(self.mitmproxy_certs_dir, self.crt_file_name)
16
+
12
17
  # https://askubuntu.com/a/94861
13
18
  def handle_debian(self):
14
19
  extra_ca_certs_dir = '/usr/local/share/ca-certificates/extra'
15
- mitm_pem_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.pem_file_name)
16
- mitm_crt_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.crt_file_name)
20
+ mitm_cer_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.cer_file_name)
17
21
  extra_crt_absolute_path = os.path.join(extra_ca_certs_dir, self.crt_file_name)
18
22
 
19
23
  subprocess.run(f"sudo mkdir -p {extra_ca_certs_dir}".split(), check=True)
20
- subprocess.run(f"openssl x509 -in {mitm_pem_absolute_path} -inform PEM -out {mitm_crt_absolute_path}".split(), check=True)
21
- subprocess.run(f"sudo cp {mitm_crt_absolute_path} {extra_crt_absolute_path}".split(), check=True)
22
- subprocess.run('sudo update-ca-certificates'.split(), check=True)
24
+ subprocess.run(f"sudo cp {mitm_cer_absolute_path} {extra_crt_absolute_path}".split(), check=True)
25
+ subprocess.run("sudo update-ca-certificates".split(), check=True)
23
26
 
24
27
  # https://www.dssw.co.uk/reference/security.html
25
28
  def handle_darwin(self):
@@ -27,3 +30,11 @@ class CACertInstaller():
27
30
  mitm_pem_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.pem_file_name)
28
31
 
29
32
  subprocess.run(f"sudo security add-trusted-cert -d -p ssl -p basic -k {system_keychain_path} {mitm_pem_absolute_path}".split(), check=True)
33
+
34
+ def handle_rhel(self):
35
+ ca_trust_dir = '/etc/pki/ca-trust/source/anchors'
36
+ mitm_cer_absolute_path = os.path.join(self.mitmproxy_certs_dir, self.cer_file_name)
37
+ ca_trust_crt_absolute_path = os.path.join(ca_trust_dir, self.crt_file_name)
38
+
39
+ subprocess.run(f"sudo cp {mitm_cer_absolute_path} {ca_trust_crt_absolute_path}".split(), check=True)
40
+ subprocess.run("sudo update-ca-trust extract".split(), check=True)
@@ -223,7 +223,16 @@ if is_remote:
223
223
  pass
224
224
 
225
225
  @project.command(
226
- help="Describe project"
226
+ help="Use local project."
227
+ )
228
+ def local(**kwargs):
229
+ project_key = ProjectKey.local_key
230
+ __set_project_key(project_key)
231
+
232
+ print("Using local project!")
233
+
234
+ @project.command(
235
+ help="Describe project."
227
236
  )
228
237
  @click.option('--select', multiple=True, help='Select column(s) to display.')
229
238
  @click.option('--without-headers', is_flag=True, default=False, help='Disable printing column headers.')
@@ -233,7 +242,7 @@ if is_remote:
233
242
 
234
243
  project_key = __project_key(settings)
235
244
  if project_key.is_local:
236
- return
245
+ return print('Using local project')
237
246
 
238
247
  kwargs['project_key'] = project_key.raw
239
248
 
@@ -252,25 +261,9 @@ if is_remote:
252
261
  )
253
262
  @click.argument('project_key')
254
263
  def set(**kwargs):
255
- settings = Settings.instance()
256
-
257
264
  project_key = kwargs['project_key']
258
- validate_project_key(project_key)
259
- _project_key = ProjectKey(project_key)
260
-
261
- data_rule = settings.proxy.data.data_rules(_project_key.id)
262
- scenario_key = data_rule.scenario_key
263
-
264
- if scenario_key:
265
- validate_scenario_key(scenario_key)
266
- scenario_key = ScenarioKey(scenario_key)
267
265
 
268
- if project_key.id != scenario_key.project_id:
269
- data_rule.scenario_key = None
270
- print("Current scenario does not belong to current project, unsetting current scenario.\n")
271
-
272
- settings.proxy.intercept.project_key = project_key
273
- settings.commit()
266
+ __set_project_key(project_key)
274
267
 
275
268
  print("Project updated!")
276
269
 
@@ -291,4 +284,23 @@ def __select_parameter_rule(kwargs):
291
284
  def __project_key(settings):
292
285
  project_key = settings.proxy.intercept.project_key
293
286
  validate_project_key(project_key)
294
- return ProjectKey(project_key)
287
+ return ProjectKey(project_key)
288
+
289
+ def __set_project_key(project_key: str):
290
+ settings = Settings.instance()
291
+ validate_project_key(project_key)
292
+ _project_key = ProjectKey(project_key)
293
+
294
+ data_rule = settings.proxy.data.data_rules(_project_key.id)
295
+ scenario_key = data_rule.scenario_key
296
+
297
+ if scenario_key:
298
+ validate_scenario_key(scenario_key)
299
+ scenario_key = ScenarioKey(scenario_key)
300
+
301
+ if project_key.id != scenario_key.project_id:
302
+ data_rule.scenario_key = None
303
+ print("Current scenario does not belong to current project, unsetting current scenario.\n")
304
+
305
+ settings.proxy.intercept.project_key = project_key
306
+ settings.commit()
@@ -1,6 +1,8 @@
1
+ import pdb
1
2
  import requests
2
3
  import time
3
4
 
5
+ from stoobly_agent.app.models.adapters.python import PythonRequestAdapterFactory
4
6
  from stoobly_agent.app.models.schemas.request import Request
5
7
 
6
8
  class ReplayContext():
@@ -14,6 +16,11 @@ class ReplayContext():
14
16
 
15
17
  self.__sequence = None
16
18
 
19
+ @classmethod
20
+ def from_python_request(cls, request: requests.Request):
21
+ stoobly_request = PythonRequestAdapterFactory(request).stoobly_request()
22
+ return cls(Request(stoobly_request))
23
+
17
24
  @property
18
25
  def end_time(self):
19
26
  return self.__end_time
@@ -0,0 +1,14 @@
1
+ import requests
2
+
3
+ from stoobly_agent.app.models.adapters.python import PythonResponseAdapterFactory
4
+ from stoobly_agent.app.proxy.mitmproxy.response_facade import MitmproxyResponseFacade
5
+ from stoobly_agent.app.proxy.upload.response_string import ResponseString
6
+
7
+ RAW_FORMAT = 'raw'
8
+
9
+ def print_raw_response(response: requests.Response):
10
+ mitmproxy_response = PythonResponseAdapterFactory(response).mitmproxy_response()
11
+ facade = MitmproxyResponseFacade(mitmproxy_response)
12
+ response_string = ResponseString(facade, None)
13
+
14
+ print(response_string.get().decode())
@@ -14,7 +14,7 @@ def select_print_options(kwargs):
14
14
  def print_projects(projects, **kwargs):
15
15
  tabulate_print(
16
16
  projects,
17
- filter=['created_at', 'organization_id', 'project_id', 'starred', 'updated_at'],
17
+ filter=['created_at', 'is_deleted', 'organization_id', 'project_id', 'starred', 'updated_at'],
18
18
  headers=not kwargs.get('without_headers'),
19
19
  select=kwargs.get('select') or []
20
20
  )
@@ -3,15 +3,59 @@ import pdb
3
3
 
4
4
  from stoobly_agent.app.cli.decorators.config import ConfigDecorator
5
5
  from stoobly_agent.app.settings import Settings
6
+ from stoobly_agent.config.constants import mode
6
7
 
7
8
  settings = Settings.instance()
9
+ mode_options = [mode.MOCK, mode.RECORD, mode.REPLAY]
10
+
11
+ if settings.cli.features.remote:
12
+ mode_options.append(mode.TEST)
8
13
 
9
14
  @click.group(
10
15
  epilog="Run 'stoobly-agent intercept COMMAND --help' for more information on a command.",
11
- help="Toggle request intercept"
16
+ help="Manage request intercept"
12
17
  )
13
18
  @click.pass_context
14
19
  def intercept(ctx):
15
20
  pass
16
21
 
17
- ConfigDecorator(intercept, settings, 'proxy.intercept.active').decorate()
22
+ @intercept.command(
23
+ help="Enable intercept"
24
+ )
25
+ @click.option('--mode', type=click.Choice(mode_options))
26
+ def enable(**kwargs):
27
+ settings = Settings.instance()
28
+
29
+ if kwargs['mode']:
30
+ settings.proxy.intercept.mode = kwargs['mode']
31
+
32
+ settings.proxy.intercept.active = True
33
+
34
+ settings.commit()
35
+
36
+ print("Intercept enabled!")
37
+
38
+ @intercept.command(
39
+ help="Disable intercept"
40
+ )
41
+ def disable(**kwargs):
42
+ settings = Settings.instance()
43
+
44
+ settings.proxy.intercept.active = False
45
+
46
+ settings.commit()
47
+
48
+ print("Intercept disabled!")
49
+
50
+ @intercept.command(
51
+ help="Show intercept"
52
+ )
53
+ def show(**kwargs):
54
+ settings = Settings.instance()
55
+
56
+ mode = settings.proxy.intercept.mode
57
+
58
+ if not mode:
59
+ print('No intercept mode set')
60
+ else:
61
+ print(f"{mode.capitalize()} {'enabled' if settings.proxy.intercept.active else 'disabled'}")
@@ -26,7 +26,7 @@ class MainGroup(click.Group):
26
26
  command_groups: List[CommandGroup] = [
27
27
  {
28
28
  'name': 'Commands',
29
- 'commands': ['dev-tools', 'exec', 'feature', 'init'],
29
+ 'commands': ['dev-tools', 'exec', 'feature', 'init', 'mock', 'record'],
30
30
  },
31
31
  {
32
32
  'name': 'Proxy Commands',
@@ -36,12 +36,12 @@ class MainGroup(click.Group):
36
36
 
37
37
  if self.__settings.cli.features.remote:
38
38
  command_groups.append({
39
- 'name': 'Remote Commands',
39
+ 'name': 'Remote Resource Commands',
40
40
  'commands': ['project', 'report', 'request', 'scenario', 'trace'],
41
41
  })
42
42
  else:
43
43
  command_groups.append({
44
- 'name': 'Local Commands',
44
+ 'name': 'Local Resource Commands',
45
45
  'commands': ['request', 'scenario'],
46
46
  })
47
47
 
@@ -57,7 +57,9 @@ def list(**kwargs):
57
57
  if not organization_key:
58
58
  try:
59
59
  project_key = ProjectKey(settings.proxy.intercept.project_key)
60
- organization_key = OrganizationKey.encode(project_key.organization_id)
60
+
61
+ if not project_key.is_local:
62
+ organization_key = OrganizationKey.encode(project_key.organization_id)
61
63
  except InvalidProjectKey as e:
62
64
  pass
63
65
 
@@ -38,7 +38,7 @@ def request(ctx):
38
38
  )
39
39
  @click.option('--page', default=0)
40
40
  @ConditionalDecorator(lambda f: click.option('--project-key')(f), is_remote)
41
- @ConditionalDecorator(lambda f: click.option('--scenario-key')(f), is_remote)
41
+ @click.option('--scenario-key')
42
42
  @click.option('--select', multiple=True, help='Select column(s) to display.')
43
43
  @click.option('--sort-by', default='created_at', help='created_at|path')
44
44
  @click.option('--sort-order', default='desc', help='asc | desc')
@@ -86,7 +86,7 @@ def list(**kwargs):
86
86
  '''
87
87
  )
88
88
  @click.option('--record', is_flag=True, default=False, help='Replay and record request.')
89
- @ConditionalDecorator(lambda f: click.option('--scenario-key', help='Record to scenario.')(f), is_remote)
89
+ @click.option('--scenario-key', help='Record to scenario.')
90
90
  @click.option('--scheme', help='Rewrite request scheme.')
91
91
  @ConditionalDecorator(lambda f: click.option('--trace-id', help='Use existing trace.')(f), is_remote)
92
92
  @ConditionalDecorator(lambda f: click.option('--validate', multiple=True, help='Validate one or more aliases. Format: <NAME>=?<TYPE>')(f), is_remote)
@@ -203,9 +203,11 @@ def get(**kwargs):
203
203
 
204
204
  print(res.content)
205
205
 
206
- @response.command()
206
+ @response.command(
207
+ help="Query properties in response"
208
+ )
207
209
  @click.option('--query', required=True)
208
- @click.argument('request-key')
210
+ @click.argument('request_key')
209
211
  def query(**kwargs):
210
212
  validate_request_key(kwargs['request_key'])
211
213
 
@@ -218,12 +220,6 @@ def query(**kwargs):
218
220
 
219
221
  print(jmespath.search(kwargs['query'], decoded_response))
220
222
 
221
- @response.command(
222
- help="Set new mocked response"
223
- )
224
- def set(**kwargs):
225
- print("Not yet implemented. Stay tuned!")
226
-
227
223
  request.add_command(response)
228
224
 
229
225
  def __replay(handler, kwargs) -> requests.Response:
@@ -0,0 +1,2 @@
1
+ from .request import MitmproxyRequestAdapterFactory
2
+ from .response import MitmproxyResponseAdapterFactory
@@ -0,0 +1,12 @@
1
+ import pdb
2
+ import requests
3
+
4
+ from .python_adapter import PythonRequestAdapter
5
+
6
+ class MitmproxyRequestAdapterFactory():
7
+
8
+ def __init__(self, request: requests.Request):
9
+ self.__request = request
10
+
11
+ def python_request(self):
12
+ return PythonRequestAdapter(self.__request).adapt()
@@ -0,0 +1,16 @@
1
+ import requests
2
+
3
+ from mitmproxy.http import Request as MitmproxyRequest
4
+
5
+ class PythonRequestAdapter():
6
+
7
+ def __init__(self, request: MitmproxyRequest):
8
+ self.__request = request
9
+
10
+ def adapt(self):
11
+ return requests.Request(
12
+ method=self.__request.method,
13
+ url=self.__request.url,
14
+ headers=self.__request.headers,
15
+ data=self.__request.content
16
+ )
@@ -0,0 +1,12 @@
1
+ import pdb
2
+ import requests
3
+
4
+ from .python_adapter import PythonResponseAdapter
5
+
6
+ class MitmproxyResponseAdapterFactory():
7
+
8
+ def __init__(self, response: requests.Response):
9
+ self.__response = response
10
+
11
+ def python_response(self):
12
+ return PythonResponseAdapter(self.__response).adapt()
@@ -0,0 +1,24 @@
1
+ from io import BytesIO
2
+ import requests
3
+
4
+ from mitmproxy.http import Response as MitmproxyResponse
5
+ from urllib3 import HTTPResponse
6
+
7
+ class PythonResponseAdapter():
8
+
9
+ def __init__(self, response: MitmproxyResponse):
10
+ self.__response = response
11
+
12
+ def adapt(self) -> requests.Response:
13
+ response = requests.Response()
14
+ response.status_code = self.__response.status_code
15
+ response.headers = self.__response.headers
16
+
17
+ response.raw = HTTPResponse(
18
+ body=BytesIO(self.__response.raw_content),
19
+ decode_content=False,
20
+ headers=self.__response.headers,
21
+ preload_content=False
22
+ )
23
+
24
+ return response
@@ -0,0 +1,2 @@
1
+ from .request import PythonRequestAdapterFactory
2
+ from .response import PythonResponseAdapterFactory
@@ -0,0 +1,18 @@
1
+ import pdb
2
+ import requests
3
+
4
+ from urllib.parse import parse_qs, urlparse
5
+
6
+ from .mitmproxy_adapter import MitmproxyRequestAdapter
7
+ from .stoobly_adapter import StooblyRequestAdapter
8
+
9
+ class PythonRequestAdapterFactory():
10
+
11
+ def __init__(self, request: requests.Request):
12
+ self.__request = request
13
+
14
+ def mitmproxy_request(self, http_version: str = 'HTTP/1.1'):
15
+ return MitmproxyRequestAdapter(http_version, self.__request).adapt()
16
+
17
+ def stoobly_request(self):
18
+ return StooblyRequestAdapter(self.__request).adapt()
@@ -0,0 +1,53 @@
1
+ import pdb
2
+ import requests
3
+
4
+ from mitmproxy.http import Headers, Request as MitmproxyRequest
5
+ from urllib.parse import urlparse
6
+
7
+ class MitmproxyRequestAdapter():
8
+
9
+ def __init__(self, http_version, request: requests.Request):
10
+ self.__http_version = http_version
11
+ self.__request = request
12
+
13
+ @property
14
+ def url(self):
15
+ parsed_url = urlparse(self.__request.url)
16
+
17
+ if not parsed_url.netloc:
18
+ parsed_url = parsed_url._replace(netloc=parsed_url.path, path='')
19
+
20
+ if not parsed_url.scheme:
21
+ parsed_url = parsed_url._replace(scheme='https')
22
+
23
+ return parsed_url.geturl()
24
+
25
+ @property
26
+ def headers(self):
27
+ return Headers(**self.__decode_dict(self.__request.headers))
28
+
29
+ @property
30
+ def data(self):
31
+ _data = self.__request.data
32
+
33
+ # If no data is provided to python requests.Request, it is sent to []
34
+ if _data == []:
35
+ return b''
36
+
37
+ return _data
38
+
39
+ def adapt(self):
40
+ request = MitmproxyRequest.make(
41
+ self.__request.method,
42
+ self.url,
43
+ self.data,
44
+ self.headers,
45
+ )
46
+ request.http_version = self.__http_version
47
+ return request
48
+
49
+ def __decode_dict(self, d):
50
+ new_d = {}
51
+ for k, v in d.items():
52
+ new_d[k.decode() if isinstance(k, bytes) else k] = v.decode() if isinstance(v, bytes) else v
53
+ return new_d
@@ -0,0 +1,49 @@
1
+ import pdb
2
+ import requests
3
+
4
+ from urllib.parse import parse_qs, urlparse
5
+
6
+ class StooblyRequestAdapter():
7
+
8
+ def __init__(self, request: requests.Request):
9
+ self.__request = request
10
+
11
+ def adapt(self):
12
+ parsed_url = urlparse(self.__request.url)
13
+
14
+ return {
15
+ 'body': self.__request.data,
16
+ 'headers': self.adapt_headers(),
17
+ 'method': self.__request.method,
18
+ 'path': parsed_url.path,
19
+ 'query_params': self.adapt_query_params(parsed_url.query),
20
+ 'url': self.__request.url,
21
+ }
22
+
23
+ def adapt_query_params(self, query = None):
24
+ if not query:
25
+ parsed_url = urlparse(self.__request.url)
26
+ query = parsed_url.query
27
+
28
+ _query_params = parse_qs(query)
29
+
30
+ query_params = []
31
+ for k, v in _query_params.items():
32
+ for param in v:
33
+ query_params.append({
34
+ 'name': k,
35
+ 'value': param,
36
+ })
37
+
38
+ return query_params
39
+
40
+ def adapt_headers(self):
41
+ headers = []
42
+
43
+ for k, v in self.__request.headers.items():
44
+ headers.append({
45
+ 'name': k,
46
+ 'value': v
47
+ })
48
+
49
+ return headers
@@ -0,0 +1,13 @@
1
+ import http
2
+ import pdb
3
+ import requests
4
+
5
+ from .mitmproxy_adapter import MitmproxyResponseAdapter
6
+
7
+ class PythonResponseAdapterFactory():
8
+
9
+ def __init__(self, response: requests.Response):
10
+ self.__response = response
11
+
12
+ def mitmproxy_response(self):
13
+ return MitmproxyResponseAdapter(self.__response).adapt()