forward-port security fixes from 3.2

- APIHandler for mime-type and Content-Security-Policy
- next_url confined to base_url
Min RK 11 years ago
commit 0ceeb5ce75

@ -25,7 +25,11 @@ class LoginHandler(IPythonHandler):
def get(self):
if self.current_user:
self.redirect(self.get_argument('next', default=self.base_url))
next_url = self.get_argument('next', default=self.base_url)
if not next_url.startswith(self.base_url):
# require that next_url be absolute path within our path
next_url = self.base_url
self.redirect(next_url)
else:
self._render()
@ -47,8 +51,12 @@ class LoginHandler(IPythonHandler):
else:
self._render(message={'error': 'Invalid password'})
return
self.redirect(self.get_argument('next', default=self.base_url))
next_url = self.get_argument('next', default=self.base_url)
if not next_url.startswith(self.base_url):
# require that next_url be absolute path within our path
next_url = self.base_url
self.redirect(next_url)
@classmethod
def get_user(cls, handler):

@ -40,16 +40,24 @@ sys_info = json.dumps(get_sys_info())
class AuthenticatedHandler(web.RequestHandler):
"""A RequestHandler with an authenticated user."""
@property
def content_security_policy(self):
"""The default Content-Security-Policy header
Can be overridden by defining Content-Security-Policy in settings['headers']
"""
return '; '.join([
"frame-ancestors 'self'",
# Make sure the report-uri is relative to the base_url
"report-uri " + url_path_join(self.base_url, csp_report_uri),
])
def set_default_headers(self):
headers = self.settings.get('headers', {})
if "Content-Security-Policy" not in headers:
headers["Content-Security-Policy"] = (
"frame-ancestors 'self'; "
# Make sure the report-uri is relative to the base_url
"report-uri " + url_path_join(self.base_url, csp_report_uri) + ";"
)
headers["Content-Security-Policy"] = self.content_security_policy
# Allow for overriding headers
for header_name,value in headers.items() :
@ -301,7 +309,22 @@ class IPythonHandler(AuthenticatedHandler):
html = self.render_template('error.html', **ns)
self.write(html)
class APIHandler(IPythonHandler):
"""Base class for API handlers"""
@property
def content_security_policy(self):
csp = '; '.join([
super(APIHandler, self).content_security_policy,
"default-src 'none'",
])
return csp
def finish(self, *args, **kwargs):
self.set_header('Content-Type', 'application/json')
return super(APIHandler, self).finish(*args, **kwargs)
class Template404(IPythonHandler):
@ -364,6 +387,7 @@ def json_errors(method):
try:
result = yield gen.maybe_future(method(self, *args, **kwargs))
except web.HTTPError as e:
self.set_header('Content-Type', 'application/json')
status = e.status_code
message = e.log_message
self.log.warn(message)
@ -371,6 +395,7 @@ def json_errors(method):
reply = dict(message=message, reason=e.reason)
self.finish(json.dumps(reply))
except Exception:
self.set_header('Content-Type', 'application/json')
self.log.error("Unhandled error in API request", exc_info=True)
status = 500
message = "Unknown server error"
@ -393,7 +418,7 @@ def json_errors(method):
# to minimize subclass changes:
HTTPError = web.HTTPError
class FileFindHandler(web.StaticFileHandler):
class FileFindHandler(IPythonHandler, web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""
# cache search results, don't search for files more than once
@ -447,7 +472,7 @@ class FileFindHandler(web.StaticFileHandler):
return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
class ApiVersionHandler(IPythonHandler):
class APIVersionHandler(APIHandler):
@json_errors
def get(self):
@ -518,5 +543,5 @@ path_regex = r"(?P<path>(?:(?:/[^/]+)+|/?))"
default_handlers = [
(r".*/", TrailingSlashHandler),
(r"api", ApiVersionHandler)
(r"api", APIVersionHandler)
]

@ -9,9 +9,9 @@ import errno
from tornado import web
from ipython_genutils.py3compat import PY3
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import APIHandler, json_errors
class ConfigHandler(IPythonHandler):
class ConfigHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH')
@web.authenticated

@ -14,7 +14,7 @@ from notebook.utils import url_path_join, url_escape
from jupyter_client.jsonutil import date_default
from notebook.base.handlers import (
IPythonHandler, json_errors, path_regex,
IPythonHandler, APIHandler, json_errors, path_regex,
)
@ -78,7 +78,7 @@ def validate_model(model, expect_content):
)
class ContentsHandler(IPythonHandler):
class ContentsHandler(APIHandler):
SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
@ -260,7 +260,7 @@ class ContentsHandler(IPythonHandler):
self.finish()
class CheckpointsHandler(IPythonHandler):
class CheckpointsHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@ -289,7 +289,7 @@ class CheckpointsHandler(IPythonHandler):
self.finish(data)
class ModifyCheckpointsHandler(IPythonHandler):
class ModifyCheckpointsHandler(APIHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')

@ -16,12 +16,12 @@ from jupyter_client.jsonutil import date_default
from ipython_genutils.py3compat import cast_unicode
from notebook.utils import url_path_join, url_escape
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import IPythonHandler, APIHandler, json_errors
from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message
from jupyter_client import protocol_version as client_protocol_version
class MainKernelHandler(IPythonHandler):
class MainKernelHandler(APIHandler):
@web.authenticated
@json_errors
@ -49,7 +49,7 @@ class MainKernelHandler(IPythonHandler):
self.finish(json.dumps(model))
class KernelHandler(IPythonHandler):
class KernelHandler(APIHandler):
SUPPORTED_METHODS = ('DELETE', 'GET', 'OPTIONS')
@ -76,7 +76,7 @@ class KernelHandler(IPythonHandler):
self.finish()
class KernelActionHandler(IPythonHandler):
class KernelActionHandler(APIHandler):
@web.authenticated
@json_errors

@ -69,7 +69,8 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.headers['Content-Security-Policy'], (
"frame-ancestors 'self'; "
"report-uri /api/security/csp-report;"
"report-uri /api/security/csp-report; "
"default-src 'none'"
))
def test_main_kernel_handler(self):
@ -82,7 +83,8 @@ class KernelAPITest(NotebookTestBase):
self.assertEqual(r.headers['Content-Security-Policy'], (
"frame-ancestors 'self'; "
"report-uri /api/security/csp-report;"
"report-uri /api/security/csp-report; "
"default-src 'none'"
))
# GET request

