commit fe0efec8b00250756d36c7fb2cd061fd8ef3d3d3 Author: Isis Lovecruft isis@torproject.org Date: Sun Apr 19 00:05:02 2015 +0000
Add bridgedb.distribute module with IDistribute and Distributor classes.
* ADD distribute.IDistibute interface. * ADD distribute.Distributor class, an implementation of IDistribute. * REMOVE bridgedb.Dist.Distributor. * RENAME HTTPSDistributor.getBridgesForIP() → HTTPSDistributor.getBridges(). * RENAME EmailBasedDistributor.getBridgesForEmail() → EmailBasedDistributor.getBridges(). * FIXES part of #12506: https://bugs.torproject.org/12506 * FIXES part of #12029: https://bugs.torproject.org/12029 --- lib/bridgedb/Dist.py | 104 +++---------- lib/bridgedb/distribute.py | 275 ++++++++++++++++++++++++++++++++++ lib/bridgedb/email/autoresponder.py | 5 +- lib/bridgedb/interfaces.py | 56 ++++++- lib/bridgedb/test/email_helpers.py | 10 +- lib/bridgedb/test/https_helpers.py | 2 +- lib/bridgedb/test/legacy_Tests.py | 64 ++++---- lib/bridgedb/test/test_Dist.py | 11 +- lib/bridgedb/test/test_distribute.py | 44 ++++++ lib/bridgedb/test/test_interfaces.py | 55 +++++++ 10 files changed, 495 insertions(+), 131 deletions(-)
diff --git a/lib/bridgedb/Dist.py b/lib/bridgedb/Dist.py index 93c08c1..fe81c04 100644 --- a/lib/bridgedb/Dist.py +++ b/lib/bridgedb/Dist.py @@ -17,20 +17,19 @@ import logging import re import time
-import bridgedb.Bridges import bridgedb.Storage
from bridgedb import proxy +from bridgedb.Bridges import BridgeRing from bridgedb.Bridges import FilteredBridgeSplitter from bridgedb.crypto import getHMAC from bridgedb.crypto import getHMACFunc +from bridgedb.distribute import Distributor from bridgedb.Filters import filterAssignBridgesToRing from bridgedb.Filters import filterBridgesByRules from bridgedb.Filters import filterBridgesByIP4 from bridgedb.Filters import filterBridgesByIP6 from bridgedb.parse import addr -from bridgedb.parse.addr import UnsupportedDomain -from bridgedb.safelog import logSafely
MAX_EMAIL_RATE = 3*3600 @@ -48,50 +47,6 @@ class EmailRequestedKey(Exception): """Raised when an incoming email requested a copy of our GnuPG keys."""
-class Distributor(object): - """Distributes bridges to clients.""" - - def __init__(self): - super(Distributor, self).__init__() - self.name = None - self.hashring = None - - def setDistributorName(self, name): - """Set a **name** for identifying this distributor. - - This is used to identify the distributor in the logs; the **name** - doesn't necessarily need to be unique. The hashrings created for this - distributor will be named after this distributor's name in - :meth:`propopulateRings`, and any sub hashrings of each of those - hashrings will also carry that name. - - >>> from bridgedb import Dist - >>> dist = Dist.HTTPSDistributor(2, 'masterkey') - >>> dist.setDistributorName('Excellent Distributor') - >>> dist.name - 'Excellent Distributor' - - :param str name: A name for this distributor. - """ - self.name = name - self.hashring.distributorName = name - - def bridgesPerResponse(self, hashring=None, maximum=3): - if hashring is None: - hashring = self.hashring - - if len(hashring) < 20: - n = 1 - if 20 <= len(hashring) < 100: - n = min(2, maximum) - if len(hashring) >= 100: - n = maximum - - logging.debug("Returning %d bridges from ring of len: %d" % - (n, len(hashring))) - return n - - class HTTPSDistributor(Distributor): """A Distributor that hands out bridges based on the IP address of an incoming request and the current time period. @@ -129,9 +84,7 @@ class HTTPSDistributor(Distributor): parameters, i.e. that an answer has "at least two obfsproxy bridges" or "at least one bridge on port 443", etc. """ - super(HTTPSDistributor, self).__init__() - - self.key = key + super(HTTPSDistributor, self).__init__(key) self.totalSubrings = totalSubrings self.answerParameters = answerParameters
@@ -154,13 +107,12 @@ class HTTPSDistributor(Distributor): self._clientToPositionHMAC = getHMACFunc(key3, hex=False) self._subnetToSubringHMAC = getHMACFunc(key4, hex=True) self.hashring = FilteredBridgeSplitter(key2, self.ringCacheSize) - logging.debug("Added %s to HTTPS distributor." % - self.hashring.__class__.__name__) + self.name = 'HTTPS' + logging.debug("Added %s to %s distributor." % + (self.hashring.__class__.__name__, self.name))
- self.setDistributorName('HTTPS') - - def bridgesPerResponse(self, hashring=None, maximum=3): - return super(HTTPSDistributor, self).bridgesPerResponse(hashring, maximum) + def bridgesPerResponse(self, hashring=None): + return super(HTTPSDistributor, self).bridgesPerResponse(hashring)
@classmethod def getSubnet(cls, ip, usingProxy=False, proxySubnets=4): @@ -311,7 +263,7 @@ class HTTPSDistributor(Distributor): for subring in range(1, self.totalSubrings + 1): filters = self._buildHashringFilters([filterFn,], subring) key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring) - ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters) + ring = BridgeRing(key1, self.answerParameters) # For consistency with previous implementation of this method, # only set the "name" for "clusters" which are for this # distributor's proxies: @@ -330,7 +282,7 @@ class HTTPSDistributor(Distributor): previousFilters.append(f) return frozenset(previousFilters)
- def getBridges(self, bridgeRequest, interval, N=1): + def getBridges(self, bridgeRequest, interval): """Return a list of bridges to give to a user.
:type bridgeRequest: :class:`bridgedb.https.request.HTTPSBridgeRequest` @@ -339,15 +291,13 @@ class HTTPSDistributor(Distributor): attribute set to a string containing the client's IP address. :param str interval: The time period when we got this request. This can be any string, so long as it changes with every period. - :param int N: The number of bridges to try to give back. (default: 1) :rtype: list :return: A list of :class:`~bridgedb.Bridges.Bridge`s to include in the response. See :meth:`bridgedb.https.server.WebResourceBridges.getBridgeRequestAnswer` for an example of how this is used. """ - logging.info("Attempting to return %d bridges to client %s..." - % (N, bridgeRequest.client)) + logging.info("Attempting to get bridges for %s..." % bridgeRequest.client)
if not len(self.hashring): logging.warn("Bailing! Hashring has zero bridges!") @@ -386,12 +336,12 @@ class HTTPSDistributor(Distributor): else: logging.debug("Cache miss %s" % filters) key1 = getHMAC(self.key, "Order-Bridges-In-Ring-%d" % subring) - ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters) + ring = BridgeRing(key1, self.answerParameters) self.hashring.addRing(ring, filters, filterBridgesByRules(filters), populate_from=self.hashring.bridges)
# Determine the appropriate number of bridges to give to the client: - returnNum = self.bridgesPerResponse(ring, maximum=N) + returnNum = self.bridgesPerResponse(ring) answer = ring.getBridges(position, returnNum)
return answer @@ -427,7 +377,7 @@ class EmailBasedDistributor(Distributor): :param whitelist: A dictionary that maps whitelisted email addresses to GnuPG fingerprints. """ - self.key = key + super(EmailBasedDistributor, self).__init__(key)
key1 = getHMAC(key, "Map-Addresses-To-Ring") self.emailHmac = getHMACFunc(key1, hex=False) @@ -440,19 +390,17 @@ class EmailBasedDistributor(Distributor): self.answerParameters = answerParameters
#XXX cache options not implemented - self.hashring = bridgedb.Bridges.FilteredBridgeSplitter( - key2, max_cached_rings=5) - - self.setDistributorName('Email') + self.hashring = FilteredBridgeSplitter(key2, max_cached_rings=5) + self.name = "Email"
- def bridgesPerResponse(self, hashring=None, maximum=3): - return super(EmailBasedDistributor, self).bridgesPerResponse(hashring, maximum) + def bridgesPerResponse(self, hashring=None): + return super(EmailBasedDistributor, self).bridgesPerResponse(hashring)
def insert(self, bridge): """Assign a bridge to this distributor.""" self.hashring.insert(bridge)
- def getBridges(self, bridgeRequest, interval, N=1): + def getBridges(self, bridgeRequest, interval): """Return a list of bridges to give to a user.
:type bridgeRequest: :class:`~bridgedb.email.request.EmailBridgeRequest` @@ -462,7 +410,6 @@ class EmailBasedDistributor(Distributor): email address. :param interval: The time period when we got this request. This can be any string, so long as it changes with every period. - :param int N: The number of bridges to try to give back. """ # All checks on the email address, such as checks for whitelisting and # canonicalization of domain name, are done in @@ -473,15 +420,13 @@ class EmailBasedDistributor(Distributor): ("%s distributor can't get bridges for invalid email email " " address: %s") % (self.name, bridgeRequest.client))
+ logging.info("Attempting to get bridges for %s..." % bridgeRequest.client) + now = time.time()
with bridgedb.Storage.getDB() as db: wasWarned = db.getWarnedEmail(bridgeRequest.client) lastSaw = db.getEmailTime(bridgeRequest.client) - - logging.info("Attempting to return for %d bridges for %s..." - % (N, bridgeRequest.client)) - if lastSaw is not None: if bridgeRequest.client in self.whitelist.keys(): logging.info(("Whitelisted email address %s was last seen " @@ -500,7 +445,6 @@ class EmailBasedDistributor(Distributor): db.commit() raise TooSoonEmail("Must wait %d seconds" % wait, bridgeRequest.client) - # warning period is over elif wasWarned: db.setWarnedEmail(bridgeRequest.client, False) @@ -518,12 +462,12 @@ class EmailBasedDistributor(Distributor):
# add new ring key1 = getHMAC(self.key, "Order-Bridges-In-Ring") - ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters) + ring = BridgeRing(key1, self.answerParameters) self.hashring.addRing(ring, ruleset, filterBridgesByRules(ruleset), populate_from=self.hashring.bridges)
- returnNum = self.bridgesPerResponse(ring, maximum=N) + returnNum = self.bridgesPerResponse(ring) result = ring.getBridges(pos, returnNum)
db.setEmailTime(bridgeRequest.client, now) @@ -553,7 +497,7 @@ class EmailBasedDistributor(Distributor): for filterFn in [filterBridgesByIP4, filterBridgesByIP6]: ruleset = frozenset([filterFn]) key1 = getHMAC(self.key, "Order-Bridges-In-Ring") - ring = bridgedb.Bridges.BridgeRing(key1, self.answerParameters) + ring = BridgeRing(key1, self.answerParameters) self.hashring.addRing(ring, ruleset, filterBridgesByRules([filterFn]), populate_from=self.hashring.bridges) diff --git a/lib/bridgedb/distribute.py b/lib/bridgedb/distribute.py new file mode 100644 index 0000000..c7c9044 --- /dev/null +++ b/lib/bridgedb/distribute.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_distribute ; -*- +#_____________________________________________________________________________ +# +# 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) 2013-2015, Isis Lovecruft +# (c) 2007-2015, The Tor Project, Inc. +# :license: see LICENSE for licensing information +#_____________________________________________________________________________ + + +"""Classes for creating bridge distribution systems. + +DEFINITELY +---------- + +Distributor { + name property + bridgesPerResponse() property FORMERLY getNumBridgesPerAnswer() + hashring struct FORMERLY KNOWN AS splitter + rotate bool + rotationGroups + rotationSchedule + key str + subrings list + - Subring + clear() + export() FORMERLY KNOWN AS dumpAssignments() + insert() + getBridges() FORMERLY KNOWN AS getBridgesForEmail() and getBridgesForIP() + handleBridgeRequest() + handleIncomingBridges() +} + +DistributionContext { # should go in bridgedb.py + distributors { + name: DistributorContext + } +} + +DistributorContext { # should go in bridgedb.py + name str + allocationPercentage property + publicKey +} + +Hashring { + assignBridgesToRings() FORMERLY filterAssignBridgesToRing() + + filters bridges uniformly into subrings + clear() / __del__() + isEmpty property +} + +MAYBE +----- +mapClientToHashring() FORMERLY KNOWN AS areaMapper AND +mapClientToSubhashring() +authenticateToBridgeDB() +maintainACL() for proxylists + +- need a way for BridgeDB to decide global parameters to be followed + by all distributors. + - BridgeAnswerParameters? + maybe call it DistributionContext? + then have DistributorContexts? + + requiredFlags AnswerParameters? + requireFlag() + requiredPorts + requirePorts() + + THINGS NEEDED FOR COMMUNICATION BETWEEN DISTRIBUTORS AND BRIDGEDB + ----------------------------------------------------------------- + * distributorCredential (for authenticating to the DB) + * metrics? + * total clients seen + * total clients served + - unique clients seen + - unique clients served + * total requests for TRANSPORT + * total times TRANSPORT was served + + THINGS DISTRIBUTORS SHOULD KEEP TRACK OF, BUT NOT REPORT + -------------------------------------------------------- + - approximate bridge bandwidth + - approximate bandwidth per client + - approximate bridge bandwidth already distributed + + NAMES FOR CHOOSING "GET ME WHATEVER TRANSPORTS" + ----------------------------------------------- + chocolate box, russian roulette + + * How much of a bad idea would it be to store bridges allocated to a + distributor as diffs over the last time the Distributor asked? +""" + +import logging +import math + +from zope import interface +from zope.interface import Attribute +from zope.interface import implements + +# from bridgedb.hashring import IHashring +from bridgedb.interfaces import IName +from bridgedb.interfaces import Named + + +class IDistribute(IName): + """An interface specification for a system which distributes bridges.""" + + _bridgesPerResponseMin = Attribute( + ("The minimum number of bridges to distribute (if possible), per " + "client request.")) + _bridgesPerResponseMax = Attribute( + ("The maximum number of bridges to distribute (if possible), per " + "client request.")) + _hashringLevelMin = Attribute( + ("The bare minimum number of bridges which should be in a hashring. " + "If there less bridges than this, then the implementer of " + "IDistribute should only distribute _bridgesPerResponseMin number " + "of bridges, per client request.")) + _hashringLevelMax = Attribute( + ("The number of bridges which should be in a hashring for the " + "implementer of IDistribute to distribute _bridgesPerResponseMax " + "number of bridges, per client request.")) + + hashring = Attribute( + ("An implementer of ``bridgedb.hashring.IHashring`` which stores the " + "entirety of bridges allocated to this ``Distributor`` by the " + "BridgeDB. This ``Distributor`` is only capable of distributing " + "these bridges to its clients, and these bridges are only " + "distributable by this ``Distributor``.")) + + key = Attribute( + ("A master key which is used to HMAC bridge and client data into " + "this Distributor's **hashring** and its subhashrings.")) + + def __str__(): + """Get a string representation of this Distributor's ``name``.""" + + def bridgesPerResponse(hashring): + """Get the current number of bridges to return in a response.""" + + def getBridges(bridgeRequest): + """Get bridges based on a client's **bridgeRequest**.""" + + +class Distributor(Named): + """A :class:`Distributor` distributes bridges to clients. + + Inherit from me to create a new type of ``Distributor``. + """ + implements(IDistribute) + + _bridgesPerResponseMin = 1 + _bridgesPerResponseMax = 3 + _hashringLevelMin = 20 + _hashringLevelMax = 100 + + def __init__(self, key=None): + """Create a new bridge Distributor. + + :param key: A master key for this Distributor. This is used to HMAC + bridge and client data in order to arrange them into hashring + structures. + """ + super(Distributor, self).__init__() + self._hashring = None + self.key = key + + def __str__(self): + """Get a string representation of this ``Distributor``'s ``name``. + + :rtype: str + :returns: This ``Distributor``'s ``name`` attribute. + """ + return self.name + + @property + def hashring(self): + """Get this Distributor's main hashring, which holds all bridges + allocated to this Distributor. + + :rtype: :class:`~bridgedb.hashring.Hashring`. + :returns: An implementer of :interface:`~bridgedb.hashring.IHashring`. + """ + return self._hashring + + @hashring.setter + def hashring(self, ring): + """Set this Distributor's main hashring. + + :type ring: :class:`~bridgedb.hashring.Hashring` + :param ring: An implementer of :interface:`~bridgedb.hashring.IHashring`. + :raises TypeError: if the **ring** does not implement the + :interface:`~bridgedb.hashring.IHashring` interface. + """ + # if not IHashring.providedBy(ring): + # raise TypeError("%r doesn't implement the IHashring interface." % ring) + + self._hashring = ring + + @hashring.deleter + def hashring(self): + """Clear this Distributor's hashring.""" + if self.hashring: + self.hashring.clear() + + @property + def name(self): + """Get the name of this Distributor. + + :rtype: str + :returns: A string which identifies this :class:`Distributor`. + """ + return self._name + + @name.setter + def name(self, name): + """Set a **name** for identifying this Distributor. + + This is used to identify the distributor in the logs; the **name** + doesn't necessarily need to be unique. The hashrings created for this + distributor will be named after this distributor's name, and any + subhashrings of each of those hashrings will also carry that name. + + >>> from bridgedb.distribute import Distributor + >>> dist = Distributor() + >>> dist.name = 'Excellent Distributor' + >>> dist.name + 'Excellent Distributor' + + :param str name: A name for this distributor. + """ + self._name = name + + try: + self.hashring.distributor = name + except AttributeError: + logging.debug(("Couldn't set distributor attribute for %s " + "Distributor's hashring." % name)) + + def bridgesPerResponse(self, hashring=None): + """Get the current number of bridge to distribute in response to a + client's request for bridges. + """ + if hashring is None: + hashring = self.hashring + + if len(hashring) < self._hashringLevelMin: + n = self._bridgesPerResponseMin + elif self._hashringLevelMin <= len(hashring) < self._hashringLevelMax: + n = int(math.ceil( + (self._bridgesPerResponseMin + self._bridgesPerResponseMax) / 2.0)) + elif self._hashringLevelMax <= len(hashring): + n = self._bridgesPerResponseMax + + logging.debug("Returning %d bridges from ring of len: %d" % + (n, len(hashring))) + + return n + + def getBridges(self, bridgeRequest): + """Get some bridges in response to a client's **bridgeRequest**. + + :type bridgeRequest: :class:`~bridgedb.bridgerequest.BridgeRequestBase` + :param bridgeRequest: A client's request for bridges, including some + information on the client making the request, whether they asked + for IPv4 or IPv6 bridges, which type of + :class:`~bridgedb.bridges.PluggableTransport` they wanted, etc. + """ + # XXX generalise the getBridges() method diff --git a/lib/bridgedb/email/autoresponder.py b/lib/bridgedb/email/autoresponder.py index 0ab04fa..b98717f 100644 --- a/lib/bridgedb/email/autoresponder.py +++ b/lib/bridgedb/email/autoresponder.py @@ -97,10 +97,7 @@ def createResponseBody(lines, context, client, lang='en'):
# Otherwise they must have requested bridges: interval = context.schedule.intervalStart(time.time()) - bridges = context.distributor.getBridges( - bridgeRequest, - interval, - context.nBridges) + bridges = context.distributor.getBridges(bridgeRequest, interval) except EmailRequestedHelp as error: logging.info(error) return templates.buildWelcomeText(translator, client) diff --git a/lib/bridgedb/interfaces.py b/lib/bridgedb/interfaces.py index 38c3541..89ddb12 100644 --- a/lib/bridgedb/interfaces.py +++ b/lib/bridgedb/interfaces.py @@ -1,4 +1,4 @@ -# -*- coding: utf-8 -*- +# -*- coding: utf-8 ; test-case-name: bridgedb.test.test_interfaces ; -*- #_____________________________________________________________________________ # # This file is part of BridgeDB, a Tor bridge distribution system. @@ -13,7 +13,55 @@
"""All available ``zope.interface``s in BridgeDB."""
+from zope.interface import Interface +from zope.interface import Attribute +from zope.interface import implementer
-from bridgedb.bridgerequest import IRequestBridges -from bridgedb.captcha import ICaptcha -from bridgedb.schedule import ISchedule + +class IName(Interface): + """An interface specification for a named object.""" + + name = Attribute("A string which identifies this object.") + + +@implementer(IName) +class Named(object): + """A named object""" + + #: The characters used to join child Named object's names with our name. + separator = ' ' + + def __init__(self): + self._name = str() + + @property + def name(self): + """Get the name of this object. + + :rtype: str + :returns: A string which identifies this object. + """ + return self._name + + @name.setter + def name(self, name): + """Set a **name** for identifying this object. + + This is used to identify the object in log messages; the **name** + doesn't necessarily need to be unique. Other :class:`Named` objects + which are properties of a :class:`Named` object may inherit their + parents' **name**s. + + >>> from bridgedb.distribute import Named + >>> named = Named() + >>> named.name = 'Excellent Super-Awesome Thing' + >>> named.name + 'Excellent Super-Awesome Thing' + + :param str name: A name for this object. + """ + self._name = name + + for attr in self.__dict__.values(): + if IName.providedBy(attr): + attr.name = self.separator.join([name, attr.name]) diff --git a/lib/bridgedb/test/email_helpers.py b/lib/bridgedb/test/email_helpers.py index 0eac35e..20aee57 100644 --- a/lib/bridgedb/test/email_helpers.py +++ b/lib/bridgedb/test/email_helpers.py @@ -124,6 +124,8 @@ class DummyEmailDistributor(object): test :class:`bridgedb.EmailServer`. """
+ _bridgesPerResponseMin = 3 + def __init__(self, key=None, domainmap=None, domainrules=None, answerParameters=None): """None of the parameters are really used, ― they are just there to retain an @@ -134,8 +136,8 @@ class DummyEmailDistributor(object): self.domainrules = domainrules self.answerParameters = answerParameters
- def getBridges(self, bridgeRequest, epoch, N=1): - return [util.DummyBridge() for _ in xrange(N)] + def getBridges(self, bridgeRequest, epoch): + return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)]
def cleanDatabase(self): pass @@ -157,14 +159,14 @@ class DummyEmailDistributorWithState(DummyEmailDistributor): super(DummyEmailDistributorWithState, self).__init__() self.alreadySeen = {}
- def getBridges(self, bridgeRequest, epoch, N=1): + def getBridges(self, bridgeRequest, epoch): # Keep track of the number of times we've seen a client. if not bridgeRequest.client in self.alreadySeen.keys(): self.alreadySeen[bridgeRequest.client] = 0 self.alreadySeen[bridgeRequest.client] += 1
if self.alreadySeen[bridgeRequest.client] <= 1: - return [util.DummyBridge() for _ in xrange(N)] + return [util.DummyBridge() for _ in xrange(self._bridgesPerResponseMin)] elif self.alreadySeen[bridgeRequest.client] == 2: raise TooSoonEmail( "Seen client '%s' %d times" diff --git a/lib/bridgedb/test/https_helpers.py b/lib/bridgedb/test/https_helpers.py index 00ccbd6..3d8ec19 100644 --- a/lib/bridgedb/test/https_helpers.py +++ b/lib/bridgedb/test/https_helpers.py @@ -105,7 +105,7 @@ class DummyHTTPSDistributor(object): _bridge_class = util.DummyBridge _bridgesPerResponseMin = 3
- def getBridges(self, bridgeRequest=None, epoch=None, N=1): + def getBridges(self, bridgeRequest=None, epoch=None): """Needed because it's called in :meth:`BridgesResource.getBridgeRequestAnswer`.""" return [self._bridge_class() for _ in range(self._bridgesPerResponseMin)] diff --git a/lib/bridgedb/test/legacy_Tests.py b/lib/bridgedb/test/legacy_Tests.py index 5621515..4cdcc03 100644 --- a/lib/bridgedb/test/legacy_Tests.py +++ b/lib/bridgedb/test/legacy_Tests.py @@ -169,11 +169,11 @@ class EmailBridgeDistTests(unittest.TestCase): {'example.com': [], 'dkim.example.com': ['dkim']}) for _ in xrange(256): d.insert(fakeBridge()) - d.getBridges('abc@example.com', 1, 3) + d.getBridges('abc@example.com', 1) self.assertRaises(bridgedb.Dist.TooSoonEmail, - d.getBridges, 'abc@example.com', 1, 3) + d.getBridges, 'abc@example.com', 1) self.assertRaises(bridgedb.Dist.IgnoreEmail, - d.getBridges, 'abc@example.com', 1, 3) + d.getBridges, 'abc@example.com', 1)
def testUnsupportedDomain(self): db = self.db @@ -184,19 +184,17 @@ class EmailBridgeDistTests(unittest.TestCase): {'example.com':[]})
class IPBridgeDistTests(unittest.TestCase): - def dumbAreaMapper(self, ip): - return ip + def testBasicDist(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(256): d.insert(fakeBridge()) - n = d.getBridges("1.2.3.4", "x", 2) - n2 = d.getBridges("1.2.3.4", "x", 2) + n = d.getBridges("1.2.3.4", "x") + n2 = d.getBridges("1.2.3.4", "x") self.assertEquals(n, n2)
def testDistWithProxies(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo", - [RhymesWith255ProxySet()]) + d = bridgedb.Dist.HTTPSDistributor(3, "Foo", [RhymesWith255ProxySet()]) for _ in xrange(256): d.insert(fakeBridge())
@@ -204,8 +202,8 @@ class IPBridgeDistTests(unittest.TestCase): # Make sure that the ProxySets do not overlap f = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(4)]) g = lambda: ".".join([str(random.randrange(1,255)) for _ in xrange(3)] + ['255']) - n = d.getBridges(g(), "x", 10) - n2 = d.getBridges(f(), "x", 10) + n = d.getBridges(g(), "x") + n2 = d.getBridges(f(), "x")
assert(len(n) > 0) assert(len(n2) > 0) @@ -219,7 +217,7 @@ class IPBridgeDistTests(unittest.TestCase): #XXX: #6175 breaks this test! #def testDistWithPortRestrictions(self): # param = bridgedb.Bridges.BridgeRingParameters(needPorts=[(443, 1)]) - # d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Baz", + # d = bridgedb.Dist.HTTPSDistributor(3, "Baz", # answerParameters=param) # for _ in xrange(32): # d.insert(fakeBridge(443)) @@ -227,7 +225,7 @@ class IPBridgeDistTests(unittest.TestCase): # d.insert(fakeBridge()) # for _ in xrange(32): # i = randomIP() - # n = d.getBridges(i, "x", 5) + # n = d.getBridges(i, "x") # count = 0 # fps = {} # for b in n: @@ -239,15 +237,15 @@ class IPBridgeDistTests(unittest.TestCase): # self.assertTrue(count >= 1)
def testDistWithFilterIP6(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True)) d.insert(fakeBridge(or_addresses=True))
for i in xrange(500): bridges = d.getBridges(randomIPv4String(), - "faketimestamp", - bridgeFilterRules=[filterBridgesByIP6]) + "faketimestamp", + bridgeFilterRules=[filterBridgesByIP6]) bridge = random.choice(bridges) bridge_line = bridge.getConfigLine(addressClass=ipaddr.IPv6Address) address, portlist = networkstatus.parseALine(bridge_line) @@ -255,15 +253,15 @@ class IPBridgeDistTests(unittest.TestCase): assert filterBridgesByIP6(random.choice(bridges))
def testDistWithFilterIP4(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True)) d.insert(fakeBridge(or_addresses=True))
for i in xrange(500): bridges = d.getBridges(randomIPv4String(), - "faketimestamp", - bridgeFilterRules=[filterBridgesByIP4]) + "faketimestamp", + bridgeFilterRules=[filterBridgesByIP4]) bridge = random.choice(bridges) bridge_line = bridge.getConfigLine(addressClass=ipaddr.IPv4Address) address, portlist = networkstatus.parseALine(bridge_line) @@ -271,17 +269,17 @@ class IPBridgeDistTests(unittest.TestCase): assert filterBridgesByIP4(random.choice(bridges))
def testDistWithFilterBoth(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True)) d.insert(fakeBridge(or_addresses=True))
for i in xrange(50): bridges = d.getBridges(randomIPv4String(), - "faketimestamp", 1, - bridgeFilterRules=[ - filterBridgesByIP4, - filterBridgesByIP6]) + "faketimestamp", + bridgeFilterRules=[ + filterBridgesByIP4, + filterBridgesByIP6]) if bridges: t = bridges.pop() assert filterBridgesByIP4(t) @@ -295,18 +293,18 @@ class IPBridgeDistTests(unittest.TestCase):
def testDistWithFilterAll(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True)) d.insert(fakeBridge(or_addresses=True))
for i in xrange(5): - b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[ filterBridgesByIP4, filterBridgesByIP6]) assert len(b) == 0
def testDistWithFilterBlockedCountries(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True)) d.insert(fakeBridge(or_addresses=True)) @@ -324,15 +322,15 @@ class IPBridgeDistTests(unittest.TestCase): b.blockingCountries[key] = set(['cn'])
for i in xrange(5): - b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[ filterBridgesByNotBlockedIn("cn")]) assert len(b) == 0 - b = d.getBridges(randomIPv4String(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPv4String(), "x", bridgeFilterRules=[ filterBridgesByNotBlockedIn("us")]) assert len(b) > 0
def testDistWithFilterBlockedCountriesAdvanced(self): - d = bridgedb.Dist.HTTPSDistributor(self.dumbAreaMapper, 3, "Foo") + d = bridgedb.Dist.HTTPSDistributor(3, "Foo") for _ in xrange(250): d.insert(fakeBridge6(or_addresses=True, transports=True)) d.insert(fakeBridge(or_addresses=True, transports=True)) @@ -355,7 +353,7 @@ class IPBridgeDistTests(unittest.TestCase): # we probably will get at least one bridge back! # it's pretty unlikely to lose a coin flip 250 times in a row for i in xrange(5): - b = d.getBridges(randomIPString(), "x", 1, + b = d.getBridges(randomIPString(), "x", bridgeFilterRules=[ filterBridgesByNotBlockedIn("cn"), filterBridgesByTransport('obfs2'), @@ -363,7 +361,7 @@ class IPBridgeDistTests(unittest.TestCase): try: assert len(b) > 0 except AssertionError: print("epic fail") - b = d.getBridges(randomIPString(), "x", 1, bridgeFilterRules=[ + b = d.getBridges(randomIPString(), "x", bridgeFilterRules=[ filterBridgesByNotBlockedIn("us")]) assert len(b) > 0
diff --git a/lib/bridgedb/test/test_Dist.py b/lib/bridgedb/test/test_Dist.py index f47449c..3de7945 100644 --- a/lib/bridgedb/test/test_Dist.py +++ b/lib/bridgedb/test/test_Dist.py @@ -125,8 +125,9 @@ class HTTPSDistributorTests(unittest.TestCase):
def test_HTTPSDistributor_bridgesPerResponse_100_max_5(self): dist = Dist.HTTPSDistributor(3, self.key) + dist._bridgesPerResponseMax = 5 [dist.insert(bridge) for bridge in self.bridges[:100]] - self.assertEqual(dist.bridgesPerResponse(maximum=5), 5) + self.assertEqual(dist.bridgesPerResponse(), 5)
def test_HTTPSDistributor_getSubnet_usingProxy(self): """HTTPSDistributor.getSubnet(usingProxy=True) should return a proxy @@ -199,11 +200,11 @@ class HTTPSDistributorTests(unittest.TestCase):
for _ in range(5): clientRequest1 = self.randomClientRequestForNotBlockedIn('cn') - b = dist.getBridges(clientRequest1, 1, 3) + b = dist.getBridges(clientRequest1, 1) self.assertEqual(len(b), 0)
clientRequest2 = self.randomClientRequestForNotBlockedIn('ir') - b = dist.getBridges(clientRequest2, 1, 3) + b = dist.getBridges(clientRequest2, 1) self.assertEqual(len(b), 3)
def test_HTTPSDistributor_getBridges_with_some_blocked_bridges(self): @@ -226,14 +227,14 @@ class HTTPSDistributorTests(unittest.TestCase):
for _ in range(5): clientRequest1 = self.randomClientRequestForNotBlockedIn('cn') - bridges = dist.getBridges(clientRequest1, 1, 3) + bridges = dist.getBridges(clientRequest1, 1) for b in bridges: self.assertFalse(b.isBlockedIn('cn')) # The client *should* have gotten some bridges still. self.assertGreater(len(bridges), 0)
clientRequest2 = self.randomClientRequestForNotBlockedIn('ir') - bridges = dist.getBridges(clientRequest2, 1, 3) + bridges = dist.getBridges(clientRequest2, 1) for b in bridges: self.assertFalse(b.isBlockedIn('ir')) self.assertGreater(len(bridges), 0) diff --git a/lib/bridgedb/test/test_distribute.py b/lib/bridgedb/test/test_distribute.py new file mode 100644 index 0000000..95fda31 --- /dev/null +++ b/lib/bridgedb/test/test_distribute.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +#_____________________________________________________________________________ +# +# 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) 2013-2015, Isis Lovecruft +# (c) 2007-2015, The Tor Project, Inc. +# :license: see LICENSE for licensing information +#_____________________________________________________________________________ + +"""Unittests for :mod:`bridgedb.distribute`.""" + +from __future__ import print_function + +from twisted.trial import unittest + +from zope.interface.verify import verifyObject + +from bridgedb.distribute import IDistribute +from bridgedb.distribute import Distributor + + +class DistributorTests(unittest.TestCase): + """Tests for :class:`bridgedb.distribute.Distributor`.""" + + def test_Distributor_implements_IDistribute(self): + IDistribute.namesAndDescriptions() + IDistribute.providedBy(Distributor) + self.assertTrue(verifyObject(IDistribute, Distributor())) + + def test_Distributor_str_no_name(self): + """str(dist) when the distributor doesn't have a name should return a + blank string. + """ + dist = Distributor() + self.assertEqual(str(dist), "") + + def test_Distributor_str_with_name(self): + """str(dist) when the distributor has a name should return the name.""" + dist = Distributor() + dist.name = "foo" + self.assertEqual(str(dist), "foo") diff --git a/lib/bridgedb/test/test_interfaces.py b/lib/bridgedb/test/test_interfaces.py new file mode 100644 index 0000000..8a78291 --- /dev/null +++ b/lib/bridgedb/test/test_interfaces.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# ____________________________________________________________________________ +# +# 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) 2013-2015, Isis Lovecruft +# (c) 2007-2015, The Tor Project, Inc. +# :license: see LICENSE for licensing information +# ____________________________________________________________________________ + +"""Unittests for :mod:`bridgedb.interfaces`.""" + +from twisted.trial import unittest + +from bridgedb import interfaces + + +class DummyNamedOtherThing(interfaces.Named): + def __init__(self): + self.name = "hipster" + + +class DummyNamedThing(interfaces.Named): + def __init__(self): + self.whatever = DummyNamedOtherThing() + self.name = "original" + + +class NamedTests(unittest.TestCase): + """Tests for :class:`bridgedb.interfaces.Named`.""" + + def test_Named_init(self): + """Initializing a Named() object should set its name to ''.""" + named = interfaces.Named() + self.assertEqual(named.name, '') + + def test_Named_name(self): + """For a Named object A without any other Named objects which have + object A as an attribute, should just have its name set to whatever + it was set to. + """ + named = DummyNamedOtherThing() + self.assertEqual(named.name, "hipster") + + def test_Named_with_named_object_for_attribute(self): + """For a Named object A which has another Named object B as an + attribute, object A should just have its name set to whatever + it was set to, and object B should have its name set to object A's + name plus whatever object B's name was set to. + """ + named = DummyNamedThing() + self.assertEqual(named.name, "original") + self.assertEqual(named.whatever.name, "original hipster")
tor-commits@lists.torproject.org