Esempio n. 1
0
    def renderAnswer(self, request, bridgeLines=None):
        """Generate a response for a client which includes **bridgesLines**.

        .. note: The generated response can be plain or HTML. A plain response
            looks like::

                voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF
                voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567

            That is, there is no HTML, what you see is what you get, and what
            you get is suitable for pasting directly into Tor Launcher (or
            into a torrc, if you prepend ``"Bridge "`` to each line). The
            plain format can be requested from BridgeDB's web service by
            adding an ``&format=plain`` HTTP GET parameter to the URL. Also
            note that you won't get a QRCode, usage instructions, error
            messages, or any other fanciness if you use the plain format.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object containing the HTTP method, full
            URI, and any URL/POST arguments and headers present.
        :type bridgeLines: list or None
        :param bridgeLines: A list of strings used to configure a Tor client
            to use a bridge. If ``None``, then the returned page will instead
            explain that there were no bridges of the type they requested,
            with instructions on how to proceed.
        :rtype: str
        :returns: A plaintext or HTML response to serve.
        """
        rtl = False
        format = self.getResponseFormat(request)

        if format == 'plain':
            request.setHeader("Content-Type", "text/plain")
            try:
                rendered = bytes('\n'.join(bridgeLines))
            except Exception as err:
                rendered = replaceErrorPage(request, err, html=False)
        else:
            request.setHeader("Content-Type", "text/html; charset=utf-8")
            qrcode = None
            qrjpeg = generateQR(bridgeLines)

            if qrjpeg:
                qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)
            try:
                langs = translations.getLocaleFromHTTPRequest(request)
                rtl = translations.usingRTLLang(langs)
                template = lookup.get_template('bridges.html')
                rendered = template.render(strings,
                                           rtl=rtl,
                                           lang=langs[0],
                                           answer=bridgeLines,
                                           qrcode=qrcode)
            except Exception as err:
                rendered = replaceErrorPage(request, err)

        return rendered
Esempio n. 2
0
    def renderAnswer(self, request, bridgeLines=None):
        """Generate a response for a client which includes **bridgesLines**.

        .. note: The generated response can be plain or HTML. A plain response
            looks like::

                voltron 1.2.3.4:1234 ABCDEF01234567890ABCDEF01234567890ABCDEF
                voltron 5.5.5.5:5555 0123456789ABCDEF0123456789ABCDEF01234567

            That is, there is no HTML, what you see is what you get, and what
            you get is suitable for pasting directly into Tor Launcher (or
            into a torrc, if you prepend ``"Bridge "`` to each line). The
            plain format can be requested from BridgeDB's web service by
            adding an ``&format=plain`` HTTP GET parameter to the URL. Also
            note that you won't get a QRCode, usage instructions, error
            messages, or any other fanciness if you use the plain format.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object containing the HTTP method, full
            URI, and any URL/POST arguments and headers present.
        :type bridgeLines: list or None
        :param bridgeLines: A list of strings used to configure a Tor client
            to use a bridge. If ``None``, then the returned page will instead
            explain that there were no bridges of the type they requested,
            with instructions on how to proceed.
        :rtype: str
        :returns: A plaintext or HTML response to serve.
        """
        rtl = False
        format = self.getResponseFormat(request)

        if format == 'plain':
            request.setHeader("Content-Type", "text/plain")
            try:
                rendered = bytes('\n'.join(bridgeLines))
            except Exception as err:
                rendered = replaceErrorPage(request, err, html=False)
        else:
            request.setHeader("Content-Type", "text/html; charset=utf-8")
            qrcode = None
            qrjpeg = generateQR(bridgeLines)

            if qrjpeg:
                qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)
            try:
                langs = translations.getLocaleFromHTTPRequest(request)
                rtl = translations.usingRTLLang(langs)
                template = lookup.get_template('bridges.html')
                rendered = template.render(strings,
                                           rtl=rtl,
                                           lang=langs[0],
                                           answer=bridgeLines,
                                           qrcode=qrcode)
            except Exception as err:
                rendered = replaceErrorPage(request, err)

        return rendered
