Show More
@@ -0,0 +1,155 | |||||
|
1 | { | |||
|
2 | "metadata": { | |||
|
3 | "cell_tags": ["<None>", null], | |||
|
4 | "name": "", | |||
|
5 | "kernel_info": { | |||
|
6 | "name": "python", | |||
|
7 | "language": "python" | |||
|
8 | } | |||
|
9 | }, | |||
|
10 | "nbformat": 4, | |||
|
11 | "nbformat_minor": 0, | |||
|
12 | "cells": [ | |||
|
13 | { | |||
|
14 | "cell_type": "heading", | |||
|
15 | "level": 1, | |||
|
16 | "metadata": {}, | |||
|
17 | "source": [ | |||
|
18 | "nbconvert latex test" | |||
|
19 | ] | |||
|
20 | }, | |||
|
21 | { | |||
|
22 | "cell_type": "markdown", | |||
|
23 | "metadata": {}, | |||
|
24 | "source": [ | |||
|
25 | "**Lorem ipsum** dolor sit amet, consectetur adipiscing elit. Nunc luctus bibendum felis dictum sodales. Ut suscipit, orci ut interdum imperdiet, purus ligula mollis *justo*, non malesuada nisl augue eget lorem. Donec bibendum, erat sit amet porttitor aliquam, urna lorem ornare libero, in vehicula diam diam ut ante. Nam non urna rhoncus, accumsan elit sit amet, mollis tellus. Vestibulum nec tellus metus. Vestibulum tempor, ligula et vehicula rhoncus, sapien turpis faucibus lorem, id dapibus turpis mauris ac orci. Sed volutpat vestibulum venenatis." | |||
|
26 | ] | |||
|
27 | }, | |||
|
28 | { | |||
|
29 | "cell_type": "heading", | |||
|
30 | "level": 2, | |||
|
31 | "metadata": {}, | |||
|
32 | "source": [ | |||
|
33 | "Printed Using Python" | |||
|
34 | ] | |||
|
35 | }, | |||
|
36 | { | |||
|
37 | "cell_type": "code", | |||
|
38 | "source": [ | |||
|
39 | "print(\"hello\")" | |||
|
40 | ], | |||
|
41 | "metadata": { | |||
|
42 | "collapsed": false, | |||
|
43 | "autoscroll": false | |||
|
44 | }, | |||
|
45 | "outputs": [ | |||
|
46 | { | |||
|
47 | "output_type": "stream", | |||
|
48 | "metadata": {}, | |||
|
49 | "name": "stdout", | |||
|
50 | "text": [ | |||
|
51 | "hello\n" | |||
|
52 | ] | |||
|
53 | } | |||
|
54 | ], | |||
|
55 | "prompt_number": 1 | |||
|
56 | }, | |||
|
57 | { | |||
|
58 | "cell_type": "heading", | |||
|
59 | "level": 2, | |||
|
60 | "metadata": {}, | |||
|
61 | "source": [ | |||
|
62 | "Pyout" | |||
|
63 | ] | |||
|
64 | }, | |||
|
65 | { | |||
|
66 | "cell_type": "code", | |||
|
67 | "source": [ | |||
|
68 | "from IPython.display import HTML\n", | |||
|
69 | "HTML(\"\"\"\n", | |||
|
70 | "<script>\n", | |||
|
71 | "console.log(\"hello\");\n", | |||
|
72 | "</script>\n", | |||
|
73 | "<b>HTML</b>\n", | |||
|
74 | "\"\"\")" | |||
|
75 | ], | |||
|
76 | "metadata": { | |||
|
77 | "collapsed": false, | |||
|
78 | "autoscroll": false | |||
|
79 | }, | |||
|
80 | "outputs": [ | |||
|
81 | { | |||
|
82 | "text/html": [ | |||
|
83 | "\n", | |||
|
84 | "<script>\n", | |||
|
85 | "console.log(\"hello\");\n", | |||
|
86 | "</script>\n", | |||
|
87 | "<b>HTML</b>\n" | |||
|
88 | ], | |||
|
89 | "metadata": {}, | |||
|
90 | "output_type": "execute_result", | |||
|
91 | "prompt_number": 3, | |||
|
92 | "text/plain": [ | |||
|
93 | "<IPython.core.display.HTML at 0x1112757d0>" | |||
|
94 | ] | |||
|
95 | } | |||
|
96 | ], | |||
|
97 | "prompt_number": 3 | |||
|
98 | }, | |||
|
99 | { | |||
|
100 | "cell_type": "code", | |||
|
101 | "source": [ | |||
|
102 | "%%javascript\n", | |||
|
103 | "console.log(\"hi\");" | |||
|
104 | ], | |||
|
105 | "metadata": { | |||
|
106 | "collapsed": false, | |||
|
107 | "autoscroll": false | |||
|
108 | }, | |||
|
109 | "outputs": [ | |||
|
110 | { | |||
|
111 | "text/javascript": [ | |||
|
112 | "console.log(\"hi\");" | |||
|
113 | ], | |||
|
114 | "metadata": {}, | |||
|
115 | "output_type": "display_data", | |||
|
116 | "text/plain": [ | |||
|
117 | "<IPython.core.display.Javascript at 0x1112b4b50>" | |||
|
118 | ] | |||
|
119 | } | |||
|
120 | ], | |||
|
121 | "prompt_number": 7 | |||
|
122 | }, | |||
|
123 | { | |||
|
124 | "cell_type": "heading", | |||
|
125 | "level": 3, | |||
|
126 | "metadata": {}, | |||
|
127 | "source": [ | |||
|
128 | "Image" | |||
|
129 | ] | |||
|
130 | }, | |||
|
131 | { | |||
|
132 | "cell_type": "code", | |||
|
133 | "source": [ | |||
|
134 | "from IPython.display import Image\n", | |||
|
135 | "Image(\"http://ipython.org/_static/IPy_header.png\")" | |||
|
136 | ], | |||
|
137 | "metadata": { | |||
|
138 | "collapsed": false, | |||
|
139 | "autoscroll": false | |||
|
140 | }, | |||
|
141 | "outputs": [ | |||
|
142 | { | |||
|
143 | "metadata": {}, | |||
|
144 | "output_type": "execute_result", | |||
|
145 | "image/png": "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\n", | |||
|
146 | "prompt_number": 6, | |||
|
147 | "text/plain": [ | |||
|
148 | "<IPython.core.display.Image at 0x111275490>" | |||
|
149 | ] | |||
|
150 | } | |||
|
151 | ], | |||
|
152 | "prompt_number": 6 | |||
|
153 | } | |||
|
154 | ] | |||
|
155 | } |
@@ -1,70 +1,54 | |||||
1 | """API for converting notebooks between versions. |
|
1 | """API for converting notebooks between versions.""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Jonathan Frederic |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2013 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | from .reader import get_version, versions |
|
6 | from .reader import get_version, versions | |
20 |
|
7 | |||
21 | #----------------------------------------------------------------------------- |
|
|||
22 | # Functions |
|
|||
23 | #----------------------------------------------------------------------------- |
|
|||
24 |
|
8 | |||
25 | def convert(nb, to_version): |
|
9 | def convert(nb, to_version): | |
26 | """Convert a notebook node object to a specific version. Assumes that |
|
10 | """Convert a notebook node object to a specific version. Assumes that | |
27 | all the versions starting from 1 to the latest major X are implemented. |
|
11 | all the versions starting from 1 to the latest major X are implemented. | |
28 | In other words, there should never be a case where v1 v2 v3 v5 exist without |
|
12 | In other words, there should never be a case where v1 v2 v3 v5 exist without | |
29 | a v4. Also assumes that all conversions can be made in one step increments |
|
13 | a v4. Also assumes that all conversions can be made in one step increments | |
30 | between major versions and ignores minor revisions. |
|
14 | between major versions and ignores minor revisions. | |
31 |
|
15 | |||
32 | Parameters |
|
16 | Parameters | |
33 | ---------- |
|
17 | ---------- | |
34 | nb : NotebookNode |
|
18 | nb : NotebookNode | |
35 | to_version : int |
|
19 | to_version : int | |
36 | Major revision to convert the notebook to. Can either be an upgrade or |
|
20 | Major revision to convert the notebook to. Can either be an upgrade or | |
37 | a downgrade. |
|
21 | a downgrade. | |
38 | """ |
|
22 | """ | |
39 |
|
23 | |||
40 | # Get input notebook version. |
|
24 | # Get input notebook version. | |
41 | (version, version_minor) = get_version(nb) |
|
25 | (version, version_minor) = get_version(nb) | |
42 |
|
26 | |||
43 | # Check if destination is current version, if so return contents |
|
27 | # Check if destination is current version, if so return contents | |
44 | if version == to_version: |
|
28 | if version == to_version: | |
45 | return nb |
|
29 | return nb | |
46 |
|
30 | |||
47 | # If the version exist, try to convert to it one step at a time. |
|
31 | # If the version exist, try to convert to it one step at a time. | |
48 | elif to_version in versions: |
|
32 | elif to_version in versions: | |
49 |
|
33 | |||
50 | # Get the the version that this recursion will convert to as a step |
|
34 | # Get the the version that this recursion will convert to as a step | |
51 | # closer to the final revision. Make sure the newer of the conversion |
|
35 | # closer to the final revision. Make sure the newer of the conversion | |
52 | # functions is used to perform the conversion. |
|
36 | # functions is used to perform the conversion. | |
53 | if to_version > version: |
|
37 | if to_version > version: | |
54 | step_version = version + 1 |
|
38 | step_version = version + 1 | |
55 | convert_function = versions[step_version].upgrade |
|
39 | convert_function = versions[step_version].upgrade | |
56 | else: |
|
40 | else: | |
57 | step_version = version - 1 |
|
41 | step_version = version - 1 | |
58 | convert_function = versions[version].downgrade |
|
42 | convert_function = versions[version].downgrade | |
59 |
|
43 | |||
60 | # Convert and make sure version changed during conversion. |
|
44 | # Convert and make sure version changed during conversion. | |
61 | converted = convert_function(nb) |
|
45 | converted = convert_function(nb) | |
62 | if converted.get('nbformat', 1) == version: |
|
46 | if converted.get('nbformat', 1) == version: | |
63 |
raise |
|
47 | raise ValueError("Cannot convert notebook from v%d to v%d. Operation" \ | |
64 | "failed silently." % (version, step_version)) |
|
48 | "failed silently." % (version, step_version)) | |
65 |
|
49 | |||
66 | # Recursively convert until target version is reached. |
|
50 | # Recursively convert until target version is reached. | |
67 | return convert(converted, to_version) |
|
51 | return convert(converted, to_version) | |
68 | else: |
|
52 | else: | |
69 |
raise |
|
53 | raise ValueError("Cannot convert notebook to v%d because that " \ | |
70 | "version doesn't exist" % (to_version)) |
|
54 | "version doesn't exist" % (to_version)) |
@@ -1,109 +1,95 | |||||
1 |
"""API for reading notebooks |
|
1 | """API for reading notebooks of different versions""" | |
2 |
|
2 | |||
3 | Authors: |
|
3 | # Copyright (c) IPython Development Team. | |
4 |
|
4 | # Distributed under the terms of the Modified BSD License. | ||
5 | * Jonathan Frederic |
|
|||
6 | """ |
|
|||
7 |
|
||||
8 | #----------------------------------------------------------------------------- |
|
|||
9 | # Copyright (C) 2013 The IPython Development Team |
|
|||
10 | # |
|
|||
11 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
12 | # the file COPYING, distributed as part of this software. |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
||||
15 | #----------------------------------------------------------------------------- |
|
|||
16 | # Imports |
|
|||
17 | #----------------------------------------------------------------------------- |
|
|||
18 |
|
5 | |||
19 | import json |
|
6 | import json | |
20 |
|
7 | |||
21 | from . import v1 |
|
8 | from . import v1 | |
22 | from . import v2 |
|
9 | from . import v2 | |
23 | from . import v3 |
|
10 | from . import v3 | |
|
11 | from . import v4 | |||
24 |
|
12 | |||
25 | versions = { |
|
13 | versions = { | |
26 | 1: v1, |
|
14 | 1: v1, | |
27 | 2: v2, |
|
15 | 2: v2, | |
28 | 3: v3, |
|
16 | 3: v3, | |
|
17 | 4: v4, | |||
29 | } |
|
18 | } | |
30 |
|
19 | |||
31 | #----------------------------------------------------------------------------- |
|
|||
32 | # Code |
|
|||
33 | #----------------------------------------------------------------------------- |
|
|||
34 |
|
20 | |||
35 | class NotJSONError(ValueError): |
|
21 | class NotJSONError(ValueError): | |
36 | pass |
|
22 | pass | |
37 |
|
23 | |||
38 | def parse_json(s, **kwargs): |
|
24 | def parse_json(s, **kwargs): | |
39 | """Parse a JSON string into a dict.""" |
|
25 | """Parse a JSON string into a dict.""" | |
40 | try: |
|
26 | try: | |
41 | nb_dict = json.loads(s, **kwargs) |
|
27 | nb_dict = json.loads(s, **kwargs) | |
42 | except ValueError: |
|
28 | except ValueError: | |
43 | # Limit the error message to 80 characters. Display whatever JSON will fit. |
|
29 | # Limit the error message to 80 characters. Display whatever JSON will fit. | |
44 | raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...") |
|
30 | raise NotJSONError(("Notebook does not appear to be JSON: %r" % s)[:77] + "...") | |
45 | return nb_dict |
|
31 | return nb_dict | |
46 |
|
32 | |||
47 | # High level API |
|
33 | # High level API | |
48 |
|
34 | |||
49 | def get_version(nb): |
|
35 | def get_version(nb): | |
50 | """Get the version of a notebook. |
|
36 | """Get the version of a notebook. | |
51 |
|
37 | |||
52 | Parameters |
|
38 | Parameters | |
53 | ---------- |
|
39 | ---------- | |
54 | nb : dict |
|
40 | nb : dict | |
55 | NotebookNode or dict containing notebook data. |
|
41 | NotebookNode or dict containing notebook data. | |
56 |
|
42 | |||
57 | Returns |
|
43 | Returns | |
58 | ------- |
|
44 | ------- | |
59 | Tuple containing major (int) and minor (int) version numbers |
|
45 | Tuple containing major (int) and minor (int) version numbers | |
60 | """ |
|
46 | """ | |
61 | major = nb.get('nbformat', 1) |
|
47 | major = nb.get('nbformat', 1) | |
62 | minor = nb.get('nbformat_minor', 0) |
|
48 | minor = nb.get('nbformat_minor', 0) | |
63 | return (major, minor) |
|
49 | return (major, minor) | |
64 |
|
50 | |||
65 |
|
51 | |||
66 | def reads(s, **kwargs): |
|
52 | def reads(s, **kwargs): | |
67 | """Read a notebook from a json string and return the |
|
53 | """Read a notebook from a json string and return the | |
68 | NotebookNode object. |
|
54 | NotebookNode object. | |
69 |
|
55 | |||
70 | This function properly reads notebooks of any version. No version |
|
56 | This function properly reads notebooks of any version. No version | |
71 | conversion is performed. |
|
57 | conversion is performed. | |
72 |
|
58 | |||
73 | Parameters |
|
59 | Parameters | |
74 | ---------- |
|
60 | ---------- | |
75 | s : unicode |
|
61 | s : unicode | |
76 | The raw unicode string to read the notebook from. |
|
62 | The raw unicode string to read the notebook from. | |
77 |
|
63 | |||
78 | Returns |
|
64 | Returns | |
79 | ------- |
|
65 | ------- | |
80 | nb : NotebookNode |
|
66 | nb : NotebookNode | |
81 | The notebook that was read. |
|
67 | The notebook that was read. | |
82 | """ |
|
68 | """ | |
83 | from .current import NBFormatError |
|
69 | from .current import NBFormatError | |
84 |
|
70 | |||
85 | nb_dict = parse_json(s, **kwargs) |
|
71 | nb_dict = parse_json(s, **kwargs) | |
86 | (major, minor) = get_version(nb_dict) |
|
72 | (major, minor) = get_version(nb_dict) | |
87 | if major in versions: |
|
73 | if major in versions: | |
88 | return versions[major].to_notebook_json(nb_dict, minor=minor) |
|
74 | return versions[major].to_notebook_json(nb_dict, minor=minor) | |
89 | else: |
|
75 | else: | |
90 | raise NBFormatError('Unsupported nbformat version %s' % major) |
|
76 | raise NBFormatError('Unsupported nbformat version %s' % major) | |
91 |
|
77 | |||
92 |
|
78 | |||
93 | def read(fp, **kwargs): |
|
79 | def read(fp, **kwargs): | |
94 | """Read a notebook from a file and return the NotebookNode object. |
|
80 | """Read a notebook from a file and return the NotebookNode object. | |
95 |
|
81 | |||
96 | This function properly reads notebooks of any version. No version |
|
82 | This function properly reads notebooks of any version. No version | |
97 | conversion is performed. |
|
83 | conversion is performed. | |
98 |
|
84 | |||
99 | Parameters |
|
85 | Parameters | |
100 | ---------- |
|
86 | ---------- | |
101 | fp : file |
|
87 | fp : file | |
102 | Any file-like object with a read method. |
|
88 | Any file-like object with a read method. | |
103 |
|
89 | |||
104 | Returns |
|
90 | Returns | |
105 | ------- |
|
91 | ------- | |
106 | nb : NotebookNode |
|
92 | nb : NotebookNode | |
107 | The notebook that was read. |
|
93 | The notebook that was read. | |
108 | """ |
|
94 | """ | |
109 | return reads(fp.read(), **kwargs) |
|
95 | return reads(fp.read(), **kwargs) |
@@ -1,312 +1,310 | |||||
1 | """Functions for signing notebooks""" |
|
1 | """Functions for signing notebooks""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | import base64 |
|
6 | import base64 | |
7 | from contextlib import contextmanager |
|
7 | from contextlib import contextmanager | |
8 | import hashlib |
|
8 | import hashlib | |
9 | from hmac import HMAC |
|
9 | from hmac import HMAC | |
10 | import io |
|
10 | import io | |
11 | import os |
|
11 | import os | |
12 |
|
12 | |||
13 | from IPython.utils.py3compat import string_types, unicode_type, cast_bytes |
|
13 | from IPython.utils.py3compat import string_types, unicode_type, cast_bytes | |
14 | from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool |
|
14 | from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool | |
15 | from IPython.config import LoggingConfigurable, MultipleInstanceError |
|
15 | from IPython.config import LoggingConfigurable, MultipleInstanceError | |
16 | from IPython.core.application import BaseIPythonApplication, base_flags |
|
16 | from IPython.core.application import BaseIPythonApplication, base_flags | |
17 |
|
17 | |||
18 | from .current import read, write |
|
18 | from .current import read, write | |
19 |
|
19 | |||
20 |
|
||||
21 | try: |
|
20 | try: | |
22 | # Python 3 |
|
21 | # Python 3 | |
23 | algorithms = hashlib.algorithms_guaranteed |
|
22 | algorithms = hashlib.algorithms_guaranteed | |
24 | except AttributeError: |
|
23 | except AttributeError: | |
25 | algorithms = hashlib.algorithms |
|
24 | algorithms = hashlib.algorithms | |
26 |
|
25 | |||
27 |
|
26 | |||
28 | def yield_everything(obj): |
|
27 | def yield_everything(obj): | |
29 | """Yield every item in a container as bytes |
|
28 | """Yield every item in a container as bytes | |
30 |
|
29 | |||
31 | Allows any JSONable object to be passed to an HMAC digester |
|
30 | Allows any JSONable object to be passed to an HMAC digester | |
32 | without having to serialize the whole thing. |
|
31 | without having to serialize the whole thing. | |
33 | """ |
|
32 | """ | |
34 | if isinstance(obj, dict): |
|
33 | if isinstance(obj, dict): | |
35 | for key in sorted(obj): |
|
34 | for key in sorted(obj): | |
36 | value = obj[key] |
|
35 | value = obj[key] | |
37 | yield cast_bytes(key) |
|
36 | yield cast_bytes(key) | |
38 | for b in yield_everything(value): |
|
37 | for b in yield_everything(value): | |
39 | yield b |
|
38 | yield b | |
40 | elif isinstance(obj, (list, tuple)): |
|
39 | elif isinstance(obj, (list, tuple)): | |
41 | for element in obj: |
|
40 | for element in obj: | |
42 | for b in yield_everything(element): |
|
41 | for b in yield_everything(element): | |
43 | yield b |
|
42 | yield b | |
44 | elif isinstance(obj, unicode_type): |
|
43 | elif isinstance(obj, unicode_type): | |
45 | yield obj.encode('utf8') |
|
44 | yield obj.encode('utf8') | |
46 | else: |
|
45 | else: | |
47 | yield unicode_type(obj).encode('utf8') |
|
46 | yield unicode_type(obj).encode('utf8') | |
48 |
|
47 | |||
49 |
|
48 | |||
50 | @contextmanager |
|
49 | @contextmanager | |
51 | def signature_removed(nb): |
|
50 | def signature_removed(nb): | |
52 | """Context manager for operating on a notebook with its signature removed |
|
51 | """Context manager for operating on a notebook with its signature removed | |
53 |
|
52 | |||
54 | Used for excluding the previous signature when computing a notebook's signature. |
|
53 | Used for excluding the previous signature when computing a notebook's signature. | |
55 | """ |
|
54 | """ | |
56 | save_signature = nb['metadata'].pop('signature', None) |
|
55 | save_signature = nb['metadata'].pop('signature', None) | |
57 | try: |
|
56 | try: | |
58 | yield |
|
57 | yield | |
59 | finally: |
|
58 | finally: | |
60 | if save_signature is not None: |
|
59 | if save_signature is not None: | |
61 | nb['metadata']['signature'] = save_signature |
|
60 | nb['metadata']['signature'] = save_signature | |
62 |
|
61 | |||
63 |
|
62 | |||
64 | class NotebookNotary(LoggingConfigurable): |
|
63 | class NotebookNotary(LoggingConfigurable): | |
65 | """A class for computing and verifying notebook signatures.""" |
|
64 | """A class for computing and verifying notebook signatures.""" | |
66 |
|
65 | |||
67 | profile_dir = Instance("IPython.core.profiledir.ProfileDir") |
|
66 | profile_dir = Instance("IPython.core.profiledir.ProfileDir") | |
68 | def _profile_dir_default(self): |
|
67 | def _profile_dir_default(self): | |
69 | from IPython.core.application import BaseIPythonApplication |
|
68 | from IPython.core.application import BaseIPythonApplication | |
70 | app = None |
|
69 | app = None | |
71 | try: |
|
70 | try: | |
72 | if BaseIPythonApplication.initialized(): |
|
71 | if BaseIPythonApplication.initialized(): | |
73 | app = BaseIPythonApplication.instance() |
|
72 | app = BaseIPythonApplication.instance() | |
74 | except MultipleInstanceError: |
|
73 | except MultipleInstanceError: | |
75 | pass |
|
74 | pass | |
76 | if app is None: |
|
75 | if app is None: | |
77 | # create an app, without the global instance |
|
76 | # create an app, without the global instance | |
78 | app = BaseIPythonApplication() |
|
77 | app = BaseIPythonApplication() | |
79 | app.initialize(argv=[]) |
|
78 | app.initialize(argv=[]) | |
80 | return app.profile_dir |
|
79 | return app.profile_dir | |
81 |
|
80 | |||
82 | algorithm = Enum(algorithms, default_value='sha256', config=True, |
|
81 | algorithm = Enum(algorithms, default_value='sha256', config=True, | |
83 | help="""The hashing algorithm used to sign notebooks.""" |
|
82 | help="""The hashing algorithm used to sign notebooks.""" | |
84 | ) |
|
83 | ) | |
85 | def _algorithm_changed(self, name, old, new): |
|
84 | def _algorithm_changed(self, name, old, new): | |
86 | self.digestmod = getattr(hashlib, self.algorithm) |
|
85 | self.digestmod = getattr(hashlib, self.algorithm) | |
87 |
|
86 | |||
88 | digestmod = Any() |
|
87 | digestmod = Any() | |
89 | def _digestmod_default(self): |
|
88 | def _digestmod_default(self): | |
90 | return getattr(hashlib, self.algorithm) |
|
89 | return getattr(hashlib, self.algorithm) | |
91 |
|
90 | |||
92 | secret_file = Unicode(config=True, |
|
91 | secret_file = Unicode(config=True, | |
93 | help="""The file where the secret key is stored.""" |
|
92 | help="""The file where the secret key is stored.""" | |
94 | ) |
|
93 | ) | |
95 | def _secret_file_default(self): |
|
94 | def _secret_file_default(self): | |
96 | if self.profile_dir is None: |
|
95 | if self.profile_dir is None: | |
97 | return '' |
|
96 | return '' | |
98 | return os.path.join(self.profile_dir.security_dir, 'notebook_secret') |
|
97 | return os.path.join(self.profile_dir.security_dir, 'notebook_secret') | |
99 |
|
98 | |||
100 | secret = Bytes(config=True, |
|
99 | secret = Bytes(config=True, | |
101 | help="""The secret key with which notebooks are signed.""" |
|
100 | help="""The secret key with which notebooks are signed.""" | |
102 | ) |
|
101 | ) | |
103 | def _secret_default(self): |
|
102 | def _secret_default(self): | |
104 | # note : this assumes an Application is running |
|
103 | # note : this assumes an Application is running | |
105 | if os.path.exists(self.secret_file): |
|
104 | if os.path.exists(self.secret_file): | |
106 | with io.open(self.secret_file, 'rb') as f: |
|
105 | with io.open(self.secret_file, 'rb') as f: | |
107 | return f.read() |
|
106 | return f.read() | |
108 | else: |
|
107 | else: | |
109 | secret = base64.encodestring(os.urandom(1024)) |
|
108 | secret = base64.encodestring(os.urandom(1024)) | |
110 | self._write_secret_file(secret) |
|
109 | self._write_secret_file(secret) | |
111 | return secret |
|
110 | return secret | |
112 |
|
111 | |||
113 | def _write_secret_file(self, secret): |
|
112 | def _write_secret_file(self, secret): | |
114 | """write my secret to my secret_file""" |
|
113 | """write my secret to my secret_file""" | |
115 | self.log.info("Writing notebook-signing key to %s", self.secret_file) |
|
114 | self.log.info("Writing notebook-signing key to %s", self.secret_file) | |
116 | with io.open(self.secret_file, 'wb') as f: |
|
115 | with io.open(self.secret_file, 'wb') as f: | |
117 | f.write(secret) |
|
116 | f.write(secret) | |
118 | try: |
|
117 | try: | |
119 | os.chmod(self.secret_file, 0o600) |
|
118 | os.chmod(self.secret_file, 0o600) | |
120 | except OSError: |
|
119 | except OSError: | |
121 | self.log.warn( |
|
120 | self.log.warn( | |
122 | "Could not set permissions on %s", |
|
121 | "Could not set permissions on %s", | |
123 | self.secret_file |
|
122 | self.secret_file | |
124 | ) |
|
123 | ) | |
125 | return secret |
|
124 | return secret | |
126 |
|
125 | |||
127 | def compute_signature(self, nb): |
|
126 | def compute_signature(self, nb): | |
128 | """Compute a notebook's signature |
|
127 | """Compute a notebook's signature | |
129 |
|
128 | |||
130 | by hashing the entire contents of the notebook via HMAC digest. |
|
129 | by hashing the entire contents of the notebook via HMAC digest. | |
131 | """ |
|
130 | """ | |
132 | hmac = HMAC(self.secret, digestmod=self.digestmod) |
|
131 | hmac = HMAC(self.secret, digestmod=self.digestmod) | |
133 | # don't include the previous hash in the content to hash |
|
132 | # don't include the previous hash in the content to hash | |
134 | with signature_removed(nb): |
|
133 | with signature_removed(nb): | |
135 | # sign the whole thing |
|
134 | # sign the whole thing | |
136 | for b in yield_everything(nb): |
|
135 | for b in yield_everything(nb): | |
137 | hmac.update(b) |
|
136 | hmac.update(b) | |
138 |
|
137 | |||
139 | return hmac.hexdigest() |
|
138 | return hmac.hexdigest() | |
140 |
|
139 | |||
141 | def check_signature(self, nb): |
|
140 | def check_signature(self, nb): | |
142 | """Check a notebook's stored signature |
|
141 | """Check a notebook's stored signature | |
143 |
|
142 | |||
144 | If a signature is stored in the notebook's metadata, |
|
143 | If a signature is stored in the notebook's metadata, | |
145 | a new signature is computed and compared with the stored value. |
|
144 | a new signature is computed and compared with the stored value. | |
146 |
|
145 | |||
147 | Returns True if the signature is found and matches, False otherwise. |
|
146 | Returns True if the signature is found and matches, False otherwise. | |
148 |
|
147 | |||
149 | The following conditions must all be met for a notebook to be trusted: |
|
148 | The following conditions must all be met for a notebook to be trusted: | |
150 | - a signature is stored in the form 'scheme:hexdigest' |
|
149 | - a signature is stored in the form 'scheme:hexdigest' | |
151 | - the stored scheme matches the requested scheme |
|
150 | - the stored scheme matches the requested scheme | |
152 | - the requested scheme is available from hashlib |
|
151 | - the requested scheme is available from hashlib | |
153 | - the computed hash from notebook_signature matches the stored hash |
|
152 | - the computed hash from notebook_signature matches the stored hash | |
154 | """ |
|
153 | """ | |
155 | stored_signature = nb['metadata'].get('signature', None) |
|
154 | stored_signature = nb['metadata'].get('signature', None) | |
156 | if not stored_signature \ |
|
155 | if not stored_signature \ | |
157 | or not isinstance(stored_signature, string_types) \ |
|
156 | or not isinstance(stored_signature, string_types) \ | |
158 | or ':' not in stored_signature: |
|
157 | or ':' not in stored_signature: | |
159 | return False |
|
158 | return False | |
160 | stored_algo, sig = stored_signature.split(':', 1) |
|
159 | stored_algo, sig = stored_signature.split(':', 1) | |
161 | if self.algorithm != stored_algo: |
|
160 | if self.algorithm != stored_algo: | |
162 | return False |
|
161 | return False | |
163 | my_signature = self.compute_signature(nb) |
|
162 | my_signature = self.compute_signature(nb) | |
164 | return my_signature == sig |
|
163 | return my_signature == sig | |
165 |
|
164 | |||
166 | def sign(self, nb): |
|
165 | def sign(self, nb): | |
167 | """Sign a notebook, indicating that its output is trusted |
|
166 | """Sign a notebook, indicating that its output is trusted | |
168 |
|
167 | |||
169 | stores 'algo:hmac-hexdigest' in notebook.metadata.signature |
|
168 | stores 'algo:hmac-hexdigest' in notebook.metadata.signature | |
170 |
|
169 | |||
171 | e.g. 'sha256:deadbeef123...' |
|
170 | e.g. 'sha256:deadbeef123...' | |
172 | """ |
|
171 | """ | |
173 | signature = self.compute_signature(nb) |
|
172 | signature = self.compute_signature(nb) | |
174 | nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) |
|
173 | nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature) | |
175 |
|
174 | |||
176 | def mark_cells(self, nb, trusted): |
|
175 | def mark_cells(self, nb, trusted): | |
177 | """Mark cells as trusted if the notebook's signature can be verified |
|
176 | """Mark cells as trusted if the notebook's signature can be verified | |
178 |
|
177 | |||
179 | Sets ``cell.metadata.trusted = True | False`` on all code cells, |
|
178 | Sets ``cell.metadata.trusted = True | False`` on all code cells, | |
180 | depending on whether the stored signature can be verified. |
|
179 | depending on whether the stored signature can be verified. | |
181 |
|
180 | |||
182 | This function is the inverse of check_cells |
|
181 | This function is the inverse of check_cells | |
183 | """ |
|
182 | """ | |
184 |
if not nb[' |
|
183 | if not nb['cells']: | |
185 | # nothing to mark if there are no cells |
|
184 | # nothing to mark if there are no cells | |
186 | return |
|
185 | return | |
187 |
for cell in nb[' |
|
186 | for cell in nb['cells']: | |
188 | if cell['cell_type'] == 'code': |
|
187 | if cell['cell_type'] == 'code': | |
189 | cell['metadata']['trusted'] = trusted |
|
188 | cell['metadata']['trusted'] = trusted | |
190 |
|
189 | |||
191 | def _check_cell(self, cell): |
|
190 | def _check_cell(self, cell): | |
192 | """Do we trust an individual cell? |
|
191 | """Do we trust an individual cell? | |
193 |
|
192 | |||
194 | Return True if: |
|
193 | Return True if: | |
195 |
|
194 | |||
196 | - cell is explicitly trusted |
|
195 | - cell is explicitly trusted | |
197 | - cell has no potentially unsafe rich output |
|
196 | - cell has no potentially unsafe rich output | |
198 |
|
197 | |||
199 | If a cell has no output, or only simple print statements, |
|
198 | If a cell has no output, or only simple print statements, | |
200 | it will always be trusted. |
|
199 | it will always be trusted. | |
201 | """ |
|
200 | """ | |
202 | # explicitly trusted |
|
201 | # explicitly trusted | |
203 | if cell['metadata'].pop("trusted", False): |
|
202 | if cell['metadata'].pop("trusted", False): | |
204 | return True |
|
203 | return True | |
205 |
|
204 | |||
206 | # explicitly safe output |
|
205 | # explicitly safe output | |
207 | safe = { |
|
206 | safe = { | |
208 | 'text/plain', 'image/png', 'image/jpeg', |
|
207 | 'text/plain', 'image/png', 'image/jpeg', | |
209 | 'text', 'png', 'jpg', # v3-style short keys |
|
|||
210 | } |
|
208 | } | |
211 |
|
209 | |||
212 | for output in cell['outputs']: |
|
210 | for output in cell['outputs']: | |
213 | output_type = output['output_type'] |
|
211 | output_type = output['output_type'] | |
214 |
if output_type in |
|
212 | if output_type in {'execute_result', 'display_data'}: | |
215 | # if there are any data keys not in the safe whitelist |
|
213 | # if there are any data keys not in the safe whitelist | |
216 | output_keys = set(output).difference({"output_type", "prompt_number", "metadata"}) |
|
214 | output_keys = set(output).difference({"output_type", "prompt_number", "metadata"}) | |
217 | if output_keys.difference(safe): |
|
215 | if output_keys.difference(safe): | |
218 | return False |
|
216 | return False | |
219 |
|
217 | |||
220 | return True |
|
218 | return True | |
221 |
|
219 | |||
222 | def check_cells(self, nb): |
|
220 | def check_cells(self, nb): | |
223 | """Return whether all code cells are trusted |
|
221 | """Return whether all code cells are trusted | |
224 |
|
222 | |||
225 | If there are no code cells, return True. |
|
223 | If there are no code cells, return True. | |
226 |
|
224 | |||
227 | This function is the inverse of mark_cells. |
|
225 | This function is the inverse of mark_cells. | |
228 | """ |
|
226 | """ | |
229 |
if not nb[' |
|
227 | if not nb['cells']: | |
230 | return True |
|
228 | return True | |
231 | trusted = True |
|
229 | trusted = True | |
232 |
for cell in nb[' |
|
230 | for cell in nb['cells']: | |
233 | if cell['cell_type'] != 'code': |
|
231 | if cell['cell_type'] != 'code': | |
234 | continue |
|
232 | continue | |
235 | # only distrust a cell if it actually has some output to distrust |
|
233 | # only distrust a cell if it actually has some output to distrust | |
236 | if not self._check_cell(cell): |
|
234 | if not self._check_cell(cell): | |
237 | trusted = False |
|
235 | trusted = False | |
238 |
|
236 | |||
239 | return trusted |
|
237 | return trusted | |
240 |
|
238 | |||
241 |
|
239 | |||
242 | trust_flags = { |
|
240 | trust_flags = { | |
243 | 'reset' : ( |
|
241 | 'reset' : ( | |
244 | {'TrustNotebookApp' : { 'reset' : True}}, |
|
242 | {'TrustNotebookApp' : { 'reset' : True}}, | |
245 | """Generate a new key for notebook signature. |
|
243 | """Generate a new key for notebook signature. | |
246 | All previously signed notebooks will become untrusted. |
|
244 | All previously signed notebooks will become untrusted. | |
247 | """ |
|
245 | """ | |
248 | ), |
|
246 | ), | |
249 | } |
|
247 | } | |
250 | trust_flags.update(base_flags) |
|
248 | trust_flags.update(base_flags) | |
251 | trust_flags.pop('init') |
|
249 | trust_flags.pop('init') | |
252 |
|
250 | |||
253 |
|
251 | |||
254 | class TrustNotebookApp(BaseIPythonApplication): |
|
252 | class TrustNotebookApp(BaseIPythonApplication): | |
255 |
|
253 | |||
256 | description="""Sign one or more IPython notebooks with your key, |
|
254 | description="""Sign one or more IPython notebooks with your key, | |
257 | to trust their dynamic (HTML, Javascript) output. |
|
255 | to trust their dynamic (HTML, Javascript) output. | |
258 |
|
256 | |||
259 | Trusting a notebook only applies to the current IPython profile. |
|
257 | Trusting a notebook only applies to the current IPython profile. | |
260 | To trust a notebook for use with a profile other than default, |
|
258 | To trust a notebook for use with a profile other than default, | |
261 | add `--profile [profile name]`. |
|
259 | add `--profile [profile name]`. | |
262 |
|
260 | |||
263 | Otherwise, you will have to re-execute the notebook to see output. |
|
261 | Otherwise, you will have to re-execute the notebook to see output. | |
264 | """ |
|
262 | """ | |
265 |
|
263 | |||
266 | examples = """ |
|
264 | examples = """ | |
267 | ipython trust mynotebook.ipynb and_this_one.ipynb |
|
265 | ipython trust mynotebook.ipynb and_this_one.ipynb | |
268 | ipython trust --profile myprofile mynotebook.ipynb |
|
266 | ipython trust --profile myprofile mynotebook.ipynb | |
269 | """ |
|
267 | """ | |
270 |
|
268 | |||
271 | flags = trust_flags |
|
269 | flags = trust_flags | |
272 |
|
270 | |||
273 | reset = Bool(False, config=True, |
|
271 | reset = Bool(False, config=True, | |
274 | help="""If True, generate a new key for notebook signature. |
|
272 | help="""If True, generate a new key for notebook signature. | |
275 | After reset, all previously signed notebooks will become untrusted. |
|
273 | After reset, all previously signed notebooks will become untrusted. | |
276 | """ |
|
274 | """ | |
277 | ) |
|
275 | ) | |
278 |
|
276 | |||
279 | notary = Instance(NotebookNotary) |
|
277 | notary = Instance(NotebookNotary) | |
280 | def _notary_default(self): |
|
278 | def _notary_default(self): | |
281 | return NotebookNotary(parent=self, profile_dir=self.profile_dir) |
|
279 | return NotebookNotary(parent=self, profile_dir=self.profile_dir) | |
282 |
|
280 | |||
283 | def sign_notebook(self, notebook_path): |
|
281 | def sign_notebook(self, notebook_path): | |
284 | if not os.path.exists(notebook_path): |
|
282 | if not os.path.exists(notebook_path): | |
285 | self.log.error("Notebook missing: %s" % notebook_path) |
|
283 | self.log.error("Notebook missing: %s" % notebook_path) | |
286 | self.exit(1) |
|
284 | self.exit(1) | |
287 | with io.open(notebook_path, encoding='utf8') as f: |
|
285 | with io.open(notebook_path, encoding='utf8') as f: | |
288 | nb = read(f, 'json') |
|
286 | nb = read(f, 'json') | |
289 | if self.notary.check_signature(nb): |
|
287 | if self.notary.check_signature(nb): | |
290 | print("Notebook already signed: %s" % notebook_path) |
|
288 | print("Notebook already signed: %s" % notebook_path) | |
291 | else: |
|
289 | else: | |
292 | print("Signing notebook: %s" % notebook_path) |
|
290 | print("Signing notebook: %s" % notebook_path) | |
293 | self.notary.sign(nb) |
|
291 | self.notary.sign(nb) | |
294 | with io.open(notebook_path, 'w', encoding='utf8') as f: |
|
292 | with io.open(notebook_path, 'w', encoding='utf8') as f: | |
295 | write(nb, f, 'json') |
|
293 | write(nb, f, 'json') | |
296 |
|
294 | |||
297 | def generate_new_key(self): |
|
295 | def generate_new_key(self): | |
298 | """Generate a new notebook signature key""" |
|
296 | """Generate a new notebook signature key""" | |
299 | print("Generating new notebook key: %s" % self.notary.secret_file) |
|
297 | print("Generating new notebook key: %s" % self.notary.secret_file) | |
300 | self.notary._write_secret_file(os.urandom(1024)) |
|
298 | self.notary._write_secret_file(os.urandom(1024)) | |
301 |
|
299 | |||
302 | def start(self): |
|
300 | def start(self): | |
303 | if self.reset: |
|
301 | if self.reset: | |
304 | self.generate_new_key() |
|
302 | self.generate_new_key() | |
305 | return |
|
303 | return | |
306 | if not self.extra_args: |
|
304 | if not self.extra_args: | |
307 | self.log.critical("Specify at least one notebook to sign.") |
|
305 | self.log.critical("Specify at least one notebook to sign.") | |
308 | self.exit(1) |
|
306 | self.exit(1) | |
309 |
|
307 | |||
310 | for notebook_path in self.extra_args: |
|
308 | for notebook_path in self.extra_args: | |
311 | self.sign_notebook(notebook_path) |
|
309 | self.sign_notebook(notebook_path) | |
312 |
|
310 |
@@ -1,69 +1,57 | |||||
1 | """ |
|
1 | """Tests for nbformat.convert""" | |
2 | Contains tests class for convert.py |
|
2 | ||
3 | """ |
|
3 | # Copyright (c) IPython Development Team. | |
4 | #----------------------------------------------------------------------------- |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 | # Copyright (C) 2013 The IPython Development Team |
|
|||
6 | # |
|
|||
7 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
8 | # the file COPYING, distributed as part of this software. |
|
|||
9 | #----------------------------------------------------------------------------- |
|
|||
10 |
|
||||
11 | #----------------------------------------------------------------------------- |
|
|||
12 | # Imports |
|
|||
13 | #----------------------------------------------------------------------------- |
|
|||
14 |
|
5 | |||
15 | from .base import TestsBase |
|
6 | from .base import TestsBase | |
16 |
|
7 | |||
17 | from ..convert import convert |
|
8 | from ..convert import convert | |
18 | from ..reader import read, get_version |
|
9 | from ..reader import read, get_version | |
19 | from ..current import current_nbformat |
|
10 | from ..current import current_nbformat | |
20 |
|
11 | |||
21 | #----------------------------------------------------------------------------- |
|
|||
22 | # Classes and functions |
|
|||
23 | #----------------------------------------------------------------------------- |
|
|||
24 |
|
12 | |||
25 | class TestConvert(TestsBase): |
|
13 | class TestConvert(TestsBase): | |
26 |
|
14 | |||
27 | def test_downgrade(self): |
|
15 | def test_downgrade_3_2(self): | |
28 | """Do notebook downgrades work?""" |
|
16 | """Do notebook downgrades work?""" | |
29 |
|
17 | |||
30 | # Open a version 3 notebook and attempt to downgrade it to version 2. |
|
18 | # Open a version 3 notebook and attempt to downgrade it to version 2. | |
31 | with self.fopen(u'test3.ipynb', u'r') as f: |
|
19 | with self.fopen(u'test3.ipynb', u'r') as f: | |
32 | nb = read(f) |
|
20 | nb = read(f) | |
33 | nb = convert(nb, 2) |
|
21 | nb = convert(nb, 2) | |
34 |
|
22 | |||
35 | # Check if downgrade was successful. |
|
23 | # Check if downgrade was successful. | |
36 | (major, minor) = get_version(nb) |
|
24 | (major, minor) = get_version(nb) | |
37 | self.assertEqual(major, 2) |
|
25 | self.assertEqual(major, 2) | |
38 |
|
26 | |||
39 |
|
27 | |||
40 | def test_upgrade(self): |
|
28 | def test_upgrade_2_3(self): | |
41 | """Do notebook upgrades work?""" |
|
29 | """Do notebook upgrades work?""" | |
42 |
|
30 | |||
43 | # Open a version 2 notebook and attempt to upgrade it to version 3. |
|
31 | # Open a version 2 notebook and attempt to upgrade it to version 3. | |
44 | with self.fopen(u'test2.ipynb', u'r') as f: |
|
32 | with self.fopen(u'test2.ipynb', u'r') as f: | |
45 | nb = read(f) |
|
33 | nb = read(f) | |
46 | nb = convert(nb, 3) |
|
34 | nb = convert(nb, 3) | |
47 |
|
35 | |||
48 | # Check if upgrade was successful. |
|
36 | # Check if upgrade was successful. | |
49 | (major, minor) = get_version(nb) |
|
37 | (major, minor) = get_version(nb) | |
50 | self.assertEqual(major, 3) |
|
38 | self.assertEqual(major, 3) | |
51 |
|
39 | |||
52 |
|
40 | |||
53 | def test_open_current(self): |
|
41 | def test_open_current(self): | |
54 | """Can an old notebook be opened and converted to the current version |
|
42 | """Can an old notebook be opened and converted to the current version | |
55 | while remembering the original version of the notebook?""" |
|
43 | while remembering the original version of the notebook?""" | |
56 |
|
44 | |||
57 | # Open a version 2 notebook and attempt to upgrade it to the current version |
|
45 | # Open a version 2 notebook and attempt to upgrade it to the current version | |
58 | # while remembering it's version information. |
|
46 | # while remembering it's version information. | |
59 | with self.fopen(u'test2.ipynb', u'r') as f: |
|
47 | with self.fopen(u'test2.ipynb', u'r') as f: | |
60 | nb = read(f) |
|
48 | nb = read(f) | |
61 | (original_major, original_minor) = get_version(nb) |
|
49 | (original_major, original_minor) = get_version(nb) | |
62 | nb = convert(nb, current_nbformat) |
|
50 | nb = convert(nb, current_nbformat) | |
63 |
|
51 | |||
64 | # Check if upgrade was successful. |
|
52 | # Check if upgrade was successful. | |
65 | (major, minor) = get_version(nb) |
|
53 | (major, minor) = get_version(nb) | |
66 | self.assertEqual(major, current_nbformat) |
|
54 | self.assertEqual(major, current_nbformat) | |
67 |
|
55 | |||
68 | # Check if the original major revision was remembered. |
|
56 | # Check if the original major revision was remembered. | |
69 | self.assertEqual(original_major, 2) |
|
57 | self.assertEqual(original_major, 2) |
@@ -1,122 +1,112 | |||||
1 | """Test Notebook signing""" |
|
1 | """Test Notebook signing""" | |
2 | #----------------------------------------------------------------------------- |
|
|||
3 | # Copyright (C) 2014, The IPython Development Team |
|
|||
4 | # |
|
|||
5 | # Distributed under the terms of the BSD License. The full license is in |
|
|||
6 | # the file COPYING, distributed as part of this software. |
|
|||
7 | #----------------------------------------------------------------------------- |
|
|||
8 |
|
2 | |||
9 | #----------------------------------------------------------------------------- |
|
3 | # Copyright (c) IPython Development Team. | |
10 | # Imports |
|
4 | # Distributed under the terms of the Modified BSD License. | |
11 | #----------------------------------------------------------------------------- |
|
|||
12 |
|
5 | |||
13 | from .. import sign |
|
6 | from .. import sign | |
14 | from .base import TestsBase |
|
7 | from .base import TestsBase | |
15 |
|
8 | |||
16 | from ..current import read |
|
9 | from ..current import read | |
17 | from IPython.core.getipython import get_ipython |
|
10 | from IPython.core.getipython import get_ipython | |
18 |
|
11 | |||
19 | #----------------------------------------------------------------------------- |
|
|||
20 | # Classes and functions |
|
|||
21 | #----------------------------------------------------------------------------- |
|
|||
22 |
|
12 | |||
23 | class TestNotary(TestsBase): |
|
13 | class TestNotary(TestsBase): | |
24 |
|
14 | |||
25 | def setUp(self): |
|
15 | def setUp(self): | |
26 | self.notary = sign.NotebookNotary( |
|
16 | self.notary = sign.NotebookNotary( | |
27 | secret=b'secret', |
|
17 | secret=b'secret', | |
28 | profile_dir=get_ipython().profile_dir |
|
18 | profile_dir=get_ipython().profile_dir | |
29 | ) |
|
19 | ) | |
30 | with self.fopen(u'test3.ipynb', u'r') as f: |
|
20 | with self.fopen(u'test3.ipynb', u'r') as f: | |
31 | self.nb = read(f, u'json') |
|
21 | self.nb = read(f, u'json') | |
32 |
|
22 | |||
33 | def test_algorithms(self): |
|
23 | def test_algorithms(self): | |
34 | last_sig = '' |
|
24 | last_sig = '' | |
35 | for algo in sign.algorithms: |
|
25 | for algo in sign.algorithms: | |
36 | self.notary.algorithm = algo |
|
26 | self.notary.algorithm = algo | |
37 | self.notary.sign(self.nb) |
|
27 | self.notary.sign(self.nb) | |
38 | sig = self.nb.metadata.signature |
|
28 | sig = self.nb.metadata.signature | |
39 | print(sig) |
|
29 | print(sig) | |
40 | self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) |
|
30 | self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) | |
41 | self.assertNotEqual(last_sig, sig) |
|
31 | self.assertNotEqual(last_sig, sig) | |
42 | last_sig = sig |
|
32 | last_sig = sig | |
43 |
|
33 | |||
44 | def test_sign_same(self): |
|
34 | def test_sign_same(self): | |
45 | """Multiple signatures of the same notebook are the same""" |
|
35 | """Multiple signatures of the same notebook are the same""" | |
46 | sig1 = self.notary.compute_signature(self.nb) |
|
36 | sig1 = self.notary.compute_signature(self.nb) | |
47 | sig2 = self.notary.compute_signature(self.nb) |
|
37 | sig2 = self.notary.compute_signature(self.nb) | |
48 | self.assertEqual(sig1, sig2) |
|
38 | self.assertEqual(sig1, sig2) | |
49 |
|
39 | |||
50 | def test_change_secret(self): |
|
40 | def test_change_secret(self): | |
51 | """Changing the secret changes the signature""" |
|
41 | """Changing the secret changes the signature""" | |
52 | sig1 = self.notary.compute_signature(self.nb) |
|
42 | sig1 = self.notary.compute_signature(self.nb) | |
53 | self.notary.secret = b'different' |
|
43 | self.notary.secret = b'different' | |
54 | sig2 = self.notary.compute_signature(self.nb) |
|
44 | sig2 = self.notary.compute_signature(self.nb) | |
55 | self.assertNotEqual(sig1, sig2) |
|
45 | self.assertNotEqual(sig1, sig2) | |
56 |
|
46 | |||
57 | def test_sign(self): |
|
47 | def test_sign(self): | |
58 | self.notary.sign(self.nb) |
|
48 | self.notary.sign(self.nb) | |
59 | sig = self.nb.metadata.signature |
|
49 | sig = self.nb.metadata.signature | |
60 | self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) |
|
50 | self.assertEqual(sig[:len(self.notary.algorithm)+1], '%s:' % self.notary.algorithm) | |
61 |
|
51 | |||
62 | def test_check_signature(self): |
|
52 | def test_check_signature(self): | |
63 | nb = self.nb |
|
53 | nb = self.nb | |
64 | md = nb.metadata |
|
54 | md = nb.metadata | |
65 | notary = self.notary |
|
55 | notary = self.notary | |
66 | check_signature = notary.check_signature |
|
56 | check_signature = notary.check_signature | |
67 | # no signature: |
|
57 | # no signature: | |
68 | md.pop('signature', None) |
|
58 | md.pop('signature', None) | |
69 | self.assertFalse(check_signature(nb)) |
|
59 | self.assertFalse(check_signature(nb)) | |
70 | # hash only, no algo |
|
60 | # hash only, no algo | |
71 | md.signature = notary.compute_signature(nb) |
|
61 | md.signature = notary.compute_signature(nb) | |
72 | self.assertFalse(check_signature(nb)) |
|
62 | self.assertFalse(check_signature(nb)) | |
73 | # proper signature, algo mismatch |
|
63 | # proper signature, algo mismatch | |
74 | notary.algorithm = 'sha224' |
|
64 | notary.algorithm = 'sha224' | |
75 | notary.sign(nb) |
|
65 | notary.sign(nb) | |
76 | notary.algorithm = 'sha256' |
|
66 | notary.algorithm = 'sha256' | |
77 | self.assertFalse(check_signature(nb)) |
|
67 | self.assertFalse(check_signature(nb)) | |
78 | # check correctly signed notebook |
|
68 | # check correctly signed notebook | |
79 | notary.sign(nb) |
|
69 | notary.sign(nb) | |
80 | self.assertTrue(check_signature(nb)) |
|
70 | self.assertTrue(check_signature(nb)) | |
81 |
|
71 | |||
82 | def test_mark_cells_untrusted(self): |
|
72 | def test_mark_cells_untrusted(self): | |
83 |
cells = self.nb. |
|
73 | cells = self.nb.cells | |
84 | self.notary.mark_cells(self.nb, False) |
|
74 | self.notary.mark_cells(self.nb, False) | |
85 | for cell in cells: |
|
75 | for cell in cells: | |
86 | self.assertNotIn('trusted', cell) |
|
76 | self.assertNotIn('trusted', cell) | |
87 | if cell.cell_type == 'code': |
|
77 | if cell.cell_type == 'code': | |
88 | self.assertIn('trusted', cell.metadata) |
|
78 | self.assertIn('trusted', cell.metadata) | |
89 | self.assertFalse(cell.metadata.trusted) |
|
79 | self.assertFalse(cell.metadata.trusted) | |
90 | else: |
|
80 | else: | |
91 | self.assertNotIn('trusted', cell.metadata) |
|
81 | self.assertNotIn('trusted', cell.metadata) | |
92 |
|
82 | |||
93 | def test_mark_cells_trusted(self): |
|
83 | def test_mark_cells_trusted(self): | |
94 |
cells = self.nb. |
|
84 | cells = self.nb.cells | |
95 | self.notary.mark_cells(self.nb, True) |
|
85 | self.notary.mark_cells(self.nb, True) | |
96 | for cell in cells: |
|
86 | for cell in cells: | |
97 | self.assertNotIn('trusted', cell) |
|
87 | self.assertNotIn('trusted', cell) | |
98 | if cell.cell_type == 'code': |
|
88 | if cell.cell_type == 'code': | |
99 | self.assertIn('trusted', cell.metadata) |
|
89 | self.assertIn('trusted', cell.metadata) | |
100 | self.assertTrue(cell.metadata.trusted) |
|
90 | self.assertTrue(cell.metadata.trusted) | |
101 | else: |
|
91 | else: | |
102 | self.assertNotIn('trusted', cell.metadata) |
|
92 | self.assertNotIn('trusted', cell.metadata) | |
103 |
|
93 | |||
104 | def test_check_cells(self): |
|
94 | def test_check_cells(self): | |
105 | nb = self.nb |
|
95 | nb = self.nb | |
106 | self.notary.mark_cells(nb, True) |
|
96 | self.notary.mark_cells(nb, True) | |
107 | self.assertTrue(self.notary.check_cells(nb)) |
|
97 | self.assertTrue(self.notary.check_cells(nb)) | |
108 |
for cell in nb. |
|
98 | for cell in nb.cells: | |
109 | self.assertNotIn('trusted', cell) |
|
99 | self.assertNotIn('trusted', cell) | |
110 | self.notary.mark_cells(nb, False) |
|
100 | self.notary.mark_cells(nb, False) | |
111 | self.assertFalse(self.notary.check_cells(nb)) |
|
101 | self.assertFalse(self.notary.check_cells(nb)) | |
112 |
for cell in nb. |
|
102 | for cell in nb.cells: | |
113 | self.assertNotIn('trusted', cell) |
|
103 | self.assertNotIn('trusted', cell) | |
114 |
|
104 | |||
115 | def test_trust_no_output(self): |
|
105 | def test_trust_no_output(self): | |
116 | nb = self.nb |
|
106 | nb = self.nb | |
117 | self.notary.mark_cells(nb, False) |
|
107 | self.notary.mark_cells(nb, False) | |
118 |
for cell in nb. |
|
108 | for cell in nb.cells: | |
119 | if cell.cell_type == 'code': |
|
109 | if cell.cell_type == 'code': | |
120 | cell.outputs = [] |
|
110 | cell.outputs = [] | |
121 | self.assertTrue(self.notary.check_cells(nb)) |
|
111 | self.assertTrue(self.notary.check_cells(nb)) | |
122 |
|
112 |
@@ -1,54 +1,57 | |||||
1 | """Test nbformat.validator""" |
|
1 | """Test nbformat.validator""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | import os |
|
6 | import os | |
7 |
|
7 | |||
8 | from .base import TestsBase |
|
8 | from .base import TestsBase | |
9 | from jsonschema import ValidationError |
|
9 | from jsonschema import ValidationError | |
10 | from ..current import read |
|
10 | from ..current import read | |
11 | from ..validator import isvalid, validate |
|
11 | from ..validator import isvalid, validate | |
12 |
|
12 | |||
13 |
|
13 | |||
14 | #----------------------------------------------------------------------------- |
|
|||
15 | # Classes and functions |
|
|||
16 | #----------------------------------------------------------------------------- |
|
|||
17 |
|
||||
18 | class TestValidator(TestsBase): |
|
14 | class TestValidator(TestsBase): | |
19 |
|
15 | |||
20 | def test_nb2(self): |
|
16 | def test_nb2(self): | |
21 |
"""Test that a v2 notebook converted to |
|
17 | """Test that a v2 notebook converted to current passes validation""" | |
22 | with self.fopen(u'test2.ipynb', u'r') as f: |
|
18 | with self.fopen(u'test2.ipynb', u'r') as f: | |
23 | nb = read(f, u'json') |
|
19 | nb = read(f, u'json') | |
24 | validate(nb) |
|
20 | validate(nb) | |
25 | self.assertEqual(isvalid(nb), True) |
|
21 | self.assertEqual(isvalid(nb), True) | |
26 |
|
22 | |||
27 | def test_nb3(self): |
|
23 | def test_nb3(self): | |
28 | """Test that a v3 notebook passes validation""" |
|
24 | """Test that a v3 notebook passes validation""" | |
29 | with self.fopen(u'test3.ipynb', u'r') as f: |
|
25 | with self.fopen(u'test3.ipynb', u'r') as f: | |
30 | nb = read(f, u'json') |
|
26 | nb = read(f, u'json') | |
31 | validate(nb) |
|
27 | validate(nb) | |
32 | self.assertEqual(isvalid(nb), True) |
|
28 | self.assertEqual(isvalid(nb), True) | |
33 |
|
29 | |||
|
30 | def test_nb4(self): | |||
|
31 | """Test that a v3 notebook passes validation""" | |||
|
32 | with self.fopen(u'test4.ipynb', u'r') as f: | |||
|
33 | nb = read(f, u'json') | |||
|
34 | validate(nb) | |||
|
35 | self.assertEqual(isvalid(nb), True) | |||
|
36 | ||||
34 | def test_invalid(self): |
|
37 | def test_invalid(self): | |
35 | """Test than an invalid notebook does not pass validation""" |
|
38 | """Test than an invalid notebook does not pass validation""" | |
36 | # this notebook has a few different errors: |
|
39 | # this notebook has a few different errors: | |
37 | # - the name is an integer, rather than a string |
|
40 | # - the name is an integer, rather than a string | |
38 | # - one cell is missing its source |
|
41 | # - one cell is missing its source | |
39 | # - one cell has an invalid level |
|
42 | # - one cell has an invalid level | |
40 | with self.fopen(u'invalid.ipynb', u'r') as f: |
|
43 | with self.fopen(u'invalid.ipynb', u'r') as f: | |
41 | nb = read(f, u'json') |
|
44 | nb = read(f, u'json') | |
42 | with self.assertRaises(ValidationError): |
|
45 | with self.assertRaises(ValidationError): | |
43 | validate(nb) |
|
46 | validate(nb) | |
44 | self.assertEqual(isvalid(nb), False) |
|
47 | self.assertEqual(isvalid(nb), False) | |
45 |
|
48 | |||
46 | def test_future(self): |
|
49 | def test_future(self): | |
47 | """Test than a notebook from the future with extra keys passes validation""" |
|
50 | """Test than a notebook from the future with extra keys passes validation""" | |
48 | with self.fopen(u'test3plus.ipynb', u'r') as f: |
|
51 | with self.fopen(u'test3plus.ipynb', u'r') as f: | |
49 | nb = read(f) |
|
52 | nb = read(f) | |
50 | with self.assertRaises(ValidationError): |
|
53 | with self.assertRaises(ValidationError): | |
51 | validate(nb, version=3) |
|
54 | validate(nb, version=3) | |
52 |
|
55 | |||
53 | self.assertEqual(isvalid(nb, version=3), False) |
|
56 | self.assertEqual(isvalid(nb, version=3), False) | |
54 | self.assertEqual(isvalid(nb), True) |
|
57 | self.assertEqual(isvalid(nb), True) |
@@ -1,19 +1,19 | |||||
1 | """The main API for the v4 notebook format.""" |
|
1 | """The main API for the v4 notebook format.""" | |
2 |
|
2 | |||
3 | # Copyright (c) IPython Development Team. |
|
3 | # Copyright (c) IPython Development Team. | |
4 | # Distributed under the terms of the Modified BSD License. |
|
4 | # Distributed under the terms of the Modified BSD License. | |
5 |
|
5 | |||
6 | from .nbbase import ( |
|
6 | from .nbbase import ( | |
7 | NotebookNode, |
|
7 | NotebookNode, from_dict, | |
8 | nbformat, nbformat_minor, nbformat_schema, |
|
8 | nbformat, nbformat_minor, nbformat_schema, | |
9 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, |
|
9 | new_code_cell, new_heading_cell, new_markdown_cell, new_notebook, | |
10 | new_output, |
|
10 | new_output, | |
11 | ) |
|
11 | ) | |
12 |
|
12 | |||
13 | from .nbjson import reads as reads_json, writes as writes_json |
|
13 | from .nbjson import reads as reads_json, writes as writes_json | |
14 | from .nbjson import reads as read_json, writes as write_json |
|
14 | from .nbjson import reads as read_json, writes as write_json | |
15 | from .nbjson import to_notebook as to_notebook_json |
|
15 | from .nbjson import to_notebook as to_notebook_json | |
16 |
|
16 | |||
17 | from .convert import downgrade, upgrade |
|
17 | from .convert import downgrade, upgrade | |
18 |
|
18 | |||
19 |
|
19 |
General Comments 0
You need to be logged in to leave comments.
Login now