1
1
import { createHmac } from 'crypto' ;
2
2
import { inject , injectable } from 'inversify' ;
3
3
import { EventEmitter , Uri } from 'vscode' ;
4
+ import { traceError } from '../../common/logger' ;
4
5
import { IConfigurationService } from '../../common/types' ;
6
+ import { sortObjectPropertiesRecursively } from '../notebookStorage/vscNotebookModel' ;
5
7
import { IDigestStorage , ITrustService } from '../types' ;
6
8
7
9
@injectable ( )
@@ -29,9 +31,14 @@ export class TrustService implements ITrustService {
29
31
if ( this . alwaysTrustNotebooks ) {
30
32
return true ; // Skip check if user manually overrode our trust checking
31
33
}
32
- // Compute digest and see if notebook is trusted
33
- const digest = await this . computeDigest ( notebookContents ) ;
34
- return this . digestStorage . containsDigest ( uri , digest ) ;
34
+ // Compute digest and see if notebook is trusted.
35
+ // Check formatted & unformatted notebook. Possible user saved nb using old extension & opening using new extension.
36
+ const [ digest1 , digest2 ] = await Promise . all ( [
37
+ this . computeDigest ( notebookContents ) ,
38
+ this . computeDigest ( this . getFormattedContents ( notebookContents ) )
39
+ ] ) ;
40
+
41
+ return this . digestStorage . containsDigest ( uri , digest1 ) || this . digestStorage . containsDigest ( uri , digest2 ) ;
35
42
}
36
43
37
44
/**
@@ -41,13 +48,28 @@ export class TrustService implements ITrustService {
41
48
*/
42
49
public async trustNotebook ( uri : Uri , notebookContents : string ) {
43
50
if ( ! this . alwaysTrustNotebooks ) {
51
+ notebookContents = this . getFormattedContents ( notebookContents ) ;
44
52
// Only update digest store if the user wants us to check trust
45
53
const digest = await this . computeDigest ( notebookContents ) ;
46
54
await this . digestStorage . saveDigest ( uri , digest ) ;
47
55
this . _onDidSetNotebookTrust . fire ( ) ;
48
56
}
49
57
}
50
-
58
+ /**
59
+ * If a notebook is opened & saved in Jupyter, even without making any changes, the JSON in ipynb could be different from the format saved by VSC.
60
+ * Similarly, the JSON saved by native notebooks could be different when compared to how they are saved by standard notebooks.
61
+ * When trusting a notebook, we don't trust the raw bytes in ipynb, we trust the contents, & ipynb stores JSON,
62
+ * Hence when computing a hash we need to ensure the hash is always the same regardless of indentation of JSON & the order of properties in json.
63
+ * This method returns the contents of the ipynb in a manner thats solves formatting issues related to JSON.
64
+ */
65
+ private getFormattedContents ( notebookContents : string ) {
66
+ try {
67
+ return JSON . stringify ( sortObjectPropertiesRecursively ( JSON . parse ( notebookContents ) ) ) ;
68
+ } catch ( ex ) {
69
+ traceError ( 'Notebook cannot be parsed into JSON' , ex ) ;
70
+ return notebookContents ;
71
+ }
72
+ }
51
73
private async computeDigest ( notebookContents : string ) {
52
74
const hmac = createHmac ( 'sha256' , await this . digestStorage . key ) ;
53
75
hmac . update ( notebookContents ) ;
0 commit comments