def test_determineBridgeRequestOptions_get_transport(self): """An invalid request for 'transport obfs3' (missing the 'get').""" lines = ['', 'transport obfs3'] reqvest = request.determineBridgeRequestOptions(lines) self.assertEqual(len(reqvest.transports), 1) self.assertEqual(reqvest.transports[0], 'obfs3') self.assertEqual(reqvest.isValid(), False)
def _recordEmailRequest(self, smtpAutoresp, success): emailAddrs = smtpAutoresp.getMailTo() if len(emailAddrs) == 0: # This is just for unit tests. emailAddr = Address("*****@*****.**") else: emailAddr = emailAddrs[0] # Get the requested transport protocol. 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 # and users can request anything, really. if not isBridgeTypeSupported(bridgeType): logging.warning("User requested unsupported transport type %s " "over email." % bridgeType) return logging.debug("Recording %svalid email request for %s from %s." % ("" if success else "in", bridgeType, emailAddr)) sld = emailAddr.domain.split(b".")[0] # Now update our metrics. key = self.createKey(self.keyPrefix, bridgeType, sld, success, self.findAnomaly(request)) self.inc(key)
def test_determineBridgeRequestOptions_get_ipv6(self): """An valid request for 'get ipv6'.""" lines = ['', 'get ipv6'] reqvest = request.determineBridgeRequestOptions(lines) self.assertIs(reqvest.ipVersion, 6) self.assertEqual(reqvest.isValid(), True)
def createResponseBody(lines, context, client, lang='en'): """Parse the **lines** from an incoming email request and determine how to respond. :param list lines: The list of lines from the original request sent by the client. :type context: :class:`bridgedb.distributors.email.server.MailServerContext` :param context: The context which contains settings for the email server. :type client: :api:`twisted.mail.smtp.Address` :param client: The client's email address which should be in the ``'To:'`` header of the response email. :param str lang: The 2-5 character locale code to use for translating the email. This is obtained from a client sending a email to a valid plus address which includes the translation desired, i.e. by sending an email to `[email protected] <mailto:[email protected]>`__, the client should receive a response in Farsi. :rtype: str :returns: ``None`` if we shouldn't respond to the client (i.e., if they have already received a rate-limiting warning email). Otherwise, returns a string containing the (optionally translated) body for the email response which we should send out. """ translator = translations.installTranslations(lang) bridges = None try: 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) except TooSoonEmail as error: logging.info("Got a mail too frequently: %s." % error) return templates.buildSpamWarning(translator, client) except (IgnoreEmail, addr.BadEmail) as error: logging.info(error) # Don't generate a response if their email address is unparsable or # invalid, or if we've already warned them about rate-limiting: return None else: answer = "(no bridges currently available)\r\n" if bridges: transport = bridgeRequest.justOnePTType() answer = "".join(" %s\r\n" % b.getBridgeLine( bridgeRequest, context.includeFingerprints) for b in bridges) return templates.buildAnswerMessage(translator, client, answer)
def test_determineBridgeRequestOptions_multiline_invalid(self): lines = ['', 'help', 'i need bridges'] reqvest = request.determineBridgeRequestOptions(lines) # We consider every request valid... self.assertEqual(reqvest.isValid(), True) # ...so by default, we return a bridge. self.assertEqual(len(reqvest.transports), 1)
def test_determineBridgeRequestOptions_get_transport(self): """An invalid request for 'transprot obfs3' (typo) should return our default bridge.""" default_transport = "obfs4" strings._setDefaultTransport(default_transport) lines = ['', 'transprot obfs3'] reqvest = request.determineBridgeRequestOptions(lines) self.assertEqual(len(reqvest.transports), 1) self.assertEqual(reqvest.transports[0], default_transport) self.assertEqual(reqvest.isValid(), True)
def createResponseBody(lines, context, client, lang='en'): """Parse the **lines** from an incoming email request and determine how to respond. :param list lines: The list of lines from the original request sent by the client. :type context: :class:`bridgedb.distributors.email.server.MailServerContext` :param context: The context which contains settings for the email server. :type client: :api:`twisted.mail.smtp.Address` :param client: The client's email address which should be in the ``'To:'`` header of the response email. :param str lang: The 2-5 character locale code to use for translating the email. This is obtained from a client sending a email to a valid plus address which includes the translation desired, i.e. by sending an email to `[email protected] <mailto:[email protected]>`__, the client should receive a response in Farsi. :rtype: str :returns: ``None`` if we shouldn't respond to the client (i.e., if they have already received a rate-limiting warning email). Otherwise, returns a string containing the (optionally translated) body for the email response which we should send out. """ translator = translations.installTranslations(lang) bridges = None try: bridgeRequest = request.determineBridgeRequestOptions(lines) bridgeRequest.client = str(client) # Otherwise they must have requested bridges: interval = context.schedule.intervalStart(time.time()) bridges = context.distributor.getBridges(bridgeRequest, interval) except TooSoonEmail as error: logging.info("Got a mail too frequently: %s." % error) return templates.buildSpamWarning(translator, client) except (IgnoreEmail, addr.BadEmail) as error: logging.info(error) # Don't generate a response if their email address is unparsable or # invalid, or if we've already warned them about rate-limiting: return None else: answer = "(no bridges currently available)\r\n" if bridges: transport = bridgeRequest.justOnePTType() answer = "".join( " %s\r\n" % b.getBridgeLine(bridgeRequest, context.includeFingerprints) for b in bridges) internalMetrix.recordHandoutsPerBridge(bridgeRequest, bridges) else: internalMetrix.recordEmptyEmailResponse() return templates.buildAnswerMessage(translator, client, answer)
def test_determineBridgeRequestOptions_multiline_invalid(self): """Requests without a 'get' are incorrect but still valid, and should return bridges.""" lines = ['', 'transport obfs3', 'ipv6 vanilla bridges'] reqvest = request.determineBridgeRequestOptions(lines) self.assertEqual(reqvest.isValid(), True) # Though they did request IPv6, technically. self.assertIs(reqvest.ipVersion, 6) # And they did request a transport, technically. self.assertEqual(len(reqvest.transports), 1) self.assertEqual(reqvest.transports[0], 'obfs3')
def test_determineBridgeRequestOptions_multiline_invalid(self): """Requests without a 'get' anywhere should be considered invalid.""" 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.assertFalse(reqvest.wantsKey()) # Though they did request IPv6, technically. self.assertIs(reqvest.ipVersion, 6) # And they did request a transport, technically. self.assertEqual(len(reqvest.transports), 1) self.assertEqual(reqvest.transports[0], 'obfs3')
def test_determineBridgeRequestOptions_multiline_invalid(self): """Requests without a 'get' anywhere should be considered invalid.""" 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.assertFalse(reqvest.wantsKey()) # Though they did request IPv6, technically. self.assertIs(reqvest.ipVersion, 6) # And they did request a transport, technically. self.assertEqual(len(reqvest.transports), 1) self.assertEqual(reqvest.transports[0], 'obfs3')
def test_determineBridgeRequestOptions_multiline_valid(self): lines = ['', 'transport obfs3', 'vanilla bridges', 'transport scramblesuit unblocked ca'] reqvest = request.determineBridgeRequestOptions(lines) # It's valid because it included a 'get'. self.assertEqual(reqvest.isValid(), True) # 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') # And they wanted this stuff to not be blocked in Canada. self.assertEqual(len(reqvest.notBlockedIn), 1) self.assertEqual(reqvest.notBlockedIn[0], 'ca')
def test_determineBridgeRequestOptions_multiline_valid(self): """Though requests with a 'get' are considered valid.""" lines = ['', 'get transport obfs3', 'vanilla bridges', 'transport scramblesuit unblocked ca'] reqvest = request.determineBridgeRequestOptions(lines) # It's valid because it included a 'get'. self.assertEqual(reqvest.isValid(), True) self.assertFalse(reqvest.wantsKey()) # Though they didn't request IPv6, so it should default to IPv4. self.assertIs(reqvest.ipVersion, 4) # And they requested two transports. self.assertEqual(len(reqvest.transports), 2) self.assertEqual(reqvest.transports[0], 'obfs3') self.assertEqual(reqvest.transports[1], 'scramblesuit') # And they wanted this stuff to not be blocked in Canada. self.assertEqual(len(reqvest.notBlockedIn), 1) self.assertEqual(reqvest.notBlockedIn[0], 'ca')
def test_determineBridgeRequestOptions_multiline_valid_OMG_CAPSLOCK(self): """Though requests with a 'get' are considered valid, even if they appear to not know the difference between Capslock and Shift. """ lines = ['', 'get TRANSPORT obfs3', 'vanilla bridges', 'TRANSPORT SCRAMBLESUIT UNBLOCKED CA'] reqvest = request.determineBridgeRequestOptions(lines) # It's valid because it included a 'get'. self.assertEqual(reqvest.isValid(), True) # Though they didn't request IPv6, so it should default to IPv4. self.assertIs(reqvest.ipVersion, 4) # And they requested two transports. self.assertEqual(len(reqvest.transports), 2) self.assertEqual(reqvest.transports[0], 'obfs3') self.assertEqual(reqvest.transports[1], 'scramblesuit') # And they wanted this stuff to not be blocked in Canada. self.assertEqual(len(reqvest.notBlockedIn), 1) self.assertEqual(reqvest.notBlockedIn[0], 'ca')
def test_determineBridgeRequestOptions_multiline_valid_OMG_CAPSLOCK(self): """Though requests with a 'get' are considered valid, even if they appear to not know the difference between Capslock and Shift. """ lines = ['', 'get TRANSPORT obfs3', 'vanilla bridges', 'TRANSPORT SCRAMBLESUIT UNBLOCKED CA'] reqvest = request.determineBridgeRequestOptions(lines) # It's valid because it included a 'get'. self.assertEqual(reqvest.isValid(), True) self.assertFalse(reqvest.wantsKey()) # Though they didn't request IPv6, so it should default to IPv4. self.assertIs(reqvest.ipVersion, 4) # And they requested two transports. self.assertEqual(len(reqvest.transports), 2) self.assertEqual(reqvest.transports[0], 'obfs3') self.assertEqual(reqvest.transports[1], 'scramblesuit') # And they wanted this stuff to not be blocked in Canada. self.assertEqual(len(reqvest.notBlockedIn), 1) self.assertEqual(reqvest.notBlockedIn[0], 'ca')