Skip to content
Snippets Groups Projects
Commit 6e7eae34 authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Moved setNode view checks from DAO to Controller; Fixed unit tests and added DAO setNode test

parent 8bc3c0de
No related branches found
No related tags found
No related merge requests found
Pipeline #1142 passed
package it.inaf.oats.vospace;
import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.LinkFoundException;
......@@ -9,12 +8,12 @@ import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
......@@ -29,9 +28,6 @@ public class SetNodeController extends BaseNodeController {
@Autowired
private NodeDAO nodeDao;
@Value("${vospace-authority}")
private String authority;
@PostMapping(value = {"/nodes", "/nodes/**"},
consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
......@@ -45,7 +41,6 @@ public class SetNodeController extends BaseNodeController {
Node toBeModifiedNode = nodeDao.listNode(path)
.orElseThrow(() -> new NodeNotFoundException(path));
// The service SHALL throw a HTTP 403 status code including a PermissionDenied fault
// in the entity-body if the user does not have permissions to perform the operation
if (!NodeUtils.checkIfWritable(toBeModifiedNode, principal.getName(), principal.getGroups())) {
......@@ -62,9 +57,86 @@ public class SetNodeController extends BaseNodeController {
throw new PermissionDeniedException(path);
}
Node result = nodeDao.setNode(node).orElseThrow(() -> new PermissionDeniedException(""));
// This method cannot be used to modify the accepts or provides list of Views for the Node.
// Only DataNodes has Views (see VOSpace Data Model)
if (node instanceof DataNode) {
checkViews((DataNode) node, (DataNode) toBeModifiedNode);
}
//The service SHOULD throw a HTTP 500 status code including an InternalFault fault
// in the entity-body if the operation fails
// Done in NodeDAO
// to be fixed
// A HTTP 200 status code and a Node representation in the entity-body The full
// expanded record for the node SHALL be returned, including any xsi:type
// specific extensions.
return ResponseEntity.ok(nodeDao.setNode(node));
}
private void checkViews(DataNode requestedDataNode, DataNode savedDataNode) {
checkAcceptsView(requestedDataNode, savedDataNode);
checkProvidesView(requestedDataNode, savedDataNode);
}
private void checkAcceptsView(DataNode requestedDataNode, DataNode savedDataNode) {
List<View> requestedAcceptedViews = requestedDataNode.getAccepts();
List<View> savedAcceptedViews = savedDataNode.getAccepts();
// If views are present it is necessary to perform the check, otherwise
// it means that the user is not going to change them
if (!requestedAcceptedViews.isEmpty() && !checkIfViewsAreEquals(requestedAcceptedViews, savedAcceptedViews)) {
LOG.debug("setNode trying to modify accepted Views.");
throw new PermissionDeniedException("Trying to modify accepted views");
}
}
private void checkProvidesView(DataNode requestedDataNode, DataNode savedDataNode) {
List<View> requestedProvidedViews = requestedDataNode.getProvides();
List<View> savedProvidedViews = savedDataNode.getProvides();
// If views are present it is necessary to perform the check, otherwise
// it means that the user is not going to change them
if (!requestedProvidedViews.isEmpty() && !checkIfViewsAreEquals(requestedProvidedViews, savedProvidedViews)) {
LOG.debug("setNode trying to modify provided Views.");
throw new PermissionDeniedException("Trying to modify provided views");
}
}
private boolean checkIfViewsAreEquals(List<View> viewsA, List<View> viewsB) {
boolean viewsAreEqual = true;
if ((viewsA == null || viewsA.isEmpty())
&& (viewsB == null || viewsB.isEmpty())) {
return true;
}
if ((viewsA == null || viewsA.isEmpty())
|| (viewsB == null || viewsB.isEmpty())) {
return false;
}
return ResponseEntity.ok(result);
if (viewsA.size() != viewsB.size()) {
return false;
}
for (int i = 0; i < viewsA.size(); i++) {
String viewUriA = viewsA.get(i).getUri();
boolean found = false;
for (int j = 0; j < viewsB.size(); j++) {
String viewUriB = viewsB.get(i).getUri();
if (viewUriA.compareTo(viewUriB) == 0) {
found = true;
}
}
if (found == false) {
viewsAreEqual = false;
break;
}
}
return viewsAreEqual;
}
}
......@@ -3,10 +3,7 @@ package it.inaf.oats.vospace.persistence;
import it.inaf.oats.vospace.DeleteNodeController;
import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.InternalFaultException;
import it.inaf.oats.vospace.exception.NodeNotFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException;
import java.sql.Array;
import net.ivoa.xml.vospace.v2.Node;
import java.sql.PreparedStatement;
......@@ -23,7 +20,6 @@ import javax.sql.DataSource;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.StructuredDataNode;
import net.ivoa.xml.vospace.v2.View;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -124,41 +120,9 @@ public class NodeDAO {
}
public Optional<Node> setNode(Node newNode) {
public Node setNode(Node newNode) {
// Verify that the node is in the database
String vosPath = NodeUtils.getVosPath(newNode);
// List<NodePaths> paths = getNodePathsFromDB(nodeURI);
// This method cannot be used to modify the accepts or provides list of Views for the Node.
// Only DataNodes has Views (see VOSpace Data Model
// Check if trying to change views getting Views (if present) of the modified node
if (newNode instanceof DataNode) {
DataNode dataNode = (DataNode)newNode;
List<View> requestedAcceptedViews = dataNode.getAccepts();
List<View> savedAcceptedViews = getAcceptedViewsFromDB(vosPath);
// Get the Views of the saved node
List<View> requestedProvidedViews = dataNode.getProvides();
List<View> savedProvidedViews = getProvidedViewsFromDB(vosPath);
if (!requestedAcceptedViews.isEmpty()) {
//se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle
// Check if requestedAcceptedViews are different from the already stored
if (!checkIfViewsAreEquals(requestedAcceptedViews,savedAcceptedViews)) {
LOG.debug("setNode trying to modify accepted Views.");
throw new PermissionDeniedException("Trying to modify accepted views");
}
}
if (!requestedProvidedViews.isEmpty()) {
//se sono non nulle, devo fare i controlli, altrimenti di sicuro l'utente non sta chiedendo di cambiarle
// Check if requestedAcceptedViews are different from the already stored
if (!checkIfViewsAreEquals(requestedProvidedViews,savedProvidedViews)) {
LOG.debug("setNode trying to modify provided Views.");
throw new PermissionDeniedException("Trying to modify provided views");
}
}
}
StringBuilder sb = new StringBuilder();
......@@ -177,40 +141,9 @@ public class NodeDAO {
return ps;
});
return Optional.of(newNode);
}
private Optional<Node> getNode(String path) {
String sql = "SELECT node_id, type, async_trans, sticky, busy_state, creator_id, group_read, group_write,\n"
+ "is_public, content_length, created_on, last_modified, accept_views, provide_views \n"
+ "FROM node WHERE path = ?";
List<Node> nodes = jdbcTemplate.query(conn -> {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, path);
return ps;
}, (row, index) -> {
return getNodeFromResultSet(row);
});
if (nodes.isEmpty()) {
return Optional.empty();
return newNode;
}
// Query returns the node (can be container or data)
Node node = nodes.get(0);
return Optional.of(node);
}
private String getFirstLevelChildrenSelector(String path) {
String select = "(SELECT path FROM node WHERE node_id = (SELECT node_id FROM node_vos_path WHERE vos_path = ?))::varchar || '";
......@@ -425,78 +358,8 @@ public class NodeDAO {
}
private boolean checkIfViewsAreEquals(List<View> viewsA, List<View> viewsB) {
boolean viewsAreEqual = true;
if ((viewsA == null || viewsA.isEmpty()) &&
(viewsB == null || viewsB.isEmpty()))
return true;
if ((viewsA == null || viewsA.isEmpty()) ||
(viewsB == null || viewsB.isEmpty()))
return false;
if( viewsA.size() != viewsB.size())
return false;
for(int i = 0; i < viewsA.size(); i++) {
String viewUriA = viewsA.get(i).getUri();
boolean found = false;
for(int j = 0; j < viewsB.size(); j++) {
String viewUriB = viewsB.get(i).getUri();
if (viewUriA.compareTo(viewUriB) == 0) {
found = true;
}
}
if(found == false) {
viewsAreEqual = false;
break;
}
}
return viewsAreEqual;
}
private List<View> getAcceptedViewsFromDB(String nodePath) {
return getViewsFromDB(nodePath, "accept_views");
}
private List<View> getProvidedViewsFromDB(String nodePath) {
return getViewsFromDB(nodePath, "provide_views");
}
private List<View> getViewsFromDB(String nodePath, String viewType) {
String sql = "SELECT accept_views FROM node WHERE node_id = "
+ "(SELECT node_id FROM node_vos_path WHERE vos_path = ?)";
Array results = jdbcTemplate.query(sql, ps -> {
ps.setString(1, nodePath);
}, (rs) -> {
if(rs.next())
return rs.getArray("accept_views");
else
return null;
});
return getViews(results);
}
private List<NodePaths> getNodePathsFromDB(String nodeURI) {
//String nodeURI = myNode.getUri();
String path = nodeURI.replaceAll("vos://[^/]+", "");
String parentPath = NodeUtils.getParentPath(path);
......
......@@ -9,9 +9,9 @@ import java.util.List;
import java.util.Optional;
import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.LinkNode;
import net.ivoa.xml.vospace.v2.Node;
import net.ivoa.xml.vospace.v2.Property;
import net.ivoa.xml.vospace.v2.View;
import org.junit.jupiter.api.Test;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
......@@ -62,7 +62,7 @@ public class SetNodeControllerTest {
private Node getWritableDataNode(String path) {
DataNode node = new DataNode();
List nodeProperties = node.getProperties();
List<Property> nodeProperties = node.getProperties();
Property groupWriteProp = new Property();
groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI);
groupWriteProp.setValue("group1");
......@@ -73,7 +73,7 @@ public class SetNodeControllerTest {
private Node getWritableDataNodeWithAcceptsAndProvides(String path) {
DataNode node = new DataNode();
List nodeProperties = node.getProperties();
List<Property> nodeProperties = node.getProperties();
Property groupWriteProp = new Property();
groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI);
groupWriteProp.setValue("group1");
......@@ -86,18 +86,22 @@ public class SetNodeControllerTest {
nodeProperties.add(groupWriteProp);
nodeProperties.add(groupReadProp);
nodeProperties.add(descriptionProp);
List accepts = new ArrayList();
accepts.add("ivo://ivoa.net/vospace/core#httpput");
accepts.add("ivo://ivoa.net/vospace/core#defaultview");
List<View> accepts = new ArrayList<>();
accepts.add(getView("ivo://ivoa.net/vospace/core#defaultview"));
node.setAccepts(accepts);
List provides = new ArrayList();
provides.add("ivo://ivoa.net/vospace/core#httpput");
provides.add("ivo://ivoa.net/vospace/core#defaultview");
node.setAccepts(provides);
List<View> provides = new ArrayList<>();
provides.add(getView("ivo://ivoa.net/vospace/core#defaultview"));
node.setProvides(provides);
node.setUri(URI_PREFIX + path);
return node;
}
private View getView(String uri) {
View view = new View();
view.setUri(uri);
return view;
}
/* Test case:
try to modify node type.
Forbidden.
......@@ -153,7 +157,7 @@ public class SetNodeControllerTest {
@Test
public void testModifyAcceptsAndProvides() throws Exception {
String requestBody = getResourceFileContent("modify-data-node-1-views_and_protocols.xml");
String requestBody = getResourceFileContent("modify-data-node-having-views.xml");
// Create node
when(nodeDao.listNode(eq("/")))
......@@ -167,7 +171,6 @@ public class SetNodeControllerTest {
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().isForbidden());
}
/* Test case:
......@@ -177,7 +180,7 @@ public class SetNodeControllerTest {
@Test
public void testModifyNodeHavingAcceptsAndProvides() throws Exception {
String requestBody = getResourceFileContent("modify-data-node-having-views_and_protocols.xml");
String requestBody = getResourceFileContent("modify-data-node-having-views.xml");
// Create node
when(nodeDao.listNode(eq("/")))
......@@ -191,7 +194,6 @@ public class SetNodeControllerTest {
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().isForbidden());
}
/* Test case:
......@@ -200,13 +202,15 @@ public class SetNodeControllerTest {
*/
@Test
public void testModifyNodeLeavingAcceptsAndProvides() throws Exception {
String requestBody = getResourceFileContent("modify-data-node_description-leaving-views_and_protocols.xml");
String requestBody = getResourceFileContent("modify-data-node-description-leaving-views.xml");
Node node = getWritableDataNodeWithAcceptsAndProvides("/mydata1");
// Create node
when(nodeDao.listNode(eq("/")))
.thenReturn(Optional.of(getContainerParentNode("/")));
when(nodeDao.listNode(eq("/mydata1"))).thenReturn(Optional.of(getWritableDataNodeWithAcceptsAndProvides("/mydata1")));
when(nodeDao.setNode(any())).thenReturn(Optional.of(getWritableDataNodeWithAcceptsAndProvides("/mydata1")));
when(nodeDao.listNode(eq("/mydata1"))).thenReturn(Optional.of(node));
when(nodeDao.setNode(any())).thenReturn(node);
mockMvc.perform(post("/nodes/mydata1")
.header("Authorization", "Bearer user2_token")
......@@ -215,7 +219,6 @@ public class SetNodeControllerTest {
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().isOk());
}
}
......@@ -111,6 +111,31 @@ public class NodeDAOTest {
assertTrue(exception);
}
@Test
public void testSetNode() {
Property publicReadProperty = new Property();
publicReadProperty.setUri(NodeProperties.PUBLIC_READ_URI);
publicReadProperty.setValue(String.valueOf(false));
Node node = new DataNode();
node.setUri("vos://example.com!vospace/mydata3");
node.getProperties().add(publicReadProperty);
dao.createNode(node);
node = dao.listNode("/mydata3").get();
assertEquals("false", NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI));
node.getProperties().clear();
publicReadProperty.setValue(String.valueOf(true));
node.getProperties().add(publicReadProperty);
dao.setNode(node);
node = dao.listNode("/mydata3").get();
assertEquals("true", NodeProperties.getNodePropertyByURI(node, NodeProperties.PUBLIC_READ_URI));
}
private String getProperty(Node node, String uri) {
for (Property property : node.getProperties()) {
if (uri.equals(property.getUri())) {
......
......@@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views or protocols</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views</vos:property>
</vos:properties>
<vos:accepts>
<vos:view uri="ivo://ivoa.net/vospace/core#anyview"/>
......
......@@ -2,7 +2,7 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:LinkNode" uri="vos://example.com!vospace/mydata1">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views or protocols</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, no views</vos:property>
</vos:properties>
<vos:accepts/>
<vos:provides/>
......
......@@ -2,17 +2,15 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views and protocols</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupread">group1 group2</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupwrite">group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#publicread">true</vos:property>
</vos:properties>
<vos:accepts>
<vos:property uri="ivo://ivoa.net/vospace/core#httpput"/>
<vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/>
</vos:accepts>
<vos:provides>
<vos:property uri="ivo://ivoa.net/vospace/core#httpput"/>
<vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/>
</vos:provides>
<vos:capabilities/>
......
......@@ -2,17 +2,15 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:vos="http://www.ivoa.net/xml/VOSpace/v2.0" xsi:type="vos:DataNode" uri="vos://example.com!vospace/mydata1">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views and protocols</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#description">Data node 1, views</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#groupread">group1</vos:property>
<vos:property uri="ivo://ivoa.net/vospace/core#publicread">true</vos:property>
</vos:properties>
<vos:accepts>
<vos:property uri="ivo://ivoa.net/vospace/core#httpget"/>
<vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/>
<vos:view uri="ivo://ivoa.net/vospace/core#anyview"/>
</vos:accepts>
<vos:provides>
<vos:property uri="ivo://ivoa.net/vospace/core#httpget"/>
<vos:view uri="ivo://ivoa.net/vospace/core#defaultview"/>
<vos:view uri="ivo://ivoa.net/vospace/core#anyview"/>
</vos:provides>
<vos:capabilities/>
</vos:node>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment