Esempio n. 1
0
    def test_EmailDistributor_getBridges_rate_limit_expiry(self):
        """A client's first email should return bridges.  The second should
        return a warning, and the third should receive no response.  After the
        EmailDistributor.emailRateMax is up, the client should be able to
        receive a response again.
        """
        clock = Clock()
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        bridgeRequest = self.makeClientRequest('*****@*****.**')

        # The first request should get a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest, 1, clock)), 3)
        # The second gets a warning, and the rest are ignored.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1,
                          clock)
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1,
                          clock)
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1,
                          clock)
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1,
                          clock)

        clock.advance(2 * dist.emailRateMax)

        # The client should again a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3, clock)
Esempio n. 2
0
    def test_EmailDistributor_getBridges_rate_limit_multiple_clients(self):
        """Each client should be rate-limited separately."""
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        bridgeRequest1 = self.makeClientRequest('*****@*****.**')
        bridgeRequest2 = self.makeClientRequest('*****@*****.**')
        bridgeRequest3 = self.makeClientRequest('*****@*****.**')

        # The first request from 'abc' should get a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest1, 1)), 3)
        # The second from 'abc' gets a warning.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest1, 1)
        # The first request from 'def' should get a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest2, 1)), 3)
        # The third from 'abc' is ignored.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
        # The second from 'def' gets a warning.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest2, 1)
        # The third from 'def' is ignored.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest2, 1)
        # The fourth from 'abc' is ignored.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest1, 1)
        # The first request from 'ghi' should get a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest3, 1)), 3)
        # The second from 'ghi' gets a warning.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest3, 1)
        # The third from 'ghi' is ignored.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
        # The fourth from 'ghi' is ignored.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest3, 1)
Esempio n. 3
0
    def test_EmailDistributor_prepopulateRings(self):
        """Calling prepopulateRings() should add two rings to the
        EmailDistributor.hashring.
        """
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)

        # There shouldn't be any subrings yet.
        self.assertEqual(len(dist.hashring.filterRings), 0)

        dist.prepopulateRings()

        # There should now be two subrings, but the subrings should be empty.
        self.assertEqual(len(dist.hashring.filterRings), 2)
        for (filtre, subring) in dist.hashring.filterRings.values():
            self.assertEqual(len(subring), 0)

        # The subrings in this Distributor have gross names, because the
        # filter functions (including their addresses in memory!) are used as
        # the subring names.  In this case, we should have something like:
        #
        #     frozenset([<function byIPv6 at 0x7eff7ad7fc80>])
        #
        # and
        #
        #     frozenset([<function byIPv4 at 0x7eff7ad7fc08>])
        #
        # So we have to join the strings together and check the whole thing,
        # since we have no other way to use these stupid subring names to
        # index into the dictionary they are stored in, because the memory
        # addresses are unknowable until runtime.

        # There should be an IPv4 subring and an IPv6 ring:
        ringnames = dist.hashring.filterRings.keys()
        self.failUnlessIn("IPv4",
                          "".join([str(ringname) for ringname in ringnames]))
        self.failUnlessIn("IPv6",
                          "".join([str(ringname) for ringname in ringnames]))

        [dist.hashring.insert(bridge) for bridge in self.bridges]

        # There should still be two subrings.
        self.assertEqual(len(dist.hashring.filterRings), 2)
        for (filtre, subring) in dist.hashring.filterRings.values():
            self.assertGreater(len(subring), 0)

        # Ugh, the hashring code is so gross looking.
        subrings = dist.hashring.filterRings
        subring1 = subrings.values()[0][1]
        subring2 = subrings.values()[1][1]
        # Each subring should have roughly the same number of bridges.
        # (Having ±10 bridges in either ring, out of 500 bridges total, should
        # be so bad.)
        self.assertApproximates(len(subring1), len(subring2), 10)
Esempio n. 4
0
    def test_EmailDistributor_getBridges_rate_limit(self):
        """A client's first email should return bridges.  The second should
        return a warning, and the third should receive no response.
        """
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        bridgeRequest = self.makeClientRequest('*****@*****.**')

        # The first request should get a response with bridges.
        bridges = dist.getBridges(bridgeRequest, 1)
        self.assertGreater(len(bridges), 0)
        [self.assertIsInstance(b, Bridge) for b in bridges]
        self.assertEqual(len(bridges), 3)

        # The second gets a warning, and the third is ignored.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)
