jean-philippe serafin avatar jean-philippe serafin committed 3f996a6 Merge

Resolved conflicts against default

Comments (0)

Files changed (25)

 
 Contributors:
 
+- Adam Lowry
 - Ali Aafshar
-- Adam Lowry
+- Andreas Madsack
 - Christopher Grebs
 - Eduardo Schettino
 - Emil Vladev

docs/crash_course.rst

 While you almost never use all three methods together in practice, it
 illustrates how WTForms looks up the `username` field:
 
-1. Check if `request.POST` has a `username` key.
-2. Check if `user` has an attribute named `username`.
-3. Check if a keyword argument named `username` was provided.
-4. Finally, if everything else fails, use the default value provided by the
-   field, if any.
+1. If a form was submitted (request.POST is not empty), process the form
+   input. Even if there was no form input for this field in particular, if
+   there exists form input of any sort, then we will process the form input.
+
+2. If there was no form input, then try the following in order:
+
+   1. Check if `user` has an attribute named `username`.
+   2. Check if a keyword argument named `username` was provided.
+   3. Finally, if everything else fails, use the default value provided by the
+      field, if any.
 
 
 Validators
 .. autoclass:: QuerySelectField(default field args, query_factory=None, get_pk=None, get_label=None, allow_blank=False, blank_text=u'')
 
 .. autoclass:: QuerySelectMultipleField(default field args, query_factory=None, get_pk=None, get_label=None, allow_blank=False, blank_text=u'')
