From 4899abadc1c34cba086d5f395cfe6ac68eb29d0c Mon Sep 17 00:00:00 2001 From: marsh_ Date: Thu, 9 Jul 2015 12:58:49 +0300 Subject: [PATCH] minor fixes and test --- captcha/backends/base.py | 6 ++- captcha/backends/db.py | 16 +++--- captcha/backends/session.py | 22 ++++++--- captcha/fields.py | 33 ++++++++----- captcha/tests/tests.py | 99 ++++++++++++++++++++++++++++++++++++- captcha/views.py | 2 + testproject/settings.py | 2 +- 7 files changed, 150 insertions(+), 30 deletions(-) diff --git a/captcha/backends/base.py b/captcha/backends/base.py index 0751a05b..99e4f092 100644 --- a/captcha/backends/base.py +++ b/captcha/backends/base.py @@ -1,9 +1,11 @@ from captcha.conf import settings as captcha_settings + class DoesNotExist(Exception): """ Can't find captcha in store """ pass + class BaseStore(object): DoesNotExist = DoesNotExist @@ -12,14 +14,14 @@ def generate_key(self): Generate captcha with unique key """ return captcha_settings.get_challenge()() - + def remove_expired(self): """ Remove expired captcha records """ pass - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): """ Get captcha from store, or rise exception if captcha wasn't found """ diff --git a/captcha/backends/db.py b/captcha/backends/db.py index 02590a33..362fa5c3 100644 --- a/captcha/backends/db.py +++ b/captcha/backends/db.py @@ -1,13 +1,13 @@ -from captcha.conf import settings as captcha_settings from ..models import CaptchaStore, get_safe_now from .base import BaseStore + class DBStore(BaseStore): - def __init__(self, key = None): + def __init__(self, key=None): self._captcha = {} if key: try: - cap = CaptchaStore.objects.get(hashkey = key) + cap = CaptchaStore.objects.get(hashkey=key) self._captcha = { 'hashkey': cap.hashkey, 'challenge': cap.challenge, @@ -16,23 +16,23 @@ def __init__(self, key = None): } except CaptchaStore.DoesNotExist: raise self.DoesNotExist - + def __getitem__(self, key): return self._captcha[key] - + def remove_expired(self): CaptchaStore.objects.filter(expiration__lte=get_safe_now()).delete() def generate_key(self): return CaptchaStore.generate_key() - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): store = DBStore(hashkey) if response and store['response'] != response: raise self.DoesNotExist if not allow_expired and store['expiration'] < get_safe_now(): raise self.DoesNotExist return store - + def delete(self): - CaptchaStore.objects.filter(hashkey = self['hashkey']).delete() + CaptchaStore.objects.filter(hashkey=self['hashkey']).delete() diff --git a/captcha/backends/session.py b/captcha/backends/session.py index bf629046..1a643293 100644 --- a/captcha/backends/session.py +++ b/captcha/backends/session.py @@ -1,27 +1,35 @@ from .base import BaseStore from captcha.conf import settings as captcha_settings from django.conf import settings -from importlib import import_module -Store = import_module(settings.SESSION_ENGINE).SessionStore import django - +try: + from importlib import import_module + Store = import_module(settings.SESSION_ENGINE).SessionStore +except: + # py 2.6 + backend = __import__(settings.SESSION_ENGINE, globals(), locals(), ['SessionStore'], -1) + Store = backend.SessionStore + + class SessionStore(BaseStore): def generate_key(self): challenge, response = super(SessionStore, self).generate_key() store = Store() store.set_expiry(60 * int(captcha_settings.CAPTCHA_TIMEOUT)) - store['challenge']=challenge - store['response']=response + store['challenge'] = challenge + store['response'] = response store.save() return store.session_key - + def remove_expired(self): if not django.get_version() < '1.5': Store.clear_expired() - def get(self, response=None, hashkey=None, allow_expired = True): + def get(self, response=None, hashkey=None, allow_expired=True): s = Store(session_key=hashkey) + if not s.get('response'): + raise self.DoesNotExist if response: if s['response'] != response: raise self.DoesNotExist diff --git a/captcha/fields.py b/captcha/fields.py index 4104c471..82f89314 100644 --- a/captcha/fields.py +++ b/captcha/fields.py @@ -8,16 +8,6 @@ from six import u from .backends.base import BaseStore -CaptchaStore = None -if settings.CAPTCHA_STORE == 'SESSION': - from .backends.session import SessionStore - CaptchaStore = SessionStore() -elif settings.CAPTCHA_STORE == 'DB': - from .backends.db import DBStore - CaptchaStore = DBStore() -else: - raise ImproperlyConfigured - class BaseCaptchaTextInput(MultiWidget): """ @@ -40,6 +30,17 @@ def fetch_captcha_store(self, name, value, attrs=None): Fetches a new CaptchaStore This has to be called inside render """ + + CaptchaStore = None + if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() + elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() + else: + raise ImproperlyConfigured + try: reverse('captcha-image', args=('dummy',)) except NoReverseMatch: @@ -135,6 +136,16 @@ def compress(self, data_list): return None def clean(self, value): + CaptchaStore = None + if settings.CAPTCHA_STORE == 'SESSION': + from .backends.session import SessionStore + CaptchaStore = SessionStore() + elif settings.CAPTCHA_STORE == 'DB': + from .backends.db import DBStore + CaptchaStore = DBStore() + else: + raise ImproperlyConfigured + super(CaptchaField, self).clean(value) response, value[1] = (value[1] or '').strip().lower(), '' CaptchaStore.remove_expired() @@ -150,7 +161,7 @@ def clean(self, value): pass else: try: - CaptchaStore.get(response=response, hashkey=value[0], allow_expired = False).delete() + CaptchaStore.get(response=response, hashkey=value[0], allow_expired=False).delete() except BaseStore.DoesNotExist: raise ValidationError(getattr(self, 'error_messages', {}).get('invalid', ugettext_lazy('Invalid CAPTCHA'))) return value diff --git a/captcha/tests/tests.py b/captcha/tests/tests.py index c82f2378..c94b1faf 100644 --- a/captcha/tests/tests.py +++ b/captcha/tests/tests.py @@ -2,11 +2,14 @@ from captcha.conf import settings from captcha.fields import CaptchaField, CaptchaTextInput from captcha.models import CaptchaStore, get_safe_now +from captcha.backends.db import DBStore +from captcha.backends.session import SessionStore from django.conf import settings as django_settings from django.core.exceptions import ImproperlyConfigured from django.core.urlresolvers import reverse from django.test import TestCase from django.utils.translation import ugettext_lazy +import django import datetime import json import re @@ -29,7 +32,6 @@ class CaptchaCase(TestCase): urls = 'captcha.tests.urls' def setUp(self): - self.stores = {} self.__current_settings_output_format = settings.CAPTCHA_OUTPUT_FORMAT self.__current_settings_dictionary = settings.CAPTCHA_WORDS_DICTIONARY @@ -332,5 +334,100 @@ def test_image_size(self): settings.CAPTCHA_IMAGE_SIZE = __current_test_mode_setting +class StoresCase(TestCase): + urls = 'captcha.tests.urls' + + # store tests + def test_db_store(self): + store = DBStore() + key = store.generate_key() + record = store.get(hashkey=key) + self.assertEqual(record['hashkey'], key) + self.assertNotEqual(record._captcha.get('challenge'), None) + self.assertNotEqual(record._captcha.get('response'), None) + record.delete() + try: + store.get(hashkey=key) + self.fail('Record deletion error') + except: + pass + + key = store.generate_key() + cap = CaptchaStore.objects.get(hashkey=key) + cap.expiration = get_safe_now() - datetime.timedelta(minutes=1) + cap.save() + try: + store.get(hashkey=key, allow_expired=False) + self.fail() + except: + pass + + store.remove_expired() + try: + store.get(hashkey=key) + self.fail('remove_expired failed') + except: + pass + + def test_session_store(self): + store = SessionStore() + key = store.generate_key() + record = store.get(hashkey=key) + self.assertEqual(record.session_key, key) + self.assertNotEqual(record.get('challenge'), None) + self.assertNotEqual(record.get('response'), None) + record.delete() + try: + store.get(hashkey=key) + self.fail('Record deletion error') + except: + pass + + key = store.generate_key() + cap = store.get(hashkey=key) + cap.set_expiry(-1 * 60) + cap.save() + try: + store.get(hashkey=key, allow_expired=False) + self.fail() + except: + pass + + if not django.get_version() < '1.5': + # django lower than 1.5 can't remove expired sessions + store.remove_expired() + try: + store.get(hashkey=key) + self.fail('remove_expired failed') + except: + pass + + # view tests + def testFormSubmit(self): + settings.CAPTCHA_STORE = 'SESSION' + r = self.client.get(reverse('captcha-test')) + self.assertEqual(r.status_code, 200) + + store = SessionStore() + hash_ = re.findall(r'value="([0-9a-z]+)"', str(r.content))[0] + response = store.get(hashkey=hash_)['response'] + + r = self.client.post(reverse('captcha-test'), dict(captcha_0=hash_, captcha_1=response, subject='xxx', sender='asasd@asdasd.com')) + self.assertEqual(r.status_code, 200) + self.assertTrue(str(r.content).find('Form validated') > 0) + + r = self.client.post(reverse('captcha-test'), dict(captcha_0=hash_, captcha_1=response, subject='xxx', sender='asasd@asdasd.com')) + self.assertEqual(r.status_code, 200) + self.assertFalse(str(r.content).find('Form validated') > 0) + + def testWrongSubmit(self): + settings.CAPTCHA_STORE = 'SESSION' + for urlname in ('captcha-test', 'captcha-test-model-form'): + r = self.client.get(reverse(urlname)) + self.assertEqual(r.status_code, 200) + r = self.client.post(reverse(urlname), dict(captcha_0='abc', captcha_1='wrong response', subject='xxx', sender='asasd@asdasd.com')) + self.assertFormError(r, 'form', 'captcha', ugettext_lazy('Invalid CAPTCHA')) + + def trivial_challenge(): return 'trivial', 'trivial' diff --git a/captcha/views.py b/captcha/views.py index d31c6c19..956a2404 100644 --- a/captcha/views.py +++ b/captcha/views.py @@ -1,6 +1,7 @@ from captcha.conf import settings from captcha.helpers import captcha_image_url from django.http import HttpResponse, Http404 +from django.core.exceptions import ImproperlyConfigured import random import re import tempfile @@ -40,6 +41,7 @@ else: raise ImproperlyConfigured + def getsize(font, text): if hasattr(font, 'getoffset'): return [x + y for x, y in zip(font.getsize(text), font.getoffset(text))] diff --git a/testproject/settings.py b/testproject/settings.py index 538842ce..9a0db118 100644 --- a/testproject/settings.py +++ b/testproject/settings.py @@ -33,7 +33,7 @@ 'captcha', ] -LANGUAGE_CODE = "en" +LANGUAGE_CODE = "en-us" LANGUAGES = ( ('en', 'English'),