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

Commit

Permalink
hook up auth verify to a session and use that for the pin (bug 805673)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andy McKay committed Nov 1, 2012
1 parent 7f111a8 commit d717465
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 20 deletions.
1 change: 1 addition & 0 deletions requirements/prod.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ nose==1.2.1
ordereddict==1.1 ordereddict==1.1
django-picklefield==0.2.1 django-picklefield==0.2.1
python-dateutil==2.1 python-dateutil==2.1
python-memcached==1.48
requests==0.14.0 requests==0.14.0
schematic==0.2 schematic==0.2
six==1.2.0 six==1.2.0
Expand Down
1 change: 0 additions & 1 deletion settings_test.py
Original file line number Original file line Diff line number Diff line change
@@ -1,7 +1,6 @@
# This file overrides what's in your webpay/settings/local.py while # This file overrides what's in your webpay/settings/local.py while
# testing. # testing.



# This tells the runner to skip the Solitude client lib tests. # This tells the runner to skip the Solitude client lib tests.
# If you set this to a URL it should look like http://localhost:9000 # If you set this to a URL it should look like http://localhost:9000
# but you probably don't want to use your local dev server. # but you probably don't want to use your local dev server.
Expand Down
17 changes: 17 additions & 0 deletions webpay/auth/decorators.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,17 @@
import functools

import commonware.log

from django.core.exceptions import PermissionDenied

log = commonware.log.getLogger('w.auth')


def user_verified(f):
@functools.wraps(f)
def wrapper(request, *args, **kw):
if not request.session.get('uuid'):
log.error('No uuid in session, not verified.')
raise PermissionDenied
return f(request, *args, **kw)
return wrapper
1 change: 1 addition & 0 deletions webpay/auth/tests/__init__.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1 @@
from test import SessionTestCase
78 changes: 78 additions & 0 deletions webpay/auth/tests/test.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,78 @@
from django import test
from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpRequest
from django.utils.importlib import import_module

import mock
from nose.tools import eq_

good_assertion = {u'status': u'okay',
u'audience': u'http://some.site',
u'expires': 1351707833170,
u'email': u'a@a.com',
u'issuer': u'login.persona.org'}


class SessionTestCase(test.TestCase):
"""
A wrapper around Django tests to provide a verify method for use
in testing.
"""

def verify(self, uuid):
# This is a rip off of the Django test client login.
engine = import_module(settings.SESSION_ENGINE)

# Create a fake request to store login details.
request = HttpRequest()
request.session = engine.SessionStore()

request.session['uuid'] = uuid
request.session.save()

# Set the cookie to represent the session.
session_cookie = settings.SESSION_COOKIE_NAME
self.client.cookies[session_cookie] = request.session.session_key
cookie_data = {
'max-age': None,
'path': '/',
'domain': settings.SESSION_COOKIE_DOMAIN,
'secure': settings.SESSION_COOKIE_SECURE or None,
'expires': None,
}
self.client.cookies[session_cookie].update(cookie_data)

def unverify(self):
# Remove the browserid verification.
del self.client.cookies[settings.SESSION_COOKIE_NAME]


@mock.patch.object(settings, 'DOMAIN', 'web.pay')
class TestAuth(SessionTestCase):

def setUp(self):
self.url = reverse('auth.verify')

@mock.patch('webpay.auth.views.verify_assertion')
def test_good(self, verify_assertion):
verify_assertion.return_value = good_assertion
eq_(self.client.post(self.url, {'assertion': 'good'}).status_code, 200)

@mock.patch('webpay.auth.views.verify_assertion')
def test_session(self, verify_assertion):
verify_assertion.return_value = good_assertion
self.client.post(self.url, {'assertion': 'good'})
assert self.client.session['uuid'].startswith('web.pay:')

@mock.patch('webpay.auth.views.verify_assertion')
def test_bad(self, verify_assertion):
verify_assertion.return_value = False
eq_(self.client.post(self.url, {'assertion': 'bad'}).status_code, 400)

@mock.patch('webpay.auth.views.verify_assertion')
def test_session_cleaned(self, verify_assertion):
self.verify('a:b')
verify_assertion.return_value = False
eq_(self.client.post(self.url, {'assertion': 'bad'}).status_code, 400)
eq_(self.client.session.get('uuid'), None)
21 changes: 21 additions & 0 deletions webpay/auth/tests/test_utils_.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,21 @@
from django import test
from django.conf import settings

import mock
from webpay.auth.utils import get_uuid


@mock.patch.object(settings, 'DOMAIN', 'web.pay')
class TestUUID(test.TestCase):

