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

Proposed by David Owen
Status: Merged
Merged at revision: 51
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/bug_574911_large_assertions
Merge into: lp:canonical-identity-provider/release
Diff against target: 182 lines (+68/-33)
3 files modified
identityprovider/templates/post-assertion.html (+23/-0)
identityprovider/tests/test_views_server.py (+36/-31)
identityprovider/views/server.py (+9/-2)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/bug_574911_large_assertions
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+27088@code.launchpad.net

Description of the change

Fixed #574911: Large OpenID assertions generate malformed POST responses.

Also, unhid some existing OpenID test cases.

I could find no explicit requirements on the Content-Type of any assertion. The only implied requirement is text/html for POST responses, so that the returned form may be rendered as HTML for user submission.

Copy-editing of the form wrapper is appreciated.

To post a comment you must log in.
Revision history for this message
Łukasz Czyżykowski (lukasz-czyzykowski) wrote :

I'm not thrilled about DecideTestCase2 name. Maybe it would be better to merge those tests with DecideTestCase?

Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Definitevely. I agree with Lukasz on this. The test on DecideTestCase2 should be moved to DecideTestCase (and possibly renamed too).

Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

Good work. Thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'identityprovider/templates/post-assertion.html'
2--- identityprovider/templates/post-assertion.html 1970-01-01 00:00:00 +0000
3+++ identityprovider/templates/post-assertion.html 2010-06-09 15:39:29 +0000
4@@ -0,0 +1,23 @@
5+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
6+GNU Affero General Public License version 3 (see the file LICENSE). -->
7+
8+{% extends "base.html" %}
9+{% load i18n %}
10+
11+{% block "title" %}
12+ {% trans "Continue to 3rd-party site" %}
13+{% endblock %}
14+
15+{% block "content" %}
16+<div class="info">
17+ <h1 class="main">{% trans "Continue to 3rd-party site" %}</h1>
18+
19+ <p>
20+ {% blocktrans %}You are ready to continue to the 3rd-party site.{% endblocktrans %}
21+ </p>
22+</div>
23+
24+<div class="actions">
25+ {{ form|safe }}
26+</div>
27+{% endblock %}
28
29=== modified file 'identityprovider/tests/test_views_server.py'
30--- identityprovider/tests/test_views_server.py 2010-06-04 14:38:15 +0000
31+++ identityprovider/tests/test_views_server.py 2010-06-09 15:39:29 +0000
32@@ -9,7 +9,8 @@
33 from openid.extensions import pape
34 from openid.extensions.sreg import SRegRequest
35
36-from openid.message import Message, OPENID2_NS, IDENTIFIER_SELECT
37+from openid.message import (Message, IDENTIFIER_SELECT, OPENID1_URL_LIMIT,
38+ OPENID2_NS)
39 from openid.server.server import Server, ProtocolError
40 from openid.yadis.constants import YADIS_HEADER_NAME
41 from urllib import quote, quote_plus
42@@ -27,7 +28,6 @@
43 BasicAccountTestCase, AuthenticatedTestCase, OpenIDProviderTestCase)
44 from identityprovider.const import PERSON_VISIBILITY_PRIVATE_MEMBERSHIP
45
46-__all__ = ['UntrustedRPTest', ]
47
48
49 class DummyORequest(object):
50@@ -54,20 +54,6 @@
51 self.session = DummySession()
52
53
54-class UntrustedRPTest(SQLCachedTestCase):
55-
56- def __init__(self, META=None, REQUEST=None):
57- class MockSession(dict):
58- def __init__(self):
59- self.session_key = 'session-key'
60- def flush(self):
61- pass
62-
63- self.META = META if META is not None else {}
64- self.REQUEST = REQUEST if REQUEST is not None else {}
65- self.session = MockSession()
66-
67-
68 class HandleOpenIDErrorTestCase(OpenIDProviderTestCase):
69
70 # tests for the _handle_openid_error method
71@@ -245,20 +231,24 @@
72
73
74 class DecideTestCase(AuthenticatedTestCase, OpenIDProviderTestCase):
75+
76+ def _prepare_openid_token(self, param_overrides=None):
77+ request = {'openid.mode': 'checkid_setup',
78+ 'openid.trust_root': 'http://localhost/',
79+ 'openid.return_to': 'http://localhost/',
80+ 'openid.identity': IDENTIFIER_SELECT}
81+ if param_overrides:
82+ request.update(param_overrides)
83+ openid_server = server._get_openid_server()
84+ self.orequest = openid_server.decodeRequest(request)
85+ self.token = create_token(16)
86+ session = self.client.session
87+ session[self.token] = signed.dumps(self.orequest, settings.SECRET_KEY)
88+ session.save()
89+
90 def setUp(self):
91 super(DecideTestCase, self).setUp(disableCSRF=True)
92-
93- request = {'openid.mode': 'checkid_setup',
94- 'openid.trust_root': 'http://localhost/',
95- 'openid.return_to': 'http://localhost/',
96- 'openid.identity': IDENTIFIER_SELECT}
97- openid_server = server._get_openid_server()
98- self.orequest = openid_server.decodeRequest(request)
99- self.token = create_token(16)
100- session = self.client.session
101- session[self.token] = signed.dumps(self.orequest,
102- settings.SECRET_KEY)
103- session.save()
104+ self._prepare_openid_token()
105
106 def test_decide_invalid(self):
107 token = 'a' * 16
108@@ -272,6 +262,21 @@
109 self.assertEqual(r.status_code, 302)
110 self.assertEqual(query['openid.mode'], 'id_res')
111
112+ def test_decide_authenticated_with_post(self):
113+ # Using a return-to URL that is more than OPENID1_URL_LIMIT
114+ # characters long will force the assertion to be sent as a
115+ # POSTing form (instead of a 302 redirect).
116+ return_to = 'http://localhost/' + (OPENID1_URL_LIMIT * 'a')
117+ self._prepare_openid_token({
118+ 'openid.return_to': return_to,
119+ 'openid.ns': OPENID2_NS,
120+ 'openid.claimed_id': 'http://localhost/~userid'})
121+ r = self.client.post("/%s/+decide" % self.token, {'yes': ''})
122+ self.assertEqual(r.status_code, 200)
123+ self.assertEqual(r['Content-Type'], 'text/html')
124+ self.assertContains(r, '<form ')
125+ self.assertContains(r, return_to)
126+
127 def test_decide_auto_authorize(self):
128 # make sure rpconfig is set to auto authorize
129 rpconfig = OpenIDRPConfig(trust_root='http://localhost/',
130@@ -700,7 +705,7 @@
131 def tearDown(self):
132 settings.SSO_RESTRICT_RP = self.old_restrict
133
134- def test_unknown_rp(self):
135+ def test_process_unknown_rp(self):
136 orequest = DummyORequest()
137 request = DummyRequest()
138 response = server._process_openid_request(request, orequest, None)
139@@ -802,12 +807,12 @@
140 team.save()
141
142
143-class DecideTestCase(BasicAccountTestCase):
144+class MarkupTestCase(BasicAccountTestCase):
145
146 fixtures = ["test"]
147 pgsql_functions = ["generate_openid_identifier"]
148
149- def test_unknown_rp(self):
150+ def test_untrusted_rp_properly_shows_markup(self):
151 self.client.login(username='mark@example.com', password='test')
152
153 session = self.client.session
154
155=== modified file 'identityprovider/views/server.py'
156--- identityprovider/views/server.py 2010-05-18 15:28:53 +0000
157+++ identityprovider/views/server.py 2010-06-09 15:39:29 +0000
158@@ -23,7 +23,7 @@
159 from django.contrib.auth.decorators import login_required
160 from django.http import (Http404, HttpResponse, HttpResponseBadRequest,
161 HttpResponseRedirect)
162-from django.template import RequestContext
163+from django.template import RequestContext, loader
164 from django.shortcuts import get_object_or_404, render_to_response
165 from django.utils.decorators import decorator_from_middleware
166
167@@ -478,7 +478,14 @@
168 request.session.session_key)
169 _add_sreg(request.user, orequest, oresponse)
170 _check_team_membership(request.user, orequest, oresponse)
171- return _django_response(request, oresponse, decision)
172+ r = _django_response(request, oresponse, decision)
173+ if r.content:
174+ # Only user-visible content is generated from this view. Wrap
175+ # it up and set the Content-Type.
176+ r.content = loader.render_to_string("post-assertion.html", {
177+ 'form': r.content})
178+ r['Content-Type'] = 'text/html'
179+ return r
180
181
182 def _get_openid_server():