I have just updated my copy of OSQA from SVN r670 to r1082, and the new CSRF protection (introduced in r988-r989) is causing errors on my OSQA setup. What could be wrong with my setup?
The problem appears to be that CookiePreHandlerMiddleware and CookiePostHandlerMiddleware (in forum.middleware.django_cookies) are interfering with CsrfViewMiddleware and CsrfResponseMiddleware, which are unable to use the “enhanced” cookies that they create. I don’t see how this is possible, however, because CsrfViewMiddleware and CsrfResponseMiddleware are loading before CookiePreHandlerMiddleware:
MIDDLEWARE_CLASSES = [
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.csrf.CsrfResponseMiddleware',
'forum.middleware.django_cookies.CookiePreHandlerMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
…
'django.middleware.transaction.TransactionMiddleware',
'forum.middleware.django_cookies.CookiePostHandlerMiddleware',
]
Here are the symptoms that I’m seeing: if I clear my browser cookies and load an OSQA page containing a form, the page loads correctly. The form contains two hidden csrfmiddlewaretoken fields (one generated by the {% csrf_token %} tag, and one generated by the CsrfResponseMiddleware:
<form id="fmanswer" action="/q-and-a/questions/3239/answer/" method="post">
<div style="display:none;"><input type="hidden" name="csrfmiddlewaretoken" value="7e6ceb6ec7c7512f3a3052c807b5b66a"></div>
<div style="display:none"><input type="hidden" name="csrfmiddlewaretoken" value="7e6ceb6ec7c7512f3a3052c807b5b66a"></div>
Additionally, the server sets a cookie:
Set-Cookie:csrftoken=7e6ceb6ec7c7512f3a3052c807b5b66a; Max-Age=31449600; Path=/
If I then refresh the page (sending the cookie with my request), Apache serves an Internal Server Error page, and I get this stack trace in my error log:
mod_wsgi (pid=3493): Exception occurred processing WSGI script '/home/kyank/learnable/osqa/osqa.wsgi'.
Traceback (most recent call last):
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/wsgi.py", line 245, in __call__
response = middleware_method(request, response)
File "/usr/local/lib/python2.6/dist-packages/django/middleware/csrf.py", line 226, in process_response
response.content, n = _POST_FORM_RE.subn(add_csrf_field, response.content)
File "/usr/local/lib/python2.6/dist-packages/django/middleware/csrf.py", line 222, in add_csrf_field
" name='csrfmiddlewaretoken' value='" + csrf_token + \\\\
TypeError: cannot concatenate 'str' and 'StringMorsel' objects
This error is generated by CsrfResponseMiddleware in its attempt to inject the CSRF token retrieved from the cookie into the form. The StringMorsel object referrred to on the last line indicates that, rather than getting a simple str value for the cookie, it got a StringMorsel object (as generated by forum.middleware.django_cookies.CookiePreHandlerMiddleware)! How this is happening given the middleware load order defined above is a mystery to me.
Since CsrfResponseMiddleware is not needed if all forms contain a {% csrf_token %} tag, I tried commenting it out, so that only CsrfViewMiddleware is loaded. This avoids the 500 error, but causes a TemplateSyntaxError when processing the {% csrf_token %} tag:
TemplateSyntaxError at /questions/1234/question-slug-here
Caught TypeError while rendering: Lazy object returned unexpected type.
Again, this appears to be caused by the {% csrf_token %} tag expecting to find a str in the cookie, and finding a StringMorsel there instead.
The stack trace looks like this:
Environment:
Request Method: GET
Request URL: http://courses.mythrildev.vm/q-and-a/questions/1234/question-slug-here
Django Version: 1.2.1
Python Version: 2.6.5
Installed Applications:
['django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.humanize',
'django.contrib.sitemaps',
'django.contrib.markup',
'forum',
'debug_toolbar',
'south']
Installed Middleware:
['django.middleware.csrf.CsrfViewMiddleware',
'forum.middleware.django_cookies.CookiePreHandlerMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'forum.middleware.extended_user.ExtendedUser',
'forum_modules.learnable.middleware.AutoLoginMiddleware',
'forum_modules.learnable.middleware.ContentAwareUserMiddleware',
'djexceptional.ExceptionalMiddleware',
'forum.middleware.anon_user.ConnectToSessionMessagesMiddleware',
'forum.middleware.request_utils.RequestUtils',
'forum.middleware.cancel.CancelActionMiddleware',
'forum.middleware.admin_messages.AdminMessagesMiddleware',
'django.middleware.transaction.TransactionMiddleware',
'forum.middleware.django_cookies.CookiePostHandlerMiddleware',
'debug_toolbar.middleware.DebugToolbarMiddleware']
…
Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response
100. response = callback(request, *callback_args, **callback_kwargs)
File "/home/kyank/learnable/osqa/forum/modules/decorators.py" in decorated
95. return decoratable(*args, **kwargs)
File "/home/kyank/learnable/osqa/forum/modules/decorators.py" in __call__
60. res = dec(res, *args, **kwargs)
File "/home/kyank/learnable/osqa/forum/views/decorators.py" in decorated
32. context_instance=RequestContext(request))
File "/usr/local/lib/python2.6/dist-packages/django/shortcuts/__init__.py" in render_to_response
20. return HttpResponse(loader.render_to_string(*args, **kwargs), **httpresponse_kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/template/loader.py" in render_to_string
186. return t.render(context_instance)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
173. return self._render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in _render
167. return self.nodelist.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
796. bits.append(self.render_node(node, context))
File "/usr/local/lib/python2.6/dist-packages/django/template/debug.py" in render_node
72. result = node.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/loader_tags.py" in render
125. return compiled_parent._render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in _render
167. return self.nodelist.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
796. bits.append(self.render_node(node, context))
File "/usr/local/lib/python2.6/dist-packages/django/template/debug.py" in render_node
72. result = node.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/loader_tags.py" in render
125. return compiled_parent._render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in _render
167. return self.nodelist.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
796. bits.append(self.render_node(node, context))
File "/usr/local/lib/python2.6/dist-packages/django/template/debug.py" in render_node
72. result = node.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/loader_tags.py" in render
139. return self.template.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
173. return self._render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in _render
167. return self.nodelist.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/__init__.py" in render
796. bits.append(self.render_node(node, context))
File "/usr/local/lib/python2.6/dist-packages/django/template/debug.py" in render_node
72. result = node.render(context)
File "/usr/local/lib/python2.6/dist-packages/django/template/defaulttags.py" in render
41. if csrf_token:
File "/usr/local/lib/python2.6/dist-packages/django/utils/functional.py" in __wrapper__
197. raise TypeError("Lazy object returned unexpected type.")
Exception Type: TemplateSyntaxError at /questions/3239/phpmyadmin-not-found
Exception Value: Caught TypeError while rendering: Lazy object returned unexpected type.