diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index 1ce17b5..97b2937 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -26,6 +26,7 @@ from tornado import websocket from zmq.eventloop import ioloop from zmq.utils import jsonapi +from IPython.external.decorator import decorator from IPython.zmq.session import Session try: @@ -34,6 +35,16 @@ except ImportError: publish_string = None +#----------------------------------------------------------------------------- +# Decorator for disabling read-only handlers +#----------------------------------------------------------------------------- + +@decorator +def not_if_readonly(f, self, *args, **kwargs): + if self.application.ipython_app.read_only: + raise web.HTTPError(403, "Notebook server is read-only") + else: + return f(self, *args, **kwargs) #----------------------------------------------------------------------------- # Top-level handlers @@ -82,6 +93,7 @@ class LoginHandler(AuthenticatedHandler): class NewHandler(AuthenticatedHandler): + @not_if_readonly @web.authenticated def get(self): nbm = self.application.notebook_manager @@ -118,11 +130,13 @@ class NamedNotebookHandler(AuthenticatedHandler): class MainKernelHandler(AuthenticatedHandler): + @not_if_readonly @web.authenticated def get(self): km = self.application.kernel_manager self.finish(jsonapi.dumps(km.kernel_ids)) + @not_if_readonly @web.authenticated def post(self): km = self.application.kernel_manager @@ -138,6 +152,7 @@ class KernelHandler(AuthenticatedHandler): SUPPORTED_METHODS = ('DELETE') + @not_if_readonly @web.authenticated def delete(self, kernel_id): km = self.application.kernel_manager @@ -148,6 +163,7 @@ class KernelHandler(AuthenticatedHandler): class KernelActionHandler(AuthenticatedHandler): + @not_if_readonly @web.authenticated def post(self, kernel_id, action): km = self.application.kernel_manager @@ -226,6 +242,7 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler): except: logging.warn("couldn't parse cookie string: %s",msg, exc_info=True) + @not_if_readonly def on_first_message(self, msg): self._inject_cookie_message(msg) if self.get_current_user() is None: @@ -369,6 +386,7 @@ class NotebookRootHandler(AuthenticatedHandler): files = nbm.list_notebooks() self.finish(jsonapi.dumps(files)) + @not_if_readonly @web.authenticated def post(self): nbm = self.application.notebook_manager @@ -401,6 +419,7 @@ class NotebookHandler(AuthenticatedHandler): self.set_header('Last-Modified', last_mod) self.finish(data) + @not_if_readonly @web.authenticated def put(self, notebook_id): nbm = self.application.notebook_manager @@ -410,6 +429,7 @@ class NotebookHandler(AuthenticatedHandler): self.set_status(204) self.finish() + @not_if_readonly @web.authenticated def delete(self, notebook_id): nbm = self.application.notebook_manager diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index d037f95..27db5ad 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -116,6 +116,10 @@ flags['no-browser']=( {'NotebookApp' : {'open_browser' : False}}, "Don't open the notebook in a browser after startup." ) +flags['read-only'] = ( + {'NotebookApp' : {'read_only' : True}}, + "Launch the Notebook server in read-only mode, not allowing execution or editing" +) # the flags that are specific to the frontend # these must be scrubbed before being passed to the kernel, @@ -203,6 +207,10 @@ class NotebookApp(BaseIPythonApplication): open_browser = Bool(True, config=True, help="Whether to open in a browser after starting.") + + read_only = Bool(False, config=True, + help="Whether to prevent editing/execution of notebooks." + ) def get_ws_url(self): """Return the WebSocket URL for this server.""" @@ -282,7 +290,7 @@ class NotebookApp(BaseIPythonApplication): # Try random ports centered around the default. from random import randint n = 50 # Max number of attempts, keep reasonably large. - for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]: + for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]: try: self.http_server.listen(port, self.ip) except socket.error, e: