Skip to content

Commit 23ccf75

Browse files
committed
- sign refactoring
- fixed bug that sign even None values - http protocol (alpha)
1 parent d5e576a commit 23ccf75

File tree

9 files changed

+81
-30
lines changed

9 files changed

+81
-30
lines changed

concurrency/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import datetime
33
import os
44

5-
VERSION = __version__ = (0, 4, 0, 'beta', 7)
5+
VERSION = __version__ = (0, 4, 0, 'beta', 8)
66
__author__ = 'sax'
77

88

concurrency/core.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
class RecordModifiedError(DatabaseError):
1010
pass
1111

12+
class Http409(Exception):
13+
pass
1214

1315
class InconsistencyError(DatabaseError):
1416
pass

concurrency/forms.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from django import forms
22
from django.core import validators
33
from django.core.exceptions import NON_FIELD_ERRORS, SuspiciousOperation
4+
from django.core.signing import Signer, BadSignature
45
from django.forms import ModelForm, HiddenInput
56
from django.utils import timezone
7+
from django.utils.encoding import smart_str
68
from django.utils.safestring import mark_safe
79
from django.utils.translation import ugettext as _
810
from concurrency.core import _select_lock, RecordModifiedError
9-
from concurrency.signing import get_signer, BadSignature
1011

1112

1213
class ConcurrentForm(ModelForm):
@@ -42,11 +43,16 @@ def render(self, name, value, attrs=None):
4243
return mark_safe("%s<div>%s</div>" % (ret, value))
4344

4445

46+
class VersionFieldSigner(Signer):
47+
pass
48+
49+
4550
class VersionField(forms.IntegerField):
4651
widget = HiddenInput # Default widget to use when rendering this type of Field.
4752
hidden_widget = HiddenInput# Default widget to use when rendering this as "hidden".
4853

4954
def __init__(self, *args, **kwargs):
55+
self._signer = kwargs.pop('signer', VersionFieldSigner())
5056
kwargs.pop('min_value', None)
5157
kwargs.pop('max_value', None)
5258
kwargs['required'] = True
@@ -56,12 +62,16 @@ def __init__(self, *args, **kwargs):
5662

5763
def clean(self, value):
5864
try:
59-
return int(get_signer().unsign(value))
60-
except BadSignature:
61-
raise SuspiciousOperation(_('Version number seems altered'))
65+
if value is not None:
66+
return int(self._signer.unsign(value))
67+
return 0
68+
except (BadSignature, ValueError):
69+
raise SuspiciousOperation(_('Version number seems tampered'))
6270

6371
def prepare_value(self, value):
64-
return get_signer().sign(value)
72+
if value:
73+
return self._signer.sign(value)
74+
return None
6575

6676
def to_python(self, value):
6777
if value is None:

concurrency/http.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
from django.http import HttpResponseRedirectBase, HttpResponse
3+
4+
5+
class HttpResponseConflict(HttpResponse):
6+
status_code = 409
7+
8+
def __init__(self):
9+
super(HttpResponse, self).__init__()

concurrency/middleware.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
from concurrency.core import Http409
3+
from concurrency.http import HttpResponseConflict
4+
5+
6+
class Http409Middleware(object):
7+
def process_exception(self, request, exception):
8+
if isinstance(exception, Http409):
9+
return HttpResponseConflict()

concurrency/signing.py

Lines changed: 0 additions & 12 deletions
This file was deleted.

concurrency/tests/all.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
import time
99
import datetime
1010
from django.contrib.auth.models import User
11-
from django.core.signing import Signer
1211
from django.forms.models import modelform_factory
1312
from django.test import TestCase
1413
from concurrency.core import RecordModifiedError
1514
from concurrency.core import apply_concurrency_check
1615
from concurrency.fields import IntegerVersionField
16+
from concurrency.forms import VersionFieldSigner
1717
from concurrency.tests.models import TestModel0, TestModel1, TestModel2, TestModel3, TestModel0_Proxy, \
1818
TestAbstractModel0, TestModelGroup, TestModelWithCustomSave, TestIssue3Model, ModelWithCustomSave, \
1919
TestModelGroupWithCustomSave, AutoIncConcurrentModel, TestCustomUser
@@ -148,7 +148,7 @@ def test_form_save(self):
148148
original_version = self.TARGET._get_test_revision_number()
149149
version_field_name = self.TARGET.RevisionMetaInfo.field.name
150150

