Parse bridge blocking info from SQL database.
[tor-bridgedb.git] / bridgedb / bridgerequest.py
blob821bd7c1c066537e9b827eb681205fef7ede972e
1 # -*- coding: utf-8 ; test-case-name: bridgedb.test.test_bridgerequest ; -*-
2 #_____________________________________________________________________________
4 # This file is part of BridgeDB, a Tor bridge distribution system.
6 # :authors: Isis Lovecruft 0xA3ADB67A2CDB8B35 <isis@torproject.org>
7 # please also see AUTHORS file
8 # :copyright: (c) 2007-2017, The Tor Project, Inc.
9 # (c) 2014-2017, Isis Lovecruft
10 # :license: see LICENSE for licensing information
11 #_____________________________________________________________________________
13 """API for creating classes which store information on the type of bridges
14 requested by a client.
16 .. inheritance-diagram:: BridgeRequestBase
17 :parts: 1
18 """
20 import ipaddr
21 import logging
23 from zope.interface import implementer
24 from zope.interface import Attribute
25 from zope.interface import Interface
27 from bridgedb.crypto import getHMACFunc
28 from bridgedb.filters import byIPv
29 from bridgedb.filters import byNotBlockedIn
30 from bridgedb.filters import byTransport
31 from bridgedb.filters import byProbingResistance
34 class IRequestBridges(Interface):
35 """Interface specification of client options for requested bridges."""
37 filters = Attribute(
38 "A list of callables used to filter bridges from a hashring.")
39 ipVersion = Attribute(
40 "The IP version of bridge addresses to distribute to the client.")
41 transports = Attribute(
42 "A list of strings of Pluggable Transport types requested.")
43 notBlockedIn = Attribute(
44 "A list of two-character country codes. The distributed bridges "
45 "should not be blocked in these countries.")
46 valid = Attribute(
47 "A boolean. Should be ``True`` if the client's request was valid.")
48 client = Attribute(
49 "This should be some information unique to the client making the "
50 "request for bridges, such that we are able to HMAC this unique "
51 "data, via :meth:`getHashringPlacement()`, in order to place the "
52 "client into a hashring (determining which bridge addresses they get "
53 "in the request response).")
55 def addFilter():
56 """Add a filter to the list of ``filters``."""
58 def clearFilters():
59 """Clear the list of ``filters``."""
61 def generateFilters():
62 """Build the list of callables, ``filters``, according to the current
63 contents of the lists of ``transports``, ``notBlockedIn``, and the
64 ``ipVersion``.
65 """
67 def getHashringPlacement():
68 """Use some unique parameters of the client making this request to
69 obtain a value which we can use to place them into one of the hashrings
70 with :class:`~bridgedb.bridges.Bridge`s in it, in order to give that
71 client different bridges than other clients.
72 """
74 def isValid():
75 """Determine if the request is ``valid`` according to some parameters."""
77 def withIPv4():
78 """Set the ``ipVersion`` to IPv4."""
80 def withIPv6():
81 """Set the ``ipVersion`` to IPv6."""
83 def withPluggableTransportType(typeOfPT):
84 """Add this **typeOfPT** to the list of requested ``transports``."""
86 def withoutBlockInCountry(countryCode):
87 """Add this **countryCode** to the list of countries which distributed
88 bridges should not be blocked in (``notBlockedIn``).
89 """
92 @implementer(IRequestBridges)
93 class BridgeRequestBase(object):
94 """A generic base class for storing options of a client bridge request.
96 :vartype filters: list
97 :ivar filters: A list of callables used to filter bridges from a hashring.
98 :vartype transports: list
99 :ivar transports: A list of strings of Pluggable Transport types requested.
100 :vartype notBlockedIn: list
101 :ivar notBlockedIn: A list of two-character country codes. The distributed
102 bridges should not be blocked in these countries.
103 :vartype client: str
104 :ivar client: This should be some information unique to the client making
105 the request for bridges, such that we are able to HMAC this unique
106 data in order to place the client into a hashring (determining which
107 bridge addresses they get in the request response). It defaults to the
108 string ``'default'``.
109 :vartype valid: bool
110 :ivar valid: Should be ``True`` if the client's request was valid.
113 def __init__(self, ipVersion=None):
114 self.ipVersion = ipVersion
115 self.filters = list()
116 self.transports = list()
117 self.notBlockedIn = list()
118 self.client = 'default'
119 self.valid = False
121 def __str__(self):
122 """Return a human-readable string describing this bridge request."""
123 return "%s(ipVersion=%d, transports=%s, notBlockedIn=%s, valid=%s)" % \
124 (self.__class__.__name__,
125 self.ipVersion,
126 self.transports,
127 self.notBlockedIn,
128 self.valid)
130 @property
131 def ipVersion(self):
132 """The IP version of bridge addresses to distribute to the client.
134 :rtype: int
135 :returns: Either ``4`` or ``6``.
137 return self._ipVersion
139 @ipVersion.setter
140 def ipVersion(self, ipVersion):
141 """The IP version of bridge addresses to distribute to the client.
143 :param int ipVersion: The IP address version for the bridge lines we
144 should distribute in response to this client request.
146 if not ipVersion in (4, 6):
147 ipVersion = 4
148 self._ipVersion = ipVersion
150 def getHashringPlacement(self, key, client=None):
151 """Create an HMAC of some **client** info using a **key**.
153 :param str key: The key to use for HMACing.
154 :param str client: Some (hopefully unique) information about the
155 client who is requesting bridges, such as an IP or email address.
156 :rtype: long
157 :returns: A long specifying index of the first node in a hashring to
158 be distributed to the client. This value should obviously be used
159 mod the number of nodes in the hashring.
161 if not client:
162 client = type('')(self.client)
164 # Get an HMAC with the key of the client identifier:
165 digest = getHMACFunc(key)(client)
166 # Take the lower 8 bytes of the digest and convert to a long:
167 position = int(digest[:8], 16)
168 return position
170 def isValid(self, valid=None):
171 """Get or set the validity of this bridge request.
173 If called without parameters, this method will return the current
174 state, otherwise (if called with the **valid** parameter), it will set
175 the current state of validity for this request.
177 :param bool valid: If given, set the validity state of this
178 request. Otherwise, get the current state.
180 if valid is not None:
181 self.valid = bool(valid)
182 return self.valid
184 def withIPv4(self):
185 """Set the ``ipVersion`` to IPv4."""
186 self.ipVersion = 4
188 def withIPv6(self):
189 """Set the ``ipVersion`` to IPv6."""
190 self.ipVersion = 6
192 def withoutBlockInCountry(self, country):
193 """Add this **countryCode** to the list of countries which distributed
194 bridges should not be blocked in (``notBlockedIn``).
196 self.notBlockedIn.append(country.lower())
198 def withPluggableTransportType(self, pt):
199 """Add this **pt** to the list of requested ``transports``.
201 :param str pt: A :class:`~bridgedb.bridges.PluggableTransport`.
202 :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>`.
204 self.transports.append(pt)
206 def addFilter(self, filtre):
207 """Add a **filtre** to the list of ``filters``.
209 :type filter: callable
210 :param filter: A filter function, e.g. one generated via
211 :mod:`bridgedb.filters`.
213 self.filters.append(filtre)
215 def clearFilters(self):
216 """Clear the list of ``filters``."""
217 self.filters = []
219 def justOnePTType(self):
220 """Get just one bridge type (e.g. a
221 :data:`methodname <bridgedb.bridges.PluggableTransport.methodname>` of
222 :class:`~bridgedb.bridges.PluggableTransport`) at a time!
224 ptType = None
225 try:
226 ptType = self.transports[-1] # Use the last PT requested
227 except IndexError:
228 logging.debug("No pluggable transports were requested.")
229 return ptType
231 def generateFilters(self):
232 """Build the list of callables, ``filters``, according to the current
233 contents of the lists of ``transports``, ``notBlockedIn``, and the
234 ``ipVersion``.
236 self.clearFilters()
238 pt = self.justOnePTType()
239 msg = ("Adding a filter to %s for %s for IPv%d"
240 % (self.__class__.__name__, self.client, self.ipVersion))
242 # If this bridge runs any active probing-resistant PTs, we should
243 # *only* hand out its active probing-resistant PTs. Otherwise, a
244 # non-resistant PT would get this bridge scanned and blocked:
245 # <https://bugs.torproject.org/28655>
246 self.addFilter(byProbingResistance(pt, self.ipVersion))
248 if self.notBlockedIn:
249 for country in self.notBlockedIn:
250 logging.info("%s %s bridges not blocked in %s..." %
251 (msg, pt or "vanilla", country))
252 self.addFilter(byNotBlockedIn(country, pt or "vanilla", self.ipVersion))
253 elif pt:
254 logging.info("%s %s bridges..." % (msg, pt))
255 self.addFilter(byTransport(pt, self.ipVersion))
256 else:
257 logging.info("%s bridges..." % msg)
258 self.addFilter(byIPv(self.ipVersion))