diff --git a/extensions/database/module/scripts/database-extension.js b/extensions/database/module/scripts/database-extension.js index 4c4347935..ab4235945 100644 --- a/extensions/database/module/scripts/database-extension.js +++ b/extensions/database/module/scripts/database-extension.js @@ -69,7 +69,7 @@ DatabaseExtension.handleConnectClicked = function(connectionName) { databaseConfig.initialDatabase = savedConfig.databaseName; databaseConfig.initialSchema = savedConfig.databaseSchema; - $.post( + Refine.postCSRF( "command/database/connect", databaseConfig, @@ -101,10 +101,10 @@ DatabaseExtension.handleConnectClicked = function(connectionName) { } }, - "json" - ).fail(function( jqXhr, textStatus, errorThrown ){ - alert( textStatus + ':' + errorThrown ); - }); + "json", + function( jqXhr, textStatus, errorThrown ){ + alert( textStatus + ':' + errorThrown ); + }); } diff --git a/extensions/database/module/scripts/index/database-import-controller.js b/extensions/database/module/scripts/index/database-import-controller.js index 636af183c..30eb34938 100644 --- a/extensions/database/module/scripts/index/database-import-controller.js +++ b/extensions/database/module/scripts/index/database-import-controller.js @@ -65,33 +65,36 @@ Refine.DatabaseImportController.prototype.startImportingDocument = function(quer //alert(queryInfo.query); var self = this; - $.post( + Refine.postCSRF( "command/core/create-importing-job", null, function(data) { - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "database/database-import-controller", - "subCommand": "initialize-parser-ui" - }), - queryInfo, - - function(data2) { - dismiss(); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "database/database-import-controller", + "subCommand": "initialize-parser-ui", + "csrf_token": token + }), + queryInfo, - if (data2.status == 'ok') { - self._queryInfo = queryInfo; - self._jobID = data.jobID; - self._options = data2.options; - - self._showParsingPanel(); - - } else { - alert(data2.message); - } - }, - "json" - ); + function(data2) { + dismiss(); + + if (data2.status == 'ok') { + self._queryInfo = queryInfo; + self._jobID = data.jobID; + self._options = data2.options; + + self._showParsingPanel(); + + } else { + alert(data2.message); + } + }, + "json" + ); + }); }, "json" ); @@ -248,40 +251,43 @@ Refine.DatabaseImportController.prototype._updatePreview = function() { this._queryInfo.options = JSON.stringify(this.getOptions()); //alert("options:" + this._queryInfo.options); - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "database/database-import-controller", - "jobID": this._jobID, - "subCommand": "parse-preview" - }), - - this._queryInfo, - - function(result) { - if (result.status == "ok") { - self._getPreviewData(function(projectData) { - self._parsingPanelElmts.progressPanel.hide(); - self._parsingPanelElmts.dataPanel.show(); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "database/database-import-controller", + "jobID": self._jobID, + "subCommand": "parse-preview", + "csrf_token": token + }), + + this._queryInfo, + + function(result) { + if (result.status == "ok") { + self._getPreviewData(function(projectData) { + self._parsingPanelElmts.progressPanel.hide(); + self._parsingPanelElmts.dataPanel.show(); - new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty()); - }); - } else { - - alert('Errors:\n' + (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job)); - self._parsingPanelElmts.progressPanel.hide(); - - Refine.CreateProjectUI.cancelImportingJob(self._jobID); - - delete self._jobID; - delete self._options; - - self._createProjectUI.showSourceSelectionPanel(); - - - } - }, - "json" - ); + new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty()); + }); + } else { + + alert('Errors:\n' + (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job)); + self._parsingPanelElmts.progressPanel.hide(); + + Refine.CreateProjectUI.cancelImportingJob(self._jobID); + + delete self._jobID; + delete self._options; + + self._createProjectUI.showSourceSelectionPanel(); + + + } + }, + "json" + ); + }); }; Refine.DatabaseImportController.prototype._getPreviewData = function(callback, numRows) { @@ -329,51 +335,54 @@ Refine.DatabaseImportController.prototype._createProject = function() { options.projectName = projectName; this._queryInfo.options = JSON.stringify(options); - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "database/database-import-controller", - "jobID": this._jobID, - "subCommand": "create-project" - }), - this._queryInfo, - function(o) { - if (o.status == 'error') { - alert(o.message); - } else { - var start = new Date(); - var timerID = window.setInterval( - function() { - self._createProjectUI.pollImportJob( - start, - self._jobID, - timerID, - function(job) { - return "projectID" in job.config; - }, - function(jobID, job) { - //alert("jobID::" + jobID + " job :" + job); - window.clearInterval(timerID); - Refine.CreateProjectUI.cancelImportingJob(jobID); - document.location = "project?project=" + job.config.projectID; - }, - function(job) { - alert(Refine.CreateProjectUI.composeErrorMessage(job)); - } - ); - }, - 1000 - ); - self._createProjectUI.showImportProgressPanel($.i18n('database-import/creating'), function() { - // stop the timed polling - window.clearInterval(timerID); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "database/database-import-controller", + "jobID": self._jobID, + "subCommand": "create-project", + "csrf_token": token + }), + this._queryInfo, + function(o) { + if (o.status == 'error') { + alert(o.message); + } else { + var start = new Date(); + var timerID = window.setInterval( + function() { + self._createProjectUI.pollImportJob( + start, + self._jobID, + timerID, + function(job) { + return "projectID" in job.config; + }, + function(jobID, job) { + //alert("jobID::" + jobID + " job :" + job); + window.clearInterval(timerID); + Refine.CreateProjectUI.cancelImportingJob(jobID); + document.location = "project?project=" + job.config.projectID; + }, + function(job) { + alert(Refine.CreateProjectUI.composeErrorMessage(job)); + } + ); + }, + 1000 + ); + self._createProjectUI.showImportProgressPanel($.i18n('database-import/creating'), function() { + // stop the timed polling + window.clearInterval(timerID); - // explicitly cancel the import job - Refine.CreateProjectUI.cancelImportingJob(jobID); + // explicitly cancel the import job + Refine.CreateProjectUI.cancelImportingJob(jobID); - self._createProjectUI.showSourceSelectionPanel(); - }); - } - }, - "json" - ); + self._createProjectUI.showSourceSelectionPanel(); + }); + } + }, + "json" + ); + }); }; diff --git a/extensions/database/module/scripts/index/database-source-ui.js b/extensions/database/module/scripts/index/database-source-ui.js index 4a4e6e234..14c1d0acd 100644 --- a/extensions/database/module/scripts/index/database-source-ui.js +++ b/extensions/database/module/scripts/index/database-source-ui.js @@ -268,7 +268,7 @@ Refine.DatabaseSourceUI.prototype._executeQuery = function(jdbcQueryInfo) { var dismiss = DialogSystem.showBusy($.i18n('database-import/checking')); - $.post( + Refine.postCSRF( "command/database/test-query", jdbcQueryInfo, function(jdbcConnectionResult) { @@ -277,8 +277,8 @@ Refine.DatabaseSourceUI.prototype._executeQuery = function(jdbcQueryInfo) { self._controller.startImportingDocument(jdbcQueryInfo); }, - "json" - ).fail(function( jqXhr, textStatus, errorThrown ){ + "json", + function( jqXhr, textStatus, errorThrown ){ dismiss(); alert( textStatus + ':' + errorThrown ); @@ -288,7 +288,7 @@ Refine.DatabaseSourceUI.prototype._executeQuery = function(jdbcQueryInfo) { Refine.DatabaseSourceUI.prototype._saveConnection = function(jdbcConnectionInfo) { var self = this; - $.post( + Refine.postCSRF( "command/database/saved-connection", jdbcConnectionInfo, function(settings) { @@ -307,8 +307,8 @@ Refine.DatabaseSourceUI.prototype._saveConnection = function(jdbcConnectionInfo) } }, - "json" - ).fail(function( jqXhr, textStatus, errorThrown ){ + "json", + function( jqXhr, textStatus, errorThrown ){ alert( textStatus + ':' + errorThrown ); }); @@ -346,7 +346,7 @@ Refine.DatabaseSourceUI.prototype._loadSavedConnections = function() { Refine.DatabaseSourceUI.prototype._testDatabaseConnect = function(jdbcConnectionInfo) { var self = this; - $.post( + Refine.postCSRF( "command/database/test-connect", jdbcConnectionInfo, function(jdbcConnectionResult) { @@ -357,8 +357,8 @@ Refine.DatabaseSourceUI.prototype._testDatabaseConnect = function(jdbcConnection } }, - "json" - ).fail(function( jqXhr, textStatus, errorThrown ){ + "json", + function( jqXhr, textStatus, errorThrown ){ alert( textStatus + ':' + errorThrown ); }); }; @@ -366,7 +366,7 @@ Refine.DatabaseSourceUI.prototype._testDatabaseConnect = function(jdbcConnection Refine.DatabaseSourceUI.prototype._connect = function(jdbcConnectionInfo) { var self = this; - $.post( + Refine.postCSRF( "command/database/connect", jdbcConnectionInfo, function(databaseInfo) { @@ -398,8 +398,8 @@ Refine.DatabaseSourceUI.prototype._connect = function(jdbcConnectionInfo) { } }, - "json" - ).fail(function( jqXhr, textStatus, errorThrown ){ + "json", + function( jqXhr, textStatus, errorThrown ){ alert( textStatus + ':' + errorThrown ); }); diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java index 19f06d86a..0e1fa1e6a 100644 --- a/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java +++ b/extensions/database/src/com/google/refine/extension/database/cmd/ConnectCommand.java @@ -56,6 +56,10 @@ public class ConnectCommand extends DatabaseCommand { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request); if(logger.isDebugEnabled()) { diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java index 17d70d954..863ec9423 100644 --- a/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java +++ b/extensions/database/src/com/google/refine/extension/database/cmd/ExecuteQueryCommand.java @@ -56,7 +56,10 @@ public class ExecuteQueryCommand extends DatabaseCommand { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request); String query = request.getParameter("queryString"); diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java index a30e2d000..0d3816d8e 100644 --- a/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java +++ b/extensions/database/src/com/google/refine/extension/database/cmd/SavedConnectionCommand.java @@ -228,6 +228,10 @@ public class SavedConnectionCommand extends DatabaseCommand { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } if(logger.isDebugEnabled()) { logger.debug("doPost Connection: {}", request.getParameter("connectionName")); diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java index 460a3ffc6..af7b6f0f9 100644 --- a/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java +++ b/extensions/database/src/com/google/refine/extension/database/cmd/TestConnectCommand.java @@ -54,7 +54,10 @@ public class TestConnectCommand extends DatabaseCommand { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } DatabaseConfiguration databaseConfiguration = getJdbcConfiguration(request); if(logger.isDebugEnabled()) { diff --git a/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java b/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java index 5ce53961d..82610f38b 100644 --- a/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java +++ b/extensions/database/src/com/google/refine/extension/database/cmd/TestQueryCommand.java @@ -56,6 +56,10 @@ public class TestQueryCommand extends DatabaseCommand { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } DatabaseConfiguration dbConfig = getJdbcConfiguration(request); String query = request.getParameter("query"); diff --git a/extensions/database/tests/src/com/google/refine/extension/database/cmd/ConnectCommandTest.java b/extensions/database/tests/src/com/google/refine/extension/database/cmd/ConnectCommandTest.java index 1ddc45267..96a2c0337 100644 --- a/extensions/database/tests/src/com/google/refine/extension/database/cmd/ConnectCommandTest.java +++ b/extensions/database/tests/src/com/google/refine/extension/database/cmd/ConnectCommandTest.java @@ -20,6 +20,7 @@ import org.testng.annotations.Parameters; import org.testng.annotations.Test; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.refine.commands.Command; import com.google.refine.extension.database.DBExtensionTests; import com.google.refine.extension.database.DatabaseConfiguration; import com.google.refine.extension.database.DatabaseService; @@ -75,6 +76,7 @@ public class ConnectCommandTest extends DBExtensionTests { when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -94,5 +96,18 @@ public class ConnectCommandTest extends DBExtensionTests { Assert.assertNotNull(databaseInfo); } + @Test + public void testCsrfProtection() throws ServletException, IOException { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + when(response.getWriter()).thenReturn(pw); + ConnectCommand connectCommand = new ConnectCommand(); + + connectCommand.doPost(request, response); + Assert.assertEquals( + ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class), + ParsingUtilities.mapper.readValue(sw.toString(), ObjectNode.class)); + } } diff --git a/extensions/database/tests/src/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java b/extensions/database/tests/src/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java index 92fb66b28..1dfafe3ce 100644 --- a/extensions/database/tests/src/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java +++ b/extensions/database/tests/src/com/google/refine/extension/database/cmd/ExecuteQueryCommandTest.java @@ -19,6 +19,7 @@ import org.testng.annotations.Parameters; import org.testng.annotations.Test; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.refine.commands.Command; import com.google.refine.extension.database.DBExtensionTests; import com.google.refine.extension.database.DatabaseConfiguration; import com.google.refine.extension.database.DatabaseService; @@ -72,6 +73,7 @@ public class ExecuteQueryCommandTest extends DBExtensionTests { when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); when(request.getParameter("queryString")).thenReturn("SELECT count(*) FROM " + testTable); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); @@ -93,4 +95,17 @@ public class ExecuteQueryCommandTest extends DBExtensionTests { Assert.assertNotNull(queryResult); } + @Test + public void testCsrfProtection() throws ServletException, IOException { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + when(response.getWriter()).thenReturn(pw); + ConnectCommand connectCommand = new ConnectCommand(); + + connectCommand.doPost(request, response); + Assert.assertEquals( + ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class), + ParsingUtilities.mapper.readValue(sw.toString(), ObjectNode.class)); + } } diff --git a/extensions/database/tests/src/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java b/extensions/database/tests/src/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java index 32ea6fb27..2225b41f7 100644 --- a/extensions/database/tests/src/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java +++ b/extensions/database/tests/src/com/google/refine/extension/database/cmd/SavedConnectionCommandTest.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.refine.ProjectManager; import com.google.refine.ProjectMetadata; import com.google.refine.RefineServlet; +import com.google.refine.commands.Command; import com.google.refine.extension.database.DBExtensionTestUtils; import com.google.refine.extension.database.DBExtensionTests; import com.google.refine.extension.database.DatabaseConfiguration; @@ -125,6 +126,7 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -150,6 +152,7 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -187,6 +190,7 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -227,6 +231,7 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); SUT.doPut(request, response); @@ -309,6 +314,7 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -320,7 +326,18 @@ public class SavedConnectionCommandTest extends DBExtensionTests{ verify(response, times(1)).sendError(HttpStatus.SC_BAD_REQUEST, "Connection Name is Invalid. Expecting [a-zA-Z0-9._-]"); } - + @Test + public void testCsrfProtection() throws ServletException, IOException { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + when(response.getWriter()).thenReturn(pw); + + SUT.doPost(request, response); + Assert.assertEquals( + ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class), + ParsingUtilities.mapper.readValue(sw.toString(), ObjectNode.class)); + } } diff --git a/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestConnectCommandTest.java b/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestConnectCommandTest.java index 064f29de9..7666e3828 100644 --- a/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestConnectCommandTest.java +++ b/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestConnectCommandTest.java @@ -19,6 +19,7 @@ import org.testng.annotations.Parameters; import org.testng.annotations.Test; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.refine.commands.Command; import com.google.refine.extension.database.DBExtensionTests; import com.google.refine.extension.database.DatabaseConfiguration; import com.google.refine.extension.database.DatabaseService; @@ -74,6 +75,7 @@ public class TestConnectCommandTest extends DBExtensionTests{ when(request.getParameter("databaseUser")).thenReturn(testDbConfig.getDatabaseUser()); when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); @@ -92,5 +94,19 @@ public class TestConnectCommandTest extends DBExtensionTests{ Assert.assertEquals(code, "ok"); } + + @Test + public void testCsrfProtection() throws ServletException, IOException { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + when(response.getWriter()).thenReturn(pw); + ConnectCommand connectCommand = new ConnectCommand(); + + connectCommand.doPost(request, response); + Assert.assertEquals( + ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class), + ParsingUtilities.mapper.readValue(sw.toString(), ObjectNode.class)); + } } diff --git a/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestQueryCommandTest.java b/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestQueryCommandTest.java index 5a87fbc2b..ab56dc7e6 100644 --- a/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestQueryCommandTest.java +++ b/extensions/database/tests/src/com/google/refine/extension/database/cmd/TestQueryCommandTest.java @@ -19,6 +19,7 @@ import org.testng.annotations.Parameters; import org.testng.annotations.Test; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.refine.commands.Command; import com.google.refine.extension.database.DBExtensionTests; import com.google.refine.extension.database.DatabaseConfiguration; import com.google.refine.extension.database.DatabaseService; @@ -73,7 +74,7 @@ public class TestQueryCommandTest extends DBExtensionTests { when(request.getParameter("databasePassword")).thenReturn(testDbConfig.getDatabasePassword()); when(request.getParameter("initialDatabase")).thenReturn(testDbConfig.getDatabaseName()); when(request.getParameter("query")).thenReturn("SELECT count(*) FROM " + testTable); - + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); StringWriter sw = new StringWriter(); @@ -94,5 +95,19 @@ public class TestQueryCommandTest extends DBExtensionTests { Assert.assertNotNull(queryResult); } + + @Test + public void testCsrfProtection() throws ServletException, IOException { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + when(response.getWriter()).thenReturn(pw); + TestQueryCommand connectCommand = new TestQueryCommand(); + + connectCommand.doPost(request, response); + Assert.assertEquals( + ParsingUtilities.mapper.readValue("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", ObjectNode.class), + ParsingUtilities.mapper.readValue(sw.toString(), ObjectNode.class)); + } } diff --git a/extensions/gdata/module/scripts/index/gdata-source-ui.js b/extensions/gdata/module/scripts/index/gdata-source-ui.js index d67e6ed50..d7773ba42 100644 --- a/extensions/gdata/module/scripts/index/gdata-source-ui.js +++ b/extensions/gdata/module/scripts/index/gdata-source-ui.js @@ -109,17 +109,20 @@ Refine.GDataSourceUI.prototype._listDocuments = function() { this._elmts.progressPage.show(); var self = this; - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "gdata/gdata-importing-controller", - "subCommand": "list-documents" - }), - null, - function(o) { - self._renderDocuments(o); - }, - "json" - ); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "gdata/gdata-importing-controller", + "subCommand": "list-documents", + "csrf_token": token + }), + null, + function(o) { + self._renderDocuments(o); + }, + "json" + ); + }); }; Refine.GDataSourceUI.prototype._renderDocuments = function(o) { diff --git a/extensions/gdata/module/scripts/index/importing-controller.js b/extensions/gdata/module/scripts/index/importing-controller.js index eda0afa16..d8fbde184 100644 --- a/extensions/gdata/module/scripts/index/importing-controller.js +++ b/extensions/gdata/module/scripts/index/importing-controller.js @@ -71,33 +71,36 @@ Refine.GDataImportingController.prototype.startImportingDocument = function(doc) var dismiss = DialogSystem.showBusy($.i18n('gdata-import/preparing')); var self = this; - $.post( + Refine.postCSRF( "command/core/create-importing-job", null, function(data) { - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "gdata/gdata-importing-controller", - "subCommand": "initialize-parser-ui", - "docUrl": doc.docSelfLink, - "docType": doc.type - }), - null, - function(data2) { - dismiss(); - - if (data2.status == 'ok') { - self._doc = doc; - self._jobID = data.jobID; - self._options = data2.options; + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "gdata/gdata-importing-controller", + "subCommand": "initialize-parser-ui", + "docUrl": doc.docSelfLink, + "docType": doc.type, + "csrf_token": token + }), + null, + function(data2) { + dismiss(); - self._showParsingPanel(); - } else { - alert(data2.message); - } - }, - "json" - ); + if (data2.status == 'ok') { + self._doc = doc; + self._jobID = data.jobID; + self._options = data2.options; + + self._showParsingPanel(); + } else { + alert(data2.message); + } + }, + "json" + ); + }); }, "json" ); @@ -315,31 +318,34 @@ Refine.GDataImportingController.prototype._updatePreview = function() { this._parsingPanelElmts.dataPanel.hide(); this._parsingPanelElmts.progressPanel.show(); - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "gdata/gdata-importing-controller", - "jobID": this._jobID, - "subCommand": "parse-preview" - }), - { - "options" : JSON.stringify(this.getOptions()) - }, - function(result) { - if (result.status == "ok") { - self._getPreviewData(function(projectData) { - self._parsingPanelElmts.progressPanel.hide(); - self._parsingPanelElmts.dataPanel.show(); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "gdata/gdata-importing-controller", + "jobID": self._jobID, + "subCommand": "parse-preview", + "csrf_token": token + }), + { + "options" : JSON.stringify(self.getOptions()) + }, + function(result) { + if (result.status == "ok") { + self._getPreviewData(function(projectData) { + self._parsingPanelElmts.progressPanel.hide(); + self._parsingPanelElmts.dataPanel.show(); - new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty()); - }); - } else { - self._parsingPanelElmts.progressPanel.hide(); - alert('Errors:\n' + - (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job)); - } - }, - "json" - ); + new Refine.PreviewTable(projectData, self._parsingPanelElmts.dataPanel.unbind().empty()); + }); + } else { + self._parsingPanelElmts.progressPanel.hide(); + alert('Errors:\n' + + (result.message) ? result.message : Refine.CreateProjectUI.composeErrorMessage(job)); + } + }, + "json" + ); + }); }; Refine.GDataImportingController.prototype._getPreviewData = function(callback, numRows) { @@ -385,52 +391,55 @@ Refine.GDataImportingController.prototype._createProject = function() { var self = this; var options = this.getOptions(); options.projectName = projectName; - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "gdata/gdata-importing-controller", - "jobID": this._jobID, - "subCommand": "create-project" - }), - { - "options" : JSON.stringify(options) - }, - function(o) { - if (o.status == 'error') { - alert(o.message); - } else { - var start = new Date(); - var timerID = window.setInterval( - function() { - self._createProjectUI.pollImportJob( - start, - self._jobID, - timerID, - function(job) { - return "projectID" in job.config; - }, - function(jobID, job) { - window.clearInterval(timerID); - Refine.CreateProjectUI.cancelImportingJob(jobID); - document.location = "project?project=" + job.config.projectID; - }, - function(job) { - alert(Refine.CreateProjectUI.composeErrorMessage(job)); - } + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "gdata/gdata-importing-controller", + "jobID": self._jobID, + "subCommand": "create-project", + "csrf_token": token + }), + { + "options" : JSON.stringify(options) + }, + function(o) { + if (o.status == 'error') { + alert(o.message); + } else { + var start = new Date(); + var timerID = window.setInterval( + function() { + self._createProjectUI.pollImportJob( + start, + self._jobID, + timerID, + function(job) { + return "projectID" in job.config; + }, + function(jobID, job) { + window.clearInterval(timerID); + Refine.CreateProjectUI.cancelImportingJob(jobID); + document.location = "project?project=" + job.config.projectID; + }, + function(job) { + alert(Refine.CreateProjectUI.composeErrorMessage(job)); + } + ); + }, + 1000 ); - }, - 1000 - ); - self._createProjectUI.showImportProgressPanel($.i18n('gdata-import/creating'), function() { - // 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 - Refine.CreateProjectUI.cancelImportingJob(jobID); + // explicitly cancel the import job + Refine.CreateProjectUI.cancelImportingJob(jobID); - self._createProjectUI.showSourceSelectionPanel(); - }); - } - }, - "json" - ); + self._createProjectUI.showSourceSelectionPanel(); + }); + } + }, + "json" + ); + }); }; diff --git a/extensions/gdata/module/scripts/project/exporters.js b/extensions/gdata/module/scripts/project/exporters.js index 92e244746..6a3f46256 100644 --- a/extensions/gdata/module/scripts/project/exporters.js +++ b/extensions/gdata/module/scripts/project/exporters.js @@ -54,7 +54,7 @@ $.i18n().load(dictionary, lang); var name = window.prompt(prompt, theProject.metadata.name); if (name) { var dismiss = DialogSystem.showBusy($.i18n('gdata-exporter/uploading')); - $.post( + Refine.postCSRF( "command/gdata/upload", { "project" : theProject.id, diff --git a/extensions/gdata/pom.xml b/extensions/gdata/pom.xml index 787a5d13c..934720a9c 100644 --- a/extensions/gdata/pom.xml +++ b/extensions/gdata/pom.xml @@ -66,7 +66,9 @@ maven-surefire-plugin ${surefire.version} - true + + tests/conf/tests.xml + @@ -162,6 +164,12 @@ 6.9.10 test + + org.mockito + mockito-core + 2.23.4 + test + diff --git a/extensions/gdata/src/com/google/refine/extension/gdata/UploadCommand.java b/extensions/gdata/src/com/google/refine/extension/gdata/UploadCommand.java index e2272fecd..122b6f47b 100644 --- a/extensions/gdata/src/com/google/refine/extension/gdata/UploadCommand.java +++ b/extensions/gdata/src/com/google/refine/extension/gdata/UploadCommand.java @@ -43,6 +43,10 @@ public class UploadCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } String token = TokenCookie.getToken(request); if (token == null) { diff --git a/extensions/gdata/tests/conf/tests.xml b/extensions/gdata/tests/conf/tests.xml new file mode 100644 index 000000000..df5920edc --- /dev/null +++ b/extensions/gdata/tests/conf/tests.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/extensions/gdata/tests/src/com/google/refine/extension/gdata/UploadCommandTest.java b/extensions/gdata/tests/src/com/google/refine/extension/gdata/UploadCommandTest.java new file mode 100644 index 000000000..5f04f585c --- /dev/null +++ b/extensions/gdata/tests/src/com/google/refine/extension/gdata/UploadCommandTest.java @@ -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)); + + } +} diff --git a/extensions/wikidata/module/scripts/dialogs/manage-account-dialog.js b/extensions/wikidata/module/scripts/dialogs/manage-account-dialog.js index 22fe0d35e..fb35fd32e 100644 --- a/extensions/wikidata/module/scripts/dialogs/manage-account-dialog.js +++ b/extensions/wikidata/module/scripts/dialogs/manage-account-dialog.js @@ -56,7 +56,7 @@ ManageAccountDialog.display = function(logged_in_username, saved_credentials, ca elmts.loginButton.click(function() { frame.hide(); - $.post( + Refine.postCSRF( "command/wikidata/login", elmts.loginForm.serialize(), function(data) { @@ -71,7 +71,7 @@ ManageAccountDialog.display = function(logged_in_username, saved_credentials, ca }); elmts.logoutButton.click(function() { - $.post( + Refine.postCSRF( "command/wikidata/login", "logout=true", function(data) { diff --git a/extensions/wikidata/module/scripts/dialogs/perform-edits-dialog.js b/extensions/wikidata/module/scripts/dialogs/perform-edits-dialog.js index 089cfedab..fa2bfd718 100644 --- a/extensions/wikidata/module/scripts/dialogs/perform-edits-dialog.js +++ b/extensions/wikidata/module/scripts/dialogs/perform-edits-dialog.js @@ -94,7 +94,7 @@ PerformEditsDialog.checkAndLaunch = function () { ManageAccountDialog.ensureLoggedIn(function(logged_in_username) { if (logged_in_username) { var discardWaiter = DialogSystem.showBusy($.i18n('perform-wikidata-edits/analyzing-edits')); - $.post( + Refine.postCSRF( "command/wikidata/preview-wikibase-schema?" + $.param({ project: theProject.id }), { engine: JSON.stringify(ui.browsingEngine.getJSON()) }, function(data) { diff --git a/extensions/wikidata/module/scripts/dialogs/schema-alignment-dialog.js b/extensions/wikidata/module/scripts/dialogs/schema-alignment-dialog.js index 6c982d6a2..cc6760e17 100644 --- a/extensions/wikidata/module/scripts/dialogs/schema-alignment-dialog.js +++ b/extensions/wikidata/module/scripts/dialogs/schema-alignment-dialog.js @@ -1283,7 +1283,7 @@ SchemaAlignmentDialog.preview = function() { $('.invalid-schema-warning').show(); return; } - $.post( + Refine.postCSRF( "command/wikidata/preview-wikibase-schema?" + $.param({ project: theProject.id }), { schema: JSON.stringify(schema), engine: JSON.stringify(ui.browsingEngine.getJSON()) }, function(data) { diff --git a/extensions/wikidata/src/org/openrefine/wikidata/commands/LoginCommand.java b/extensions/wikidata/src/org/openrefine/wikidata/commands/LoginCommand.java index 83c194582..cc3330934 100644 --- a/extensions/wikidata/src/org/openrefine/wikidata/commands/LoginCommand.java +++ b/extensions/wikidata/src/org/openrefine/wikidata/commands/LoginCommand.java @@ -41,6 +41,11 @@ public class LoginCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + String username = request.getParameter("wb-username"); String password = request.getParameter("wb-password"); String remember = request.getParameter("remember-credentials"); diff --git a/extensions/wikidata/src/org/openrefine/wikidata/commands/PreviewWikibaseSchemaCommand.java b/extensions/wikidata/src/org/openrefine/wikidata/commands/PreviewWikibaseSchemaCommand.java index 7c1c25621..1ebdfb1dd 100644 --- a/extensions/wikidata/src/org/openrefine/wikidata/commands/PreviewWikibaseSchemaCommand.java +++ b/extensions/wikidata/src/org/openrefine/wikidata/commands/PreviewWikibaseSchemaCommand.java @@ -45,6 +45,13 @@ import com.google.refine.commands.Command; import com.google.refine.model.Project; public class PreviewWikibaseSchemaCommand extends Command { + + /** + * This command uses POST but is left CSRF-unprotected since it does not + * incur a side effect or state change in the backend. + * The reason why it uses POST is to make sure large schemas and engines + * can be passed as parameters. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) diff --git a/extensions/wikidata/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommand.java b/extensions/wikidata/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommand.java index 0115ff8d0..728eeefd2 100644 --- a/extensions/wikidata/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommand.java +++ b/extensions/wikidata/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommand.java @@ -46,6 +46,10 @@ public class SaveWikibaseSchemaCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/LoginCommandTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/LoginCommandTest.java index 36ca6c4bd..10221863b 100644 --- a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/LoginCommandTest.java +++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/LoginCommandTest.java @@ -1,6 +1,7 @@ package org.openrefine.wikidata.commands; import static org.testng.Assert.assertEquals; +import static org.mockito.Mockito.when; import java.io.IOException; @@ -9,6 +10,9 @@ import javax.servlet.ServletException; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.refine.commands.Command; +import com.google.refine.util.TestUtils; + public class LoginCommandTest extends CommandTest { @BeforeMethod @@ -18,8 +22,16 @@ public class LoginCommandTest extends CommandTest { @Test public void testNoCredentials() throws ServletException, IOException { + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); + command.doPost(request, response); assertEquals("{\"logged_in\":false,\"username\":null}", writer.toString()); } + + @Test + public void testCsrfProtection() throws ServletException, IOException { + command.doPost(request, response); + TestUtils.assertEqualAsJson("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", writer.toString()); + } } diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommandTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommandTest.java index cd981382b..26d094c67 100644 --- a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommandTest.java +++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SaveWikibaseSchemaCommandTest.java @@ -34,6 +34,9 @@ import javax.servlet.ServletException; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.refine.commands.Command; +import com.google.refine.util.TestUtils; + public class SaveWikibaseSchemaCommandTest extends SchemaCommandTest { @BeforeMethod @@ -44,6 +47,8 @@ public class SaveWikibaseSchemaCommandTest extends SchemaCommandTest { @Test public void testValidSchema() throws ServletException, IOException { + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); + String schemaJson = jsonFromFile("schema/inception.json").toString(); when(request.getParameter("schema")).thenReturn(schemaJson); @@ -54,6 +59,8 @@ public class SaveWikibaseSchemaCommandTest extends SchemaCommandTest { @Test public void testInvalidSchema() throws ServletException, IOException { + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); + String schemaJson = "{\"itemDocuments\":[{\"statementGroups\":[{\"statements\":[]}]," +"\"nameDescs\":[]}],\"wikibasePrefix\":\"http://www.wikidata.org/entity/\"}"; @@ -62,4 +69,13 @@ public class SaveWikibaseSchemaCommandTest extends SchemaCommandTest { assertTrue(writer.toString().contains("\"error\"")); } + + @Test + public void testCsrfProtection() throws ServletException, IOException { + String schemaJson = jsonFromFile("schema/inception.json").toString(); + when(request.getParameter("schema")).thenReturn(schemaJson); + + command.doPost(request, response); + TestUtils.assertEqualAsJson("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", writer.toString()); + } } diff --git a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SchemaCommandTest.java b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SchemaCommandTest.java index 8af1528cd..7316f2906 100644 --- a/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SchemaCommandTest.java +++ b/extensions/wikidata/tests/src/org/openrefine/wikidata/commands/SchemaCommandTest.java @@ -32,6 +32,7 @@ import javax.servlet.ServletException; import org.testng.annotations.Test; +import com.google.refine.commands.Command; import com.google.refine.util.ParsingUtilities; public abstract class SchemaCommandTest extends CommandTest { @@ -39,9 +40,11 @@ public abstract class SchemaCommandTest extends CommandTest { @Test public void testNoSchema() throws ServletException, IOException { + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); + command.doPost(request, response); - assertEquals("{\"code\":\"error\",\"message\":\"No Wikibase schema provided.\"}", writer.toString()); + assertEquals(writer.toString(), "{\"code\":\"error\",\"message\":\"No Wikibase schema provided.\"}"); } @Test diff --git a/main/src/com/google/refine/commands/CSRFTokenFactory.java b/main/src/com/google/refine/commands/CSRFTokenFactory.java new file mode 100644 index 000000000..af9c1ac36 --- /dev/null +++ b/main/src/com/google/refine/commands/CSRFTokenFactory.java @@ -0,0 +1,94 @@ +package com.google.refine.commands; + +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang.RandomStringUtils; + +import java.security.SecureRandom; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +/** + * Generates CSRF tokens and checks their validity. + * @author Antonin Delpeuch + * + */ +public class CSRFTokenFactory { + + /** + * Maps each token to the time it was generated + */ + protected final LoadingCache tokenCache; + + /** + * Time to live for tokens, in seconds + */ + protected final long timeToLive; + + /** + * Length of the tokens to generate + */ + protected final int tokenLength; + + /** + * Random number generator used to create tokens + */ + protected final SecureRandom rng; + + /** + * Constructs a new CSRF token factory. + * + * @param timeToLive + * Time to live for tokens, in seconds + * @param tokenLength + * Length of the tokens generated + */ + public CSRFTokenFactory(long timeToLive, int tokenLength) { + tokenCache = CacheBuilder.newBuilder() + .expireAfterWrite(timeToLive, TimeUnit.SECONDS) + .build( + new CacheLoader() { + @Override + public Instant load(String key) { + return Instant.now(); + } + + }); + this.timeToLive = timeToLive; + this.rng = new SecureRandom(); + this.tokenLength = tokenLength; + } + + /** + * Generates a fresh CSRF token, which will remain valid for the configured amount of time. + */ + public String getFreshToken() { + // Generate a random token + String token = RandomStringUtils.random(tokenLength, 0, 0, true, true, null, rng); + // Put it in the cache + try { + tokenCache.get(token); + } catch (ExecutionException e) { + // cannot happen + } + return token; + } + + /** + * Checks that a given CSRF token is valid. + * @param token + * the token to verify + * @return + * true if the token is valid + */ + public boolean validToken(String token) { + Map map = tokenCache.asMap(); + Instant cutoff = Instant.now().minusSeconds(timeToLive); + return map.containsKey(token) && map.get(token).isAfter(cutoff); + } +} diff --git a/main/src/com/google/refine/commands/Command.java b/main/src/com/google/refine/commands/Command.java index 5c605f7e5..7cfa2bda0 100644 --- a/main/src/com/google/refine/commands/Command.java +++ b/main/src/com/google/refine/commands/Command.java @@ -37,6 +37,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import javax.servlet.ServletException; @@ -66,6 +68,8 @@ import com.google.refine.util.ParsingUtilities; public abstract class Command { final static protected Logger logger = LoggerFactory.getLogger("command"); + + final static public CSRFTokenFactory csrfFactory = new CSRFTokenFactory(3600, 32); protected RefineServlet servlet; @@ -215,6 +219,42 @@ public abstract class Command { return def; } + /** + * Utility method for retrieving the CSRF token stored in the "csrf_token" parameter of the request, + * and checking that it is valid. + * + * @param request + * @return + * @throws ServletException + */ + protected boolean hasValidCSRFToken(HttpServletRequest request) throws ServletException { + if (request == null) { + throw new IllegalArgumentException("parameter 'request' should not be null"); + } + try { + String token = request.getParameter("csrf_token"); + return token != null && csrfFactory.validToken(token); + } catch (Exception e) { + // ignore + } + throw new ServletException("Can't find CSRF token: missing or bad URL parameter"); + } + + + /** + * Checks the validity of a CSRF token, without reading the whole POST body. + * Useful when we need to control how the POST body is read (for instance if it + * contains files). + */ + protected boolean hasValidCSRFTokenAsGET(HttpServletRequest request) { + if (request == null) { + throw new IllegalArgumentException("parameter 'request' should not be null"); + } + Properties options = ParsingUtilities.parseUrlParameters(request); + String token = options.getProperty("csrf_token"); + return token != null && csrfFactory.validToken(token); + } + protected static class HistoryEntryResponse { @JsonProperty("code") protected String getCode() { return "ok"; } @@ -277,7 +317,7 @@ public abstract class Command { w.close(); } - static protected void respondJSON(HttpServletResponse response, Object o) + public static void respondJSON(HttpServletResponse response, Object o) throws IOException { respondJSON(response, o, new Properties()); @@ -297,6 +337,13 @@ public abstract class Command { w.flush(); w.close(); } + + static protected void respondCSRFError(HttpServletResponse response) throws IOException { + Map responseJSON = new HashMap<>(); + responseJSON.put("code", "error"); + responseJSON.put("message", "Missing or invalid csrf_token parameter"); + respondJSON(response, responseJSON); + } static protected void respondException(HttpServletResponse response, Exception e) throws IOException, ServletException { diff --git a/main/src/com/google/refine/commands/EngineDependentCommand.java b/main/src/com/google/refine/commands/EngineDependentCommand.java index a13330ae5..bc091803d 100644 --- a/main/src/com/google/refine/commands/EngineDependentCommand.java +++ b/main/src/com/google/refine/commands/EngineDependentCommand.java @@ -70,6 +70,10 @@ abstract public class EngineDependentCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/GetAllPreferencesCommand.java b/main/src/com/google/refine/commands/GetAllPreferencesCommand.java index cdf641e08..4cc2212a6 100644 --- a/main/src/com/google/refine/commands/GetAllPreferencesCommand.java +++ b/main/src/com/google/refine/commands/GetAllPreferencesCommand.java @@ -46,6 +46,10 @@ import com.google.refine.model.Project; import com.google.refine.preference.PreferenceStore; public class GetAllPreferencesCommand extends Command { + /** + * The command uses POST (not sure why?) but does not actually modify any state + * so it does not require CSRF. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/commands/GetCSRFTokenCommand.java b/main/src/com/google/refine/commands/GetCSRFTokenCommand.java new file mode 100644 index 000000000..f5f668c35 --- /dev/null +++ b/main/src/com/google/refine/commands/GetCSRFTokenCommand.java @@ -0,0 +1,18 @@ +package com.google.refine.commands; + +import java.io.IOException; +import java.util.Collections; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Generates a fresh CSRF token. + */ +public class GetCSRFTokenCommand extends Command { + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + respondJSON(response, Collections.singletonMap("token", csrfFactory.getFreshToken())); + } +} diff --git a/main/src/com/google/refine/commands/OpenWorkspaceDirCommand.java b/main/src/com/google/refine/commands/OpenWorkspaceDirCommand.java index 49296707a..b09ffb0fe 100644 --- a/main/src/com/google/refine/commands/OpenWorkspaceDirCommand.java +++ b/main/src/com/google/refine/commands/OpenWorkspaceDirCommand.java @@ -48,6 +48,10 @@ public class OpenWorkspaceDirCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } String serverName = request.getServerName(); diff --git a/main/src/com/google/refine/commands/SetPreferenceCommand.java b/main/src/com/google/refine/commands/SetPreferenceCommand.java index 43857e11f..bc897f8a9 100644 --- a/main/src/com/google/refine/commands/SetPreferenceCommand.java +++ b/main/src/com/google/refine/commands/SetPreferenceCommand.java @@ -34,6 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.google.refine.commands; import java.io.IOException; +import java.util.Collections; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -49,6 +50,10 @@ public class SetPreferenceCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } Project project = request.getParameter("project") != null ? getProject(request) : null; PreferenceStore ps = project != null ? @@ -63,7 +68,7 @@ public class SetPreferenceCommand extends Command { ps.put(prefName, PreferenceStore.loadObject(o)); - respond(response, "{ \"code\" : \"ok\" }"); + respondJSON(response, Collections.singletonMap("code", "ok")); } catch (IOException e) { respondException(response, e); } diff --git a/main/src/com/google/refine/commands/browsing/ComputeClustersCommand.java b/main/src/com/google/refine/commands/browsing/ComputeClustersCommand.java index bc86ae763..c499015f7 100644 --- a/main/src/com/google/refine/commands/browsing/ComputeClustersCommand.java +++ b/main/src/com/google/refine/commands/browsing/ComputeClustersCommand.java @@ -52,7 +52,11 @@ import com.google.refine.util.ParsingUtilities; public class ComputeClustersCommand extends Command { final static Logger logger = LoggerFactory.getLogger("compute-clusters_command"); - + + /** + * This command uses POST (probably to allow for larger parameters) but does not actually modify any state + * so we do not add CSRF protection to it. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/commands/browsing/ComputeFacetsCommand.java b/main/src/com/google/refine/commands/browsing/ComputeFacetsCommand.java index b0d91aef1..b27fbd1bc 100644 --- a/main/src/com/google/refine/commands/browsing/ComputeFacetsCommand.java +++ b/main/src/com/google/refine/commands/browsing/ComputeFacetsCommand.java @@ -44,6 +44,11 @@ import com.google.refine.commands.Command; import com.google.refine.model.Project; public class ComputeFacetsCommand extends Command { + + /** + * This command uses POST (probably to allow for larger parameters) but does not actually modify any state + * so we do not add CSRF protection to it. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/commands/cell/EditOneCellCommand.java b/main/src/com/google/refine/commands/cell/EditOneCellCommand.java index e299000ce..55cf39252 100644 --- a/main/src/com/google/refine/commands/cell/EditOneCellCommand.java +++ b/main/src/com/google/refine/commands/cell/EditOneCellCommand.java @@ -84,6 +84,10 @@ public class EditOneCellCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { request.setCharacterEncoding("UTF-8"); diff --git a/main/src/com/google/refine/commands/cell/JoinMultiValueCellsCommand.java b/main/src/com/google/refine/commands/cell/JoinMultiValueCellsCommand.java index 29d043d47..f65c1c0ee 100644 --- a/main/src/com/google/refine/commands/cell/JoinMultiValueCellsCommand.java +++ b/main/src/com/google/refine/commands/cell/JoinMultiValueCellsCommand.java @@ -50,6 +50,10 @@ public class JoinMultiValueCellsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/cell/KeyValueColumnizeCommand.java b/main/src/com/google/refine/commands/cell/KeyValueColumnizeCommand.java index f941e5e8a..b926dfc4d 100644 --- a/main/src/com/google/refine/commands/cell/KeyValueColumnizeCommand.java +++ b/main/src/com/google/refine/commands/cell/KeyValueColumnizeCommand.java @@ -50,6 +50,10 @@ public class KeyValueColumnizeCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/cell/SplitMultiValueCellsCommand.java b/main/src/com/google/refine/commands/cell/SplitMultiValueCellsCommand.java index 071614259..0918b04ba 100644 --- a/main/src/com/google/refine/commands/cell/SplitMultiValueCellsCommand.java +++ b/main/src/com/google/refine/commands/cell/SplitMultiValueCellsCommand.java @@ -52,6 +52,10 @@ public class SplitMultiValueCellsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommand.java b/main/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommand.java index db26bd954..24c16b95f 100644 --- a/main/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommand.java +++ b/main/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommand.java @@ -50,6 +50,10 @@ public class TransposeColumnsIntoRowsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommand.java b/main/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommand.java index 7803d4860..8ace53158 100644 --- a/main/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommand.java +++ b/main/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommand.java @@ -50,6 +50,10 @@ public class TransposeRowsIntoColumnsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/column/MoveColumnCommand.java b/main/src/com/google/refine/commands/column/MoveColumnCommand.java index d88791763..75a5463ca 100644 --- a/main/src/com/google/refine/commands/column/MoveColumnCommand.java +++ b/main/src/com/google/refine/commands/column/MoveColumnCommand.java @@ -50,6 +50,10 @@ public class MoveColumnCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/column/RemoveColumnCommand.java b/main/src/com/google/refine/commands/column/RemoveColumnCommand.java index 2a8e0915e..362691748 100644 --- a/main/src/com/google/refine/commands/column/RemoveColumnCommand.java +++ b/main/src/com/google/refine/commands/column/RemoveColumnCommand.java @@ -50,6 +50,10 @@ public class RemoveColumnCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/column/RenameColumnCommand.java b/main/src/com/google/refine/commands/column/RenameColumnCommand.java index 5ff00f811..725887c8b 100644 --- a/main/src/com/google/refine/commands/column/RenameColumnCommand.java +++ b/main/src/com/google/refine/commands/column/RenameColumnCommand.java @@ -50,6 +50,10 @@ public class RenameColumnCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/expr/LogExpressionCommand.java b/main/src/com/google/refine/commands/expr/LogExpressionCommand.java index 818da044a..71df8ca24 100644 --- a/main/src/com/google/refine/commands/expr/LogExpressionCommand.java +++ b/main/src/com/google/refine/commands/expr/LogExpressionCommand.java @@ -48,7 +48,11 @@ public class LogExpressionCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + try { String expression = request.getParameter("expression"); diff --git a/main/src/com/google/refine/commands/expr/PreviewExpressionCommand.java b/main/src/com/google/refine/commands/expr/PreviewExpressionCommand.java index 1ba11ac7e..a31b8c94c 100644 --- a/main/src/com/google/refine/commands/expr/PreviewExpressionCommand.java +++ b/main/src/com/google/refine/commands/expr/PreviewExpressionCommand.java @@ -111,6 +111,10 @@ public class PreviewExpressionCommand extends Command { this.results = evaluated; } } + /** + * The command uses POST but does not actually modify any state so it does + * not require CSRF. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) diff --git a/main/src/com/google/refine/commands/expr/ToggleStarredExpressionCommand.java b/main/src/com/google/refine/commands/expr/ToggleStarredExpressionCommand.java index 7a06011ed..fce0c7418 100644 --- a/main/src/com/google/refine/commands/expr/ToggleStarredExpressionCommand.java +++ b/main/src/com/google/refine/commands/expr/ToggleStarredExpressionCommand.java @@ -40,6 +40,11 @@ public class ToggleStarredExpressionCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + String expression = request.getParameter("expression"); TopList starredExpressions = ((TopList) ProjectManager.singleton.getPreferenceStore().get( diff --git a/main/src/com/google/refine/commands/history/ApplyOperationsCommand.java b/main/src/com/google/refine/commands/history/ApplyOperationsCommand.java index df93d964f..c57ac44c3 100644 --- a/main/src/com/google/refine/commands/history/ApplyOperationsCommand.java +++ b/main/src/com/google/refine/commands/history/ApplyOperationsCommand.java @@ -54,6 +54,10 @@ public class ApplyOperationsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } Project project = getProject(request); String jsonString = request.getParameter("operations"); diff --git a/main/src/com/google/refine/commands/history/CancelProcessesCommand.java b/main/src/com/google/refine/commands/history/CancelProcessesCommand.java index 8f1192bf6..d416efbde 100644 --- a/main/src/com/google/refine/commands/history/CancelProcessesCommand.java +++ b/main/src/com/google/refine/commands/history/CancelProcessesCommand.java @@ -53,6 +53,10 @@ public class CancelProcessesCommand extends Command { if( response == null ) { throw new IllegalArgumentException("parameter 'request' should not be null"); } + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/history/UndoRedoCommand.java b/main/src/com/google/refine/commands/history/UndoRedoCommand.java index aa7d4aa62..0bf9de91e 100644 --- a/main/src/com/google/refine/commands/history/UndoRedoCommand.java +++ b/main/src/com/google/refine/commands/history/UndoRedoCommand.java @@ -48,6 +48,10 @@ public class UndoRedoCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/importing/CancelImportingJobCommand.java b/main/src/com/google/refine/commands/importing/CancelImportingJobCommand.java index c501ddcd3..7ec5cdd7e 100644 --- a/main/src/com/google/refine/commands/importing/CancelImportingJobCommand.java +++ b/main/src/com/google/refine/commands/importing/CancelImportingJobCommand.java @@ -48,6 +48,10 @@ public class CancelImportingJobCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } long jobID = Long.parseLong(request.getParameter("jobID")); ImportingJob job = ImportingManager.getJob(jobID); diff --git a/main/src/com/google/refine/commands/importing/CreateImportingJobCommand.java b/main/src/com/google/refine/commands/importing/CreateImportingJobCommand.java index 27ed84979..3f8cea114 100644 --- a/main/src/com/google/refine/commands/importing/CreateImportingJobCommand.java +++ b/main/src/com/google/refine/commands/importing/CreateImportingJobCommand.java @@ -52,6 +52,10 @@ public class CreateImportingJobCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } long id = ImportingManager.createJob().id; diff --git a/main/src/com/google/refine/commands/importing/GetImportingConfigurationCommand.java b/main/src/com/google/refine/commands/importing/GetImportingConfigurationCommand.java index 2899114d6..b31204a39 100644 --- a/main/src/com/google/refine/commands/importing/GetImportingConfigurationCommand.java +++ b/main/src/com/google/refine/commands/importing/GetImportingConfigurationCommand.java @@ -49,6 +49,10 @@ public class GetImportingConfigurationCommand extends Command { @JsonProperty("config") ImportingConfiguration config = new ImportingConfiguration(); } + /** + * This command uses POST but does not actually modify any state so + * it is not CSRF-protected. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java b/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java index 930b22032..cc6ec9afe 100644 --- a/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java +++ b/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java @@ -34,7 +34,6 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package com.google.refine.commands.importing; import java.io.IOException; -import java.io.Writer; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -46,7 +45,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.refine.commands.Command; import com.google.refine.importing.ImportingJob; import com.google.refine.importing.ImportingManager; -import com.google.refine.util.ParsingUtilities; public class GetImportingJobStatusCommand extends Command { protected static class JobStatusResponse { @@ -66,6 +64,10 @@ public class GetImportingJobStatusCommand extends Command { } } + /** + * This command uses POST but does not actually modify any state so + * it is not CSRF-protected. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -73,11 +75,10 @@ public class GetImportingJobStatusCommand extends Command { long jobID = Long.parseLong(request.getParameter("jobID")); ImportingJob job = ImportingManager.getJob(jobID); - Writer w = response.getWriter(); if (job == null) { - ParsingUtilities.defaultWriter.writeValue(w, new JobStatusResponse("error", "No such import job", null)); + respondJSON(response, new JobStatusResponse("error", "No such import job", null)); } else { - ParsingUtilities.defaultWriter.writeValue(w, new JobStatusResponse("ok", null, job)); + respondJSON(response, new JobStatusResponse("ok", null, job)); } } } diff --git a/main/src/com/google/refine/commands/importing/ImportingControllerCommand.java b/main/src/com/google/refine/commands/importing/ImportingControllerCommand.java index 02a279db9..1acb732b0 100644 --- a/main/src/com/google/refine/commands/importing/ImportingControllerCommand.java +++ b/main/src/com/google/refine/commands/importing/ImportingControllerCommand.java @@ -56,9 +56,15 @@ public class ImportingControllerCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFTokenAsGET(request)) { + respondCSRFError(response); + return; + } ImportingController controller = getController(request); if (controller != null) { + response.setCharacterEncoding("UTF-8"); + response.setHeader("Content-Type", "application/json"); controller.doPost(request, response); } else { HttpUtilities.respond(response, "error", "No such import controller"); @@ -71,6 +77,8 @@ public class ImportingControllerCommand extends Command { ImportingController controller = getController(request); if (controller != null) { + response.setCharacterEncoding("UTF-8"); + response.setHeader("Content-Type", "application/json"); controller.doPost(request, response); } else { HttpUtilities.respond(response, "error", "No such import controller"); diff --git a/main/src/com/google/refine/commands/lang/GetLanguagesCommand.java b/main/src/com/google/refine/commands/lang/GetLanguagesCommand.java index 7af237b3d..a07bf1e98 100644 --- a/main/src/com/google/refine/commands/lang/GetLanguagesCommand.java +++ b/main/src/com/google/refine/commands/lang/GetLanguagesCommand.java @@ -93,12 +93,6 @@ public class GetLanguagesCommand extends Command { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - doPost(request, response); - } - - @Override - public void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { String modname = request.getParameter("module"); if (modname == null) { diff --git a/main/src/com/google/refine/commands/lang/LoadLanguageCommand.java b/main/src/com/google/refine/commands/lang/LoadLanguageCommand.java index 15336ec1d..892dab86d 100644 --- a/main/src/com/google/refine/commands/lang/LoadLanguageCommand.java +++ b/main/src/com/google/refine/commands/lang/LoadLanguageCommand.java @@ -62,11 +62,16 @@ public class LoadLanguageCommand extends Command { throws ServletException, IOException { doPost(request, response); } + + /** + * POST is supported but does not actually change any state so we do + * not add CSRF protection to it. This ensures existing extensions will not + * have to be updated to add a CSRF token to their requests (2019-11-10) + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - String modname = request.getParameter("module"); if (modname == null) { modname = "core"; diff --git a/main/src/com/google/refine/commands/project/CreateProjectCommand.java b/main/src/com/google/refine/commands/project/CreateProjectCommand.java index f5f99dc24..8a0f133d5 100644 --- a/main/src/com/google/refine/commands/project/CreateProjectCommand.java +++ b/main/src/com/google/refine/commands/project/CreateProjectCommand.java @@ -64,6 +64,10 @@ public class CreateProjectCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFTokenAsGET(request)) { + respondCSRFError(response); + return; + } ProjectManager.singleton.setBusy(true); try { diff --git a/main/src/com/google/refine/commands/project/DeleteProjectCommand.java b/main/src/com/google/refine/commands/project/DeleteProjectCommand.java index 9d146a01e..b04037198 100644 --- a/main/src/com/google/refine/commands/project/DeleteProjectCommand.java +++ b/main/src/com/google/refine/commands/project/DeleteProjectCommand.java @@ -49,6 +49,11 @@ public class DeleteProjectCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + response.setHeader("Content-Type", "application/json"); try { long projectID = Long.parseLong(request.getParameter("project")); diff --git a/main/src/com/google/refine/commands/project/ExportProjectCommand.java b/main/src/com/google/refine/commands/project/ExportProjectCommand.java index 3252f6a2a..ee129f7c9 100644 --- a/main/src/com/google/refine/commands/project/ExportProjectCommand.java +++ b/main/src/com/google/refine/commands/project/ExportProjectCommand.java @@ -46,6 +46,10 @@ import com.google.refine.io.FileProjectManager; import com.google.refine.model.Project; public class ExportProjectCommand extends Command { + + /** + * This command uses POST but is left CSRF-unprotected as it does not incur a state change. + */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) diff --git a/main/src/com/google/refine/commands/project/ExportRowsCommand.java b/main/src/com/google/refine/commands/project/ExportRowsCommand.java index 44c031da4..dd6444a1f 100644 --- a/main/src/com/google/refine/commands/project/ExportRowsCommand.java +++ b/main/src/com/google/refine/commands/project/ExportRowsCommand.java @@ -61,6 +61,10 @@ import com.google.refine.model.Project; public class ExportRowsCommand extends Command { private static final Logger logger = LoggerFactory.getLogger("ExportRowsCommand"); + + /** + * This command uses POST but is left CSRF-unprotected as it does not incur a state change. + */ @SuppressWarnings("unchecked") static public Properties getRequestParameters(HttpServletRequest request) { diff --git a/main/src/com/google/refine/commands/project/GetModelsCommand.java b/main/src/com/google/refine/commands/project/GetModelsCommand.java index ebf707d7b..b980154dc 100644 --- a/main/src/com/google/refine/commands/project/GetModelsCommand.java +++ b/main/src/com/google/refine/commands/project/GetModelsCommand.java @@ -55,6 +55,11 @@ import com.google.refine.model.Project; import com.google.refine.model.RecordModel; public class GetModelsCommand extends Command { + + /** + * This command uses POST but is left CSRF-unprotected as it does not incur a state change. + */ + @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/commands/project/ImportProjectCommand.java b/main/src/com/google/refine/commands/project/ImportProjectCommand.java index 740b72c9b..aac3ba489 100644 --- a/main/src/com/google/refine/commands/project/ImportProjectCommand.java +++ b/main/src/com/google/refine/commands/project/ImportProjectCommand.java @@ -63,6 +63,10 @@ public class ImportProjectCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFTokenAsGET(request)) { + respondCSRFError(response); + return; + } ProjectManager.singleton.setBusy(true); try { diff --git a/main/src/com/google/refine/commands/project/RenameProjectCommand.java b/main/src/com/google/refine/commands/project/RenameProjectCommand.java index b8023df4a..b3365edf3 100644 --- a/main/src/com/google/refine/commands/project/RenameProjectCommand.java +++ b/main/src/com/google/refine/commands/project/RenameProjectCommand.java @@ -46,7 +46,11 @@ public class RenameProjectCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + try { String name = request.getParameter("name"); ProjectMetadata pm = getProjectMetadata(request); diff --git a/main/src/com/google/refine/commands/project/SetProjectMetadataCommand.java b/main/src/com/google/refine/commands/project/SetProjectMetadataCommand.java index c121c6b10..55297c3f3 100644 --- a/main/src/com/google/refine/commands/project/SetProjectMetadataCommand.java +++ b/main/src/com/google/refine/commands/project/SetProjectMetadataCommand.java @@ -41,6 +41,10 @@ public class SetProjectMetadataCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } Project project = request.getParameter("project") != null ? getProject(request) : null; String metaName = request.getParameter("name"); diff --git a/main/src/com/google/refine/commands/project/SetProjectTagsCommand.java b/main/src/com/google/refine/commands/project/SetProjectTagsCommand.java index 71fbba7f1..b6a928dba 100644 --- a/main/src/com/google/refine/commands/project/SetProjectTagsCommand.java +++ b/main/src/com/google/refine/commands/project/SetProjectTagsCommand.java @@ -43,6 +43,11 @@ public class SetProjectTagsCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } + response.setHeader("Content-Type", "application/json"); Project project; diff --git a/main/src/com/google/refine/commands/recon/GuessTypesOfColumnCommand.java b/main/src/com/google/refine/commands/recon/GuessTypesOfColumnCommand.java index 7e747706f..0d5f38951 100644 --- a/main/src/com/google/refine/commands/recon/GuessTypesOfColumnCommand.java +++ b/main/src/com/google/refine/commands/recon/GuessTypesOfColumnCommand.java @@ -93,6 +93,10 @@ public class GuessTypesOfColumnCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/recon/PreviewExtendDataCommand.java b/main/src/com/google/refine/commands/recon/PreviewExtendDataCommand.java index 57bdffab8..6775f1c28 100644 --- a/main/src/com/google/refine/commands/recon/PreviewExtendDataCommand.java +++ b/main/src/com/google/refine/commands/recon/PreviewExtendDataCommand.java @@ -79,6 +79,10 @@ public class PreviewExtendDataCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java b/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java index 6234f872e..30dc67ec6 100644 --- a/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java +++ b/main/src/com/google/refine/commands/recon/ReconClearOneCellCommand.java @@ -75,6 +75,10 @@ public class ReconClearOneCellCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java b/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java index 778ec2cd4..f37dc1838 100644 --- a/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java +++ b/main/src/com/google/refine/commands/recon/ReconJudgeOneCellCommand.java @@ -59,6 +59,10 @@ public class ReconJudgeOneCellCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { request.setCharacterEncoding("UTF-8"); diff --git a/main/src/com/google/refine/commands/row/AnnotateOneRowCommand.java b/main/src/com/google/refine/commands/row/AnnotateOneRowCommand.java index 6555f8b47..1d27cb819 100644 --- a/main/src/com/google/refine/commands/row/AnnotateOneRowCommand.java +++ b/main/src/com/google/refine/commands/row/AnnotateOneRowCommand.java @@ -50,6 +50,10 @@ public class AnnotateOneRowCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } response.setCharacterEncoding("UTF-8"); response.setHeader("Content-Type", "application/json"); diff --git a/main/src/com/google/refine/commands/row/DenormalizeCommand.java b/main/src/com/google/refine/commands/row/DenormalizeCommand.java index 6ba38d6a1..0a13177d1 100644 --- a/main/src/com/google/refine/commands/row/DenormalizeCommand.java +++ b/main/src/com/google/refine/commands/row/DenormalizeCommand.java @@ -50,6 +50,10 @@ public class DenormalizeCommand extends Command { @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + if(!hasValidCSRFToken(request)) { + respondCSRFError(response); + return; + } try { Project project = getProject(request); diff --git a/main/src/com/google/refine/commands/row/GetRowsCommand.java b/main/src/com/google/refine/commands/row/GetRowsCommand.java index cfeb21cdc..5467b4246 100644 --- a/main/src/com/google/refine/commands/row/GetRowsCommand.java +++ b/main/src/com/google/refine/commands/row/GetRowsCommand.java @@ -111,6 +111,10 @@ public class GetRowsCommand extends Command { } } + /** + * This command accepts both POST and GET. It is not CSRF-protected as it does not incur any state change. + */ + @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { diff --git a/main/src/com/google/refine/importing/DefaultImportingController.java b/main/src/com/google/refine/importing/DefaultImportingController.java index ed8dbba75..a11812169 100644 --- a/main/src/com/google/refine/importing/DefaultImportingController.java +++ b/main/src/com/google/refine/importing/DefaultImportingController.java @@ -50,6 +50,7 @@ import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.refine.RefineServlet; +import com.google.refine.commands.Command; import com.google.refine.commands.HttpUtilities; import com.google.refine.importing.ImportingManager.Format; import com.google.refine.util.JSONUtilities; @@ -218,7 +219,7 @@ public class DefaultImportingController implements ImportingController { JSONUtilities.safePut(result, "status", "ok"); JSONUtilities.safePut(result, "options", options); - HttpUtilities.respond(response, result.toString()); + Command.respondJSON(response, result); } else { HttpUtilities.respond(response, "error", "Unrecognized format or format has no parser"); } diff --git a/main/tests/server/src/com/google/refine/commands/CSRFTokenFactoryTests.java b/main/tests/server/src/com/google/refine/commands/CSRFTokenFactoryTests.java new file mode 100644 index 000000000..f966e971b --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/CSRFTokenFactoryTests.java @@ -0,0 +1,49 @@ +package com.google.refine.commands; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +import java.time.Instant; + +import org.testng.annotations.Test; + +public class CSRFTokenFactoryTests { + + static class CSRFTokenFactoryStub extends CSRFTokenFactory{ + public CSRFTokenFactoryStub(long timeToLive, int tokenLength) { + super(timeToLive, tokenLength); + } + public void tamperWithToken(String token, Instant newGenerationTime) { + tokenCache.asMap().put(token, newGenerationTime); + } + } + + @Test + public void testGenerateValidToken() { + CSRFTokenFactory factory = new CSRFTokenFactory(10, 25); + // Generate a fresh token + String token = factory.getFreshToken(); + // Immediately after, the token is still valid + assertTrue(factory.validToken(token)); + // The token has the right length + assertEquals(25, token.length()); + } + + @Test + public void testInvalidToken() { + CSRFTokenFactory factory = new CSRFTokenFactory(10, 25); + assertFalse(factory.validToken("bogusToken")); + } + + @Test + public void testOldToken() { + CSRFTokenFactoryStub stub = new CSRFTokenFactoryStub(10, 25); + // Generate a fresh token + String token = stub.getFreshToken(); + // Manually change the generation time + stub.tamperWithToken(token, Instant.now().minusSeconds(100)); + // The token should now be invalid + assertFalse(stub.validToken(token)); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/CommandTestBase.java b/main/tests/server/src/com/google/refine/commands/CommandTestBase.java new file mode 100644 index 000000000..7e7c84915 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/CommandTestBase.java @@ -0,0 +1,41 @@ +package com.google.refine.commands; + +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.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.testng.annotations.BeforeMethod; + +import com.google.refine.util.TestUtils; + +public class CommandTestBase { + 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(); + try { + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Convenience method to check that CSRF protection was triggered + */ + protected void assertCSRFCheckFailed() { + TestUtils.assertEqualAsJson("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", writer.toString()); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/EngineDependentCommandTests.java b/main/tests/server/src/com/google/refine/commands/EngineDependentCommandTests.java new file mode 100644 index 000000000..923613aa3 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/EngineDependentCommandTests.java @@ -0,0 +1,38 @@ +package com.google.refine.commands; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.browsing.EngineConfig; +import com.google.refine.model.AbstractOperation; +import com.google.refine.model.Project; + +public class EngineDependentCommandTests extends CommandTestBase { + + private static class EngineDependentCommandStub extends EngineDependentCommand { + + @Override + protected AbstractOperation createOperation(Project project, HttpServletRequest request, + EngineConfig engineConfig) throws Exception { + return null; + } + + } + + @BeforeMethod + public void setUpCommand() { + command = new EngineDependentCommandStub(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/GetCSRFTokenCommandTest.java b/main/tests/server/src/com/google/refine/commands/GetCSRFTokenCommandTest.java new file mode 100644 index 000000000..6133a2e91 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/GetCSRFTokenCommandTest.java @@ -0,0 +1,49 @@ +package com.google.refine.commands; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertTrue; + +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.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.refine.util.ParsingUtilities; + +public class GetCSRFTokenCommandTest { + protected HttpServletRequest request = null; + protected HttpServletResponse response = null; + protected StringWriter writer = null; + protected Command command = null; + + @BeforeMethod + public void setUp() { + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + command = new GetCSRFTokenCommand(); + writer = new StringWriter(); + try { + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testGetToken() throws JsonParseException, JsonMappingException, IOException, ServletException { + command.doGet(request, response); + ObjectNode result = ParsingUtilities.mapper.readValue(writer.toString(), ObjectNode.class); + String token = result.get("token").asText(); + assertTrue(Command.csrfFactory.validToken(token)); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/OpenWorkspaceDirCommandTests.java b/main/tests/server/src/com/google/refine/commands/OpenWorkspaceDirCommandTests.java new file mode 100644 index 000000000..770b86a66 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/OpenWorkspaceDirCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands; +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 OpenWorkspaceDirCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new OpenWorkspaceDirCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/SetPreferenceCommandTests.java b/main/tests/server/src/com/google/refine/commands/SetPreferenceCommandTests.java new file mode 100644 index 000000000..9ad95db7c --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/SetPreferenceCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands; + +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 SetPreferenceCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new SetPreferenceCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/cell/EditOneCellCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/EditOneCellCommandTests.java new file mode 100644 index 000000000..d9fcc4c07 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/EditOneCellCommandTests.java @@ -0,0 +1,78 @@ +package com.google.refine.commands.cell; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +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.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.RefineTest; +import com.google.refine.commands.Command; +import com.google.refine.model.Project; +import com.google.refine.util.TestUtils; + +public class EditOneCellCommandTests extends RefineTest { + + protected Project project = null; + protected HttpServletRequest request = null; + protected HttpServletResponse response = null; + protected Command command = null; + protected StringWriter writer = null; + + @BeforeMethod + public void setUpProject() { + project = createCSVProject( + "first_column,second_column\n" + + "a,b\n" + + "c,d\n"); + command = new EditOneCellCommand(); + request = mock(HttpServletRequest.class); + response = mock(HttpServletResponse.class); + writer = new StringWriter(); + try { + when(response.getWriter()).thenReturn(new PrintWriter(writer)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void testEditOneCell() throws ServletException, IOException { + when(request.getParameter("project")).thenReturn(Long.toString(project.id)); + when(request.getParameter("row")).thenReturn("1"); + when(request.getParameter("cell")).thenReturn("0"); + when(request.getParameter("type")).thenReturn("string"); + when(request.getParameter("value")).thenReturn("e"); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); + + command.doPost(request, response); + + assertEquals("a", project.rows.get(0).cells.get(0).value); + assertEquals("b", project.rows.get(0).cells.get(1).value); + assertEquals("e", project.rows.get(1).cells.get(0).value); + assertEquals("d", project.rows.get(1).cells.get(1).value); + } + + @Test + public void testMissingCSRFToken() throws ServletException, IOException { + when(request.getParameter("project")).thenReturn(Long.toString(project.id)); + when(request.getParameter("row")).thenReturn("1"); + when(request.getParameter("cell")).thenReturn("0"); + when(request.getParameter("type")).thenReturn("string"); + when(request.getParameter("value")).thenReturn("e"); + + command.doPost(request, response); + + assertEquals("c", project.rows.get(1).cells.get(0).value); + TestUtils.assertEqualAsJson("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", writer.toString()); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/cell/JoinMultiValueCellsCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/JoinMultiValueCellsCommandTests.java new file mode 100644 index 000000000..e9678d367 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/JoinMultiValueCellsCommandTests.java @@ -0,0 +1,25 @@ +package com.google.refine.commands.cell; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.commands.CommandTestBase; +import com.google.refine.commands.cell.JoinMultiValueCellsCommand; + +public class JoinMultiValueCellsCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new JoinMultiValueCellsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/cell/KeyValueColumnizeCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/KeyValueColumnizeCommandTests.java new file mode 100644 index 000000000..efb533700 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/KeyValueColumnizeCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands.cell; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.commands.CommandTestBase; + +public class KeyValueColumnizeCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new KeyValueColumnizeCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/cell/SplitMultiValueCellsCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/SplitMultiValueCellsCommandTests.java new file mode 100644 index 000000000..ed3adbc54 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/SplitMultiValueCellsCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.cell; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.commands.CommandTestBase; + +public class SplitMultiValueCellsCommandTests extends CommandTestBase { + @BeforeMethod + public void setUpCommand() { + command = new SplitMultiValueCellsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommandTests.java new file mode 100644 index 000000000..c6562c624 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.cell; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.commands.CommandTestBase; + +public class TransposeColumnsIntoRowsCommandTests extends CommandTestBase { + @BeforeMethod + public void setUpCommand() { + command = new TransposeColumnsIntoRowsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommandTests.java b/main/tests/server/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommandTests.java new file mode 100644 index 000000000..105b1be31 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommandTests.java @@ -0,0 +1,22 @@ +package com.google.refine.commands.cell; +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import com.google.refine.commands.CommandTestBase; + +public class TransposeRowsIntoColumnsCommandTests extends CommandTestBase { + @BeforeMethod + public void setUpCommand() { + command = new TransposeRowsIntoColumnsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/column/MoveColumnCommandTests.java b/main/tests/server/src/com/google/refine/commands/column/MoveColumnCommandTests.java new file mode 100644 index 000000000..da7f25b3b --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/column/MoveColumnCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands.column; + +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 MoveColumnCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new MoveColumnCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/column/RemoveColumnCommandTests.java b/main/tests/server/src/com/google/refine/commands/column/RemoveColumnCommandTests.java new file mode 100644 index 000000000..d666f0c3c --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/column/RemoveColumnCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.column; + +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 RemoveColumnCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new RemoveColumnCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/column/RenameColumnCommandTests.java b/main/tests/server/src/com/google/refine/commands/column/RenameColumnCommandTests.java new file mode 100644 index 000000000..a0d8d94bc --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/column/RenameColumnCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.column; + +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 RenameColumnCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new RenameColumnCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/expr/LogExpressionCommandTests.java b/main/tests/server/src/com/google/refine/commands/expr/LogExpressionCommandTests.java new file mode 100644 index 000000000..1283820ef --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/expr/LogExpressionCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.expr; + +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 LogExpressionCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new LogExpressionCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/expr/ToggleStarredExpressionCommandTests.java b/main/tests/server/src/com/google/refine/commands/expr/ToggleStarredExpressionCommandTests.java index 2b8376db4..d4166cd62 100644 --- a/main/tests/server/src/com/google/refine/commands/expr/ToggleStarredExpressionCommandTests.java +++ b/main/tests/server/src/com/google/refine/commands/expr/ToggleStarredExpressionCommandTests.java @@ -35,7 +35,9 @@ import javax.servlet.ServletException; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import com.google.refine.commands.Command; import com.google.refine.commands.expr.ToggleStarredExpressionCommand; +import com.google.refine.util.TestUtils; public class ToggleStarredExpressionCommandTests extends ExpressionCommandTestBase { @@ -70,7 +72,14 @@ public class ToggleStarredExpressionCommandTests extends ExpressionCommandTestBa " }"; when(request.getParameter("expression")).thenReturn("grel:facetCount(value, 'value', 'Column 1')"); when(request.getParameter("returnList")).thenReturn("yes"); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); command.doPost(request, response); assertResponseJsonIs(json); } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + TestUtils.assertEqualAsJson("{\"code\":\"error\",\"message\":\"Missing or invalid csrf_token parameter\"}", writer.toString()); + } } diff --git a/main/tests/server/src/com/google/refine/commands/history/ApplyOperationsCommandTests.java b/main/tests/server/src/com/google/refine/commands/history/ApplyOperationsCommandTests.java new file mode 100644 index 000000000..d7422eb18 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/history/ApplyOperationsCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands.history; + +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 ApplyOperationsCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new ApplyOperationsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/history/CancelProcessesCommandTests.java b/main/tests/server/src/com/google/refine/commands/history/CancelProcessesCommandTests.java new file mode 100644 index 000000000..dcf3ec07e --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/history/CancelProcessesCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.history; + +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 CancelProcessesCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new CancelProcessesCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/history/UndoRedoCommandTests.java b/main/tests/server/src/com/google/refine/commands/history/UndoRedoCommandTests.java new file mode 100644 index 000000000..dc4ed459e --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/history/UndoRedoCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.history; + +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 UndoRedoCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new UndoRedoCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/importing/CancelImportingJobCommandTests.java b/main/tests/server/src/com/google/refine/commands/importing/CancelImportingJobCommandTests.java new file mode 100644 index 000000000..b7c69e977 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/importing/CancelImportingJobCommandTests.java @@ -0,0 +1,22 @@ +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 CancelImportingJobCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new CancelImportingJobCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/importing/CreateImportingJobCommandTests.java b/main/tests/server/src/com/google/refine/commands/importing/CreateImportingJobCommandTests.java new file mode 100644 index 000000000..70f44ff52 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/importing/CreateImportingJobCommandTests.java @@ -0,0 +1,23 @@ +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 CreateImportingJobCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new CreateImportingJobCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/importing/ImportingControllerCommandTests.java b/main/tests/server/src/com/google/refine/commands/importing/ImportingControllerCommandTests.java new file mode 100644 index 000000000..7f3a29da6 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/importing/ImportingControllerCommandTests.java @@ -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(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/lang/LoadLanguageCommandTests.java b/main/tests/server/src/com/google/refine/commands/lang/LoadLanguageCommandTests.java new file mode 100644 index 000000000..f239673df --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/lang/LoadLanguageCommandTests.java @@ -0,0 +1,48 @@ +package com.google.refine.commands.lang; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertTrue; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.refine.RefineServlet; +import com.google.refine.commands.CommandTestBase; +import com.google.refine.util.ParsingUtilities; + +import edu.mit.simile.butterfly.ButterflyModule; + +import java.io.File; +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +public class LoadLanguageCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new LoadLanguageCommand(); + ButterflyModule coreModule = mock(ButterflyModule.class); + + when(coreModule.getName()).thenReturn("core"); + when(coreModule.getPath()).thenReturn(new File("webapp/modules/core")); + RefineServlet servlet = mock(RefineServlet.class); + when(servlet.getModule("core")).thenReturn(coreModule); + command.init(servlet); + } + + @Test + public void testLoadLanguages() throws ServletException, IOException { + when(request.getParameter("module")).thenReturn("core"); + when(request.getParameterValues("lang")).thenReturn(new String[] {"en"}); + + command.doPost(request, response); + + JsonNode response = ParsingUtilities.mapper.readValue(writer.toString(), JsonNode.class); + assertTrue(response.has("dictionary")); + assertTrue(response.has("lang")); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/project/ImportProjectCommandTests.java b/main/tests/server/src/com/google/refine/commands/project/ImportProjectCommandTests.java new file mode 100644 index 000000000..063f526e6 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/project/ImportProjectCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands.project; + +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 ImportProjectCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new ImportProjectCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/project/RenameProjectCommandTests.java b/main/tests/server/src/com/google/refine/commands/project/RenameProjectCommandTests.java new file mode 100644 index 000000000..4b60756e0 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/project/RenameProjectCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.project; + +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 RenameProjectCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new RenameProjectCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/project/SetProjectMetadataCommandTests.java b/main/tests/server/src/com/google/refine/commands/project/SetProjectMetadataCommandTests.java index 3771bd05a..008dc08fb 100644 --- a/main/tests/server/src/com/google/refine/commands/project/SetProjectMetadataCommandTests.java +++ b/main/tests/server/src/com/google/refine/commands/project/SetProjectMetadataCommandTests.java @@ -58,6 +58,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.refine.ProjectManager; import com.google.refine.ProjectMetadata; import com.google.refine.RefineTest; +import com.google.refine.commands.Command; import com.google.refine.commands.project.SetProjectMetadataCommand; import com.google.refine.model.Project; import com.google.refine.util.ParsingUtilities; @@ -101,6 +102,7 @@ public class SetProjectMetadataCommandTests extends RefineTest { // mock dependencies when(request.getParameter("project")).thenReturn(PROJECT_ID); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); when(projMan.getProject(anyLong())).thenReturn(proj); when(proj.getMetadata()).thenReturn(metadata); diff --git a/main/tests/server/src/com/google/refine/commands/project/SetProjectTagsCommandTests.java b/main/tests/server/src/com/google/refine/commands/project/SetProjectTagsCommandTests.java new file mode 100644 index 000000000..3b073fac4 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/project/SetProjectTagsCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.project; + +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 SetProjectTagsCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new SetProjectTagsCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/recon/GuessTypesOfColumnCommandTests.java b/main/tests/server/src/com/google/refine/commands/recon/GuessTypesOfColumnCommandTests.java new file mode 100644 index 000000000..03010589b --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/recon/GuessTypesOfColumnCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.recon; + +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 GuessTypesOfColumnCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new GuessTypesOfColumnCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/recon/PreviewExtendDataCommandTests.java b/main/tests/server/src/com/google/refine/commands/recon/PreviewExtendDataCommandTests.java new file mode 100644 index 000000000..b4c04eddd --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/recon/PreviewExtendDataCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.recon; + +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 PreviewExtendDataCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new PreviewExtendDataCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/recon/ReconClearOneCellCommandTests.java b/main/tests/server/src/com/google/refine/commands/recon/ReconClearOneCellCommandTests.java new file mode 100644 index 000000000..72ca77b73 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/recon/ReconClearOneCellCommandTests.java @@ -0,0 +1,24 @@ +package com.google.refine.commands.recon; + +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 ReconClearOneCellCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new ReconClearOneCellCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java b/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java index 3dc741b6b..b53b11249 100644 --- a/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java +++ b/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java @@ -82,6 +82,7 @@ public class ReconJudgeOneCellCommandTest extends RefineTest { response = mock(HttpServletResponse.class); when(request.getParameter("project")).thenReturn(String.valueOf(project.id)); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); writer = mock(PrintWriter.class); try { diff --git a/main/tests/server/src/com/google/refine/commands/row/AnnotateOneRowCommandTests.java b/main/tests/server/src/com/google/refine/commands/row/AnnotateOneRowCommandTests.java new file mode 100644 index 000000000..1b1e19e91 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/row/AnnotateOneRowCommandTests.java @@ -0,0 +1,22 @@ +package com.google.refine.commands.row; +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 AnnotateOneRowCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new AnnotateOneRowCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} diff --git a/main/tests/server/src/com/google/refine/commands/row/DenormalizeCommandTests.java b/main/tests/server/src/com/google/refine/commands/row/DenormalizeCommandTests.java new file mode 100644 index 000000000..885806bc5 --- /dev/null +++ b/main/tests/server/src/com/google/refine/commands/row/DenormalizeCommandTests.java @@ -0,0 +1,23 @@ +package com.google.refine.commands.row; +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 DenormalizeCommandTests extends CommandTestBase { + + @BeforeMethod + public void setUpCommand() { + command = new DenormalizeCommand(); + } + + @Test + public void testCSRFProtection() throws ServletException, IOException { + command.doPost(request, response); + assertCSRFCheckFailed(); + } +} + diff --git a/main/tests/server/src/com/google/refine/commands/util/CancelProcessesCommandTests.java b/main/tests/server/src/com/google/refine/commands/util/CancelProcessesCommandTests.java index 03cd8c70c..3be08e976 100644 --- a/main/tests/server/src/com/google/refine/commands/util/CancelProcessesCommandTests.java +++ b/main/tests/server/src/com/google/refine/commands/util/CancelProcessesCommandTests.java @@ -56,6 +56,7 @@ import org.testng.annotations.Test; import com.google.refine.ProjectManager; import com.google.refine.RefineTest; +import com.google.refine.commands.Command; import com.google.refine.commands.history.CancelProcessesCommand; import com.google.refine.model.Project; import com.google.refine.process.ProcessManager; @@ -159,6 +160,7 @@ public class CancelProcessesCommandTests extends RefineTest { // mock dependencies when(request.getParameter("project")).thenReturn(PROJECT_ID); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); when(projMan.getProject(anyLong())).thenReturn(proj); when(proj.getProcessManager()).thenReturn(processMan); try { @@ -197,6 +199,7 @@ public class CancelProcessesCommandTests extends RefineTest { public void doPostThrowsIfCommand_getProjectReturnsNull(){ // mock dependencies when(request.getParameter("project")).thenReturn(PROJECT_ID); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); when(projMan.getProject(anyLong())) .thenReturn(null); try { @@ -225,6 +228,7 @@ public class CancelProcessesCommandTests extends RefineTest { // mock dependencies when(request.getParameter("project")).thenReturn(PROJECT_ID); + when(request.getParameter("csrf_token")).thenReturn(Command.csrfFactory.getFreshToken()); when(projMan.getProject(anyLong())).thenReturn(proj); when(proj.getProcessManager()).thenReturn(processMan); try { diff --git a/main/webapp/modules/core/MOD-INF/controller.js b/main/webapp/modules/core/MOD-INF/controller.js index cae639734..237c1ac3b 100644 --- a/main/webapp/modules/core/MOD-INF/controller.js +++ b/main/webapp/modules/core/MOD-INF/controller.js @@ -54,6 +54,7 @@ function registerCommands() { var RS = Packages.com.google.refine.RefineServlet; RS.registerCommand(module, "get-version", new Packages.com.google.refine.commands.GetVersionCommand()); + RS.registerCommand(module, "get-csrf-token", new Packages.com.google.refine.commands.GetCSRFTokenCommand()); RS.registerCommand(module, "get-importing-configuration", new Packages.com.google.refine.commands.importing.GetImportingConfigurationCommand()); RS.registerCommand(module, "create-importing-job", new Packages.com.google.refine.commands.importing.CreateImportingJobCommand()); @@ -68,7 +69,7 @@ function registerCommands() { RS.registerCommand(module, "get-project-metadata", new Packages.com.google.refine.commands.project.GetProjectMetadataCommand()); RS.registerCommand(module, "get-all-project-metadata", new Packages.com.google.refine.commands.workspace.GetAllProjectMetadataCommand()); - RS.registerCommand(module, "set-metaData", new Packages.com.google.refine.commands.project.SetProjectMetadataCommand()); + RS.registerCommand(module, "set-project-metadata", new Packages.com.google.refine.commands.project.SetProjectMetadataCommand()); RS.registerCommand(module, "get-all-project-tags", new Packages.com.google.refine.commands.workspace.GetAllProjectTagsCommand()); RS.registerCommand(module, "set-project-tags", new Packages.com.google.refine.commands.project.SetProjectTagsCommand()); diff --git a/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js b/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js index ae6b337fd..78f77f284 100644 --- a/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js +++ b/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js @@ -157,7 +157,7 @@ ExpressionPreviewDialog.Widget.prototype.getExpression = function(commit) { s = this._getLanguage() + ":" + s; if (commit) { - $.post( + Refine.postCSRF( "command/core/log-expression?" + $.param({ project: theProject.id }), { expression: s }, function(data) { @@ -284,9 +284,11 @@ ExpressionPreviewDialog.Widget.prototype._renderExpressionHistory = function(dat .addClass(entry.starred ? "data-table-star-on" : "data-table-star-off") .appendTo(tr.insertCell(0)) .click(function() { - $.post( + Refine.postCSRF( "command/core/toggle-starred-expression", - { expression: entry.code }, + { + expression: entry.code + }, function(data) { entry.starred = !entry.starred; renderEntry(self,tr,entry); @@ -348,7 +350,7 @@ ExpressionPreviewDialog.Widget.prototype._renderStarredExpressions = function(da var o = Scripting.parse(entry.code); $(''+$.i18n('core-dialogs/remove')+'').appendTo(tr.insertCell(0)).click(function() { - $.post( + Refine.postCSRF( "command/core/toggle-starred-expression", { expression: entry.code, returnList: true }, function(data) { diff --git a/main/webapp/modules/core/scripts/dialogs/extend-data-preview-dialog.js b/main/webapp/modules/core/scripts/dialogs/extend-data-preview-dialog.js index d60d9440c..6acb515dc 100644 --- a/main/webapp/modules/core/scripts/dialogs/extend-data-preview-dialog.js +++ b/main/webapp/modules/core/scripts/dialogs/extend-data-preview-dialog.js @@ -185,7 +185,7 @@ ExtendReconciledDataPreviewDialog.prototype._update = function() { this._elmts.previewContainer.empty(); } else { // otherwise, refresh the preview - $.post( + Refine.postCSRF( "command/core/preview-extend-data?" + $.param(params), { rowIndices: JSON.stringify(this._rowIndices), @@ -194,10 +194,10 @@ ExtendReconciledDataPreviewDialog.prototype._update = function() { function(data) { self._renderPreview(data); }, - "json" - ).fail(function(data) { - alert($.i18n('core-views/internal-err')); - }); + "json", + function(data) { + alert($.i18n('core-views/internal-err')); + }); } }; diff --git a/main/webapp/modules/core/scripts/facets/list-facet.js b/main/webapp/modules/core/scripts/facets/list-facet.js index a02c57650..cdd7daccf 100644 --- a/main/webapp/modules/core/scripts/facets/list-facet.js +++ b/main/webapp/modules/core/scripts/facets/list-facet.js @@ -721,7 +721,7 @@ ListFacet.prototype._setChoiceCountLimit = function(choiceCount) { if (!isNaN(n)) { var self = this; - $.post( + Refine.postCSRF( "command/core/set-preference", { name : "ui.browsing.listFacet.limit", diff --git a/main/webapp/modules/core/scripts/index.js b/main/webapp/modules/core/scripts/index.js index 1a22bfba2..731b59857 100644 --- a/main/webapp/modules/core/scripts/index.js +++ b/main/webapp/modules/core/scripts/index.js @@ -37,6 +37,37 @@ var Refine = { actionAreas: [] }; +// Requests a CSRF token and calls the supplied callback +// with the token +Refine.wrapCSRF = function(onCSRF) { + $.get( + "command/core/get-csrf-token", + {}, + function(response) { + onCSRF(response['token']); + }, + "json" + ); +}; + +// 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, failCallback) { + return Refine.wrapCSRF(function(token) { + var fullData = data || {}; + if (typeof fullData == 'string') { + fullData = fullData + "&" + $.param({csrf_token: token}); + } else { + fullData['csrf_token'] = token; + } + var req = $.post(url, fullData, success, dataType); + if (failCallback !== undefined) { + req.fail(failCallback); + } + }); +}; + var lang = (navigator.language|| navigator.userLanguage).split("-")[0]; var dictionary = ""; $.ajax({ diff --git a/main/webapp/modules/core/scripts/index/create-project-ui.js b/main/webapp/modules/core/scripts/index/create-project-ui.js index 7e97009a4..4b176830f 100644 --- a/main/webapp/modules/core/scripts/index/create-project-ui.js +++ b/main/webapp/modules/core/scripts/index/create-project-ui.js @@ -271,5 +271,8 @@ Refine.CreateProjectUI.composeErrorMessage = function(job) { }; Refine.CreateProjectUI.cancelImportingJob = function(jobID) { - $.post("command/core/cancel-importing-job?" + $.param({ "jobID": jobID })); + Refine.wrapCSRF(function(token) { + $.post("command/core/cancel-importing-job?" + $.param({ "jobID": jobID }), + {csrf_token: token}); + }); }; diff --git a/main/webapp/modules/core/scripts/index/default-importing-controller/controller.js b/main/webapp/modules/core/scripts/index/default-importing-controller/controller.js index 83e85ef74..a1eb4492a 100644 --- a/main/webapp/modules/core/scripts/index/default-importing-controller/controller.js +++ b/main/webapp/modules/core/scripts/index/default-importing-controller/controller.js @@ -78,61 +78,64 @@ Refine.DefaultImportingController.prototype.startImportJob = function(form, prog return this.value === ""; }).attr("disabled", "disabled"); - $.post( - "command/core/create-importing-job", - null, - function(data) { - var jobID = self._jobID = data.jobID; + Refine.wrapCSRF(function(token) { + $.post( + "command/core/create-importing-job", + { csrf_token: token }, + function(data) { + var jobID = self._jobID = data.jobID; - form.attr("method", "post") - .attr("enctype", "multipart/form-data") - .attr("accept-charset", "UTF-8") - .attr("target", "create-project-iframe") - .attr("action", "command/core/importing-controller?" + $.param({ - "controller": "core/default-importing-controller", - "jobID": jobID, - "subCommand": "load-raw-data" - })); - form[0].submit(); + form.attr("method", "post") + .attr("enctype", "multipart/form-data") + .attr("accept-charset", "UTF-8") + .attr("target", "create-project-iframe") + .attr("action", "command/core/importing-controller?" + $.param({ + "controller": "core/default-importing-controller", + "jobID": jobID, + "subCommand": "load-raw-data", + "csrf_token": token + })); + form[0].submit(); - var start = new Date(); - var timerID = window.setInterval( - function() { - self._createProjectUI.pollImportJob( - start, jobID, timerID, - function(job) { - return job.config.hasData; - }, - function(jobID, job) { - self._job = job; - self._onImportJobReady(); - if (callback) { - callback(jobID, job); + var start = new Date(); + var timerID = window.setInterval( + function() { + self._createProjectUI.pollImportJob( + start, jobID, timerID, + function(job) { + return job.config.hasData; + }, + function(jobID, job) { + self._job = job; + self._onImportJobReady(); + if (callback) { + callback(jobID, job); + } + }, + function(job) { + alert(job.config.error + '\n' + job.config.errorDetails); + self._startOver(); } - }, - function(job) { - alert(job.config.error + '\n' + job.config.errorDetails); - self._startOver(); - } + ); + }, + 1000 ); - }, - 1000 - ); - self._createProjectUI.showImportProgressPanel(progressMessage, function() { - // stop the iframe - $('#create-project-iframe')[0].contentWindow.stop(); + self._createProjectUI.showImportProgressPanel(progressMessage, function() { + // stop the iframe + $('#create-project-iframe')[0].contentWindow.stop(); - // stop the timed polling - window.clearInterval(timerID); + // stop the timed polling + window.clearInterval(timerID); - // explicitly cancel the import job - Refine.CreateProjectUI.cancelImportingJob(jobID); + // explicitly cancel the import job + Refine.CreateProjectUI.cancelImportingJob(jobID); - self._createProjectUI.showSourceSelectionPanel(); - }); - }, - "json" - ); + self._createProjectUI.showSourceSelectionPanel(); + }); + }, + "json" + ); + }); }; Refine.DefaultImportingController.prototype._onImportJobReady = function() { @@ -180,27 +183,30 @@ Refine.DefaultImportingController.prototype._ensureFormatParserUIHasInitializati if (!(format in this._parserOptions)) { var self = this; var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting')); - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "core/default-importing-controller", - "jobID": this._jobID, - "subCommand": "initialize-parser-ui", - "format": format - }), - null, - function(data) { - dismissBusy(); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "core/default-importing-controller", + "jobID": self._jobID, + "subCommand": "initialize-parser-ui", + "format": format, + "csrf_token": token + }), + null, + function(data) { + dismissBusy(); - if (data.options) { - self._parserOptions[format] = data.options; - onDone(); - } - }, - "json" - ) - .fail(function() { - dismissBusy(); - alert($.i18n('core-views/check-format')); + if (data.options) { + self._parserOptions[format] = data.options; + onDone(); + } + }, + "json" + ) + .fail(function() { + dismissBusy(); + alert($.i18n('core-views/check-format')); + }); }); } else { onDone(); @@ -209,32 +215,35 @@ Refine.DefaultImportingController.prototype._ensureFormatParserUIHasInitializati Refine.DefaultImportingController.prototype.updateFormatAndOptions = function(options, callback, finallyCallBack) { var self = this; - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "core/default-importing-controller", - "jobID": this._jobID, - "subCommand": "update-format-and-options" - }), - { - "format" : this._format, - "options" : JSON.stringify(options) - }, - function(o) { - if (o.status == 'error') { - if (o.message) { - alert(o.message); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "core/default-importing-controller", + "jobID": self._jobID, + "subCommand": "update-format-and-options", + "csrf_token": token + }), + { + "format" : self._format, + "options" : JSON.stringify(options) + }, + function(o) { + 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 { - var messages = []; - $.each(o.errors, function() { messages.push(this.message); }); - alert(messages.join('\n\n')); + callback(o); } - finallyCallBack(); - } else { - callback(o); - } - }, - "json" - ); + }, + "json" + ); + }); }; Refine.DefaultImportingController.prototype.getPreviewData = function(callback, numRows) { @@ -242,7 +251,7 @@ Refine.DefaultImportingController.prototype.getPreviewData = function(callback, var result = {}; $.post( - "command/core/get-models?" + $.param({ "importingJobID" : this._jobID }), + "command/core/get-models?" + $.param({ "importingJobID" : self._jobID }), null, function(data) { for (var n in data) { @@ -284,56 +293,59 @@ Refine.DefaultImportingController.prototype._createProject = function() { var options = this._formatParserUI.getOptions(); options.projectName = projectName; options.projectTags = projectTags; - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "core/default-importing-controller", - "jobID": this._jobID, - "subCommand": "create-project" - }), - { - "format" : this._format, - "options" : JSON.stringify(options) - }, - function(o) { - if (o.status == 'error') { - alert(o.message); - return; - } - - var start = new Date(); - var timerID = window.setInterval( - function() { - self._createProjectUI.pollImportJob( - start, - self._jobID, - timerID, - function(job) { - return "projectID" in job.config; - }, - function(jobID, job) { - Refine.CreateProjectUI.cancelImportingJob(jobID); - document.location = "project?project=" + job.config.projectID; - }, - function(job) { - alert($.i18n('core-index-import/errors')+'\n' + Refine.CreateProjectUI.composeErrorMessage(job)); - self._onImportJobReady(); - } + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "core/default-importing-controller", + "jobID": self._jobID, + "subCommand": "create-project", + "csrf_token": token + }), + { + "format" : self._format, + "options" : JSON.stringify(options) + }, + function(o) { + if (o.status == 'error') { + alert(o.message); + return; + } + + var start = new Date(); + var timerID = window.setInterval( + function() { + self._createProjectUI.pollImportJob( + start, + self._jobID, + timerID, + function(job) { + return "projectID" in job.config; + }, + function(jobID, job) { + Refine.CreateProjectUI.cancelImportingJob(jobID); + document.location = "project?project=" + job.config.projectID; + }, + function(job) { + alert($.i18n('core-index-import/errors')+'\n' + Refine.CreateProjectUI.composeErrorMessage(job)); + self._onImportJobReady(); + } + ); + }, + 1000 ); - }, - 1000 + 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" ); - 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" - ); + }); } }; diff --git a/main/webapp/modules/core/scripts/index/default-importing-controller/file-selection-panel.js b/main/webapp/modules/core/scripts/index/default-importing-controller/file-selection-panel.js index 90c63b722..d7bc53f05 100644 --- a/main/webapp/modules/core/scripts/index/default-importing-controller/file-selection-panel.js +++ b/main/webapp/modules/core/scripts/index/default-importing-controller/file-selection-panel.js @@ -328,30 +328,33 @@ Refine.DefaultImportingController.prototype._commitFileSelection = function() { var self = this; var dismissBusy = DialogSystem.showBusy($.i18n('core-index-import/inspecting-files')); - $.post( - "command/core/importing-controller?" + $.param({ - "controller": "core/default-importing-controller", - "jobID": this._jobID, - "subCommand": "update-file-selection" - }), - { - "fileSelection" : JSON.stringify(this._job.config.fileSelection) - }, - function(data) { - dismissBusy(); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/importing-controller?" + $.param({ + "controller": "core/default-importing-controller", + "jobID": self._jobID, + "subCommand": "update-file-selection", + "csrf_token": token + }), + { + "fileSelection" : JSON.stringify(self._job.config.fileSelection) + }, + function(data) { + dismissBusy(); - if (!(data)) { - self._createProjectUI.showImportJobError($.i18n('core-index-import/unknown-err')); - } 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')); - } else { - // Different files might be selected. We start over again. - delete this._parserOptions; + if (!(data)) { + self._createProjectUI.showImportJobError($.i18n('core-index-import/unknown-err')); + } 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')); + } else { + // Different files might be selected. We start over again. + delete self._parserOptions; - self._job = data.job; - self._showParsingPanel(true); - } - }, - "json" - ); + self._job = data.job; + self._showParsingPanel(true); + } + }, + "json" + ); + }); }; diff --git a/main/webapp/modules/core/scripts/index/edit-metadata-dialog.js b/main/webapp/modules/core/scripts/index/edit-metadata-dialog.js index 6fb7ea275..b58c1c823 100644 --- a/main/webapp/modules/core/scripts/index/edit-metadata-dialog.js +++ b/main/webapp/modules/core/scripts/index/edit-metadata-dialog.js @@ -31,16 +31,16 @@ function EditMetadataDialog(metaData, targetRowElem) { if (newTags !== null) { $(td1).text(newTags); metaData[key] = newTags; - $.ajax({ - type : "POST", - url : "command/core/set-project-tags", - data : { + Refine.postCSRF( + "command/core/set-project-tags", + { "project" : project, "old" : oldTags, "new" : newTags }, - dataType : "json", - }); + function(data) {}, + "json" + ); } Refine.OpenProjectUI.refreshProject(targetRowElem, metaData, project); @@ -58,8 +58,8 @@ function EditMetadataDialog(metaData, targetRowElem) { if (newValue !== null) { $(td1).text(newValue); metaData[key] = newValue; - $.post( - "command/core/set-metaData", + Refine.postCSRF( + "command/core/set-project-metadata", { project : project, name : key, diff --git a/main/webapp/modules/core/scripts/index/import-project-ui.js b/main/webapp/modules/core/scripts/index/import-project-ui.js index 5229cd69e..8535cec5f 100644 --- a/main/webapp/modules/core/scripts/index/import-project-ui.js +++ b/main/webapp/modules/core/scripts/index/import-project-ui.js @@ -33,6 +33,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Refine.ImportProjectUI = function(elmt) { elmt.html(DOM.loadHTML("core", "scripts/index/import-project-ui.html")); + + Refine.wrapCSRF(function(token) { + elmt.attr('action', "command/core/import-project?" + $.param({ csrf_token: token})); + }); this._elmt = elmt; this._elmts = DOM.bind(elmt); diff --git a/main/webapp/modules/core/scripts/index/lang-settings-ui.js b/main/webapp/modules/core/scripts/index/lang-settings-ui.js index b322ba825..ceb1bc8d1 100644 --- a/main/webapp/modules/core/scripts/index/lang-settings-ui.js +++ b/main/webapp/modules/core/scripts/index/lang-settings-ui.js @@ -28,19 +28,22 @@ Refine.SetLanguageUI = function(elmt) { }); this._elmts.set_lan_btn.bind('click', function(e) { - $.ajax({ - url : "command/core/set-preference?", - type : "POST", - async : false, - data : { - name : "userLang", - value : JSON.stringify($("#langDD option:selected").val()) - }, - success : function(data) { - alert($.i18n('core-index-lang/page-reload')); - location.reload(true); - } - }); + Refine.wrapCSRF(function(token) { + $.ajax({ + url : "command/core/set-preference?", + type : "POST", + async : false, + data : { + name : "userLang", + value : JSON.stringify($("#langDD option:selected").val()), + csrf_token: token + }, + success : function(data) { + alert($.i18n('core-index-lang/page-reload')); + location.reload(true); + } + }); + }); }); }; diff --git a/main/webapp/modules/core/scripts/index/open-project-ui.js b/main/webapp/modules/core/scripts/index/open-project-ui.js index c3340f950..142c4f3bf 100644 --- a/main/webapp/modules/core/scripts/index/open-project-ui.js +++ b/main/webapp/modules/core/scripts/index/open-project-ui.js @@ -59,16 +59,16 @@ Refine.OpenProjectUI = function(elmt) { $('#projects-workspace-open').text($.i18n('core-index-open/browse')); $('#projects-workspace-open').click(function() { - $.ajax({ - type: "POST", - url: "command/core/open-workspace-dir", - dataType: "json", - success: function (data) { + Refine.postCSRF( + "command/core/open-workspace-dir", + {}, + function (data) { if (data.code != "ok" && "message" in data) { alert(data.message); } - } - }); + }, + "json" + ); }); Refine.TagsManager.allProjectTags = []; this._buildTagsAndFetchProjects(); @@ -221,18 +221,17 @@ Refine.OpenProjectUI.prototype._renderProjects = function(data) { .html("") .click(function() { if (window.confirm($.i18n('core-index-open/del-body') + project.name + "\"?")) { - $.ajax({ - type: "POST", - url: "command/core/delete-project", - data: { "project" : project.id }, - dataType: "json", - success: function (data) { + Refine.postCSRF( + "command/core/delete-project", + { "project" : project.id }, + function (data) { if (data && typeof data.code != 'undefined' && data.code == "ok") { Refine.TagsManager.allProjectTags = []; self._buildTagsAndFetchProjects(); } - } - }); + }, + "json" + ); } return false; }).appendTo( diff --git a/main/webapp/modules/core/scripts/preferences.js b/main/webapp/modules/core/scripts/preferences.js index 19bf905eb..36d7afcc2 100644 --- a/main/webapp/modules/core/scripts/preferences.js +++ b/main/webapp/modules/core/scripts/preferences.js @@ -33,6 +33,41 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. var preferenceUIs = []; +var Refine = { +}; + +// Requests a CSRF token and calls the supplied callback +// with the token +Refine.wrapCSRF = function(onCSRF) { + $.get( + "command/core/get-csrf-token", + {}, + function(response) { + onCSRF(response['token']); + }, + "json" + ); +}; + +// 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, failCallback) { + return Refine.wrapCSRF(function(token) { + var fullData = data || {}; + if (typeof fullData == 'string') { + fullData = fullData + "&" + $.param({csrf_token: token}); + } else { + fullData['csrf_token'] = token; + } + var req = $.post(url, fullData, success, dataType); + if (failCallback !== undefined) { + req.fail(failCallback); + } + }); +}; + + var lang = (navigator.language|| navigator.userLanguage).split("-")[0]; var dictionary = ""; $.ajax({ @@ -78,7 +113,7 @@ function PreferenceUI(tr, key, value) { } $(td1).text(newValue); - $.post( + Refine.postCSRF( "command/core/set-preference", { name : key, @@ -96,7 +131,7 @@ function PreferenceUI(tr, key, value) { $('