##// END OF EJS Templates
Merge remote-tracking branch 'upstream/master'
Nathan Heijermans -
r19175:e2ea8a76 merge
parent child Browse files
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):
@@ -194,6 +194,26 b' class Config(dict):'
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
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008 The IPython Development Team
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
3
18 #-----------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
19 # Imports
5 # Distributed under the terms of the Modified BSD License.
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()
@@ -118,6 +98,34 b' class TestFileCL(TestCase):'
118 config = cl.load_config()
98 config = cl.load_config()
119 self._check_conf(config)
99 self._check_conf(config)
120
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 })
128
121 def test_v2raise(self):
129 def test_v2raise(self):
122 fd, fname = mkstemp('.json')
130 fd, fname = mkstemp('.json')
123 f = os.fdopen(fd, 'w')
131 f = os.fdopen(fd, 'w')
@@ -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 "X-Frame-Options" not in headers:
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 pass
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['x-frame-options'], "SAMEORIGIN")
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['x-frame-options'], "SAMEORIGIN")
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,6 +286,7 b' define(['
286 });
286 });
287 };
287 };
288
288
289 Kernel.prototype.restart = function (success, error) {
289 /**
290 /**
290 * POST /api/kernels/[:kernel_id]/restart
291 * POST /api/kernels/[:kernel_id]/restart
291 *
292 *
@@ -295,7 +296,6 b' define(['
295 * @param {function} [success] - function executed on ajax success
296 * @param {function} [success] - function executed on ajax success
296 * @param {function} [error] - functon executed on ajax error
297 * @param {function} [error] - functon executed on ajax error
297 */
298 */
298 Kernel.prototype.restart = function (success, error) {
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,6 +327,7 b' define(['
327 });
327 });
328 };
328 };
329
329
330 Kernel.prototype.reconnect = function () {
330 /**
331 /**
331 * Reconnect to a disconnected kernel. This is not actually a
332 * Reconnect to a disconnected kernel. This is not actually a
332 * standard HTTP request, but useful function nonetheless for
333 * standard HTTP request, but useful function nonetheless for
@@ -334,7 +335,6 b' define(['
334 *
335 *
335 * @function reconnect
336 * @function reconnect
336 */
337 */
337 Kernel.prototype.reconnect = function () {
338 if (this.is_connected()) {
338 if (this.is_connected()) {
339 return;
339 return;
340 }
340 }
@@ -346,6 +346,7 b' define(['
346 this.start_channels();
346 this.start_channels();
347 };
347 };
348
348
349 Kernel.prototype._on_success = function (success) {
349 /**
350 /**
350 * Handle a successful AJAX request by updating the kernel id and
351 * Handle a successful AJAX request by updating the kernel id and
351 * name from the response, and then optionally calling a provided
352 * name from the response, and then optionally calling a provided
@@ -354,7 +355,6 b' define(['
354 * @function _on_success
355 * @function _on_success
355 * @param {function} success - callback
356 * @param {function} success - callback
356 */
357 */
357 Kernel.prototype._on_success = function (success) {
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,6 +368,7 b' define(['
368 };
368 };
369 };
369 };
370
370
371 Kernel.prototype._on_error = function (error) {
371 /**
372 /**
372 * Handle a failed AJAX request by logging the error message, and
373 * Handle a failed AJAX request by logging the error message, and
373 * then optionally calling a provided callback.
374 * then optionally calling a provided callback.
@@ -375,7 +376,6 b' define(['
375 * @function _on_error
376 * @function _on_error
376 * @param {function} error - callback
377 * @param {function} error - callback
377 */
378 */
378 Kernel.prototype._on_error = function (error) {
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,6 +384,7 b' define(['
384 };
384 };
385 };
385 };
386
386
387 Kernel.prototype._kernel_created = function (data) {
387 /**
388 /**
388 * Perform necessary tasks once the kernel has been started,
389 * Perform necessary tasks once the kernel has been started,
389 * including actually connecting to the kernel.
390 * including actually connecting to the kernel.
@@ -391,12 +392,12 b' define(['
391 * @function _kernel_created
392 * @function _kernel_created
392 * @param {Object} data - information about the kernel including id
393 * @param {Object} data - information about the kernel including id
393 */
394 */
394 Kernel.prototype._kernel_created = function (data) {
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 Kernel.prototype._kernel_connected = function () {
400 /**
401 /**
401 * Perform necessary tasks once the connection to the kernel has
402 * Perform necessary tasks once the connection to the kernel has
402 * been established. This includes requesting information about
403 * been established. This includes requesting information about
@@ -404,7 +405,6 b' define(['
404 *
405 *
405 * @function _kernel_connected
406 * @function _kernel_connected
406 */
407 */
407 Kernel.prototype._kernel_connected = function () {
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,6 +415,7 b' define(['
415 });
415 });
416 };
416 };
417
417
418 Kernel.prototype._kernel_dead = function () {
418 /**
419 /**
419 * Perform necessary tasks after the kernel has died. This closing
420 * Perform necessary tasks after the kernel has died. This closing
420 * communication channels to the kernel if they are still somehow
421 * communication channels to the kernel if they are still somehow
@@ -422,17 +423,16 b' define(['
422 *
423 *
423 * @function _kernel_dead
424 * @function _kernel_dead
424 */
425 */
425 Kernel.prototype._kernel_dead = function () {
426 this.stop_channels();
426 this.stop_channels();
427 };
427 };
428
428
429 Kernel.prototype.start_channels = function () {
429 /**
430 /**
430 * Start the `shell`and `iopub` channels.
431 * Start the `shell`and `iopub` channels.
431 * Will stop and restart them if they already exist.
432 * Will stop and restart them if they already exist.
432 *
433 *
433 * @function start_channels
434 * @function start_channels
434 */
435 */
435 Kernel.prototype.start_channels = function () {
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,19 +506,20 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 Kernel.prototype._ws_opened = function (evt) {
509 /**
510 /**
510 * Handle a websocket entering the open state,
511 * Handle a websocket entering the open state,
511 * signaling that the kernel is connected when all channels are open.
512 * signaling that the kernel is connected when all channels are open.
512 *
513 *
513 * @function _ws_opened
514 * @function _ws_opened
514 */
515 */
515 Kernel.prototype._ws_opened = function (evt) {
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 Kernel.prototype._ws_closed = function(ws_url, error) {
522 /**
523 /**
523 * Handle a websocket entering the closed state. This closes the
524 * Handle a websocket entering the closed state. This closes the
524 * other communication channels if they are open. If the websocket
525 * other communication channels if they are open. If the websocket
@@ -528,7 +529,6 b' define(['
528 * @param {string} ws_url - the websocket url
529 * @param {string} ws_url - the websocket url
529 * @param {bool} error - whether the connection was closed due to an error
530 * @param {bool} error - whether the connection was closed due to an error
530 */
531 */
531 Kernel.prototype._ws_closed = function(ws_url, error) {
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 Kernel.prototype.stop_channels = function () {
558 /**
559 /**
559 * Close the websocket channels. After successful close, the value
560 * Close the websocket channels. After successful close, the value
560 * in `this.channels[channel_name]` will be null.
561 * in `this.channels[channel_name]` will be null.
561 *
562 *
562 * @function stop_channels
563 * @function stop_channels
563 */
564 */
564 Kernel.prototype.stop_channels = function () {
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,6 +582,7 b' define(['
582 }
582 }
583 };
583 };
584
584
585 Kernel.prototype.is_connected = function () {
585 /**
586 /**
586 * Check whether there is a connection to the kernel. This
587 * Check whether there is a connection to the kernel. This
587 * function only returns true if all channel objects have been
588 * function only returns true if all channel objects have been
@@ -590,7 +591,6 b' define(['
590 * @function is_connected
591 * @function is_connected
591 * @returns {bool} - whether there is a connection
592 * @returns {bool} - whether there is a connection
592 */
593 */
593 Kernel.prototype.is_connected = function () {
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,6 +603,7 b' define(['
603 return true;
603 return true;
604 };
604 };
605
605
606 Kernel.prototype.is_fully_disconnected = function () {
606 /**
607 /**
607 * Check whether the connection to the kernel has been completely
608 * Check whether the connection to the kernel has been completely
608 * severed. This function only returns true if all channel objects
609 * severed. This function only returns true if all channel objects
@@ -611,7 +612,6 b' define(['
611 * @function is_fully_disconnected
612 * @function is_fully_disconnected
612 * @returns {bool} - whether the kernel is fully disconnected
613 * @returns {bool} - whether the kernel is fully disconnected
613 */
614 */
614 Kernel.prototype.is_fully_disconnected = function () {
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 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
623 /**
624 /**
624 * Send a message on the Kernel's shell channel
625 * Send a message on the Kernel's shell channel
625 *
626 *
626 * @function send_shell_message
627 * @function send_shell_message
627 */
628 */
628 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
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,6 +635,7 b' define(['
635 return msg.header.msg_id;
635 return msg.header.msg_id;
636 };
636 };
637
637
638 Kernel.prototype.kernel_info = function (callback) {
638 /**
639 /**
639 * Get kernel info
640 * Get kernel info
640 *
641 *
@@ -645,7 +646,6 b' define(['
645 * The callback will be passed the complete `kernel_info_reply` message documented
646 * 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 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
647 */
648 */
648 Kernel.prototype.kernel_info = function (callback) {
649 var callbacks;
649 var callbacks;
650 if (callback) {
650 if (callback) {
651 callbacks = { shell : { reply : callback } };
651 callbacks = { shell : { reply : callback } };
@@ -653,6 +653,7 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 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
656 /**
657 /**
657 * Get info on an object
658 * Get info on an object
658 *
659 *
@@ -665,7 +666,6 b' define(['
665 * @param cursor_pos {integer}
666 * @param cursor_pos {integer}
666 * @param callback {function}
667 * @param callback {function}
667 */
668 */
668 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
669 var callbacks;
669 var callbacks;
670 if (callback) {
670 if (callback) {
671 callbacks = { shell : { reply : callback } };
671 callbacks = { shell : { reply : callback } };
@@ -679,6 +679,7 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 Kernel.prototype.execute = function (code, callbacks, options) {
682 /**
683 /**
683 * Execute given code into kernel, and pass result to callback.
684 * Execute given code into kernel, and pass result to callback.
684 *
685 *
@@ -728,7 +729,6 b' define(['
728 * arugment. Payload handlers will be passed the corresponding
729 * arugment. Payload handlers will be passed the corresponding
729 * payload and the execute_reply message.
730 * payload and the execute_reply message.
730 */
731 */
731 Kernel.prototype.execute = function (code, callbacks, options) {
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