Hi Simon,

OK, here is my response.  I hope this doesn't turn into a personal my-code-vs-
your-code match (especially as most of "my code" is really other people's code 
and ideas :-) but I want to make sure we do this right, as it potentially has 
big implications, so the following will be "sleeves rolled up" (getting into 
details) and "gloves off" when it comes to criticisms of the code itself :-)

Advantages of SafeForm
----------------------

I can see the value of having contrib apps secured whatever the user has in 
their settings.py

Unfortunately, that is the only advantage of any significance that I can 
see.  And there are *much* easier ways to achieve that goal if that is our 
main priority (see the bottom of my e-mail for details).

There is the 'advantage' that Russell mentioned of not having to touch the 
tutorials.  This is a pretty dubious advantage IMO because it means that the 
tutorials will still lead to insecure code.  Far from 'raising the profile' of 
CSRF as Simon rightly suggested we should do, this is just keeping quiet about 
it.

Disadvantages of SafeForm
-------------------------

1) Big changes are required to use it.

The changes to use SafeForm are much more intrusive than using the template 
tag.  It requires quite extensive changes to both templates and views:

 * different API and different flow control for SafeForm compared to Form (it
   may be better, but it's different, which means a big upgrade cost)
 * a decorator for views
 * a decorator for forms
 * quite commonly, significant changes to the template:
   * Often you have to explicitly add the csrf token field.  And you've 
     got to worry about whether you need to or not.
   * If you weren't displaying form.non_field_errors before (which can be
     common if you don't need it), you have to add it.
 * For the multiple forms in a <form> case, you've got even more changes
   to your view function and your template, and you have to learn about
   CsrfForm on top of everything else.

Using SafeForm is a *lot* more complex than the template tag method.  The 
basic instructions for day-to-day usage of the template tag method (i.e. not 
including change-once settings) go like this:

 If the target of a <form> is internal:
   * add {% load csrf %} to the template and {% csrf_token %} to the form
   * use RequestContext in the corresponding view.

And that's it.

2) More than one way to do CSRF protection.

Although you provide a low level mechanism for forms, it still means you have 
Two Ways To Do CSRF Protection, and they are quite different (from the users 
point of view).  Once you've added in the multiple forms in a <form> problem, 
you've now got Three Ways To Do CSRF Protection, all of which will be needed 
in quite common circumstances.

(Yes, the template tag also provides a lower level mechanism (using get_token 
directly), but developers will only ever need to use it if they not using 
templates to generate pages, which is very uncommon).

3) More than one way to do Forms

Switching between Form and SafeForm requires API changes for the form itself 
and significant flow control changes in the view. So we have "Two Ways To 
Do Forms".

Every user now faces a choice - use Form or SafeForm?  Form seems to be 
simpler; the API is possible to play with at the interactive prompt (see 
tests/regressiontests/forms.py, for example), unlike SafeForm which requires 
an HttpRequest object; and SafeForm sounds like something for the paranoid, 
ultra-secure types.  So you can bet that tutorials and code across the 
internet are going to use Form.

Even if people standardise on using SafeForm everywhere, you will have the 
case where they use Django forms to generate a form even when it is targeted 
externally.  Because they have switched to using SafeForm, that form will now 
include the CSRF token, which is leaked to external sites, which is a security 
vulnerability.  Further, it's really not obvious, because the place where the 
use of SafeForm is defined (in their forms.py or views.py somewhere) is a very 
long way from the information that defines whether they need it or not (the 
target attribute of the <form> in the template).

4) CSRF is baked in to view functions.

