Skip to content

Commit 37f05c2

Browse files
Merge pull request #657 from ColonelThirtyTwo/oplog-sanitize-fix
Fix oplog sanitize, especially with large oplogs
2 parents 7cabbb1 + c2c2168 commit 37f05c2

2 files changed

Lines changed: 53 additions & 57 deletions

File tree

ghostwriter/oplog/tests/test_views.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ def test_view_with_invalid_fields(self):
724724
**{"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"},
725725
)
726726
self.assertEqual(response.status_code, 200)
727-
self.assertIn("One of the fields submitted for sanitization does not exist", force_str(response.content))
727+
self.assertIn("No fields selected for sanitization", force_str(response.content))
728728

729729
def test_view_with_empty_fields(self):
730730
data = {
@@ -769,6 +769,6 @@ def test_field_sanitization_with_extra_field(self):
769769
self.assertEqual(response.status_code, 200)
770770
self.assertJSONEqual(force_str(response.content), data)
771771
self.entry.refresh_from_db()
772-
self.assertEqual(self.entry.user_context, None)
772+
self.assertEqual(self.entry.user_context, "")
773773
self.assertEqual(self.entry.command, "some")
774-
self.assertEqual(self.entry.extra_fields, {"test_field": None, "test_field_2": "test value"})
774+
self.assertEqual(self.entry.extra_fields, {"test_field": "", "test_field_2": "test value"})

ghostwriter/oplog/views.py

Lines changed: 50 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
# Django Imports
1010
from django.contrib import messages
1111
from django.contrib.auth.decorators import login_required
12-
from django.core.exceptions import FieldDoesNotExist
1312
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
1413
from django.shortcuts import get_object_or_404, redirect, render
1514
from django.urls import reverse
@@ -36,7 +35,6 @@
3635
# Using __name__ resolves to ghostwriter.oplog.views
3736
logger = logging.getLogger(__name__)
3837

39-
4038
def escape_message(message):
4139
"""
4240
Escape single quotes, double quotes, newlines and other characters
@@ -109,30 +107,6 @@ def post(self, *args, **kwargs):
109107

110108
return JsonResponse(data)
111109

112-
113-
def parse_fields(data: dict, entry_field_specs: dict) -> tuple:
114-
"""
115-
Parse a dictionary of fields and return a list of fields and extra fields.
116-
117-
**Parameters**
118-
119-
``data`` (dict)
120-
Dictionary of fields
121-
``entry_field_specs`` (dict)
122-
Dictionary of field specifications
123-
"""
124-
fields = [field["name"] for field in data]
125-
extra_fields = []
126-
# Remove any extra fields from the list of fields
127-
for field_spec in entry_field_specs:
128-
if field_spec["internal_name"] in fields:
129-
fields.pop(fields.index(field_spec["internal_name"]))
130-
extra_fields.append(field_spec["internal_name"])
131-
if extra_fields:
132-
fields.append("extra_fields")
133-
return fields, extra_fields
134-
135-
136110
class OplogSanitize(RoleBasedAccessControlMixin, SingleObjectMixin, View):
137111
"""
138112
Sanitize all :model:`oplog.OplogEntry` objects associated with an individual :model:`oplog.Oplog`.
@@ -144,6 +118,20 @@ class OplogSanitize(RoleBasedAccessControlMixin, SingleObjectMixin, View):
144118

145119
model = Oplog
146120

121+
clearable_fields = {
122+
"identifier",
123+
"start_date",
124+
"end_date",
125+
"source_ip",
126+
"dest_ip",
127+
"tool",
128+
"user_context",
129+
"description",
130+
"output",
131+
"comments",
132+
"operator_name",
133+
}
134+
147135
def test_func(self):
148136
return verify_user_is_privileged(self.request.user)
149137

@@ -155,17 +143,32 @@ def post(self, *args, **kwargs):
155143
obj = self.get_object()
156144
data = self.request.POST.get("fields", None)
157145
try:
158-
json_data = json.loads(data)
146+
fields_json = json.loads(data)
159147
except (json.JSONDecodeError, TypeError):
160-
json_data = None
161-
162-
if json_data and len(json_data) > 0:
148+
fields_json = []
149+
150+
entry_field_specs = {spec.internal_name: spec for spec in ExtraFieldSpec.for_model(OplogEntry)}
151+
fields = [
152+
field["name"]
153+
for field in fields_json
154+
if field["name"] == "command"
155+
or field["name"] == "tags"
156+
or field["name"] in self.clearable_fields
157+
or field["name"] in entry_field_specs
158+
]
159+
160+
bulk_update_fields = [
161+
field["name"]
162+
for field in fields_json
163+
if field["name"] == "command"
164+
or field["name"] == "tags"
165+
or field["name"] in self.clearable_fields
166+
]
167+
if any(field["name"] in entry_field_specs for field in fields_json):
168+
bulk_update_fields.append("extra_fields")
169+
170+
if fields:
163171
entries = obj.entries.all()
164-
entry_field_specs = ExtraFieldsSpecSerializer(
165-
ExtraFieldSpec.objects.filter(target_model=OplogEntry._meta.label), many=True
166-
).data
167-
fields, _ = parse_fields(json_data, entry_field_specs)
168-
169172
logger.info(
170173
"Sanitizing log entries for %s %s by request of %s", obj.__class__.__name__, obj.id, self.request.user
171174
)
@@ -176,29 +179,22 @@ def post(self, *args, **kwargs):
176179
try:
177180
for entry in entries:
178181
extra_fields_data = entry.extra_fields
179-
for field in json_data:
180-
for field_spec in entry_field_specs:
181-
if field_spec["internal_name"] == field["name"]:
182-
extra_fields_data[field["name"]] = None
183-
else:
184-
if field["name"] == "command":
185-
if entry.command:
186-
setattr(entry, field["name"], entry.command.split(" ")[0])
187-
else:
188-
setattr(entry, field["name"], None)
182+
for field in fields:
183+
if field == "command":
184+
if entry.command:
185+
setattr(entry, field, entry.command.split(" ")[0])
186+
elif field == "tags":
187+
entry.tags.clear()
188+
elif field in self.clearable_fields:
189+
setattr(entry, field, "")
190+
elif field in entry_field_specs:
191+
extra_fields_data[field] = entry_field_specs[field].empty_value()
189192
entry.extra_fields = extra_fields_data
190-
try:
191-
OplogEntry.objects.bulk_update(entries, fields)
192-
except FieldDoesNotExist as exception:
193-
logger.error("One of the fields submitted for sanitization does not exist: %s", exception)
194-
data = {
195-
"result": "failed",
196-
"message": "One of the fields submitted for sanitization does not exist.",
197-
}
193+
OplogEntry.objects.bulk_update(entries, bulk_update_fields, batch_size=100)
198194
except Exception as exception: # pragma: no cover
199195
template = "An exception of type {0} occurred. Arguments:\n{1!r}"
200196
log_message = template.format(type(exception).__name__, exception.args)
201-
logger.error(log_message)
197+
logger.exception(log_message)
202198
data = {
203199
"result": "failed",
204200
"message": "An error occurred while sanitizing log entries.",

0 commit comments

Comments
 (0)