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

Proposed by Simon Davy
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: no longer in the source branch.
Merged at revision: 413
Proposed branch: lp:~canonical-isd-hackers/canonical-identity-provider/1013494
Merge into: lp:canonical-identity-provider/release
Diff against target: 121 lines (+33/-5)
2 files modified
identityprovider/api10/handlers.py (+9/-4)
identityprovider/tests/unit/test_handlers.py (+24/-1)
To merge this branch: bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/1013494
Reviewer Review Type Date Requested Status
Ricardo Kirkner (community) Approve
Review via email: mp+110497@code.launchpad.net

Commit message

Fix for #1013494, XSS vuln.

Description of the change

Fix for #1013494, XSS vuln.

To post a comment you must log in.
Revision history for this message
Ricardo Kirkner (ricardokirkner) wrote :

LGTM. Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'identityprovider/api10/handlers.py'
2--- identityprovider/api10/handlers.py 2012-01-15 15:52:38 +0000
3+++ identityprovider/api10/handlers.py 2012-06-15 14:49:20 +0000
4@@ -51,6 +51,11 @@
5 from oauth_backend.models import Token
6
7
8+def api_error(cls, msg):
9+ """Ensure 4xx response content in plain text to avoid XSS"""
10+ return cls(msg, content_type='text/plain')
11+
12+
13 class LazrRestfulEmitter(Emitter):
14 """JSON emitter, lazr.restful flavoured.
15
16@@ -90,7 +95,7 @@
17
18 def create(self, request):
19 if not 'ws.op' in request.POST:
20- return HttpResponseBadRequest('No operation name given.')
21+ return api_error(HttpResponseBadRequest, 'No operation name given.')
22 return self.named_operation(request, request.POST)
23
24 def named_operation(self, request, serialized):
25@@ -98,7 +103,7 @@
26 method = getattr(self, method_name, None)
27 is_named_operation = getattr(method, 'is_named_operation', False)
28 if not is_named_operation:
29- return HttpResponseBadRequest('No such operation: %s' %
30+ return api_error(HttpResponseBadRequest, 'No such operation: %s' %
31 method_name)
32 request.data = self.lazr_restful_deserialize(serialized)
33 return method(request)
34@@ -232,7 +237,7 @@
35
36 account = Account.objects.get_by_email(email)
37 if account is None or not account.can_reset_password:
38- return HttpResponseForbidden(
39+ return api_error(HttpResponseForbidden,
40 "CanNotResetPasswordError: Can't reset password for this "
41 "account.")
42
43@@ -255,7 +260,7 @@
44 email=data['email'], token=data['token'],
45 token_type=LoginTokenType.PASSWORDRECOVERY)
46 except AuthToken.DoesNotExist:
47- return HttpResponseForbidden(
48+ return api_error(HttpResponseForbidden,
49 "CanNotSetPasswordError: Invalid token.")
50 if not token.requester:
51 token.delete()
52
53=== modified file 'identityprovider/tests/unit/test_handlers.py'
54--- identityprovider/tests/unit/test_handlers.py 2012-01-17 16:16:01 +0000
55+++ identityprovider/tests/unit/test_handlers.py 2012-06-15 14:49:20 +0000
56@@ -1,6 +1,8 @@
57 # encoding: utf-8
58 import base64
59 import json
60+from unittest import TestCase
61+
62 from django.utils import simplejson
63 from mock import patch
64 from oauth.oauth import (
65@@ -11,7 +13,10 @@
66 )
67 from lazr.restfulclient.errors import HTTPError
68
69-from identityprovider.api10.handlers import AuthenticationHandler
70+from identityprovider.api10.handlers import (
71+ AuthenticationHandler,
72+ LazrRestfulHandler,
73+)
74 from identityprovider.tests.utils import AnonAPITestCase, TestCaseWithFactory
75 from identityprovider.models import (
76 Account,
77@@ -35,6 +40,21 @@
78 self.environ = {'REMOTE_ADDR': '127.0.0.1'}
79
80
81+class TestLazrRestfulHandler(TestCase):
82+ handler = LazrRestfulHandler()
83+
84+ def test_named_operation_error_is_plaintext(self):
85+ script = '<script>alert("boo");</script>'
86+ response = self.handler.named_operation(None, {'ws.op': script})
87+ self.assertEqual(response['content-type'], 'text/plain')
88+
89+ def test_create_error_is_plaintext(self):
90+ request = MockRequest()
91+ request.POST = {}
92+ response = self.handler.create(request)
93+ self.assertEqual(response['content-type'], 'text/plain')
94+
95+
96 class RegistrationHandlerTestCase(AnonAPITestCase):
97
98 def test_authtoken_without_requester_is_handled_properly(self):
99@@ -53,6 +73,7 @@
100 email='non-existing@example.com')
101 except HTTPError, e:
102 self.assertEqual(e.response.status, 403)
103+ self.assertEquals(e.response['content-type'], "text/plain")
104 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))
105
106 def test_reset_token_when_account_is_disabled(self):
107@@ -66,6 +87,7 @@
108 email=email_address)
109 except HTTPError, e:
110 self.assertEqual(e.response.status, 403)
111+ self.assertEquals(e.response['content-type'], "text/plain")
112 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))
113
114 def test_reset_password_when_not_existing_token_is_passed(self):
115@@ -76,6 +98,7 @@
116 new_password='password')
117 except HTTPError, e:
118 self.assertEqual(e.response.status, 403)
119+ self.assertEqual(e.response['content-type'], "text/plain")
120 self.assertTrue(e.content.startswith(
121 "CanNotSetPasswordError: Invalid token."))
122