Show More
@@ -0,0 +1,4 b'' | |||||
|
1 | # URI for the CSP Report. Included here to prevent a cyclic dependency. | |||
|
2 | # csp_report_uri is needed both by the BaseHandler (for setting the report-uri) | |||
|
3 | # and by the CSPReportHandler (which depends on the BaseHandler). | |||
|
4 | csp_report_uri = r"/api/security/csp-report" |
@@ -0,0 +1,23 b'' | |||||
|
1 | """Tornado handlers for security logging.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | from tornado import gen, web | |||
|
7 | ||||
|
8 | from ...base.handlers import IPythonHandler, json_errors | |||
|
9 | from . import csp_report_uri | |||
|
10 | ||||
|
11 | class CSPReportHandler(IPythonHandler): | |||
|
12 | '''Accepts a content security policy violation report''' | |||
|
13 | @web.authenticated | |||
|
14 | @json_errors | |||
|
15 | def post(self): | |||
|
16 | '''Log a content security policy violation report''' | |||
|
17 | csp_report = self.get_json_body() | |||
|
18 | self.log.warn("Content security violation: %s", | |||
|
19 | self.request.body.decode('utf8', 'replace')) | |||
|
20 | ||||
|
21 | default_handlers = [ | |||
|
22 | (csp_report_uri, CSPReportHandler) | |||
|
23 | ] |
@@ -6,6 +6,7 b'' | |||||
6 |
|
6 | |||
7 | from __future__ import print_function |
|
7 | from __future__ import print_function | |
8 |
|
8 | |||
|
9 | import json | |||
9 | import logging |
|
10 | import logging | |
10 | import os |
|
11 | import os | |
11 | import re |
|
12 | import re | |
@@ -535,8 +536,17 b' class Application(SingletonConfigurable):' | |||||
535 | def load_config_file(self, filename, path=None): |
|
536 | def load_config_file(self, filename, path=None): | |
536 | """Load config files by filename and path.""" |
|
537 | """Load config files by filename and path.""" | |
537 | filename, ext = os.path.splitext(filename) |
|
538 | filename, ext = os.path.splitext(filename) | |
|
539 | loaded = [] | |||
538 | for config in self._load_config_files(filename, path=path, log=self.log): |
|
540 | for config in self._load_config_files(filename, path=path, log=self.log): | |
|
541 | loaded.append(config) | |||
539 | self.update_config(config) |
|
542 | self.update_config(config) | |
|
543 | if len(loaded) > 1: | |||
|
544 | collisions = loaded[0].collisions(loaded[1]) | |||
|
545 | if collisions: | |||
|
546 | self.log.warn("Collisions detected in {0}.py and {0}.json config files." | |||
|
547 | " {0}.json has higher priority: {1}".format( | |||
|
548 | filename, json.dumps(collisions, indent=2), | |||
|
549 | )) | |||
540 |
|
550 | |||
541 |
|
551 | |||
542 | def generate_config_file(self): |
|
552 | def generate_config_file(self): |
@@ -193,7 +193,27 b' class Config(dict):' | |||||
193 | to_update[k] = copy.deepcopy(v) |
|
193 | to_update[k] = copy.deepcopy(v) | |
194 |
|
194 | |||
195 | self.update(to_update) |
|
195 | self.update(to_update) | |
196 |
|
196 | |||
|
197 | def collisions(self, other): | |||
|
198 | """Check for collisions between two config objects. | |||
|
199 | ||||
|
200 | Returns a dict of the form {"Class": {"trait": "collision message"}}`, | |||
|
201 | indicating which values have been ignored. | |||
|
202 | ||||
|
203 | An empty dict indicates no collisions. | |||
|
204 | """ | |||
|
205 | collisions = {} | |||
|
206 | for section in self: | |||
|
207 | if section not in other: | |||
|
208 | continue | |||
|
209 | mine = self[section] | |||
|
210 | theirs = other[section] | |||
|
211 | for key in mine: | |||
|
212 | if key in theirs and mine[key] != theirs[key]: | |||
|
213 | collisions.setdefault(section, {}) | |||
|
214 | collisions[section][key] = "%r ignored, using %r" % (mine[key], theirs[key]) | |||
|
215 | return collisions | |||
|
216 | ||||
197 | def __contains__(self, key): |
|
217 | def __contains__(self, key): | |
198 | # allow nested contains of the form `"Section.key" in config` |
|
218 | # allow nested contains of the form `"Section.key" in config` | |
199 | if '.' in key: |
|
219 | if '.' in key: | |
@@ -565,7 +585,7 b' class KeyValueConfigLoader(CommandLineConfigLoader):' | |||||
565 |
|
585 | |||
566 |
|
586 | |||
567 | def _decode_argv(self, argv, enc=None): |
|
587 | def _decode_argv(self, argv, enc=None): | |
568 | """decode argv if bytes, using stin.encoding, falling back on default enc""" |
|
588 | """decode argv if bytes, using stdin.encoding, falling back on default enc""" | |
569 | uargv = [] |
|
589 | uargv = [] | |
570 | if enc is None: |
|
590 | if enc is None: | |
571 | enc = DEFAULT_ENCODING |
|
591 | enc = DEFAULT_ENCODING |
@@ -1,28 +1,12 b'' | |||||
1 | # encoding: utf-8 |
|
1 | # encoding: utf-8 | |
2 | """ |
|
2 | """Tests for IPython.config.loader""" | |
3 | Tests for IPython.config.loader |
|
|||
4 |
|
||||
5 | Authors: |
|
|||
6 |
|
||||
7 | * Brian Granger |
|
|||
8 | * Fernando Perez (design help) |
|
|||
9 | """ |
|
|||
10 |
|
3 | |||
11 | #----------------------------------------------------------------------------- |
|
4 | # Copyright (c) IPython Development Team. | |
12 | # Copyright (C) 2008 The IPython Development Team |
|
5 | # Distributed under the terms of the Modified BSD License. | |
13 | # |
|
|||
14 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
15 | # the file COPYING, distributed as part of this software. |
|
|||
16 | #----------------------------------------------------------------------------- |
|
|||
17 |
|
||||
18 | #----------------------------------------------------------------------------- |
|
|||
19 | # Imports |
|
|||
20 | #----------------------------------------------------------------------------- |
|
|||
21 |
|
6 | |||
22 | import os |
|
7 | import os | |
23 | import pickle |
|
8 | import pickle | |
24 | import sys |
|
9 | import sys | |
25 | import json |
|
|||
26 |
|
10 | |||
27 | from tempfile import mkstemp |
|
11 | from tempfile import mkstemp | |
28 | from unittest import TestCase |
|
12 | from unittest import TestCase | |
@@ -43,10 +27,6 b' from IPython.config.loader import (' | |||||
43 | ConfigError, |
|
27 | ConfigError, | |
44 | ) |
|
28 | ) | |
45 |
|
29 | |||
46 | #----------------------------------------------------------------------------- |
|
|||
47 | # Actual tests |
|
|||
48 | #----------------------------------------------------------------------------- |
|
|||
49 |
|
||||
50 |
|
30 | |||
51 | pyfile = """ |
|
31 | pyfile = """ | |
52 | c = get_config() |
|
32 | c = get_config() | |
@@ -117,6 +97,34 b' class TestFileCL(TestCase):' | |||||
117 | cl = JSONFileConfigLoader(fname, log=log) |
|
97 | cl = JSONFileConfigLoader(fname, log=log) | |
118 | config = cl.load_config() |
|
98 | config = cl.load_config() | |
119 | self._check_conf(config) |
|
99 | self._check_conf(config) | |
|
100 | ||||
|
101 | def test_collision(self): | |||
|
102 | a = Config() | |||
|
103 | b = Config() | |||
|
104 | self.assertEqual(a.collisions(b), {}) | |||
|
105 | a.A.trait1 = 1 | |||
|
106 | b.A.trait2 = 2 | |||
|
107 | self.assertEqual(a.collisions(b), {}) | |||
|
108 | b.A.trait1 = 1 | |||
|
109 | self.assertEqual(a.collisions(b), {}) | |||
|
110 | b.A.trait1 = 0 | |||
|
111 | self.assertEqual(a.collisions(b), { | |||
|
112 | 'A': { | |||
|
113 | 'trait1': "1 ignored, using 0", | |||
|
114 | } | |||
|
115 | }) | |||
|
116 | self.assertEqual(b.collisions(a), { | |||
|
117 | 'A': { | |||
|
118 | 'trait1': "0 ignored, using 1", | |||
|
119 | } | |||
|
120 | }) | |||
|
121 | a.A.trait2 = 3 | |||
|
122 | self.assertEqual(b.collisions(a), { | |||
|
123 | 'A': { | |||
|
124 | 'trait1': "0 ignored, using 1", | |||
|
125 | 'trait2': "2 ignored, using 3", | |||
|
126 | } | |||
|
127 | }) | |||
120 |
|
128 | |||
121 | def test_v2raise(self): |
|
129 | def test_v2raise(self): | |
122 | fd, fname = mkstemp('.json') |
|
130 | fd, fname = mkstemp('.json') |
@@ -32,6 +32,8 b' from IPython.utils.path import filefind' | |||||
32 | from IPython.utils.py3compat import string_types |
|
32 | from IPython.utils.py3compat import string_types | |
33 | from IPython.html.utils import is_hidden, url_path_join, url_escape |
|
33 | from IPython.html.utils import is_hidden, url_path_join, url_escape | |
34 |
|
34 | |||
|
35 | from IPython.html.services.security import csp_report_uri | |||
|
36 | ||||
35 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
36 | # Top-level handlers |
|
38 | # Top-level handlers | |
37 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
@@ -45,17 +47,22 b' class AuthenticatedHandler(web.RequestHandler):' | |||||
45 | def set_default_headers(self): |
|
47 | def set_default_headers(self): | |
46 | headers = self.settings.get('headers', {}) |
|
48 | headers = self.settings.get('headers', {}) | |
47 |
|
49 | |||
48 |
if " |
|
50 | if "Content-Security-Policy" not in headers: | |
49 | headers["X-Frame-Options"] = "SAMEORIGIN" |
|
51 | headers["Content-Security-Policy"] = ( | |
|
52 | "frame-ancestors 'self'; " | |||
|
53 | # Make sure the report-uri is relative to the base_url | |||
|
54 | "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" | |||
|
55 | ) | |||
50 |
|
56 | |||
|
57 | # Allow for overriding headers | |||
51 | for header_name,value in headers.items() : |
|
58 | for header_name,value in headers.items() : | |
52 | try: |
|
59 | try: | |
53 | self.set_header(header_name, value) |
|
60 | self.set_header(header_name, value) | |
54 | except Exception: |
|
61 | except Exception as e: | |
55 | # tornado raise Exception (not a subclass) |
|
62 | # tornado raise Exception (not a subclass) | |
56 | # if method is unsupported (websocket and Access-Control-Allow-Origin |
|
63 | # if method is unsupported (websocket and Access-Control-Allow-Origin | |
57 | # for example, so just ignore) |
|
64 | # for example, so just ignore) | |
58 |
|
|
65 | self.log.debug(e) | |
59 |
|
66 | |||
60 | def clear_login_cookie(self): |
|
67 | def clear_login_cookie(self): | |
61 | self.clear_cookie(self.cookie_name) |
|
68 | self.clear_cookie(self.cookie_name) |
@@ -225,7 +225,7 b' class NotebookWebApplication(web.Application):' | |||||
225 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
225 | handlers.extend(load_handlers('services.sessions.handlers')) | |
226 | handlers.extend(load_handlers('services.nbconvert.handlers')) |
|
226 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |
227 | handlers.extend(load_handlers('services.kernelspecs.handlers')) |
|
227 | handlers.extend(load_handlers('services.kernelspecs.handlers')) | |
228 |
|
228 | handlers.extend(load_handlers('services.security.handlers')) | ||
229 | handlers.append( |
|
229 | handlers.append( | |
230 | (r"/nbextensions/(.*)", FileFindHandler, { |
|
230 | (r"/nbextensions/(.*)", FileFindHandler, { | |
231 | 'path': settings['nbextensions_path'], |
|
231 | 'path': settings['nbextensions_path'], |
@@ -0,0 +1,1 b'' | |||||
|
1 | from .manager import ConfigManager |
@@ -65,7 +65,10 b' class KernelAPITest(NotebookTestBase):' | |||||
65 | self.assertEqual(r.status_code, 201) |
|
65 | self.assertEqual(r.status_code, 201) | |
66 | self.assertIsInstance(kern1, dict) |
|
66 | self.assertIsInstance(kern1, dict) | |
67 |
|
67 | |||
68 |
self.assertEqual(r.headers[' |
|
68 | self.assertEqual(r.headers['Content-Security-Policy'], ( | |
|
69 | "frame-ancestors 'self'; " | |||
|
70 | "report-uri /api/security/csp-report;" | |||
|
71 | )) | |||
69 |
|
72 | |||
70 | def test_main_kernel_handler(self): |
|
73 | def test_main_kernel_handler(self): | |
71 | # POST request |
|
74 | # POST request | |
@@ -75,7 +78,10 b' class KernelAPITest(NotebookTestBase):' | |||||
75 | self.assertEqual(r.status_code, 201) |
|
78 | self.assertEqual(r.status_code, 201) | |
76 | self.assertIsInstance(kern1, dict) |
|
79 | self.assertIsInstance(kern1, dict) | |
77 |
|
80 | |||
78 |
self.assertEqual(r.headers[' |
|
81 | self.assertEqual(r.headers['Content-Security-Policy'], ( | |
|
82 | "frame-ancestors 'self'; " | |||
|
83 | "report-uri /api/security/csp-report;" | |||
|
84 | )) | |||
79 |
|
85 | |||
80 | # GET request |
|
86 | # GET request | |
81 | r = self.kern_api.list() |
|
87 | r = self.kern_api.list() |
@@ -286,16 +286,16 b' define([' | |||||
286 | }); |
|
286 | }); | |
287 | }; |
|
287 | }; | |
288 |
|
288 | |||
289 | /** |
|
|||
290 | * POST /api/kernels/[:kernel_id]/restart |
|
|||
291 | * |
|
|||
292 | * Restart the kernel. |
|
|||
293 | * |
|
|||
294 | * @function interrupt |
|
|||
295 | * @param {function} [success] - function executed on ajax success |
|
|||
296 | * @param {function} [error] - functon executed on ajax error |
|
|||
297 | */ |
|
|||
298 | Kernel.prototype.restart = function (success, error) { |
|
289 | Kernel.prototype.restart = function (success, error) { | |
|
290 | /** | |||
|
291 | * POST /api/kernels/[:kernel_id]/restart | |||
|
292 | * | |||
|
293 | * Restart the kernel. | |||
|
294 | * | |||
|
295 | * @function interrupt | |||
|
296 | * @param {function} [success] - function executed on ajax success | |||
|
297 | * @param {function} [error] - functon executed on ajax error | |||
|
298 | */ | |||
299 | this.events.trigger('kernel_restarting.Kernel', {kernel: this}); |
|
299 | this.events.trigger('kernel_restarting.Kernel', {kernel: this}); | |
300 | this.stop_channels(); |
|
300 | this.stop_channels(); | |
301 |
|
301 | |||
@@ -327,14 +327,14 b' define([' | |||||
327 | }); |
|
327 | }); | |
328 | }; |
|
328 | }; | |
329 |
|
329 | |||
330 | /** |
|
|||
331 | * Reconnect to a disconnected kernel. This is not actually a |
|
|||
332 | * standard HTTP request, but useful function nonetheless for |
|
|||
333 | * reconnecting to the kernel if the connection is somehow lost. |
|
|||
334 | * |
|
|||
335 | * @function reconnect |
|
|||
336 | */ |
|
|||
337 | Kernel.prototype.reconnect = function () { |
|
330 | Kernel.prototype.reconnect = function () { | |
|
331 | /** | |||
|
332 | * Reconnect to a disconnected kernel. This is not actually a | |||
|
333 | * standard HTTP request, but useful function nonetheless for | |||
|
334 | * reconnecting to the kernel if the connection is somehow lost. | |||
|
335 | * | |||
|
336 | * @function reconnect | |||
|
337 | */ | |||
338 | if (this.is_connected()) { |
|
338 | if (this.is_connected()) { | |
339 | return; |
|
339 | return; | |
340 | } |
|
340 | } | |
@@ -346,15 +346,15 b' define([' | |||||
346 | this.start_channels(); |
|
346 | this.start_channels(); | |
347 | }; |
|
347 | }; | |
348 |
|
348 | |||
349 | /** |
|
|||
350 | * Handle a successful AJAX request by updating the kernel id and |
|
|||
351 | * name from the response, and then optionally calling a provided |
|
|||
352 | * callback. |
|
|||
353 | * |
|
|||
354 | * @function _on_success |
|
|||
355 | * @param {function} success - callback |
|
|||
356 | */ |
|
|||
357 | Kernel.prototype._on_success = function (success) { |
|
349 | Kernel.prototype._on_success = function (success) { | |
|
350 | /** | |||
|
351 | * Handle a successful AJAX request by updating the kernel id and | |||
|
352 | * name from the response, and then optionally calling a provided | |||
|
353 | * callback. | |||
|
354 | * | |||
|
355 | * @function _on_success | |||
|
356 | * @param {function} success - callback | |||
|
357 | */ | |||
358 | var that = this; |
|
358 | var that = this; | |
359 | return function (data, status, xhr) { |
|
359 | return function (data, status, xhr) { | |
360 | if (data) { |
|
360 | if (data) { | |
@@ -368,14 +368,14 b' define([' | |||||
368 | }; |
|
368 | }; | |
369 | }; |
|
369 | }; | |
370 |
|
370 | |||
371 | /** |
|
|||
372 | * Handle a failed AJAX request by logging the error message, and |
|
|||
373 | * then optionally calling a provided callback. |
|
|||
374 | * |
|
|||
375 | * @function _on_error |
|
|||
376 | * @param {function} error - callback |
|
|||
377 | */ |
|
|||
378 | Kernel.prototype._on_error = function (error) { |
|
371 | Kernel.prototype._on_error = function (error) { | |
|
372 | /** | |||
|
373 | * Handle a failed AJAX request by logging the error message, and | |||
|
374 | * then optionally calling a provided callback. | |||
|
375 | * | |||
|
376 | * @function _on_error | |||
|
377 | * @param {function} error - callback | |||
|
378 | */ | |||
379 | return function (xhr, status, err) { |
|
379 | return function (xhr, status, err) { | |
380 | utils.log_ajax_error(xhr, status, err); |
|
380 | utils.log_ajax_error(xhr, status, err); | |
381 | if (error) { |
|
381 | if (error) { | |
@@ -384,27 +384,27 b' define([' | |||||
384 | }; |
|
384 | }; | |
385 | }; |
|
385 | }; | |
386 |
|
386 | |||
387 | /** |
|
|||
388 | * Perform necessary tasks once the kernel has been started, |
|
|||
389 | * including actually connecting to the kernel. |
|
|||
390 | * |
|
|||
391 | * @function _kernel_created |
|
|||
392 | * @param {Object} data - information about the kernel including id |
|
|||
393 | */ |
|
|||
394 | Kernel.prototype._kernel_created = function (data) { |
|
387 | Kernel.prototype._kernel_created = function (data) { | |
|
388 | /** | |||
|
389 | * Perform necessary tasks once the kernel has been started, | |||
|
390 | * including actually connecting to the kernel. | |||
|
391 | * | |||
|
392 | * @function _kernel_created | |||
|
393 | * @param {Object} data - information about the kernel including id | |||
|
394 | */ | |||
395 | this.id = data.id; |
|
395 | this.id = data.id; | |
396 | this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id); |
|
396 | this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id); | |
397 | this.start_channels(); |
|
397 | this.start_channels(); | |
398 | }; |
|
398 | }; | |
399 |
|
399 | |||
400 | /** |
|
|||
401 | * Perform necessary tasks once the connection to the kernel has |
|
|||
402 | * been established. This includes requesting information about |
|
|||
403 | * the kernel. |
|
|||
404 | * |
|
|||
405 | * @function _kernel_connected |
|
|||
406 | */ |
|
|||
407 | Kernel.prototype._kernel_connected = function () { |
|
400 | Kernel.prototype._kernel_connected = function () { | |
|
401 | /** | |||
|
402 | * Perform necessary tasks once the connection to the kernel has | |||
|
403 | * been established. This includes requesting information about | |||
|
404 | * the kernel. | |||
|
405 | * | |||
|
406 | * @function _kernel_connected | |||
|
407 | */ | |||
408 | this.events.trigger('kernel_connected.Kernel', {kernel: this}); |
|
408 | this.events.trigger('kernel_connected.Kernel', {kernel: this}); | |
409 | this.events.trigger('kernel_starting.Kernel', {kernel: this}); |
|
409 | this.events.trigger('kernel_starting.Kernel', {kernel: this}); | |
410 | // get kernel info so we know what state the kernel is in |
|
410 | // get kernel info so we know what state the kernel is in | |
@@ -415,24 +415,24 b' define([' | |||||
415 | }); |
|
415 | }); | |
416 | }; |
|
416 | }; | |
417 |
|
417 | |||
418 | /** |
|
|||
419 | * Perform necessary tasks after the kernel has died. This closing |
|
|||
420 | * communication channels to the kernel if they are still somehow |
|
|||
421 | * open. |
|
|||
422 | * |
|
|||
423 | * @function _kernel_dead |
|
|||
424 | */ |
|
|||
425 | Kernel.prototype._kernel_dead = function () { |
|
418 | Kernel.prototype._kernel_dead = function () { | |
|
419 | /** | |||
|
420 | * Perform necessary tasks after the kernel has died. This closing | |||
|
421 | * communication channels to the kernel if they are still somehow | |||
|
422 | * open. | |||
|
423 | * | |||
|
424 | * @function _kernel_dead | |||
|
425 | */ | |||
426 | this.stop_channels(); |
|
426 | this.stop_channels(); | |
427 | }; |
|
427 | }; | |
428 |
|
428 | |||
429 | /** |
|
|||
430 | * Start the `shell`and `iopub` channels. |
|
|||
431 | * Will stop and restart them if they already exist. |
|
|||
432 | * |
|
|||
433 | * @function start_channels |
|
|||
434 | */ |
|
|||
435 | Kernel.prototype.start_channels = function () { |
|
429 | Kernel.prototype.start_channels = function () { | |
|
430 | /** | |||
|
431 | * Start the `shell`and `iopub` channels. | |||
|
432 | * Will stop and restart them if they already exist. | |||
|
433 | * | |||
|
434 | * @function start_channels | |||
|
435 | */ | |||
436 | var that = this; |
|
436 | var that = this; | |
437 | this.stop_channels(); |
|
437 | this.stop_channels(); | |
438 | var ws_host_url = this.ws_url + this.kernel_url; |
|
438 | var ws_host_url = this.ws_url + this.kernel_url; | |
@@ -506,29 +506,29 b' define([' | |||||
506 | this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this); |
|
506 | this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this); | |
507 | }; |
|
507 | }; | |
508 |
|
508 | |||
509 | /** |
|
|||
510 | * Handle a websocket entering the open state, |
|
|||
511 | * signaling that the kernel is connected when all channels are open. |
|
|||
512 | * |
|
|||
513 | * @function _ws_opened |
|
|||
514 | */ |
|
|||
515 | Kernel.prototype._ws_opened = function (evt) { |
|
509 | Kernel.prototype._ws_opened = function (evt) { | |
|
510 | /** | |||
|
511 | * Handle a websocket entering the open state, | |||
|
512 | * signaling that the kernel is connected when all channels are open. | |||
|
513 | * | |||
|
514 | * @function _ws_opened | |||
|
515 | */ | |||
516 | if (this.is_connected()) { |
|
516 | if (this.is_connected()) { | |
517 | // all events ready, trigger started event. |
|
517 | // all events ready, trigger started event. | |
518 | this._kernel_connected(); |
|
518 | this._kernel_connected(); | |
519 | } |
|
519 | } | |
520 | }; |
|
520 | }; | |
521 |
|
521 | |||
522 | /** |
|
|||
523 | * Handle a websocket entering the closed state. This closes the |
|
|||
524 | * other communication channels if they are open. If the websocket |
|
|||
525 | * was not closed due to an error, try to reconnect to the kernel. |
|
|||
526 | * |
|
|||
527 | * @function _ws_closed |
|
|||
528 | * @param {string} ws_url - the websocket url |
|
|||
529 | * @param {bool} error - whether the connection was closed due to an error |
|
|||
530 | */ |
|
|||
531 | Kernel.prototype._ws_closed = function(ws_url, error) { |
|
522 | Kernel.prototype._ws_closed = function(ws_url, error) { | |
|
523 | /** | |||
|
524 | * Handle a websocket entering the closed state. This closes the | |||
|
525 | * other communication channels if they are open. If the websocket | |||
|
526 | * was not closed due to an error, try to reconnect to the kernel. | |||
|
527 | * | |||
|
528 | * @function _ws_closed | |||
|
529 | * @param {string} ws_url - the websocket url | |||
|
530 | * @param {bool} error - whether the connection was closed due to an error | |||
|
531 | */ | |||
532 | this.stop_channels(); |
|
532 | this.stop_channels(); | |
533 |
|
533 | |||
534 | this.events.trigger('kernel_disconnected.Kernel', {kernel: this}); |
|
534 | this.events.trigger('kernel_disconnected.Kernel', {kernel: this}); | |
@@ -555,13 +555,13 b' define([' | |||||
555 | } |
|
555 | } | |
556 | }; |
|
556 | }; | |
557 |
|
557 | |||
558 | /** |
|
|||
559 | * Close the websocket channels. After successful close, the value |
|
|||
560 | * in `this.channels[channel_name]` will be null. |
|
|||
561 | * |
|
|||
562 | * @function stop_channels |
|
|||
563 | */ |
|
|||
564 | Kernel.prototype.stop_channels = function () { |
|
558 | Kernel.prototype.stop_channels = function () { | |
|
559 | /** | |||
|
560 | * Close the websocket channels. After successful close, the value | |||
|
561 | * in `this.channels[channel_name]` will be null. | |||
|
562 | * | |||
|
563 | * @function stop_channels | |||
|
564 | */ | |||
565 | var that = this; |
|
565 | var that = this; | |
566 | var close = function (c) { |
|
566 | var close = function (c) { | |
567 | return function () { |
|
567 | return function () { | |
@@ -582,15 +582,15 b' define([' | |||||
582 | } |
|
582 | } | |
583 | }; |
|
583 | }; | |
584 |
|
584 | |||
585 | /** |
|
|||
586 | * Check whether there is a connection to the kernel. This |
|
|||
587 | * function only returns true if all channel objects have been |
|
|||
588 | * created and have a state of WebSocket.OPEN. |
|
|||
589 | * |
|
|||
590 | * @function is_connected |
|
|||
591 | * @returns {bool} - whether there is a connection |
|
|||
592 | */ |
|
|||
593 | Kernel.prototype.is_connected = function () { |
|
585 | Kernel.prototype.is_connected = function () { | |
|
586 | /** | |||
|
587 | * Check whether there is a connection to the kernel. This | |||
|
588 | * function only returns true if all channel objects have been | |||
|
589 | * created and have a state of WebSocket.OPEN. | |||
|
590 | * | |||
|
591 | * @function is_connected | |||
|
592 | * @returns {bool} - whether there is a connection | |||
|
593 | */ | |||
594 | for (var c in this.channels) { |
|
594 | for (var c in this.channels) { | |
595 | // if any channel is not ready, then we're not connected |
|
595 | // if any channel is not ready, then we're not connected | |
596 | if (this.channels[c] === null) { |
|
596 | if (this.channels[c] === null) { | |
@@ -603,15 +603,15 b' define([' | |||||
603 | return true; |
|
603 | return true; | |
604 | }; |
|
604 | }; | |
605 |
|
605 | |||
606 | /** |
|
|||
607 | * Check whether the connection to the kernel has been completely |
|
|||
608 | * severed. This function only returns true if all channel objects |
|
|||
609 | * are null. |
|
|||
610 | * |
|
|||
611 | * @function is_fully_disconnected |
|
|||
612 | * @returns {bool} - whether the kernel is fully disconnected |
|
|||
613 | */ |
|
|||
614 | Kernel.prototype.is_fully_disconnected = function () { |
|
606 | Kernel.prototype.is_fully_disconnected = function () { | |
|
607 | /** | |||
|
608 | * Check whether the connection to the kernel has been completely | |||
|
609 | * severed. This function only returns true if all channel objects | |||
|
610 | * are null. | |||
|
611 | * | |||
|
612 | * @function is_fully_disconnected | |||
|
613 | * @returns {bool} - whether the kernel is fully disconnected | |||
|
614 | */ | |||
615 | for (var c in this.channels) { |
|
615 | for (var c in this.channels) { | |
616 | if (this.channels[c] === null) { |
|
616 | if (this.channels[c] === null) { | |
617 | return true; |
|
617 | return true; | |
@@ -620,12 +620,12 b' define([' | |||||
620 | return false; |
|
620 | return false; | |
621 | }; |
|
621 | }; | |
622 |
|
622 | |||
623 | /** |
|
|||
624 | * Send a message on the Kernel's shell channel |
|
|||
625 | * |
|
|||
626 | * @function send_shell_message |
|
|||
627 | */ |
|
|||
628 | Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) { |
|
623 | Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) { | |
|
624 | /** | |||
|
625 | * Send a message on the Kernel's shell channel | |||
|
626 | * | |||
|
627 | * @function send_shell_message | |||
|
628 | */ | |||
629 | if (!this.is_connected()) { |
|
629 | if (!this.is_connected()) { | |
630 | throw new Error("kernel is not connected"); |
|
630 | throw new Error("kernel is not connected"); | |
631 | } |
|
631 | } | |
@@ -635,17 +635,17 b' define([' | |||||
635 | return msg.header.msg_id; |
|
635 | return msg.header.msg_id; | |
636 | }; |
|
636 | }; | |
637 |
|
637 | |||
638 | /** |
|
|||
639 | * Get kernel info |
|
|||
640 | * |
|
|||
641 | * @function kernel_info |
|
|||
642 | * @param callback {function} |
|
|||
643 | * |
|
|||
644 | * When calling this method, pass a callback function that expects one argument. |
|
|||
645 | * The callback will be passed the complete `kernel_info_reply` message documented |
|
|||
646 | * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info) |
|
|||
647 | */ |
|
|||
648 | Kernel.prototype.kernel_info = function (callback) { |
|
638 | Kernel.prototype.kernel_info = function (callback) { | |
|
639 | /** | |||
|
640 | * Get kernel info | |||
|
641 | * | |||
|
642 | * @function kernel_info | |||
|
643 | * @param callback {function} | |||
|
644 | * | |||
|
645 | * When calling this method, pass a callback function that expects one argument. | |||
|
646 | * The callback will be passed the complete `kernel_info_reply` message documented | |||
|
647 | * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info) | |||
|
648 | */ | |||
649 | var callbacks; |
|
649 | var callbacks; | |
650 | if (callback) { |
|
650 | if (callback) { | |
651 | callbacks = { shell : { reply : callback } }; |
|
651 | callbacks = { shell : { reply : callback } }; | |
@@ -653,19 +653,19 b' define([' | |||||
653 | return this.send_shell_message("kernel_info_request", {}, callbacks); |
|
653 | return this.send_shell_message("kernel_info_request", {}, callbacks); | |
654 | }; |
|
654 | }; | |
655 |
|
655 | |||
656 | /** |
|
|||
657 | * Get info on an object |
|
|||
658 | * |
|
|||
659 | * When calling this method, pass a callback function that expects one argument. |
|
|||
660 | * The callback will be passed the complete `inspect_reply` message documented |
|
|||
661 | * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) |
|
|||
662 | * |
|
|||
663 | * @function inspect |
|
|||
664 | * @param code {string} |
|
|||
665 | * @param cursor_pos {integer} |
|
|||
666 | * @param callback {function} |
|
|||
667 | */ |
|
|||
668 | Kernel.prototype.inspect = function (code, cursor_pos, callback) { |
|
656 | Kernel.prototype.inspect = function (code, cursor_pos, callback) { | |
|
657 | /** | |||
|
658 | * Get info on an object | |||
|
659 | * | |||
|
660 | * When calling this method, pass a callback function that expects one argument. | |||
|
661 | * The callback will be passed the complete `inspect_reply` message documented | |||
|
662 | * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) | |||
|
663 | * | |||
|
664 | * @function inspect | |||
|
665 | * @param code {string} | |||
|
666 | * @param cursor_pos {integer} | |||
|
667 | * @param callback {function} | |||
|
668 | */ | |||
669 | var callbacks; |
|
669 | var callbacks; | |
670 | if (callback) { |
|
670 | if (callback) { | |
671 | callbacks = { shell : { reply : callback } }; |
|
671 | callbacks = { shell : { reply : callback } }; | |
@@ -679,56 +679,56 b' define([' | |||||
679 | return this.send_shell_message("inspect_request", content, callbacks); |
|
679 | return this.send_shell_message("inspect_request", content, callbacks); | |
680 | }; |
|
680 | }; | |
681 |
|
681 | |||
682 | /** |
|
|||
683 | * Execute given code into kernel, and pass result to callback. |
|
|||
684 | * |
|
|||
685 | * @async |
|
|||
686 | * @function execute |
|
|||
687 | * @param {string} code |
|
|||
688 | * @param [callbacks] {Object} With the following keys (all optional) |
|
|||
689 | * @param callbacks.shell.reply {function} |
|
|||
690 | * @param callbacks.shell.payload.[payload_name] {function} |
|
|||
691 | * @param callbacks.iopub.output {function} |
|
|||
692 | * @param callbacks.iopub.clear_output {function} |
|
|||
693 | * @param callbacks.input {function} |
|
|||
694 | * @param {object} [options] |
|
|||
695 | * @param [options.silent=false] {Boolean} |
|
|||
696 | * @param [options.user_expressions=empty_dict] {Dict} |
|
|||
697 | * @param [options.allow_stdin=false] {Boolean} true|false |
|
|||
698 | * |
|
|||
699 | * @example |
|
|||
700 | * |
|
|||
701 | * The options object should contain the options for the execute |
|
|||
702 | * call. Its default values are: |
|
|||
703 | * |
|
|||
704 | * options = { |
|
|||
705 | * silent : true, |
|
|||
706 | * user_expressions : {}, |
|
|||
707 | * allow_stdin : false |
|
|||
708 | * } |
|
|||
709 | * |
|
|||
710 | * When calling this method pass a callbacks structure of the |
|
|||
711 | * form: |
|
|||
712 | * |
|
|||
713 | * callbacks = { |
|
|||
714 | * shell : { |
|
|||
715 | * reply : execute_reply_callback, |
|
|||
716 | * payload : { |
|
|||
717 | * set_next_input : set_next_input_callback, |
|
|||
718 | * } |
|
|||
719 | * }, |
|
|||
720 | * iopub : { |
|
|||
721 | * output : output_callback, |
|
|||
722 | * clear_output : clear_output_callback, |
|
|||
723 | * }, |
|
|||
724 | * input : raw_input_callback |
|
|||
725 | * } |
|
|||
726 | * |
|
|||
727 | * Each callback will be passed the entire message as a single |
|
|||
728 | * arugment. Payload handlers will be passed the corresponding |
|
|||
729 | * payload and the execute_reply message. |
|
|||
730 | */ |
|
|||
731 | Kernel.prototype.execute = function (code, callbacks, options) { |
|
682 | Kernel.prototype.execute = function (code, callbacks, options) { | |
|
683 | /** | |||
|
684 | * Execute given code into kernel, and pass result to callback. | |||
|
685 | * | |||
|
686 | * @async | |||
|
687 | * @function execute | |||
|
688 | * @param {string} code | |||
|
689 | * @param [callbacks] {Object} With the following keys (all optional) | |||
|
690 | * @param callbacks.shell.reply {function} | |||
|
691 | * @param callbacks.shell.payload.[payload_name] {function} | |||
|
692 | * @param callbacks.iopub.output {function} | |||
|
693 | * @param callbacks.iopub.clear_output {function} | |||
|
694 | * @param callbacks.input {function} | |||
|
695 | * @param {object} [options] | |||
|
696 | * @param [options.silent=false] {Boolean} | |||
|
697 | * @param [options.user_expressions=empty_dict] {Dict} | |||
|
698 | * @param [options.allow_stdin=false] {Boolean} true|false | |||
|
699 | * | |||
|
700 | * @example | |||
|
701 | * | |||
|
702 | * The options object should contain the options for the execute | |||
|
703 | * call. Its default values are: | |||
|
704 | * | |||
|
705 | * options = { | |||
|
706 | * silent : true, | |||
|
707 | * user_expressions : {}, | |||
|
708 | * allow_stdin : false | |||
|
709 | * } | |||
|
710 | * | |||
|
711 | * When calling this method pass a callbacks structure of the | |||
|
712 | * form: | |||
|
713 | * | |||
|
714 | * callbacks = { | |||
|
715 | * shell : { | |||
|
716 | * reply : execute_reply_callback, | |||
|
717 | * payload : { | |||
|
718 | * set_next_input : set_next_input_callback, | |||
|
719 | * } | |||
|
720 | * }, | |||
|
721 | * iopub : { | |||
|
722 | * output : output_callback, | |||
|
723 | * clear_output : clear_output_callback, | |||
|
724 | * }, | |||
|
725 | * input : raw_input_callback | |||
|
726 | * } | |||
|
727 | * | |||
|
728 | * Each callback will be passed the entire message as a single | |||
|
729 | * arugment. Payload handlers will be passed the corresponding | |||
|
730 | * payload and the execute_reply message. | |||
|
731 | */ | |||
732 | var content = { |
|
732 | var content = { | |
733 | code : code, |
|
733 | code : code, | |
734 | silent : true, |
|
734 | silent : true, |
@@ -101,8 +101,7 b' define([' | |||||
101 | var parameters = {model: model, options: options}; |
|
101 | var parameters = {model: model, options: options}; | |
102 | var view = new ViewType(parameters); |
|
102 | var view = new ViewType(parameters); | |
103 | view.listenTo(model, 'destroy', view.remove); |
|
103 | view.listenTo(model, 'destroy', view.remove); | |
104 | view.render(); |
|
104 | return Promise.resolve(view.render()).then(function() {return view;}); | |
105 | return view; |
|
|||
106 | }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true)); |
|
105 | }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true)); | |
107 | }); |
|
106 | }); | |
108 | return model.state_change; |
|
107 | return model.state_change; |
@@ -180,16 +180,42 b' Backwards incompatible changes' | |||||
180 |
|
180 | |||
181 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. |
|
181 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. | |
182 |
|
182 | |||
183 | IFrame embedding |
|
183 | Content Security Policy | |
184 | ```````````````` |
|
184 | ``````````````````````` | |
185 |
|
185 | |||
186 | The IPython Notebook and its APIs by default will only be allowed to be |
|
186 | The Content Security Policy is a web standard for adding a layer of security to | |
187 | embedded in an iframe on the same origin. |
|
187 | detect and mitigate certain classes of attacks, including Cross Site Scripting | |
|
188 | (XSS) and data injection attacks. This was introduced into the notebook to | |||
|
189 | ensure that the IPython Notebook and its APIs (by default) can only be embedded | |||
|
190 | in an iframe on the same origin. | |||
188 |
|
191 | |||
189 | To override this, set ``headers[X-Frame-Options]`` to one of |
|
192 | Override ``headers['Content-Security-Policy']`` within your notebook | |
|
193 | configuration to extend for alternate domains and security settings.:: | |||
190 |
|
|
194 | ||
191 | * DENY |
|
195 | c.NotebookApp.tornado_settings = { | |
192 | * SAMEORIGIN |
|
196 | 'headers': { | |
193 | * ALLOW-FROM uri |
|
197 | 'Content-Security-Policy': "frame-ancestors 'self'" | |
|
198 | } | |||
|
199 | } | |||
194 |
|
|
200 | ||
195 | See `Mozilla's guide to X-Frame-Options <https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options>`_ for more examples. |
|
201 | Example policies:: | |
|
202 | ||||
|
203 | Content-Security-Policy: default-src 'self' https://*.jupyter.org | |||
|
204 | ||||
|
205 | Matches embeddings on any subdomain of jupyter.org, so long as they are served | |||
|
206 | over SSL. | |||
|
207 | ||||
|
208 | There is a `report-uri <https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives#report-uri>`_ endpoint available for logging CSP violations, located at | |||
|
209 | ``/api/security/csp-report``. To use it, set ``report-uri`` as part of the CSP:: | |||
|
210 | ||||
|
211 | c.NotebookApp.tornado_settings = { | |||
|
212 | 'headers': { | |||
|
213 | 'Content-Security-Policy': "frame-ancestors 'self'; report-uri /api/security/csp-report" | |||
|
214 | } | |||
|
215 | } | |||
|
216 | ||||
|
217 | It simply provides the CSP report as a warning in IPython's logs. The default | |||
|
218 | CSP sets this report-uri relative to the ``base_url`` (not shown above). | |||
|
219 | ||||
|
220 | For a more thorough and accurate guide on Content Security Policies, check out | |||
|
221 | `MDN's Using Content Security Policy <https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy>`_ for more examples. |
General Comments 0
You need to be logged in to leave comments.
Login now