diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index 202bf36..d6c7b8b 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -42,16 +42,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() :
@@ -307,7 +315,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):
@@ -370,6 +393,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)
@@ -377,6 +401,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"
@@ -399,7 +424,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
@@ -453,7 +478,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):
@@ -524,5 +549,5 @@ path_regex = r"(?P(?:(?:/[^/]+)+|/?))"
default_handlers = [
(r".*/", TrailingSlashHandler),
- (r"api", ApiVersionHandler)
+ (r"api", APIVersionHandler)
]
diff --git a/IPython/html/services/clusters/handlers.py b/IPython/html/services/clusters/handlers.py
index a6d6312..a16d21f 100644
--- a/IPython/html/services/clusters/handlers.py
+++ b/IPython/html/services/clusters/handlers.py
@@ -7,28 +7,28 @@ import json
from tornado import web
-from ...base.handlers import IPythonHandler
+from ...base.handlers import APIHandler
#-----------------------------------------------------------------------------
# Cluster handlers
#-----------------------------------------------------------------------------
-class MainClusterHandler(IPythonHandler):
+class MainClusterHandler(APIHandler):
@web.authenticated
def get(self):
self.finish(json.dumps(self.cluster_manager.list_profiles()))
-class ClusterProfileHandler(IPythonHandler):
+class ClusterProfileHandler(APIHandler):
@web.authenticated
def get(self, profile):
self.finish(json.dumps(self.cluster_manager.profile_info(profile)))
-class ClusterActionHandler(IPythonHandler):
+class ClusterActionHandler(APIHandler):
@web.authenticated
def post(self, profile, action):
diff --git a/IPython/html/services/config/handlers.py b/IPython/html/services/config/handlers.py
index a7ff896..ae7f7e4 100644
--- a/IPython/html/services/config/handlers.py
+++ b/IPython/html/services/config/handlers.py
@@ -9,9 +9,9 @@ import errno
from tornado import web
from IPython.utils.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
diff --git a/IPython/html/services/contents/handlers.py b/IPython/html/services/contents/handlers.py
index 1e8bd33..5cd849e 100644
--- a/IPython/html/services/contents/handlers.py
+++ b/IPython/html/services/contents/handlers.py
@@ -11,7 +11,7 @@ from IPython.html.utils import url_path_join, url_escape
from IPython.utils.jsonutil import date_default
from IPython.html.base.handlers import (
- IPythonHandler, json_errors, path_regex,
+ IPythonHandler, APIHandler, json_errors, path_regex,
)
@@ -75,7 +75,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')
@@ -257,7 +257,7 @@ class ContentsHandler(IPythonHandler):
self.finish()
-class CheckpointsHandler(IPythonHandler):
+class CheckpointsHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'POST')
@@ -286,7 +286,7 @@ class CheckpointsHandler(IPythonHandler):
self.finish(data)
-class ModifyCheckpointsHandler(IPythonHandler):
+class ModifyCheckpointsHandler(APIHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
diff --git a/IPython/html/services/kernels/handlers.py b/IPython/html/services/kernels/handlers.py
index 14390ac..9be6845 100644
--- a/IPython/html/services/kernels/handlers.py
+++ b/IPython/html/services/kernels/handlers.py
@@ -13,12 +13,12 @@ from IPython.utils.jsonutil import date_default
from IPython.utils.py3compat import cast_unicode
from IPython.html.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 IPython.core.release import kernel_protocol_version
-class MainKernelHandler(IPythonHandler):
+class MainKernelHandler(APIHandler):
@web.authenticated
@json_errors
@@ -46,7 +46,7 @@ class MainKernelHandler(IPythonHandler):
self.finish(json.dumps(model))
-class KernelHandler(IPythonHandler):
+class KernelHandler(APIHandler):
SUPPORTED_METHODS = ('DELETE', 'GET')
@@ -67,7 +67,7 @@ class KernelHandler(IPythonHandler):
self.finish()
-class KernelActionHandler(IPythonHandler):
+class KernelActionHandler(APIHandler):
@web.authenticated
@json_errors
diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py
index b33142c..b779786 100644
--- a/IPython/html/services/kernels/tests/test_kernels_api.py
+++ b/IPython/html/services/kernels/tests/test_kernels_api.py
@@ -67,7 +67,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):
@@ -80,7 +81,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
diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py
index 7239778..b826927 100644
--- a/IPython/html/services/kernelspecs/handlers.py
+++ b/IPython/html/services/kernelspecs/handlers.py
@@ -10,7 +10,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):
@@ -40,7 +40,7 @@ def kernelspec_model(handler, name):
)
return d
-class MainKernelSpecHandler(IPythonHandler):
+class MainKernelSpecHandler(APIHandler):
SUPPORTED_METHODS = ('GET',)
@web.authenticated
@@ -62,7 +62,7 @@ class MainKernelSpecHandler(IPythonHandler):
self.finish(json.dumps(model))
-class KernelSpecHandler(IPythonHandler):
+class KernelSpecHandler(APIHandler):
SUPPORTED_METHODS = ('GET',)
@web.authenticated
diff --git a/IPython/html/services/nbconvert/handlers.py b/IPython/html/services/nbconvert/handlers.py
index 1c74de5..d6e9e0d 100644
--- a/IPython/html/services/nbconvert/handlers.py
+++ b/IPython/html/services/nbconvert/handlers.py
@@ -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
diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py
index 18f7874..03a0dcf 100644
--- a/IPython/html/services/security/handlers.py
+++ b/IPython/html/services/security/handlers.py
@@ -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
diff --git a/IPython/html/services/sessions/handlers.py b/IPython/html/services/sessions/handlers.py
index 9d0a5e4..36c5160 100644
--- a/IPython/html/services/sessions/handlers.py
+++ b/IPython/html/services/sessions/handlers.py
@@ -7,13 +7,13 @@ import json
from tornado import web
-from ...base.handlers import IPythonHandler, json_errors
+from ...base.handlers import APIHandler, json_errors
from IPython.utils.jsonutil import date_default
from IPython.html.utils import url_path_join, url_escape
from IPython.kernel.kernelspec import NoSuchKernel
-class SessionRootHandler(IPythonHandler):
+class SessionRootHandler(APIHandler):
@web.authenticated
@json_errors
@@ -65,7 +65,7 @@ class SessionRootHandler(IPythonHandler):
self.set_status(201)
self.finish(json.dumps(model, default=date_default))
-class SessionHandler(IPythonHandler):
+class SessionHandler(APIHandler):
SUPPORTED_METHODS = ('GET', 'PATCH', 'DELETE')
diff --git a/IPython/html/terminal/api_handlers.py b/IPython/html/terminal/api_handlers.py
index 6895e71..a7be0ae 100644
--- a/IPython/html/terminal/api_handlers.py
+++ b/IPython/html/terminal/api_handlers.py
@@ -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