+
+
+CSRF
+----
+.. module:: wtforms.ext.csrf
+
+The CSRF package includes tools that help you implement checking against
+cross-site request forgery ("csrf"). Due to the large number of variations on
+approaches people take to CSRF (and the fact that many make compromises) the
+base implementation allows you to plug in a number of CSRF validation
+approaches.
+
+CSRF implementations are made by subclassing
+:class:`~wtforms.ext.csrf.form.SecureForm`. For utility, we have provided one
+possible CSRF implementation in the package that can be used with many
+frameworks for session-based hash secure keying,
+:class:`~wtforms.ext.csrf.session.SessionSecureForm`.
+
+All CSRF implementations hinge around creating a special token, which is put in
+a hidden field on the form named 'csrf_token', which must be rendered in your
+template to be passed from the browser back to your view. There are many
+different methods of generating this token, but they are usually the result of
+a cryptographic hash function against some data which would be hard to forge.
+
+.. module:: wtforms.ext.csrf.form
+
+.. autoclass:: SecureForm
+
+    .. automethod:: generate_csrf_token
+
+    .. automethod:: validate_csrf_token
+
+Creating your own CSRF implementation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Here we will sketch out a simple theoretical CSRF implementation which
+generates a hash token based on the user's IP.
+
+**Note** This is a simplistic example meant to illustrate creating a CSRF
+implementation. This isn't recommended to be used in production because the
+token is deterministic and non-changing per-IP, which means this isn't the
+most secure implementation of CSRF.
+
+First, let's create our SecureForm base class::
+
+    from wtforms.ext.csrf import SecureForm
+    from hashlib import md5
+
+    SECRET_KEY = '1234567890'
+
+    class IPSecureForm(SecureForm):
+        """
+        Generate a CSRF token based on the user's IP. I am probably not very
+        secure, so don't use me.
+        """
+
+        def generate_csrf_token(self, csrf_context):
+            # csrf_context is passed transparently from the form constructor,
+            # in this case it's the IP address of the user
+            token = md5(SECRET_KEY + csrf_context).hexdigest()
+            return token
+
+        def validate_csrf_token(self, field):
+            if field.data != field.current_token:
+                raise ValueError('Invalid CSRF')
+
+
+Now that we have this taken care of, let's write a simple form and view which would implement this::
+
+    class RegistrationForm(IPSecureForm):
+        name = TextField('Your Name')
+        email = TextField('Email', [validators.email()])
+
+    def register(request):
+        form = RegistrationForm(request.POST, csrf_context=request.ip)
+
+        if request.method == 'POST' and form.validate():
+            pass # We're all good, create a user or whatever it is you do
+        elif form.csrf_token.errors:
+            pass # If we're here we suspect the user of cross-site request forgery
+        else:
+            pass # Any other errors
+
+        return render('register.html', form=form)
+
+And finally, a simple template:
+
+.. code-block:: html+jinja
+
+    <form action="register" method="POST">
+        {{ form.csrf_token }}
+        <p>{{ form.name.label }}: {{ form.name }}</p>
+        <p>{{ form.email.label }}: {{ form.email }}</p>
+        <input type="submit" value="Register">
+    </form>
+
+
+Please note that implementing CSRF detection is not fool-proof, and even with
+the best CSRF protection implementation, it's possible for requests to be
+forged by expert attackers. However, a good CSRF protection would make it
+infeasible for someone from an external site to hijack a form submission from
+another user and perform actions as them without additional a priori knowledge.
+
+In addition, it's important to understand that very often, the more strict the
+CSRF protection, the higher the chance of false positives occurring (ie,
+legitimate users getting blocked by your CSRF protection) and choosing a CSRF
+implementation is actually a matter of compromise. We will attempt to provide a
+handful of usable reference algorithms built in to this library in the future, to
+allow that choice to be easy.
+
+Some tips on criteria people often examine when evaluating CSRF implementations:
+
+ * **Reproducability** If a token is based on attributes about the user, it
+   gains the advantage that one does not need secondary storage in which to
+   store the value between requests. However, if the same attributes can be
+   reproduced by an attacker, then the attacker can potentially forge this
+   information.
+
+ * **Reusability**. It might be desired to make a completely different token
+   every use, and disallow users from re-using past tokens. This is an
+   extremely powerful protection, but can have consequences on if the user uses
+   the back button (or in some cases runs forms simultaneously in multiple
+   browser tabs) and submits an old token, or otherwise. A possible compromise
+   is to allow reusability in a time window (more on that later).
+
+ * **Time Ranges** Many CSRF approaches use time-based expiry to make sure that
+   a token cannot be (re)used beyond a certain point. Care must be taken in
+   choosing the time criteria for this to not lock out legitimate users. For
+   example, if a user might walk away while filling out a long-ish form, or to
+   go look for their credit card, the time for expiry should take that into
+   consideration to provide a balance between security and limiting user
+   inconvenience.
+
+ * **Requirements** Some CSRF-prevention methods require the use of browser
+   cookies, and some even require client-side scripting support. The webmaster
+   implementing the CSRF needs to consider that such requirements (though
+   effective) may lock certain legitimate users out, and make this
+   determination whether it is a good idea to use. For example, for a site
+   already using cookies for login, adding another for CSRF isn't as big of a
+   deal, but for other sites it may not be feasible.
+
+
+Session-based CSRF implementation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. automodule:: wtforms.ext.csrf.session
+
+**Usage**
+
+First, create a SessionSecureForm subclass that you can use as your base class
+for any forms you want CSRF support for::
+
+    from wtforms.ext.csrf.session import SessionSecureForm
+
+    class MyBaseForm(SessionSecureForm):
+        SECRET_KEY = 'EPj00jpfj8Gx1SjnyLxwBBSQfnQ9DJYe0Ym'
+        TIME_LIMIT = timedelta(minutes=20)
+
+Now incorporate it into any form/view by further subclassing::
+
+    class Registration(MyBaseForm):
+        name = TextField()
+
+    def view(request):
+        form = Registration(request.POST, csrf_context=request.session)
+        # rest of view here
+
+Note that request.session is passed as the ``csrf_context=`` parameter, this is
+so that the CSRF token can be stored in your session for comparison on a later
+request.
+
+.. autoclass:: SessionSecureForm
+
+    A provided CSRF implementation which puts CSRF data in a session. Must be
+    subclassed to be used.
+    
+    **Class Attributes**
+    .. attribute:: SECRET_KEY
+        
+        Must be set by subclasses to a random byte string that will be used to generate HMAC digests. 
+
+    .. attribute:: TIME_LIMIT
+
+        If None, CSRF tokens never expire. If set to a ``datetime.timedelta``,
+        this is how long til a generated token expires. Defaults to
+        ``timedelta(minutes=30)``
+
 What versions of Python are supported?
 --------------------------------------
 
-WTForms supports Python versions 2.4 and up. Presently, Python 3.x is not
-supported, as we are waiting for library support by the major frameworks. As
-soon as we have something we can test/deploy on we will consider making the
-provisions to support WTForms for Python 3.x.
+WTForms supports Python versions 2.5 and up. Presently (as of December 2011),
+Python 3.x is not officially supported, but the development version has made
+headway on this. We expect the upcoming 1.0 release to fully support Python 3
+using the '2to3' tool.
 
 
 How can I contribute to WTForms?
 
     .. automethod:: __contains__
 
+    .. automethod:: _get_translations
+
 Defining Forms
 --------------
 

docs/validators.rst

 
 .. autoclass:: wtforms.validators.Length
 
+.. autoclass:: wtforms.validators.MacAddress
+
 .. autoclass:: wtforms.validators.NumberRange
 
 .. autoclass:: wtforms.validators.Optional
 
 .. autoclass:: wtforms.validators.URL
 
+.. autoclass:: wtforms.validators.UUID
+
 .. autoclass:: wtforms.validators.AnyOf
 
 .. autoclass:: wtforms.validators.NoneOf
 import os, sys
 sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
 
