From 480beffd7b0d1077fd099fdc4e8541781f307a28 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 29 Nov 2014 15:33:14 +0000 Subject: [PATCH 01/20] Move js donc into function themselves. Then it is possible in the browser js console to get the documentation interactively. --- .../html/static/services/kernels/kernel.js | 342 +++++++++--------- 1 file changed, 171 insertions(+), 171 deletions(-) diff --git a/IPython/html/static/services/kernels/kernel.js b/IPython/html/static/services/kernels/kernel.js index 77cf4b1f5..f4369b739 100644 --- a/IPython/html/static/services/kernels/kernel.js +++ b/IPython/html/static/services/kernels/kernel.js @@ -284,16 +284,16 @@ define([ }); }; - /** - * POST /api/kernels/[:kernel_id]/restart - * - * Restart the kernel. - * - * @function interrupt - * @param {function} [success] - function executed on ajax success - * @param {function} [error] - functon executed on ajax error - */ Kernel.prototype.restart = function (success, error) { + /** + * POST /api/kernels/[:kernel_id]/restart + * + * Restart the kernel. + * + * @function interrupt + * @param {function} [success] - function executed on ajax success + * @param {function} [error] - functon executed on ajax error + */ this.events.trigger('kernel_restarting.Kernel', {kernel: this}); this.stop_channels(); @@ -325,14 +325,14 @@ define([ }); }; - /** - * Reconnect to a disconnected kernel. This is not actually a - * standard HTTP request, but useful function nonetheless for - * reconnecting to the kernel if the connection is somehow lost. - * - * @function reconnect - */ Kernel.prototype.reconnect = function () { + /** + * Reconnect to a disconnected kernel. This is not actually a + * standard HTTP request, but useful function nonetheless for + * reconnecting to the kernel if the connection is somehow lost. + * + * @function reconnect + */ if (this.is_connected()) { return; } @@ -344,15 +344,15 @@ define([ this.start_channels(); }; - /** - * Handle a successful AJAX request by updating the kernel id and - * name from the response, and then optionally calling a provided - * callback. - * - * @function _on_success - * @param {function} success - callback - */ Kernel.prototype._on_success = function (success) { + /** + * Handle a successful AJAX request by updating the kernel id and + * name from the response, and then optionally calling a provided + * callback. + * + * @function _on_success + * @param {function} success - callback + */ var that = this; return function (data, status, xhr) { if (data) { @@ -366,14 +366,14 @@ define([ }; }; - /** - * Handle a failed AJAX request by logging the error message, and - * then optionally calling a provided callback. - * - * @function _on_error - * @param {function} error - callback - */ Kernel.prototype._on_error = function (error) { + /** + * Handle a failed AJAX request by logging the error message, and + * then optionally calling a provided callback. + * + * @function _on_error + * @param {function} error - callback + */ return function (xhr, status, err) { utils.log_ajax_error(xhr, status, err); if (error) { @@ -382,27 +382,27 @@ define([ }; }; - /** - * Perform necessary tasks once the kernel has been started, - * including actually connecting to the kernel. - * - * @function _kernel_created - * @param {Object} data - information about the kernel including id - */ Kernel.prototype._kernel_created = function (data) { + /** + * Perform necessary tasks once the kernel has been started, + * including actually connecting to the kernel. + * + * @function _kernel_created + * @param {Object} data - information about the kernel including id + */ this.id = data.id; this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id); this.start_channels(); }; - /** - * Perform necessary tasks once the connection to the kernel has - * been established. This includes requesting information about - * the kernel. - * - * @function _kernel_connected - */ Kernel.prototype._kernel_connected = function () { + /** + * Perform necessary tasks once the connection to the kernel has + * been established. This includes requesting information about + * the kernel. + * + * @function _kernel_connected + */ this.events.trigger('kernel_connected.Kernel', {kernel: this}); this.events.trigger('kernel_starting.Kernel', {kernel: this}); // get kernel info so we know what state the kernel is in @@ -413,24 +413,24 @@ define([ }); }; - /** - * Perform necessary tasks after the kernel has died. This closing - * communication channels to the kernel if they are still somehow - * open. - * - * @function _kernel_dead - */ Kernel.prototype._kernel_dead = function () { + /** + * Perform necessary tasks after the kernel has died. This closing + * communication channels to the kernel if they are still somehow + * open. + * + * @function _kernel_dead + */ this.stop_channels(); }; - /** - * Start the `shell`and `iopub` channels. - * Will stop and restart them if they already exist. - * - * @function start_channels - */ Kernel.prototype.start_channels = function () { + /** + * Start the `shell`and `iopub` channels. + * Will stop and restart them if they already exist. + * + * @function start_channels + */ var that = this; this.stop_channels(); var ws_host_url = this.ws_url + this.kernel_url; @@ -504,29 +504,29 @@ define([ this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this); }; - /** - * Handle a websocket entering the open state, - * signaling that the kernel is connected when all channels are open. - * - * @function _ws_opened - */ Kernel.prototype._ws_opened = function (evt) { + /** + * Handle a websocket entering the open state, + * signaling that the kernel is connected when all channels are open. + * + * @function _ws_opened + */ if (this.is_connected()) { // all events ready, trigger started event. this._kernel_connected(); } }; - /** - * Handle a websocket entering the closed state. This closes the - * other communication channels if they are open. If the websocket - * was not closed due to an error, try to reconnect to the kernel. - * - * @function _ws_closed - * @param {string} ws_url - the websocket url - * @param {bool} error - whether the connection was closed due to an error - */ Kernel.prototype._ws_closed = function(ws_url, error) { + /** + * Handle a websocket entering the closed state. This closes the + * other communication channels if they are open. If the websocket + * was not closed due to an error, try to reconnect to the kernel. + * + * @function _ws_closed + * @param {string} ws_url - the websocket url + * @param {bool} error - whether the connection was closed due to an error + */ this.stop_channels(); this.events.trigger('kernel_disconnected.Kernel', {kernel: this}); @@ -553,13 +553,13 @@ define([ } }; - /** - * Close the websocket channels. After successful close, the value - * in `this.channels[channel_name]` will be null. - * - * @function stop_channels - */ Kernel.prototype.stop_channels = function () { + /** + * Close the websocket channels. After successful close, the value + * in `this.channels[channel_name]` will be null. + * + * @function stop_channels + */ var that = this; var close = function (c) { return function () { @@ -580,15 +580,15 @@ define([ } }; - /** - * Check whether there is a connection to the kernel. This - * function only returns true if all channel objects have been - * created and have a state of WebSocket.OPEN. - * - * @function is_connected - * @returns {bool} - whether there is a connection - */ Kernel.prototype.is_connected = function () { + /** + * Check whether there is a connection to the kernel. This + * function only returns true if all channel objects have been + * created and have a state of WebSocket.OPEN. + * + * @function is_connected + * @returns {bool} - whether there is a connection + */ for (var c in this.channels) { // if any channel is not ready, then we're not connected if (this.channels[c] === null) { @@ -601,15 +601,15 @@ define([ return true; }; - /** - * Check whether the connection to the kernel has been completely - * severed. This function only returns true if all channel objects - * are null. - * - * @function is_fully_disconnected - * @returns {bool} - whether the kernel is fully disconnected - */ Kernel.prototype.is_fully_disconnected = function () { + /** + * Check whether the connection to the kernel has been completely + * severed. This function only returns true if all channel objects + * are null. + * + * @function is_fully_disconnected + * @returns {bool} - whether the kernel is fully disconnected + */ for (var c in this.channels) { if (this.channels[c] === null) { return true; @@ -618,12 +618,12 @@ define([ return false; }; - /** - * Send a message on the Kernel's shell channel - * - * @function send_shell_message - */ Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) { + /** + * Send a message on the Kernel's shell channel + * + * @function send_shell_message + */ if (!this.is_connected()) { throw new Error("kernel is not connected"); } @@ -633,17 +633,17 @@ define([ return msg.header.msg_id; }; - /** - * Get kernel info - * - * @function kernel_info - * @param callback {function} - * - * When calling this method, pass a callback function that expects one argument. - * The callback will be passed the complete `kernel_info_reply` message documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info) - */ Kernel.prototype.kernel_info = function (callback) { + /** + * Get kernel info + * + * @function kernel_info + * @param callback {function} + * + * When calling this method, pass a callback function that expects one argument. + * The callback will be passed the complete `kernel_info_reply` message documented + * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info) + */ var callbacks; if (callback) { callbacks = { shell : { reply : callback } }; @@ -651,19 +651,19 @@ define([ return this.send_shell_message("kernel_info_request", {}, callbacks); }; - /** - * Get info on an object - * - * When calling this method, pass a callback function that expects one argument. - * The callback will be passed the complete `inspect_reply` message documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) - * - * @function inspect - * @param code {string} - * @param cursor_pos {integer} - * @param callback {function} - */ Kernel.prototype.inspect = function (code, cursor_pos, callback) { + /** + * Get info on an object + * + * When calling this method, pass a callback function that expects one argument. + * The callback will be passed the complete `inspect_reply` message documented + * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) + * + * @function inspect + * @param code {string} + * @param cursor_pos {integer} + * @param callback {function} + */ var callbacks; if (callback) { callbacks = { shell : { reply : callback } }; @@ -677,56 +677,56 @@ define([ return this.send_shell_message("inspect_request", content, callbacks); }; - /** - * Execute given code into kernel, and pass result to callback. - * - * @async - * @function execute - * @param {string} code - * @param [callbacks] {Object} With the following keys (all optional) - * @param callbacks.shell.reply {function} - * @param callbacks.shell.payload.[payload_name] {function} - * @param callbacks.iopub.output {function} - * @param callbacks.iopub.clear_output {function} - * @param callbacks.input {function} - * @param {object} [options] - * @param [options.silent=false] {Boolean} - * @param [options.user_expressions=empty_dict] {Dict} - * @param [options.allow_stdin=false] {Boolean} true|false - * - * @example - * - * The options object should contain the options for the execute - * call. Its default values are: - * - * options = { - * silent : true, - * user_expressions : {}, - * allow_stdin : false - * } - * - * When calling this method pass a callbacks structure of the - * form: - * - * callbacks = { - * shell : { - * reply : execute_reply_callback, - * payload : { - * set_next_input : set_next_input_callback, - * } - * }, - * iopub : { - * output : output_callback, - * clear_output : clear_output_callback, - * }, - * input : raw_input_callback - * } - * - * Each callback will be passed the entire message as a single - * arugment. Payload handlers will be passed the corresponding - * payload and the execute_reply message. - */ Kernel.prototype.execute = function (code, callbacks, options) { + /** + * Execute given code into kernel, and pass result to callback. + * + * @async + * @function execute + * @param {string} code + * @param [callbacks] {Object} With the following keys (all optional) + * @param callbacks.shell.reply {function} + * @param callbacks.shell.payload.[payload_name] {function} + * @param callbacks.iopub.output {function} + * @param callbacks.iopub.clear_output {function} + * @param callbacks.input {function} + * @param {object} [options] + * @param [options.silent=false] {Boolean} + * @param [options.user_expressions=empty_dict] {Dict} + * @param [options.allow_stdin=false] {Boolean} true|false + * + * @example + * + * The options object should contain the options for the execute + * call. Its default values are: + * + * options = { + * silent : true, + * user_expressions : {}, + * allow_stdin : false + * } + * + * When calling this method pass a callbacks structure of the + * form: + * + * callbacks = { + * shell : { + * reply : execute_reply_callback, + * payload : { + * set_next_input : set_next_input_callback, + * } + * }, + * iopub : { + * output : output_callback, + * clear_output : clear_output_callback, + * }, + * input : raw_input_callback + * } + * + * Each callback will be passed the entire message as a single + * arugment. Payload handlers will be passed the corresponding + * payload and the execute_reply message. + */ var content = { code : code, silent : true, From a00d6930f208169734cad3cfb2c21306abd40b40 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 12:29:26 -0600 Subject: [PATCH 02/20] Adapt headers to using Content Security Policy --- IPython/html/base/handlers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index ae58e7ac8..e6e5cd117 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -45,8 +45,8 @@ class AuthenticatedHandler(web.RequestHandler): def set_default_headers(self): headers = self.settings.get('headers', {}) - if "X-Frame-Options" not in headers: - headers["X-Frame-Options"] = "SAMEORIGIN" + if "Content-Security-Policy" not in headers: + headers["Content-Security-Policy"] = "default-src 'self'" for header_name,value in headers.items() : try: From 1f03954dd8ae9523da4e7d229c44f5c8adb7c42e Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 14:01:06 -0600 Subject: [PATCH 03/20] Handle CSP Reports --- IPython/html/services/security/__init__.py | 0 IPython/html/services/security/handlers.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 IPython/html/services/security/__init__.py create mode 100644 IPython/html/services/security/handlers.py diff --git a/IPython/html/services/security/__init__.py b/IPython/html/services/security/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py new file mode 100644 index 000000000..1b842edeb --- /dev/null +++ b/IPython/html/services/security/handlers.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class CSPReportHandler(IPythonHandler): + '''Accepts a content security policy violation report''' + @web.authenticated + @json_errors + def post(self): + '''Log a content security policy violation report''' + csp_report = self.get_json_body() + self.log.debug(csp_report) + +csp_report_uri = r"/api/security/csp-report" + +default_handlers = [ + (csp_report_uri, CSPReportHandler) +] From 23b9f09177cb39f94bb8a6c7965dad7a3d17f70d Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 16:15:15 -0600 Subject: [PATCH 04/20] Log CSP violations via report --- IPython/html/base/handlers.py | 16 +++++++++++++++- IPython/html/services/security/handlers.py | 10 ++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index e6e5cd117..6b34488e3 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -32,6 +32,8 @@ from IPython.utils.path import filefind from IPython.utils.py3compat import string_types from IPython.html.utils import is_hidden, url_path_join, url_escape +from IPython.html.services.security.handlers import csp_report_uri + #----------------------------------------------------------------------------- # Top-level handlers #----------------------------------------------------------------------------- @@ -46,8 +48,20 @@ class AuthenticatedHandler(web.RequestHandler): headers = self.settings.get('headers', {}) if "Content-Security-Policy" not in headers: - headers["Content-Security-Policy"] = "default-src 'self'" + #headers["Content-Security-Policy"] = "" + pass + + if "Content-Security-Policy-Report-Only" not in headers: + + reporter_policy = ("default-src 'self'; " + + "report-uri " + url_path_join(self.base_url, csp_report_uri) + + ";" + ) + self.log.info(reporter_policy) + + headers["Content-Security-Policy-Report-Only"] = reporter_policy + # Allow for overriding headers for header_name,value in headers.items() : try: self.set_header(header_name, value) diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py index 1b842edeb..120279fa4 100644 --- a/IPython/html/services/security/handlers.py +++ b/IPython/html/services/security/handlers.py @@ -1,5 +1,11 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +"""Tornado handlers for security logging.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +from tornado import gen, web + +from ...base.handlers import IPythonHandler, json_errors class CSPReportHandler(IPythonHandler): '''Accepts a content security policy violation report''' From 5b53d2db45e92826c22726445b04bc3c2c671a54 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 16:23:17 -0600 Subject: [PATCH 05/20] Set default policy to nothing, only report. --- IPython/html/base/handlers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 6b34488e3..07d0ce45c 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -48,8 +48,7 @@ class AuthenticatedHandler(web.RequestHandler): headers = self.settings.get('headers', {}) if "Content-Security-Policy" not in headers: - #headers["Content-Security-Policy"] = "" - pass + headers["Content-Security-Policy"] = "" if "Content-Security-Policy-Report-Only" not in headers: From cb19f07c9aebfba290cf1fa1b9885b42220d5cb2 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 16:26:10 -0600 Subject: [PATCH 06/20] csp_report_uri caused a cyclic dependency --- IPython/html/base/handlers.py | 2 +- IPython/html/services/security/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 07d0ce45c..d27c938d9 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -32,7 +32,7 @@ from IPython.utils.path import filefind from IPython.utils.py3compat import string_types from IPython.html.utils import is_hidden, url_path_join, url_escape -from IPython.html.services.security.handlers import csp_report_uri +from IPython.html.services.security import csp_report_uri #----------------------------------------------------------------------------- # Top-level handlers diff --git a/IPython/html/services/security/__init__.py b/IPython/html/services/security/__init__.py index e69de29bb..c660a562f 100644 --- a/IPython/html/services/security/__init__.py +++ b/IPython/html/services/security/__init__.py @@ -0,0 +1 @@ +csp_report_uri = r"/api/security/csp-report" From 3068733c46b4de573a55ab817f86af12b7643307 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 16:51:11 -0600 Subject: [PATCH 07/20] Log reporter policy to debug --- IPython/html/base/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index d27c938d9..c4da28c37 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -56,7 +56,7 @@ class AuthenticatedHandler(web.RequestHandler): "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" ) - self.log.info(reporter_policy) + self.log.debug(reporter_policy) headers["Content-Security-Policy-Report-Only"] = reporter_policy From 7fb8cd94ecd98d08a221a1cf0849072921c4e318 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Fri, 28 Nov 2014 12:19:06 -0600 Subject: [PATCH 08/20] Load the security service handlers. Conflicts: IPython/html/notebookapp.py --- IPython/html/notebookapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py index e21db1aea..24d18e89f 100644 --- a/IPython/html/notebookapp.py +++ b/IPython/html/notebookapp.py @@ -225,7 +225,7 @@ class NotebookWebApplication(web.Application): handlers.extend(load_handlers('services.sessions.handlers')) handlers.extend(load_handlers('services.nbconvert.handlers')) handlers.extend(load_handlers('services.kernelspecs.handlers')) - + handlers.extend(load_handlers('services.security.handlers')) handlers.append( (r"/nbextensions/(.*)", FileFindHandler, { 'path': settings['nbextensions_path'], From bd85ff25df951126afe2c8ae3b3c5bce8e60fbbe Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 22:40:30 -0600 Subject: [PATCH 09/20] Restrict frame-ancestors to 'self' in CSP --- IPython/html/base/handlers.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index c4da28c37..005c61a46 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -48,10 +48,9 @@ class AuthenticatedHandler(web.RequestHandler): headers = self.settings.get('headers', {}) if "Content-Security-Policy" not in headers: - headers["Content-Security-Policy"] = "" + headers["Content-Security-Policy"] = "frame-ancestors 'self'" if "Content-Security-Policy-Report-Only" not in headers: - reporter_policy = ("default-src 'self'; " + "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" From 5065429bfdc21b1dc4ae16caeb3154356ac40d9c Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 22:41:11 -0600 Subject: [PATCH 10/20] Clean up logs, enable debug log for header except --- IPython/html/base/handlers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 005c61a46..1fe1e8aec 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -55,18 +55,17 @@ class AuthenticatedHandler(web.RequestHandler): "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" ) - self.log.debug(reporter_policy) - headers["Content-Security-Policy-Report-Only"] = reporter_policy # Allow for overriding headers for header_name,value in headers.items() : try: self.set_header(header_name, value) - except Exception: + except Exception as e: # tornado raise Exception (not a subclass) # if method is unsupported (websocket and Access-Control-Allow-Origin # for example, so just ignore) + self.log.debug(e) pass def clear_login_cookie(self): From 392118d5367fc6f0192cbea4f271b9149de78dd1 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 22:41:45 -0600 Subject: [PATCH 11/20] One unified CSP report URI --- IPython/html/services/security/__init__.py | 3 +++ IPython/html/services/security/handlers.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/IPython/html/services/security/__init__.py b/IPython/html/services/security/__init__.py index c660a562f..9cf0d476b 100644 --- a/IPython/html/services/security/__init__.py +++ b/IPython/html/services/security/__init__.py @@ -1 +1,4 @@ +# URI for the CSP Report. Included here to prevent a cyclic dependency. +# csp_report_uri is needed both by the BaseHandler (for setting the report-uri) +# and by the CSPReportHandler (which depends on the BaseHandler). csp_report_uri = r"/api/security/csp-report" diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py index 120279fa4..e33fe83ce 100644 --- a/IPython/html/services/security/handlers.py +++ b/IPython/html/services/security/handlers.py @@ -6,6 +6,7 @@ from tornado import gen, web from ...base.handlers import IPythonHandler, json_errors +from . import csp_report_uri class CSPReportHandler(IPythonHandler): '''Accepts a content security policy violation report''' @@ -16,8 +17,6 @@ class CSPReportHandler(IPythonHandler): csp_report = self.get_json_body() self.log.debug(csp_report) -csp_report_uri = r"/api/security/csp-report" - default_handlers = [ (csp_report_uri, CSPReportHandler) ] From 0c22c140c3c4b4ab5b13ee8ddb70821ed9cc4460 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 22:44:42 -0600 Subject: [PATCH 12/20] Turn x-frame-options tests into CSP tests --- IPython/html/services/kernels/tests/test_kernels_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py index 8f29a07f6..c7abbfcc6 100644 --- a/IPython/html/services/kernels/tests/test_kernels_api.py +++ b/IPython/html/services/kernels/tests/test_kernels_api.py @@ -65,7 +65,7 @@ class KernelAPITest(NotebookTestBase): self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN") + self.assertEqual(r.headers['Content-Security-Policy'], "frame-ancestors 'self'") def test_main_kernel_handler(self): # POST request @@ -75,7 +75,7 @@ class KernelAPITest(NotebookTestBase): self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['x-frame-options'], "SAMEORIGIN") + self.assertEqual(r.headers['Content-Security-Policy'], "frame-ancestors 'self'") # GET request r = self.kern_api.list() From 5343787e9d97c055c680bd24b5c710b5db3aeb7f Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 22:58:37 -0600 Subject: [PATCH 13/20] Remove CSP reporting since it fills console. --- IPython/html/base/handlers.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 1fe1e8aec..7ed1c3542 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -50,13 +50,6 @@ class AuthenticatedHandler(web.RequestHandler): if "Content-Security-Policy" not in headers: headers["Content-Security-Policy"] = "frame-ancestors 'self'" - if "Content-Security-Policy-Report-Only" not in headers: - reporter_policy = ("default-src 'self'; " + - "report-uri " + url_path_join(self.base_url, csp_report_uri) + - ";" - ) - headers["Content-Security-Policy-Report-Only"] = reporter_policy - # Allow for overriding headers for header_name,value in headers.items() : try: From 17d1c2d4886ebd2860e52dc75281b3c9b62c8181 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 23:20:57 -0600 Subject: [PATCH 14/20] Report CSP violations as warnings. --- IPython/html/base/handlers.py | 7 ++++++- IPython/html/services/security/handlers.py | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index 7ed1c3542..dc6a877a9 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -48,7 +48,12 @@ class AuthenticatedHandler(web.RequestHandler): headers = self.settings.get('headers', {}) if "Content-Security-Policy" not in headers: - headers["Content-Security-Policy"] = "frame-ancestors 'self'" + headers["Content-Security-Policy"] = ( + "frame-ancestors 'self'; " + # Make sure the report-uri comes out on the base_url + "report-uri " + url_path_join(self.base_url, csp_report_uri) + + ";" + ) # Allow for overriding headers for header_name,value in headers.items() : diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py index e33fe83ce..3d04861f3 100644 --- a/IPython/html/services/security/handlers.py +++ b/IPython/html/services/security/handlers.py @@ -15,7 +15,7 @@ class CSPReportHandler(IPythonHandler): def post(self): '''Log a content security policy violation report''' csp_report = self.get_json_body() - self.log.debug(csp_report) + self.log.warn(csp_report) default_handlers = [ (csp_report_uri, CSPReportHandler) From 219ca3046aceba1c2975acf79a24758b863db727 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 23:30:49 -0600 Subject: [PATCH 15/20] Clean up default content security policy setup --- IPython/html/base/handlers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index dc6a877a9..a0a9b6106 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -50,9 +50,8 @@ class AuthenticatedHandler(web.RequestHandler): if "Content-Security-Policy" not in headers: headers["Content-Security-Policy"] = ( "frame-ancestors 'self'; " - # Make sure the report-uri comes out on the base_url - "report-uri " + url_path_join(self.base_url, csp_report_uri) + - ";" + # Make sure the report-uri is relative to the base_url + "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" ) # Allow for overriding headers From 7a48687929eb8be4a367596b96b2e1544b8281ed Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Sat, 22 Nov 2014 23:44:46 -0600 Subject: [PATCH 16/20] Update CSP tests for new default. --- .../html/services/kernels/tests/test_kernels_api.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/IPython/html/services/kernels/tests/test_kernels_api.py b/IPython/html/services/kernels/tests/test_kernels_api.py index c7abbfcc6..b33142c92 100644 --- a/IPython/html/services/kernels/tests/test_kernels_api.py +++ b/IPython/html/services/kernels/tests/test_kernels_api.py @@ -65,7 +65,10 @@ class KernelAPITest(NotebookTestBase): self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['Content-Security-Policy'], "frame-ancestors 'self'") + self.assertEqual(r.headers['Content-Security-Policy'], ( + "frame-ancestors 'self'; " + "report-uri /api/security/csp-report;" + )) def test_main_kernel_handler(self): # POST request @@ -75,7 +78,10 @@ class KernelAPITest(NotebookTestBase): self.assertEqual(r.status_code, 201) self.assertIsInstance(kern1, dict) - self.assertEqual(r.headers['Content-Security-Policy'], "frame-ancestors 'self'") + self.assertEqual(r.headers['Content-Security-Policy'], ( + "frame-ancestors 'self'; " + "report-uri /api/security/csp-report;" + )) # GET request r = self.kern_api.list() From 858d153bff0d67c4e6fd5ec8dea63213c7a5644a Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Mon, 24 Nov 2014 14:38:37 -0600 Subject: [PATCH 17/20] Remove extraneous pass YOU SHALL NOT PASS! I rap fast like Shadowfax! --- IPython/html/base/handlers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py index a0a9b6106..fa58c2b25 100644 --- a/IPython/html/base/handlers.py +++ b/IPython/html/base/handlers.py @@ -63,7 +63,6 @@ class AuthenticatedHandler(web.RequestHandler): # if method is unsupported (websocket and Access-Control-Allow-Origin # for example, so just ignore) self.log.debug(e) - pass def clear_login_cookie(self): self.clear_cookie(self.cookie_name) From a6d3d6a93e56ca296af4f397f07681d55375368b Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 2 Dec 2014 14:14:18 -0600 Subject: [PATCH 18/20] Log warning directly. --- IPython/html/services/security/handlers.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/html/services/security/handlers.py b/IPython/html/services/security/handlers.py index 3d04861f3..18f7874cd 100644 --- a/IPython/html/services/security/handlers.py +++ b/IPython/html/services/security/handlers.py @@ -15,7 +15,8 @@ class CSPReportHandler(IPythonHandler): def post(self): '''Log a content security policy violation report''' csp_report = self.get_json_body() - self.log.warn(csp_report) + self.log.warn("Content security violation: %s", + self.request.body.decode('utf8', 'replace')) default_handlers = [ (csp_report_uri, CSPReportHandler) From 63f8566c6f94786524ccb423280086aa39debfb3 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 2 Dec 2014 22:38:19 +0000 Subject: [PATCH 19/20] Wait for any promises returned by a view's render method before considering the view created This lets a view wait on children views to be created before considering itself created. Thanks to @ssunkara for catching this. --- IPython/html/static/widgets/js/manager.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/IPython/html/static/widgets/js/manager.js b/IPython/html/static/widgets/js/manager.js index e7ee68653..c0f17fbdb 100644 --- a/IPython/html/static/widgets/js/manager.js +++ b/IPython/html/static/widgets/js/manager.js @@ -101,8 +101,7 @@ define([ var parameters = {model: model, options: options}; var view = new ViewType(parameters); view.listenTo(model, 'destroy', view.remove); - view.render(); - return view; + return Promise.resolve(view.render()).then(function() {return view;}); }).catch(utils.reject("Couldn't create a view for model id '" + String(model.id) + "'", true)); }); return model.state_change; From 3cad1f4b4db25b21ae8e220ed6a743bf1703e893 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 2 Dec 2014 16:51:35 -0800 Subject: [PATCH 20/20] Expose ConfigManager one level up the hierarchy Because `from IPython.html.services.config import ConfigManager` is long enough. --- IPython/html/services/config/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/IPython/html/services/config/__init__.py b/IPython/html/services/config/__init__.py index e69de29bb..d8d938020 100644 --- a/IPython/html/services/config/__init__.py +++ b/IPython/html/services/config/__init__.py @@ -0,0 +1 @@ +from .manager import ConfigManager