Repository: django-cms/django-cms

Issue:
X_FRAME_OPTIONS settings parameter prevents plugin's ModelAdmin from model.
The Django settings parameter X_FRAME_OPTIONS(https://docs.djangoproject.com/en/dev/ref/clickjacking/#setting-x-frame-options-for-all-responses) which got introduced with 1.4 prevents the inline ModelAdmins of every plugin from loading.



Discussion:
Hi,

that's only true if you set `X_FRAME_OPTIONS` to `'DENY'`. The default is `SAMEORIGIN`, which according to my short test, does work.

The CMS admin requires iframes, there is no way around it. 

OK, on a second thought, it would probably make sense to decorate `PageAdmin.edit_plugin` and `PlaceholderAdmin.edit_plugin` with `xframe_options_sameorigin`, with a noop fallback for Django 1.3

That's just what I thought! It doesn't make much sense to me to deny the whole app the strict setting simply because of some admin views depending on a 'SAMEORIGIN' setting, especially with the nice decorators at hand.


Resolving patch:
diff --git a/cms/admin/pageadmin.py b/cms/admin/pageadmin.py
--- a/cms/admin/pageadmin.py
+++ b/cms/admin/pageadmin.py
@@ -2,6 +2,7 @@
 from copy import deepcopy
 from distutils.version import LooseVersion
 from urllib2 import unquote
+from django.views.decorators.clickjacking import xframe_options_sameorigin
 from cms.utils.conf import get_cms_setting
 from cms.utils.helpers import find_placeholder_relation
 
@@ -1094,6 +1095,7 @@ def descendants(self, request, page_id):
                                                   template="admin/cms/page/lazy_menu.html")
 
     @require_POST
+    @xframe_options_sameorigin
     @create_revision()
     def add_plugin(self, request):
         """
@@ -1197,6 +1199,7 @@ def copy_plugins(self, request):
         return render_to_response('admin/cms/page/widgets/plugin_item.html', {'plugin_list': plugin_list},
                                   RequestContext(request))
 
+    @xframe_options_sameorigin
     @create_revision()
     def edit_plugin(self, request, plugin_id):
         plugin_id = int(plugin_id)
@@ -1323,6 +1326,7 @@ def edit_plugin(self, request, plugin_id):
         return response
 
     @require_POST
+    @xframe_options_sameorigin
     @create_revision()
     def move_plugin(self, request):
         if 'history' in request.path:
@@ -1387,6 +1391,7 @@ def move_plugin(self, request):
         return HttpResponse(str("ok"))
 
     @require_POST
+    @xframe_options_sameorigin
     @create_revision()
     def remove_plugin(self, request):
         if 'history' in request.path:
diff --git a/cms/admin/placeholderadmin.py b/cms/admin/placeholderadmin.py
--- a/cms/admin/placeholderadmin.py
+++ b/cms/admin/placeholderadmin.py
@@ -1,4 +1,5 @@
 # -*- coding: utf-8 -*-
+from django.views.decorators.clickjacking import xframe_options_sameorigin
 from cms.exceptions import PluginLimitReached
 from cms.forms.fields import PlaceholderFormField
 from cms.models.fields import PlaceholderField
@@ -9,15 +10,12 @@
 from cms.utils.permissions import has_plugin_permission
 from cms.plugins.utils import has_reached_plugin_limit
 from copy import deepcopy
-from django.conf import settings
 from django.contrib.admin import ModelAdmin
-from django.http import (HttpResponse, Http404, HttpResponseBadRequest, 
-    HttpResponseForbidden)
+from django.http import HttpResponse, Http404, HttpResponseBadRequest, HttpResponseForbidden
 from django.shortcuts import render_to_response, get_object_or_404
 from django.template import RequestContext
 from django.template.defaultfilters import force_escape, escapejs
 from django.utils.translation import ugettext as _
-from cms.templatetags.cms_admin import admin_static_url
 
 
 class PlaceholderAdmin(ModelAdmin):
@@ -125,7 +123,8 @@ def get_urls(self):
             pat(r'copy-plugins/$', self.copy_plugins),            
         )
         return url_patterns + super(PlaceholderAdmin, self).get_urls()
-    
+
+    @xframe_options_sameorigin
     def add_plugin(self, request):
         # only allow POST
         if request.method != "POST":
@@ -164,7 +163,8 @@ def add_plugin(self, request):
         
         # returns it's ID as response
         return HttpResponse(str(plugin.pk))
-    
+
+    @xframe_options_sameorigin
     def edit_plugin(self, request, plugin_id):
         plugin_id = int(plugin_id)
         # get the plugin to edit of bail out
@@ -242,6 +242,7 @@ def edit_plugin(self, request, plugin_id):
             
         return response
 
+    @xframe_options_sameorigin
     def move_plugin(self, request):
         # only allow POST
         if request.method != "POST":
@@ -292,7 +293,8 @@ def move_plugin(self, request):
         else:
             HttpResponse(str("error"))
         return HttpResponse(str("ok"))
-    
+
+    @xframe_options_sameorigin
     def remove_plugin(self, request):
         if request.method != "POST": # only allow POST
             raise Http404
@@ -307,7 +309,8 @@ def remove_plugin(self, request):
         plugin_name = unicode(plugin_pool.get_plugin(plugin.plugin_type).name)
         comment = _(u"%(plugin_name)s plugin at position %(position)s in %(placeholder)s was deleted.") % {'plugin_name':plugin_name, 'position':plugin.position, 'placeholder':plugin.placeholder}
         return HttpResponse("%s,%s" % (plugin_id, comment))
-    
+
+    @xframe_options_sameorigin
     def copy_plugins(self, request):
         # only allow POST
         if request.method != "POST":


Test patch: