Show More
@@ -121,7 +121,7 b' mysqlclient==2.1.1' | |||||
121 | nbconvert==7.7.3 |
|
121 | nbconvert==7.7.3 | |
122 | beautifulsoup4==4.11.2 |
|
122 | beautifulsoup4==4.11.2 | |
123 | soupsieve==2.4 |
|
123 | soupsieve==2.4 | |
124 |
bleach==6. |
|
124 | bleach==6.1.0 | |
125 | six==1.16.0 |
|
125 | six==1.16.0 | |
126 | webencodings==0.5.1 |
|
126 | webencodings==0.5.1 | |
127 | defusedxml==0.7.1 |
|
127 | defusedxml==0.7.1 |
@@ -424,11 +424,13 b' class MarkupRenderer(object):' | |||||
424 | @classmethod |
|
424 | @classmethod | |
425 | def jupyter(cls, source, safe=True): |
|
425 | def jupyter(cls, source, safe=True): | |
426 | from rhodecode.lib import helpers |
|
426 | from rhodecode.lib import helpers | |
|
427 | from .html_sanitizer_defs import markdown_attrs, all_tags, all_styles | |||
427 |
|
428 | |||
428 | from traitlets import default, config |
|
429 | from traitlets import default, config | |
429 | import nbformat |
|
430 | import nbformat | |
430 | from nbconvert import HTMLExporter |
|
431 | from nbconvert import HTMLExporter | |
431 | from nbconvert.preprocessors import Preprocessor |
|
432 | from nbconvert.preprocessors import Preprocessor | |
|
433 | from nbconvert.preprocessors.sanitize import SanitizeHTML | |||
432 |
|
434 | |||
433 | class CustomHTMLExporter(HTMLExporter): |
|
435 | class CustomHTMLExporter(HTMLExporter): | |
434 |
|
436 | |||
@@ -439,24 +441,20 b' class MarkupRenderer(object):' | |||||
439 |
|
441 | |||
440 | class Sandbox(Preprocessor): |
|
442 | class Sandbox(Preprocessor): | |
441 |
|
443 | |||
442 |
def preprocess(self, |
|
444 | def preprocess_cell(self, cell, resources, cell_index): | |
443 | sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)' |
|
|||
444 | for cell in nb['cells']: |
|
|||
445 |
|
|
445 | if not safe: | |
446 |
|
|
446 | return cell, resources | |
|
447 | sandbox_text = 'SandBoxed(IPython.core.display.Javascript object)' | |||
|
448 | if cell.cell_type == "markdown": | |||
|
449 | cell.source = cls.sanitize_html(cell.source) | |||
|
450 | return cell, resources | |||
447 |
|
451 | |||
448 |
|
|
452 | for cell_output in cell.outputs: | |
449 | for cell_output in cell['outputs']: |
|
|||
450 |
|
|
453 | if 'data' in cell_output: | |
451 |
|
|
454 | if 'application/javascript' in cell_output['data']: | |
452 |
|
|
455 | cell_output['data']['text/plain'] = sandbox_text | |
453 |
|
|
456 | cell_output['data'].pop('application/javascript', None) | |
454 |
|
457 | return cell, resources | ||
455 | if 'source' in cell and cell['cell_type'] == 'markdown': |
|
|||
456 | # sanitize similar like in markdown |
|
|||
457 | cell['source'] = cls.sanitize_html(cell['source']) |
|
|||
458 |
|
||||
459 | return nb, resources |
|
|||
460 |
|
458 | |||
461 | def _sanitize_resources(input_resources): |
|
459 | def _sanitize_resources(input_resources): | |
462 | """ |
|
460 | """ | |
@@ -475,8 +473,29 b' class MarkupRenderer(object):' | |||||
475 |
|
473 | |||
476 | def as_html(notebook): |
|
474 | def as_html(notebook): | |
477 | conf = config.Config() |
|
475 | conf = config.Config() | |
478 | conf.CustomHTMLExporter.default_preprocessors = [Sandbox] |
|
476 | # TODO: Keep an eye on the order of preprocessors | |
|
477 | conf.CustomHTMLExporter.default_preprocessors = [Sandbox, SanitizeHTML] | |||
479 | conf.Sandbox.enabled = True |
|
478 | conf.Sandbox.enabled = True | |
|
479 | conf.SanitizeHTML.enabled = True | |||
|
480 | conf.SanitizeHTML.attributes = markdown_attrs | |||
|
481 | conf.SanitizeHTML.tags = all_tags | |||
|
482 | conf.SanitizeHTML.styles = all_styles | |||
|
483 | conf.SanitizeHTML.sanitized_output_types = { | |||
|
484 | "text/html", | |||
|
485 | "text/markdown", | |||
|
486 | } | |||
|
487 | conf.SanitizeHTML.safe_output_keys = { | |||
|
488 | "metadata", | |||
|
489 | "text/plain", | |||
|
490 | "text/latex", | |||
|
491 | "application/json", | |||
|
492 | "image/png", | |||
|
493 | "image/jpg" | |||
|
494 | "image/jpeg", | |||
|
495 | "image/svg", | |||
|
496 | "image/svg+xml" | |||
|
497 | } | |||
|
498 | ||||
480 | html_exporter = CustomHTMLExporter(config=conf) |
|
499 | html_exporter = CustomHTMLExporter(config=conf) | |
481 |
|
500 | |||
482 | (body, resources) = html_exporter.from_notebook_node(notebook) |
|
501 | (body, resources) = html_exporter.from_notebook_node(notebook) |
@@ -17,6 +17,7 b'' | |||||
17 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
17 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
18 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
19 |
|
19 | |||
|
20 | import mock | |||
20 | import pytest |
|
21 | import pytest | |
21 |
|
22 | |||
22 | from rhodecode.lib.markup_renderer import ( |
|
23 | from rhodecode.lib.markup_renderer import ( | |
@@ -778,8 +779,119 b' def test_relative_links(src_html, expect' | |||||
778 | """, "Hello, World!") |
|
779 | """, "Hello, World!") | |
779 | ]) |
|
780 | ]) | |
780 | def test_jp_notebook_html_generation(notebook_source, expected_output): |
|
781 | def test_jp_notebook_html_generation(notebook_source, expected_output): | |
781 | import mock |
|
|||
782 | with mock.patch('rhodecode.lib.helpers.asset'): |
|
782 | with mock.patch('rhodecode.lib.helpers.asset'): | |
783 | body = MarkupRenderer.jupyter(notebook_source) |
|
783 | body = MarkupRenderer.jupyter(notebook_source) | |
784 | assert "<!-- ## IPYTHON NOTEBOOK RENDERING ## -->" in body |
|
784 | assert "<!-- ## IPYTHON NOTEBOOK RENDERING ## -->" in body | |
785 | assert expected_output in body |
|
785 | assert expected_output in body | |
|
786 | ||||
|
787 | ||||
|
788 | @pytest.mark.parametrize("notebook_source, expected_output", [ | |||
|
789 | ({"cells": [ | |||
|
790 | { | |||
|
791 | "cell_type": "code", | |||
|
792 | "execution_count": 0, | |||
|
793 | "metadata": {}, | |||
|
794 | "outputs": [ | |||
|
795 | { | |||
|
796 | "data": { | |||
|
797 | "text/html": [ | |||
|
798 | "<select><iframe></select><img src=x: onerror=alert('xss')>\n" | |||
|
799 | ], | |||
|
800 | "text/plain": [] | |||
|
801 | }, | |||
|
802 | "metadata": {}, | |||
|
803 | "output_type": "display_data" | |||
|
804 | } | |||
|
805 | ], | |||
|
806 | "source": [ | |||
|
807 | "" | |||
|
808 | ] | |||
|
809 | } | |||
|
810 | ], | |||
|
811 | "metadata": { | |||
|
812 | "kernelspec": { | |||
|
813 | "display_name": "Python 3", | |||
|
814 | "language": "python", | |||
|
815 | "name": "python3" | |||
|
816 | }, | |||
|
817 | "language_info": { | |||
|
818 | "codemirror_mode": { | |||
|
819 | "name": "ipython", | |||
|
820 | "version": 3 | |||
|
821 | }, | |||
|
822 | "file_extension": ".py", | |||
|
823 | "mimetype": "text/x-python", | |||
|
824 | "name": "python", | |||
|
825 | "nbconvert_exporter": "python", | |||
|
826 | "pygments_lexer": "ipython3", | |||
|
827 | "version": "3.9.6" | |||
|
828 | } | |||
|
829 | }, | |||
|
830 | "nbformat": 4, | |||
|
831 | "nbformat_minor": 5 | |||
|
832 | }, '<select></select><img alt="No description has been provided for this image"/>'), | |||
|
833 | ({ | |||
|
834 | "cells": [ | |||
|
835 | { | |||
|
836 | "cell_type": "markdown", | |||
|
837 | "metadata": {}, | |||
|
838 | "source": [ | |||
|
839 | "<label for=buttonid style=\"cursor: text\">not safe to click here</label>\n" | |||
|
840 | ] | |||
|
841 | }, | |||
|
842 | { | |||
|
843 | "cell_type": "markdown", | |||
|
844 | "metadata": { | |||
|
845 | "highlighter": "codemirror" | |||
|
846 | }, | |||
|
847 | "source": "<div class=\"jp-InputArea-editor\"><div class=\"CodeMirror cm-s-jupyter\"><div class=\"CodeMirror-scroll\"><div class=\"CodeMirror-sizer\"><div style=\"top:0px; position:relative\"><div class=\"CodeMirror-lines\"><div style=\"outline:none; position:relative\"><div class=\"CodeMirror-code\"><div style=\"position: relative\"><label for=buttonid style=\"cursor: text\"><pre class=\"CodeMirror-line\" style=\"background:transparent\"><span style=\"padding-right: 0.1px\"><span class=\"cm-builtin\">print</span>(<span class=\"cm-string\">"Also not safe to click here"</span>)</span></pre></label></div></div></div></div></div></div></div></div></div>" | |||
|
848 | }, | |||
|
849 | { | |||
|
850 | "cell_type": "code", | |||
|
851 | "execution_count": 0, | |||
|
852 | "metadata": { | |||
|
853 | "xrender": True | |||
|
854 | }, | |||
|
855 | "outputs": [ | |||
|
856 | { | |||
|
857 | "data": { | |||
|
858 | "text/html": [ | |||
|
859 | "<form id=jp-mk-edit action='javascript:alert(1)' style='display:none'><button type=submit id=buttonid></form>\n" | |||
|
860 | ], | |||
|
861 | "text/plain": [] | |||
|
862 | }, | |||
|
863 | "metadata": {}, | |||
|
864 | "output_type": "display_data" | |||
|
865 | } | |||
|
866 | ], | |||
|
867 | "source": "" | |||
|
868 | } | |||
|
869 | ], | |||
|
870 | "metadata": { | |||
|
871 | "kernelspec": { | |||
|
872 | "display_name": "Python 3", | |||
|
873 | "language": "python", | |||
|
874 | "name": "python3" | |||
|
875 | }, | |||
|
876 | "language_info": { | |||
|
877 | "codemirror_mode": { | |||
|
878 | "name": "ipython", | |||
|
879 | "version": 3 | |||
|
880 | }, | |||
|
881 | "file_extension": ".py", | |||
|
882 | "mimetype": "text/x-python", | |||
|
883 | "name": "python", | |||
|
884 | "nbconvert_exporter": "python", | |||
|
885 | "pygments_lexer": "ipython3", | |||
|
886 | "version": "3.9.6" | |||
|
887 | } | |||
|
888 | }, | |||
|
889 | "nbformat": 4, | |||
|
890 | "nbformat_minor": 5 | |||
|
891 | }, '<form style="display:none;">') | |||
|
892 | ]) | |||
|
893 | def test_jp_notebook_against_xss(notebook_source, expected_output): | |||
|
894 | import json | |||
|
895 | with mock.patch('rhodecode.lib.helpers.asset'): | |||
|
896 | body = MarkupRenderer.jupyter(json.dumps(notebook_source)) | |||
|
897 | assert expected_output in body |
General Comments 0
You need to be logged in to leave comments.
Login now