Errors for invalid ws msgs instead of silent drop

This commit is contained in:
Michael Wedl 2024-04-29 08:12:02 +02:00
parent b667704aca
commit 6f75e008b8
2 changed files with 26 additions and 21 deletions

View File

@ -84,8 +84,14 @@ class WebsocketConsumerBase(AsyncJsonWebsocketConsumer):
await self.close(code=4443)
return
with history_context(history_user=self.scope.get('user')):
return await super().websocket_receive(message)
try:
with history_context(history_user=self.scope.get('user')):
return await super().websocket_receive(message)
except ValidationError as ex:
await self.send_json({
'type': 'error',
'message': ex.message,
})
async def websocket_disconnect(self, message):
try:
@ -288,7 +294,7 @@ class NotesConsumerBase(WebsocketConsumerBase):
.select_for_update(of=['self'], no_key=True) \
.first()
if not note:
return None, None
raise ValidationError('Invalid path: ID not found')
return note, path_parts[2]
@database_sync_to_async
@ -297,8 +303,6 @@ class NotesConsumerBase(WebsocketConsumerBase):
# Validate path and get note
valid_paths = {k for k, f in self.get_serializer().fields.items() if not f.read_only} - {'title', 'text'}
note, key = self.get_note_for_update(path=content.get('path'), valid_paths=valid_paths)
if not note:
return None
# Update in DB
serializer = self.get_serializer(instance=note, data={key: content.get('value')}, partial=True)
@ -325,8 +329,6 @@ class NotesConsumerBase(WebsocketConsumerBase):
if not content.get('updates', []):
raise ValidationError('No updates')
note, key = self.get_note_for_update(path=content.get('path'), valid_paths=['title', 'text'])
if not note:
return None
version = content['version']
# TODO: reject updates for versions that are too old
@ -349,7 +351,7 @@ class NotesConsumerBase(WebsocketConsumerBase):
for u in e.data.get('updates', [])] for e in over_updates])),
)
if not updates:
return None
raise ValidationError('No updates')
# Update in DB
changes = updates[0].changes
@ -585,7 +587,7 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
.select_for_update(of=['self'], no_key=True) \
.first()
if not obj:
return None, None, None
raise ValidationError('Invalid path: ID not found')
# Validate path in top-level or in field definition
if path_parts[2] == 'data':
@ -605,8 +607,8 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
def collab_update_key(self, content):
# Validate path and get section/finding
obj, path, definition = self.get_object_for_update(content.get('path'))
if not obj or (definition and definition.type in [FieldDataType.MARKDOWN, FieldDataType.STRING]):
return None
if definition and definition.type in [FieldDataType.MARKDOWN, FieldDataType.STRING]:
raise ValidationError('collab.update_key is not supported for text fields. Use collab.update_text instead.')
# Update data in DB
if definition:
@ -641,8 +643,8 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
@transaction.atomic()
def collab_update_text(self, content):
obj, path, definition = self.get_object_for_update(content.get('path'))
if not obj or not definition or definition.type not in [FieldDataType.MARKDOWN, FieldDataType.STRING]:
return None
if not definition or definition.type not in [FieldDataType.MARKDOWN, FieldDataType.STRING]:
raise ValidationError('collab.update_text is not supported for non-text fields. Use collab.update_key instead.')
version = content['version']
# TODO: reject updates for versions that are too old
@ -662,7 +664,7 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
for u in e.data.get('updates', [])] for e in over_updates])),
)
if not updates:
return None
raise ValidationError('No updates')
# Update in DB
changes = updates[0].changes
@ -697,8 +699,8 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
@transaction.atomic()
def collab_create(self, content):
obj, path, definition = self.get_object_for_update(content.get('path'))
if not obj or not definition or definition.type != FieldDataType.LIST:
return None
if not definition or definition.type != FieldDataType.LIST:
raise ValidationError('collab.create is only supported for list fields')
# Update DB
updated_data = obj.data
@ -726,16 +728,16 @@ class ProjectReportingConsumer(WebsocketConsumerBase):
@transaction.atomic()
def collab_delete(self, content):
obj, path, definition = self.get_object_for_update(content.get('path'))
if not obj or not definition:
return None
if not definition:
raise ValidationError('collab.delete is only supported for fields')
updated_data = obj.data
lst = get_value_at_path(updated_data, path[1:-1])
if not isinstance(lst, list):
return None
raise ValidationError('collab.delete is only supported for fields')
index = int(path[-1][1:-1] if path[-1].startswith('[') and path[-1].endswith(']') else path[-1])
if not (0 <= index < len(lst)):
return None
raise ValidationError('Invalid list index')
lst.pop(index)
serializer = (ReportSectionSerializer if isinstance(obj, ReportSection) else PentestFindingSerializer)(instance=obj, data={'data': updated_data}, partial=True)
serializer.is_valid(raise_exception=True)

View File

@ -130,7 +130,7 @@ export function useCollab<T = any>(storeState: CollabStoreState<T>) {
})
storeState.websocket.addEventListener('close', (event) => {
// Error handling
if (event.code === 4443 || (event.code === 1006 && storeState.connectionState === CollabConnectionState.CONNECTING)) {
if (event.code === 4443) {
storeState.connectionError = { error: event, message: event.reason || 'Permission denied' };
} else if (storeState.connectionState === CollabConnectionState.CONNECTING) {
storeState.connectionError = { error: event, message: event.reason || 'Failed to establish connection' };
@ -232,6 +232,9 @@ export function useCollab<T = any>(storeState: CollabStoreState<T>) {
}),
};
}
} else if (msgData.type === 'error') {
// eslint-disable-next-line no-console
console.error('Received error from websocket:', msgData);
} else if (msgData.type === 'ping') {
// Do nothing
} else {