From 70e37b90853fc50160a8adbb3b1730e126ac421e Mon Sep 17 00:00:00 2001 From: Antonin Delpeuch Date: Fri, 11 Oct 2019 12:24:44 +0100 Subject: [PATCH] Add CSRF protection to cell, history, column and expr commands --- .../commands/EngineDependentCommand.java | 4 + .../commands/GetAllPreferencesCommand.java | 4 + .../cell/JoinMultiValueCellsCommand.java | 4 + .../cell/KeyValueColumnizeCommand.java | 4 + .../cell/SplitMultiValueCellsCommand.java | 4 + .../cell/TransposeColumnsIntoRowsCommand.java | 4 + .../cell/TransposeRowsIntoColumnsCommand.java | 4 + .../commands/column/MoveColumnCommand.java | 4 + .../commands/column/RemoveColumnCommand.java | 4 + .../commands/column/RenameColumnCommand.java | 4 + .../commands/expr/LogExpressionCommand.java | 6 +- .../expr/PreviewExpressionCommand.java | 4 + .../expr/ToggleStarredExpressionCommand.java | 5 + .../history/ApplyOperationsCommand.java | 4 + .../history/CancelProcessesCommand.java | 4 + .../commands/history/UndoRedoCommand.java | 4 + .../importing/CancelImportingJobCommand.java | 4 + .../importing/CreateImportingJobCommand.java | 4 + .../GetImportingConfigurationCommand.java | 4 + .../GetImportingJobStatusCommand.java | 4 + .../refine/commands/CommandTestBase.java | 41 ++++++++ .../commands/EngineDependentCommandTests.java | 38 +++++++ .../cell/JoinMultiValueCellsCommandTests.java | 25 +++++ .../cell/KeyValueColumnizeCommandTests.java | 24 +++++ .../SplitMultiValueCellsCommandTests.java | 23 +++++ .../TransposeColumnsIntoRowsCommandTests.java | 23 +++++ .../TransposeRowsIntoColumnsCommandTests.java | 22 +++++ .../column/MoveColumnCommandTests.java | 24 +++++ .../column/RemoveColumnCommandTests.java | 23 +++++ .../column/RenameColumnCommandTests.java | 23 +++++ .../expr/LogExpressionCommandTests.java | 23 +++++ .../ToggleStarredExpressionCommandTests.java | 9 ++ .../history/ApplyOperationsCommandTests.java | 24 +++++ .../history/CancelProcessesCommandTests.java | 23 +++++ .../history/UndoRedoCommandTests.java | 23 +++++ .../CancelImportingJobCommandTests.java | 22 +++++ .../CreateImportingJobCommandTests.java | 23 +++++ .../util/CancelProcessesCommandTests.java | 4 + .../dialogs/expression-preview-dialog.js | 61 +++++++----- .../core/scripts/index/create-project-ui.js | 5 +- .../controller.js | 98 ++++++++++--------- main/webapp/modules/core/scripts/project.js | 25 +++-- .../core/scripts/project/process-panel.js | 20 ++-- 43 files changed, 616 insertions(+), 93 deletions(-) create mode 100644 main/tests/server/src/com/google/refine/commands/CommandTestBase.java create mode 100644 main/tests/server/src/com/google/refine/commands/EngineDependentCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/cell/JoinMultiValueCellsCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/cell/KeyValueColumnizeCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/cell/SplitMultiValueCellsCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/cell/TransposeColumnsIntoRowsCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/cell/TransposeRowsIntoColumnsCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/column/MoveColumnCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/column/RemoveColumnCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/column/RenameColumnCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/expr/LogExpressionCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/history/ApplyOperationsCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/history/CancelProcessesCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/history/UndoRedoCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/importing/CancelImportingJobCommandTests.java create mode 100644 main/tests/server/src/com/google/refine/commands/importing/CreateImportingJobCommandTests.java 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/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..fbfe4d0a6 100644 --- a/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java +++ b/main/src/com/google/refine/commands/importing/GetImportingJobStatusCommand.java @@ -66,6 +66,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 { 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/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/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/scripts/dialogs/expression-preview-dialog.js b/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js index ae6b337fd..5554b5ca5 100644 --- a/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js +++ b/main/webapp/modules/core/scripts/dialogs/expression-preview-dialog.js @@ -157,13 +157,15 @@ ExpressionPreviewDialog.Widget.prototype.getExpression = function(commit) { s = this._getLanguage() + ":" + s; if (commit) { - $.post( - "command/core/log-expression?" + $.param({ project: theProject.id }), - { expression: s }, - function(data) { - }, - "json" - ); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/log-expression?" + $.param({ project: theProject.id }), + { expression: s, csrf_token: token }, + function(data) { + }, + "json" + ); + }); } return s; @@ -284,16 +286,21 @@ ExpressionPreviewDialog.Widget.prototype._renderExpressionHistory = function(dat .addClass(entry.starred ? "data-table-star-on" : "data-table-star-off") .appendTo(tr.insertCell(0)) .click(function() { - $.post( - "command/core/toggle-starred-expression", - { expression: entry.code }, - function(data) { - entry.starred = !entry.starred; - renderEntry(self,tr,entry); - self._renderStarredExpressionsTab(); - }, - "json" - ); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/toggle-starred-expression", + { + expression: entry.code, + csrf_token: token + }, + function(data) { + entry.starred = !entry.starred; + renderEntry(self,tr,entry); + self._renderStarredExpressionsTab(); + }, + "json" + ); + }); }); $(''+$.i18n('core-dialogs/reuse')+'').appendTo(tr.insertCell(1)).click(function() { @@ -348,15 +355,17 @@ ExpressionPreviewDialog.Widget.prototype._renderStarredExpressions = function(da var o = Scripting.parse(entry.code); $(''+$.i18n('core-dialogs/remove')+'').appendTo(tr.insertCell(0)).click(function() { - $.post( - "command/core/toggle-starred-expression", - { expression: entry.code, returnList: true }, - function(data) { - self._renderStarredExpressions(data); - self._renderExpressionHistoryTab(); - }, - "json" - ); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/toggle-starred-expression", + { expression: entry.code, returnList: true, csrf_token: token }, + function(data) { + self._renderStarredExpressions(data); + self._renderExpressionHistoryTab(); + }, + "json" + ); + }); }); $('Reuse').appendTo(tr.insertCell(1)).click(function() { 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..031738616 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,63 @@ 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" + })); + 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() { diff --git a/main/webapp/modules/core/scripts/project.js b/main/webapp/modules/core/scripts/project.js index 0f1bc8f65..e5ede5e5d 100644 --- a/main/webapp/modules/core/scripts/project.js +++ b/main/webapp/modules/core/scripts/project.js @@ -388,22 +388,18 @@ Refine.postProcess = function(moduleName, command, params, body, updateOptions, Refine.setAjaxInProgress(); - // Get a CSRF token first - $.get( - "command/core/get-csrf-token", - {}, - function(response) { + Refine.wrapCSRF( + function(token) { // Add it to the body and submit it as a POST request - body['csrf_token'] = response['token']; + body['csrf_token'] = token; $.post( "command/" + moduleName + "/" + command + "?" + $.param(params), body, onDone, "json" ); - }, - "json" + } ); window.setTimeout(function() { @@ -413,6 +409,19 @@ Refine.postProcess = function(moduleName, command, params, body, updateOptions, }, 500); }; +// 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" + ); +}; + Refine.setAjaxInProgress = function() { $(document.body).attr("ajax_in_progress", "true"); }; diff --git a/main/webapp/modules/core/scripts/project/process-panel.js b/main/webapp/modules/core/scripts/project/process-panel.js index f0bd55b85..886ea5a28 100644 --- a/main/webapp/modules/core/scripts/project/process-panel.js +++ b/main/webapp/modules/core/scripts/project/process-panel.js @@ -124,15 +124,17 @@ ProcessPanel.prototype.undo = function() { ProcessPanel.prototype._cancelAll = function() { var self = this; - $.post( - "command/core/cancel-processes?" + $.param({ project: theProject.id }), - null, - function(o) { - self._data = null; - self._runOnDones(); - }, - "json" - ); + Refine.wrapCSRF(function(token) { + $.post( + "command/core/cancel-processes?" + $.param({ project: theProject.id }), + { csrf_token: token }, + function(o) { + self._data = null; + self._runOnDones(); + }, + "json" + ); + }); }; ProcessPanel.prototype._render = function(newData) {