Roundup Wiki

This extension adds reCAPTCHA ( to the login form. This helps prevent password guessing by automated login attempts.

There are three parts to the extensions:

  1. Modification of the login form in html/page.html to add the html and javascript required to display the reCAPTCHA.
  2. is added to the extensions directory
  3. config.ini which is added to the extension directory and used to enable and control the reCAPTCHA module.

Changes to the html/page.html login form

Put this block of html just before the login button in html/page.html

      <tal:if tal:condition="python:getattr(db.config.ext,
          'RECAPTCHA_SECRET', '') not in ('','none')">
        <!-- recaptcha block -->
        <div class="g-recaptcha"
              'RECAPTCHA_SITEKEY', '')"
           tal:attributes="nonce request/client/client_nonce"
           async defer></script>
              Please enable JavaScript. Then solve the                      
              test that helps keep your account safe.                       

You should delete the tal:attributes line that sets the nonce unless you are running a 1.6.0 or newer release of roundup (1.6.0 is the development release at the time this page was created).

Put the block above just before the line that looks something like:

  <input type="submit" name="Login" value="Login" i18n:attributes="value"><br>

Python extension to put in extensions subdirectory of tracker

Put the contents of this area into a .py file in the extensions directory of the tracker. E.G.

Because white space is mangled by the wiki, copy it in raw mode.

   1 from roundup.cgi.actions import LoginAction
   2 from roundup.exceptions import Reject, RejectRaw
   3 from roundup.anypy.http_ import client
   5 from urlparse import urlparse
   6 import json
   8 import logging
   9 logger = logging.getLogger('actions')
  11 class reCAPTCHAloginAction(LoginAction):
  12     '''Login action requiring reCAPTCHA before username/password checked
  14        Set recaptcha 'secret' in the [recaptcha] section of
  15        extensions/config.ini to the secret to enable recaptcha.
  16        Set the 'sitekey' in the same section to the site key.
  17        See:
  19        for details.
  21        This code validates the passed in recaptcha code that is generated
  22        by the recaptcha javascript.
  23     '''
  25     # URL for the validation API.
  26     url = ""
  28     def handle(self):
  29         ''' Implement alternate login
  31             Things to improve:
  32                generate email if reCAPTCHA validation fails.
  33         '''
  35         if __debug__:
  36             logger.debug("reCAPTCHAloginAction: enter")
  38         secret = getattr(self.db.config.ext, 'RECAPTCHA_SECRET', False)
  40         # if secret missing or set to none skip recaptcha
  41         if secret and secret not in ("", "none"):
  42             if __debug__:
  43                 logger.debug("reCAPTCHAloginAction: validating w/ secret=%s",
  44                              secret)
  46             if 'g-recaptcha-response' not in self.form:
  47                 raise Reject(self._('Missing reCAPTCHA response.'))
  49             recaptcha_solution = self.form['g-recaptcha-response'].value
  51             data = 'secret=%s&response=%s'%(secret,recaptcha_solution)
  52             headers = {"Content-type": "application/x-www-form-urlencoded",
  53                        "Accept": "text/plain"}
  55             urlparts = urlparse(self.url)
  56             conn = client.HTTPSConnection(urlparts.netloc,
  57                                     urlparts.port or 443, timeout=5)
  58             # enable to trace the http payload
  59             # conn.set_debuglevel(1)
  60             conn.request("POST", urlparts.path, data, headers)
  61             resp = conn.getresponse()
  62             json_response =
  63             response = json.loads(json_response)
  65             if __debug__:
  66                 logger.debug("reCAPTCHAloginAction: %s", response)
  68             if response['success'] is False:
  69                 logger.error("reCAPTCHAloginAction: Validation failed response: %s", response)
  70                 raise Reject(self._('reCAPTCHA validation failed.'))
  72         # call the core EditItemAction to process the login username/password.
  73         LoginAction.handle(self)

config.ini to put in extensions subdirectory of tracker

Add this to the end of the extensions/config.ini file. Create the file if required.

# Configure the reCAPTCHAlogin extension module
# Adds a reCAPTCHA as part of the login form.
# See:
# secret key - if setting is commented out/missing or
# value is empty or value is set to none, module is disabled.
# secret = none

# Must set to the site key matching the secret key above.
# sitekey = 

To enable reCAPTCHA uncomment the sitekey and secret lines and insert the codes you got when you signed up for reCAPTCHA at google.

Then restart your tracker instance.

You should see something like the following:


If the user does not complete the reCAPTCHA the login is denied. The result of the reCAPTCHA is validated using the Google validation api. If that is not available, the login attempt will fail. The code needs some polish. E.G. some way to limit the time spent trying to validate the reCAPTCHA should be added. Currently it's possible an exception may be raised if the validation timed out. Also an option should be added (set in the exceptions/config.ini [recaptcha] section) to enable completing the login if the validation times out or errors out.