Esempio n. 3
0
 def test_generateQR_bridgeSchema(self):
     """Calling generateQR() with bridgeSchema=True should prepend
     ``'bridge://`` to each of the QR encoded bridge lines.
     """
     # If we were to install the python-qrtools Debian package, we'd be
     # able to decode the resulting QRCode to check that it contains the
     # 'bridge://' prefix for each bridge line… but that would add another
     # Debian dependency just to unittest 5 lines of code.
     #
     # Instead:
     self.assertTrue(qrcodes.generateQR(self.bridgelines, bridgeSchema=True))
Esempio n. 4
0
    def renderAnswer(self, request, bridgeLines=None, format=None):
        """Generate a response for a client which includes **bridges**.

        The generated response can be plaintext or HTML.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object containing the HTTP method, full
            URI, and any URL/POST arguments and headers present.
        :type bridgeLines: list or None
        :param bridgeLines: A list of strings used to configure a Tor client
            to use a bridge.
        :type format: str or None
        :param format: If ``'plain'``, return a plaintext response. Otherwise,
            use the :file:`bridgedb/templates/bridges.html` template to render
            an HTML response page which includes the **bridges**.
        :rtype: str
        :returns: A plaintext or HTML response to serve.
        """
        rtl = False

        if format == 'plain':
            request.setHeader("Content-Type", "text/plain")
            rendered = bridgeLines
        else:
            request.setHeader("Content-Type", "text/html; charset=utf-8")
            qrcode = None
            qrjpeg = generateQR(bridgeLines)

            if qrjpeg:
                qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)
            try:
                langs = translations.getLocaleFromHTTPRequest(request)
                rtl = translations.usingRTLLang(langs)
                template = lookup.get_template('bridges.html')
                rendered = template.render(strings,
                                           rtl=rtl,
                                           lang=langs[0],
                                           answer=bridgeLines,
                                           qrcode=qrcode)
            except Exception as err:
                rendered = replaceErrorPage(err)

        return rendered
Esempio n. 5
0
 def test_generateQR_save_nonexistent_format(self):
     """Calling generateQR() with imageFormat=u'FOOBAR' should return None.
     """
     self.assertIsNone(qrcodes.generateQR(self.bridgelines, imageFormat=u'FOOBAR'))
Esempio n. 6
0
 def test_generateQR_no_qrcode_module(self):
     """Calling generateQR() without the qrcode module installed should
     return None.
     """
     qrcodes.qrcode = None
     self.assertIsNone(qrcodes.generateQR(self.bridgelines))
Esempio n. 7
0
 def test_generateQR_no_bridgelines(self):
     """Calling generateQR() without bridgelines should return None."""
     self.assertIsNone(qrcodes.generateQR(""))
Esempio n. 8
0
 def test_generateQR_bad_bridgelines(self):
     """Calling generateQR() with a bad type for the bridgelines should
     return None.
     """
     self.assertIsNone(qrcodes.generateQR(list()))
Esempio n. 9
0
 def test_generateQR(self):
     """Calling generateQR() should generate an image."""
     self.assertTrue(qrcodes.generateQR(self.bridgelines))
