Source code for bridgedb.bridgerequest
# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridgerequest ; -*-
#_____________________________________________________________________________
#
# This file is part of BridgeDB, a Tor bridge distribution system.
#
# :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org>
# please also see AUTHORS file
# :copyright: (c) 2007-2015, The Tor Project, Inc.
# (c) 2014-2015, Isis Lovecruft
# :license: see LICENSE for licensing information
#_____________________________________________________________________________
"""API for creating classes which store information on the type of bridges
requested by a client.
.. inheritance-diagram:: BridgeRequestBase
:parts: 1
"""
import ipaddr
import logging
from zope.interface import implementer
from zope.interface import Attribute
from zope.interface import Interface
from bridgedb.crypto import getHMACFunc
from bridgedb.filters import byIPv
from bridgedb.filters import byNotBlockedIn
from bridgedb.filters import byTransport
[docs]class IRequestBridges(Interface):
"""Interface specification of client options for requested bridges."""
filters = Attribute(
"A list of callables used to filter bridges from a hashring.")
ipVersion = Attribute(
"The IP version of bridge addresses to distribute to the client.")
transports = Attribute(
"A list of strings of Pluggable Transport types requested.")
notBlockedIn = Attribute(
"A list of two-character country codes. The distributed bridges "
"should not be blocked in these countries.")
valid = Attribute(
"A boolean. Should be ``True`` if the client's request was valid.")
client = Attribute(
"This should be some information unique to the client making the "
"request for bridges, such that we are able to HMAC this unique "
"data, via :meth:`getHashringPlacement()`, in order to place the "
"client into a hashring (determining which bridge addresses they get "
"in the request response).")
def addFilter():
"""Add a filter to the list of ``filters``."""
def clearFilters():
"""Clear the list of ``filters``."""
def generateFilters():
"""Build the list of callables, ``filters``, according to the current
contents of the lists of ``transports``, ``notBlockedIn``, and the
``ipVersion``.
"""
def getHashringPlacement():
"""Use some unique parameters of the client making this request to
obtain a value which we can use to place them into one of the hashrings
with :class:`~bridgedb.bridges.Bridge`s in it, in order to give that
client different bridges than other clients.
"""
def isValid():
"""Determine if the request is ``valid`` according to some parameters."""
def withIPv4():
"""Set the ``ipVersion`` to IPv4."""
def withIPv6():
"""Set the ``ipVersion`` to IPv6."""
def withPluggableTransportType(typeOfPT):
"""Add this **typeOfPT** to the list of requested ``transports``."""
def withoutBlockInCountry(countryCode):
"""Add this **countryCode** to the list of countries which distributed
bridges should not be blocked in (``notBlockedIn``).
"""
@implementer(IRequestBridges)
[docs]class BridgeRequestBase(object):
"""A generic base class for storing options of a client bridge request.
:vartype filters: list
:ivar filters: A list of callables used to filter bridges from a hashring.
:vartype transports: list
:ivar transports: A list of strings of Pluggable Transport types requested.
:vartype notBlockedIn: list
:ivar notBlockedIn: A list of two-character country codes. The distributed
bridges should not be blocked in these countries.
:vartype client: str
:ivar client: This should be some information unique to the client making
the request for bridges, such that we are able to HMAC this unique
data in order to place the client into a hashring (determining which
bridge addresses they get in the request response). It defaults to the
string ``'default'``.
:vartype valid: bool
:ivar valid: Should be ``True`` if the client's request was valid.
"""
def __init__(self, ipVersion=None):
self.ipVersion = ipVersion
self.filters = list()
self.transports = list()
self.notBlockedIn = list()
self.client = 'default'
self.valid = False
@property
def ipVersion(self):
"""The IP version of bridge addresses to distribute to the client.
:rtype: int
:returns: Either ``4`` or ``6``.
"""
return self._ipVersion
@ipVersion.setter
def ipVersion(self, ipVersion):
"""The IP version of bridge addresses to distribute to the client.
:param int ipVersion: The IP address version for the bridge lines we
should distribute in response to this client request.
"""
if not ipVersion in (4, 6):
ipVersion = 4
self._ipVersion = ipVersion
[docs] def getHashringPlacement(self, key, client=None):
"""Create an HMAC of some **client** info using a **key**.
:param str key: The key to use for HMACing.
:param str client: Some (hopefully unique) information about the
client who is requesting bridges, such as an IP or email address.
:rtype: long
:returns: A long specifying index of the first node in a hashring to
be distributed to the client. This value should obviously be used
mod the number of nodes in the hashring.
"""
if not client:
client = self.client
# Get an HMAC with the key of the client identifier:
digest = getHMACFunc(key)(client)
# Take the lower 8 bytes of the digest and convert to a long:
position = long(digest[:8], 16)
return position
[docs] def isValid(self, valid=None):
"""Get or set the validity of this bridge request.
If called without parameters, this method will return the current
state, otherwise (if called with the **valid** parameter), it will set
the current state of validity for this request.
:param bool valid: If given, set the validity state of this
request. Otherwise, get the current state.
"""
if valid is not None:
self.valid = bool(valid)
return self.valid
[docs] def withIPv4(self):
"""Set the ``ipVersion`` to IPv4."""
self.ipVersion = 4
[docs] def withIPv6(self):
"""Set the ``ipVersion`` to IPv6."""
self.ipVersion = 6
[docs] def withoutBlockInCountry(self, country):
"""Add this **countryCode** to the list of countries which distributed
bridges should not be blocked in (``notBlockedIn``).
"""
self.notBlockedIn.append(country.lower())
[docs] def withPluggableTransportType(self, pt):
"""Add this **pt** to the list of requested ``transports``.
:param str pt: A :class:`~bridgedb.bridges.PluggableTransport`.
:data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`.
"""
self.transports.append(pt)
[docs] def addFilter(self, filtre):
"""Add a **filtre** to the list of ``filters``.
:type filter: callable
:param filter: A filter function, e.g. one generated via
:mod:`bridgedb.filters`.
"""
self.filters.append(filtre)
[docs] def clearFilters(self):
"""Clear the list of ``filters``."""
self.filters = []
[docs] def justOnePTType(self):
"""Get just one bridge type (e.g. a
:data:`methodname <bridgedb.bridges.PluggableTransport.methodname>` of
:class:`~bridgedb.bridges.PluggableTransport`) at a time!
"""
ptType = None
try:
ptType = self.transports[-1] # Use the last PT requested
except IndexError:
logging.debug("No pluggable transports were requested.")
return ptType
[docs] def generateFilters(self):
"""Build the list of callables, ``filters``, according to the current
contents of the lists of ``transports``, ``notBlockedIn``, and the
``ipVersion``.
"""
self.clearFilters()
pt = self.justOnePTType()
msg = ("Adding a filter to %s for %s for IPv%d"
% (self.__class__.__name__, self.client, self.ipVersion))
if self.notBlockedIn:
for country in self.notBlockedIn:
logging.info("%s %s bridges not blocked in %s..." %
(msg, pt or "vanilla", country))
self.addFilter(byNotBlockedIn(country, pt, self.ipVersion))
elif pt:
logging.info("%s %s bridges..." % (msg, pt))
self.addFilter(byTransport(pt, self.ipVersion))
else:
logging.info("%s bridges..." % msg)
self.addFilter(byIPv(self.ipVersion))