-from distutils.core import setup
-import wtforms
+extra = {}
+
+try:
+    from setuptools import setup
+    has_setuptools = True
+    extra['test_suite'] = 'tests.runtests'
+except ImportError:
+    from distutils.core import setup
+    has_setuptools = False
+
+if sys.version_info >= (3, ):
+    if not has_setuptools:
+        raise Exception('Python3 support in WTForms requires distribute.')
+    extra['use_2to3'] = True
+    extra['use_2to3_exclude_fixers'] = ['lib2to3.fixes.filter']
 
 setup(
     name='WTForms',
-    version=wtforms.__version__,
+    version='0.6.4dev',
     url='http://wtforms.simplecodes.com/',
     license='BSD',
     author='Thomas Johansson, James Crasta',
         'wtforms.widgets',
         'wtforms.ext',
         'wtforms.ext.appengine',
+        'wtforms.ext.csrf',
         'wtforms.ext.dateutil',
         'wtforms.ext.django',
         'wtforms.ext.django.templatetags',
         'wtforms.ext.sqlalchemy',
-    ]
+    ],
+    **extra
 )

Empty file added.

tests/ext_csrf.py

+from unittest import TestCase
+
+from wtforms.fields import TextField
+from wtforms.ext.csrf import SecureForm
+from wtforms.ext.csrf.session import SessionSecureForm
+
+import hashlib
+import hmac
+
+class DummyPostData(dict):
+    def getlist(self, key):
+        v = self[key]
+        if not isinstance(v, (list, tuple)):
+            v = [v]
+        return v
+
+class InsecureForm(SecureForm):
+    def generate_csrf_token(self, csrf_context):
+        return csrf_context
+
+    a = TextField()
+
+class FakeSessionRequest(object):
+    def __init__(self, session):
+        self.session = session
+
+
+class SecureFormTest(TestCase):
+    def test_base_class(self):
+        self.assertRaises(NotImplementedError, SecureForm)
+
+    def test_basic_impl(self):
+        form = InsecureForm(csrf_context=42)
+        self.assertEqual(form.csrf_token.current_token, 42)
+        self.assert_(not form.validate())
+        self.assertEqual(len(form.csrf_token.errors), 1)
+        self.assertEqual(form.csrf_token._value(), 42)
+        # Make sure csrf_token is taken out from .data
+        self.assertEqual(form.data, {'a': None})
+
+    def test_with_data(self):
+        post_data = DummyPostData(csrf_token=u'test', a='hi')
+        form = InsecureForm(post_data, csrf_context=u'test')
+        self.assert_(form.validate())
+        self.assertEqual(form.data, {'a': u'hi'})
+
+        form = InsecureForm(post_data, csrf_context=u'something')
+        self.assert_(not form.validate())
+
+        # Make sure that value is still the current token despite
+        # the posting of a different value
+        self.assertEqual(form.csrf_token._value(), u'something')
+
+    def test_with_missing_token(self):
+        post_data = DummyPostData(a='hi')
+        form = InsecureForm(post_data, csrf_context=u'test')
+        self.assert_(not form.validate())
+
+        self.assertEqual(form.csrf_token.data, u'')
+        self.assertEqual(form.csrf_token._value(), u'test')
+
+
+
+class SessionSecureFormTest(TestCase):
+    class SSF(SessionSecureForm):
+        SECRET_KEY = 'abcdefghijklmnop'.encode('ascii')
+
+    class NoTimeSSF(SessionSecureForm):
+        SECRET_KEY = 'abcdefghijklmnop'.encode('ascii')
+        TIME_LIMIT = None
+
+    def test_basic(self):
+        self.assertRaises(TypeError, self.SSF)
+
+        session = {}
+        form = self.SSF(csrf_context=FakeSessionRequest(session))
+        assert 'csrf' in session
+
+    def test_timestamped(self):
+        session = {}
+        postdata = DummyPostData(csrf_token=u'fake##fake')
+        form = self.SSF(postdata, csrf_context=session)
+        assert 'csrf' in session
+        assert form.csrf_token._value()
+        assert form.csrf_token._value() != session['csrf']
+        assert not form.validate()
+        self.assertEqual(form.csrf_token.errors[0], u'CSRF failed')
+        # TODO: More stringent test with timestamps and all that
+
+    def test_notime(self):
+        session = {}
+        form = self.NoTimeSSF(csrf_context=session)
+        hmacced = hmac.new(form.SECRET_KEY, session['csrf'].encode('utf8'), digestmod=hashlib.sha1)
+        self.assertEqual(form.csrf_token._value(), '##%s' % hmacced.hexdigest())
+        assert not form.validate()
+        self.assertEqual(form.csrf_token.errors[0], u'CSRF token missing') 
+
+        # Test with pre-made values
+        session = {'csrf': u'00e9fa5fe507251ac5f32b1608e9282f75156a05'}
+        postdata = DummyPostData(csrf_token=u'##d21f54b7dd2041fab5f8d644d4d3690c77beeb14')
+
+        form = self.NoTimeSSF(postdata, csrf_context=session)
+        assert form.validate()

