From 1fdcd375ab26176a41d67685290b7d85111ce879 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 12 Jan 2018 17:47:01 +0000 Subject: [PATCH 1/3] Fix clearing two cookies with the same name Closes gh-3196 --- notebook/base/handlers.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 3982957ea..6046b6e55 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -3,6 +3,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +import datetime import functools import json import mimetypes @@ -15,15 +16,17 @@ import warnings try: # py3 from http.client import responses + from http.cookies import Morsel except ImportError: from httplib import responses + from Cookie import Morsel try: from urllib.parse import urlparse # Py 3 except ImportError: from urlparse import urlparse # Py 2 from jinja2 import TemplateNotFound -from tornado import web, gen, escape +from tornado import web, gen, escape, httputil from tornado.log import app_log from notebook._sysinfo import get_sys_info @@ -91,14 +94,39 @@ class AuthenticatedHandler(web.RequestHandler): # for example, so just ignore) self.log.debug(e) + def force_clear_cookie(self, name, path="/", domain=None): + """Deletes the cookie with the given name. + + Tornado's cookie handling currently (Jan 2018) stores cookies in a dict + keyed by name, so it can only modify one cookie with a given name per + response. The browser can store multiple cookies with the same name + but different domains and/or paths. This method lets us clear multiple + cookies with the same name. + + Due to limitations of the cookie protocol, you must pass the same + path and domain to clear a cookie as were used when that cookie + was set (but there is no way to find out on the server side + which values were used for a given cookie). + """ + name = escape.native_str(name) + expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) + + morsel = Morsel() + morsel.set(name, '', '""') + morsel['expires'] = httputil.format_timestamp(expires) + morsel['path'] = path + if domain: + morsel['domain'] = domain + self.add_header("Set-Cookie", morsel.OutputString()) + def clear_login_cookie(self): cookie_options = self.settings.get('cookie_options', {}) path = cookie_options.setdefault('path', self.base_url) - self.clear_cookie(self.cookie_name, path=path) + self.force_clear_cookie(self.cookie_name, path=path) if path and path != '/': # also clear cookie on / to ensure old cookies # are cleared after the change in path behavior. - self.clear_cookie(self.cookie_name) + self.force_clear_cookie(self.cookie_name) def get_current_user(self): if self.login_handler is None: From 6ba7b171812f3303bf90e9ac75df1af62cdf5782 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 16 Jan 2018 11:47:10 +0000 Subject: [PATCH 2/3] Only use force_clear_cookie for the extra compatibility piece --- notebook/base/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 6046b6e55..e7b371813 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -122,7 +122,7 @@ class AuthenticatedHandler(web.RequestHandler): def clear_login_cookie(self): cookie_options = self.settings.get('cookie_options', {}) path = cookie_options.setdefault('path', self.base_url) - self.force_clear_cookie(self.cookie_name, path=path) + self.clear_cookie(self.cookie_name, path=path) if path and path != '/': # also clear cookie on / to ensure old cookies # are cleared after the change in path behavior. From 0f0fe8474095e698bb8c6b5e898bf41fbbce1b4a Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 16 Jan 2018 11:51:43 +0000 Subject: [PATCH 3/3] Expand description of compatibility code --- notebook/base/handlers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index e7b371813..ddd82426d 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -124,8 +124,10 @@ class AuthenticatedHandler(web.RequestHandler): path = cookie_options.setdefault('path', self.base_url) self.clear_cookie(self.cookie_name, path=path) if path and path != '/': - # also clear cookie on / to ensure old cookies - # are cleared after the change in path behavior. + # also clear cookie on / to ensure old cookies are cleared + # after the change in path behavior (changed in notebook 5.2.2). + # N.B. This bypasses the normal cookie handling, which can't update + # two cookies with the same name. See the method above. self.force_clear_cookie(self.cookie_name) def get_current_user(self):