Live To Server

How To Use Ckeditor In Django Admin With Django Csp

You've just installed Django CKEditor in the admin panel of your site, and because you are security conscious, you've also added Django CSP to your project to mitigate any potential injection attacks. Excellent, until you try to add content to your new site because now CKEditor is completely broken. What happened? Unfortunately, CKEditor uses inline Javascript and CSS to run, and Django CSP by default disables these inline features, features we don't want to enable unless we have to. Fortunately, while we do have to enable this functionality for part of the admin section, we can easily limit where it is activated allowing our public-facing site to be fully secured.

The key lies in utilizing decorators offered by Django CSP that allow us to modify and outright replace the content security policy headers on a per-view basis. For a normal view, we simply decorate the view function, giving the decorator the arguments relating to the specific headers we wish to change, which in our case would be the SCRIPT_SRC and STYLE_SRC parameters. Each of these parameters takes a tuple containing the headers we wish to change or add.  For this example, we will use the csp_update  decorator to add the 'use-inline' to allow for inline Javascript and CSS to run, and the https: header to limit the effect to secure sources.  Note: Make sure to include the single quotes around use-inline within the text.

from cps.decorators import csp_update

@csp_update(SCRIPT_SRC=("'use-inline'","https:"),STYLE_SRC=("'use-inline'","https:"))
def my_view(response):
   #view code here

This, of course, is only the first piece to solving our puzzle.  As you can see, these decorators are designed to work predominantly with function-based views, but the admin section uses special object-oriented views. Additionally, it is not clear which portion of the admin view object we need to decorate.  Luckily, the Django team has provided a decorator to solve the first issue, and with a little bit of digging, we can solve the second. 

The decorator in question is the function method_decorator contained unsurprisingly within the django.utils.decorators package. This decorator allows us to apply a standard python decorator to a method of a class as though it was a function. To utilize it, we can decorate the class with method_decorator, and then pass the function returned from calling our decorator and the name method we wish to decorate.

from django.util.decorators import method_decorator
from my_stuff import my_decorator

@method_decorator(my_decorator(arg1,arg2),name='my_method')
class MyClass:

    def my_method(self):
        #do stuff here

The last piece of our puzzle we need to solve is to determine which method or methods to decorate in our admin view.  When utilizing a standard ModelAdmin class as our view, there are two methods that will be rendering our CKEditor instance: ModelAdmin.add_view and ModelAdmin.change_view, and these are the methods we wish to decorate.  As such, we will call method_decorator twice on our ModelAdmin instance, once for each method. The following is the complete code example:

from django.contrib import admin
from django.utils.decorators import method_decorator
from csp.decorators import csp_update
from my_app.models import MyModel

@method_decorator(csp_update(SCRIPT_SRC=("'use-inline'",'https:'),STYLE_SRC=("'use-inline'",'https:')),name='add_view')
@method_decorator(csp_update(SCRIPT_SRC=("'use-inline'",'https:'),STYLE_SRC=("'use-inline'",'https:')),name='change_view')
@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
    #your admin code here

With all of this put together, only these specific views in the admin sections will allow for the execution of inline Javascript and CSS allowing CKEditor to function, but the rest of our site will remain fully locked down.  

Tags: