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
=== modified file 'identityprovider/api10/handlers.py'
--- identityprovider/api10/handlers.py 2012-01-15 15:52:38 +0000
+++ identityprovider/api10/handlers.py 2012-06-15 14:49:20 +0000
@@ -51,6 +51,11 @@
51from oauth_backend.models import Token51from oauth_backend.models import Token
5252
5353
54def api_error(cls, msg):
55 """Ensure 4xx response content in plain text to avoid XSS"""
56 return cls(msg, content_type='text/plain')
57
58
54class LazrRestfulEmitter(Emitter):59class LazrRestfulEmitter(Emitter):
55 """JSON emitter, lazr.restful flavoured.60 """JSON emitter, lazr.restful flavoured.
5661
@@ -90,7 +95,7 @@
9095
91 def create(self, request):96 def create(self, request):
92 if not 'ws.op' in request.POST:97 if not 'ws.op' in request.POST:
93 return HttpResponseBadRequest('No operation name given.')98 return api_error(HttpResponseBadRequest, 'No operation name given.')
94 return self.named_operation(request, request.POST)99 return self.named_operation(request, request.POST)
95100
96 def named_operation(self, request, serialized):101 def named_operation(self, request, serialized):
@@ -98,7 +103,7 @@
98 method = getattr(self, method_name, None)103 method = getattr(self, method_name, None)
99 is_named_operation = getattr(method, 'is_named_operation', False)104 is_named_operation = getattr(method, 'is_named_operation', False)
100 if not is_named_operation:105 if not is_named_operation:
101 return HttpResponseBadRequest('No such operation: %s' %106 return api_error(HttpResponseBadRequest, 'No such operation: %s' %
102 method_name)107 method_name)
103 request.data = self.lazr_restful_deserialize(serialized)108 request.data = self.lazr_restful_deserialize(serialized)
104 return method(request)109 return method(request)
@@ -232,7 +237,7 @@
232237
233 account = Account.objects.get_by_email(email)238 account = Account.objects.get_by_email(email)
234 if account is None or not account.can_reset_password:239 if account is None or not account.can_reset_password:
235 return HttpResponseForbidden(240 return api_error(HttpResponseForbidden,
236 "CanNotResetPasswordError: Can't reset password for this "241 "CanNotResetPasswordError: Can't reset password for this "
237 "account.")242 "account.")
238243
@@ -255,7 +260,7 @@
255 email=data['email'], token=data['token'],260 email=data['email'], token=data['token'],
256 token_type=LoginTokenType.PASSWORDRECOVERY)261 token_type=LoginTokenType.PASSWORDRECOVERY)
257 except AuthToken.DoesNotExist:262 except AuthToken.DoesNotExist:
258 return HttpResponseForbidden(263 return api_error(HttpResponseForbidden,
259 "CanNotSetPasswordError: Invalid token.")264 "CanNotSetPasswordError: Invalid token.")
260 if not token.requester:265 if not token.requester:
261 token.delete()266 token.delete()
262267
=== modified file 'identityprovider/tests/unit/test_handlers.py'
--- identityprovider/tests/unit/test_handlers.py 2012-01-17 16:16:01 +0000
+++ identityprovider/tests/unit/test_handlers.py 2012-06-15 14:49:20 +0000
@@ -1,6 +1,8 @@
1# encoding: utf-81# encoding: utf-8
2import base642import base64
3import json3import json
4from unittest import TestCase
5
4from django.utils import simplejson6from django.utils import simplejson
5from mock import patch7from mock import patch
6from oauth.oauth import (8from oauth.oauth import (
@@ -11,7 +13,10 @@
11 )13 )
12from lazr.restfulclient.errors import HTTPError14from lazr.restfulclient.errors import HTTPError
1315
14from identityprovider.api10.handlers import AuthenticationHandler16from identityprovider.api10.handlers import (
17 AuthenticationHandler,
18 LazrRestfulHandler,
19)
15from identityprovider.tests.utils import AnonAPITestCase, TestCaseWithFactory20from identityprovider.tests.utils import AnonAPITestCase, TestCaseWithFactory
16from identityprovider.models import (21from identityprovider.models import (
17 Account,22 Account,
@@ -35,6 +40,21 @@
35 self.environ = {'REMOTE_ADDR': '127.0.0.1'}40 self.environ = {'REMOTE_ADDR': '127.0.0.1'}
3641
3742
43class TestLazrRestfulHandler(TestCase):
44 handler = LazrRestfulHandler()
45
46 def test_named_operation_error_is_plaintext(self):
47 script = '<script>alert("boo");</script>'
48 response = self.handler.named_operation(None, {'ws.op': script})
49 self.assertEqual(response['content-type'], 'text/plain')
50
51 def test_create_error_is_plaintext(self):
52 request = MockRequest()
53 request.POST = {}
54 response = self.handler.create(request)
55 self.assertEqual(response['content-type'], 'text/plain')
56
57
38class RegistrationHandlerTestCase(AnonAPITestCase):58class RegistrationHandlerTestCase(AnonAPITestCase):
3959
40 def test_authtoken_without_requester_is_handled_properly(self):60 def test_authtoken_without_requester_is_handled_properly(self):
@@ -53,6 +73,7 @@
53 email='non-existing@example.com')73 email='non-existing@example.com')
54 except HTTPError, e:74 except HTTPError, e:
55 self.assertEqual(e.response.status, 403)75 self.assertEqual(e.response.status, 403)
76 self.assertEquals(e.response['content-type'], "text/plain")
56 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))77 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))
5778
58 def test_reset_token_when_account_is_disabled(self):79 def test_reset_token_when_account_is_disabled(self):
@@ -66,6 +87,7 @@
66 email=email_address)87 email=email_address)
67 except HTTPError, e:88 except HTTPError, e:
68 self.assertEqual(e.response.status, 403)89 self.assertEqual(e.response.status, 403)
90 self.assertEquals(e.response['content-type'], "text/plain")
69 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))91 self.assertTrue(e.content.startswith("CanNotResetPasswordError:"))
7092
71 def test_reset_password_when_not_existing_token_is_passed(self):93 def test_reset_password_when_not_existing_token_is_passed(self):
@@ -76,6 +98,7 @@
76 new_password='password')98 new_password='password')
77 except HTTPError, e:99 except HTTPError, e:
78 self.assertEqual(e.response.status, 403)100 self.assertEqual(e.response.status, 403)
101 self.assertEqual(e.response['content-type'], "text/plain")
79 self.assertTrue(e.content.startswith(102 self.assertTrue(e.content.startswith(
80 "CanNotSetPasswordError: Invalid token."))103 "CanNotSetPasswordError: Invalid token."))
81104