tests/ext_sqlalchemy.py

         )
 
         Test = type('Test', (Base, ), {})
-        PKTest = type('PKTest', (Base, ), {'__unicode__': lambda x: x.baz })
+        PKTest = type('PKTest', (Base, ), {
+            '__unicode__': lambda x: x.baz,
+            '__str__': lambda x: x.baz,
+        })
 
         mapper(Test, test_table, order_by=[test_table.c.name])
         mapper(PKTest, pk_test_table, order_by=[pk_test_table.c.baz])
 from wtforms.form import Form
 
 
-PYTHON_VERSION = platform.python_version_tuple()
+PYTHON_VERSION = tuple(int(x) for x in platform.python_version_tuple())
 
 class DummyPostData(dict):
     def getlist(self, key):
         self.assertEqual(unicode(label), expected)
         self.assertEqual(label.__html__(), expected)
         self.assertEqual(label().__html__(), expected)
-        self.assertEqual(label('hello'), u"""<label for="test">hello</label>""")
+        self.assertEqual(label(u'hello'), u"""<label for="test">hello</label>""")
         self.assertEqual(TextField(u'hi').bind(Form(), 'a').label.text, u'hi')
-        self.assertEqual(repr(label), "Label('test', u'Caption')") 
+        if PYTHON_VERSION < (3, 0, 0):
+            self.assertEqual(repr(label), "Label('test', u'Caption')") 
+        else:
+            self.assertEqual(repr(label), "Label('test', 'Caption')") 
 
     def test_auto_label(self):
-        t1 = TextField().bind(Form(), 'foo_bar')
-        self.assertEqual(t1.label.text, 'Foo Bar')
+        t1 = TextField().bind(Form(), u'foo_bar')
+        self.assertEqual(t1.label.text, u'Foo Bar')
 
-        t2 = TextField('').bind(Form(), 'foo_bar')
-        self.assertEqual(t2.label.text, '')
+        t2 = TextField(u'').bind(Form(), u'foo_bar')
+        self.assertEqual(t2.label.text, u'')
 
 
 class FlagsTest(TestCase):
 
 class FiltersTest(TestCase):
     class F(Form):
-        a = TextField(default=' hello', filters=[lambda x: x.strip()])
+        a = TextField(default=u' hello', filters=[lambda x: x.strip()])
+        b = TextField(default=u'42', filters=[lambda x: int(x)])
 
-    def test(self):
-        self.assertEqual(self.F().a.data, 'hello')
-        self.assertEqual(self.F(DummyPostData(a=['  foo bar  '])).a.data, 'foo bar')
+    def test_working(self):
+        form = self.F()
+        self.assertEqual(form.a.data, u'hello')
+        self.assertEqual(form.b.data, 42)
+        assert form.validate()
+
+    def test_failure(self):
+        form = self.F(DummyPostData(a=[u'  foo bar  '], b=[u'hi']))
+        self.assertEqual(form.a.data, u'foo bar')
+        self.assertEqual(form.b.data, u'hi')
+        self.assertEqual(len(form.b.process_errors), 1)
+        assert not form.validate()
 
 
 class FieldTest(TestCase):
     class F(Form):
-        a = TextField(default='hello')
+        a = TextField(default=u'hello')
 
     def setUp(self):
         self.field = self.F().a 
 
+    def test_unbound_field(self):
+        unbound = self.F.a
+        assert unbound.creation_counter != 0
+        assert unbound.field_class is TextField
+        self.assertEqual(unbound.args, ())
+        self.assertEqual(unbound.kwargs, {'default': u'hello'})
+        assert repr(unbound).startswith(u'<UnboundField(TextField')
+
     def test_htmlstring(self):
         self.assert_(isinstance(self.field.__html__(), widgets.HTMLString))
 
         a = DateField()
         b = DateField(format='%m/%d %Y')
 
-    def test(self):
+    def test_basic(self):
         d = date(2008, 5, 7)
         form = self.F(DummyPostData(a=['2008-05-07'], b=['05/07', '2008']))
         self.assertEqual(form.a.data, d)
         self.assertEqual(form.b.data, d)
         self.assertEqual(form.b._value(), '05/07 2008')
 
