|
|
|
|
@ -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)
|
|
|
|
|
]
|
|
|
|
|
|