def test_good(self):
res = get_uuid('f@f.com')
assert res.startswith('web.pay:')

def test_unicode(self):
res = get_uuid(u'f@f.com')
assert res.startswith('web.pay:')

def test_bad(self):
with self.assertRaises(ValueError):
get_uuid(None)
30 changes: 30 additions & 0 deletions webpay/auth/utils.py
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,30 @@
import hashlib

from django.conf import settings


def get_uuid(email):
"""
Given an email returns the hash of the email for this site. This will be
consistent for each email for this site and can be used as the uuid in
solitude. Because the leakage of the email is more of a privacy concern
than a security concern, we are just doing a simple sha1 hash.
:email: the email to hash.
"""
if not isinstance(email, basestring):
raise ValueError('get_uuid requires a string or unicode')
hashed = hashlib.sha1()
hashed.update(email)
return '%s:%s' % (settings.DOMAIN, hashed.hexdigest())


def get_user(request):
try:
return request.session.get('uuid')
except KeyError:
raise KeyError('Attempt to access user without it being set, '
'did you use the user_verified decorator?')

def set_user(request, email):
request.session['uuid'] = get_uuid(email)
6 changes: 5 additions & 1 deletion webpay/auth/views.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
from django_browserid.forms import BrowserIDForm from django_browserid.forms import BrowserIDForm
from session_csrf import anonymous_csrf_exempt from session_csrf import anonymous_csrf_exempt


log = commonware.log.getLogger('w.pay') from utils import set_user

log = commonware.log.getLogger('w.auth')




@anonymous_csrf_exempt @anonymous_csrf_exempt
Expand All @@ -19,6 +21,8 @@ def verify(request):
get_audience(request)) get_audience(request))
if result: if result:
log.info('assertion ok: %s' % result) log.info('assertion ok: %s' % result)
set_user(request, result['email'])
return http.HttpResponse('ok') return http.HttpResponse('ok')


request.session.clear()
return http.HttpResponseBadRequest() return http.HttpResponseBadRequest()
23 changes: 21 additions & 2 deletions webpay/pin/tests/test_views.py
Original file line number Original file line Diff line number Diff line change
@@ -1,24 +1,29 @@
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase


from mock import patch from mock import patch
from nose.tools import eq_ from nose.tools import eq_


from lib.solitude.api import client from lib.solitude.api import client
from lib.solitude.errors import ERROR_STRINGS from lib.solitude.errors import ERROR_STRINGS
from webpay.auth.tests import SessionTestCase
from webpay.pay import get_payment_url from webpay.pay import get_payment_url




class PinViewTestCase(TestCase): class PinViewTestCase(SessionTestCase):
url_name = '' url_name = ''


def setUp(self): def setUp(self):
self.url = reverse(self.url_name) self.url = reverse(self.url_name)
self.verify('a:b')




class CreatePinViewTest(PinViewTestCase): class CreatePinViewTest(PinViewTestCase):
url_name = 'pin.create' url_name = 'pin.create'


def test_unauth(self):
self.unverify()
eq_(self.client.post(self.url, data={'pin': '1234'}).status_code, 403)

@patch('lib.solitude.api.client.create_buyer', auto_spec=True) @patch('lib.solitude.api.client.create_buyer', auto_spec=True)
@patch('lib.solitude.api.client.change_pin', auto_spec=True) @patch('lib.solitude.api.client.change_pin', auto_spec=True)
@patch.object(client, 'get_buyer', lambda x: {}) @patch.object(client, 'get_buyer', lambda x: {})
Expand Down Expand Up @@ -76,6 +81,10 @@ def test_buyer_does_exist_with_alpha_pin(self, create_buyer):
class VerifyPinViewTest(PinViewTestCase): class VerifyPinViewTest(PinViewTestCase):
url_name = 'pin.verify' url_name = 'pin.verify'


def test_unauth(self):
self.unverify()
eq_(self.client.post(self.url, data={'pin': '1234'}).status_code, 403)

@patch.object(client, 'verify_pin', lambda x, y: True) @patch.object(client, 'verify_pin', lambda x, y: True)
def test_good_pin(self): def test_good_pin(self):
res = self.client.post(self.url, data={'pin': '1234'}) res = self.client.post(self.url, data={'pin': '1234'})
Expand All @@ -86,10 +95,20 @@ def test_bad_pin(self):
res = self.client.post(self.url, data={'pin': '1234'}) res = self.client.post(self.url, data={'pin': '1234'})
assert not 'Success' in res.content assert not 'Success' in res.content


@patch.object(client, 'verify_pin')
def test_uuid_used(self, verify_pin):
verify_pin.return_value = True
self.client.post(self.url, data={'pin': '1234'})
eq_(verify_pin.call_args[0][0], 'a:b')



