diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7b7842e55..641c2ac87 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -11,7 +11,8 @@ For more detailed information, see Use ``pip install notebook --upgrade`` or ``conda upgrade notebook`` to upgrade to the latest release. - + + .. _release-4.3.1: 4.3.1 @@ -19,28 +20,46 @@ For more detailed information, see 4.3.1 is a patch release with a security patch, a couple bug fixes, and improvements to the newly-released token authentication. +**Security fix**: + +- CVE-2016-9971. Fix CSRF vulnerability, + where malicious forms could create untitled files and start kernels + (no remote execution or modification of existing files) + for users of certain browsers (Firefox, Internet Explorer / Edge). + All previous notebook releases are affected. + Bug fixes: - Fix carriage return handling -- Make the font size more robust against fickle brow +- Make the font size more robust against fickle browsers - Ignore resize events that bubbled up and didn't come from window +- Add Authorization to allowed CORS headers +- Downgrade CodeMirror to 5.16 while we figure out issues in Safari Other improvements: - Better docs for token-based authentication - Further highlight token info in log output when autogenerated -- Add Authorization to allowed CORS headers -See the 4.3 milestone on GitHub for a complete list of -`issues `__ -and `pull requests `__ involved in this release. +See the 4.3.1 milestone on GitHub for a complete list of +`issues `__ +and `pull requests `__ involved in this release. .. _release-4.3: -4.3 ---- +4.3.0 +----- 4.3 is a minor release with many bug fixes and improvements. +The biggest user-facing change is the addition of token authentication, +which is enabled by default. +A token is generated and used when your browser is opened automatically, +so you shouldn't have to enter anything in the default circumstances. +If you see a login page +(e.g. by switching browsers, or launching on a new port with ``--no-browser``), +you get a login URL with the token from the command ``jupyter notebook list``, +which you can paste into your browser. + Highlights: @@ -88,6 +107,7 @@ See the 4.3 milestone on GitHub for a complete list of `issues `__ and `pull requests `__ involved in this release. + .. _release-4.2.3: 4.2.3 diff --git a/notebook/auth/login.py b/notebook/auth/login.py index ee260b7da..9ddfb7e52 100644 --- a/notebook/auth/login.py +++ b/notebook/auth/login.py @@ -97,7 +97,7 @@ class LoginHandler(IPythonHandler): auth_header_pat = re.compile('token\s+(.+)', re.IGNORECASE) @classmethod - def get_user_token(cls, handler): + def get_token(cls, handler): """Get the user token from a request Default: @@ -117,14 +117,29 @@ class LoginHandler(IPythonHandler): @classmethod def should_check_origin(cls, handler): """Should the Handler check for CORS origin validation? - + Origin check should be skipped for token-authenticated requests. + + Returns: + - True, if Handler must check for valid CORS origin. + - False, if Handler should skip origin check since requests are token-authenticated. + """ + return not cls.is_token_authenticated(handler) + + @classmethod + def is_token_authenticated(cls, handler): + """Returns True if handler has been token authenticated. Otherwise, False. + + Login with a token is used to signal certain things, such as: + + - permit access to REST API + - xsrf protection + - skip origin-checks for scripts """ if getattr(handler, '_user_id', None) is None: # ensure get_user has been called, so we know if we're token-authenticated handler.get_current_user() - token_authenticated = getattr(handler, '_token_authenticated', False) - return not token_authenticated + return getattr(handler, '_token_authenticated', False) @classmethod def get_user(cls, handler): @@ -136,40 +151,56 @@ class LoginHandler(IPythonHandler): # called on LoginHandler itself. if getattr(handler, '_user_id', None): return handler._user_id - user_id = handler.get_secure_cookie(handler.cookie_name) - if not user_id: + user_id = cls.get_user_token(handler) + if user_id is None: + user_id = handler.get_secure_cookie(handler.cookie_name) + else: + cls.set_login_cookie(handler, user_id) + # Record that the current request has been authenticated with a token. + # Used in is_token_authenticated above. + handler._token_authenticated = True + if user_id is None: # prevent extra Invalid cookie sig warnings: handler.clear_login_cookie() - token = handler.token - if not token and not handler.login_available: + if not handler.login_available: # Completely insecure! No authentication at all. # No need to warn here, though; validate_security will have already done that. - return 'anonymous' - if token: - # check login token from URL argument or Authorization header - user_token = cls.get_user_token(handler) - one_time_token = handler.one_time_token - authenticated = False - if user_token == token: - # token-authenticated, set the login cookie - handler.log.info("Accepting token-authenticated connection from %s", handler.request.remote_ip) - authenticated = True - elif one_time_token and user_token == one_time_token: - # one-time-token-authenticated, only allow this token once - handler.settings.pop('one_time_token', None) - handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip) - authenticated = True - if authenticated: - user_id = uuid.uuid4().hex - cls.set_login_cookie(handler, user_id) - # Record that we've been authenticated with a token. - # Used in should_check_origin above. - handler._token_authenticated = True + user_id = 'anonymous' # cache value for future retrievals on the same request handler._user_id = user_id return user_id + @classmethod + def get_user_token(cls, handler): + """Identify the user based on a token in the URL or Authorization header + + Returns: + - uuid if authenticated + - None if not + """ + token = handler.token + if not token: + return + # check login token from URL argument or Authorization header + user_token = cls.get_token(handler) + one_time_token = handler.one_time_token + authenticated = False + if user_token == token: + # token-authenticated, set the login cookie + handler.log.debug("Accepting token-authenticated connection from %s", handler.request.remote_ip) + authenticated = True + elif one_time_token and user_token == one_time_token: + # one-time-token-authenticated, only allow this token once + handler.settings.pop('one_time_token', None) + handler.log.info("Accepting one-time-token-authenticated connection from %s", handler.request.remote_ip) + authenticated = True + + if authenticated: + return uuid.uuid4().hex + else: + return None + @classmethod def validate_security(cls, app, ssl_options=None): diff --git a/notebook/base/handlers.py b/notebook/base/handlers.py index 5ef320b13..be0fd1ac6 100755 --- a/notebook/base/handlers.py +++ b/notebook/base/handlers.py @@ -48,7 +48,7 @@ def log(): class AuthenticatedHandler(web.RequestHandler): """A RequestHandler with an authenticated user.""" - + @property def content_security_policy(self): """The default Content-Security-Policy header @@ -95,6 +95,13 @@ class AuthenticatedHandler(web.RequestHandler): return False return not self.login_handler.should_check_origin(self) + @property + def token_authenticated(self): + """Have I been authenticated with a token?""" + if self.login_handler is None or not hasattr(self.login_handler, 'is_token_authenticated'): + return False + return self.login_handler.is_token_authenticated(self) + @property def cookie_name(self): default_cookie_name = non_alphanum.sub('-', 'username-{}'.format( @@ -288,8 +295,12 @@ class IPythonHandler(AuthenticatedHandler): host = self.request.headers.get("Host") origin = self.request.headers.get("Origin") - # If no header is provided, assume it comes from a script/curl. - # We are only concerned with cross-site browser stuff here. + # If no header is provided, let the request through. + # Origin can be None for: + # - same-origin (IE, Firefox) + # - Cross-site POST form (IE, Firefox) + # - Scripts + # The cross-site POST (XSRF) case is handled by tornado's xsrf_token if origin is None or host is None: return True @@ -313,7 +324,15 @@ class IPythonHandler(AuthenticatedHandler): self.request.path, origin, host, ) return allow - + + def check_xsrf_cookie(self): + """Bypass xsrf cookie checks when token-authenticated""" + if self.token_authenticated or self.settings.get('disable_check_xsrf', False): + # Token-authenticated requests do not need additional XSRF-check + # Servers without authentication are vulnerable to XSRF + return + return super(IPythonHandler, self).check_xsrf_cookie() + #--------------------------------------------------------------- # template rendering #--------------------------------------------------------------- @@ -340,6 +359,10 @@ class IPythonHandler(AuthenticatedHandler): sys_info=sys_info, contents_js_source=self.contents_js_source, version_hash=self.version_hash, + ignore_minified_js=self.ignore_minified_js, + xsrf_form_html=self.xsrf_form_html, + token=self.token, + xsrf_token=self.xsrf_token.decode('utf8'), **self.jinja_template_vars ) diff --git a/notebook/bundler/tests/test_bundler_api.py b/notebook/bundler/tests/test_bundler_api.py index 542044b44..d3b509e4f 100644 --- a/notebook/bundler/tests/test_bundler_api.py +++ b/notebook/bundler/tests/test_bundler_api.py @@ -4,10 +4,8 @@ # Distributed under the terms of the Modified BSD License. import io -import requests from os.path import join as pjoin -from notebook.utils import url_path_join from notebook.tests.launchnotebook import NotebookTestBase from nbformat import write from nbformat.v4 import ( @@ -45,20 +43,20 @@ class BundleAPITest(NotebookTestBase): def test_missing_bundler_arg(self): """Should respond with 400 error about missing bundler arg""" - resp = requests.get(url_path_join(self.base_url(), 'bundle', 'fake.ipynb')) + resp = self.request('GET', 'bundle/fake.ipynb') self.assertEqual(resp.status_code, 400) self.assertIn('Missing argument bundler', resp.text) def test_notebook_not_found(self): """Shoudl respond with 404 error about missing notebook""" - resp = requests.get(url_path_join(self.base_url(), 'bundle', 'fake.ipynb'), + resp = self.request('GET', 'bundle/fake.ipynb', params={'bundler': 'fake_bundler'}) self.assertEqual(resp.status_code, 404) self.assertIn('Not Found', resp.text) def test_bundler_not_enabled(self): """Should respond with 400 error about disabled bundler""" - resp = requests.get(url_path_join(self.base_url(), 'bundle', 'testnb.ipynb'), + resp = self.request('GET', 'bundle/testnb.ipynb', params={'bundler': 'fake_bundler'}) self.assertEqual(resp.status_code, 400) self.assertIn('Bundler fake_bundler not enabled', resp.text) @@ -67,7 +65,7 @@ class BundleAPITest(NotebookTestBase): """Should respond with 500 error about failure to load bundler module""" with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock: mock.return_value = {'module_name': 'fake_module'} - resp = requests.get(url_path_join(self.base_url(), 'bundle', 'testnb.ipynb'), + resp = self.request('GET', 'bundle/testnb.ipynb', params={'bundler': 'fake_bundler'}) mock.assert_called_with('fake_bundler') self.assertEqual(resp.status_code, 500) @@ -77,7 +75,7 @@ class BundleAPITest(NotebookTestBase): """Should respond with 200 and output from test bundler stub""" with patch('notebook.bundler.handlers.BundlerHandler.get_bundler') as mock: mock.return_value = {'module_name': 'notebook.bundler.tests.test_bundler_api'} - resp = requests.get(url_path_join(self.base_url(), 'bundle', 'testnb.ipynb'), + resp = self.request('GET', 'bundle/testnb.ipynb', params={'bundler': 'stub_bundler'}) mock.assert_called_with('stub_bundler') self.assertEqual(resp.status_code, 200) diff --git a/notebook/nbconvert/tests/test_nbconvert_handlers.py b/notebook/nbconvert/tests/test_nbconvert_handlers.py index 23d0aada7..8b36932c3 100644 --- a/notebook/nbconvert/tests/test_nbconvert_handlers.py +++ b/notebook/nbconvert/tests/test_nbconvert_handlers.py @@ -26,12 +26,12 @@ except ImportError: #PY2 class NbconvertAPI(object): """Wrapper for nbconvert API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None, params=None): - response = requests.request(verb, - url_path_join(self.base_url, 'nbconvert', path), + response = self.request(verb, + url_path_join('nbconvert', path), data=body, params=params, ) response.raise_for_status() @@ -84,7 +84,7 @@ class APITest(NotebookTestBase): encoding='utf-8') as f: write(nb, f, version=4) - self.nbconvert_api = NbconvertAPI(self.base_url()) + self.nbconvert_api = NbconvertAPI(self.request) @onlyif_cmds_exist('pandoc') def test_from_file(self): @@ -118,8 +118,7 @@ class APITest(NotebookTestBase): @onlyif_cmds_exist('pandoc') def test_from_post(self): - nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') - nbmodel = requests.get(nbmodel_url).json() + nbmodel = self.request('GET', 'api/contents/foo/testnb.ipynb').json() r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel) self.assertEqual(r.status_code, 200) @@ -133,8 +132,7 @@ class APITest(NotebookTestBase): @onlyif_cmds_exist('pandoc') def test_from_post_zip(self): - nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb') - nbmodel = requests.get(nbmodel_url).json() + nbmodel = self.request('GET', 'api/contents/foo/testnb.ipynb').json() r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel) self.assertIn(u'application/zip', r.headers['Content-Type']) diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 7998c14d4..f49f3c900 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -225,6 +225,8 @@ class NotebookWebApplication(web.Application): login_handler_class=jupyter_app.login_handler_class, logout_handler_class=jupyter_app.logout_handler_class, password=jupyter_app.password, + xsrf_cookies=True, + disable_check_xsrf=jupyter_app.disable_check_xsrf, # managers kernel_manager=kernel_manager, @@ -631,6 +633,22 @@ class NotebookApp(JupyterApp): """ ) + disable_check_xsrf = Bool(False, config=True, + help="""Disable cross-site-request-forgery protection + + Jupyter notebook 4.3.1 introduces protection from cross-site request forgeries, + requiring API requests to either: + + - originate from pages served by this server (validated with XSRF cookie and token), or + - authenticate with a token + + Some anonymous compute resources still desire the ability to run code, + completely without authentication. + These services can disable all authentication and security checks, + with the full knowledge of what that implies. + """ + ) + open_browser = Bool(True, config=True, help="""Whether to open in a browser after starting. The specific browser used is platform dependent and diff --git a/notebook/services/config/tests/test_config_api.py b/notebook/services/config/tests/test_config_api.py index a7778d1fa..28b931b17 100644 --- a/notebook/services/config/tests/test_config_api.py +++ b/notebook/services/config/tests/test_config_api.py @@ -11,12 +11,12 @@ from notebook.tests.launchnotebook import NotebookTestBase class ConfigAPI(object): """Wrapper for notebook API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, section, body=None): - response = requests.request(verb, - url_path_join(self.base_url, 'api/config', section), + response = self.request(verb, + url_path_join('api/config', section), data=body, ) response.raise_for_status() @@ -34,7 +34,7 @@ class ConfigAPI(object): class APITest(NotebookTestBase): """Test the config web service API""" def setUp(self): - self.config_api = ConfigAPI(self.base_url()) + self.config_api = ConfigAPI(self.request) def test_create_retrieve_config(self): sample = {'foo': 'bar', 'baz': 73} diff --git a/notebook/services/contents/tests/test_contents_api.py b/notebook/services/contents/tests/test_contents_api.py index a7471cf3d..2227a4461 100644 --- a/notebook/services/contents/tests/test_contents_api.py +++ b/notebook/services/contents/tests/test_contents_api.py @@ -50,12 +50,12 @@ def dirs_only(dir_model): class API(object): """Wrapper for contents API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None, params=None): - response = requests.request(verb, - url_path_join(self.base_url, 'api/contents', path), + response = self.request(verb, + url_path_join('api/contents', path), data=body, params=params, ) response.raise_for_status() @@ -220,7 +220,7 @@ class APITest(NotebookTestBase): self.make_blob(blobname, blob) self.addCleanup(partial(self.delete_file, blobname)) - self.api = API(self.base_url()) + self.api = API(self.request) def test_list_notebooks(self): nbs = notebooks_only(self.api.list().json()) diff --git a/notebook/services/kernels/tests/test_kernels_api.py b/notebook/services/kernels/tests/test_kernels_api.py index 380228b00..ee35b9731 100644 --- a/notebook/services/kernels/tests/test_kernels_api.py +++ b/notebook/services/kernels/tests/test_kernels_api.py @@ -10,12 +10,12 @@ from notebook.tests.launchnotebook import NotebookTestBase, assert_http_error class KernelAPI(object): """Wrapper for kernel REST API requests""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None): - response = requests.request(verb, - url_path_join(self.base_url, 'api/kernels', path), data=body) + response = self.request(verb, + url_path_join('api/kernels', path), data=body) if 400 <= response.status_code < 600: try: @@ -48,7 +48,7 @@ class KernelAPI(object): class KernelAPITest(NotebookTestBase): """Test the kernels web service API""" def setUp(self): - self.kern_api = KernelAPI(self.base_url()) + self.kern_api = KernelAPI(self.request) def tearDown(self): for k in self.kern_api.list().json(): diff --git a/notebook/services/kernelspecs/tests/test_kernelspecs_api.py b/notebook/services/kernelspecs/tests/test_kernelspecs_api.py index de65743ed..62c7b4030 100644 --- a/notebook/services/kernelspecs/tests/test_kernelspecs_api.py +++ b/notebook/services/kernelspecs/tests/test_kernelspecs_api.py @@ -26,12 +26,12 @@ some_resource = u"The very model of a modern major general" class KernelSpecAPI(object): """Wrapper for notebook API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None): - response = requests.request(verb, - url_path_join(self.base_url, path), + response = self.request(verb, + path, data=body, ) response.raise_for_status() @@ -52,7 +52,7 @@ class APITest(NotebookTestBase): def setUp(self): self.create_spec('sample') self.create_spec('sample 2') - self.ks_api = KernelSpecAPI(self.base_url()) + self.ks_api = KernelSpecAPI(self.request) def create_spec(self, name): sample_kernel_dir = pjoin(self.data_dir.name, 'kernels', name) diff --git a/notebook/services/nbconvert/tests/test_nbconvert_api.py b/notebook/services/nbconvert/tests/test_nbconvert_api.py index d33c53767..d6ef9d2ca 100644 --- a/notebook/services/nbconvert/tests/test_nbconvert_api.py +++ b/notebook/services/nbconvert/tests/test_nbconvert_api.py @@ -5,12 +5,12 @@ from notebook.tests.launchnotebook import NotebookTestBase class NbconvertAPI(object): """Wrapper for nbconvert API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None, params=None): - response = requests.request(verb, - url_path_join(self.base_url, 'api/nbconvert', path), + response = self.request(verb, + url_path_join('api/nbconvert', path), data=body, params=params, ) response.raise_for_status() @@ -21,7 +21,7 @@ class NbconvertAPI(object): class APITest(NotebookTestBase): def setUp(self): - self.nbconvert_api = NbconvertAPI(self.base_url()) + self.nbconvert_api = NbconvertAPI(self.request) def test_list_formats(self): formats = self.nbconvert_api.list_formats().json() diff --git a/notebook/services/sessions/tests/test_sessions_api.py b/notebook/services/sessions/tests/test_sessions_api.py index 1d19bc4b2..dba1417d6 100644 --- a/notebook/services/sessions/tests/test_sessions_api.py +++ b/notebook/services/sessions/tests/test_sessions_api.py @@ -18,12 +18,12 @@ from nbformat import write class SessionAPI(object): """Wrapper for notebook API calls.""" - def __init__(self, base_url): - self.base_url = base_url + def __init__(self, request): + self.request = request def _req(self, verb, path, body=None): - response = requests.request(verb, - url_path_join(self.base_url, 'api/sessions', path), data=body) + response = self.request(verb, + url_path_join('api/sessions', path), data=body) if 400 <= response.status_code < 600: try: @@ -95,7 +95,7 @@ class SessionAPITest(NotebookTestBase): nb = new_notebook() write(nb, f, version=4) - self.sess_api = SessionAPI(self.base_url()) + self.sess_api = SessionAPI(self.request) @self.addCleanup def cleanup_sessions(): @@ -154,7 +154,7 @@ class SessionAPITest(NotebookTestBase): def test_create_with_kernel_id(self): # create a new kernel - r = requests.post(url_path_join(self.base_url(), 'api/kernels')) + r = self.request('POST', 'api/kernels') r.raise_for_status() kernel = r.json() @@ -222,7 +222,7 @@ class SessionAPITest(NotebookTestBase): self.assertNotEqual(after['kernel']['id'], before['kernel']['id']) # check kernel list, to be sure previous kernel was cleaned up - r = requests.get(url_path_join(self.base_url(), 'api/kernels')) + r = self.request('GET', 'api/kernels') r.raise_for_status() kernel_list = r.json() self.assertEqual(kernel_list, [after['kernel']]) @@ -232,7 +232,7 @@ class SessionAPITest(NotebookTestBase): sid = before['id'] # create a new kernel - r = requests.post(url_path_join(self.base_url(), 'api/kernels')) + r = self.request('POST', 'api/kernels') r.raise_for_status() kernel = r.json() @@ -245,7 +245,7 @@ class SessionAPITest(NotebookTestBase): self.assertEqual(after['kernel']['id'], kernel['id']) # check kernel list, to be sure previous kernel was cleaned up - r = requests.get(url_path_join(self.base_url(), 'api/kernels')) + r = self.request('GET', 'api/kernels') r.raise_for_status() kernel_list = r.json() self.assertEqual(kernel_list, [kernel]) diff --git a/notebook/static/base/js/utils.js b/notebook/static/base/js/utils.js index 46911233b..48803365b 100644 --- a/notebook/static/base/js/utils.js +++ b/notebook/static/base/js/utils.js @@ -603,7 +603,7 @@ define([ var to_absolute_cursor_pos = function (cm, cursor) { console.warn('`utils.to_absolute_cursor_pos(cm, pos)` is deprecated. Use `cm.indexFromPos(cursor)`'); - return cm.indexFromPos(cusrsor); + return cm.indexFromPos(cursor); }; var from_absolute_cursor_pos = function (cm, cursor_pos) { @@ -752,6 +752,35 @@ define([ return wrapped_error; }; + var ajax = function (url, settings) { + // like $.ajax, but ensure Authorization header is set + settings = _add_auth_header(settings); + return $.ajax(url, settings); + }; + + var _get_cookie = function (name) { + // from tornado docs: http://www.tornadoweb.org/en/stable/guide/security.html + var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); + return r ? r[1] : undefined; + } + + var _add_auth_header = function (settings) { + /** + * Adds auth header to jquery ajax settings + */ + settings = settings || {}; + if (!settings.headers) { + settings.headers = {}; + } + if (!settings.headers.Authorization) { + var xsrf_token = _get_cookie('_xsrf'); + if (xsrf_token) { + settings.headers['X-XSRFToken'] = xsrf_token; + } + } + return settings; + }; + var promising_ajax = function(url, settings) { /** * Like $.ajax, but returning an ES6 promise. success and error settings @@ -766,7 +795,7 @@ define([ log_ajax_error(jqXHR, status, error); reject(wrap_ajax_error(jqXHR, status, error)); }; - $.ajax(url, settings); + ajax(url, settings); }); }; @@ -1010,10 +1039,11 @@ define([ is_or_has : is_or_has, is_focused : is_focused, mergeopt: mergeopt, - ajax_error_msg : ajax_error_msg, - log_ajax_error : log_ajax_error, requireCodeMirrorMode : requireCodeMirrorMode, XHR_ERROR : XHR_ERROR, + ajax : ajax, + ajax_error_msg : ajax_error_msg, + log_ajax_error : log_ajax_error, wrap_ajax_error : wrap_ajax_error, promising_ajax : promising_ajax, WrappedError: WrappedError, diff --git a/notebook/static/services/kernels/kernel.js b/notebook/static/services/kernels/kernel.js index 1b609dd94..a5942d87d 100644 --- a/notebook/static/services/kernels/kernel.js +++ b/notebook/static/services/kernels/kernel.js @@ -152,7 +152,7 @@ define([ * @param {function} [error] - functon executed on ajax error */ Kernel.prototype.list = function (success, error) { - $.ajax(this.kernel_service_url, { + utils.ajax(this.kernel_service_url, { processData: false, cache: false, type: "GET", @@ -194,7 +194,7 @@ define([ } }; - $.ajax(url, { + utils.ajax(url, { processData: false, cache: false, type: "POST", @@ -218,7 +218,7 @@ define([ * @param {function} [error] - functon executed on ajax error */ Kernel.prototype.get_info = function (success, error) { - $.ajax(this.kernel_url, { + utils.ajax(this.kernel_url, { processData: false, cache: false, type: "GET", @@ -244,7 +244,7 @@ define([ Kernel.prototype.kill = function (success, error) { this.events.trigger('kernel_killed.Kernel', {kernel: this}); this._kernel_dead(); - $.ajax(this.kernel_url, { + utils.ajax(this.kernel_url, { processData: false, cache: false, type: "DELETE", @@ -278,7 +278,7 @@ define([ }; var url = utils.url_path_join(this.kernel_url, 'interrupt'); - $.ajax(url, { + utils.ajax(url, { processData: false, cache: false, type: "POST", @@ -323,7 +323,7 @@ define([ }; var url = utils.url_path_join(this.kernel_url, 'restart'); - $.ajax(url, { + utils.ajax(url, { processData: false, cache: false, type: "POST", diff --git a/notebook/static/services/sessions/session.js b/notebook/static/services/sessions/session.js index 4224c0ec1..ade904f1f 100644 --- a/notebook/static/services/sessions/session.js +++ b/notebook/static/services/sessions/session.js @@ -76,7 +76,7 @@ define([ * @param {function} [error] - functon executed on ajax error */ Session.prototype.list = function (success, error) { - $.ajax(this.session_service_url, { + utils.ajax(this.session_service_url, { processData: false, cache: false, type: "GET", @@ -117,7 +117,7 @@ define([ } }; - $.ajax(this.session_service_url, { + utils.ajax(this.session_service_url, { processData: false, cache: false, type: "POST", @@ -139,7 +139,7 @@ define([ * @param {function} [error] - functon executed on ajax error */ Session.prototype.get_info = function (success, error) { - $.ajax(this.session_url, { + utils.ajax(this.session_url, { processData: false, cache: false, type: "GET", @@ -165,7 +165,7 @@ define([ this.notebook_model.path = path; } - $.ajax(this.session_url, { + utils.ajax(this.session_url, { processData: false, cache: false, type: "PATCH", @@ -192,7 +192,7 @@ define([ this.kernel._kernel_dead(); } - $.ajax(this.session_url, { + utils.ajax(this.session_url, { processData: false, cache: false, type: "DELETE", diff --git a/notebook/static/tree/js/notebooklist.js b/notebook/static/tree/js/notebooklist.js index 4019e435a..dfe08b3e5 100644 --- a/notebook/static/tree/js/notebooklist.js +++ b/notebook/static/tree/js/notebooklist.js @@ -734,7 +734,7 @@ define([ 'api/sessions', encodeURIComponent(session.id) ); - $.ajax(url, settings); + utils.ajax(url, settings); } }; diff --git a/notebook/static/tree/js/sessionlist.js b/notebook/static/tree/js/sessionlist.js index 001eb9f04..3c664193b 100644 --- a/notebook/static/tree/js/sessionlist.js +++ b/notebook/static/tree/js/sessionlist.js @@ -62,7 +62,7 @@ define([ error : utils.log_ajax_error, }; var url = utils.url_path_join(this.base_url, 'api/sessions'); - $.ajax(url, settings); + utils.ajax(url, settings); }; SesssionList.prototype.sessions_loaded = function(data){ diff --git a/notebook/static/tree/js/terminallist.js b/notebook/static/tree/js/terminallist.js index c8e8fcf1c..975aad906 100644 --- a/notebook/static/tree/js/terminallist.js +++ b/notebook/static/tree/js/terminallist.js @@ -60,12 +60,12 @@ define([ this.base_url, 'api/terminals' ); - $.ajax(url, settings); + utils.ajax(url, settings); }; TerminalList.prototype.load_terminals = function() { var url = utils.url_path_join(this.base_url, 'api/terminals'); - $.ajax(url, { + utils.ajax(url, { type: "GET", cache: false, dataType: "json", @@ -113,7 +113,7 @@ define([ }; var url = utils.url_path_join(that.base_url, 'api/terminals', utils.encode_uri_components(name)); - $.ajax(url, settings); + utils.ajax(url, settings); return false; }); item.find(".item_buttons").text("").append(shutdown_button); diff --git a/notebook/templates/login.html b/notebook/templates/login.html index df394db32..61aafaf17 100644 --- a/notebook/templates/login.html +++ b/notebook/templates/login.html @@ -22,6 +22,7 @@
diff --git a/notebook/templates/page.html b/notebook/templates/page.html index e96aac227..5fb920a5d 100644 --- a/notebook/templates/page.html +++ b/notebook/templates/page.html @@ -197,7 +197,13 @@ - +