stoobly-agent 1.8.4__py3-none-any.whl → 1.9.0__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 (67) hide show
  1. stoobly_agent/__init__.py +1 -1
  2. stoobly_agent/app/api/configs_controller.py +3 -3
  3. stoobly_agent/app/api/headers_controller.py +1 -1
  4. stoobly_agent/app/api/query_params_controller.py +4 -4
  5. stoobly_agent/app/cli/helpers/handle_config_update_service.py +12 -12
  6. stoobly_agent/app/cli/intercept_cli.py +30 -7
  7. stoobly_agent/app/cli/scaffold/templates/app/.Dockerfile.context +1 -1
  8. stoobly_agent/app/cli/scaffold/templates/app/.Makefile +6 -0
  9. stoobly_agent/app/cli/scaffold/templates/build/workflows/exec/.overwrite +13 -0
  10. stoobly_agent/app/cli/scaffold/templates/workflow/record/bin/configure +16 -0
  11. stoobly_agent/app/cli/snapshot_cli.py +57 -0
  12. stoobly_agent/app/cli/types/snapshot_migration.py +61 -0
  13. stoobly_agent/app/models/adapters/joined_request_adapter.py +1 -1
  14. stoobly_agent/app/models/adapters/orm/request/mitmproxy_adapter.py +1 -1
  15. stoobly_agent/app/models/adapters/python/request/__init__.py +2 -2
  16. stoobly_agent/app/models/adapters/python/request/mitmproxy_adapter.py +1 -1
  17. stoobly_agent/app/models/adapters/python/request/raw_adapter.py +2 -2
  18. stoobly_agent/app/models/factories/resource/local_db/helpers/log.py +7 -0
  19. stoobly_agent/app/models/factories/resource/local_db/helpers/log_event.py +10 -0
  20. stoobly_agent/app/models/factories/resource/local_db/helpers/request_snapshot.py +20 -0
  21. stoobly_agent/app/models/factories/resource/local_db/helpers/scenario_snapshot.py +8 -1
  22. stoobly_agent/app/models/factories/resource/local_db/helpers/snapshot.py +1 -1
  23. stoobly_agent/app/models/factories/resource/local_db/query_param_adapter.py +2 -2
  24. stoobly_agent/app/models/helpers/apply.py +7 -6
  25. stoobly_agent/app/models/helpers/create_request_params_service.py +4 -2
  26. stoobly_agent/app/proxy/handle_mock_service.py +19 -6
  27. stoobly_agent/app/proxy/handle_record_service.py +16 -7
  28. stoobly_agent/app/proxy/intercept_settings.py +12 -1
  29. stoobly_agent/app/proxy/mitmproxy/request_facade.py +1 -1
  30. stoobly_agent/app/proxy/record/join_request_service.py +22 -10
  31. stoobly_agent/app/proxy/record/proxy_request.py +9 -0
  32. stoobly_agent/app/proxy/record/request_string.py +19 -10
  33. stoobly_agent/app/proxy/record/upload_request_service.py +7 -5
  34. stoobly_agent/app/proxy/replay/body_parser_service.py +15 -4
  35. stoobly_agent/app/proxy/replay/replay_request_service.py +1 -0
  36. stoobly_agent/app/proxy/test/helpers/upload_test_service.py +1 -1
  37. stoobly_agent/app/settings/data_rules.py +16 -1
  38. stoobly_agent/config/constants/custom_headers.py +1 -0
  39. stoobly_agent/config/constants/lifecycle_hooks.py +2 -0
  40. stoobly_agent/config/constants/record_order.py +6 -0
  41. stoobly_agent/config/constants/record_policy.py +2 -2
  42. stoobly_agent/lib/orm/transformers/orm_to_request_transformer.py +1 -1
  43. stoobly_agent/public/18-es2015.beb31fe4a4dee3007cb2.js +1 -0
  44. stoobly_agent/public/18-es5.beb31fe4a4dee3007cb2.js +1 -0
  45. stoobly_agent/public/index.html +2 -2
  46. stoobly_agent/public/main-es2015.089b46f303768fbe864f.js +1 -0
  47. stoobly_agent/public/main-es5.089b46f303768fbe864f.js +1 -0
  48. stoobly_agent/public/{runtime-es2015.b13c22b834b51724d30a.js → runtime-es2015.f8c814b38b27708e91c1.js} +1 -1
  49. stoobly_agent/public/{runtime-es5.b13c22b834b51724d30a.js → runtime-es5.f8c814b38b27708e91c1.js} +1 -1
  50. stoobly_agent/public/{styles.ab281309cf423b2cdcb0.css → styles.817f011ab81b18b0e5c2.css} +1 -1
  51. stoobly_agent/test/app/cli/config/scenario/config_scenario_set_test.py +3 -3
  52. stoobly_agent/test/app/cli/intercept/intercept_configure_test.py +5 -7
  53. stoobly_agent/test/app/cli/intercept/intercept_enable_test.py +3 -5
  54. stoobly_agent/test/app/cli/scenario/scenario_snapshot_test.py +1 -1
  55. stoobly_agent/test/app/cli/snapshot/lifecycle_hooks_migrate.py +10 -0
  56. stoobly_agent/test/app/cli/snapshot/snapshot_migrate_test.py +181 -0
  57. stoobly_agent/test/app/models/schemas/.stoobly/db/VERSION +1 -1
  58. stoobly_agent/test/cli/record_test.py +3 -3
  59. {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/METADATA +1 -1
  60. {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/RECORD +63 -58
  61. stoobly_agent/public/18-es2015.d07dd29def7e2574c5b7.js +0 -1
  62. stoobly_agent/public/18-es5.d07dd29def7e2574c5b7.js +0 -1
  63. stoobly_agent/public/main-es2015.ce00115b0520fa030f01.js +0 -1
  64. stoobly_agent/public/main-es5.ce00115b0520fa030f01.js +0 -1
  65. {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/LICENSE +0 -0
  66. {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/WHEEL +0 -0
  67. {stoobly_agent-1.8.4.dist-info → stoobly_agent-1.9.0.dist-info}/entry_points.txt +0 -0
stoobly_agent/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
1
  COMMAND = 'stoobly-agent'
2
- VERSION = '1.8.4'
2
+ VERSION = '1.9.0'
@@ -4,7 +4,7 @@ from mergedeep import merge
4
4
 
5
5
  from stoobly_agent.app.api.simple_http_request_handler import SimpleHTTPRequestHandler
6
6
  from stoobly_agent.app.cli.helpers.handle_config_update_service import (
7
- context as handle_context, handle_intercept_active_update, handle_policy_update, handle_project_update, handle_scenario_update
7
+ context as handle_context, handle_intercept_active_update, handle_order_update, handle_project_update, handle_scenario_update
8
8
  )
9
9
  from stoobly_agent.app.models.scenario_model import ScenarioModel
10
10
  from stoobly_agent.app.proxy.intercept_settings import InterceptSettings
@@ -41,7 +41,7 @@ class ConfigsController:
41
41
  )
42
42
  elif active_mode == mode.RECORD:
43
43
  context.render(
44
- json = [record_policy.ALL, record_policy.FOUND, record_policy.NOT_FOUND, record_policy.OVERWRITE],
44
+ json = [record_policy.ALL, record_policy.API, record_policy.FOUND, record_policy.NOT_FOUND],
45
45
  status = 200
46
46
  )
47
47
  elif active_mode == mode.REPLAY:
@@ -112,7 +112,7 @@ class ConfigsController:
112
112
  _handle_context = handle_context()
113
113
 
114
114
  handle_intercept_active_update(settings, _handle_context)
115
- handle_policy_update(settings, _handle_context)
115
+ handle_order_update(settings, _handle_context)
116
116
  handle_project_update(settings, _handle_context)
117
117
  handle_scenario_update(settings, _handle_context)
118
118
 
@@ -86,7 +86,7 @@ class HeadersController:
86
86
 
87
87
  context.render(
88
88
  plain = '',
89
- status = 200
89
+ status = 204
90
90
  )
91
91
 
92
92
  def __header_model(self, context: SimpleHTTPRequestHandler):
@@ -103,13 +103,13 @@ class QueryParamsController:
103
103
 
104
104
  if context.filter_response(query_param, status):
105
105
  return
106
-
106
+
107
107
  context.render(
108
- plain = query_param,
109
- status = status
108
+ plain = '',
109
+ status = 204
110
110
  )
111
111
 
112
112
  def __query_param_model(self, context: SimpleHTTPRequestHandler):
113
113
  access_token = context.headers.get('access-token')
114
114
  query_param_model = QueryParamModel(Settings.instance(), access_token=access_token)
115
- return query_param_model
115
+ return query_param_model
@@ -6,7 +6,7 @@ from stoobly_agent.app.models.scenario_model import ScenarioModel
6
6
  from stoobly_agent.app.settings.constants import intercept_mode
7
7
  from stoobly_agent.app.settings.constants.intercept_mode import Mode
8
8
  from stoobly_agent.app.settings import ProxySettings, Settings
9
- from stoobly_agent.config.constants import record_policy
9
+ from stoobly_agent.config.constants import record_order
10
10
  from stoobly_agent.lib.api.keys.project_key import ProjectKey
11
11
  from stoobly_agent.lib.api.keys.scenario_key import ScenarioKey
12
12
 
@@ -28,15 +28,15 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
28
28
 
29
29
  if not was_active and is_active:
30
30
  if _mode == intercept_mode.RECORD:
31
- new_policy = data_rule.record_policy
31
+ new_order = data_rule.record_order
32
32
  scenario_key = data_rule.scenario_key
33
33
  _scenario_key = __parse_scenario_key(scenario_key)
34
34
 
35
35
  if _scenario_key:
36
36
  scenario_model = ScenarioModel(new_settings)
37
37
 
38
- if new_policy == record_policy.OVERWRITE:
39
- # If policy is overwrite when recording, whenever intercept is enabled,
38
+ if new_order == record_order.OVERWRITE:
39
+ # If order is overwrite when recording, whenever intercept is enabled,
40
40
  # set active scenario to be overwritable
41
41
  scenario_model.update(_scenario_key.id, **{ 'overwritable': True })[1]
42
42
  else:
@@ -44,16 +44,16 @@ def handle_intercept_active_update(new_settings: Settings, context: Context = No
44
44
  elif was_active and not is_active:
45
45
  if _mode == intercept_mode.RECORD:
46
46
  old_proxy_settings = __current_proxy_settings(context)
47
- old_policy = __data_rule(old_proxy_settings).record_policy
47
+ old_order = __data_rule(old_proxy_settings).record_order
48
48
 
49
- if old_policy == record_policy.OVERWRITE:
49
+ if old_order == record_order.OVERWRITE:
50
50
  scenario_key = data_rule.scenario_key
51
51
  _scenario_key = __parse_scenario_key(scenario_key)
52
52
 
53
53
  if _scenario_key:
54
54
  scenario_model = ScenarioModel(new_settings)
55
55
 
56
- # If policy is overwrite when recording, whenever intercept is disabled,
56
+ # If order is overwrite when recording, whenever intercept is disabled,
57
57
  # set active scenario to not be overwritable
58
58
  scenario_model.update(_scenario_key.id, **{ 'overwritable': False })[1]
59
59
  elif _mode == intercept_mode.MOCK:
@@ -73,7 +73,7 @@ def handle_scenario_update(new_settings: Settings, context = None):
73
73
  if old_scenario_key != new_scenario_key:
74
74
  data_rule = __data_rule(new_settings.proxy)
75
75
 
76
- if data_rule.record_policy == record_policy.OVERWRITE:
76
+ if data_rule.record_order == record_order.OVERWRITE:
77
77
  scenario_model = ScenarioModel(new_settings)
78
78
 
79
79
  _old_scenario_key = __parse_scenario_key(old_scenario_key)
@@ -108,7 +108,7 @@ def handle_project_update(new_settings: Settings, context: Context = None):
108
108
  if _project_key.id != _new_scenario_key.project_id:
109
109
  data_rule.scenario_key = None
110
110
 
111
- def handle_policy_update(new_settings: Settings, context: Context = None):
111
+ def handle_order_update(new_settings: Settings, context: Context = None):
112
112
  data_rule = __data_rule(new_settings.proxy)
113
113
  _mode = context['mode'] if context and context.get('mode') else new_settings.proxy.intercept.mode
114
114
 
@@ -116,10 +116,10 @@ def handle_policy_update(new_settings: Settings, context: Context = None):
116
116
  old_data_rule = __data_rule(old_proxy_settings)
117
117
 
118
118
  if _mode == intercept_mode.RECORD:
119
- new_policy = data_rule.record_policy
120
- old_policy = old_data_rule.record_policy
119
+ new_order = data_rule.record_order
120
+ old_order = old_data_rule.record_order
121
121
 
122
- if new_policy != old_policy and old_policy == record_policy.OVERWRITE:
122
+ if new_order != old_order and old_order == record_order.OVERWRITE:
123
123
  scenario_key = data_rule.scenario_key
124
124
  _scenario_key = __parse_scenario_key(scenario_key)
125
125
 
@@ -3,10 +3,10 @@ import pdb
3
3
  import sys
4
4
 
5
5
  from stoobly_agent.app.settings import Settings
6
- from stoobly_agent.config.constants import mode, mock_policy, record_policy, replay_policy
6
+ from stoobly_agent.config.constants import mode, mock_policy, record_order, record_policy, replay_policy
7
7
  from stoobly_agent.lib.api.keys.project_key import ProjectKey
8
8
 
9
- from .helpers.handle_config_update_service import handle_intercept_active_update, handle_policy_update
9
+ from .helpers.handle_config_update_service import handle_intercept_active_update, handle_order_update
10
10
 
11
11
  settings = Settings.instance()
12
12
 
@@ -17,16 +17,25 @@ if settings.cli.features.remote:
17
17
 
18
18
  active_mode = settings.proxy.intercept.mode
19
19
 
20
+ def __get_order_options(active_mode):
21
+ if active_mode == mode.RECORD:
22
+ return [record_order.APPEND, record_order.OVERWRITE]
23
+ else:
24
+ return []
25
+
20
26
  def __get_policy_options(active_mode):
21
27
  if active_mode == mode.MOCK:
22
28
  return [mock_policy.ALL, mock_policy.FOUND]
23
29
  elif active_mode == mode.RECORD:
24
- return [record_policy.ALL, record_policy.FOUND, record_policy.NOT_FOUND,record_policy.OVERWRITE]
30
+ return [record_policy.ALL, record_policy.API, record_policy.FOUND, record_policy.NOT_FOUND]
25
31
  elif active_mode == mode.REPLAY:
26
32
  return [replay_policy.ALL]
27
33
  elif active_mode == mode.TEST:
28
34
  return [mock_policy.FOUND]
35
+ else:
36
+ return []
29
37
 
38
+ order_options = __get_order_options(active_mode)
30
39
  policy_options = __get_policy_options(active_mode)
31
40
 
32
41
  @click.group(
@@ -69,11 +78,12 @@ def disable(**kwargs):
69
78
  help="Configure intercept"
70
79
  )
71
80
  @click.option('--mode', type=click.Choice(mode_options))
81
+ @click.option('--order', type=click.Choice(order_options))
72
82
  @click.option('--policy', type=click.Choice(policy_options))
73
83
  def configure(**kwargs):
74
- settings = Settings.instance()
84
+ settings: Settings = Settings.instance()
75
85
 
76
- if not kwargs['mode'] and not kwargs['policy']:
86
+ if not kwargs['mode'] and not kwargs['order'] and not kwargs['policy']:
77
87
  print("Error: Missing an option")
78
88
  sys.exit(1)
79
89
 
@@ -89,6 +99,21 @@ def configure(**kwargs):
89
99
 
90
100
  _mode = kwargs['mode'] or settings.proxy.intercept.mode
91
101
 
102
+ if kwargs['order']:
103
+ active_mode = settings.proxy.intercept.mode
104
+
105
+ if active_mode == mode.RECORD:
106
+ project_key = ProjectKey(settings.proxy.intercept.project_key)
107
+ data_rule = settings.proxy.data.data_rules(project_key.id)
108
+ data_rule.record_order = kwargs['order']
109
+ else:
110
+ print("Error: set --mode to a intercept mode that supports the order option", file=sys.stderr)
111
+ sys.exit(1)
112
+
113
+ handle_order_update(settings)
114
+
115
+ print(f"Updating {_mode} order to {kwargs['order']}")
116
+
92
117
  if kwargs['policy']:
93
118
  active_mode = settings.proxy.intercept.mode
94
119
  valid_policies = __get_policy_options(active_mode)
@@ -109,8 +134,6 @@ def configure(**kwargs):
109
134
  elif active_mode == mode.TEST:
110
135
  data_rule.test_policy = kwargs['policy']
111
136
 
112
- handle_policy_update(settings)
113
-
114
137
  print(f"Updating {_mode} policy to {kwargs['policy']}")
115
138
 
116
139
  settings.commit()
@@ -1,4 +1,4 @@
1
- FROM stoobly/agent:1.8
1
+ FROM stoobly/agent:1.9
2
2
 
3
3
  ARG USER_ID
4
4
 
@@ -143,6 +143,12 @@ scenario/list:
143
143
  @export EXEC_COMMAND=.list && \
144
144
  export EXEC_OPTIONS="$(options)" && \
145
145
  $(stoobly_exec)
146
+ scenario/overwrite:
147
+ # Overwrite a scenario
148
+ @export EXEC_COMMAND=.overwrite && \
149
+ export EXEC_OPTIONS="$(options)" && \
150
+ export EXEC_ARGS="$(key)" && \
151
+ $(stoobly_exec)
146
152
  scenario/reset:
147
153
  # Resets a scenario to its last snapshot
148
154
  @export EXEC_COMMAND=.reset && \:
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ scenario_key=$1
4
+
5
+ if [ ! "$scenario_key" ]; then
6
+ echo "Missing argument 'scenario_key'"
7
+ stoobly-agent scenario list
8
+ exit
9
+ fi
10
+
11
+ stoobly-agent config scenario set "$scenario_key"
12
+ stoobly-agent intercept configure --mode record --order overwrite
13
+ stoobly-agent intercept disable
@@ -22,6 +22,14 @@ stoobly-agent config firewall set \
22
22
 
23
23
  # Rewrite
24
24
  echo "Configuring rewrite rules..."
25
+ stoobly-agent config rewrite set \
26
+ --method GET --method POST --method OPTIONS --method PUT --method DELETE \
27
+ --mode record \
28
+ --name authorization \
29
+ --pattern "$origin/?.*?" \
30
+ --type Header \
31
+ --value '' \
32
+
25
33
  stoobly-agent config rewrite set \
26
34
  --method GET --method POST --method OPTIONS --method PUT --method DELETE \
27
35
  --mode record \
@@ -29,3 +37,11 @@ stoobly-agent config rewrite set \
29
37
  --pattern "$origin/?.*?" \
30
38
  --type Header \
31
39
  --value '' \
40
+
41
+ stoobly-agent config rewrite set \
42
+ --method GET --method POST --method OPTIONS --method PUT --method DELETE \
43
+ --mode record \
44
+ --name set-cookie \
45
+ --pattern "$origin/?.*?" \
46
+ --type 'Response Header' \
47
+ --value '' \
@@ -20,6 +20,7 @@ from stoobly_agent.lib.api.keys import RequestKey, ScenarioKey
20
20
 
21
21
  from .helpers.print_service import FORMATS, print_snapshots, select_print_options
22
22
  from .helpers.verify_raw_request_service import verify_raw_request
23
+ from .types.snapshot_migration import SnapshotMigration
23
24
 
24
25
  @click.group(
25
26
  epilog="Run 'stoobly-agent project COMMAND --help' for more information on a command.",
@@ -91,6 +92,62 @@ def _list(**kwargs):
91
92
  if len(formatted_events):
92
93
  print_snapshots(formatted_events, **print_options)
93
94
 
95
+ @snapshot.command(
96
+ help="Migrate snapshots."
97
+ )
98
+ @click.option('--scenario-key', help='Apply migration to specific scenario.')
99
+ @click.argument('lifecycle_hooks_path')
100
+ def migrate(**kwargs):
101
+ from runpy import run_path
102
+ from stoobly_agent.config.constants.lifecycle_hooks import BEFORE_MIGRATE
103
+
104
+ scenario_key = None
105
+ if kwargs['scenario_key']:
106
+ scenario_key = ScenarioKey(kwargs['scenario_key'])
107
+
108
+ try:
109
+ lifecycle_hooks = run_path(kwargs['lifecycle_hooks_path'])
110
+ except Exception as e:
111
+ lifecycle_hooks = {}
112
+
113
+ before_migrate_hook = None
114
+ if BEFORE_MIGRATE not in lifecycle_hooks:
115
+ sys.exit(1)
116
+ else:
117
+ before_migrate_hook = lifecycle_hooks[BEFORE_MIGRATE]
118
+
119
+ log = Log()
120
+
121
+ request_snapshots = []
122
+ for event in log.resource_events:
123
+ if event.is_scenario():
124
+ # If scenario_key is set, only consider that scenario
125
+ if scenario_key and event.resource_uuid != scenario_key.id:
126
+ continue
127
+
128
+ scenario_snapshot: ScenarioSnapshot = event.snapshot()
129
+ _request_snapshots = scenario_snapshot.request_snapshots
130
+
131
+ for request_snapshot in _request_snapshots:
132
+ # Scenario requests will have the same log event
133
+ request_snapshots.append((request_snapshot, event))
134
+ elif event.is_request():
135
+ # If scenario_key is set, only consider that scenario
136
+ if scenario_key:
137
+ continue
138
+
139
+ request_snapshot: RequestSnapshot = event.snapshot()
140
+ request_snapshots.append((request_snapshot, event))
141
+
142
+ snapshot_migrations = {}
143
+ for request_snapshot, event in request_snapshots:
144
+ if request_snapshot.uuid not in snapshot_migrations:
145
+ snapshot_migration = SnapshotMigration(request_snapshot, event)
146
+ snapshot_migrations[request_snapshot.uuid] = snapshot_migration
147
+
148
+ if before_migrate_hook(snapshot_migration):
149
+ break
150
+
94
151
  @snapshot.command(
95
152
  help="Prune deleted snapshots."
96
153
  )
@@ -0,0 +1,61 @@
1
+ from mitmproxy.http import Request, Response
2
+
3
+ from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
4
+ from stoobly_agent.app.models.factories.resource.local_db.helpers.log_event import LogEvent
5
+ from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
6
+ from stoobly_agent.app.proxy.record.join_request_service import join_request_from_request_response
7
+
8
+ class SnapshotMigration():
9
+ _request: Request
10
+ _response: Response
11
+ _snapshot: RequestSnapshot
12
+
13
+ def __init__(self, snapshot: RequestSnapshot, log_event: LogEvent, log: Log = None):
14
+ self._event = log_event
15
+ self._log = log or Log()
16
+ self._request = snapshot.mitmproxy_request
17
+ self._response = snapshot.mitmproxy_response
18
+ self._snapshot = snapshot
19
+
20
+ @property
21
+ def request(self):
22
+ return self._request
23
+
24
+ @request.setter
25
+ def request(self, v: Request):
26
+ if not isinstance(v, Request):
27
+ raise TypeError('Invalid type.')
28
+ self._request = v
29
+
30
+ @property
31
+ def response(self):
32
+ return self._response
33
+
34
+ @response.setter
35
+ def response(self, v: Response):
36
+ if not isinstance(v, Response):
37
+ raise TypeError('Invalid type.')
38
+ self._response = v
39
+
40
+ @property
41
+ def snapshot(self):
42
+ return self._snapshot
43
+
44
+ @property
45
+ def uuid(self):
46
+ return self.snapshot.uuid
47
+
48
+ def delete(self, log: Log = None):
49
+ log = log or self._log
50
+ new_event = self._event.duplicate_as_delete()
51
+ log.append(str(new_event))
52
+
53
+ def save(self, log: Log = None):
54
+ log = log or self._log
55
+ request_uuid = self.snapshot.uuid
56
+ joined_request = join_request_from_request_response(self.request, self.response, id=request_uuid)
57
+ raw_request = joined_request.build()
58
+ self.snapshot.write_raw(raw_request)
59
+ new_event = self._event.duplicate()
60
+ log.append(str(new_event))
61
+ return new_event
@@ -46,7 +46,7 @@ class JoinedRequestAdapter():
46
46
  def build_request_string(self):
47
47
  request_string = RequestString(None)
48
48
 
49
- delimitter = RequestStringCLRF.encode()
49
+ delimitter = RequestStringCLRF
50
50
  request_string_toks = self.__split_joined_request_string[0].split(delimitter)
51
51
  request_string.set(self.raw_request_string or delimitter.join(request_string_toks[1:]))
52
52
  request_string.control = request_string_toks[0]
@@ -15,4 +15,4 @@ class MitmproxyRequestAdapter():
15
15
  protocol = raw_http_request_adapter.protocol
16
16
  http_version = protocol.split('/')[1]
17
17
 
18
- return PythonRequestMitmproxyRequestAdapter(http_version, python_request).adapt()
18
+ return PythonRequestMitmproxyRequestAdapter(python_request, http_version).adapt()
@@ -13,10 +13,10 @@ class PythonRequestAdapterFactory():
13
13
  self.__request = request
14
14
 
15
15
  def mitmproxy_request(self, http_version: str = 'HTTP/1.1'):
16
- return MitmproxyRequestAdapter(http_version, self.__request).adapt()
16
+ return MitmproxyRequestAdapter(self.__request, http_version).adapt()
17
17
 
18
18
  def raw_request(self, http_version: str = 'HTTP/1.1'):
19
- return RawRequestAdapter(http_version, self.__request).adapt()
19
+ return RawRequestAdapter(self.__request, http_version).adapt()
20
20
 
21
21
  def stoobly_request(self):
22
22
  return StooblyRequestAdapter(self.__request).adapt()
@@ -8,7 +8,7 @@ from stoobly_agent.lib.utils.decode import decode
8
8
 
9
9
  class MitmproxyRequestAdapter():
10
10
 
11
- def __init__(self, http_version: str, request: requests.Request):
11
+ def __init__(self, request: requests.Request, http_version: str = 'HTTP/1.1'):
12
12
  self.__http_version = http_version
13
13
  self.__request = request
14
14
 
@@ -9,12 +9,12 @@ from .mitmproxy_adapter import MitmproxyRequestAdapter
9
9
 
10
10
  class RawRequestAdapter():
11
11
 
12
- def __init__(self, http_version: str, request: requests.Request):
12
+ def __init__(self, request: requests.Request, http_version: str):
13
13
  self.__http_version = http_version
14
14
  self.__request = request
15
15
 
16
16
  def adapt(self):
17
- mitmproxy_request = MitmproxyRequestAdapter(self.__http_version, self.__request).adapt()
17
+ mitmproxy_request = MitmproxyRequestAdapter(self.__request, self.__http_version).adapt()
18
18
  adapted_request = MitmproxyRequestFacade(mitmproxy_request)
19
19
  proxy_request = ProxyRequest(adapted_request)
20
20
  request_string = RequestString(proxy_request)
@@ -82,6 +82,13 @@ class Log():
82
82
 
83
83
  return self.remove_processed_events(events, version_uuids)
84
84
 
85
+ @property
86
+ def resource_events(self) -> List[LogEvent]:
87
+ events = self.target_events
88
+ events = self.remove_dangling_events(events, events)
89
+
90
+ return list(filter(lambda e: e.action != DELETE_ACTION, events))
91
+
85
92
  @property
86
93
  def version(self):
87
94
  version_file_path = DataDir.instance().snapshosts_version_path
@@ -84,6 +84,16 @@ class LogEvent():
84
84
  toks[4] = str(int(time.time() * 1000))
85
85
  return LogEvent(COLUMN_DELIMITTER.join(toks))
86
86
 
87
+ def duplicate_as_delete(self):
88
+ event = self.duplicate()
89
+ event.action = DELETE_ACTION
90
+ return event
91
+
92
+ def duplicate_as_put(self):
93
+ event = self.duplicate()
94
+ event.action = PUT_ACTION
95
+ return event
96
+
87
97
  def is_request(self):
88
98
  return self.resource == REQUEST_RESOURCE
89
99
 
@@ -34,6 +34,26 @@ class RequestSnapshot(Snapshot):
34
34
  with open(request_file_path, 'rb') as fp:
35
35
  return fp.read()
36
36
 
37
+ @property
38
+ def mitmproxy_request(self):
39
+ from stoobly_agent.app.models.adapters.python.request.mitmproxy_adapter import MitmproxyRequestAdapter
40
+ return MitmproxyRequestAdapter(self.python_request).adapt()
41
+
42
+ @property
43
+ def mitmproxy_response(self):
44
+ from stoobly_agent.app.models.adapters.python.response.mitmproxy_adapter import MitmproxyResponseAdapter
45
+ return MitmproxyResponseAdapter(self.python_response).adapt()
46
+
47
+ @property
48
+ def python_request(self):
49
+ from stoobly_agent.app.models.adapters.raw_joined.request.python_adapter import PythonRequestAdapter
50
+ return PythonRequestAdapter(self.request).adapt()
51
+
52
+ @property
53
+ def python_response(self):
54
+ from stoobly_agent.app.models.adapters.raw_joined.response.python_adapter import PythonResponseAdapter
55
+ return PythonResponseAdapter(self.request).adapt()
56
+
37
57
  @property
38
58
  def path(self):
39
59
  dir_path = os.path.join(self.__requests_dir_path, self.uuid[0:2])
@@ -3,7 +3,7 @@ import os
3
3
  import pdb
4
4
  import shutil
5
5
 
6
- from typing import Callable
6
+ from typing import Callable, List
7
7
 
8
8
  from stoobly_agent.lib.orm.scenario import Scenario
9
9
 
@@ -46,6 +46,13 @@ class ScenarioSnapshot(Snapshot):
46
46
  def metadata_path(self):
47
47
  return os.path.join(self.__scenarios_dir_path, self.uuid)
48
48
 
49
+ @property
50
+ def request_snapshots(self) -> List[RequestSnapshot]:
51
+ snapshots = []
52
+ handler = lambda request_snapshot: snapshots.append(request_snapshot)
53
+ self.iter_request_snapshots(handler)
54
+ return snapshots
55
+
49
56
  @property
50
57
  def requests(self):
51
58
  requests_file_path = self.requests_path
@@ -7,7 +7,7 @@ class Snapshot():
7
7
 
8
8
  def __init__(self, uuid: str):
9
9
  self.__uuid = uuid
10
- self.__data_dir = DataDir.instance()
10
+ self.__data_dir: DataDir = DataDir.instance()
11
11
 
12
12
  @property
13
13
  def uuid(self):
@@ -122,10 +122,10 @@ class LocalDBQueryParamAdapter(LocalDBAdapter):
122
122
  parsed_url = parsed_url._replace(query=urlencode(_query_params, True))
123
123
  request = LocalDBRequestAdapter(self.__request_orm).update(request_id, url=parsed_url.geturl())
124
124
 
125
- return {
125
+ return self.success({
126
126
  'name': name,
127
127
  'value': value,
128
- }
128
+ })
129
129
 
130
130
  def __decode_id(self, id: str):
131
131
  id = base64.b64decode(id)
@@ -3,7 +3,7 @@ import pdb
3
3
  from stoobly_agent.app.models.factories.resource.local_db.helpers.log import Log
4
4
  from stoobly_agent.app.models.factories.resource.local_db.helpers.request_snapshot import RequestSnapshot
5
5
  from stoobly_agent.app.models.factories.resource.local_db.helpers.scenario_snapshot import ScenarioSnapshot
6
- from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF, RequestStringControl
6
+ from stoobly_agent.app.proxy.record import REQUEST_STRING_CLRF
7
7
  from stoobly_agent.app.settings import Settings
8
8
  from stoobly_agent.lib.logger import bcolors
9
9
 
@@ -218,15 +218,16 @@ class Apply():
218
218
 
219
219
  snapshot_requests = {}
220
220
 
221
- raw_requests = snapshot.requests
222
- for raw_request in raw_requests:
223
- toks = raw_request.split(REQUEST_STRING_CLRF.encode(), 1)
221
+ request_snapshots = snapshot.request_snapshots
222
+ for request_snapshot in request_snapshots:
223
+ raw_request = request_snapshot.request
224
+
225
+ toks = raw_request.split(REQUEST_STRING_CLRF, 1)
224
226
 
225
227
  if len(toks) != 2:
226
228
  return f"{snapshot.requests_path} contains an invalid request", 400
227
229
 
228
- control = RequestStringControl(toks[0])
229
- uuid = control.id
230
+ uuid = request_snapshot.uuid
230
231
  res, status = self.__put_request(uuid, raw_request, scenario_id=scenario['id'])
231
232
 
232
233
  if status != 200:
@@ -4,7 +4,7 @@ import requests
4
4
  from stoobly_agent.app.models.adapters import JoinedRequestAdapter, RawHttpRequestAdapter, RawHttpResponseAdapter
5
5
  from stoobly_agent.app.models.adapters.python import PythonRequestAdapterFactory, PythonResponseAdapterFactory
6
6
 
7
- from stoobly_agent.app.proxy.record.join_request_service import InterceptSettings, join_request, MitmproxyRequestFacade, MitmproxyResponseFacade
7
+ from stoobly_agent.app.proxy.record.join_request_service import InterceptSettings, join_request_from_request_response
8
8
  from stoobly_agent.app.settings import Settings
9
9
  from stoobly_agent.lib.logger import Logger
10
10
 
@@ -40,7 +40,9 @@ def build_params_from_python(request: requests.Request, response: requests.Respo
40
40
  mitmproxy_response = PythonResponseAdapterFactory(response).mitmproxy_response()
41
41
 
42
42
  intercept_settings = InterceptSettings(Settings.instance(), mitmproxy_request)
43
- joined_request = join_request(MitmproxyRequestFacade(mitmproxy_request), MitmproxyResponseFacade(mitmproxy_response), intercept_settings)
43
+ joined_request = join_request_from_request_response(
44
+ mitmproxy_request, mitmproxy_response, intercept_settings=intercept_settings
45
+ )
44
46
 
45
47
  mitmproxy_flow_mock = MitmproxyFlowMock(mitmproxy_request, mitmproxy_response)
46
48