diegojromerolopez/django-ws-include

View on GitHub
ws_include/consumers.py

Summary

Maintainability
A
1 hr
Test Coverage
import ast
import json

import jsonpickle
from channels.generic.websocket import WebsocketConsumer
from django.apps import apps
from django.conf import settings
from django.template.loader import render_to_string
from django.utils import translation

from . import checksum
from . import crypto


class TemplateGetter(WebsocketConsumer):
    def connect(self):
        self.accept()

    def receive(self, text_data: str = None, bytes_data=None):
        json_body = jsonpickle.loads(text_data)
        block_id: str = json_body.get('block_id')
        path: str = json_body.get('path')

        # Remote context
        # The caller has sent the model objects and
        # safe values (strings, numbers, etc.) as a dict with
        # the app_labels, model and id
        context = json_body.get('context')

        # language
        language_code = json_body.get('language_code')

        replacements = {}

        # For each remote context value, we load it again
        for context_object_name, context_object_load_params in json.loads(context).items():
            # Type of the value
            object_type = context_object_load_params['type']

            # If the value is a model, we load the model object and
            # include it in the template replacements
            if object_type == 'model':
                app_name = context_object_load_params['app_name']
                model_name = context_object_load_params['model']
                object_id = context_object_load_params['id']
                # Loading the model
                model = apps.get_model(app_name, model_name)
                # Loading the object and including it as a replacement
                model_object = model.objects.get(id=object_id)
                # Checking if JSON has been tampered
                model_object_as_str = '{0}-{1}-{2}'.format(
                    app_name, model_name, object_id
                )
                if (
                        context_object_load_params['__checksum__'] !=
                        checksum.make(model_object_as_str)
                ):
                    raise ValueError(
                        'JSON tampering detected when loading object'
                    )

                replacements[context_object_name] = model_object

            # If the value is a QuerySet we include it in
            # the template replacements
            elif object_type == 'QuerySet':
                # Loading the model
                app_name = context_object_load_params['app_name']
                model_name = context_object_load_params['model']
                model = apps.get_model(app_name, model_name)
                query_size = int(context_object_load_params['query_size'])
                nonce = context_object_load_params['nonce']
                tag = context_object_load_params['tag']

                try:
                    # Decryption of the data
                    raw_query_with_params = crypto.decrypt_from_base64_str(
                        key=settings.SECRET_KEY[:16],
                        nonce=nonce,
                        encrypted_data=context_object_load_params['query_with_params'],
                        tag=tag
                    )
                    raw_query = raw_query_with_params[:query_size]
                    params = raw_query_with_params[query_size:]
                    # Loading the object and including it as a replacement
                    replacements[context_object_name] = model.objects.raw(
                        raw_query, ast.literal_eval(params)
                    )
                except ValueError:
                    raise

            # If the value is a safe value,
            # we include it in the template replacements
            elif object_type == 'safe_value':
                value = context_object_load_params['value']
                value_as_str = context_object_load_params['value_as_str']
                # Checking if JSON has been tampered
                if (
                        context_object_load_params['__checksum__'] !=
                        checksum.make(value_as_str)
                ):
                    raise ValueError(
                        'JSON tampering detected when loading safe value ' +
                        'for attribute \'{0}\'. Value: \'{1}\''.format(
                            context_object_name, value_as_str
                        )
                    )

                # Including the safe value as a replacement
                replacements[context_object_name] = value

        # Activate the language
        translation.activate(language_code)

        # Response
        response = {
            'blockId': block_id,
            'html': render_to_string(path, replacements)
        }
        json_response = json.dumps(response)
        self.send(json_response)