Skip to content

Commit

Permalink
Merge pull request #2853 from minrk/api-403
Browse files Browse the repository at this point in the history
Update error handling on APIHandlers
  • Loading branch information
rgbkrk authored Sep 25, 2017
2 parents 92354d5 + 4467dc9 commit b2edf89
Show file tree
Hide file tree
Showing 10 changed files with 45 additions and 66 deletions.
61 changes: 36 additions & 25 deletions notebook/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import re
import sys
import traceback
import types
import warnings
try:
# py3
from http.client import responses
Expand Down Expand Up @@ -450,6 +452,34 @@ def prepare(self):
raise web.HTTPError(404)
return super(APIHandler, self).prepare()

def write_error(self, status_code, **kwargs):
"""APIHandler errors are JSON, not human pages"""
self.set_header('Content-Type', 'application/json')
message = responses.get(status_code, 'Unknown HTTP Error')
reply = {
'message': message,
}
exc_info = kwargs.get('exc_info')
if exc_info:
e = exc_info[1]
if isinstance(e, HTTPError):
reply['message'] = e.log_message or message
else:
reply['message'] = 'Unhandled error'
reply['traceback'] = ''.join(traceback.format_exception(*exc_info))
self.log.warning(reply['message'])
self.finish(json.dumps(reply))

def get_current_user(self):
"""Raise 403 on API handlers instead of redirecting to human login page"""
# preserve _user_cache so we don't raise more than once
if hasattr(self, '_user_cache'):
return self._user_cache
self._user_cache = user = super(APIHandler, self).get_current_user()
if user is None:
raise web.HTTPError(403)
return user