class ChangePinViewTest(PinViewTestCase): class ChangePinViewTest(PinViewTestCase):
url_name = 'pin.change' url_name = 'pin.change'


def test_unauth(self):
self.unverify()
eq_(self.client.post(self.url, data={'pin': '1234'}).status_code, 403)

@patch('lib.solitude.api.client.change_pin', auto_spec=True) @patch('lib.solitude.api.client.change_pin', auto_spec=True)
@patch.object(client, 'verify_pin', lambda x, y: True) @patch.object(client, 'verify_pin', lambda x, y: True)
@patch.object(client, 'get_buyer', lambda x: {'uuid': x}) @patch.object(client, 'get_buyer', lambda x: {'uuid': x})
Expand Down
28 changes: 12 additions & 16 deletions webpay/pin/views.py
Original file line number Original file line Diff line number Diff line change
@@ -1,22 +1,24 @@
from django import http from django import http
from django.core.exceptions import PermissionDenied
from django.shortcuts import render from django.shortcuts import render


import commonware.log
from session_csrf import anonymous_csrf_exempt from session_csrf import anonymous_csrf_exempt


from lib.solitude.api import client from lib.solitude.api import client
from webpay.auth.decorators import user_verified
from webpay.auth.utils import get_user
from webpay.pay import get_payment_url from webpay.pay import get_payment_url
from . import forms from . import forms


log = commonware.log.getLogger('w.pin')


# TODO(Wraithan): remove all the anonymous once identity is figured out.
@anonymous_csrf_exempt @user_verified
def create(request): def create(request):
form = forms.CreatePinForm() form = forms.CreatePinForm()
if request.method == 'POST': if request.method == 'POST':
# TODO(Wraithan): Get the buyer's UUID once identity is figured out form = forms.CreatePinForm(uuid=get_user(request), data=request.POST)
# with webpay.
stub_uuid = 'dat:uuid'
form = forms.CreatePinForm(uuid=stub_uuid, data=request.POST)
if form.is_valid(): if form.is_valid():
if hasattr(form, 'buyer'): if hasattr(form, 'buyer'):
res = client.change_pin(form.buyer, form.cleaned_data['pin']) res = client.change_pin(form.buyer, form.cleaned_data['pin'])
Expand All @@ -29,27 +31,21 @@ def create(request):
return render(request, 'pin/create.html', {'form': form}) return render(request, 'pin/create.html', {'form': form})




@anonymous_csrf_exempt @user_verified
def verify(request): def verify(request):
form = forms.VerifyPinForm() form = forms.VerifyPinForm()
if request.method == 'POST': if request.method == 'POST':
# TODO(Wraithan): Get the buyer's UUID once identity is figured out form = forms.VerifyPinForm(uuid=get_user(request), data=request.POST)
# with webpay.
stub_uuid = 'dat:uuid'
form = forms.VerifyPinForm(uuid=stub_uuid, data=request.POST)
if form.is_valid(): if form.is_valid():
return http.HttpResponseRedirect(get_payment_url()) return http.HttpResponseRedirect(get_payment_url())
return render(request, 'pin/verify.html', {'form': form}) return render(request, 'pin/verify.html', {'form': form})




@anonymous_csrf_exempt @user_verified
def change(request): def change(request):
form = forms.ChangePinForm() form = forms.ChangePinForm()
if request.method == 'POST': if request.method == 'POST':
# TODO(Wraithan): Get the buyer's UUID once identity is figured out form = forms.ChangePinForm(uuid=get_user(request), data=request.POST)
# with webpay.
stub_uuid = 'dat:uuid'
form = forms.ChangePinForm(uuid=stub_uuid, data=request.POST)
if form.is_valid(): if form.is_valid():
res = client.change_pin(form.buyer, form.cleaned_data['pin']) res = client.change_pin(form.buyer, form.cleaned_data['pin'])
if form.handle_client_errors(res): if form.handle_client_errors(res):
Expand Down
2 changes: 2 additions & 0 deletions webpay/settings/base.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@
'mobility.middleware.XMobileMiddleware', 'mobility.middleware.XMobileMiddleware',
) )


SESSION_ENGINE = 'django.contrib.sessions.backends.cache'

# This is the key and secret for purchases, our special marketplace key and # This is the key and secret for purchases, our special marketplace key and
# secret for selling apps. # secret for selling apps.
KEY = 'marketplace' # would typically be a URL KEY = 'marketplace' # would typically be a URL
Expand Down

0 comments on commit d717465

Please sign in to comment.