+    def test_failure(self):
+        form = self.F(DummyPostData(a=['2008-bb-cc'], b=['hi']))
+        assert not form.validate()
+        self.assertEqual(len(form.a.process_errors), 1)
+        self.assertEqual(len(form.a.errors), 1)
+        self.assertEqual(len(form.b.errors), 1)
+        assert u'not match format' in form.a.process_errors[0]
+
 
 class DateTimeFieldTest(TestCase):
     class F(Form):
         self.assert_(form.validate())
         form = self.F(DummyPostData(a=['2008-05-05']))
         self.assert_(not form.validate())
-        self.assert_("not match format" in form.a.errors[0])
+        self.assert_(u'not match format' in form.a.errors[0])
 
     def test_microseconds(self):
         if PYTHON_VERSION < (2, 6, 0):
             return # Microsecond formatting support was only added in 2.6
-        
+
         d = datetime(2011, 5, 7, 3, 23, 14, 424200)
         F = make_form(a=DateTimeField(format='%Y-%m-%d %H:%M:%S.%f'))
         form = F(DummyPostData(a=['2011-05-07 03:23:14.4242']))

tests/runtests.py

 import sys
 from unittest import defaultTestLoader, TextTestRunner, TestSuite
 
-TESTS = ['form', 'fields', 'validators', 'widgets', 'webob_wrapper', 'translations']
-TESTS.extend([x for x in sys.argv[1:] if '-' not in x])
+TESTS = ('form', 'fields', 'validators', 'widgets', 'webob_wrapper', 'translations', 'ext_csrf')
 
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+def make_suite(prefix='', extra=()):
+    tests = TESTS + extra
+    test_names = list(prefix + x for x in tests)
+    suite = TestSuite()
+    suite.addTest(defaultTestLoader.loadTestsFromNames(test_names))
+    return suite
 
-suite = TestSuite()
-suite.addTest(defaultTestLoader.loadTestsFromNames(TESTS))
+def additional_tests():
+    """
+    This is called automatically by setup.py test
+    """
+    return make_suite('tests.')
 
-runner = TextTestRunner(verbosity=(sys.argv.count('-v') - sys.argv.count('-q') + 1))
-result = runner.run(suite)
-sys.exit(not result.wasSuccessful())
+def main():
+    extra_tests = tuple(x for x in sys.argv[1:] if '-' not in x)
+    suite = make_suite('', )
+
+    sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
+
+    runner = TextTestRunner(verbosity=(sys.argv.count('-v') - sys.argv.count('-q') + 1))
+    result = runner.run(suite)
+    sys.exit(not result.wasSuccessful())
+
+if __name__ == '__main__':
+    main()

tests/validators.py

 #!/usr/bin/env python
 from unittest import TestCase
-from wtforms.validators import StopValidation, ValidationError, email, equal_to, ip_address, length, required, optional, regexp, url, NumberRange, AnyOf, NoneOf
+from wtforms.validators import (
+    StopValidation, ValidationError, email, equal_to,
+    ip_address, length, required, optional, regexp,
+    url, NumberRange, AnyOf, NoneOf, mac_address, UUID
+)
+from functools import partial
 
 class DummyTranslations(object):
     def gettext(self, string):
         self.assertRaises(ValidationError, ip_address(), self.form, DummyField('1278.0.0.1'))
         self.assertRaises(ValidationError, ip_address(), self.form, DummyField('127.0.0.abc'))
 
+    def test_mac_address(self):
+        self.assertEqual(mac_address()(self.form, 
+                                       DummyField('01:23:45:67:ab:CD')), None)
+
+        check_fail = partial(
+            self.assertRaises, ValidationError, 
+            mac_address(), self.form
+        )
+
+        check_fail(DummyField('00:00:00:00:00'))
+        check_fail(DummyField('01:23:45:67:89:'))
+        check_fail(DummyField('01:23:45:67:89:gh'))
+        check_fail(DummyField('123:23:45:67:89:00'))
+
+
+    def test_uuid(self):
+        self.assertEqual(UUID()(self.form, DummyField(
+                    '2bc1c94f-0deb-43e9-92a1-4775189ec9f8')), None)
+        self.assertRaises(ValidationError, UUID(), self.form, 
+                          DummyField('2bc1c94f-deb-43e9-92a1-4775189ec9f8'))
+        self.assertRaises(ValidationError, UUID(), self.form, 
+                          DummyField('2bc1c94f-0deb-43e9-92a1-4775189ec9f'))
+        self.assertRaises(ValidationError, UUID(), self.form, 
+                          DummyField('gbc1c94f-0deb-43e9-92a1-4775189ec9f8'))
+        self.assertRaises(ValidationError, UUID(), self.form, 
+                          DummyField('2bc1c94f 0deb-43e9-92a1-4775189ec9f8'))
+
     def test_length(self):
         field = DummyField('foobar')
         self.assertEqual(length(min=2, max=6)(self.form, field), None)
 
     _value       = lambda x: x.data
     __unicode__  = lambda x: x.data
