Commits

Anonymous committed 66f18ee

[svn] add the secure_form_tag_module. generates form tags including client-specific auth tokens
for preventing CSRF attacks
Original patch by David Turner
Refs #157

Comments (0)

Files changed (4)

 WebHelpers ChangeLog
 
 0.3.1 (**svn**)
+* Added the secure_form_tag helper module, for generating form tags
+  including client-specific authorization tokens for preventing CSRF
+  attacks. Original patch by David Turner. Fixes #157.
 * current_url now accepts arguments to pass along to url_for. Fixes #251.
 * Updated prototype to 1.5.1.
 * Added image support to button_to. Patch by Alex Conrad. Fixes #184.

tests/test_secure_form_tag.py

+# -*- coding: utf-8 -*-
+from util import WebHelpersTestCase
+import unittest
+
+from webhelpers.rails.secure_form_tag import authentication_token, \
+    get_session, secure_form, secure_form_remote_tag, token_key
+from webhelpers.rails.urls import url
+
+class TestSecureFormTagHelper(WebHelpersTestCase):
+    def setUp(self):
+        super(self.__class__, self).setUp()
+        self.authentication_token = authentication_token()
+        assert self.authentication_token
+        assert get_session()[token_key] == self.authentication_token
+        
+    def test_secure_form(self):
+        self.assertEqual(
+            secure_form(url="http://www.example.com"),
+            '<form action="http://www.example.com" method="POST">\n<div style="display: none;"><input id="%s" name="%s" type="hidden" value="%s" /></div>' % (token_key, token_key, self.authentication_token)
+        )
+        self.assertEqual(
+            secure_form(url="http://www.example.com", method='GET'),
+            '<form action="http://www.example.com" method="GET">\n<div style="display: none;"><input id="%s" name="%s" type="hidden" value="%s" /></div>' % (token_key, token_key, self.authentication_token)
+        )
+        self.assertEqual(
+            secure_form(url('/test/edit/1')),
+            '<form action="/test/edit/1" method="POST">\n<div style="display: none;"><input id="%s" name="%s" type="hidden" value="%s" /></div>' % (token_key, token_key, self.authentication_token)
+        )
+
+    def test_secure_form_remote_tag(self):
+        self.assertEqual(secure_form_remote_tag(update="glass_of_beer",url='http://www.example.com/fast'),
+        """<form action="http://www.example.com/fast" method="POST" onsubmit="new Ajax.Updater(\'glass_of_beer\', \'http://www.example.com/fast\', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">\n<div style="display: none;"><input id="%s" name="%s" type="hidden" value="%s" /></div>""" % (token_key, token_key, self.authentication_token))
+
+if __name__ == '__main__':
+    suite = [unittest.makeSuite(TestSecureFormTagHelper)]
+    for testsuite in suite:
+        unittest.TextTestRunner(verbosity=1).run(testsuite)

webhelpers/rails/__init__.py

 from prototype import *
 from scriptaculous import *
 from form_tag import *
+from secure_form_tag import *
 from text import *
 from form_options import *
 from date import *

webhelpers/rails/secure_form_tag.py

+"""
+Secure Form Tag Helpers -- For prevention of Cross-site request forgery (CSRF)
+attacks.
+
+Generates form tags that include client-specific authorization tokens to be
+verified by the destined web app.
+
+Authorization tokens are stored in the client's session. The web app can then
+verify the request's submitted authorization token with the value in the
+client's session.
+
+This ensures the request came from the originating page. See
+http://en.wikipedia.org/wiki/Cross-site_request_forgery for more information.
+
+Pylons provides an ``authenticate_form`` decorator that does this verfication
+on the behalf of controllers.
+"""
+import random
+
+from routes import request_config
+
+from form_tag import form, hidden_field
+from prototype import form_remote_tag
+from tags import content_tag
+
+token_key = '_authentication_token'
+
+def get_session():
+    """Return the current session from the environ provided by routes. A Pylons
+    supported session is assumed. A KeyError is raised if one doesn't exist."""
+    environ = request_config().environ
+    session_key = environ['pylons.environ_config']['session']
+    session = environ[session_key]
+    return session
+
+def authentication_token():
+    """Return the current authentication token, creating one if one doesn't
+    already exist."""
+    session = get_session()
+    if not token_key in session:
+        session[token_key] = str(random.getrandbits(128))
+        if hasattr(session, 'save'):
+            session.save()
+    return session[token_key]
+
+def secure_form(url, **args):
+    """Create a form tag (like webhelpers.rails.form_tag.form) including a
+    hidden authentication token field.
+    """
+    id = authentication_token()
+    form_html = form(url, **args)
+    return '%s\n%s' % (form_html,
+                       content_tag('div', hidden_field(token_key, id),
+                                   style='display: none;'))
+
+def secure_form_remote_tag(**args):
+    """Create a form tag (like webhelpers.rails.prototype.form_remote_tag)
+    including a hidden authentication token field.
+    """
+    id = authentication_token()
+    form_html = form_remote_tag(**args)
+    return '%s\n%s' % (form_html,
+                       content_tag('div', hidden_field(token_key, id),
+                                   style='display: none;'))
+
+__all__ = ['secure_form', 'secure_form_remote_tag']
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.