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