Every view function now has CSRF baked in, either 'hand rolled' or via 
SafeForm.  While this is the major advantage of SafeForm, it is also a major 
disadvantage, because:

 * If we need to change anything about the API in the future, we will require
   extensive changes to Django code and everyone else's.

 * Developers can't 'swap out' the implementation of CSRF protection.  With
   the template tag method, this is easy to do.  If someone has custom
   requirements, or because of their situation they can just do
   strict Referer checking or Origin checking or something, they can implement
   their own middleware and template tag, and switch *all* the Django apps
   over to their system simply by changing some settings.

 * Testing view functions becomes a lot harder.  Many of the admin view tests
   would break if we switched to SafeForm, because you've now got to include
   the token, which you have to get from the previous page.  So you have to
   correctly parse the form (ensuring you don't pick up some other form that
   happens to be in the template) and extract the token.  Not only do you have
   to do this for all the apps in contrib, you are requiring every user of
   Django to do this as well - if they want CSRF protection.  Testing tools 
   like twill can solve this problem - once you've rewritten all your tests 
   of course.

   This is a massive disadvantage IMO.

 * If developers want to change the way the 'Form session expired' error is 
   handled, they have to change code across the code base.  With the 
   middleware, they can just implement their own view function and
   set CSRF_FAILURE_VIEW.  There doesn't really seem to be any need
   to handle this on a per-form basis, especially as the false positives
   associated with session cycling have now been eliminated - it will be
   very rare that a user will ever actually see the CSRF error message.

 * It's just messy.  We've got flow control and code that's concerned with
   one particular security problem spread throughout the code base. This
   isn't exactly 'separation of concerns'.  The template tag method is not
   ideal in this regard, but the SafeForm method is quite a lot worse.

5) Silently insecure

With the middleware, if you forget to add protection to one template/view, the 
system will complain loudly and offensively the minute anyone tries to POST to 
that view.  You can make it nicer if you want by implementing your own 
CSRF_FAILURE_VIEW, which could e-mail the admins if the token was absent but 
other indicators (like Referer) checked out OK.  We could even put that in by 
default.  But the big point is that you (or someone) will *know* about it, and 
you *won't* have a CSRF vulnerability.

With SafeForm, you have to remember to implement it for every POST form.  If 
you forget one, you have a vulnerability, and you *won't know* until it is 
exploited.  With lots of Django code being open source re-usable apps, 
targetted exploits that rely on knowing the source code are quite feasible.

6) Backwards incompatibilities with contrib Forms.

What do we do with SetPasswordForm etc.?  Really, we ought to do this:

  SetPasswordForm = SafeForm(SetPasswordForm)

which would of course break everyone's code if they have used or subclassed 
these forms.  So instead we need:

  SafeSetPasswordForm = SafeForm(SetPasswordForm)

Also, we need to take into consideration the various 'extension points' in 
contrib code that allow custom forms to be passed in e.g:

 * def password_reset_confirm(request, uidb36=None, token=None,
                              
template_name='registration/password_reset_confirm.html',
                              token_generator=default_token_generator, 
                              set_password_form=SetPasswordForm,
                              post_reset_redirect=None):

 * ModelAdmin.form
 * etc.

All of these will have to continue to use the normal Form, because they are 
public interfaces and SafeForms are not API compatible.  At the last minute, 
view code must wrap those forms with SafeForm and then use them.  This has the 
following consequences:

1. People are going to have to keep and pass around the undecorated Forms, as 
well as (sometimes) create Safe versions for their own use. This is pretty 
messy.

2. We've taken away some control over the actual form used, since we always 
wrap in SafeForm.  (If we give the option of using SafeForm or not, things get 
*really* messy)

3. If anyone has overridden ModelAdmin.change_view or add_view, they will have 
to update it to add the SafeForm decoration, and also add the changes to the 
usage of the form instance.

4. There are problems for anyone who has created custom templates.  This is 
pretty common, especially with the admin.  Contrib apps are now going to use 
the SafeForm-wrapped-Form with a template that was designed for Form, and 
quite often the custom template will be broken (i.e. if it needs to include 
the CSRF field or non_field_errors but doesn't).  Basically, in order to 
upgrade contrib apps to use SafeForm, we also need to check and upgrade all 
the templates, but we don't have access to all the templates!

Now, with the template tag, we also need to upgrade custom contrib templates 
but:
 1) Developers don't have to do it straight away - they can install 
    CsrfResponseMiddleware and everything will work perfectly.  
 2) The upgrade is much simpler.

With SafeForm, their templates and views will break instantly (in some cases), 
but they won't do so loudly, or with an obvious cause of the error, or with a 
workaround to buy them time to update their code.

