CSRF protection for gdata extension

This commit is contained in:
Antonin Delpeuch 2019-10-17 09:54:53 +01:00
parent b52c009491
commit 24feda600a
7 changed files with 192 additions and 105 deletions

View File

@ -109,17 +109,20 @@ Refine.GDataSourceUI.prototype._listDocuments = function() {
this._elmts.progressPage.show(); this._elmts.progressPage.show();
var self = this; var self = this;
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "gdata/gdata-importing-controller", "command/core/importing-controller?" + $.param({
"subCommand": "list-documents" "controller": "gdata/gdata-importing-controller",
}), "subCommand": "list-documents",
null, "csrf_token": token
function(o) { }),
self._renderDocuments(o); null,
}, function(o) {
"json" self._renderDocuments(o);
); },
"json"
);
});
}; };
Refine.GDataSourceUI.prototype._renderDocuments = function(o) { Refine.GDataSourceUI.prototype._renderDocuments = function(o) {

View File

@ -71,33 +71,36 @@ Refine.GDataImportingController.prototype.startImportingDocument = function(doc)
var dismiss = DialogSystem.showBusy($.i18n('gdata-import/preparing')); var dismiss = DialogSystem.showBusy($.i18n('gdata-import/preparing'));
var self = this; var self = this;
$.post( Refine.postCSRF(
"command/core/create-importing-job", "command/core/create-importing-job",
null, null,
function(data) { function(data) {
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "gdata/gdata-importing-controller", "command/core/importing-controller?" + $.param({
"subCommand": "initialize-parser-ui", "controller": "gdata/gdata-importing-controller",
"docUrl": doc.docSelfLink, "subCommand": "initialize-parser-ui",
"docType": doc.type "docUrl": doc.docSelfLink,
}), "docType": doc.type,
null, "csrf_token": token
function(data2) { }),
dismiss(); null,
function(data2) {
if (data2.status == 'ok') { dismiss();
self._doc = doc;
self._jobID = data.jobID;
self._options = data2.options;
self._showParsingPanel(); if (data2.status == 'ok') {
} else { self._doc = doc;
alert(data2.message); self._jobID = data.jobID;
} self._options = data2.options;
},
"json" self._showParsingPanel();
); } else {
alert(data2.message);
}
},
"json"
);
});
}, },
"json" "json"
); );
@ -315,31 +318,34 @@ Refine.GDataImportingController.prototype._updatePreview = function() {
this._parsingPanelElmts.dataPanel.hide(); this._parsingPanelElmts.dataPanel.hide();
this._parsingPanelElmts.progressPanel.show(); this._parsingPanelElmts.progressPanel.show();
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "gdata/gdata-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "gdata/gdata-importing-controller",
"subCommand": "parse-preview" "jobID": this._jobID,
}), "subCommand": "parse-preview",
{ "csrf_token": token
"options" : JSON.stringify(this.getOptions()) }),
}, {
function(result) { "options" : JSON.stringify(this.getOptions())
if (result.status == "ok") { },
self._getPreviewData(function(projectData) { function(result) {
self._parsingPanelElmts.progressPanel.hide(); if (result.status == "ok") {
self._parsingPanelElmts.dataPanel.show(); self._getPreviewData(function(projectData) {
self._parsingPanelElmts.progressPanel.hide();
self._parsingPanelElmts.dataPanel.show();
new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty()); new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty());
}); });
} else { } else {
self._parsingPanelElmts.progressPanel.hide(); self._parsingPanelElmts.progressPanel.hide();
alert('Errors:\n' + alert('Errors:\n' +
(result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job)); (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job));
} }
}, },
"json" "json"
); );
});
}; };
Refine.GDataImportingController.prototype._getPreviewData = function(callback, numRows) { Refine.GDataImportingController.prototype._getPreviewData = function(callback, numRows) {
@ -385,52 +391,55 @@ Refine.GDataImportingController.prototype._createProject = function() {
var self = this; var self = this;
var options = this.getOptions(); var options = this.getOptions();
options.projectName = projectName; options.projectName = projectName;
$.post( Refine.wrapCSRF(function(token) {
"command/core/importing-controller?" + $.param({ $.post(
"controller": "gdata/gdata-importing-controller", "command/core/importing-controller?" + $.param({
"jobID": this._jobID, "controller": "gdata/gdata-importing-controller",
"subCommand": "create-project" "jobID": this._jobID,
}), "subCommand": "create-project",
{ "csrf_token": token
"options" : JSON.stringify(options) }),
}, {
function(o) { "options" : JSON.stringify(options)
if (o.status == 'error') { },
alert(o.message); function(o) {
} else { if (o.status == 'error') {
var start = new Date(); alert(o.message);
var timerID = window.setInterval( } else {
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;
window.clearInterval(timerID); },
Refine.CreateProjectUI.cancelImportingJob(jobID); function(jobID, job) {
document.location = "project?project=" + job.config.projectID; window.clearInterval(timerID);
}, Refine.CreateProjectUI.cancelImportingJob(jobID);
function(job) { document.location = "project?project=" + job.config.projectID;
alert(Refine.CreateProjectUI.composeErrorMessage(job)); },
} function(job) {
alert(Refine.CreateProjectUI.composeErrorMessage(job));
}
);
},
1000
); );
}, self._createProjectUI.showImportProgressPanel($.i18n('gdata-import/creating'), function() {
1000 // stop the timed polling
); window.clearInterval(timerID);
self._createProjectUI.showImportProgressPanel($.i18n('gdata-import/creating'), function() {
// stop the timed polling
window.clearInterval(timerID);
// explicitly cancel the import job // explicitly cancel the import job
Refine.CreateProjectUI.cancelImportingJob(jobID); Refine.CreateProjectUI.cancelImportingJob(jobID);
self._createProjectUI.showSourceSelectionPanel(); self._createProjectUI.showSourceSelectionPanel();
}); });
} }
}, },
"json" "json"
); );
});
}; };