+    __str__      = lambda x: x.data
     __call__     = lambda x, **k: x.data
     __iter__     = lambda x: iter(x.data)
     iter_choices = lambda x: iter(x.data)

wtforms/ext/appengine/db.py

     # Get the field names we want to include or exclude, starting with the
     # full list of model properties.
     props = model.properties()
-    field_names = props.keys()
+    sorted_props = sorted(props.iteritems(), key=lambda prop: prop[1].creation_counter)
+    field_names = list(x[0] for x in sorted_props)
+
     if only:
         field_names = list(f for f in only if f in field_names)
     elif exclude:

wtforms/ext/csrf/__init__.py

+from wtforms.ext.csrf.form import SecureForm

wtforms/ext/csrf/fields.py

+from wtforms.fields import HiddenField
+
+
+class CSRFTokenField(HiddenField):
+    current_token = None
+
+    def _value(self):
+        """
+        We want to always return the current token on render, regardless of
+        whether a good or bad token was passed.
+        """
+        return self.current_token
+
+    def populate_obj(self, *args):
+        """
+        Don't populate objects with the CSRF token
+        """
+        pass

wtforms/ext/csrf/form.py

+from wtforms.form import Form
+from wtforms.validators import ValidationError
+
+from .fields import CSRFTokenField
+
+
+class SecureForm(Form):
+    """
+    Form that enables CSRF processing via subclassing hooks.
+    """
+    csrf_token = CSRFTokenField()
+
+    def __init__(self, formdata=None, obj=None, prefix='', csrf_context=None, **kwargs):
+        """
+        :param csrf_context: 
+            Optional extra data which is passed transparently to your 
+            CSRF implementation.
+        """
+        super(SecureForm, self).__init__(formdata, obj, prefix, **kwargs)
+        self.csrf_token.current_token = self.generate_csrf_token(csrf_context)
+
+    def generate_csrf_token(self, csrf_context):
+        """
+        Implementations must override this to provide a method with which one
+        can get a CSRF token for this form.
+
+        A CSRF token should be a string which can be generated
+        deterministically so that on the form POST, the generated string is
+        (usually) the same assuming the user is using the site normally.
+
+        :param csrf_context: 
+            A transparent object which can be used as contextual info for
+            generating the token.
+        """
+        raise NotImplementedError()
+
+    def validate_csrf_token(self, field):
+        """
+        Override this method to provide custom CSRF validation logic.
+
+        The default CSRF validation logic simply checks if the recently
+        generated token equals the one we received as formdata.
+        """
+        if field.current_token != field.data:
+            raise ValidationError(field.gettext(u'Invalid CSRF Token'))
+
+    @property
+    def data(self):
+        d = super(SecureForm, self).data
+        d.pop('csrf_token')
+        return d

wtforms/ext/csrf/session.py

+"""
+A provided CSRF implementation which puts CSRF data in a session.
+
+This can be used fairly comfortably with many `request.session` type
+objects, including the Werkzeug/Flask session store, Django sessions, and
+potentially other similar objects which use a dict-like API for storing
+session keys.
+
+The basic concept is a randomly generated value is stored in the user's
+session, and an hmac-sha1 of it (along with an optional expiration time,
+for extra security) is used as the value of the csrf_token. If this token
+validates with the hmac of the random value + expiration time, and the
+expiration time is not passed, the CSRF validation will pass.
+"""
+
+import hmac
+import os
+
+from hashlib import sha1
+from datetime import datetime, timedelta
+
+from ...validators import ValidationError
+from .form import SecureForm
+
+__all__ = ('SessionSecureForm', )
+
+class SessionSecureForm(SecureForm):
+    TIME_FORMAT = '%Y%m%d%H%M%S'
+    TIME_LIMIT = timedelta(minutes=30)
+    SECRET_KEY = None
+
+    def generate_csrf_token(self, csrf_context):
+        if self.SECRET_KEY is None:
+            raise Exception('must set SECRET_KEY in a subclass of this form for it to work')
+        if csrf_context is None:
+            raise TypeError('Must provide a session-like object as csrf context')
+
+        session = getattr(csrf_context, 'session', csrf_context)
+
+        if 'csrf' not in session:
+            session['csrf'] = sha1(os.urandom(64)).hexdigest()
+
+        self.csrf_token.csrf_key = session['csrf']
+        if self.TIME_LIMIT:
+            expires = (datetime.now() + self.TIME_LIMIT).strftime(self.TIME_FORMAT)
+            csrf_build = '%s%s' % (session['csrf'], expires)
+        else:
+            expires = ''
+            csrf_build = session['csrf']
+
+        hmac_csrf = hmac.new(self.SECRET_KEY, csrf_build.encode('utf8'), digestmod=sha1) 
+        return '%s##%s' % (expires, hmac_csrf.hexdigest())
+
+    def validate_csrf_token(self, field):
+        if not field.data or '##' not in field.data:
+            raise ValidationError(field.gettext(u'CSRF token missing'))
+
+        expires, hmac_csrf = field.data.split('##')
+
+        check_val = (field.csrf_key + expires).encode('utf8')
+
+        hmac_compare = hmac.new(self.SECRET_KEY, check_val, digestmod=sha1)
+        if hmac_compare.hexdigest() != hmac_csrf:
+            raise ValidationError(field.gettext(u'CSRF failed'))
+
+        if self.TIME_LIMIT:
+            now_formatted = datetime.now().strftime(self.TIME_FORMAT)
+            if now_formatted > expires:
+                raise ValidationError(field.gettext(u'CSRF token expired'))

