commit 1958bbb7e95df0f810e219955595f353933e1302 Author: Philipp Winter phw@nymity.ch Date: Tue Mar 31 09:37:37 2020 -0700
Make our email autoresponder more usable.
So far, our autoresponder would only send you bridges if your request was valid. Unfortunately, it's not very easy to figure out what a correct request looks like. This patch returns bridges regardless of if the request was valid or not. We also remove the "help" autoresponse because there's no longer a need for it, and we simplify BridgeDB's auto response, hopefully making it less frustrating for users.
This fixes https://bugs.torproject.org/30941. --- CHANGELOG | 6 ++ bridgedb/distributors/email/autoresponder.py | 10 -- bridgedb/distributors/email/distributor.py | 6 +- bridgedb/distributors/email/request.py | 20 ++-- bridgedb/distributors/email/templates.py | 83 +++++----------- bridgedb/i18n/templates/bridgedb.pot | 143 +++++++++++---------------- bridgedb/metrics.py | 7 +- bridgedb/strings.py | 49 ++++----- bridgedb/test/test_email_autoresponder.py | 5 +- bridgedb/test/test_email_request.py | 46 ++++----- bridgedb/test/test_email_templates.py | 31 +----- 11 files changed, 151 insertions(+), 255 deletions(-)
diff --git a/CHANGELOG b/CHANGELOG index 3e179e1..f85283b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,9 @@ + * FIXES https://bugs.torproject.org/30941 + Make our email responder more usable. This patch removes the concept of + "valid" email commands and returns bridges (obfs4, for now) no matter + what the user sends. BridgeDB still supports email commands in case the + user needs a vanilla or IPv6 bridge. + * FIXES https://bugs.torproject.org/29686 Rename files that contain "Bridges" to "bridgerings", to eliminate headache on file systems that are case insensitive. diff --git a/bridgedb/distributors/email/autoresponder.py b/bridgedb/distributors/email/autoresponder.py index 3711eae..5e1fcd0 100644 --- a/bridgedb/distributors/email/autoresponder.py +++ b/bridgedb/distributors/email/autoresponder.py @@ -55,7 +55,6 @@ from bridgedb import safelog from bridgedb.distributors.email import dkim from bridgedb.distributors.email import request from bridgedb.distributors.email import templates -from bridgedb.distributors.email.distributor import EmailRequestedHelp from bridgedb.distributors.email.distributor import EmailRequestedKey from bridgedb.distributors.email.distributor import TooSoonEmail from bridgedb.distributors.email.distributor import IgnoreEmail @@ -98,18 +97,9 @@ def createResponseBody(lines, context, client, lang='en'): bridgeRequest = request.determineBridgeRequestOptions(lines) bridgeRequest.client = str(client)
- # The request was invalid, respond with a help email which explains - # valid email commands: - if not bridgeRequest.isValid(): - raise EmailRequestedHelp("Email request from '%s' was invalid." - % str(client)) - # Otherwise they must have requested bridges: interval = context.schedule.intervalStart(time.time()) bridges = context.distributor.getBridges(bridgeRequest, interval) - except EmailRequestedHelp as error: - logging.info(error) - return templates.buildWelcomeText(translator, client) except EmailRequestedKey as error: logging.info(error) return templates.buildKeyMessage(translator, client) diff --git a/bridgedb/distributors/email/distributor.py b/bridgedb/distributors/email/distributor.py index a90e725..c6ac2fd 100644 --- a/bridgedb/distributors/email/distributor.py +++ b/bridgedb/distributors/email/distributor.py @@ -20,7 +20,7 @@ bridgedb.distributors.email.autoresponder A :class:`~bridgedb.distribute.Distributor` which hands out :class:`bridges <bridgedb.bridges.Bridge>` to clients via an email interface.
-.. inheritance-diagram:: IgnoreEmail TooSoonEmail EmailRequestedHelp EmailRequestedKey EmailDistributor +.. inheritance-diagram:: IgnoreEmail TooSoonEmail EmailRequestedKey EmailDistributor :parts: 1 """
@@ -55,10 +55,6 @@ class TooSoonEmail(addr.BadEmail): """Raised when we got a request from this address too recently."""
-class EmailRequestedHelp(Exception): - """Raised when a client has emailed requesting help.""" - - class EmailRequestedKey(Exception): """Raised when an incoming email requested a copy of our GnuPG keys."""
diff --git a/bridgedb/distributors/email/request.py b/bridgedb/distributors/email/request.py index 83c203d..90576c1 100644 --- a/bridgedb/distributors/email/request.py +++ b/bridgedb/distributors/email/request.py @@ -43,8 +43,8 @@ from __future__ import unicode_literals import logging import re
+from bridgedb import strings from bridgedb import bridgerequest -from bridgedb.distributors.email.distributor import EmailRequestedHelp from bridgedb.distributors.email.distributor import EmailRequestedKey
@@ -61,7 +61,6 @@ UNBLOCKED_PATTERN = re.compile(UNBLOCKED_REGEXP) #: Regular expressions that we use to match for email commands. Any command is #: valid as long as it wasn't quoted, i.e., the line didn't start with a '>' #: character. -HELP_LINE = re.compile("([^>].*)?h[ae]lp") GET_LINE = re.compile("([^>].*)?get") KEY_LINE = re.compile("([^>].*)?key") IPV6_LINE = re.compile("([^>].*)?ipv6") @@ -69,14 +68,13 @@ TRANSPORT_LINE = re.compile("([^>].*)?transport") UNBLOCKED_LINE = re.compile("([^>].*)?unblocked")
def determineBridgeRequestOptions(lines): - """Figure out which :mod:`~bridgedb.filters` to apply, or offer help. + """Figure out which :mod:`~bridgedb.filters` to apply.
.. note:: If any ``'transport TYPE'`` was requested, or bridges not blocked in a specific CC (``'unblocked CC'``), then the ``TYPE`` and/or ``CC`` will *always* be stored as a *lowercase* string.
:param list lines: A list of lines from an email, including the headers. - :raises EmailRequestedHelp: if the client requested help. :raises EmailRequestedKey: if the client requested our GnuPG key. :rtype: :class:`EmailBridgeRequest` :returns: A :class:`~bridgerequest.BridgeRequest` with all of the requested @@ -92,9 +90,6 @@ def determineBridgeRequestOptions(lines): if not line: skippedHeaders = True if not skippedHeaders: continue
- if HELP_LINE.match(line) is not None: - raise EmailRequestedHelp("Client requested help.") - if GET_LINE.match(line) is not None: request.isValid(True) logging.debug("Email request was valid.") @@ -108,6 +103,17 @@ def determineBridgeRequestOptions(lines): if UNBLOCKED_LINE.match(line) is not None: request.withoutBlockInCountry(line)
+ # We cannot expect all users to understand BridgeDB's commands, so we will + # return bridges even if the request was invalid. + if not request.isValid(): + logging.debug("Email request was invalid.") + request.isValid(True) + # We will respond with our default transport protocol. + if not len(request.transports): + # Note that this variable must satisfy TRANSPORT_PATTERN. + default_transport = "transport %s" % strings._getDefaultTransport() + request.withPluggableTransportType(default_transport) + logging.debug("Generating hashring filters for request.") request.generateFilters() return request diff --git a/bridgedb/distributors/email/templates.py b/bridgedb/distributors/email/templates.py index d618c37..2eb0a31 100644 --- a/bridgedb/distributors/email/templates.py +++ b/bridgedb/distributors/email/templates.py @@ -21,9 +21,6 @@ bridgedb.distributors.email.templates Templates for formatting emails sent out by the email distributor. """
-from __future__ import print_function -from __future__ import unicode_literals - import logging import os
@@ -34,12 +31,17 @@ from bridgedb.distributors.email.distributor import MAX_EMAIL_RATE
def addCommands(template): - """Add some text telling a client about supported email command, as well as - which Pluggable Transports are currently available. + """Add text telling a client about supported email command. + + :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` + :param template: A gettext translations instance, optionally with fallback + languages set. + :rtype: str + :returns: A string explaining email commands. """ # Tell them about the various email commands: cmdlist = [] - cmdlist.append(template.gettext(strings.EMAIL_MISC_TEXT.get(3))) + cmdlist.append(template.gettext(strings.EMAIL_MISC_TEXT.get(3)) + "\n") for cmd, desc in strings.EMAIL_COMMANDS.items(): command = ' ' command += cmd @@ -48,29 +50,20 @@ def addCommands(template): command += template.gettext(desc) cmdlist.append(command)
- commands = "\n".join(cmdlist) + "\n\n" - # And include the currently supported transports: - commands += template.gettext(strings.EMAIL_MISC_TEXT.get(5)) - commands += "\n" - for pt in strings._getSupportedTransports(): - commands += ' ' + pt + "\n" + return "\n".join(cmdlist) + "\n\n"
- return commands +def addGreeting(template): + """Our "greeting" clarifies that this is an automated email response.
-def addGreeting(template, clientName=None, welcome=False): - greeting = "" - clientName = clientName.decode('utf-8') if isinstance(clientName, bytes) else clientName - - if not clientName: - greeting = template.gettext(strings.EMAIL_MISC_TEXT[7]) - else: - greeting = template.gettext(strings.EMAIL_MISC_TEXT[6]) % clientName + :type template: ``gettext.NullTranslation`` or ``gettext.GNUTranslation`` + :param template: A gettext translations instance, optionally with fallback + languages set. + :rtype: str + :returns: A string containing our "greeting". + """
- if greeting: - if welcome: - greeting += u' ' - greeting += template.gettext(strings.EMAIL_MISC_TEXT[4]) - greeting += u'\n\n' + greeting = template.gettext(strings.EMAIL_MISC_TEXT[0]) + greeting += u"\n\n"
return greeting
@@ -79,11 +72,10 @@ def addKeyfile(template):
def addBridgeAnswer(template, answer): # Give the user their bridges, i.e. the `answer`: - bridgeLines = template.gettext(strings.EMAIL_MISC_TEXT[0]) - bridgeLines += u"\n\n" + bridgeLines = u"" bridgeLines += template.gettext(strings.EMAIL_MISC_TEXT[1]) bridgeLines += u"\n\n" - bridgeLines += u"%s\n\n" % answer + bridgeLines += u"%s\n" % answer
return bridgeLines
@@ -94,40 +86,15 @@ def addHowto(template): :param template: A gettext translations instance, optionally with fallback languages set. """ - howToTBB = template.gettext(strings.HOWTO_TBB[1]) % strings.EMAIL_SPRINTF["HOWTO_TBB1"] - howToTBB += u'\n\n' - howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB1") - howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB2") - howToTBB += strings.EMAIL_REFERENCE_LINKS.get("HOWTO_TBB3") - howToTBB += u'\n\n' - return howToTBB + return template.gettext(strings.HOWTO_TBB[2])
def buildKeyMessage(template, clientAddress=None): message = addKeyfile(template) return message
-def buildWelcomeText(template, clientAddress=None): - sections = [] - sections.append(addGreeting(template, clientAddress.local, welcome=True)) - - commands = addCommands(template) - sections.append(commands) - - # Include the same messages as the homepage of the HTTPS distributor: - welcome = template.gettext(strings.WELCOME[0]) % strings.EMAIL_SPRINTF["WELCOME0"] - welcome += template.gettext(strings.WELCOME[1]) - welcome += template.gettext(strings.WELCOME[2]) % strings.EMAIL_SPRINTF["WELCOME2"] - sections.append(welcome) - - message = u"\n\n".join(sections) - # Add the markdown links at the end: - message += strings.EMAIL_REFERENCE_LINKS.get("WELCOME0") - - return message - def buildAnswerMessage(template, clientAddress=None, answer=None): try: - message = addGreeting(template, clientAddress.local) + message = addGreeting(template) message += addBridgeAnswer(template, answer) message += addHowto(template) message += u'\n\n' @@ -139,11 +106,9 @@ def buildAnswerMessage(template, clientAddress=None, answer=None): return message
def buildSpamWarning(template, clientAddress=None): - message = addGreeting(template, clientAddress.local) + message = addGreeting(template)
try: - message += template.gettext(strings.EMAIL_MISC_TEXT[0]) - message += u"\n\n" message += template.gettext(strings.EMAIL_MISC_TEXT[2]) \ % str(MAX_EMAIL_RATE / 3600) except Exception as error: # pragma: no cover diff --git a/bridgedb/i18n/templates/bridgedb.pot b/bridgedb/i18n/templates/bridgedb.pot index 8fa69c6..65f8ca6 100644 --- a/bridgedb/i18n/templates/bridgedb.pot +++ b/bridgedb/i18n/templates/bridgedb.pot @@ -5,11 +5,11 @@ # msgid "" msgstr "" -"Project-Id-Version: bridgedb 0.9.4+8.g5afd164.dirty\n" +"Project-Id-Version: bridgedb 0.10.0+8.g97621c0\n" "Report-Msgid-Bugs-To: " "'https://trac.torproject.org/projects/tor/newticket?component=BridgeDB&ke..." "=bridgedb-reported,msgid&cc=isis,sysrqb&owner=isis'\n" -"POT-Creation-Date: 2020-03-24 10:22-0700\n" +"POT-Creation-Date: 2020-04-06 13:53-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME EMAIL@ADDRESS\n" "Language-Team: LANGUAGE LL@li.org\n" @@ -66,6 +66,10 @@ msgstr "" msgid "Changelog" msgstr ""
+#: bridgedb/distributors/https/templates/base.html:102 +msgid "Public Keys" +msgstr "" + #: bridgedb/distributors/https/templates/bridges.html:35 msgid "Select All" msgstr "" @@ -83,7 +87,7 @@ msgstr "" #. for Italian, you might translate this into "Mama mia!", #. or for French: "Sacrebleu!". :) #: bridgedb/distributors/https/templates/bridges.html:67 -#: bridgedb/distributors/https/templates/bridges.html:125 +#: bridgedb/distributors/https/templates/bridges.html:119 msgid "Uh oh, spaghettios!" msgstr ""
@@ -97,12 +101,12 @@ msgid "" "your bridge lines onto mobile and other devices." msgstr ""
-#: bridgedb/distributors/https/templates/bridges.html:131 +#: bridgedb/distributors/https/templates/bridges.html:125 msgid "There currently aren't any bridges available..." msgstr ""
-#: bridgedb/distributors/https/templates/bridges.html:133 -#: bridgedb/distributors/https/templates/bridges.html:137 +#: bridgedb/distributors/https/templates/bridges.html:127 +#: bridgedb/distributors/https/templates/bridges.html:131 #, python-format msgid " Perhaps you should try %s going back %s and choosing a different bridge type!" msgstr "" @@ -176,15 +180,15 @@ msgstr "" msgid "%sG%set Bridges" msgstr ""
-#: bridgedb/strings.py:43 -msgid "[This is an automated message; please do not reply.]" +#: bridgedb/strings.py:42 +msgid "[This is an automated email.]" msgstr ""
-#: bridgedb/strings.py:45 +#: bridgedb/strings.py:44 msgid "Here are your bridges:" msgstr ""
-#: bridgedb/strings.py:47 +#: bridgedb/strings.py:46 #, python-format msgid "" "You have exceeded the rate limit. Please slow down! The minimum time between\n" @@ -192,48 +196,17 @@ msgid "" "ignored." msgstr ""
-#: bridgedb/strings.py:50 -msgid "COMMANDs: (combine COMMANDs to specify multiple options simultaneously)" -msgstr "" - -#. TRANSLATORS: Please DO NOT translate the word "BridgeDB". -#: bridgedb/strings.py:53 -msgid "Welcome to BridgeDB!" -msgstr "" - -#. TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE". -#: bridgedb/strings.py:55 -msgid "Currently supported transport TYPEs:" -msgstr "" - -#: bridgedb/strings.py:56 -#, python-format -msgid "Hey, %s!" -msgstr "" - -#: bridgedb/strings.py:57 -msgid "Hello, friend!" -msgstr "" - -#: bridgedb/distributors/https/templates/base.html:102 bridgedb/strings.py:58 -msgid "Public Keys" -msgstr "" - -#. TRANSLATORS: This string will end up saying something like: -#. "This email was generated with rainbows, unicorns, and sparkles -#. for alice@example.com on Friday, 09 May, 2014 at 18:59:39." -#: bridgedb/strings.py:62 -#, python-format +#: bridgedb/strings.py:49 msgid "" -"This email was generated with rainbows, unicorns, and sparkles\n" -"for %s on %s at %s." +"If these bridges are not what you need, reply to this email with one of\n" +"the following commands in the message body:" msgstr ""
#. TRANSLATORS: Please DO NOT translate "BridgeDB". #. TRANSLATORS: Please DO NOT translate "Pluggable Transports". #. TRANSLATORS: Please DO NOT translate "Tor". #. TRANSLATORS: Please DO NOT translate "Tor Network". -#: bridgedb/strings.py:72 +#: bridgedb/strings.py:59 #, python-format msgid "" "BridgeDB can provide bridges with several %stypes of Pluggable Transports%s,\n" @@ -245,7 +218,7 @@ msgid "" msgstr ""
#. TRANSLATORS: Please DO NOT translate "Pluggable Transports". -#: bridgedb/strings.py:79 +#: bridgedb/strings.py:66 msgid "" "Some bridges with IPv6 addresses are also available, though some Pluggable\n" "Transports aren't IPv6 compatible.\n" @@ -257,7 +230,7 @@ msgstr "" #. regular, or unexciting". Like vanilla ice cream. It refers to bridges #. which do not have Pluggable Transports, and only speak the regular, #. boring Tor protocol. Translate it as you see fit. Have fun with it. -#: bridgedb/strings.py:88 +#: bridgedb/strings.py:75 #, python-format msgid "" "Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any" @@ -268,21 +241,21 @@ msgid "" "\n" msgstr ""
-#: bridgedb/strings.py:101 bridgedb/test/test_https.py:356 +#: bridgedb/strings.py:87 bridgedb/test/test_https.py:356 msgid "What are bridges?" msgstr ""
-#: bridgedb/strings.py:102 +#: bridgedb/strings.py:88 #, python-format msgid "%s Bridges %s are Tor relays that help you circumvent censorship." msgstr ""
-#: bridgedb/strings.py:107 +#: bridgedb/strings.py:93 msgid "I need an alternative way of getting bridges!" msgstr ""
#. TRANSLATORS: Please DO NOT translate "get transport obfs4". -#: bridgedb/strings.py:109 +#: bridgedb/strings.py:95 #, python-format msgid "" "Another way to get bridges is to send an email to %s. Leave the email subject" @@ -294,32 +267,32 @@ msgid "" "providers: %s or %s." msgstr ""
-#: bridgedb/strings.py:117 +#: bridgedb/strings.py:103 msgid "My bridges don't work! I need help!" msgstr ""
#. TRANSLATORS: Please DO NOT translate "Tor Browser". #. TRANSLATORS: The two '%s' are substituted with "Tor Browser Manual" and #. "Support Portal", respectively. -#: bridgedb/strings.py:121 +#: bridgedb/strings.py:107 #, python-format msgid "If your Tor Browser cannot connect, please take a look at the %s and our %s." msgstr ""
-#: bridgedb/strings.py:125 +#: bridgedb/strings.py:111 msgid "Here are your bridge lines:" msgstr ""
-#: bridgedb/strings.py:126 +#: bridgedb/strings.py:112 msgid "Get Bridges!" msgstr ""
-#: bridgedb/strings.py:130 +#: bridgedb/strings.py:116 msgid "Bridge distribution mechanisms" msgstr ""
#. TRANSLATORS: Please DO NOT translate "BridgeDB", "HTTPS", and "Moat". -#: bridgedb/strings.py:132 +#: bridgedb/strings.py:118 #, python-format msgid "" "BridgeDB implements four mechanisms to distribute bridges: "HTTPS", " @@ -333,7 +306,7 @@ msgid "" "mechanisms is." msgstr ""
-#: bridgedb/strings.py:138 +#: bridgedb/strings.py:124 #, python-format msgid "" "The "HTTPS" distribution mechanism hands out bridges over this website. To" @@ -343,7 +316,7 @@ msgid "" "solve the subsequent CAPTCHA." msgstr ""
-#: bridgedb/strings.py:142 +#: bridgedb/strings.py:128 #, python-format msgid "" "The "Moat" distribution mechanism is part of Tor Browser, allowing users to" @@ -356,7 +329,7 @@ msgid "" "bridges." msgstr ""
-#: bridgedb/strings.py:148 +#: bridgedb/strings.py:134 #, python-format msgid "" "Users can request bridges from the "Email" distribution mechanism by " @@ -366,11 +339,11 @@ msgid "" "email body." msgstr ""
-#: bridgedb/strings.py:152 +#: bridgedb/strings.py:138 msgid "Reserved" msgstr ""
-#: bridgedb/strings.py:153 +#: bridgedb/strings.py:139 #, python-format msgid "" "BridgeDB maintains a small number of bridges that are not distributed\n" @@ -384,11 +357,11 @@ msgid "" "called "Unallocated" in %sbridge pool assignment%s files." msgstr ""
-#: bridgedb/strings.py:160 +#: bridgedb/strings.py:146 msgid "None" msgstr ""
-#: bridgedb/strings.py:161 +#: bridgedb/strings.py:147 msgid "" "Bridges whose distribution mechanism is "None" are not distributed by " "BridgeDB.\n" @@ -399,33 +372,33 @@ msgid "" "it will then change to the bridge's actual distribution mechanism.\n" msgstr ""
-#: bridgedb/strings.py:171 +#: bridgedb/strings.py:157 msgid "Please select options for bridge type:" msgstr ""
-#: bridgedb/strings.py:172 +#: bridgedb/strings.py:158 msgid "Do you need IPv6 addresses?" msgstr ""
-#: bridgedb/strings.py:173 +#: bridgedb/strings.py:159 #, python-format msgid "Do you need a %s?" msgstr ""
-#: bridgedb/strings.py:177 +#: bridgedb/strings.py:163 msgid "Your browser is not displaying images properly." msgstr ""
-#: bridgedb/strings.py:178 +#: bridgedb/strings.py:164 msgid "Enter the characters from the image above..." msgstr ""
-#: bridgedb/strings.py:182 +#: bridgedb/strings.py:168 msgid "How to start using your bridges" msgstr ""
#. TRANSLATORS: Please DO NOT translate "Tor Browser". -#: bridgedb/strings.py:184 +#: bridgedb/strings.py:170 #, python-format msgid "" " First, you need to %sdownload Tor Browser%s. Our Tor Browser User\n" @@ -434,29 +407,29 @@ msgid "" " are using Android, %sclick here%s." msgstr ""
-#: bridgedb/strings.py:192 -msgid "Displays this message." +#: bridgedb/strings.py:175 +msgid "" +"Add these bridges to your Tor Browser by opening your browser\n" +"preferences, clicking on "Tor", and then adding them to the "Provide a\n" +"bridge" field." msgstr ""
-#. TRANSLATORS: Please try to make it clear that "vanilla" here refers to the -#. same non-Pluggable Transport bridges described above as being -#. "plain-ol'-vanilla" bridges. -#: bridgedb/strings.py:196 -msgid "Request vanilla bridges." +#: bridgedb/strings.py:182 +msgid "(Request unobfuscated Tor bridges.)" msgstr ""
-#: bridgedb/strings.py:197 -msgid "Request IPv6 bridges." +#: bridgedb/strings.py:183 +msgid "(Request IPv6 bridges.)" msgstr ""
-#. TRANSLATORS: Please DO NOT translate the word the word "TYPE". -#: bridgedb/strings.py:199 -msgid "Request a Pluggable Transport by TYPE." +#. TRANSLATORS: Please DO NOT translate the word "TYPE". +#: bridgedb/strings.py:185 +msgid "(Request obfuscated bridges. Replace TYPE with 'obfs4'.)" msgstr ""
#. TRANSLATORS: Please DO NOT translate "BridgeDB". #. TRANSLATORS: Please DO NOT translate "GnuPG". -#: bridgedb/strings.py:202 -msgid "Get a copy of BridgeDB's public GnuPG key." +#: bridgedb/strings.py:189 +msgid "(Get a copy of BridgeDB's public GnuPG key.)" msgstr ""
diff --git a/bridgedb/metrics.py b/bridgedb/metrics.py index bb888d9..48713f0 100644 --- a/bridgedb/metrics.py +++ b/bridgedb/metrics.py @@ -22,7 +22,6 @@ import datetime from bridgedb import geo from bridgedb.distributors.common.http import getClientIP from bridgedb.distributors.email import request -from bridgedb.distributors.email.distributor import EmailRequestedHelp
from twisted.mail.smtp import Address
@@ -387,11 +386,7 @@ class EmailMetrics(Metrics): emailAddr = emailAddrs[0]
# Get the requested transport protocol. - try: - br = request.determineBridgeRequestOptions( - smtpAutoresp.incoming.lines) - except EmailRequestedHelp: - return + br = request.determineBridgeRequestOptions( smtpAutoresp.incoming.lines) bridgeType = "vanilla" if not len(br.transports) else br.transports[0]
# Over email, transports are requested by typing them. Typos happen diff --git a/bridgedb/strings.py b/bridgedb/strings.py index b6a7f1f..f3ea849 100644 --- a/bridgedb/strings.py +++ b/bridgedb/strings.py @@ -38,30 +38,17 @@ def _(text): return text
-# TRANSLATORS: Please do not translate the word "TYPE". EMAIL_MISC_TEXT = { 0: _("""\ -[This is an automated message; please do not reply.]"""), +[This is an automated email.]"""), 1: _("""\ Here are your bridges:"""), 2: _("""\ You have exceeded the rate limit. Please slow down! The minimum time between emails is %s hours. All further emails during this time period will be ignored."""), 3: _("""\ -COMMANDs: (combine COMMANDs to specify multiple options simultaneously)"""), - # TRANSLATORS: Please DO NOT translate the word "BridgeDB". - 4: _("Welcome to BridgeDB!"), - # TRANSLATORS: Please DO NOT translate the words "transport" or "TYPE". - 5: _("Currently supported transport TYPEs:"), - 6: _("Hey, %s!"), - 7: _("Hello, friend!"), - 8: _("Public Keys"), - # TRANSLATORS: This string will end up saying something like: - # "This email was generated with rainbows, unicorns, and sparkles - # for alice@example.com on Friday, 09 May, 2014 at 18:59:39." - 9: _("""\ -This email was generated with rainbows, unicorns, and sparkles -for %s on %s at %s."""), +If these bridges are not what you need, reply to this email with one of +the following commands in the message body:"""), }
WELCOME = { @@ -90,11 +77,10 @@ Additionally, BridgeDB has plenty of plain-ol'-vanilla bridges %s without any Pluggable Transports %s which maybe doesn't sound as cool, but they can still help to circumvent internet censorship in many cases.\n\n"""), } -"""These strings should go on the first "Welcome" email sent by the -:mod:`~bridgedb.EmailServer`, as well as on the ``index.html`` template used -by the :mod:`~bridgedb.distributors.https.server`. They are used as an introduction to -explain what Tor bridges are, what bridges do, and why someone might want to -use bridges. +"""These strings should go on the ``options.html`` template used by the +:mod:`~bridgedb.distributors.https.server`. They are used as an +introduction to explain what Tor bridges are, what bridges do, and why +someone might want to use bridges. """
FAQ = { @@ -186,22 +172,21 @@ HOWTO_TBB = { Manual explains how you can add your bridges to Tor Browser. If you are using Windows, Linux, or OS X, %sclick here%s to learn more. If you are using Android, %sclick here%s."""), + 2: _("""\ +Add these bridges to your Tor Browser by opening your browser +preferences, clicking on "Tor", and then adding them to the "Provide a +bridge" field."""), }
EMAIL_COMMANDS = { - "get help": _("Displays this message."), -# TRANSLATORS: Please try to make it clear that "vanilla" here refers to the -# same non-Pluggable Transport bridges described above as being -# "plain-ol'-vanilla" bridges. - "get bridges": _("Request vanilla bridges."), - "get ipv6": _("Request IPv6 bridges."), - # TRANSLATORS: Please DO NOT translate the word the word "TYPE". - "get transport [TYPE]": _("Request a Pluggable Transport by TYPE."), + "get bridges": _("(Request unobfuscated Tor bridges.)"), + "get ipv6": _("(Request IPv6 bridges.)"), + # TRANSLATORS: Please DO NOT translate the word "TYPE". + "get transport TYPE": _("(Request obfuscated bridges. Replace TYPE with " + "'obfs4'.)"), # TRANSLATORS: Please DO NOT translate "BridgeDB". # TRANSLATORS: Please DO NOT translate "GnuPG". - "get key": _("Get a copy of BridgeDB's public GnuPG key."), - #"subscribe": _("Subscribe to receive new bridges once per week"), - #"unsubscribe": _("Cancel a subscription to new bridges"), + "get key": _("(Get a copy of BridgeDB's public GnuPG key.)"), }
#----------------------------------------------------------------------------- diff --git a/bridgedb/test/test_email_autoresponder.py b/bridgedb/test/test_email_autoresponder.py index c8e9624..d5023e0 100644 --- a/bridgedb/test/test_email_autoresponder.py +++ b/bridgedb/test/test_email_autoresponder.py @@ -70,11 +70,12 @@ class CreateResponseBodyTests(unittest.TestCase): self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', ret)
def test_createResponseBody_bridges_invalid(self): - """An invalid request for 'transport obfs3' should get help text.""" + """An invalid request for 'transport obfs3' should still return + bridges.""" lines = self._getIncomingLines("testing@localhost") lines[4] = "transport obfs3" ret = autoresponder.createResponseBody(lines, self.ctx, self.toAddress) - self.assertSubstring("COMMANDs", ret) + self.assertSubstring("Here are your bridges:", ret)
def test_createResponseBody_bridges_obfs3(self): """A request for 'get transport obfs3' should receive a response.""" diff --git a/bridgedb/test/test_email_request.py b/bridgedb/test/test_email_request.py index 0c87d36..e0bb642 100644 --- a/bridgedb/test/test_email_request.py +++ b/bridgedb/test/test_email_request.py @@ -17,26 +17,25 @@ import ipaddr
from twisted.trial import unittest
+from bridgedb import strings from bridgedb.distributors.email import request
class DetermineBridgeRequestOptionsTests(unittest.TestCase): """Unittests for :func:`b.e.request.determineBridgeRequestOptions`."""
- def test_determineBridgeRequestOptions_get_help(self): - """Requesting 'get help' should raise EmailRequestedHelp.""" - lines = ['', - 'get help'] - self.assertRaises(request.EmailRequestedHelp, - request.determineBridgeRequestOptions, lines) - - def test_determineBridgeRequestOptions_get_halp(self): - """Requesting 'get halp' should raise EmailRequestedHelp.""" + def test_determineBridgeRequestOptions_multiline_invalid(self): lines = ['', - 'get halp'] - self.assertRaises(request.EmailRequestedHelp, - request.determineBridgeRequestOptions, lines) - + 'help', + 'i need bridges', + 'give me your gpgs'] + reqvest = request.determineBridgeRequestOptions(lines) + # We consider every request valid... + self.assertEqual(reqvest.isValid(), True) + self.assertFalse(reqvest.wantsKey()) + # ...so by default, we return a bridge. + self.assertEqual(len(reqvest.transports), 1) + def test_determineBridgeRequestOptions_get_key(self): """Requesting 'get key' should raise EmailRequestedKey.""" lines = ['', @@ -45,14 +44,14 @@ class DetermineBridgeRequestOptionsTests(unittest.TestCase): request.determineBridgeRequestOptions, lines)
def test_determineBridgeRequestOptions_multiline_invalid(self): - """Requests without a 'get' anywhere should be considered invalid.""" + """Requests without a 'get' are incorrect but still valid, and should + return bridges.""" lines = ['', 'transport obfs3', 'ipv6 vanilla bridges', 'give me your gpgs'] reqvest = request.determineBridgeRequestOptions(lines) - # It's invalid because it didn't include a 'get' anywhere. - self.assertEqual(reqvest.isValid(), False) + self.assertEqual(reqvest.isValid(), True) self.assertFalse(reqvest.wantsKey()) # Though they did request IPv6, technically. self.assertIs(reqvest.ipVersion, 6) @@ -61,9 +60,8 @@ class DetermineBridgeRequestOptionsTests(unittest.TestCase): self.assertEqual(reqvest.transports[0], 'obfs3')
def test_determineBridgeRequestOptions_multiline_valid(self): - """Though requests with a 'get' are considered valid.""" lines = ['', - 'get transport obfs3', + 'transport obfs3', 'vanilla bridges', 'transport scramblesuit unblocked ca'] reqvest = request.determineBridgeRequestOptions(lines) @@ -73,6 +71,7 @@ class DetermineBridgeRequestOptionsTests(unittest.TestCase): # Though they didn't request IPv6, so it should default to IPv4. self.assertIs(reqvest.ipVersion, 4) # And they requested two transports. + print(reqvest.transports) self.assertEqual(len(reqvest.transports), 2) self.assertEqual(reqvest.transports[0], 'obfs3') self.assertEqual(reqvest.transports[1], 'scramblesuit') @@ -103,13 +102,16 @@ class DetermineBridgeRequestOptionsTests(unittest.TestCase): self.assertEqual(reqvest.notBlockedIn[0], 'ca')
def test_determineBridgeRequestOptions_get_transport(self): - """An invalid request for 'transport obfs3' (missing the 'get').""" + """An invalid request for 'transprot obfs3' (typo) should return our + default bridge.""" + default_transport = "obfs4" + strings._setDefaultTransport(default_transport) lines = ['', - 'transport obfs3'] + 'transprot obfs3'] reqvest = request.determineBridgeRequestOptions(lines) self.assertEqual(len(reqvest.transports), 1) - self.assertEqual(reqvest.transports[0], 'obfs3') - self.assertEqual(reqvest.isValid(), False) + self.assertEqual(reqvest.transports[0], default_transport) + self.assertEqual(reqvest.isValid(), True)
def test_determineBridgeRequestOptions_get_ipv6(self): """An valid request for 'get ipv6'.""" diff --git a/bridgedb/test/test_email_templates.py b/bridgedb/test/test_email_templates.py index 1d55dd9..4a5979d 100644 --- a/bridgedb/test/test_email_templates.py +++ b/bridgedb/test/test_email_templates.py @@ -36,7 +36,7 @@ class EmailTemplatesTests(unittest.TestCase): self.offlineFingerprint = '7B78437015E63DF47BB1270ACBD97AA24E8E472E'
def shouldIncludeCommands(self, text): - self.assertSubstring('COMMANDs', text) + self.assertSubstring('commands', text)
def shouldIncludeInstructions(self, text): self.assertSubstring('Tor Browser', text) @@ -46,10 +46,10 @@ class EmailTemplatesTests(unittest.TestCase): self.assertSubstring('Here are your bridges:', text)
def shouldIncludeGreeting(self, text): - self.assertSubstring('Hey, blackhole!', text) + self.assertSubstring('This is an automated email', text)
def shouldIncludeAutomationNotice(self, text): - self.assertSubstring('automated message', text) + self.assertSubstring('automated email', text)
def shouldIncludeKey(self, text): self.assertSubstring('-----BEGIN PGP PUBLIC KEY BLOCK-----', text) @@ -59,26 +59,9 @@ class EmailTemplatesTests(unittest.TestCase): self.shouldIncludeCommands(text)
def test_templates_addGreeting(self): - text = templates.addGreeting(self.t, self.client.local) + text = templates.addGreeting(self.t) self.shouldIncludeGreeting(text)
- def test_templates_addGreeting_noClient(self): - text = templates.addGreeting(self.t, None) - self.assertSubstring('Hello, friend!', text) - - def test_templates_addGreeting_withWelcome(self): - text = templates.addGreeting(self.t, self.client.local, welcome=True) - self.shouldIncludeGreeting(text) - self.assertSubstring('Welcome to BridgeDB!', text) - - def test_templates_addGreeting_trueClient(self): - text = templates.addGreeting(self.t, True) - self.assertSubstring('Hey', text) - - def test_templates_addGreeting_23Client(self): - text = templates.addGreeting(self.t, 23) - self.assertSubstring('Hey', text) - def test_templates_addHowto(self): text = templates.addHowto(self.t) self.shouldIncludeInstructions(text) @@ -97,12 +80,6 @@ class EmailTemplatesTests(unittest.TestCase): text = templates.buildKeyMessage(self.t, self.client) self.assertSubstring(self.offlineFingerprint, text)
- def test_templates_buildWelcomeText(self): - text = templates.buildWelcomeText(self.t, self.client) - self.shouldIncludeGreeting(text) - self.assertSubstring('Welcome to BridgeDB!', text) - self.shouldIncludeCommands(text) - def test_templates_buildSpamWarning(self): text = templates.buildSpamWarning(self.t, self.client) self.shouldIncludeGreeting(text)