...
 
Commits (10)
minerva (14.0.7) stable; urgency=high
* Bug fix: API allowed to provide malformed plugin url that could be used for
xss attack (#1073)
-- Piotr Gawron <piotr.gawron@uni.lu> Tue, 14 Jan 2020 17:00:00 +0200
minerva (14.0.6) stable; urgency=medium
* Bug fix: opening map with no background overlays and search request in url
provides proper error message (#1046)
......
......@@ -16,6 +16,7 @@ var logger = require('../../logger');
var Functions = require('../../Functions');
var GuiConnector = require('../../GuiConnector');
var Promise = require("bluebird");
var xss = require('xss');
/**
*
......@@ -266,7 +267,7 @@ MapsAdminPanel.prototype.projectToTableRow = function (project, row, user) {
if (row[2] === undefined) {
row[2] = 'N/A';
}
row[3] = project.getName();
row[3] = xss(project.getName());
row[4] = disease;
row[5] = organism;
row[6] = status;
......
......@@ -13,6 +13,7 @@ var PrivilegeType = require('../../map/data/PrivilegeType');
var logger = require('../../logger');
var Promise = require('bluebird');
var xss = require('xss');
/**
*
......@@ -193,10 +194,15 @@ PluginAdminPanel.prototype.setPlugins = function (plugins) {
*/
PluginAdminPanel.prototype.pluginToTableRow = function (plugin) {
var row = [];
var urls = plugin.getUrls().slice();
row[0] = plugin.getName();
row[1] = plugin.getVersion();
row[2] = plugin.getUrls();
for (var i = 0; i < urls.length; i++) {
urls[i] = xss(urls[i]);
}
row[0] = xss(plugin.getName());
row[1] = xss(plugin.getVersion());
row[2] = urls;
row[3] = "<button name='removePlugin' data='" + plugin.getHash() + "' ><i class='fa fa-trash-o' style='font-size:17px'></button>";
return row;
};
......
......@@ -11,6 +11,7 @@ var PublicationListDialog = require('./PublicationListDialog');
var logger = require('../../logger');
var Functions = require('../../Functions');
var xss = require('xss');
/**
*
......@@ -288,8 +289,8 @@ ProjectInfoPanel.prototype.refresh = function () {
var projectDiseaseText = $("[name='projectDisease']", self.getElement())[0];
var project = self.getProject();
projectNameText.innerHTML = project.getName();
projectVersionText.innerHTML = project.getVersion();
projectNameText.innerHTML = xss(project.getName());
projectVersionText.innerHTML = xss(project.getVersion());
var promises = [
ServerConnector.getProjectStatistics().then(function (statistics) {
......
......@@ -60,7 +60,8 @@ public class Plugin implements Serializable {
@ElementCollection
@CollectionTable(name = "plugin_urls", joinColumns = @JoinColumn(name = "plugin_id"))
@Column(name = "url")
private Set<String> urls = new HashSet<>();
@OrderBy("idx")
private List<String> urls = new ArrayList<>();
public String getHash() {
return hash;
......@@ -70,14 +71,10 @@ public class Plugin implements Serializable {
this.hash = hash;
}
public Set<String> getUrls() {
public List<String> getUrls() {
return urls;
}
public void setUrls(Set<String> urls) {
this.urls = urls;
}
public String getName() {
return name;
}
......
alter table plugin_urls add column idx integer;
alter table plugin_urls add column idx_c varchar;
update plugin_urls set idx_c = ctid;
update plugin_urls p1 set idx = (select count(*) from plugin_urls p2 where p2.idx_c < p1.idx_c);
alter table plugin_urls drop column idx_c;
alter table plugin_urls alter COLUMN idx set not null;
CREATE SEQUENCE plugin_url_sequence;
alter table plugin_urls alter COLUMN idx set default nextval('plugin_url_sequence');
SELECT setval('plugin_url_sequence', (select max(idx)+1 from plugin_urls));
\ No newline at end of file
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>lcsb.mapviewer</groupId>
......@@ -73,6 +75,24 @@
<version>${jackson.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>${commons-validator.version}</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
<exclusion>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
......
......@@ -11,8 +11,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
import lcsb.mapviewer.api.BaseController;
import lcsb.mapviewer.api.ObjectNotFoundException;
import lcsb.mapviewer.api.*;
import lcsb.mapviewer.model.user.User;
import lcsb.mapviewer.services.interfaces.IUserService;
......@@ -41,7 +40,7 @@ public class PluginController extends BaseController {
@RequestParam(value = "name") String name,
@RequestParam(value = "version") String version,
@RequestParam(value = "isPublic", defaultValue = "false") boolean isPublic,
@RequestParam(value = "url", defaultValue = "") String url) {
@RequestParam(value = "url", defaultValue = "") String url) throws QueryException {
return pluginRest.createPlugin(hash, name, version, url, isPublic);
}
......
......@@ -2,12 +2,12 @@ package lcsb.mapviewer.api.plugins;
import java.util.*;
import org.apache.commons.validator.routines.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lcsb.mapviewer.api.BaseRestImpl;
import lcsb.mapviewer.api.ObjectNotFoundException;
import lcsb.mapviewer.api.*;
import lcsb.mapviewer.model.plugin.Plugin;
import lcsb.mapviewer.model.plugin.PluginDataEntry;
import lcsb.mapviewer.model.user.User;
......@@ -28,7 +28,10 @@ public class PluginRestImpl extends BaseRestImpl {
this.pluginDataEntryDao = pluginDataEntryDao;
}
public Map<String, Object> createPlugin(String hash, String name, String version, String url, boolean isPublic) {
public Map<String, Object> createPlugin(String hash, String name, String version, String url, boolean isPublic) throws QueryException {
if (!UrlValidator.getInstance().isValid(url)) {
throw new QueryException("Invalid url: " + url);
}
Plugin plugin = pluginDao.getByHash(hash);
if (plugin != null) {
plugin.getUrls().add(url);
......@@ -54,9 +57,8 @@ public class PluginRestImpl extends BaseRestImpl {
result.put("name", plugin.getName());
result.put("version", plugin.getVersion());
result.put("isPublic", plugin.isPublic());
List<String> urls = new ArrayList<>(plugin.getUrls());
Collections.sort(urls);
result.put("urls", urls);
plugin.getUrls().contains("");
result.put("urls", plugin.getUrls());
return result;
}
......
......@@ -12,12 +12,12 @@ public class PluginRestImplTest extends RestTestFunctions {
@Test
public void testCreatePlugin() throws Exception {
pluginRestImpl.createPlugin("x", "x", "x", "x", true);
pluginRestImpl.createPlugin("x", "x", "x", "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js", true);
}
@Test
public void testRemovePlugin() throws Exception {
pluginRestImpl.createPlugin("x", "x", "x", "x", true);
pluginRestImpl.createPlugin("x", "x", "x", "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js", true);
pluginRestImpl.removePlugin("x");
}
......
......@@ -61,7 +61,7 @@ public class PluginControllerIntegrationTest extends ControllerIntegrationTest {
new BasicNameValuePair("name", "x"),
new BasicNameValuePair("version", "x"),
new BasicNameValuePair("isPublic", "true"),
new BasicNameValuePair("url", "x"))));
new BasicNameValuePair("url", "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js"))));
RequestBuilder request = post("/plugins/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
......@@ -82,7 +82,7 @@ public class PluginControllerIntegrationTest extends ControllerIntegrationTest {
new BasicNameValuePair("name", "x"),
new BasicNameValuePair("version", "x"),
new BasicNameValuePair("isPublic", "true"),
new BasicNameValuePair("url", "x"))));
new BasicNameValuePair("url", "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js"))));
RequestBuilder request = post("/plugins/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
......@@ -94,6 +94,28 @@ public class PluginControllerIntegrationTest extends ControllerIntegrationTest {
assertEquals(1, pluginDao.getAll().size());
}
@Test
public void createPublicPluginWithInvalidUrl() throws Exception {
MockHttpSession session = createSession(TEST_USER_LOGIN, TEST_USER_PASSWORD);
userService.grantUserPrivilege(user, PrivilegeType.IS_ADMIN);
String body = EntityUtils.toString(new UrlEncodedFormEntity(Arrays.asList(
new BasicNameValuePair("hash", "x"),
new BasicNameValuePair("name", "x"),
new BasicNameValuePair("version", "x"),
new BasicNameValuePair("isPublic", "true"),
new BasicNameValuePair("url", "x"))));
RequestBuilder request = post("/plugins/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.content(body)
.session(session);
mockMvc.perform(request)
.andExpect(status().isBadRequest());
assertEquals(0, pluginDao.getAll().size());
}
@Test
public void createPrivatePlugin() throws Exception {
MockHttpSession session = createSession(TEST_USER_LOGIN, TEST_USER_PASSWORD);
......@@ -103,7 +125,7 @@ public class PluginControllerIntegrationTest extends ControllerIntegrationTest {
new BasicNameValuePair("name", "x"),
new BasicNameValuePair("version", "x"),
new BasicNameValuePair("isPublic", "false"),
new BasicNameValuePair("url", "x"))));
new BasicNameValuePair("url", "https://minerva-dev.lcsb.uni.lu/plugins/starter-kit/plugin.js"))));
RequestBuilder request = post("/plugins/")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
......