CSRF protection for ImportingController

This commit is contained in:
Antonin Delpeuch 2019-10-11 13:53:43 +01:00
parent 70e37b9085
commit 91cead27f8
7 changed files with 221 additions and 175 deletions

View File

@ -56,6 +56,10 @@ public class ImportingControllerCommand extends Command {
@Override @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
if(!checkCSRF(request)) {
respondCSRFError(response);
return;
}
ImportingController controller = getController(request); ImportingController controller = getController(request);
if (controller != null) { if (controller != null) {
@ -92,4 +96,14 @@ public class ImportingControllerCommand extends Command {
} }
return null; return null;
} }
/**
* Checks the validity of a CSRF token, without reading the whole POST body.
* See above for details.
*/
private boolean checkCSRF(HttpServletRequest request) {
Properties options = ParsingUtilities.parseUrlParameters(request);
String token = options.getProperty("csrf_token");
return token != null && csrfFactory.validToken(token);
}
} }

View File

@ -0,0 +1,24 @@
package com.google.refine.commands.importing;
import com.google.refine.commands.CommandTestBase;
import java.io.IOException;
import javax.servlet.ServletException;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class ImportingControllerCommandTests extends CommandTestBase {
@BeforeMethod
public void setUpCommand() {
command = new ImportingControllerCommand();
}
@Test
public void testCSRFProtection() throws ServletException, IOException {
command.doPost(request, response);
assertCSRFCheckFailed();
}
}

View File

