Skip to content
This repository has been archived by the owner on Jan 25, 2018. It is now read-only.

Commit

Permalink
Reset pin front end (bug 802646)
Browse files Browse the repository at this point in the history
  • Loading branch information
wraithan committed Jan 7, 2013
1 parent 639f115 commit 321c869
Show file tree
Hide file tree
Showing 17 changed files with 327 additions and 95 deletions.
63 changes: 53 additions & 10 deletions lib/solitude/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,11 @@ def _buyer_from_response(self, res):
if res.get('errors'):
return res
elif res.get('objects'):
buyer['id'] = res['objects'][0]['resource_pk']
buyer['pin'] = res['objects'][0]['pin']
buyer['uuid'] = res['objects'][0]['uuid']
buyer = res['objects'][0]
buyer['id'] = res['objects'][0].get('resource_pk')
elif res.get('resource_pk'):
buyer['id'] = res['resource_pk']
buyer['pin'] = res['pin']
buyer['uuid'] = res['uuid']
buyer = res
buyer['id'] = res.get('resource_pk')
return buyer

def buyer_has_pin(self, uuid):
Expand All @@ -61,21 +59,54 @@ def create_buyer(self, uuid, pin=None):
'pin': pin})
return self._buyer_from_response(res)

def change_pin(self, buyer_id, pin):
"""Changes a buyer's PIN in solitude.
def set_needs_pin_reset(self, uuid, value=True):
"""Set flag for user to go through reset flow or not on next log in.
:param uuid: String to identify the buyer by.
:param value: Boolean for whether they should go into the reset flow or
not, defaults to True
:rtype: dictionary
"""
buyer = self.get_buyer(uuid)
res = self.safe_run(self.slumber.generic.buyer(id=buyer['id']).patch,
{'needs_pin_reset': value})
if 'errors' in res:
return res
return {}

def change_pin(self, uuid, pin):
"""Changes the pin of a buyer, for use with buyers who exist without
pins.
:param buyer_id integer: ID of the buyer you'd like to change the PIN
for.
:param pin: PIN to replace the buyer's pin with.
:param pin: PIN the user would like to change to.
:rtype: dictionary
"""
res = self.safe_run(self.slumber.generic.buyer(id=buyer_id).patch,
buyer = self.get_buyer(uuid)
res = self.safe_run(self.slumber.generic.buyer(id=buyer['id']).patch,
{'pin': pin})
# Empty string is a good thing from tastypie for a PATCH.
if 'errors' in res:
return res
return {}

def set_new_pin(self, uuid, new_pin):
"""Sets the new_pin for use with a buyer that is resetting their pin.
:param buyer_id integer: ID of the buyer you'd like to change the PIN
for.
:param pin: PIN the user would like to change to.
:rtype: dictionary
"""
buyer = self.get_buyer(uuid)
res = self.safe_run(self.slumber.generic.buyer(id=buyer['id']).patch,
{'new_pin': new_pin})
# Empty string is a good thing from tastypie for a PATCH.
if 'errors' in res:
return res
return {}

def get_buyer(self, uuid):
"""Retrieves a buyer by the their uuid.
Expand Down Expand Up @@ -111,6 +142,18 @@ def confirm_pin(self, uuid, pin):
{'uuid': uuid, 'pin': pin})
return res.get('confirmed', False)

def reset_confirm_pin(self, uuid, pin):
"""Confirms the buyer's pin, marking it at confirmed in solitude
:param uuid: String to identify the buyer by.
:param pin: PIN to confirm
:rtype: boolean
"""

res = self.safe_run(self.slumber.generic.reset_confirm_pin.post,
{'uuid': uuid, 'pin': pin})
return res.get('confirmed', False)