@property
def content_security_policy(self):
csp = '; '.join([
Expand Down Expand Up @@ -547,32 +577,14 @@ def json_errors(method):
2. Create and return a JSON body with a message field describing
the error in a human readable form.
"""
warnings.warn('@json_errors is deprecated in notebook 5.2.0. Subclass APIHandler instead.',
DeprecationWarning,
stacklevel=2,
)
@functools.wraps(method)
@gen.coroutine
def wrapper(self, *args, **kwargs):
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.warning(message)
self.set_status(e.status_code)
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"
t, value, tb = sys.exc_info()
self.set_status(status)
tb_text = ''.join(traceback.format_exception(t, value, tb))
reply = dict(message=message, reason=None, traceback=tb_text)
self.finish(json.dumps(reply))
else:
# FIXME: can use regular return in generators in py3
raise gen.Return(result)
self.write_error = types.MethodType(APIHandler.write_error, self)
return method(self, *args, **kwargs)
return wrapper


Expand Down Expand Up @@ -643,7 +655,6 @@ def validate_absolute_path(self, root, absolute_path):

class APIVersionHandler(APIHandler):

@json_errors
def get(self):
# not authenticated, so give as few info as possible
self.finish(json.dumps({"version":notebook.__version__}))
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/api/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from tornado import gen, web

from ...base.handlers import IPythonHandler, APIHandler, json_errors
from ...base.handlers import IPythonHandler, APIHandler
from notebook._tz import utcfromtimestamp, isoformat

import os
Expand All @@ -28,7 +28,6 @@ class APIStatusHandler(APIHandler):

_track_activity = False

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
Expand Down
5 changes: 1 addition & 4 deletions notebook/services/config/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,21 @@
from tornado import web

from ipython_genutils.py3compat import PY3
from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler

class ConfigHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, section_name):
self.set_header("Content-Type", 'application/json')
self.finish(json.dumps(self.config_manager.get(section_name)))

@json_errors
@web.authenticated
def put(self, section_name):
data = self.get_json_body() # Will raise 400 if content is not valid JSON
self.config_manager.set(section_name, data)
self.set_status(204)

@json_errors
@web.authenticated
def patch(self, section_name):
new_data = self.get_json_body()
Expand Down
12 changes: 1 addition & 11 deletions notebook/services/contents/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from jupyter_client.jsonutil import date_default

from notebook.base.handlers import (
IPythonHandler, APIHandler, json_errors, path_regex,
IPythonHandler, APIHandler, path_regex,
)


Expand Down Expand Up @@ -87,7 +87,6 @@ def _finish_model(self, model, location=True):
self.set_header('Content-Type', 'application/json')
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def get(self, path=''):
Expand Down Expand Up @@ -115,7 +114,6 @@ def get(self, path=''):
validate_model(model, expect_content=content)
self._finish_model(model, location=False)

@json_errors
@web.authenticated
@gen.coroutine
def patch(self, path=''):
Expand Down Expand Up @@ -168,7 +166,6 @@ def _save(self, model, path):
validate_model(model, expect_content=False)
self._finish_model(model)

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path=''):
Expand Down Expand Up @@ -204,7 +201,6 @@ def post(self, path=''):
else:
yield self._new_untitled(path)

@json_errors
@web.authenticated
@gen.coroutine
def put(self, path=''):
Expand All @@ -230,7 +226,6 @@ def put(self, path=''):
else:
yield gen.maybe_future(self._new_untitled(path))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, path=''):
Expand All @@ -244,7 +239,6 @@ def delete(self, path=''):

class CheckpointsHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self, path=''):
Expand All @@ -254,7 +248,6 @@ def get(self, path=''):
data = json.dumps(checkpoints, default=date_default)
self.finish(data)

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path=''):
Expand All @@ -271,7 +264,6 @@ def post(self, path=''):

class ModifyCheckpointsHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def post(self, path, checkpoint_id):
Expand All @@ -281,7 +273,6 @@ def post(self, path, checkpoint_id):
self.set_status(204)
self.finish()

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, path, checkpoint_id):
Expand Down Expand Up @@ -310,7 +301,6 @@ def get(self, path):
class TrustNotebooksHandler(IPythonHandler):
""" Handles trust/signing of notebooks """

@json_errors
@web.authenticated
@gen.coroutine
def post(self,path=''):
Expand Down
7 changes: 1 addition & 6 deletions notebook/services/kernels/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,20 @@
from ipython_genutils.py3compat import cast_unicode
from notebook.utils import url_path_join, url_escape

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from ...base.zmqhandlers import AuthenticatedZMQStreamHandler, deserialize_binary_message

from jupyter_client import protocol_version as client_protocol_version

class MainKernelHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
km = self.kernel_manager
kernels = yield gen.maybe_future(km.list_kernels())
self.finish(json.dumps(kernels, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def post(self):
Expand All @@ -56,15 +54,13 @@ def post(self):

class KernelHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, kernel_id):
km = self.kernel_manager
km._check_kernel_id(kernel_id)
model = km.kernel_model(kernel_id)
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, kernel_id):
Expand All @@ -76,7 +72,6 @@ def delete(self, kernel_id):

class KernelActionHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def post(self, kernel_id, action):
Expand Down
4 changes: 1 addition & 3 deletions notebook/services/kernelspecs/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from ...utils import url_path_join, url_unescape

def kernelspec_model(handler, name):
Expand Down Expand Up @@ -45,7 +45,6 @@ def kernelspec_model(handler, name):

class MainKernelSpecHandler(APIHandler):

@json_errors
@web.authenticated
def get(self):
ksm = self.kernel_spec_manager
Expand All @@ -66,7 +65,6 @@ def get(self):

class KernelSpecHandler(APIHandler):

@json_errors
@web.authenticated
def get(self, kernel_name):
try:
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/nbconvert/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler

class NbconvertRootHandler(APIHandler):

@json_errors
@web.authenticated
def get(self):
try:
Expand Down
3 changes: 1 addition & 2 deletions notebook/services/security/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from tornado import web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from . import csp_report_uri

class CSPReportHandler(APIHandler):
Expand All @@ -21,7 +21,6 @@ def check_xsrf_cookie(self):
# don't check XSRF for CSP reports
return

@json_errors
@web.authenticated
def post(self):
'''Log a content security policy violation report'''
Expand Down
7 changes: 1 addition & 6 deletions notebook/services/sessions/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@

from tornado import gen, web

from ...base.handlers import APIHandler, json_errors
from ...base.handlers import APIHandler
from jupyter_client.jsonutil import date_default
from notebook.utils import url_path_join
from jupyter_client.kernelspec import NoSuchKernel


class SessionRootHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self):
Expand All @@ -28,7 +27,6 @@ def get(self):
sessions = yield gen.maybe_future(sm.list_sessions())
self.finish(json.dumps(sessions, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def post(self):
Expand Down Expand Up @@ -90,7 +88,6 @@ def post(self):

class SessionHandler(APIHandler):

@json_errors
@web.authenticated
@gen.coroutine
def get(self, session_id):
Expand All @@ -99,7 +96,6 @@ def get(self, session_id):
model = yield gen.maybe_future(sm.get_session(session_id=session_id))
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def patch(self, session_id):
Expand Down Expand Up @@ -153,7 +149,6 @@ def patch(self, session_id):
)
self.finish(json.dumps(model, default=date_default))

@json_errors
@web.authenticated
@gen.coroutine
def delete(self, session_id):
Expand Down
Loading

0 comments on commit b2edf89

Please sign in to comment.