@ -157,15 +157,13 @@ ExpressionPreviewDialog.Widget.prototype.getExpression = function(commit) {
s = this._getLanguage() + ":" + s; s = this._getLanguage() + ":" + s;
if (commit) { if (commit) {
Refine.wrapCSRF(function(token) { Refine.postCSRF(
$.post( "command/core/log-expression?" + $.param({ project: theProject.id }),
"command/core/log-expression?" + $.param({ project: theProject.id }), { expression: s },
{ expression: s, csrf_token: token }, function(data) {
function(data) { },
}, "json"
"json" );
);
});
} }
return s; return s;
@ -286,21 +284,18 @@ ExpressionPreviewDialog.Widget.prototype._renderExpressionHistory = function(dat
.addClass(entry.starred ? "data-table-star-on" : "data-table-star-off") .addClass(entry.starred ? "data-table-star-on" : "data-table-star-off")
.appendTo(tr.insertCell(0)) .appendTo(tr.insertCell(0))
.click(function() { .click(function() {
Refine.wrapCSRF(function(token) { Refine.postCSRF(
$.post( "command/core/toggle-starred-expression",
"command/core/toggle-starred-expression", {
{ expression: entry.code
expression: entry.code, },
csrf_token: token function(data) {
}, entry.starred = !entry.starred;
function(data) { renderEntry(self,tr,entry);
entry.starred = !entry.starred; self._renderStarredExpressionsTab();
renderEntry(self,tr,entry); },
self._renderStarredExpressionsTab(); "json"
}, );
"json"
);
});
}); });
$('<a href="javascript:{}">'+$.i18n('core-dialogs/reuse')+'</a>').appendTo(tr.insertCell(1)).click(function() { $('<a href="javascript:{}">'+$.i18n('core-dialogs/reuse')+'</a>').appendTo(tr.insertCell(1)).click(function() {
@ -355,17 +350,15 @@ ExpressionPreviewDialog.Widget.prototype._renderStarredExpressions = function(da
var o = Scripting.parse(entry.code); var o = Scripting.parse(entry.code);
$('<a href="javascript:{}">'+$.i18n('core-dialogs/remove')+'</a>').appendTo(tr.insertCell(0)).click(function() { $('<a href="javascript:{}">'+$.i18n('core-dialogs/remove')+'</a>').appendTo(tr.insertCell(0)).click(function() {
Refine.wrapCSRF(function(token) { Refine.postCSRF(
$.post( "command/core/toggle-starred-expression",
"command/core/toggle-starred-expression", { expression: entry.code, returnList: true },
{ expression: entry.code, returnList: true, csrf_token: token }, function(data) {
function(data) { self._renderStarredExpressions(data);
self._renderStarredExpressions(data); self._renderExpressionHistoryTab();
self._renderExpressionHistoryTab(); },
}, "json"
"json" );
);
});
}); });
$('<a href="javascript:{}">Reuse</a>').appendTo(tr.insertCell(1)).click(function() { $('<a href="javascript:{}">Reuse</a>').appendTo(tr.insertCell(1)).click(function() {

View File

@ -92,7 +92,8 @@ Refine.DefaultImportingController.prototype.startImportJob = function(form, prog
.attr("action", "command/core/importing-controller?" + $.param({ .attr("action", "command/core/importing-controller?" + $.param({
"controller": "core/default-importing-controller", "controller": "core/default-importing-controller",
"jobID": jobID, "jobID": jobID,
"subCommand": "load-raw-data" "subCommand": "load-raw-data",
"csrf_token": token
})); }));
form[0].submit(); form[0].submit();
@ -182,27 +183,30 @@ Refine.DefaultImportingController.prototype._ensureFormatParserUIHasInitializati
if (!(format in this._parserOptions)) { if (!(format in this._parserOptions)) {
var self = this; var self = this;
var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting')); var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting'));
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "core/default-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "core/default-importing-controller",
"subCommand": "initialize-parser-ui", "jobID": this._jobID,
"format": format "subCommand": "initialize-parser-ui",
}), "format": format,
null, "csrf_token": token
function(data) { }),
dismissBusy(); null,
function(data) {
dismissBusy();
if (data.options) { if (data.options) {
self._parserOptions[format] = data.options; self._parserOptions[format] = data.options;
onDone(); onDone();
} }
}, },
"json" "json"
) )
.fail(function() { .fail(function() {
dismissBusy(); dismissBusy();
alert($.i18n('core-views/check-format')); alert($.i18n('core-views/check-format'));
});
}); });
} else { } else {
onDone(); onDone();
@ -211,32 +215,35 @@ Refine.DefaultImportingController.prototype._ensureFormatParserUIHasInitializati
Refine.DefaultImportingController.prototype.updateFormatAndOptions = function(options, callback, finallyCallBack) { Refine.DefaultImportingController.prototype.updateFormatAndOptions = function(options, callback, finallyCallBack) {
var self = this; var self = this;
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "core/default-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "core/default-importing-controller",
"subCommand": "update-format-and-options" "jobID": this._jobID,
}), "subCommand": "update-format-and-options",
{ "csrf_token": token
"format" : this._format, }),
"options" : JSON.stringify(options) {
}, "format" : this._format,
function(o) { "options" : JSON.stringify(options)
if (o.status == 'error') { },
if (o.message) { function(o) {
alert(o.message); if (o.status == 'error') {
if (o.message) {
alert(o.message);
} else {
var messages = [];
$.each(o.errors, function() { messages.push(this.message); });
alert(messages.join('\n\n'));
}
finallyCallBack();
} else { } else {
var messages = []; callback(o);
$.each(o.errors, function() { messages.push(this.message); });
alert(messages.join('\n\n'));
} }
finallyCallBack(); },
} else { "json"
callback(o); );
} });
},
"json"
);
}; };
Refine.DefaultImportingController.prototype.getPreviewData = function(callback, numRows) { Refine.DefaultImportingController.prototype.getPreviewData = function(callback, numRows) {
@ -286,56 +293,59 @@ Refine.DefaultImportingController.prototype._createProject = function() {
var options = this._formatParserUI.getOptions(); var options = this._formatParserUI.getOptions();
options.projectName = projectName; options.projectName = projectName;
options.projectTags = projectTags; options.projectTags = projectTags;
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "core/default-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "core/default-importing-controller",
"subCommand": "create-project" "jobID": this._jobID,
}), "subCommand": "create-project",
{ "csrf_token": token
"format" : this._format, }),
"options" : JSON.stringify(options) {
}, "format" : this._format,
function(o) { "options" : JSON.stringify(options)
if (o.status == 'error') { },
alert(o.message); function(o) {
return; if (o.status == 'error') {
} alert(o.message);
return;
var start = new Date(); }
var timerID = window.setInterval(
function() { var start = new Date();
self._createProjectUI.pollImportJob( var timerID = window.setInterval(
start, function() {
self._jobID, self._createProjectUI.pollImportJob(
timerID, start,
function(job) { self._jobID,
return "projectID" in job.config; timerID,
}, function(job) {
function(jobID, job) { return "projectID" in job.config;
Refine.CreateProjectUI.cancelImportingJob(jobID); },
document.location = "project?project=" + job.config.projectID; function(jobID, job) {
}, Refine.CreateProjectUI.cancelImportingJob(jobID);
function(job) { document.location = "project?project=" + job.config.projectID;
alert($.i18n('core-index-import/errors')+'\n' + Refine.CreateProjectUI.composeErrorMessage(job)); },
self._onImportJobReady(); function(job) {
} alert($.i18n('core-index-import/errors')+'\n' + Refine.CreateProjectUI.composeErrorMessage(job));
self._onImportJobReady();
}
);
},
1000
); );
}, self._createProjectUI.showImportProgressPanel($.i18n('core-index-import/creating-proj'), function() {
1000 // stop the timed polling
window.clearInterval(timerID);
// explicitly cancel the import job
Refine.CreateProjectUI.cancelImportingJob(self._jobID);
self._createProjectUI.showSourceSelectionPanel();
});
},
"json"
); );
self._createProjectUI.showImportProgressPanel($.i18n('core-index-import/creating-proj'), function() { });
// stop the timed polling
window.clearInterval(timerID);
// explicitly cancel the import job
Refine.CreateProjectUI.cancelImportingJob(self._jobID);
self._createProjectUI.showSourceSelectionPanel();
});
},
"json"
);
} }
}; };

