Require CSRF token in EditOneCellCommand

This commit is contained in:
Antonin Delpeuch 2019-10-11 09:50:01 +01:00
parent 21b841a089
commit 51ddd27909
7 changed files with 139 additions and 6 deletions

View File

@ -37,6 +37,8 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -67,7 +69,7 @@ public abstract class Command {
final static protected Logger logger = LoggerFactory.getLogger("command"); final static protected Logger logger = LoggerFactory.getLogger("command");
final static CSRFTokenFactory csrfFactory = new CSRFTokenFactory(3600, 32); final static public CSRFTokenFactory csrfFactory = new CSRFTokenFactory(3600, 32);
protected RefineServlet servlet; protected RefineServlet servlet;
@ -217,6 +219,27 @@ public abstract class Command {
return def; 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");
}
protected static class HistoryEntryResponse { protected static class HistoryEntryResponse {
@JsonProperty("code") @JsonProperty("code")
protected String getCode() { return "ok"; } protected String getCode() { return "ok"; }
@ -299,6 +322,13 @@ public abstract class Command {
w.flush(); w.flush();
w.close(); w.close();
} }
static protected void respondCSRFError(HttpServletResponse response) throws IOException {
Map<String, String> 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) static protected void respondException(HttpServletResponse response, Exception e)
throws IOException, ServletException { throws IOException, ServletException {

View File

@ -52,7 +52,11 @@ import com.google.refine.util.ParsingUtilities;
public class ComputeClustersCommand extends Command { public class ComputeClustersCommand extends Command {
final static Logger logger = LoggerFactory.getLogger("compute-clusters_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 @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {

View File

@ -44,6 +44,11 @@ import com.google.refine.commands.Command;
import com.google.refine.model.Project; import com.google.refine.model.Project;
public class ComputeFacetsCommand extends Command { 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 @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {

View File

@ -84,6 +84,10 @@ public class EditOneCellCommand extends Command {
@Override @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
if(!hasValidCSRFToken(request)) {
respondCSRFError(response);
return;
}
try { try {
request.setCharacterEncoding("UTF-8"); request.setCharacterEncoding("UTF-8");

View File

@ -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());
}
}

View File

@ -54,6 +54,7 @@ function registerCommands() {
var RS = Packages.com.google.refine.RefineServlet; var RS = Packages.com.google.refine.RefineServlet;
RS.registerCommand(module, "get-version", new Packages.com.google.refine.commands.GetVersionCommand()); 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, "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()); RS.registerCommand(module, "create-importing-job", new Packages.com.google.refine.commands.importing.CreateImportingJobCommand());

View File

@ -388,10 +388,21 @@ Refine.postProcess = function(moduleName, command, params, body, updateOptions,
Refine.setAjaxInProgress(); Refine.setAjaxInProgress();
$.post( // Get a CSRF token first
"command/" + moduleName + "/" + command + "?" + $.param(params), $.get(
body, "command/core/get-csrf-token",
onDone, {},
function(response) {
// Add it to the body and submit it as a POST request
body['csrf_token'] = response['token'];
$.post(
"command/" + moduleName + "/" + command + "?" + $.param(params),
body,
onDone,
"json"
);
},
"json" "json"
); );