Show More
@@ -26,6 +26,7 b' from tornado import websocket' | |||
|
26 | 26 | from zmq.eventloop import ioloop |
|
27 | 27 | from zmq.utils import jsonapi |
|
28 | 28 | |
|
29 | from IPython.external.decorator import decorator | |
|
29 | 30 | from IPython.zmq.session import Session |
|
30 | 31 | |
|
31 | 32 | try: |
@@ -34,6 +35,16 b' except ImportError:' | |||
|
34 | 35 | publish_string = None |
|
35 | 36 | |
|
36 | 37 | |
|
38 | #----------------------------------------------------------------------------- | |
|
39 | # Decorator for disabling read-only handlers | |
|
40 | #----------------------------------------------------------------------------- | |
|
41 | ||
|
42 | @decorator | |
|
43 | def not_if_readonly(f, self, *args, **kwargs): | |
|
44 | if self.application.ipython_app.read_only: | |
|
45 | raise web.HTTPError(403, "Notebook server is read-only") | |
|
46 | else: | |
|
47 | return f(self, *args, **kwargs) | |
|
37 | 48 | |
|
38 | 49 | #----------------------------------------------------------------------------- |
|
39 | 50 | # Top-level handlers |
@@ -82,6 +93,7 b' class LoginHandler(AuthenticatedHandler):' | |||
|
82 | 93 | |
|
83 | 94 | class NewHandler(AuthenticatedHandler): |
|
84 | 95 | |
|
96 | @not_if_readonly | |
|
85 | 97 | @web.authenticated |
|
86 | 98 | def get(self): |
|
87 | 99 | nbm = self.application.notebook_manager |
@@ -118,11 +130,13 b' class NamedNotebookHandler(AuthenticatedHandler):' | |||
|
118 | 130 | |
|
119 | 131 | class MainKernelHandler(AuthenticatedHandler): |
|
120 | 132 | |
|
133 | @not_if_readonly | |
|
121 | 134 | @web.authenticated |
|
122 | 135 | def get(self): |
|
123 | 136 | km = self.application.kernel_manager |
|
124 | 137 | self.finish(jsonapi.dumps(km.kernel_ids)) |
|
125 | 138 | |
|
139 | @not_if_readonly | |
|
126 | 140 | @web.authenticated |
|
127 | 141 | def post(self): |
|
128 | 142 | km = self.application.kernel_manager |
@@ -138,6 +152,7 b' class KernelHandler(AuthenticatedHandler):' | |||
|
138 | 152 | |
|
139 | 153 | SUPPORTED_METHODS = ('DELETE') |
|
140 | 154 | |
|
155 | @not_if_readonly | |
|
141 | 156 | @web.authenticated |
|
142 | 157 | def delete(self, kernel_id): |
|
143 | 158 | km = self.application.kernel_manager |
@@ -148,6 +163,7 b' class KernelHandler(AuthenticatedHandler):' | |||
|
148 | 163 | |
|
149 | 164 | class KernelActionHandler(AuthenticatedHandler): |
|
150 | 165 | |
|
166 | @not_if_readonly | |
|
151 | 167 | @web.authenticated |
|
152 | 168 | def post(self, kernel_id, action): |
|
153 | 169 | km = self.application.kernel_manager |
@@ -226,6 +242,7 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler):' | |||
|
226 | 242 | except: |
|
227 | 243 | logging.warn("couldn't parse cookie string: %s",msg, exc_info=True) |
|
228 | 244 | |
|
245 | @not_if_readonly | |
|
229 | 246 | def on_first_message(self, msg): |
|
230 | 247 | self._inject_cookie_message(msg) |
|
231 | 248 | if self.get_current_user() is None: |
@@ -369,6 +386,7 b' class NotebookRootHandler(AuthenticatedHandler):' | |||
|
369 | 386 | files = nbm.list_notebooks() |
|
370 | 387 | self.finish(jsonapi.dumps(files)) |
|
371 | 388 | |
|
389 | @not_if_readonly | |
|
372 | 390 | @web.authenticated |
|
373 | 391 | def post(self): |
|
374 | 392 | nbm = self.application.notebook_manager |
@@ -401,6 +419,7 b' class NotebookHandler(AuthenticatedHandler):' | |||
|
401 | 419 | self.set_header('Last-Modified', last_mod) |
|
402 | 420 | self.finish(data) |
|
403 | 421 | |
|
422 | @not_if_readonly | |
|
404 | 423 | @web.authenticated |
|
405 | 424 | def put(self, notebook_id): |
|
406 | 425 | nbm = self.application.notebook_manager |
@@ -410,6 +429,7 b' class NotebookHandler(AuthenticatedHandler):' | |||
|
410 | 429 | self.set_status(204) |
|
411 | 430 | self.finish() |
|
412 | 431 | |
|
432 | @not_if_readonly | |
|
413 | 433 | @web.authenticated |
|
414 | 434 | def delete(self, notebook_id): |
|
415 | 435 | nbm = self.application.notebook_manager |
@@ -116,6 +116,10 b" flags['no-browser']=(" | |||
|
116 | 116 | {'NotebookApp' : {'open_browser' : False}}, |
|
117 | 117 | "Don't open the notebook in a browser after startup." |
|
118 | 118 | ) |
|
119 | flags['read-only'] = ( | |
|
120 | {'NotebookApp' : {'read_only' : True}}, | |
|
121 | "Launch the Notebook server in read-only mode, not allowing execution or editing" | |
|
122 | ) | |
|
119 | 123 | |
|
120 | 124 | # the flags that are specific to the frontend |
|
121 | 125 | # these must be scrubbed before being passed to the kernel, |
@@ -203,6 +207,10 b' class NotebookApp(BaseIPythonApplication):' | |||
|
203 | 207 | |
|
204 | 208 | open_browser = Bool(True, config=True, |
|
205 | 209 | help="Whether to open in a browser after starting.") |
|
210 | ||
|
211 | read_only = Bool(False, config=True, | |
|
212 | help="Whether to prevent editing/execution of notebooks." | |
|
213 | ) | |
|
206 | 214 | |
|
207 | 215 | def get_ws_url(self): |
|
208 | 216 | """Return the WebSocket URL for this server.""" |
@@ -282,7 +290,7 b' class NotebookApp(BaseIPythonApplication):' | |||
|
282 | 290 | # Try random ports centered around the default. |
|
283 | 291 | from random import randint |
|
284 | 292 | n = 50 # Max number of attempts, keep reasonably large. |
|
285 |
for port in |
|
|
293 | for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]: | |
|
286 | 294 | try: |
|
287 | 295 | self.http_server.listen(port, self.ip) |
|
288 | 296 | except socket.error, e: |
General Comments 0
You need to be logged in to leave comments.
Login now