View File

@ -54,7 +54,7 @@ $.i18n().load(dictionary, lang);
var name = window.prompt(prompt, theProject.metadata.name); var name = window.prompt(prompt, theProject.metadata.name);
if (name) { if (name) {
var dismiss = DialogSystem.showBusy($.i18n('gdata-exporter/uploading')); var dismiss = DialogSystem.showBusy($.i18n('gdata-exporter/uploading'));
$.post( Refine.postCSRF(
"command/gdata/upload", "command/gdata/upload",
{ {
"project" : theProject.id, "project" : theProject.id,

View File

@ -66,7 +66,9 @@
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.version}</version> <version>${surefire.version}</version>
<configuration> <configuration>
<skip>true</skip> <suiteXmlFiles>
<suiteXmlFile>tests/conf/tests.xml</suiteXmlFile>
</suiteXmlFiles>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
@ -162,6 +164,12 @@
<version>6.9.10</version> <version>6.9.10</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.23.4</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>

View File

@ -43,6 +43,10 @@ public class UploadCommand 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(!hasValidCSRFToken(request)) {
respondCSRFError(response);
return;
}
String token = TokenCookie.getToken(request); String token = TokenCookie.getToken(request);
if (token == null) { if (token == null) {

View File

@ -0,0 +1,14 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="GData extension">
<test name="tests">
<groups>
<run>
<exclude name="broken" />
</run>
</groups>
<packages>
<package name="com.google.refine.extension.gdata.*" />
</packages>
</test>
</suite>

View File

@ -0,0 +1,49 @@
package com.google.refine.extension.gdata;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.testng.Assert;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.refine.commands.Command;
import com.google.refine.util.ParsingUtilities;
public class UploadCommandTest {
protected HttpServletRequest request = null;
protected HttpServletResponse response = null;
protected Command command = null;
protected StringWriter writer = null;
@BeforeMethod
public void setUpRequestResponse() {
request = mock(HttpServletRequest.class);
response = mock(HttpServletResponse.class);
writer = new StringWriter();
command = new UploadCommand();
try {
when(response.getWriter()).thenReturn(new PrintWriter(writer));
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testCsrfProtection() throws ServletException, IOException {
command.doPost(request, response);
Assert.assertEquals(
ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class),
ParsingUtilities.mapper.readValue(writer.toString(), ObjectNode.class));
}
}