(core) apply access control to code view

Summary:
Names of private tables and columns were leaking via Code View.
This plugs that leak.

Test Plan: adds test

Reviewers: dsagal

Reviewed By: dsagal

Differential Revision: https://phab.getgrist.com/D2840
This commit is contained in:
Paul Fitzpatrick 2021-05-27 09:18:57 -04:00
parent 96fee73b70
commit 37698f9cb5
3 changed files with 28 additions and 4 deletions

View File

@ -17,3 +17,7 @@
.g-code-viewer.hljs { .g-code-viewer.hljs {
background-color: inherit; background-color: inherit;
} }
.g-code-panel-denied {
text-align: center;
}

View File

@ -14,6 +14,7 @@ hljs.registerLanguage('python', require('highlight.js/lib/languages/python'));
function CodeEditorPanel(gristDoc) { function CodeEditorPanel(gristDoc) {
this._gristDoc = gristDoc; this._gristDoc = gristDoc;
this._schema = ko.observable(''); this._schema = ko.observable('');
this._denied = ko.observable(false);
this.listenTo(this._gristDoc, 'schemaUpdateAction', this.onSchemaAction); this.listenTo(this._gristDoc, 'schemaUpdateAction', this.onSchemaAction);
this.onSchemaAction(); // Fetch the schema to initialize this.onSchemaAction(); // Fetch the schema to initialize
@ -28,6 +29,10 @@ CodeEditorPanel.prototype.buildDom = function() {
// interfere with text selection even for un-focusable elements. // interfere with text selection even for un-focusable elements.
return dom('div.g-code-panel.clipboard', return dom('div.g-code-panel.clipboard',
{tabIndex: "-1"}, {tabIndex: "-1"},
kd.maybe(this._denied, () => dom('div.g-code-panel-denied',
dom('h2', kd.text('Access denied')),
dom('div', kd.text('Code View is available only when you have full document access.')),
)),
kd.scope(this._schema, function(schema) { kd.scope(this._schema, function(schema) {
// The reason to scope and rebuild instead of using `kd.text(schema)` is because // The reason to scope and rebuild instead of using `kd.text(schema)` is because
// hljs.highlightBlock(elem) replaces `elem` with a whole new dom tree. // hljs.highlightBlock(elem) replaces `elem` with a whole new dom tree.
@ -45,13 +50,22 @@ CodeEditorPanel.prototype.buildDom = function() {
); );
}; };
CodeEditorPanel.prototype.onSchemaAction = function(actions) { CodeEditorPanel.prototype.onSchemaAction = async function(actions) {
return this._gristDoc.docComm.fetchTableSchema() try {
.then(schema => { const schema = await this._gristDoc.docComm.fetchTableSchema();
if (!this.isDisposed()) { if (!this.isDisposed()) {
this._schema(schema); this._schema(schema);
this._denied(false);
} }
}); } catch (err) {
if (!String(err).match(/Cannot view code/)) {
throw err;
}
if (!this.isDisposed()) {
this._schema('');
this._denied(true);
}
}
}; };
module.exports = CodeEditorPanel; module.exports = CodeEditorPanel;

View File

@ -658,6 +658,12 @@ export class ActiveDoc extends EventEmitter {
*/ */
public async fetchTableSchema(docSession: DocSession): Promise<string> { public async fetchTableSchema(docSession: DocSession): Promise<string> {
this.logInfo(docSession, "fetchTableSchema(%s)", docSession); this.logInfo(docSession, "fetchTableSchema(%s)", docSession);
// Permit code view if user can read everything, or can download/copy (perhaps
// via an exceptional permission for sample documents)
if (!(await this._granularAccess.canReadEverything(docSession) ||
await this.canDownload(docSession))) {
throw new ApiError('Cannot view code, it may contain private material', 403);
}
await this.waitForInitialization(); await this.waitForInitialization();
return this._pyCall('fetch_table_schema'); return this._pyCall('fetch_table_schema');
} }