wtforms/ext/sqlalchemy/orm.py

         field_args['validators'].append(validators.IPAddress())
         return f.TextField(**field_args)
 
-    @converts('MANYTOMANY', 'ONETOMANY')
-    def conv_ManyToMany(self, field_args, **extra):
-        return QuerySelectMultipleField(**field_args)
+    @converts('dialects.postgresql.base.MACADDR')
+    def conv_PGMacaddr(self, field_args, **extra):
+        field_args.setdefault('label', u'MAC Address')
+        field_args['validators'].append(validators.MacAddress())
+        return f.TextField(**field_args)
+
+    @converts('dialects.postgresql.base.UUID')
+    def conv_PGUuid(self, field_args, **extra):
+        field_args.setdefault('label', u'UUID')
+        field_args['validators'].append(validators.UUID())
+        return f.TextField(**field_args)
 
     @converts('MANYTOONE')
     def conv_ManyToOne(self, field_args, **extra):
         return QuerySelectField(**field_args)
 
+    @converts('MANYTOMANY', 'ONETOMANY')
+    def conv_ManyToMany(self, field_args, **extra):
+        return QuerySelectMultipleField(**field_args)
+
 
 def model_fields(model, db_session, only=None, exclude=None, field_args=None,
     converter=None):

wtforms/fields/__init__.py

 from wtforms.fields.core import *
 
+from wtforms.fields.simple import *
+
 # Compatibility imports
 from wtforms.fields.core import Label, Field, _unset_value, SelectFieldBase, Flags

wtforms/fields/core.py

 
 __all__ = (
     'BooleanField', 'DecimalField', 'DateField', 'DateTimeField', 'FieldList',
-    'FileField', 'FloatField', 'FormField', 'HiddenField', 'IntegerField',
-    'PasswordField', 'RadioField', 'SelectField', 'SelectMultipleField',
-    'SubmitField', 'TextField', 'TextAreaField',
+    'FloatField', 'FormField', 'IntegerField', 'RadioField', 'SelectField',
+    'SelectMultipleField', 'StringField',
 )
 
 
     option_widget = widgets.RadioInput()
 
 
-class TextField(Field):
+class StringField(Field):
     """
     This field is the base for most of the more complicated fields, and
     represents an ``<input type="text">``.
         return self.data is not None and unicode(self.data) or u''
 
 
-class HiddenField(TextField):
-    """
-    Represents an ``<input type="hidden">``.
-    """
-    widget = widgets.HiddenInput()
-
-
-class TextAreaField(TextField):
-    """
-    This field represents an HTML ``<textarea>`` and can be used to take
-    multi-line input.
-    """
-    widget = widgets.TextArea()
-
-
-class PasswordField(TextField):
-    """
-    Represents an ``<input type="password">``.
-    """
-    widget = widgets.PasswordInput()
-
-
-class FileField(TextField):
-    """
-    Can render a file-upload field.  Will take any passed filename value, if
-    any is sent by the browser in the post params.  This field will NOT
-    actually handle the file upload portion, as wtforms does not deal with
-    individual frameworks' file handling capabilities.
-    """
-    widget = widgets.FileInput()
-
-
-class IntegerField(TextField):
+class IntegerField(Field):
     """
     A text field, except all input is coerced to an integer.  Erroneous input
     is ignored and will not be accepted as a value.
     """
+    widget = widgets.TextInput()
+
     def __init__(self, label=None, validators=None, **kwargs):
         super(IntegerField, self).__init__(label, validators, **kwargs)
 
                 raise ValueError(self.gettext(u'Not a valid integer value'))
 
 
-class DecimalField(TextField):
+class DecimalField(Field):
     """
     A text field which displays and coerces data of the `decimal.Decimal` type.
 
         `decimal.ROUND_UP`. If unset, uses the rounding value from the
         current thread's context.
     """
+    widget = widgets.TextInput()
 
     def __init__(self, label=None, validators=None, places=2, rounding=None, **kwargs):
         super(DecimalField, self).__init__(label, validators, **kwargs)
                 raise ValueError(self.gettext(u'Not a valid decimal value'))
 
 
-class FloatField(TextField):
+class FloatField(Field):
     """
     A text field, except all input is coerced to an float.  Erroneous input
     is ignored and will not be accepted as a value.
     """
+    widget = widgets.TextInput()
+
     def __init__(self, label=None, validators=None, **kwargs):
         super(FloatField, self).__init__(label, validators, **kwargs)
 
                 raise
 
 
-class SubmitField(BooleanField):
-    """
-    Represents an ``<input type="submit">``.  This allows checking if a given
-    submit button has been pressed.
-    """
-    widget = widgets.SubmitInput()
-
-
 class FormField(Field):
     """
     Encapsulate a form as a field in another form.

