##// END OF EJS Templates
Merge branch 'enh/httpauth' of https://github.com/satra/ipython into satra-enh/httpauth
Brian E. Granger -
r4723:06032c55 merge
parent child Browse files
Show More
@@ -0,0 +1,66 b''
1 <!DOCTYPE HTML>
2 <html>
3
4 <head>
5 <meta charset="utf-8">
6
7 <title>IPython Notebook</title>
8
9 <link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
10 <!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
11 <!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
12
13 <link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
14 <link rel="stylesheet" href="static/css/layout.css" type="text/css" />
15 <link rel="stylesheet" href="static/css/base.css" type="text/css" />
16 <script type="text/javascript" charset="utf-8">
17 function add_next_to_action(){
18 // add 'next' argument to action url, to preserve redirect
19 var query = location.search.substring(1);
20 var form = document.forms[0];
21 var action = form.getAttribute("action");
22 form.setAttribute("action", action + '?' + query);
23 }
24 </script>
25 </head>
26
27 <body onload="add_next_to_action()">
28
29 <div id="header">
30 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
31 </div>
32
33 <div id="header_border"></div>
34
35 <div id="main_app">
36
37 <div id="app_hbox">
38
39 <div id="left_panel">
40 </div>
41
42 <div id="content_panel">
43 <form action="/login" method="post">
44 Name: <input type="text" name="name" value="{{user_id}}">
45 Password: <input type="password" name="password">
46 <input type="submit" value="Sign in">
47 </form>
48 </div>
49 <div id="right_panel">
50 </div>
51
52 </div>
53
54 </div>
55
56 <script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
57 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
58 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
59 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
61
62 </body>
63
64 </html>
65
66
@@ -16,6 +16,9 b' Authors:'
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
20 import Cookie
21
19 from tornado import web
22 from tornado import web
20 from tornado import websocket
23 from tornado import websocket
21
24
@@ -35,21 +38,46 b' except ImportError:'
35 # Top-level handlers
38 # Top-level handlers
36 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
37
40
38
41 class AuthenticatedHandler(web.RequestHandler):
39 class NBBrowserHandler(web.RequestHandler):
42 """A RequestHandler with an authenticated user."""
43 def get_current_user(self):
44 password = self.get_secure_cookie("password")
45 if password is None:
46 # cookie doesn't exist, or is invalid. Clear to prevent repeated
47 # 'Invalid cookie signature' warnings.
48 self.clear_cookie('password')
49 self.clear_cookie("user_id")
50 if self.application.password and self.application.password != password:
51 return None
52 return self.get_secure_cookie("user") or 'anonymous'
53
54 class NBBrowserHandler(AuthenticatedHandler):
55 @web.authenticated
40 def get(self):
56 def get(self):
41 nbm = self.application.notebook_manager
57 nbm = self.application.notebook_manager
42 project = nbm.notebook_dir
58 project = nbm.notebook_dir
43 self.render('nbbrowser.html', project=project)
59 self.render('nbbrowser.html', project=project)
44
60
61 class LoginHandler(AuthenticatedHandler):
62 def get(self):
63 user_id = self.get_secure_cookie("user") or ''
64 self.render('login.html', user_id=user_id)
65
66 def post(self):
67 self.set_secure_cookie("user", self.get_argument("name", default=u''))
68 self.set_secure_cookie("password", self.get_argument("password", default=u''))
69 url = self.get_argument("next", default="/")
70 self.redirect(url)
45
71
46 class NewHandler(web.RequestHandler):
72 class NewHandler(AuthenticatedHandler):
73 @web.authenticated
47 def get(self):
74 def get(self):
48 notebook_id = self.application.notebook_manager.new_notebook()
75 notebook_id = self.application.notebook_manager.new_notebook()
49 self.render('notebook.html', notebook_id=notebook_id)
76 self.render('notebook.html', notebook_id=notebook_id)
50
77
51
78
52 class NamedNotebookHandler(web.RequestHandler):
79 class NamedNotebookHandler(AuthenticatedHandler):
80 @web.authenticated
53 def get(self, notebook_id):
81 def get(self, notebook_id):
54 nbm = self.application.notebook_manager
82 nbm = self.application.notebook_manager
55 if not nbm.notebook_exists(notebook_id):
83 if not nbm.notebook_exists(notebook_id):
@@ -62,12 +90,14 b' class NamedNotebookHandler(web.RequestHandler):'
62 #-----------------------------------------------------------------------------
90 #-----------------------------------------------------------------------------
63
91
64
92
65 class MainKernelHandler(web.RequestHandler):
93 class MainKernelHandler(AuthenticatedHandler):
66
94
95 @web.authenticated
67 def get(self):
96 def get(self):
68 km = self.application.kernel_manager
97 km = self.application.kernel_manager
69 self.finish(jsonapi.dumps(km.kernel_ids))
98 self.finish(jsonapi.dumps(km.kernel_ids))
70
99
100 @web.authenticated
71 def post(self):
101 def post(self):
72 km = self.application.kernel_manager
102 km = self.application.kernel_manager
73 notebook_id = self.get_argument('notebook', default=None)
103 notebook_id = self.get_argument('notebook', default=None)
@@ -78,10 +108,11 b' class MainKernelHandler(web.RequestHandler):'
78 self.finish(jsonapi.dumps(data))
108 self.finish(jsonapi.dumps(data))
79
109
80
110
81 class KernelHandler(web.RequestHandler):
111 class KernelHandler(AuthenticatedHandler):
82
112
83 SUPPORTED_METHODS = ('DELETE')
113 SUPPORTED_METHODS = ('DELETE')
84
114
115 @web.authenticated
85 def delete(self, kernel_id):
116 def delete(self, kernel_id):
86 km = self.application.kernel_manager
117 km = self.application.kernel_manager
87 km.kill_kernel(kernel_id)
118 km.kill_kernel(kernel_id)
@@ -89,8 +120,9 b' class KernelHandler(web.RequestHandler):'
89 self.finish()
120 self.finish()
90
121
91
122
92 class KernelActionHandler(web.RequestHandler):
123 class KernelActionHandler(AuthenticatedHandler):
93
124
125 @web.authenticated
94 def post(self, kernel_id, action):
126 def post(self, kernel_id, action):
95 km = self.application.kernel_manager
127 km = self.application.kernel_manager
96 if action == 'interrupt':
128 if action == 'interrupt':
@@ -136,20 +168,58 b' class ZMQStreamHandler(websocket.WebSocketHandler):'
136 else:
168 else:
137 self.write_message(msg)
169 self.write_message(msg)
138
170
139
171 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
140 class IOPubHandler(ZMQStreamHandler):
172 def open(self, kernel_id):
173 self.kernel_id = kernel_id
174 self.session = Session()
175 self.save_on_message = self.on_message
176 self.on_message = self.on_first_message
177
178 def get_current_user(self):
179 password = self.get_secure_cookie("password")
180 if password is None:
181 # clear cookies, to prevent future Invalid cookie signature warnings
182 self._cookies = Cookie.SimpleCookie()
183 if self.application.password and self.application.password != password:
184 return None
185 return self.get_secure_cookie("user") or 'anonymous'
186
187 def _inject_cookie_message(self, msg):
188 """Inject the first message, which is the document cookie,
189 for authentication."""
190 if isinstance(msg, unicode):
191 # Cookie can't constructor doesn't accept unicode strings for some reason
192 msg = msg.encode('utf8', 'replace')
193 try:
194 self._cookies = Cookie.SimpleCookie(msg)
195 except:
196 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
197
198 def on_first_message(self, msg):
199 self._inject_cookie_message(msg)
200 if self.get_current_user() is None:
201 logging.warn("Couldn't authenticate WebSocket connection")
202 raise web.HTTPError(403)
203 self.on_message = self.save_on_message
204
205
206 class IOPubHandler(AuthenticatedZMQStreamHandler):
141
207
142 def initialize(self, *args, **kwargs):
208 def initialize(self, *args, **kwargs):
143 self._kernel_alive = True
209 self._kernel_alive = True
144 self._beating = False
210 self._beating = False
145 self.iopub_stream = None
211 self.iopub_stream = None
146 self.hb_stream = None
212 self.hb_stream = None
147
213
148 def open(self, kernel_id):
214 def on_first_message(self, msg):
215 try:
216 super(IOPubHandler, self).on_first_message(msg)
217 except web.HTTPError:
218 self.close()
219 return
149 km = self.application.kernel_manager
220 km = self.application.kernel_manager
150 self.kernel_id = kernel_id
151 self.session = Session()
152 self.time_to_dead = km.time_to_dead
221 self.time_to_dead = km.time_to_dead
222 kernel_id = self.kernel_id
153 try:
223 try:
154 self.iopub_stream = km.create_iopub_stream(kernel_id)
224 self.iopub_stream = km.create_iopub_stream(kernel_id)
155 self.hb_stream = km.create_hb_stream(kernel_id)
225 self.hb_stream = km.create_hb_stream(kernel_id)
@@ -158,9 +228,13 b' class IOPubHandler(ZMQStreamHandler):'
158 # close the connection.
228 # close the connection.
159 if not self.stream.closed():
229 if not self.stream.closed():
160 self.stream.close()
230 self.stream.close()
231 self.close()
161 else:
232 else:
162 self.iopub_stream.on_recv(self._on_zmq_reply)
233 self.iopub_stream.on_recv(self._on_zmq_reply)
163 self.start_hb(self.kernel_died)
234 self.start_hb(self.kernel_died)
235
236 def on_message(self, msg):
237 pass
164
238
165 def on_close(self):
239 def on_close(self):
166 # This method can be called twice, once by self.kernel_died and once
240 # This method can be called twice, once by self.kernel_died and once
@@ -216,15 +290,20 b' class IOPubHandler(ZMQStreamHandler):'
216 self.on_close()
290 self.on_close()
217
291
218
292
219 class ShellHandler(ZMQStreamHandler):
293 class ShellHandler(AuthenticatedZMQStreamHandler):
220
294
221 def initialize(self, *args, **kwargs):
295 def initialize(self, *args, **kwargs):
222 self.shell_stream = None
296 self.shell_stream = None
223
297
224 def open(self, kernel_id):
298 def on_first_message(self, msg):
299 try:
300 super(ShellHandler, self).on_first_message(msg)
301 except web.HTTPError:
302 self.close()
303 return
225 km = self.application.kernel_manager
304 km = self.application.kernel_manager
226 self.max_msg_size = km.max_msg_size
305 self.max_msg_size = km.max_msg_size
227 self.kernel_id = kernel_id
306 kernel_id = self.kernel_id
228 try:
307 try:
229 self.shell_stream = km.create_shell_stream(kernel_id)
308 self.shell_stream = km.create_shell_stream(kernel_id)
230 except web.HTTPError:
309 except web.HTTPError:
@@ -232,8 +311,8 b' class ShellHandler(ZMQStreamHandler):'
232 # close the connection.
311 # close the connection.
233 if not self.stream.closed():
312 if not self.stream.closed():
234 self.stream.close()
313 self.stream.close()
314 self.close()
235 else:
315 else:
236 self.session = Session()
237 self.shell_stream.on_recv(self._on_zmq_reply)
316 self.shell_stream.on_recv(self._on_zmq_reply)
238
317
239 def on_message(self, msg):
318 def on_message(self, msg):
@@ -251,13 +330,15 b' class ShellHandler(ZMQStreamHandler):'
251 # Notebook web service handlers
330 # Notebook web service handlers
252 #-----------------------------------------------------------------------------
331 #-----------------------------------------------------------------------------
253
332
254 class NotebookRootHandler(web.RequestHandler):
333 class NotebookRootHandler(AuthenticatedHandler):
255
334
335 @web.authenticated
256 def get(self):
336 def get(self):
257 nbm = self.application.notebook_manager
337 nbm = self.application.notebook_manager
258 files = nbm.list_notebooks()
338 files = nbm.list_notebooks()
259 self.finish(jsonapi.dumps(files))
339 self.finish(jsonapi.dumps(files))
260
340
341 @web.authenticated
261 def post(self):
342 def post(self):
262 nbm = self.application.notebook_manager
343 nbm = self.application.notebook_manager
263 body = self.request.body.strip()
344 body = self.request.body.strip()
@@ -271,10 +352,11 b' class NotebookRootHandler(web.RequestHandler):'
271 self.finish(jsonapi.dumps(notebook_id))
352 self.finish(jsonapi.dumps(notebook_id))
272
353
273
354
274 class NotebookHandler(web.RequestHandler):
355 class NotebookHandler(AuthenticatedHandler):
275
356
276 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
357 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
277
358
359 @web.authenticated
278 def get(self, notebook_id):
360 def get(self, notebook_id):
279 nbm = self.application.notebook_manager
361 nbm = self.application.notebook_manager
280 format = self.get_argument('format', default='json')
362 format = self.get_argument('format', default='json')
@@ -288,6 +370,7 b' class NotebookHandler(web.RequestHandler):'
288 self.set_header('Last-Modified', last_mod)
370 self.set_header('Last-Modified', last_mod)
289 self.finish(data)
371 self.finish(data)
290
372
373 @web.authenticated
291 def put(self, notebook_id):
374 def put(self, notebook_id):
292 nbm = self.application.notebook_manager
375 nbm = self.application.notebook_manager
293 format = self.get_argument('format', default='json')
376 format = self.get_argument('format', default='json')
@@ -296,6 +379,7 b' class NotebookHandler(web.RequestHandler):'
296 self.set_status(204)
379 self.set_status(204)
297 self.finish()
380 self.finish()
298
381
382 @web.authenticated
299 def delete(self, notebook_id):
383 def delete(self, notebook_id):
300 nbm = self.application.notebook_manager
384 nbm = self.application.notebook_manager
301 nbm.delete_notebook(notebook_id)
385 nbm.delete_notebook(notebook_id)
@@ -307,8 +391,9 b' class NotebookHandler(web.RequestHandler):'
307 #-----------------------------------------------------------------------------
391 #-----------------------------------------------------------------------------
308
392
309
393
310 class RSTHandler(web.RequestHandler):
394 class RSTHandler(AuthenticatedHandler):
311
395
396 @web.authenticated
312 def post(self):
397 def post(self):
313 if publish_string is None:
398 if publish_string is None:
314 raise web.HTTPError(503, u'docutils not available')
399 raise web.HTTPError(503, u'docutils not available')
@@ -35,7 +35,7 b' from tornado import httpserver'
35 from tornado import web
35 from tornado import web
36
36
37 from .kernelmanager import MappingKernelManager
37 from .kernelmanager import MappingKernelManager
38 from .handlers import (
38 from .handlers import (LoginHandler,
39 NBBrowserHandler, NewHandler, NamedNotebookHandler,
39 NBBrowserHandler, NewHandler, NamedNotebookHandler,
40 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
40 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
41 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
41 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
@@ -80,6 +80,7 b' class NotebookWebApplication(web.Application):'
80 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
80 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
81 handlers = [
81 handlers = [
82 (r"/", NBBrowserHandler),
82 (r"/", NBBrowserHandler),
83 (r"/login", LoginHandler),
83 (r"/new", NewHandler),
84 (r"/new", NewHandler),
84 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
85 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
85 (r"/kernels", MainKernelHandler),
86 (r"/kernels", MainKernelHandler),
@@ -94,6 +95,8 b' class NotebookWebApplication(web.Application):'
94 settings = dict(
95 settings = dict(
95 template_path=os.path.join(os.path.dirname(__file__), "templates"),
96 template_path=os.path.join(os.path.dirname(__file__), "templates"),
96 static_path=os.path.join(os.path.dirname(__file__), "static"),
97 static_path=os.path.join(os.path.dirname(__file__), "static"),
98 cookie_secret=os.urandom(1024),
99 login_url="/login",
97 )
100 )
98 web.Application.__init__(self, handlers, **settings)
101 web.Application.__init__(self, handlers, **settings)
99
102
@@ -122,7 +125,7 b' aliases.update({'
122 'keyfile': 'IPythonNotebookApp.keyfile',
125 'keyfile': 'IPythonNotebookApp.keyfile',
123 'certfile': 'IPythonNotebookApp.certfile',
126 'certfile': 'IPythonNotebookApp.certfile',
124 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
127 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
125 'notebook-dir': 'NotebookManager.notebook_dir'
128 'notebook-dir': 'NotebookManager.notebook_dir',
126 })
129 })
127
130
128 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
131 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
@@ -185,6 +188,10 b' class IPythonNotebookApp(BaseIPythonApplication):'
185 help="""The full path to a private key file for usage with SSL/TLS."""
188 help="""The full path to a private key file for usage with SSL/TLS."""
186 )
189 )
187
190
191 password = Unicode(u'', config=True,
192 help="""Password to use for web authentication"""
193 )
194
188 def get_ws_url(self):
195 def get_ws_url(self):
189 """Return the WebSocket URL for this server."""
196 """Return the WebSocket URL for this server."""
190 if self.certfile:
197 if self.certfile:
@@ -241,6 +248,7 b' class IPythonNotebookApp(BaseIPythonApplication):'
241 ssl_options['keyfile'] = self.keyfile
248 ssl_options['keyfile'] = self.keyfile
242 else:
249 else:
243 ssl_options = None
250 ssl_options = None
251 self.web_app.password = self.password
244 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
252 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
245 if ssl_options is None and not self.ip:
253 if ssl_options is None and not self.ip:
246 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
254 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
@@ -95,6 +95,12 b' var IPython = (function (IPython) {'
95 console.log("Starting WS:", ws_url);
95 console.log("Starting WS:", ws_url);
96 this.shell_channel = new this.WebSocket(ws_url + "/shell");
96 this.shell_channel = new this.WebSocket(ws_url + "/shell");
97 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
97 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
98 send_cookie = function(){
99 this.send(document.cookie);
100 console.log(this);
101 }
102 this.shell_channel.onopen = send_cookie;
103 this.iopub_channel.onopen = send_cookie;
98 };
104 };
99
105
100
106
General Comments 0
You need to be logged in to leave comments. Login now