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> </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]