Jouni K. Seppänen has proposed merging lp:~jks/mailman/hcaptcha into 
lp:mailman/2.1.

Requested reviews:
  Mailman Coders (mailman-coders)

For more details, see:
https://code.launchpad.net/~jks/mailman/hcaptcha/+merge/389691

I noticed that a Mailman 2.1 site I run was being used for sending subscribe 
confirmation messages to people. I didn't want to implement reCaptcha because 
of privacy concerns, and hCaptcha seems to be almost a drop-in replacement that 
at least promises to respect user privacy. This change allows using hCaptcha 
instead of reCaptcha for guarding the subscription form.

When testing, the dummy keys from https://docs.hcaptcha.com/#localdev may be 
useful:

Dummy Site Key:10000000-ffff-ffff-ffff-000000000001 
Dummy Secret Key:0x0000000000000000000000000000000000000000

-- 
Your team Mailman Coders is requested to review the proposed merge of 
lp:~jks/mailman/hcaptcha into lp:mailman/2.1.
=== modified file 'Mailman/Cgi/listinfo.py'
--- Mailman/Cgi/listinfo.py	2019-06-19 23:56:49 +0000
+++ Mailman/Cgi/listinfo.py	2020-08-23 10:21:44 +0000
@@ -263,15 +263,25 @@
     replacements['<mm-fullname-box>'] = mlist.FormatBox('fullname', size=30)
     # If reCAPTCHA is enabled, display its user interface
     if mm_cfg.RECAPTCHA_SITE_KEY:
+        captcha_api = 'https://www.google.com/recaptcha/api.js?hl=%s' % lang
+        div_class = 'g-recaptcha'
+        sitekey = mm_cfg.RECAPTCHA_SITE_KEY
+    elif mm_cfg.HCAPTCHA_SITE_KEY:
+        captcha_api = 'https://hcaptcha.com/1/api.js?hl=%s' % lang
+        div_class = 'h-captcha'
+        sitekey = mm_cfg.HCAPTCHA_SITE_KEY
+    else:
+        captcha_api = None
+    if captcha_api is not None:
         noscript = _('This form requires JavaScript.')
         replacements['<mm-recaptcha-ui>'] = (
             """<tr><td>&nbsp;</td><td>
             <noscript>%s</noscript>
-            <script src="https://www.google.com/recaptcha/api.js?hl=%s";>
+            <script src="%s">
             </script>
-            <div class="g-recaptcha" data-sitekey="%s"></div>
+            <div class="%s" data-sitekey="%s"></div>
             </td></tr>"""
-            % (noscript, lang, mm_cfg.RECAPTCHA_SITE_KEY))
+            % (noscript, captcha_api, div_class, sitekey))
     else:
         replacements['<mm-recaptcha-ui>'] = ''
 

=== modified file 'Mailman/Cgi/subscribe.py'
--- Mailman/Cgi/subscribe.py	2020-06-10 22:04:26 +0000
+++ Mailman/Cgi/subscribe.py	2020-08-23 10:21:44 +0000
@@ -39,6 +39,25 @@
 SLASH = '/'
 ERRORSEP = '\n\n<p>'
 COMMASPACE = ', '
+CAPTCHA = {
+    'recaptcha': {
+        'name': 'reCAPTCHA',
+        'verify': 'https://www.google.com/recaptcha/api/siteverify',
+        'secret_key': mm_cfg.RECAPTCHA_SECRET_KEY,
+        'site_key': mm_cfg.RECAPTCHA_SITE_KEY,
+        'value': 'g-recaptcha-response',
+        'include_site_key': False
+    },
+    'hcaptcha': {
+        'name': 'hCAPTCHA',
+        'verify': 'https://hcaptcha.com/siteverify',
+        'secret_key': mm_cfg.HCAPTCHA_SECRET_KEY,
+        'site_key': mm_cfg.HCAPTCHA_SITE_KEY,
+        'value': 'h-captcha-response',
+        'include_site_key': True
+    },
+}
+
 
 # Set up i18n
 _ = i18n._
@@ -136,24 +155,37 @@
              os.environ.get('REMOTE_ADDR',
                             'unidentified origin')))
 
-    # Check reCAPTCHA submission, if enabled
+    # Check reCAPTCHA/hCAPTCHA submission, if enabled
     if mm_cfg.RECAPTCHA_SECRET_KEY:
+        captcha = CAPTCHA['recaptcha']
+    elif mm_cfg.HCAPTCHA_SECRET_KEY:
+        captcha = CAPTCHA['hcaptcha']
+    else:
+        captcha = None
+        
+    if captcha is not None:
+        name = captcha['name']
+        data = {
+            'secret': captcha['secret_key'],
+            'response': cgidata.getvalue(captcha['value'], ''),
+            'remoteip': remote 
+       }
+        if captcha['include_site_key']:
+            data['sitekey'] = captcha['site_key']
         request = urllib2.Request(
-            url = 'https://www.google.com/recaptcha/api/siteverify',
-            data = urllib.urlencode({
-                'secret': mm_cfg.RECAPTCHA_SECRET_KEY,
-                'response': cgidata.getvalue('g-recaptcha-response', ''),
-                'remoteip': remote}))
+            url = captcha['verify'],
+            data = urllib.urlencode(data)
+        )
         try:
             httpresp = urllib2.urlopen(request)
             captcha_response = json.load(httpresp)
             httpresp.close()
             if not captcha_response['success']:
                 e_codes = COMMASPACE.join(captcha_response['error-codes'])
-                results.append(_('reCAPTCHA validation failed: %(e_codes)s'))
+                results.append(_('%(name)s validation failed: %(e_codes)s'))
         except urllib2.URLError, e:
             e_reason = e.reason
-            results.append(_('reCAPTCHA could not be validated: %(e_reason)s'))
+            results.append(_('%(name)s could not be validated: %(e_reason)s'))
 
     # Are we checking the hidden data?
     if mm_cfg.SUBSCRIBE_FORM_SECRET:

=== modified file 'Mailman/Defaults.py.in'
--- Mailman/Defaults.py.in	2020-06-10 22:04:26 +0000
+++ Mailman/Defaults.py.in	2020-08-23 10:21:44 +0000
@@ -156,6 +156,12 @@
 RECAPTCHA_SITE_KEY = None
 RECAPTCHA_SECRET_KEY = None
 
+# Use hCAPTCHA to protect the subscription form from spam bots, as an
+# alternative to the Google reCAPTCHA (at most one of the two can be enabled).
+# Obtain the following keys from https://www.hcaptcha.com
+HCAPTCHA_SITE_KEY = None
+HCAPTCHA_SECRET_KEY = None
+
 # Installation wide ban list.  This is a list of email addresses and regexp
 # patterns (beginning with ^) which are not allowed to subscribe to any lists
 # in the installation.  This supplements the individual list's ban_list.

_______________________________________________
Mailman-coders mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/mailman-coders.python.org/
Member address: [email protected]

Reply via email to