Esempio n. 5
0
def createBridgeRings(cfg, proxyList, key):
    """Create the bridge distributors defined by the config file

    :type cfg:  :class:`Conf`
    :param cfg: The current configuration, including any in-memory settings
        (i.e. settings whose values were not obtained from the config file,
        but were set via a function somewhere)
    :type proxyList: :class:`~bridgedb.proxy.ProxySet`
    :param proxyList: The container for the IP addresses of any currently
        known open proxies.
    :param bytes key: Hashring master key
    :rtype: tuple
    :returns: A BridgeSplitter hashring, an
        :class:`~bridgedb.https.distributor.HTTPSDistributor` or None, and an
        :class:`~bridgedb.email.distributor.EmailDistributor` or None.
    """
    # Create a BridgeSplitter to assign the bridges to the different
    # distributors.
    hashring = Bridges.BridgeSplitter(crypto.getHMAC(key, "Hashring-Key"))
    logging.debug("Created hashring: %r" % hashring)

    # Create ring parameters.
    ringParams = Bridges.BridgeRingParameters(needPorts=cfg.FORCE_PORTS,
                                              needFlags=cfg.FORCE_FLAGS)

    emailDistributor = ipDistributor = None
    # As appropriate, create an IP-based distributor.
    if cfg.HTTPS_DIST and cfg.HTTPS_SHARE:
        logging.debug("Setting up HTTPS Distributor...")
        ipDistributor = HTTPSDistributor(cfg.N_IP_CLUSTERS,
                                         crypto.getHMAC(
                                             key, "HTTPS-IP-Dist-Key"),
                                         proxyList,
                                         answerParameters=ringParams)
        hashring.addRing(ipDistributor.hashring, "https", cfg.HTTPS_SHARE)

    # As appropriate, create an email-based distributor.
    if cfg.EMAIL_DIST and cfg.EMAIL_SHARE:
        logging.debug("Setting up Email Distributor...")
        emailDistributor = EmailDistributor(
            crypto.getHMAC(key, "Email-Dist-Key"),
            cfg.EMAIL_DOMAIN_MAP.copy(),
            cfg.EMAIL_DOMAIN_RULES.copy(),
            answerParameters=ringParams,
            whitelist=cfg.EMAIL_WHITELIST.copy())
        hashring.addRing(emailDistributor.hashring, "email", cfg.EMAIL_SHARE)

    # As appropriate, tell the hashring to leave some bridges unallocated.
    if cfg.RESERVED_SHARE:
        hashring.addRing(Bridges.UnallocatedHolder(), "unallocated",
                         cfg.RESERVED_SHARE)

    # Add pseudo distributors to hashring
    for pseudoRing in cfg.FILE_BUCKETS.keys():
        hashring.addPseudoRing(pseudoRing)

    return hashring, emailDistributor, ipDistributor
Esempio n. 6
0
    def test_EmailDistributor_getBridges_with_whitelist(self):
        """If an email address is in the whitelist, it should get a response
        every time it asks (i.e. no rate-limiting).
        """
        # The whitelist should be in the form {EMAIL: GPG_FINGERPRINT}
        whitelist = {
            '*****@*****.**': '0123456789ABCDEF0123456789ABCDEF01234567'
        }
        dist = EmailDistributor(self.key,
                                self.domainmap,
                                self.domainrules,
                                whitelist=whitelist)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        # A request from a whitelisted address should always get a response.
        bridgeRequest = self.makeClientRequest('*****@*****.**')
        for i in range(5):
            bridges = dist.getBridges(bridgeRequest, 1)
            self.assertEqual(len(bridges), 3)
Esempio n. 7
0
    def test_EmailDistributor_getBridges_default_client(self):
        """If EmailBridgeRequest.client was not set, then getBridges() should
        raise a bridgedb.parse.addr.BadEmail exception.
        """
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        # The "default" client is literally the string "default", see
        # bridgedb.bridgerequest.BridgeRequestBase.
        bridgeRequest = self.makeClientRequest('default')

        self.assertRaises(BadEmail, dist.getBridges, bridgeRequest, 1)
Esempio n. 8
0
    def test_EmailDistributor_cleanDatabase(self):
        """Calling cleanDatabase() should cleanup email times in database, but
        not allow clients who have been recently warned and/or ignored to
        receive a response again until the remainder of their MAX_EMAIL_RATE
        time is up.
        """
        dist = EmailDistributor(self.key, self.domainmap, self.domainrules)
        [dist.hashring.insert(bridge) for bridge in self.bridges]

        bridgeRequest = self.makeClientRequest('*****@*****.**')

        # The first request should get a response with bridges.
        self.assertEqual(len(dist.getBridges(bridgeRequest, 1)), 3)
        # The second gets a warning, and the third is ignored.
        self.assertRaises(TooSoonEmail, dist.getBridges, bridgeRequest, 1)
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)

        dist.cleanDatabase()

        # Cleaning the warning email times in the database shouldn't cause
        # '*****@*****.**' to be able to email again, because only the times
        # which aren't older than EMAIL_MAX_RATE should be cleared.
        self.assertRaises(IgnoreEmail, dist.getBridges, bridgeRequest, 1)