Skip to content

Commit

Permalink
Launch the browser with a redirect file
Browse files Browse the repository at this point in the history
This avoids putting the authentication token into a command-line
argument to launch the browser, where it's visible to other users.
Filesystem permissions should ensure that only the user who started the
notebook can use this route to authenticate.
Thanks to Dr Owain Kenway for suggesting this technique.
  • Loading branch information
takluyver committed Dec 11, 2018
1 parent f759e4d commit 270c0f9
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 26 deletions.
96 changes: 73 additions & 23 deletions notebook/notebookapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import signal
import socket
import sys
import tempfile
import threading
import time
import warnings
Expand Down Expand Up @@ -107,7 +108,7 @@
from notebook._sysinfo import get_sys_info

from ._tz import utcnow, utcfromtimestamp
from .utils import url_path_join, check_pid, url_escape
from .utils import url_path_join, check_pid, url_escape, urljoin, pathname2url

#-----------------------------------------------------------------------------
# Module globals
Expand Down Expand Up @@ -1184,6 +1185,13 @@ def _update_mathjax_config(self, change):
def _default_info_file(self):
info_file = "nbserver-%s.json" % os.getpid()
return os.path.join(self.runtime_dir, info_file)

browser_open_file = Unicode()

@default('browser_open_file')
def _default_browser_open_file(self):
basename = "nbserver-%s-open.html" % os.getpid()
return os.path.join(self.runtime_dir, basename)

pylab = Unicode('disabled', config=True,
help=_("""
Expand Down Expand Up @@ -1697,6 +1705,67 @@ def remove_server_info_file(self):
if e.errno != errno.ENOENT:
raise

def write_browser_open_file(self):
"""Write an nbserver-<pid>-open.html file
This can be used to open the notebook in a browser
"""
# default_url contains base_url, but so does connection_url
open_url = self.default_url[len(self.base_url):]

with open(self.browser_open_file, 'w', encoding='utf-8') as f:
self._write_browser_open_file(open_url, f)

def _write_browser_open_file(self, url, fh):
if self.token:
url = url_concat(url, {'token': self.one_time_token})
url = url_path_join(self.connection_url, url)

jinja2_env = self.web_app.settings['jinja2_env']
template = jinja2_env.get_template('browser-open.html')
fh.write(template.render(open_url=url))

def remove_browser_open_file(self):
"""Remove the nbserver-<pid>-open.html file created for this server.
Ignores the error raised when the file has already been removed.
"""
try:
os.unlink(self.browser_open_file)
except OSError as e:
if e.errno != errno.ENOENT:
raise

def launch_browser(self):
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warning(_('No web browser found: %s.') % e)
browser = None

if not browser:
return

if self.file_to_run:
if not os.path.exists(self.file_to_run):
self.log.critical(_("%s does not exist") % self.file_to_run)
self.exit(1)

relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep)))

# Write a temporary file to open in the browser
fd, open_file = tempfile.mkstemp(suffix='.html')
with open(fd, 'w', encoding='utf-8') as fh:
self._write_browser_open_file(uri, fh)
else:
open_file = self.browser_open_file

b = lambda: browser.open(
urljoin('file:', pathname2url(open_file)),
new=self.webbrowser_open_new)
threading.Thread(target=b).start()

def start(self):
""" Start the Notebook server app, after initialization
Expand Down Expand Up @@ -1726,30 +1795,10 @@ def start(self):
"resources section at https://jupyter.org/community.html."))

self.write_server_info_file()
self.write_browser_open_file()

if self.open_browser or self.file_to_run:
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warning(_('No web browser found: %s.') % e)
browser = None

if self.file_to_run:
if not os.path.exists(self.file_to_run):
self.log.critical(_("%s does not exist") % self.file_to_run)
self.exit(1)

relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep)))
else:
# default_url contains base_url, but so does connection_url
uri = self.default_url[len(self.base_url):]
if self.one_time_token:
uri = url_concat(uri, {'token': self.one_time_token})
if browser:
b = lambda : browser.open(url_path_join(self.connection_url, uri),
new=self.webbrowser_open_new)
threading.Thread(target=b).start()
self.launch_browser()

if self.token and self._token_generated:
# log full URL with generated token, so there's a copy/pasteable link
Expand All @@ -1773,6 +1822,7 @@ def start(self):
info(_("Interrupted..."))
finally:
self.remove_server_info_file()
self.remove_browser_open_file()
self.cleanup_kernels()

def stop(self):
Expand Down
18 changes: 18 additions & 0 deletions notebook/templates/browser-open.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{# This template is not served, but written as a file to open in the browser,
passing the token without putting it in a command-line argument. #}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="1;url={{ open_url }}" />
<title>Opening Jupyter Notebook</title>
</head>
<body>

<p>
This page should redirect you to Jupyter Notebook. If it doesn't,
<a href="{{ open_url }}">click here to go to Jupyter</a>.
</p>

</body>
</html>
7 changes: 4 additions & 3 deletions notebook/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
from distutils.version import LooseVersion

try:
from urllib.parse import quote, unquote, urlparse
from urllib.parse import quote, unquote, urlparse, urljoin
from urllib.request import pathname2url
except ImportError:
from urllib import quote, unquote
from urlparse import urlparse
from urllib import quote, unquote, pathname2url
from urlparse import urlparse, urljoin

from ipython_genutils import py3compat

Expand Down

0 comments on commit 270c0f9

Please sign in to comment.