wtforms/fields/simple.py

+from .. import widgets
+from .core import StringField, BooleanField
+
+
+__all__ = (
+    'BooleanField', 'TextAreaField', 'PasswordField', 'FileField',
+    'HiddenField', 'SubmitField', 'TextField'
+)
+
+
+class TextField(StringField):
+    """
+    Legacy alias for StringField
+    """
+
+class TextAreaField(TextField):
+    """
+    This field represents an HTML ``<textarea>`` and can be used to take
+    multi-line input.
+    """
+    widget = widgets.TextArea()
+
+
+class PasswordField(TextField):
+    """
+    Represents an ``<input type="password">``.
+    """
+    widget = widgets.PasswordInput()
+
+
+class FileField(TextField):
+    """
+    Can render a file-upload field.  Will take any passed filename value, if
+    any is sent by the browser in the post params.  This field will NOT
+    actually handle the file upload portion, as wtforms does not deal with
+    individual frameworks' file handling capabilities.
+    """
+    widget = widgets.FileInput()
+
+
+class HiddenField(TextField):
+    """
+    Represents an ``<input type="hidden">``.
+    """
+    widget = widgets.HiddenInput()
+
+
+class SubmitField(BooleanField):
+    """
+    Represents an ``<input type="submit">``.  This allows checking if a given
+    submit button has been pressed.
+    """
+    widget = widgets.SubmitInput()
+
         self._errors = None
         self._fields = {}
 
-        if hasattr(fields, 'iteritems'):
-            fields = fields.iteritems()
+        if hasattr(fields, 'items'):
+            fields = fields.items()
 
         translations = self._get_translations()
 
         """ Iterate form fields in arbitrary order """
         return self._fields.itervalues()
 
-    def __contains__(self, item):
+    def __contains__(self, name):
         """ Returns `True` if the named field is a member of this form. """
-        return (item in self._fields)
+        return (name in self._fields)
 
     def __getitem__(self, name):
         """ Dict-style access to this form's fields."""

wtforms/validators.py

     'Email', 'email', 'EqualTo', 'equal_to', 'IPAddress', 'ip_address',
     'Length', 'length', 'NumberRange', 'number_range', 'Optional', 'optional',
     'Required', 'required', 'Regexp', 'regexp', 'URL', 'url', 'AnyOf',
-    'any_of', 'NoneOf', 'none_of'
+    'any_of', 'NoneOf', 'none_of', 'MacAddress', 'mac_address', 'UUID'
 )
 
 
         super(IPAddress, self).__call__(form, field)
 
 
+class MacAddress(Regexp):
+    """
+    Validates a MAC address.
+
+    :param message:
+        Error message to raise in case of a validation error.
+    """
+    def __init__(self, message=None):
+        pattern = r'^(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$'
+        super(MacAddress, self).__init__(pattern, message=message)
+
+    def __call__(self, form, field):
+        if self.message is None:
+            self.message = field.gettext(u'Invalid Mac address.')
+
+        super(MacAddress, self).__call__(form, field)
+
+
 class URL(Regexp):
     """
     Simple regexp based url validation. Much like the email validator, you
         super(URL, self).__call__(form, field)
 
 
+class UUID(Regexp):
+    """
+    Validates a UUID.
+
+    :param message:
+        Error message to raise in case of a validation error.
+    """
+    def __init__(self, message=None):
+        pattern = r'^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$'
+        super(UUID, self).__init__(pattern, message=message)
+
+    def __call__(self, form, field):
+        if self.message is None:
+            self.message = field.gettext(u'Invalid UUID.')
+
+        super(UUID, self).__call__(form, field)
+
+
 class AnyOf(object):
     """
     Compares the incoming data to a sequence of valid inputs.
 email = Email
 equal_to = EqualTo
 ip_address = IPAddress
+mac_address = MacAddress
 length = Length
 number_range = NumberRange
 optional = Optional
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.