CSRF protection for OpenWorkspaceDirCommand and language loading

This commit is contained in:
Antonin Delpeuch 2019-10-11 15:30:54 +01:00
parent f7177e670d
commit a340c137d0
8 changed files with 97 additions and 19 deletions

View File

@ -48,6 +48,10 @@ public class OpenWorkspaceDirCommand extends Command {
@Override @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
if(!hasValidCSRFToken(request)) {
respondCSRFError(response);
return;
}
String serverName = request.getServerName(); String serverName = request.getServerName();

View File

@ -93,12 +93,6 @@ public class GetLanguagesCommand extends Command {
@Override @Override
public void doGet(HttpServletRequest request, HttpServletResponse response) public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
doPost(request, response);
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String modname = request.getParameter("module"); String modname = request.getParameter("module");
if (modname == null) { if (modname == null) {

View File

@ -63,10 +63,15 @@ public class LoadLanguageCommand extends Command {
doPost(request, response); 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 @Override
public void doPost(HttpServletRequest request, HttpServletResponse response) public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { throws ServletException, IOException {
String modname = request.getParameter("module"); String modname = request.getParameter("module");
if (modname == null) { if (modname == null) {
modname = "core"; modname = "core";

View File

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

View File

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

View File

@ -37,6 +37,8 @@ var Refine = {
actionAreas: [] actionAreas: []
}; };
// Requests a CSRF token and calls the supplied callback
// with the token
Refine.wrapCSRF = function(onCSRF) { Refine.wrapCSRF = function(onCSRF) {
$.get( $.get(
"command/core/get-csrf-token", "command/core/get-csrf-token",
@ -48,15 +50,17 @@ Refine.wrapCSRF = function(onCSRF) {
); );
}; };
// Performs a POST request where an additional CSRF token
// is supplied in the POST data. The arguments match those
// of $.post().
Refine.postCSRF = function(url, data, success, dataType) { Refine.postCSRF = function(url, data, success, dataType) {
Refine.wrapCSRF(function(token) { Refine.wrapCSRF(function(token) {
var fullData = data || {}; var fullData = data || {};
data['csrf_token'] = token; fullData['csrf_token'] = token;
$.post(url, fulldata, success, dataType); $.post(url, fullData, success, dataType);
}); });
}; };
var lang = (navigator.language|| navigator.userLanguage).split("-")[0]; var lang = (navigator.language|| navigator.userLanguage).split("-")[0];
var dictionary = ""; var dictionary = "";
$.ajax({ $.ajax({

View File

@ -59,16 +59,16 @@ Refine.OpenProjectUI = function(elmt) {
$('#projects-workspace-open').text($.i18n('core-index-open/browse')); $('#projects-workspace-open').text($.i18n('core-index-open/browse'));
$('#projects-workspace-open').click(function() { $('#projects-workspace-open').click(function() {
$.ajax({ Refine.postCSRF(
type: "POST", "command/core/open-workspace-dir",
url: "command/core/open-workspace-dir", {},
dataType: "json", function (data) {
success: function (data) {
if (data.code != "ok" && "message" in data) { if (data.code != "ok" && "message" in data) {
alert(data.message); alert(data.message);
} }
} },
}); "json"
);
}); });
Refine.TagsManager.allProjectTags = []; Refine.TagsManager.allProjectTags = [];
this._buildTagsAndFetchProjects(); this._buildTagsAndFetchProjects();

View File

@ -421,8 +421,8 @@ Refine.wrapCSRF = function(onCSRF) {
Refine.postCSRF = function(url, data, success, dataType) { Refine.postCSRF = function(url, data, success, dataType) {
Refine.wrapCSRF(function(token) { Refine.wrapCSRF(function(token) {
var fullData = data || {}; var fullData = data || {};
data['csrf_token'] = token; fullData['csrf_token'] = token;
$.post(url, fulldata, success, dataType); $.post(url, fullData, success, dataType);
}); });
}; };