[tor-commits] [bridgedb/develop] Add bridgedb.captcha.GimpCaptcha implementation.

isis at torproject.org isis at torproject.org
Sun Mar 16 16:38:45 UTC 2014


commit f387a0a4d4bc5eeba6d0d8c965ebe35959ff5ce7
Author: Isis Lovecruft <isis at torproject.org>
Date:   Tue Mar 4 08:28:51 2014 +0000

    Add bridgedb.captcha.GimpCaptcha implementation.
    
     * ADD GimpCaptcha class, which handles retrieving cached CAPTCHAs from a
       local directory, creating challenge strings from the CAPTCHA answers, and
       later checking a client's proposed solution against the challenge.
     * FIXES #10809.
---
 lib/bridgedb/captcha.py |   74 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 74 insertions(+)

diff --git a/lib/bridgedb/captcha.py b/lib/bridgedb/captcha.py
index 07b06d4..a76303f 100644
--- a/lib/bridgedb/captcha.py
+++ b/lib/bridgedb/captcha.py
@@ -32,6 +32,10 @@ gimp-captcha_ and then cached locally.
 .. _gimp-captcha: https://github.com/isislovecruft/gimp-captcha
 """
 
+import hashlib
+import logging
+import random
+import os
 import urllib2
 
 from BeautifulSoup import BeautifulSoup
@@ -46,6 +50,9 @@ class ReCaptchaKeyError(Exception):
         msg = 'You must supply recaptcha API keys'
         Exception.__init__(self, msg)
 
+class GimpCaptchaError(Exception):
+    """General exception raised when a Gimp CAPTCHA cannot be retrieved."""
+
 class ICaptcha(Interface):
     """Interface specification for CAPTCHAs."""
     image = Attribute("A CAPTCHA image.")
@@ -64,6 +71,7 @@ class Captcha(object):
     def get(self):
         return self.image
 
+
 class ReCaptcha(Captcha):
     """A reCaptcha CAPTCHA."""
 
@@ -94,3 +102,69 @@ class ReCaptcha(Captcha):
         self.challenge = str(soup.find('input', {'name' : 'recaptcha_challenge_field'})['value'])
         self.image = urllib2.urlopen(imgurl).read()
 
+
+class GimpCaptcha(Captcha):
+    """A cached CAPTCHA image which was created with Gimp."""
+
+    def __init__(self, cacheDir=None, clientIP=None):
+        """Create a ``GimpCaptcha`` which retrieves images from **cacheDir**.
+
+        :raises GimpCaptchaError: if **cacheDir** is not a directory.
+        """
+        if not os.path.isdir(cacheDir):
+            raise GimpCaptchaError("Gimp captcha cache isn't a directory: %r"
+                                   % cacheDir)
+
+        self.image = None
+        self.challenge = None
+        self.cacheDir = cacheDir
+        self.clientIP = clientIP
+        super(GimpCaptcha, self).__init__()
+
+    @classmethod
+    def check(cls, challenge, answer, clientIP=None):
+        """Check a client's CAPTCHA solution against the **challenge**.
+
+        :rtype: bool
+        :returns: True if the CAPTCHA solution was correct.
+        """
+        logging.debug("Checking CAPTCHA solution %r against challenge %r"
+                      % (answer, challenge))
+        solution = cls.createChallenge(answer, clientIP)
+        if (not challenge) or (challenge != solution):
+            return False
+        return True
+
+    @classmethod
+    def createChallenge(cls, answer, clientIP=None):
+        """Hash a CAPTCHA answer together with a **clientIP**, if given.
+
+        :param str answer: The answer (either actual, or a client's proposed
+            solution) to a CAPTCHA.
+        :param str clientIP: The client's IP address.
+        """
+        challenge = '\n'.join([answer, str(clientIP)])
+        return hashlib.sha256(challenge).hexdigest()
+
+    def get(self):
+        """Get a random CAPTCHA from the cache directory.
+
+        :raises GimpCaptchaError: if the chosen CAPTCHA image file could not
+                                  be read.
+        :returns: A 2-tuple of ``(captcha, None)``, where ``captcha`` is the
+                  image file contents.
+        """
+        imageFilename = random.choice(os.listdir(self.cacheDir))
+        imagePath = os.path.join(self.cacheDir, imageFilename)
+
+        try:
+            with open(imagePath) as imageFile:
+                self.image = imageFile.read()
+        except (OSError, IOError) as err:
+            raise GimpCaptchaError("Could not read Gimp captcha image file: %r"
+                                   % imageFilename)
+
+        captchaAnswer = imageFilename.rsplit(os.path.extsep, 1)[0]
+        self.challenge = self.createChallenge(captchaAnswer, self.clientIP)
+
+        return (self.image, self.challenge)





More information about the tor-commits mailing list