Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Solitude side of reset pin (bug 802646)

  • Loading branch information...
commit 498cee56b9178baeb1ec06f583d67d119a3d8b64 1 parent d4f4de5
@wraithan wraithan authored
View
14 lib/buyers/field.py
@@ -1,6 +1,7 @@
from django.contrib.auth.hashers import (make_password, check_password,
get_hasher)
from django.db.models import CharField, SubfieldBase
+from django.forms import CharField as FormCharField
class HashField(CharField):
@@ -68,3 +69,16 @@ def __repr__(self):
def __len__(self):
return len(self.value)
+
+
+class FormHashField(FormCharField):
+ # This was written to pass along the bool that is generated by the
+ # dehydrate_pin method on BuyerResource. Tastypie does this if PIN
+ # wasn't passed along in a PATCH. Without this the CharField would
+ # coerce True to 'True' and we wouldn't be able to tell the
+ # difference between Tastypie being a pain or a user typing 'True'
+ # into the PIN entry.
+ def to_python(self, value):
+ if value is None or isinstance(value, (str, unicode, bool)):
+ return value
+ return unicode(value)
View
39 lib/buyers/forms.py
@@ -3,26 +3,44 @@
from django_paranoia.forms import ParanoidForm, ParanoidModelForm
from tastypie.validation import FormValidation
+from .field import FormHashField
from .models import Buyer
-class PinMixin(object):
- def clean_pin(self):
- pin = self.cleaned_data['pin']
+def base_clean_pin(form, field_name='pin'):
+ pin = form.cleaned_data[field_name]
- if pin is None or len(pin) == 0:
- return pin
+ # pin will be a boolean if it was filled in by tastypie using the
+ # dehydrate method. I wrote a custom FormHashField that will pass
+ # the bool along if tastypie sent it, is so we can tell the
+ # difference between tastypie doing this or someone typing "True"
+ # into the PIN entry.
+ if isinstance(pin, bool):
+ return form.instance.pin
- if not len(pin) == 4:
- raise forms.ValidationError('PIN must be exactly 4 numbers long')
+ if pin is None or len(pin) == 0:
+ return pin
- if not pin.isdigit():
- raise forms.ValidationError('PIN may only consists of numbers')
+ if not len(pin) == 4:
+ raise forms.ValidationError('PIN must be exactly 4 numbers long')
- return pin
+ if not pin.isdigit():
+ raise forms.ValidationError('PIN may only consists of numbers')
+
+ return pin
+
+
+class PinMixin(object):
+ def clean_pin(self):
+ return base_clean_pin(self)
+
+ def clean_new_pin(self):
+ return base_clean_pin(self, field_name='new_pin')
class BuyerForm(ParanoidModelForm, PinMixin):
+ pin = FormHashField(required=False)
+ new_pin = FormHashField(required=False)
class Meta:
model = Buyer
@@ -43,5 +61,6 @@ def is_valid(self, bundle, request=None):
else:
form = self.form_class(data)
if form.is_valid():
+ bundle.data.update(form.cleaned_data)
return {}
return form.errors
View
2  lib/buyers/models.py
@@ -11,6 +11,8 @@ class Buyer(Model):
pin = HashField(blank=True, null=True)
pin_confirmed = models.BooleanField(default=False)
active = models.BooleanField(default=True, db_index=True)
+ new_pin = HashField(blank=True, null=True)
+ needs_pin_reset = models.BooleanField(default=False)
class Meta(Model.Meta):
db_table = 'buyer'
View
26 lib/buyers/resources.py
@@ -13,7 +13,7 @@ class BuyerResource(ModelResource):
class Meta(ModelResource.Meta):
queryset = Buyer.objects.filter()
- fields = ['uuid', 'pin', 'active']
+ fields = ['uuid', 'pin', 'active', 'new_pin', 'needs_pin_reset']
list_allowed_methods = ['get', 'post', 'put']
allowed_methods = ['get', 'patch', 'put']
resource_name = 'buyer'
@@ -26,6 +26,9 @@ class Meta(ModelResource.Meta):
def dehydrate_pin(self, bundle):
return bool(bundle.obj.pin)
+ def dehydrate_new_pin(self, bundle):
+ return bool(bundle.obj.new_pin)
+
class BuyerPaypalResource(ModelResource):
buyer = fields.ToOneField('lib.buyers.resources.BuyerResource',
@@ -93,3 +96,24 @@ def obj_create(self, bundle, request=None, **kwargs):
else:
bundle.obj.valid = False
return bundle
+
+
+class BuyerResetPinResource(BuyerEndpointBase):
+ confirmed = fields.BooleanField(attribute='confirmed')
+
+ class Meta(BuyerEndpointBase.Meta):
+ resource_name = 'reset_confirm_pin'
+
+ def obj_create(self, bundle, request=None, **kwargs):
+ buyer = self.get_data(bundle)
+ pin = bundle.data.pop('pin')
+ if buyer.new_pin == pin:
+ buyer.pin = pin
+ buyer.new_pin = None
+ buyer.needs_pin_reset = False
+ buyer.pin_confirmed = True
+ buyer.save()
+ bundle.obj.confirmed = True
+ else:
+ bundle.obj.confirmed = False
+ return bundle
View
45 lib/buyers/tests/test_api.py
@@ -292,3 +292,48 @@ def test_bad_uuid(self):
def test_empty_post(self):
res = self.client.post(self.list_url, data={})
eq_(res.status_code, 400)
+
+
+class TestBuyerResetPin(APITest):
+
+ def setUp(self):
+ self.api_name = 'generic'
+ self.uuid = 'sample:uid'
+ self.pin = '1234'
+ self.new_pin = '4321'
+ self.buyer = Buyer.objects.create(uuid=self.uuid, pin=self.pin,
+ new_pin=self.new_pin,
+ needs_pin_reset=True)
+ self.list_url = self.get_list_url('reset_confirm_pin')
+
+ def test_good_uuid_and_pin(self):
+ res = self.client.post(self.list_url, data={'uuid': self.uuid,
+ 'pin': self.new_pin})
+ eq_(res.status_code, 201)
+ data = json.loads(res.content)
+ assert data.get('confirmed', False)
+ buyer = self.buyer.reget()
+ assert not buyer.needs_pin_reset
+ assert buyer.pin_confirmed
+ eq_(buyer.pin, self.new_pin)
+ eq_(data['uuid'], self.uuid)
+
+ def test_good_uuid_and_bad_pin(self):
+ res = self.client.post(self.list_url, data={'uuid': self.uuid,
+ 'pin': self.pin})
+ eq_(res.status_code, 201)
+ data = json.loads(res.content)
+ assert not data.get('confirmed', False)
+ buyer = self.buyer.reget()
+ assert not buyer.pin == self.new_pin
+ assert buyer.needs_pin_reset
+ eq_(data['uuid'], self.uuid)
+
+ def test_bad_uuid(self):
+ res = self.client.post(self.list_url, data={'uuid': 'bad:uuid',
+ 'pin': '4321'})
+ eq_(res.status_code, 404)
+
+ def test_empty_post(self):
+ res = self.client.post(self.list_url, data={})
+ eq_(res.status_code, 400)
View
2  migrations/28-add-reset-to-buyer.sql
@@ -0,0 +1,2 @@
+ALTER TABLE `buyer` ADD COLUMN `new_pin` varchar(255);
+ALTER TABLE `buyer` ADD COLUMN `needs_pin_reset` boolean NOT NULL DEFAULT 0;
View
4 solitude/urls.py
@@ -5,7 +5,8 @@
from lib.bango.urls import bango
from lib.delayable.resources import DelayableResource, ReplayResource
-from lib.buyers.resources import (BuyerConfirmPinResource, BuyerPaypalResource,
+from lib.buyers.resources import (BuyerConfirmPinResource,
+ BuyerResetPinResource, BuyerPaypalResource,
BuyerResource, BuyerVerifyPinResource)
from lib.paypal.urls import paypal
from lib.sellers.resources import (SellerResource, SellerPaypalResource,
@@ -19,6 +20,7 @@
api.register(BuyerResource())
api.register(BuyerConfirmPinResource())
api.register(BuyerVerifyPinResource())
+api.register(BuyerResetPinResource())
api.register(SellerResource())
api.register(SellerProductResource())
api.register(TransactionResource())

0 comments on commit 498cee5

Please sign in to comment.
Something went wrong with that request. Please try again.