|
|
|
|
@ -16,6 +16,9 @@ Authors:
|
|
|
|
|
# Imports
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
import Cookie
|
|
|
|
|
|
|
|
|
|
from tornado import web
|
|
|
|
|
from tornado import websocket
|
|
|
|
|
|
|
|
|
|
@ -35,21 +38,46 @@ except ImportError:
|
|
|
|
|
# Top-level handlers
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NBBrowserHandler(web.RequestHandler):
|
|
|
|
|
class AuthenticatedHandler(web.RequestHandler):
|
|
|
|
|
"""A RequestHandler with an authenticated user."""
|
|
|
|
|
def get_current_user(self):
|
|
|
|
|
password = self.get_secure_cookie("password")
|
|
|
|
|
if password is None:
|
|
|
|
|
# cookie doesn't exist, or is invalid. Clear to prevent repeated
|
|
|
|
|
# 'Invalid cookie signature' warnings.
|
|
|
|
|
self.clear_cookie('password')
|
|
|
|
|
self.clear_cookie("user_id")
|
|
|
|
|
if self.application.password and self.application.password != password:
|
|
|
|
|
return None
|
|
|
|
|
return self.get_secure_cookie("user") or 'anonymous'
|
|
|
|
|
|
|
|
|
|
class NBBrowserHandler(AuthenticatedHandler):
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
project = nbm.notebook_dir
|
|
|
|
|
self.render('nbbrowser.html', project=project)
|
|
|
|
|
|
|
|
|
|
class LoginHandler(AuthenticatedHandler):
|
|
|
|
|
def get(self):
|
|
|
|
|
user_id = self.get_secure_cookie("user") or ''
|
|
|
|
|
self.render('login.html', user_id=user_id)
|
|
|
|
|
|
|
|
|
|
def post(self):
|
|
|
|
|
self.set_secure_cookie("user", self.get_argument("name", default=u''))
|
|
|
|
|
self.set_secure_cookie("password", self.get_argument("password", default=u''))
|
|
|
|
|
url = self.get_argument("next", default="/")
|
|
|
|
|
self.redirect(url)
|
|
|
|
|
|
|
|
|
|
class NewHandler(web.RequestHandler):
|
|
|
|
|
class NewHandler(AuthenticatedHandler):
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self):
|
|
|
|
|
notebook_id = self.application.notebook_manager.new_notebook()
|
|
|
|
|
self.render('notebook.html', notebook_id=notebook_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NamedNotebookHandler(web.RequestHandler):
|
|
|
|
|
class NamedNotebookHandler(AuthenticatedHandler):
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self, notebook_id):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
if not nbm.notebook_exists(notebook_id):
|
|
|
|
|
@ -62,12 +90,14 @@ class NamedNotebookHandler(web.RequestHandler):
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MainKernelHandler(web.RequestHandler):
|
|
|
|
|
class MainKernelHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self):
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
self.finish(jsonapi.dumps(km.kernel_ids))
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def post(self):
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
notebook_id = self.get_argument('notebook', default=None)
|
|
|
|
|
@ -78,10 +108,11 @@ class MainKernelHandler(web.RequestHandler):
|
|
|
|
|
self.finish(jsonapi.dumps(data))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KernelHandler(web.RequestHandler):
|
|
|
|
|
class KernelHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
SUPPORTED_METHODS = ('DELETE')
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def delete(self, kernel_id):
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
km.kill_kernel(kernel_id)
|
|
|
|
|
@ -89,8 +120,9 @@ class KernelHandler(web.RequestHandler):
|
|
|
|
|
self.finish()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class KernelActionHandler(web.RequestHandler):
|
|
|
|
|
class KernelActionHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def post(self, kernel_id, action):
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
if action == 'interrupt':
|
|
|
|
|
@ -136,20 +168,58 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
|
|
|
|
|
else:
|
|
|
|
|
self.write_message(msg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IOPubHandler(ZMQStreamHandler):
|
|
|
|
|
class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
|
|
|
|
|
def open(self, kernel_id):
|
|
|
|
|
self.kernel_id = kernel_id
|
|
|
|
|
self.session = Session()
|
|
|
|
|
self.save_on_message = self.on_message
|
|
|
|
|
self.on_message = self.on_first_message
|
|
|
|
|
|
|
|
|
|
def get_current_user(self):
|
|
|
|
|
password = self.get_secure_cookie("password")
|
|
|
|
|
if password is None:
|
|
|
|
|
# clear cookies, to prevent future Invalid cookie signature warnings
|
|
|
|
|
self._cookies = Cookie.SimpleCookie()
|
|
|
|
|
if self.application.password and self.application.password != password:
|
|
|
|
|
return None
|
|
|
|
|
return self.get_secure_cookie("user") or 'anonymous'
|
|
|
|
|
|
|
|
|
|
def _inject_cookie_message(self, msg):
|
|
|
|
|
"""Inject the first message, which is the document cookie,
|
|
|
|
|
for authentication."""
|
|
|
|
|
if isinstance(msg, unicode):
|
|
|
|
|
# Cookie can't constructor doesn't accept unicode strings for some reason
|
|
|
|
|
msg = msg.encode('utf8', 'replace')
|
|
|
|
|
try:
|
|
|
|
|
self._cookies = Cookie.SimpleCookie(msg)
|
|
|
|
|
except:
|
|
|
|
|
logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
|
|
|
|
|
|
|
|
|
|
def on_first_message(self, msg):
|
|
|
|
|
self._inject_cookie_message(msg)
|
|
|
|
|
if self.get_current_user() is None:
|
|
|
|
|
logging.warn("Couldn't authenticate WebSocket connection")
|
|
|
|
|
raise web.HTTPError(403)
|
|
|
|
|
self.on_message = self.save_on_message
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IOPubHandler(AuthenticatedZMQStreamHandler):
|
|
|
|
|
|
|
|
|
|
def initialize(self, *args, **kwargs):
|
|
|
|
|
self._kernel_alive = True
|
|
|
|
|
self._beating = False
|
|
|
|
|
self.iopub_stream = None
|
|
|
|
|
self.hb_stream = None
|
|
|
|
|
|
|
|
|
|
def open(self, kernel_id):
|
|
|
|
|
|
|
|
|
|
def on_first_message(self, msg):
|
|
|
|
|
try:
|
|
|
|
|
super(IOPubHandler, self).on_first_message(msg)
|
|
|
|
|
except web.HTTPError:
|
|
|
|
|
self.close()
|
|
|
|
|
return
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
self.kernel_id = kernel_id
|
|
|
|
|
self.session = Session()
|
|
|
|
|
self.time_to_dead = km.time_to_dead
|
|
|
|
|
kernel_id = self.kernel_id
|
|
|
|
|
try:
|
|
|
|
|
self.iopub_stream = km.create_iopub_stream(kernel_id)
|
|
|
|
|
self.hb_stream = km.create_hb_stream(kernel_id)
|
|
|
|
|
@ -158,9 +228,13 @@ class IOPubHandler(ZMQStreamHandler):
|
|
|
|
|
# close the connection.
|
|
|
|
|
if not self.stream.closed():
|
|
|
|
|
self.stream.close()
|
|
|
|
|
self.close()
|
|
|
|
|
else:
|
|
|
|
|
self.iopub_stream.on_recv(self._on_zmq_reply)
|
|
|
|
|
self.start_hb(self.kernel_died)
|
|
|
|
|
|
|
|
|
|
def on_message(self, msg):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
def on_close(self):
|
|
|
|
|
# This method can be called twice, once by self.kernel_died and once
|
|
|
|
|
@ -216,15 +290,20 @@ class IOPubHandler(ZMQStreamHandler):
|
|
|
|
|
self.on_close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ShellHandler(ZMQStreamHandler):
|
|
|
|
|
class ShellHandler(AuthenticatedZMQStreamHandler):
|
|
|
|
|
|
|
|
|
|
def initialize(self, *args, **kwargs):
|
|
|
|
|
self.shell_stream = None
|
|
|
|
|
|
|
|
|
|
def open(self, kernel_id):
|
|
|
|
|
def on_first_message(self, msg):
|
|
|
|
|
try:
|
|
|
|
|
super(ShellHandler, self).on_first_message(msg)
|
|
|
|
|
except web.HTTPError:
|
|
|
|
|
self.close()
|
|
|
|
|
return
|
|
|
|
|
km = self.application.kernel_manager
|
|
|
|
|
self.max_msg_size = km.max_msg_size
|
|
|
|
|
self.kernel_id = kernel_id
|
|
|
|
|
kernel_id = self.kernel_id
|
|
|
|
|
try:
|
|
|
|
|
self.shell_stream = km.create_shell_stream(kernel_id)
|
|
|
|
|
except web.HTTPError:
|
|
|
|
|
@ -232,8 +311,8 @@ class ShellHandler(ZMQStreamHandler):
|
|
|
|
|
# close the connection.
|
|
|
|
|
if not self.stream.closed():
|
|
|
|
|
self.stream.close()
|
|
|
|
|
self.close()
|
|
|
|
|
else:
|
|
|
|
|
self.session = Session()
|
|
|
|
|
self.shell_stream.on_recv(self._on_zmq_reply)
|
|
|
|
|
|
|
|
|
|
def on_message(self, msg):
|
|
|
|
|
@ -251,13 +330,15 @@ class ShellHandler(ZMQStreamHandler):
|
|
|
|
|
# Notebook web service handlers
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
class NotebookRootHandler(web.RequestHandler):
|
|
|
|
|
class NotebookRootHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
files = nbm.list_notebooks()
|
|
|
|
|
self.finish(jsonapi.dumps(files))
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def post(self):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
body = self.request.body.strip()
|
|
|
|
|
@ -271,10 +352,11 @@ class NotebookRootHandler(web.RequestHandler):
|
|
|
|
|
self.finish(jsonapi.dumps(notebook_id))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class NotebookHandler(web.RequestHandler):
|
|
|
|
|
class NotebookHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def get(self, notebook_id):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
format = self.get_argument('format', default='json')
|
|
|
|
|
@ -288,6 +370,7 @@ class NotebookHandler(web.RequestHandler):
|
|
|
|
|
self.set_header('Last-Modified', last_mod)
|
|
|
|
|
self.finish(data)
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def put(self, notebook_id):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
format = self.get_argument('format', default='json')
|
|
|
|
|
@ -296,6 +379,7 @@ class NotebookHandler(web.RequestHandler):
|
|
|
|
|
self.set_status(204)
|
|
|
|
|
self.finish()
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def delete(self, notebook_id):
|
|
|
|
|
nbm = self.application.notebook_manager
|
|
|
|
|
nbm.delete_notebook(notebook_id)
|
|
|
|
|
@ -307,8 +391,9 @@ class NotebookHandler(web.RequestHandler):
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RSTHandler(web.RequestHandler):
|
|
|
|
|
class RSTHandler(AuthenticatedHandler):
|
|
|
|
|
|
|
|
|
|
@web.authenticated
|
|
|
|
|
def post(self):
|
|
|
|
|
if publish_string is None:
|
|
|
|
|
raise web.HTTPError(503, u'docutils not available')
|
|
|
|
|
|