OK, now onto a reply, an alternative, and some comparisons:

> >  * it's off by default.  Turning it on by default will require making
> > django.forms.Form subclass SafeForm, which will almost certainly require
> > a huge and immediate upgrade effort, because SafeForm cannot have the
> > same API as Form.  If it's off by default, I see no point at all in this
> > entire exercise.  If we don't arrive at a point where the POST form
> > created in the tutorial is safe from CSRF, I think we've failed.
> 
> My priority for this is a little different: while I would dearly love
> to see all Django applications secure against CSRF by default, I can't
> see a way of doing it that doesn't break existing apps. More important
> for me though is that the Django admin (and other reusable apps, such
> as Pinax stuff) MUST be secure against CSRF out of the box, no matter
> what the user puts in their settings.py file. If developers fail to
> protect themselves (especially when the solution is well documented)
> then at least it isn't our fault.

Given the massive (IMO) disadvantages to the SafeForm solution I've described 
above, I think it's pretty disingenuous to say "well we provided you with the 
tools, it's your fault if you're not protected".  The solution needs to be 
much better than well documented -- it needs to be easy to use, and easy to 
change to from existing code.  You also need to update all the existing Forms 
documentation to tell people that really they shouldn't be using this. With 
the amount of new stuff people need to learn to use SafeForm, they are just 
not going to bother.

I guess the biggest thing here is we've got different priorities, as you said 
-- I want to make it easy for developers to create apps that are secure 
against CSRF, while you want to make the contrib apps secure, and want to 
ensure no breakage even if people don't read release notes.  The SafeForm 
solution doesn't actually achieve that (see 6.3 and 6.4 above), and even if it 
did, it is at the massive cost of baking in a CSRF solution which has such big 
usability problems that I doubt anything outside contrib apps will adopt it.

In fact, if you simply want to fix contrib apps, SafeForm is a massively over-
engineered solution, as there are *much* less invasive ways to do it.  All you 
need to do is:
 1) add the CSRF template tag to builtins and use it in templates.
 2) add a @csrf_protect decorator on views.
 3) hack RequestContext so that it always includes the CSRF context processor.

All contrib apps use RequestContext anyway, so the changes needed for that 
method would be tiny, with no changes to settings.py required.  This would 
handle errors like the current middleware, but the error messages would only 
occur when there is a genuine CSRF attack.  (The reason I don't favour this 
solution is because of criticisms 4 and 5 above).

The best test case for which solution is easier to use is to start by patching 
all the contrib apps - forms, views, templates and tests.  Then you have to 
remember that you are expecting every developer out there to do the same with 
their code base if the solution is really going to be a viable 'blessed' 
solution.

I don't particularly want you to write that patch for SafeForm, because I 
think it would be a waste of time, but I think that you'd be convinced of the 
points above if you did.  For comparison, the template tag required the 
following changes to contrib apps:

View functions:
 0 changes (because they all used RequestContext already, as will much
            existing code - all that use generic views etc.)

Tests:
 a single 1 line change to a test that counted the number of <input>s in an 
 admin page (which was a pretty fragile test that has been updated multiple
 times already).

Templates:
 18 templates - added {% load csrf %} and {% csrf_token %}

To give another example, here is the changeset for one real site (about 12,000 
LOC) to switch to the template tag method:

http://bitbucket.org/spookylukey/cciw-website/changeset/f2edd690fd9e/

It has changes to settings and templates, but zero changes to view functions, 
and zero changes to tests.  That's better than some projects, because I 
obviously must have already been using RequestContext for all POST forms, but 
I don't think that will be atypical.

OK, I'm done!

Wondering-if-I-overdid-it-ly :-)

Luke

-- 
"Oh, look. I appear to be lying at the bottom of a very deep, dark 
hole. That seems a familiar concept. What does it remind me of? Ah, I 
remember. Life."  (Marvin the paranoid android)

Luke Plant || http://lukeplant.me.uk/

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django developers" group.
To post to this group, send email to django-developers@googlegroups.com
To unsubscribe from this group, send email to 
django-developers+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/django-developers?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to