misc: let django generate set-cookies headers (#72613)
Django's HttpResponse cannot hold more than one value for an HTTP headers, so if multiple Set-Cookie are produced by a Quixote HttpResponse the first set-cookie headers will be overwritten by the last one.
This commit is contained in:
parent
49c1d439b5
commit
2fcdde657a
|
@ -5733,13 +5733,13 @@ def test_session_cookie_flags(pub):
|
|||
create_formdef()
|
||||
app = get_app(pub)
|
||||
resp = app.get('/test/', status=200)
|
||||
assert resp.headers['Set-Cookie'].startswith('sessionid-')
|
||||
assert resp.headers['Set-Cookie'].strip().startswith('sessionid-')
|
||||
assert 'HttpOnly' in resp.headers['Set-Cookie']
|
||||
assert 'Secure' not in resp.headers['Set-Cookie']
|
||||
|
||||
app = get_app(pub, https=True)
|
||||
resp = app.get('/test/', status=200)
|
||||
assert resp.headers['Set-Cookie'].startswith('sessionid-')
|
||||
assert resp.headers['Set-Cookie'].strip().startswith('sessionid-')
|
||||
assert 'HttpOnly' in resp.headers['Set-Cookie']
|
||||
assert 'Secure' in resp.headers['Set-Cookie']
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ def test_login_cookie(pub):
|
|||
assert list(cookie_store.keys()) == [cookie_name]
|
||||
assert 'HttpOnly' in resp.headers['Set-Cookie']
|
||||
assert 'SameSite=None' in resp.headers['Set-Cookie']
|
||||
assert 'path=/' in resp.headers['Set-Cookie']
|
||||
assert 'Path=/' in resp.headers['Set-Cookie']
|
||||
|
||||
|
||||
def test_login_logout(pub):
|
||||
|
|
|
@ -607,7 +607,7 @@ def test_opened_session_cookie(pub):
|
|||
assert 'Secure' in resp.headers['Set-Cookie']
|
||||
assert 'HttpOnly' in resp.headers['Set-Cookie']
|
||||
assert 'SameSite=None' in resp.headers['Set-Cookie']
|
||||
assert 'path=/' in resp.headers['Set-Cookie']
|
||||
assert 'Path=/' in resp.headers['Set-Cookie']
|
||||
assert resp.status_int == 302
|
||||
assert (
|
||||
resp.location
|
||||
|
|
|
@ -33,6 +33,31 @@ from .qommon.http_request import HTTPRequest
|
|||
from .qommon.publisher import set_publisher_class
|
||||
|
||||
|
||||
def transfer_cookies(quixote_response, django_response):
|
||||
for name, attrs in quixote_response.cookies.items():
|
||||
value = str(attrs['value'])
|
||||
if 'samesite' not in attrs:
|
||||
attrs['samesite'] = 'None'
|
||||
kwargs = {}
|
||||
samesite_none = False
|
||||
for attr, val in attrs.items():
|
||||
attr = attr.lower()
|
||||
if val is None:
|
||||
continue
|
||||
if attr == 'comment':
|
||||
continue
|
||||
if attr == 'samesite' and val.lower() == 'none':
|
||||
samesite_none = True
|
||||
elif attr in ('expires', 'domain', 'path', 'max_age', 'samesite'):
|
||||
kwargs[attr] = val
|
||||
elif attr in ('httponly', 'secure') and val:
|
||||
kwargs[attr] = True
|
||||
django_response.set_cookie(name, value, **kwargs)
|
||||
# work around absent support for None in django 2.2
|
||||
if samesite_none:
|
||||
django_response.cookies[name]['samesite'] = 'None'
|
||||
|
||||
|
||||
class TemplateWithFallbackView(TemplateView):
|
||||
quixote_response = None
|
||||
|
||||
|
@ -70,6 +95,8 @@ class TemplateWithFallbackView(TemplateView):
|
|||
else:
|
||||
response = self.render_to_response(context)
|
||||
|
||||
transfer_cookies(self.quixote_response, response)
|
||||
|
||||
for name, value in self.quixote_response.generate_headers():
|
||||
if name in ('Connection', 'Content-Length'):
|
||||
continue
|
||||
|
@ -83,6 +110,7 @@ class TemplateWithFallbackView(TemplateView):
|
|||
if self.quixote_response and self.quixote_response.status_code != 200:
|
||||
django_response.status_code = self.quixote_response.status_code
|
||||
django_response.reason_phrase = self.quixote_response.reason_phrase
|
||||
transfer_cookies(self.quixote_response, django_response)
|
||||
for name, value in self.quixote_response.generate_headers():
|
||||
if name in ('Connection', 'Content-Length'):
|
||||
continue
|
||||
|
@ -211,6 +239,8 @@ class CompatWcsPublisher(WcsPublisher):
|
|||
self.session_manager.finish_successful_request()
|
||||
request.ignore_session = True # no further changes
|
||||
|
||||
transfer_cookies(response, django_response)
|
||||
|
||||
for name, value in response.generate_headers():
|
||||
if name in ('Connection', 'Content-Length'):
|
||||
continue
|
||||
|
@ -264,6 +294,8 @@ class PublishErrorMiddleware(MiddlewareMixin):
|
|||
reason=request.response.reason_phrase,
|
||||
)
|
||||
|
||||
transfer_cookies(request.response, django_response)
|
||||
|
||||
for name, value in request.response.generate_headers():
|
||||
if name in ('Connection', 'Content-Length'):
|
||||
continue
|
||||
|
|
|
@ -26,7 +26,7 @@ from django.utils.deprecation import MiddlewareMixin
|
|||
from quixote import get_publisher
|
||||
from quixote.errors import RequestError
|
||||
|
||||
from .compat import CompatHTTPRequest, CompatWcsPublisher
|
||||
from .compat import CompatHTTPRequest, CompatWcsPublisher, transfer_cookies
|
||||
from .qommon.http_response import HTTPResponse
|
||||
from .qommon.publisher import ImmediateRedirectException
|
||||
|
||||
|
@ -87,6 +87,7 @@ class PublisherInitialisationMiddleware(MiddlewareMixin):
|
|||
if compat_request.form:
|
||||
new_query_string = '?' + urllib.parse.urlencode(compat_request.form)
|
||||
response = HttpResponseRedirect(compat_request.get_path() + new_query_string)
|
||||
transfer_cookies(compat_request.response, response)
|
||||
for name, value in compat_request.response.generate_headers():
|
||||
if name == 'Content-Length':
|
||||
continue
|
||||
|
|
|
@ -35,29 +35,7 @@ class HTTPResponse(quixote.http_response.HTTPResponse):
|
|||
self.charset = get_publisher().site_charset
|
||||
|
||||
def _gen_cookie_headers(self):
|
||||
cookie_headers = []
|
||||
for name, attrs in self.cookies.items():
|
||||
value = str(attrs['value'])
|
||||
if '"' in value:
|
||||
value = value.replace('"', '\\"')
|
||||
chunks = ['%s="%s"' % (name, value)]
|
||||
if 'samesite' not in attrs:
|
||||
attrs['samesite'] = 'None'
|
||||
for name, val in attrs.items():
|
||||
name = name.lower()
|
||||
if val is None:
|
||||
continue
|
||||
if name in ('expires', 'domain', 'path', 'max_age', 'comment'):
|
||||
name = name.replace('_', '-')
|
||||
chunks.append('%s=%s' % (name, val))
|
||||
elif name == 'samesite':
|
||||
chunks.append('SameSite=%s' % val)
|
||||
elif name == 'secure' and val:
|
||||
chunks.append('Secure')
|
||||
elif name == 'httponly' and val:
|
||||
chunks.append('HttpOnly')
|
||||
cookie_headers.append(('Set-Cookie', '; '.join(chunks)))
|
||||
return cookie_headers
|
||||
return []
|
||||
|
||||
def reset_includes(self):
|
||||
self.javascript_scripts = None
|
||||
|
|
Loading…
Reference in New Issue