View File

@ -328,30 +328,33 @@ Refine.DefaultImportingController.prototype._commitFileSelection = function() {
var self = this; var self = this;
var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting-files')); var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting-files'));
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "core/default-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "core/default-importing-controller",
"subCommand": "update-file-selection" "jobID": this._jobID,
}), "subCommand": "update-file-selection",
{ "csrf_token": token
"fileSelection" : JSON.stringify(this._job.config.fileSelection) }),
}, {
function(data) { "fileSelection" : JSON.stringify(this._job.config.fileSelection)
dismissBusy(); },
function(data) {
dismissBusy();
if (!(data)) { if (!(data)) {
self._createProjectUI.showImportJobError($.i18n('core-index-import/unknown-err')); self._createProjectUI.showImportJobError($.i18n('core-index-import/unknown-err'));
} else if (data.code == "error" || !("job" in data)) { } else if (data.code == "error" || !("job" in data)) {
self._createProjectUI.showImportJobError((data.message) ? ($.i18n('core-index-import/error')+ ' ' + data.message) : $.i18n('core-index-import/unknown-err')); self._createProjectUI.showImportJobError((data.message) ? ($.i18n('core-index-import/error')+ ' ' + data.message) : $.i18n('core-index-import/unknown-err'));
} else { } else {
// Different files might be selected. We start over again. // Different files might be selected. We start over again.
delete this._parserOptions; delete this._parserOptions;
self._job = data.job; self._job = data.job;
self._showParsingPanel(true); self._showParsingPanel(true);
} }
}, },
"json" "json"
); );
});
}; };

View File

@ -388,18 +388,11 @@ Refine.postProcess = function(moduleName, command, params, body, updateOptions,
Refine.setAjaxInProgress(); Refine.setAjaxInProgress();
Refine.wrapCSRF( Refine.postCSRF(
function(token) { "command/" + moduleName + "/" + command + "?" + $.param(params),
body,
// Add it to the body and submit it as a POST request onDone,
body['csrf_token'] = token; "json"
$.post(
"command/" + moduleName + "/" + command + "?" + $.param(params),
body,
onDone,
"json"
);
}
); );
window.setTimeout(function() { window.setTimeout(function() {
@ -422,6 +415,17 @@ Refine.wrapCSRF = function(onCSRF) {
); );
}; };
// Performs a POST request where an additional CSRF token
// is supplied in the POST data. The arguments match those
// of $.post().
Refine.postCSRF = function(url, data, success, dataType) {
Refine.wrapCSRF(function(token) {
var fullData = data || {};
data['csrf_token'] = token;
$.post(url, fulldata, success, dataType);
});
};
Refine.setAjaxInProgress = function() { Refine.setAjaxInProgress = function() {
$(document.body).attr("ajax_in_progress", "true"); $(document.body).attr("ajax_in_progress", "true");
}; };

View File

@ -124,17 +124,15 @@ ProcessPanel.prototype.undo = function() {
ProcessPanel.prototype._cancelAll = function() { ProcessPanel.prototype._cancelAll = function() {
var self = this; var self = this;
Refine.wrapCSRF(function(token) { Refine.postCSRF(
$.post( "command/core/cancel-processes?" + $.param({ project: theProject.id }),
"command/core/cancel-processes?" + $.param({ project: theProject.id }), { },
{ csrf_token: token }, function(o) {
function(o) { self._data = null;
self._data = null; self._runOnDones();
self._runOnDones(); },
}, "json"
"json" );
);
});
}; };
ProcessPanel.prototype._render = function(newData) { ProcessPanel.prototype._render = function(newData) {