Source code for onbrisca.views

# SPDX-FileCopyrightText: 2022 The Tor Project, Inc.
#
# SPDX-License-Identifier: BSD-3-Clause
import datetime
import json
import logging

from django.conf import settings
from django.db.models import ObjectDoesNotExist
from django.http import JsonResponse

from onbasca.onbasca.models.consensus import Consensus
from onbrisca import config

from .models import Bridge

logger = logging.getLogger(__name__)


def _create_bridge(bridge_line, mu, muf, bridge_ratio):
    error = None
    # When a bridge has not been tested yet,
    last_tested = None
    ratio = 0
    logger.debug("Creating bridge with bridge_line %s", bridge_line)
    bridge, _ = Bridge.objects.from_bridgeline(bridge_line)
    if not isinstance(bridge, Bridge):
        logger.info(
            "Bridge with bridgeline %s wasn't added to the DB.", bridge_line
        )
        logger.debug(bridge)
        error = bridge
        # Return functional=True for the bridges it can't parse bridgeline,
        # since they're never measured.
        valid = True
    else:
        ratio = bridge.set_ratios(mu, muf)
        valid, error = bridge.is_valid(ratio, bridge_ratio)
        last_tested = bridge._last_measured
    if last_tested:
        # Format datetime as expected by golang time. When `Z` is not included
        # or there's hour after it, rdsys logs:
        # `request failed: parsing time "\"2023-01-25T08:28:45Z00:07\""
        # as "\"2006-01-02T15:04:05Z07:00\"": cannot parse "00:07\"" as "\""`
        last_tested = last_tested.strftime("%Y-%m-%dT%H:%M:%SZ")
    bridge_result = {
        bridge_line: {
            "ratio": ratio,
            "functional": valid,
            "last_tested": last_tested,
        }
    }
    if error:
        bridge_result[bridge_line]["error"] = error
    return bridge_result


[docs] def create_bridges(request): """Returns JSON data to GET requests following ``bridgestrap`` API. (https://gitlab.torproject.org/tpo/anti-censorship/bridgestrap#input) """ start = datetime.datetime.utcnow() response_data = {"bridge_results": [], "time": 0} bridge_results = {} # Check cookie: # The token sent by rdsys is not in the `Cookie`` header but in `Token` and # `Authorization` headers, as a `Bearer` token. These can also be read in # Django via `request.META['HTTP_AUTHORIZATION']` or # request.META['HTTP_TOKEN']. The value could also be checked via Django # `AnonymousUser` authorization. token = request.headers.get(settings.API_TOKEN_NAME, None) if token != settings.API_TOKEN_VALUE: logger.debug("Cookie token doesn't match.") return JsonResponse(response_data, status=403) # Forbidden if not request.method == "GET": logger.debug("No GET request.") return JsonResponse(response_data, status=403) # Forbidden logger.debug("GET request, Content-Type %s", request.content_type) if request.content_type == "application/json": data = json.loads(request.body) # with GET requests, the data is pass in the body, except in the tests # with django.test.Client, that is pass in the `request.GET` QueryDict # and the Content-Type is ignored. # rdsys is only doing GET requests with JSON. else: data = dict(request.GET) bridge_lines = data.get("bridge_lines", None) if not bridge_lines: logger.debug("No `bridge_lines` in request.") return JsonResponse(response_data, status=400) # BadRequest if not isinstance(bridge_lines, list): logger.debug("`bridge_lines` is not a list.") return JsonResponse(response_data, status=400) # BadRequest # Response just 200 as far as there're bridge_lines? response_code = 200 try: consensus = Consensus.objects.latest() except ObjectDoesNotExist: bridge_ratio = config.BRIDGE_RATIO_THRESHOLD else: bridge_ratio = consensus._bridge_ratio or config.BRIDGE_RATIO_THRESHOLD response_data["bridge_ratio_threshold"] = bridge_ratio mu = Bridge.objects.mu() logger.debug("mu: %s", mu) muf = Bridge.objects.muf() logger.debug("muf: %s", muf) for bridge_line in bridge_lines: bridge_result = _create_bridge(bridge_line, mu, muf, bridge_ratio) bridge_results[bridge_line] = bridge_result[bridge_line] response_data["bridge_results"] = bridge_results end = datetime.datetime.utcnow() dt = (end - start).total_seconds() response_data["time"] = dt logger.info("response: %s", response_data) response = JsonResponse(response_data, status=response_code) return response