Esempio n. 10
0
    def render_POST(self, request):
        """Process a client's CAPTCHA solution.

        If the client's CAPTCHA solution is valid (according to
        :meth:`checkSolution`), process and serve their original
        request. Otherwise, redirect them back to a new CAPTCHA page.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object, including POST arguments which
            should include two key/value pairs: one key being
            ``'captcha_challenge_field'``, and the other,
            ``'captcha_response_field'``. These POST arguments should be
            obtained from :meth:`render_GET`.
        :rtype: str
        :returns: A rendered HTML page containing a ReCaptcha challenge image
            for the client to solve.
        """
        valid = False
        error = self.checkRequestHeaders(request)

        if error:  # pragma: no cover
            logging.debug("Error while checking moat request headers.")
            metrix.recordInvalidMoatRequest(request)
            return error.render(request)

        data = {
            "data": [{
                "id": '3',
                "type": 'moat-bridges',
                "version": MOAT_API_VERSION,
                "bridges": None,
                "qrcode": None,
            }]
        }

        try:
            pos = request.content.tell()
            encoded_client_data = request.content.read()
            # We rewind the stream to its previous position to allow the
            # metrix module to read the request's content too.
            request.content.seek(pos)
            client_data = json.loads(encoded_client_data)["data"][0]
            clientIP = self.getClientIP(request)

            (include_qrcode, transport, challenge,
             solution) = self.extractClientSolution(client_data)

            valid = self.checkSolution(challenge, solution, clientIP)
        except captcha.CaptchaExpired:
            logging.debug("The challenge had timed out")
            metrix.recordInvalidMoatRequest(request)
            return self.failureResponse(5, request)
        except Exception as impossible:
            logging.warn(
                "Unhandled exception while processing a POST /fetch request!")
            logging.error(impossible)
            metrix.recordInvalidMoatRequest(request)
            return self.failureResponse(4, request)

        if valid:
            qrcode = None
            bridgeRequest = self.createBridgeRequest(clientIP, client_data)
            bridgeLines = self.getBridgeLines(bridgeRequest)
            metrix.recordValidMoatRequest(request)

            # If we can only return less than the configured
            # MOAT_BRIDGES_PER_ANSWER then log a warning.
            if len(bridgeLines) < self.nBridgesToGive:
                logging.warn(
                    ("Not enough bridges of the type specified to "
                     "fulfill the following request: %s") % bridgeRequest)

            if antibot.isRequestFromBot(request):
                ttype = transport or "vanilla"
                bridgeLines = antibot.getDecoyBridge(ttype,
                                                     bridgeRequest.ipVersion)

            # If we have no bridges at all to give to the client, then
            # return a JSON API 404 error.
            if not bridgeLines:
                return self.failureResponse(6, request)

            if include_qrcode:
                qrjpeg = generateQR(bridgeLines)
                if qrjpeg:
                    qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(
                        qrjpeg)

            data["data"][0]["qrcode"] = qrcode
            data["data"][0]["bridges"] = bridgeLines

            return self.formatDataForResponse(data, request)
        else:
            metrix.recordInvalidMoatRequest(request)
            return self.failureResponse(4, request)
Esempio n. 11
0
    def render_POST(self, request):
        """Process a client's CAPTCHA solution.

        If the client's CAPTCHA solution is valid (according to
        :meth:`checkSolution`), process and serve their original
        request. Otherwise, redirect them back to a new CAPTCHA page.

        :type request: :api:`twisted.web.http.Request`
        :param request: A ``Request`` object, including POST arguments which
            should include two key/value pairs: one key being
            ``'captcha_challenge_field'``, and the other,
            ``'captcha_response_field'``. These POST arguments should be
            obtained from :meth:`render_GET`.
        :rtype: str
        :returns: A rendered HTML page containing a ReCaptcha challenge image
            for the client to solve.
        """
        valid = False
        error = self.checkRequestHeaders(request)

        if error:  # pragma: no cover
            return error.render(request)

        data = {
            "data": [{
                "id": '3',
                "type": 'moat-bridges',
                "version": MOAT_API_VERSION,
                "bridges": None,
                "qrcode": None,
            }]
        }

        try:
            encoded_client_data = request.content.read()
            client_data = json.loads(encoded_client_data)["data"][0]
            clientIP = self.getClientIP(request)

            (include_qrcode, transport,
             challenge, solution) = self.extractClientSolution(client_data)

            valid = self.checkSolution(challenge, solution, clientIP)
        except captcha.CaptchaExpired:
            logging.debug("The challenge had timed out")
            return self.failureResponse(5, request)
        except Exception as impossible:
            logging.warn("Unhandled exception while processing a POST /fetch request!")
            logging.error(impossible)
            return self.failureResponse(4, request)

        if valid:
            qrcode = None
            bridgeRequest = self.createBridgeRequest(clientIP, client_data)
            bridgeLines = self.getBridgeLines(bridgeRequest)

            # If we can only return less than the configured
            # MOAT_BRIDGES_PER_ANSWER then log a warning.
            if len(bridgeLines) < self.nBridgesToGive:
                logging.warn(("Not enough bridges of the type specified to "
                              "fulfill the following request: %s") % bridgeRequest)

            # If we have no bridges at all to give to the client, then
            # return a JSON API 404 error.
            if not bridgeLines:
                return self.failureResponse(6, request)

            if include_qrcode:
                qrjpeg = generateQR(bridgeLines)
                if qrjpeg:
                    qrcode = 'data:image/jpeg;base64,%s' % base64.b64encode(qrjpeg)

            data["data"][0]["qrcode"] = qrcode
            data["data"][0]["bridges"] = bridgeLines

            return self.formatDataForResponse(data, request)
        else:
            return self.failureResponse(4, request)