Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Change history for XBlock
Unreleased
----------

* Fixed ``webob_to_django_response`` dropping all but the last ``Set-Cookie``
header; they are now loaded into the Django response's cookie jar so every
cookie is sent to the client.
* Removed ``threading.RLock`` from ``Mixologist``'s class cache to make it safe
for ASGI/async deployments. ``dict.setdefault()`` atomicity (via CPython's GIL)
provides equivalent thread safety without blocking the event loop.
Expand Down
7 changes: 6 additions & 1 deletion xblock/django/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ def webob_to_django_response(webob_response, streaming=False):
status=webob_response.status_code,
)
for name, value in webob_response.headerlist:
django_response[name] = value
if name.lower() == 'set-cookie':
# Django headers can't hold multiple values with the same name,
# so Set-Cookie headers go through the response's cookie jar.
django_response.cookies.load(value)
else:
django_response[name] = value
return django_response


Expand Down
19 changes: 19 additions & 0 deletions xblock/test/django/test_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ def test_headers(self):
self.assertIn('X-Foo', self._as_django(headerlist=[('X-Foo', 'bar')]))
self.assertEqual(self._as_django(headerlist=[('X-Foo', 'bar')])['X-Foo'], 'bar')

def test_multiple_set_cookie_headers(self):
response = self._as_django(headerlist=[
('Set-Cookie', 'yummy_cookie=choco; Path=/; HttpOnly'),
('Set-Cookie', 'tasty_cookie=strawberry; SameSite=Lax'),
])
self.assertEqual(response.cookies['yummy_cookie'].value, 'choco')
self.assertEqual(response.cookies['yummy_cookie']['path'], '/')
self.assertTrue(response.cookies['yummy_cookie']['httponly'])
self.assertEqual(response.cookies['tasty_cookie'].value, 'strawberry')
self.assertEqual(response.cookies['tasty_cookie']['samesite'], 'Lax')

def test_content_types(self):
# JSON content type (no charset should be returned)
self.assertEqual(
Expand Down Expand Up @@ -105,6 +116,14 @@ def test_headers(self):
self.assertIn('X-Foo', self._as_django(headerlist=[('X-Foo', 'bar')]))
self.assertEqual(self._as_django(headerlist=[('X-Foo', 'bar')])['X-Foo'], 'bar')

def test_multiple_set_cookie_headers(self):
response = self._as_django(headerlist=[
('Set-Cookie', 'yummy_cookie=choco'),
('Set-Cookie', 'tasty_cookie=strawberry'),
])
self.assertEqual(response.cookies['yummy_cookie'].value, 'choco')
self.assertEqual(response.cookies['tasty_cookie'].value, 'strawberry')

def test_content_types(self):
# JSON content type (no charset should be returned)
self.assertEqual(
Expand Down