CSRF protection for project and recon commands
This commit is contained in:
parent
a340c137d0
commit
3559eeb11f
@ -240,6 +240,21 @@ public abstract class Command {
|
||||
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"; }
|
||||
|
@ -56,7 +56,7 @@ public class ImportingControllerCommand extends Command {
|
||||
@Override
|
||||
public void doPost(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
if(!checkCSRF(request)) {
|
||||
if(!hasValidCSRFTokenAsGET(request)) {
|
||||
respondCSRFError(response);
|
||||
return;
|
||||
}
|
||||
@ -96,14 +96,4 @@ public class ImportingControllerCommand extends Command {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the validity of a CSRF token, without reading the whole POST body.
|
||||
* See above for details.
|
||||
*/
|
||||
private boolean checkCSRF(HttpServletRequest request) {
|
||||
Properties options = ParsingUtilities.parseUrlParameters(request);
|
||||
String token = options.getProperty("csrf_token");
|
||||
return token != null && csrfFactory.validToken(token);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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"));
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -69,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());
|
||||
|
||||
|
@ -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'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -53,11 +53,14 @@ 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.wrapCSRF(function(token) {
|
||||
Refine.postCSRF = function(url, data, success, dataType, failCallback) {
|
||||
return Refine.wrapCSRF(function(token) {
|
||||
var fullData = data || {};
|
||||
fullData['csrf_token'] = token;
|
||||
$.post(url, fullData, success, dataType);
|
||||
var req = $.post(url, fullData, success, dataType);
|
||||
if (failCallback !== undefined) {
|
||||
req.fail(failCallback);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
elem.attr('action', "command/core/import-project?" + $.param({ csrf_token: token});
|
||||
});
|
||||
|
||||
this._elmt = elmt;
|
||||
this._elmts = DOM.bind(elmt);
|
||||
|
@ -221,18 +221,17 @@ Refine.OpenProjectUI.prototype._renderProjects = function(data) {
|
||||
.html("<img src='images/close.png' />")
|
||||
.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(
|
||||
|
@ -216,20 +216,19 @@ Refine._renameProject = function() {
|
||||
return;
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: "command/core/rename-project",
|
||||
data: { "project" : theProject.id, "name" : name },
|
||||
dataType: "json",
|
||||
success: function (data) {
|
||||
Refine.postCSRF(
|
||||
"command/core/rename-project",
|
||||
{ "project" : theProject.id, "name" : name },
|
||||
function (data) {
|
||||
if (data && typeof data.code != "undefined" && data.code == "ok") {
|
||||
theProject.metadata.name = name;
|
||||
Refine.setTitle();
|
||||
} else {
|
||||
alert($.i18n('core-index/error-rename')+" " + data.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
"json"
|
||||
);
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -44,7 +44,7 @@ ReconStandardServicePanel.prototype._guessTypes = function(f) {
|
||||
var self = this;
|
||||
var dismissBusy = DialogSystem.showBusy();
|
||||
|
||||
$.post(
|
||||
Refine.postCSRF(
|
||||
"command/core/guess-types-of-column?" + $.param({
|
||||
project: theProject.id,
|
||||
columnName: this._column.name,
|
||||
@ -74,7 +74,8 @@ ReconStandardServicePanel.prototype._guessTypes = function(f) {
|
||||
|
||||
dismissBusy();
|
||||
f();
|
||||
}
|
||||
},
|
||||
"json"
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user