@ -13,7 +13,7 @@ pjoin = os.path.join
from tornado import web
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import APIHandler, json_errors
from ...utils import url_path_join
def kernelspec_model(handler, name):
@ -43,7 +43,7 @@ def kernelspec_model(handler, name):
)
return d
class MainKernelSpecHandler(IPythonHandler):
class MainKernelSpecHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'OPTIONS')
@web.authenticated
@ -70,7 +70,7 @@ class MainKernelSpecHandler(IPythonHandler):
self.finish()
class KernelSpecHandler(IPythonHandler):
class KernelSpecHandler(APIHandler):
SUPPORTED_METHODS = ('GET',)
@web.authenticated

@ -2,9 +2,9 @@ import json
from tornado import web
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import APIHandler, json_errors
class NbconvertRootHandler(IPythonHandler):
class NbconvertRootHandler(APIHandler):
SUPPORTED_METHODS = ('GET',)
@web.authenticated

@ -5,10 +5,10 @@
from tornado import gen, web
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import APIHandler, json_errors
from . import csp_report_uri
class CSPReportHandler(IPythonHandler):
class CSPReportHandler(APIHandler):
'''Accepts a content security policy violation report'''
@web.authenticated
@json_errors

@ -10,13 +10,13 @@ import json
from tornado import web
from ...base.handlers import IPythonHandler, json_errors
from ...base.handlers import APIHandler, json_errors
from jupyter_client.jsonutil import date_default
from notebook.utils import url_path_join, url_escape
from jupyter_client.kernelspec import NoSuchKernel
class SessionRootHandler(IPythonHandler):
class SessionRootHandler(APIHandler):
@web.authenticated
@json_errors
@ -74,7 +74,7 @@ class SessionRootHandler(IPythonHandler):
self.set_header('Access-Control-Allow-Headers', 'accept, content-type')
self.finish()
class SessionHandler(IPythonHandler):
class SessionHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')

@ -1,9 +1,9 @@
import json
from tornado import web, gen
from ..base.handlers import IPythonHandler, json_errors
from ..base.handlers import APIHandler, json_errors
from ..utils import url_path_join
class TerminalRootHandler(IPythonHandler):
class TerminalRootHandler(APIHandler):
@web.authenticated
@json_errors
def get(self):
@ -19,7 +19,7 @@ class TerminalRootHandler(IPythonHandler):
self.finish(json.dumps({'name': name}))
class TerminalHandler(IPythonHandler):
class TerminalHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'DELETE')
@web.authenticated

Loading…
Cancel
Save