Merge lp:~canonical-isd-hackers/canonical-identity-provider/bug_392101_cookie_test_alt into lp:canonical-identity-provider/release

Proposed by David Owen
Status: Merged
Merged at revision: 54
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/bug_392101_cookie_test_alt
Merge into: lp:canonical-identity-provider/release
Diff against target: 250 lines (+96/-6)
8 files modified
identityprovider/decorators.py (+14/-0)
identityprovider/templates/cookies.html (+18/-0)
identityprovider/tests/test_views_ui.py (+41/-4)
identityprovider/tests/utils.py (+3/-0)
identityprovider/urls.py (+1/-0)
identityprovider/views/account.py (+13/-1)
identityprovider/views/ui.py (+4/-1)
scripts/test (+2/-0)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/bug_392101_cookie_test_alt
Reviewer Review Type Date Requested Status
Canonical ISD hackers Pending
Review via email: mp+26104@code.launchpad.net

Description of the change

Added checks for cookies to the major entry points, except the root. Root can't have a redirect because it's used as a service endpoint for other servers (OpenID Relying Parties).

To post a comment you must log in.
Revision history for this message
David Owen (dsowen) wrote :

I found the Django test client used in some of our unit tests (I hadn't known about this test client before). It doesn't follow redirects automatically, you have to walk it through them. I think I'd be able to use this to test the cookie-test flows, but our doctests I think aren't setup for this client yet.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/decorators.py'
2--- identityprovider/decorators.py 2010-05-05 08:32:42 +0000
3+++ identityprovider/decorators.py 2010-06-01 20:49:21 +0000
4@@ -9,6 +9,7 @@
5 from datetime import datetime, timedelta
6 import functools
7 from hashlib import sha1
8+import urllib
9
10
11 def guest_required(func):
12@@ -48,6 +49,19 @@
13 return wrapper
14
15
16+disable_cookie_check = False
17+
18+def requires_cookies(func):
19+ @functools.wraps(func)
20+ def wrapper(request, *args, **kwargs):
21+ if disable_cookie_check or request.session.test_cookie_worked():
22+ return func(request, *args, **kwargs)
23+ request.session.set_test_cookie()
24+ quoted = urllib.quote_plus(request.get_full_path())
25+ return HttpResponseRedirect('/+cookie;' + quoted)
26+ return wrapper
27+
28+
29 class ratelimit(object):
30 """ A rate-limiting decorator.
31 Strongly based on Simon Willison's code,
32
33=== added file 'identityprovider/templates/cookies.html'
34--- identityprovider/templates/cookies.html 1970-01-01 00:00:00 +0000
35+++ identityprovider/templates/cookies.html 2010-06-01 20:49:21 +0000
36@@ -0,0 +1,18 @@
37+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
38+GNU Affero General Public License version 3 (see the file LICENSE). -->
39+
40+{% extends "base.html" %}
41+{% load i18n %}
42+
43+{% block "title" %}{% trans "Cookies required" %}{% endblock %}
44+
45+{% block "content" %}
46+<h1 class="main">{% trans "Cookies required" %}</h1>
47+
48+<div>
49+ <p>
50+ {% blocktrans %}Cookies are required to use this service. Please
51+ enable cookies in your browser.{% endblocktrans %}
52+ </p>
53+</div>
54+{% endblock %}
55
56=== modified file 'identityprovider/tests/test_views_ui.py'
57--- identityprovider/tests/test_views_ui.py 2010-05-26 16:59:02 +0000
58+++ identityprovider/tests/test_views_ui.py 2010-06-01 20:49:21 +0000
59@@ -3,16 +3,17 @@
60 # GNU Affero General Public License version 3 (see the file LICENSE).
61
62 import logging
63+import re
64 import urllib2
65-import re
66+from urlparse import urlsplit
67
68 from django.conf import settings
69-from django.core import mail
70-from django.core import urlresolvers
71+from django.core import mail, urlresolvers
72+from django.http import QueryDict
73 from django.test.client import Client
74 from openid.message import IDENTIFIER_SELECT
75
76-from identityprovider import signed
77+from identityprovider import decorators, signed
78 from identityprovider.models import authtoken as at
79 from identityprovider.models import EmailAddress, Person, OpenIDRPConfig
80 from identityprovider.models.const import EmailStatus
81@@ -781,3 +782,39 @@
82 self.assertContains(self.client.get('/'), 'es.png')
83 self.client.login(username='mark@example.com', password='test')
84 self.assertContains(self.client.get('/'), 'es.png')
85+
86+
87+class CookiesTestCase(BasicAccountTestCase):
88+
89+ def setUp(self):
90+ super(CookiesTestCase, self).setUp()
91+ self._disable_cookie_check = decorators.disable_cookie_check
92+ decorators.disable_cookie_check = False
93+
94+ def tearDown(self):
95+ decorators.disable_cookie_check = self._disable_cookie_check
96+ super(CookiesTestCase, self).tearDown()
97+
98+ def _get(self, url, *params):
99+ r = self.client.get(url)
100+ # The following line makes this cookieless:
101+ self.client.cookies.clear()
102+ if 300 <= r.status_code < 400:
103+ location = r['Location']
104+ scheme, netloc, path, query, fragment = urlsplit(location)
105+ return self._get(path, QueryDict(query))
106+ else:
107+ return r
108+
109+ def _check_url(self, url):
110+ r = self._get(url)
111+ self.assertContains(r, '<title>Cookies required</title>')
112+
113+ def test_login(self):
114+ self._check_url('/+login')
115+
116+ def test_forgot_password(self):
117+ self._check_url('/+forgot_password')
118+
119+ def test_new_account(self):
120+ self._check_url('/+new_account')
121
122=== modified file 'identityprovider/tests/utils.py'
123--- identityprovider/tests/utils.py 2010-05-28 15:11:06 +0000
124+++ identityprovider/tests/utils.py 2010-06-01 20:49:21 +0000
125@@ -23,6 +23,8 @@
126 OpenIDServiceEndpoint, OPENID_1_0_TYPE, OPENID_1_1_TYPE,
127 OPENID_2_0_TYPE, OPENID_IDP_2_0_TYPE)
128 from openid.message import IDENTIFIER_SELECT
129+
130+from identityprovider import decorators
131 from identityprovider.tests import mockdb
132
133
134@@ -219,6 +221,7 @@
135 sys.stderr = NullDevice()
136
137 setup_test_environment()
138+ decorators.disable_cookie_check = True
139 settings.DEBUG = False
140 suite = TestSuite()
141
142
143=== modified file 'identityprovider/urls.py'
144--- identityprovider/urls.py 2010-05-12 19:41:02 +0000
145+++ identityprovider/urls.py 2010-06-01 20:49:21 +0000
146@@ -45,6 +45,7 @@
147 urlpatterns += patterns('identityprovider.views.account',
148 (r'^%(optional_token)s\+edit$' % repls, 'index'),
149 (r'^$', 'index'),
150+ (r'^\+cookie;(?P<url>.+)$', 'cookie'),
151 (r'^%(optional_token)s\+index$' % repls, 'index'),
152 (r'^%(optional_token)s\+new-email$' % repls, 'new_email'),
153 (r'^%(optional_token)s\+verify-email$' % repls, 'verify_email'),
154
155=== modified file 'identityprovider/views/account.py'
156--- identityprovider/views/account.py 2010-05-21 14:02:52 +0000
157+++ identityprovider/views/account.py 2010-06-01 20:49:21 +0000
158@@ -2,10 +2,11 @@
159 # GNU Affero General Public License version 3 (see the file LICENSE).
160
161 from openid.yadis.constants import YADIS_HEADER_NAME
162+import urllib
163
164 from django.conf import settings
165 from django.contrib.auth.decorators import login_required
166-from django.http import HttpResponseRedirect
167+from django.http import HttpResponse, HttpResponseRedirect
168 from django.shortcuts import render_to_response, get_object_or_404
169 from django.template import RequestContext
170 from django.utils.translation import ugettext as _
171@@ -24,6 +25,8 @@
172 if request.user.is_authenticated():
173 resp = account_index(request, token)
174 else:
175+ if not request.session.test_cookie_worked():
176+ request.session.set_test_cookie()
177 context = RequestContext(request, {
178 'form': LoginForm(),
179 })
180@@ -32,6 +35,15 @@
181 return resp
182
183
184+def cookie(request, url=None):
185+ if request.session.test_cookie_worked():
186+ return HttpResponseRedirect(url or '/')
187+ # Send the cookie again, so that the user can hit reload on the
188+ # page we return to continue on.
189+ request.session.set_test_cookie()
190+ return render_to_response('cookies.html')
191+
192+
193 @login_required
194 def account_index(request, token=None):
195 account = request.user
196
197=== modified file 'identityprovider/views/ui.py'
198--- identityprovider/views/ui.py 2010-06-01 15:39:30 +0000
199+++ identityprovider/views/ui.py 2010-06-01 20:49:21 +0000
200@@ -20,7 +20,7 @@
201 from django.template.loader import render_to_string
202
203 from identityprovider.branding import current_brand
204-from identityprovider.decorators import guest_required, dont_cache, limitlogin
205+from identityprovider.decorators import dont_cache, guest_required, limitlogin, requires_cookies
206 from identityprovider.forms import (LoginForm, ForgotPasswordForm,
207 ResetPasswordForm, NewAccountForm, ConfirmNewAccountForm)
208 from identityprovider.models import (Account, AccountPassword, AuthToken,
209@@ -42,6 +42,7 @@
210 @guest_required
211 @dont_cache
212 @limitlogin()
213+@requires_cookies
214 def login(request, token=None, rpconfig=None):
215 form = LoginForm()
216 if request.method == 'POST':
217@@ -123,6 +124,7 @@
218
219 @guest_required
220 @check_readonly
221+@requires_cookies
222 def new_account(request, token=None):
223 if request.method == 'GET':
224 if 'email' in request.GET:
225@@ -334,6 +336,7 @@
226
227 @guest_required
228 @check_readonly
229+@requires_cookies
230 def forgot_password(request, token=None):
231 if request.method == 'GET':
232 if 'email' in request.GET:
233
234=== modified file 'scripts/test'
235--- scripts/test 2010-05-28 14:30:22 +0000
236+++ scripts/test 2010-06-01 20:49:21 +0000
237@@ -89,11 +89,13 @@
238
239 sudo $ENV `which python` wsgi_test_server.py &
240 sso_pid=$!
241+ trap "sudo kill $sso_pid ; exit" SIGINT
242
243 wait_until_up
244
245 (cd doctests && python runner.py)
246
247+ trap SIGINT
248 sudo kill $sso_pid
249
250 sleep 1