151-
form = formClass(self._get_form_data(**{version_field_name: Signer().sign(original_version)}),
151+
form = formClass(self._get_form_data(**{version_field_name: VersionFieldSigner().sign(original_version)}),
152152
instance=self.TARGET)
153153

154154
self.assertTrue(form.is_valid(), form.errors)
@@ -157,7 +157,7 @@ def test_form_save(self):
157157
# self.assertGreater(obj._get_test_revision_number(), original_version)
158158
self.assertNotEqual(obj._get_test_revision_number(), original_version)
159159

160-
form = formClass(self._get_form_data(**{version_field_name: Signer().sign(obj._get_test_revision_number())}),
160+
form = formClass(self._get_form_data(**{version_field_name: VersionFieldSigner().sign(obj._get_test_revision_number())}),
161161
instance=obj)
162162
self.assertTrue(form.is_valid(), form.errors)
163163
pre_save_version = obj._get_test_revision_number()

concurrency/tests/contrib_admin.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
from django.conf import global_settings
55
from django.contrib.auth.models import User
66
import django.core.management
7-
from django.core.signing import Signer
87
from django.core.urlresolvers import reverse
98
from django.forms.models import modelform_factory
109
from django.test import TestCase
1110
# from concurrency.tests.models import *
1211
from concurrency import forms
13-
from concurrency.forms import ConcurrentForm, VersionWidget
12+
from concurrency.forms import ConcurrentForm, VersionWidget, VersionFieldSigner
1413
from concurrency.tests import TestModel0, TestModel1
1514

1615
INSTALLED_APPS = (
@@ -73,7 +72,7 @@ def test_creation(self):
7372
url = reverse('admin:concurrency_testmodel0_add')
7473
data = {'username': u'new_username',
7574
'last_name': None,
76-
'version': Signer().sign(0),
75+
'version': VersionFieldSigner().sign(0),
7776
'char_field': None,
7877
'_continue': 1,
7978
'date_field': '2010-09-01'}
@@ -86,7 +85,7 @@ def test_creation_with_customform(self):
8685
url = reverse('admin:concurrency_testmodel1_add')
8786
data = {'username': u'new_username',
8887
'last_name': None,
89-
'version': Signer().sign(0),
88+
'version': VersionFieldSigner().sign(0),
9089
'char_field': None,
9190
'_continue': 1,
9291
'date_field': '2010-09-01'}
@@ -110,7 +109,7 @@ def test_standard_update(self):
110109
# data = model_to_dict(target, exclude=['id'])
111110
data = {'username': u'new_username',
112111
'last_name': None,
113-
'version': Signer().sign(target.version),
112+
'version': VersionFieldSigner().sign(target.version),
114113
'char_field': None,
115114
'_continue': 1,
116115
'date_field': '2010-09-01'}

concurrency/tests/forms.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,51 @@
1-
from django.core.signing import Signer
1+
from django.core.exceptions import SuspiciousOperation
22
from django.forms.models import modelform_factory
33
from django.forms.widgets import HiddenInput, TextInput
4+
from django.utils.encoding import smart_str
45
from django.utils.unittest.case import TestCase
5-
from concurrency.forms import ConcurrentForm
6+
from concurrency.forms import ConcurrentForm, VersionField, VersionFieldSigner
67
from concurrency.tests import TestModel0, TestIssue3Model
78

89

10+
class DummySigner():
11+
def sign(self, value):
12+
return smart_str(value)
13+
14+
def unsign(self, signed_value):
15+
return smart_str(signed_value)
16+
917
class ConcurrentFormTest(TestCase):
1018
def test_version(self):
1119
Form = modelform_factory(TestModel0, ConcurrentForm)
1220
form = Form()
1321
self.assertIsInstance(form.fields['version'].widget, HiddenInput)
1422

23+
def test_signer(self):
24+
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
25+
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {'revision': VersionField(signer=DummySigner())}))
26+
data = {'id': 1,
27+
'revision': obj.revision}
28+
form = Form(data, instance=obj)
29+
self.assertTrue(form.is_valid(), form.non_field_errors())
30+
31+
def test_signer(self):
32+
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
33+
form = Form({})
34+
self.assertTrue(form.is_valid(), form.non_field_errors())
35+
36+
def test_tamperig(self):
37+
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
38+
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
39+
data = {'username': 'aaa',
40+
'last_name': None,
41+
'date_field': None,
42+
'char_field': None,
43+
'version': u'abc',
44+
'id': 1,
45+
'revision': obj.revision}
46+
form = Form(data, instance=obj)
47+
self.assertRaises(SuspiciousOperation, form.is_valid)
48+
1549
def test_custom_name(self):
1650
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
1751
form = Form()
@@ -28,7 +62,7 @@ def test_save(self):
2862
'char_field': None,
2963
'version': u'abc',
3064
'id': 1,
31-
'revision': Signer().sign(obj.revision)}
65+
'revision': VersionFieldSigner().sign(obj.revision)}
3266
form = Form(data, instance=obj)
3367
obj_copy.save() # save
3468
self.assertFalse(form.is_valid())
@@ -43,7 +77,7 @@ def test_is_valid(self):
4377
'char_field': None,
4478
'version': u'abc',
4579
'id': 1,
46-
'revision': Signer().sign(obj.revision)}
80+
'revision': VersionFieldSigner().sign(obj.revision)}
4781
form = Form(data, instance=obj)
4882
obj.save() # save again simulate concurrent editing
4983
self.assertRaises(ValueError, form.save)

0 commit comments

Comments
 (0)