def verify_pin(self, uuid, pin):
"""Checks the buyer's PIN against what is stored in solitude.
Expand Down
52 changes: 44 additions & 8 deletions lib/solitude/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,6 @@ def setUpClass(cls):
raise SkipTest
client.create_buyer('dat:uuid', '1234')

def test_change_pin(self):
buyer_id = client.get_buyer(self.uuid)['id']
new_pin = self.pin[::-1]
eq_(client.change_pin(buyer_id, new_pin), {})
assert client.confirm_pin(self.uuid, new_pin)
assert client.verify_pin(self.uuid, new_pin)
eq_(client.change_pin(buyer_id, self.pin), {})

def test_get_buyer(self):
buyer = client.get_buyer(self.uuid)
eq_(buyer.get('uuid'), self.uuid)
Expand Down Expand Up @@ -105,6 +97,50 @@ def test_verify_without_confirm_and_good_pin(self):
def test_verify_alpha_pin(self):
assert not client.verify_pin(self.uuid, 'lame')

def test_reset_pin_flag_set(self):
# set
res = client.set_needs_pin_reset(self.uuid)
eq_(res, {})
buyer = client.get_buyer(self.uuid)
assert buyer['needs_pin_reset']

# unset
res = client.set_needs_pin_reset(self.uuid, False)
eq_(res, {})
buyer = client.get_buyer(self.uuid)
assert not buyer['needs_pin_reset']

def test_set_new_pin_for_reset(self):
uuid = 'set_new_pin_for_reset'
client.create_buyer(uuid, self.pin)
eq_(client.set_new_pin(uuid, '1122'), {})

def test_set_new_pin_for_reset_with_alpha_pin(self):
uuid = 'set_new_pin_for_reset_with_alpha_pin'
client.create_buyer(uuid, self.pin)
res = client.set_new_pin(uuid, 'meow')
assert res.get('errors')
eq_(res['errors'].get('new_pin'),
[ERROR_STRINGS['PIN may only consists of numbers']])

def test_reset_confirm_pin_with_good_pin(self):
uuid = 'reset_confirm_pin_good_pin'
new_pin = '1122'
client.create_buyer(uuid, self.pin)
client.set_new_pin(uuid, new_pin)
assert client.reset_confirm_pin(uuid, new_pin)
assert client.verify_pin(uuid, new_pin)

def test_reset_confirm_pin_with_bad_pin(self):
uuid = 'reset_confirm_pin_bad_pin'
new_pin = '1122'
client.create_buyer(uuid, self.pin)
client.set_new_pin(uuid, new_pin)
assert client.reset_confirm_pin(uuid, new_pin)
assert client.verify_pin(uuid, new_pin)
assert not client.reset_confirm_pin(uuid, self.pin)
assert client.verify_pin(uuid, new_pin)


class CreateBangoTest(TestCase):
uuid = 'some:pin'
Expand Down
46 changes: 43 additions & 3 deletions webpay/auth/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ def unverify(self):
del self.client.cookies[settings.SESSION_COOKIE_NAME]


@mock.patch.object(client, 'buyer_has_pin', lambda *args: False)
@mock.patch.object(client, 'get_buyer', lambda *args: {'pin': False,
'needs_pin_reset': False})
@mock.patch.object(settings, 'DOMAIN', 'web.pay')
class TestAuth(SessionTestCase):

Expand Down Expand Up @@ -106,7 +107,8 @@ def test_no_user(self, slumber):
def test_user_no_pin(self, slumber):
slumber.generic.buyer.get.return_value = {
'meta': {'total_count': 1},
'objects': [{'pin': False}]
'objects': [{'pin': False,
'needs_pin_reset': False}]
}
self.do_auth()
eq_(self.client.session.get('uuid_has_pin'), False)
Expand All @@ -115,9 +117,47 @@ def test_user_no_pin(self, slumber):
def test_user_with_pin(self, slumber):
slumber.generic.buyer.get.return_value = {
'meta': {'total_count': 1},
'objects': [{'pin': True}]
'objects': [{'pin': True,
'needs_pin_reset': False}]
}
data = self.do_auth()
eq_(self.client.session.get('uuid_has_pin'), True)
eq_(data['has_pin'], True)
eq_(data['pin_create'], reverse('pin.create'))


@mock.patch.object(auth_views, 'verify_assertion', lambda *a: good_assertion)
class TestBuyerHasResetFlag(SessionTestCase):

def do_auth(self):
res = self.client.post(reverse('auth.verify'), {'assertion': 'good'})
eq_(res.status_code, 200, res)
return json.loads(res.content)

@mock.patch('lib.solitude.api.client.slumber')
def test_no_user(self, slumber):
slumber.generic.buyer.get.return_value = {
'meta': {'total_count': 0}
}
data = self.do_auth()
eq_(self.client.session.get('uuid_reset_pin'), False)

@mock.patch('lib.solitude.api.client.slumber')
def test_user_no_reset_pin_flag(self, slumber):
slumber.generic.buyer.get.return_value = {
'meta': {'total_count': 1},
'objects': [{'pin': True,
'needs_pin_reset': False}]
}
self.do_auth()
eq_(self.client.session.get('uuid_reset_pin'), False)

@mock.patch('lib.solitude.api.client.slumber')
def test_user_with_reset_pin_flag(self, slumber):
slumber.generic.buyer.get.return_value = {
'meta': {'total_count': 1},
'objects': [{'pin': True,
'needs_pin_reset': True}]
}
data = self.do_auth()
eq_(self.client.session.get('uuid_reset_pin'), True)
1 change: 1 addition & 0 deletions webpay/auth/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@

urlpatterns = patterns('',
url(r'^verify$', views.verify, name='auth.verify'),
url(r'^logout$', views.logout, name='auth.logout'),
)
8 changes: 7 additions & 1 deletion webpay/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,14 @@ def get_user(request):
def set_user(request, email):
uuid = get_uuid(email)
request.session['uuid'] = uuid
set_user_has_pin(request, client.buyer_has_pin(uuid))
buyer = client.get_buyer(uuid)
set_user_has_pin(request, buyer.get('pin', False))
set_user_reset_pin(request, buyer.get('needs_pin_reset', False))


def set_user_has_pin(request, has_pin):
request.session['uuid_has_pin'] = has_pin


def set_user_reset_pin(request, reset_pin):
request.session['uuid_reset_pin'] = reset_pin
5 changes: 5 additions & 0 deletions webpay/auth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ def verify(request):

request.session.clear()
return http.HttpResponseBadRequest()


def logout(request):
# do logout stuff
return
17 changes: 8 additions & 9 deletions webpay/pin/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,11 @@ def clean_pin(self, *args, **kwargs):
raise forms.ValidationError(_('Incorrect PIN.'))


class ChangePinForm(BasePinForm):
old_pin = forms.CharField(max_length=4, required=True)

def clean_old_pin(self, *args, **kwargs):
old_pin = self.cleaned_data['old_pin']
if self.handle_client_errors(client.verify_pin(self.uuid, old_pin)):
self.buyer = self.handle_client_errors(client.get_buyer(self.uuid))
return old_pin
raise forms.ValidationError(_('Incorrect PIN'))
class ResetConfirmPinForm(BasePinForm):

def clean_pin(self, *args, **kwargs):
pin = self.cleaned_data['pin']
if self.handle_client_errors(client.reset_confirm_pin(self.uuid, pin)):
return pin

raise forms.ValidationError(_('Incorrect PIN.'))
2 changes: 1 addition & 1 deletion webpay/pin/templates/pin/change.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block content %}
<p>Change your PIN:</p>
<p>
<form action="{{ url('pin.change') }}" method="post">
<form action="{{ url('pin.reset') }}" method="post">
{{ form.non_field_errors() }}
{{ form.as_p()}}
<input type="submit" value="Create">
Expand Down
4 changes: 3 additions & 1 deletion webpay/pin/templates/pin/confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ <h2>Confirm your PIN:</h2>
{{ form.pin }}
</div>
<footer>
<a class="button" href="{{ url('pin.change') }}">Forgot PIN</a>
{# TODO: This is supposed to be a cancel button
that takes you out of the webpay process #}
<a class="button" href="#">Cancel</a>
<button type="submit">Continue</button>
</footer>
</form>
Expand Down
4 changes: 3 additions & 1 deletion webpay/pin/templates/pin/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ <h2>Create your PIN:</h2>
{{ form.pin }}
</div>
<footer>
<a class="button" href="{{ url('pin.change') }}">Forgot PIN</a>
{# TODO: This is supposed to be a cancel button
that takes you out of the webpay process #}
<a class="button" href="#">Cancel</a>
<button type="submit">Continue</button>
</footer>
</form>
Expand Down
2 changes: 1 addition & 1 deletion webpay/pin/templates/pin/includes/verify_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ <h2>Enter payment PIN</h2>
{{ form.pin }}
</div>
<footer>
<a class="button" href="{{ url('pin.change') }}">Forgot PIN</a>
<a class="button" href="{{ url('pin.reset_start') }}">Forgot PIN</a>
<button type="submit">Continue</button></footer>
</form>
</p>
Expand Down
19 changes: 19 additions & 0 deletions webpay/pin/templates/pin/reset_confirm.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "base.html" %}

{% block content %}
<h2>Confirm your new PIN:</h2>
<p>
<form id="pin" action="{{ url('pin.reset_confirm') }}" method="post">
{{ csrf() }}
{{ form.non_field_errors() }}
<div class="pinbox">
{{ form.pin }}
</div>
<footer>
<a class="button" href="{{ url('pin.reset_cancel') }}">Cancel</a>
<button type="submit">Continue</button>
</footer>
</form>
</p>

{% endblock %}
19 changes: 19 additions & 0 deletions webpay/pin/templates/pin/reset_create.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% extends "base.html" %}

{% block content %}
<h2>Your new PIN:</h2>
<p>
<form id="pin" action="{{ url('pin.reset_new_pin') }}" method="post">
{{ csrf() }}
{{ form.non_field_errors() }}
<div class="pinbox">
{{ form.pin }}
</div>
<footer>
<a class="button" href="{{ url('pin.reset_cancel') }}">Cancel</a>
<button type="submit">Continue</button>
</footer>
</form>
</p>

{% endblock %}
19 changes: 6 additions & 13 deletions webpay/pin/tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,28 +80,21 @@ def test_too_long_pin(self):
assert 'has at most 4' in str(form.errors['pin'])


class ChangePinFormTest(BasePinFormTestCase):
class ResetConfirmPinFormTest(BasePinFormTestCase):

def setUp(self):
super(ChangePinFormTest, self).setUp()
self.data = {'old_pin': 'old', 'pin': 'new'}

@patch.object(client, 'verify_pin', lambda x, y: True)
@patch.object(client, 'get_buyer', lambda x: {'uuid': x})
@patch.object(client, 'reset_confirm_pin', lambda x, y: True)
def test_correct_pin(self):
form = forms.ChangePinForm(uuid=self.uuid, data=self.data)
form = forms.ResetConfirmPinForm(uuid=self.uuid, data=self.data)
assert form.is_valid()
assert hasattr(form, 'buyer')

@patch.object(client, 'verify_pin', lambda x, y: False)
@patch.object(client, 'reset_confirm_pin', lambda x, y: False)
def test_incorrect_pin(self):
form = forms.ChangePinForm(uuid=self.uuid, data=self.data)
form = forms.ResetConfirmPinForm(uuid=self.uuid, data=self.data)
assert not form.is_valid()
assert 'Incorrect PIN' in str(form.errors)

@patch.object(client, 'verify_pin', lambda x, y: False)
def test_too_long_pin(self):
self.data.update({'pin': 'way too long pin'})
form = forms.ChangePinForm(uuid=self.uuid, data=self.data)
form = forms.ResetConfirmPinForm(uuid=self.uuid, data=self.data)
assert not form.is_valid()
assert 'has at most 4' in str(form.errors['pin'])

0 comments on commit 321c869

Please sign in to comment.