How to integrate Captcha (Recaptcha) for WTForms in CherryPy

1.6k views Asked by At

I want to use a RecaptchaField() in my WTForms, such as:

class MyForm(Form):
    name = StringField('Your name', [InputRequired(message='Please enter your name!')])
    recaptcha = RecaptchaField() # RecaptchaField as provided by Flask-WTF
    submit = SubmitField('Send')

Since I am using CherryPy, I am not sure whether or not I should use Flask-WTF, because Flask is a whole framework itself. I am wondering if I can use the Recaptcha functionality of Flask-WTF within my CherryPy solution. I tried the following:

from wtforms import StringField
from wtforms.validators import InputReqired
from flask.ext.wtf import Form
from flask.ext.wtf.recaptcha import RecaptchaField

# ...

form = MyForm() # Somewhere in my code

as seen in this Example here. I get the following Exception:

RuntimeError: working outside of application context

It means I have to properly set up a Flask app considering the right context... This is where I am starting to wonder if I am doing the right approach. Is there no other way than set up a separate Flask app inside my CherryPy app??

1

There are 1 answers

2
saaj On BEST ANSWER

My answer is mostly relevant to CherryPy and reCaptcha parts of the question. In my opinion, usage of WTForms and other similar libraries leads to a design problem when, speaking in terms of MVC-like design, you scatter the view and the controller into your own code and WTForms code/configuration. Things are simple when you manage one thing in a single place. Thus I suggest to use template engine like Jinja2 for the view (you can create a macro for a repetitive form element) and use input validation library like voluptuous in the controller (you can use same schema for form and API validation).

If you can't avoid WTForms, just grab validateRecaptcha and turn it into WTForms custom validator.

#!/usr/bin/env python
# -*- coding: utf-8 -*-


import urllib
import json

import cherrypy
import voluptuous as volu


config = {
  'global' : {
    'server.socket_host' : '127.0.0.1',
    'server.socket_port' : 8080,
    'server.thread_pool' : 8
  },
  '/' : {
    'recaptcha' : {
        # By default, all keys work on localhost
       'siteKey' : '6LeYIbsSAAAAACRPIllxA7wvXjIE411PfdB2gt2J',
       'secret'  : '6LeYIbsSAAAAAJezaIq3Ft_hSTo0YtyeFG-JgRtu'
     }
  }
}

def validateRecaptcha(value):
  '''https://developers.google.com/recaptcha/docs/verify'''

  if 'g-recaptcha-response' not in cherrypy.request.params:
    raise volu.Invalid('Recaptcha response is missing') 

  payload = urllib.urlencode({
    'secret'   : cherrypy.request.config['recaptcha']['secret'],
    'remoteip' : cherrypy.request.headers['remote-addr'],
    'response' : cherrypy.request.params['g-recaptcha-response']
  })

  url      = 'https://www.google.com/recaptcha/api/siteverify'
  response = json.load(urllib.urlopen(url, payload))
  if not response['success']:
    cherrypy.log(str(response))
    raise volu.Invalid(response['error-codes'])


class App:

  @cherrypy.expose
  def index(self, **kwargs):
    form = dict(form = {'value': ''}, errors = '')

    if cherrypy.request.method == 'POST':
      schema = volu.Schema({
        'value'                : volu.All(unicode, volu.Length(min = 8, max = 16)),
        'g-recaptcha-response' : validateRecaptcha,
      }, required = True, extra = True)
      try:
        kwargs = schema(kwargs)
      except volu.MultipleInvalid as ex:
        form = dict(form = kwargs, errors = {e.path[0] for e in ex.errors})
      else:
        raise cherrypy.HTTPRedirect('#success')

    return '''<!DOCTYPE html>
      <html>
        <head>
          <title>reCAPTCHA demo</title>
          <script src="https://www.google.com/recaptcha/api.js" type="text/javascript"></script>
        </head>
        <body>
          <form action="/" method="POST">
            <div style='border: 1px red solid'>{errors}</div>
            <div>Name</div>
            <input type="text" name="value" value="{form[value]}"/>
            <br/>
            <div class="g-recaptcha" data-sitekey="{0}"></div>
            <br/>
            <input type="submit" value="Submit"/>
          </form>
        </body>
      </html>
    '''.format(cherrypy.request.config['recaptcha']['siteKey'], **form)


if __name__ == '__main__':
  cherrypy.quickstart(App(), '/', config)