Skip to content
Snippets Groups Projects
Commit f1bf44a8 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Added LinkNodes validation to CreateNodeController

parent f329a67f
No related branches found
No related tags found
No related merge requests found
...@@ -6,6 +6,9 @@ ...@@ -6,6 +6,9 @@
package it.inaf.oats.vospace; package it.inaf.oats.vospace;
import it.inaf.ia2.aa.data.User; import it.inaf.ia2.aa.data.User;
import it.inaf.oats.vospace.exception.InvalidArgumentException;
import it.inaf.oats.vospace.exception.InvalidURIException;
import net.ivoa.xml.vospace.v2.LinkNode;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
...@@ -14,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -14,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
@RestController @RestController
public class CreateNodeController extends BaseNodeController { public class CreateNodeController extends BaseNodeController {
...@@ -23,6 +27,9 @@ public class CreateNodeController extends BaseNodeController { ...@@ -23,6 +27,9 @@ public class CreateNodeController extends BaseNodeController {
@Autowired @Autowired
private CreateNodeService createNodeService; private CreateNodeService createNodeService;
@Value("${vospace-authority}")
private String authority;
@PutMapping(value = {"/nodes", "/nodes/**"}, @PutMapping(value = {"/nodes", "/nodes/**"},
consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, 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}) produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
...@@ -32,8 +39,36 @@ public class CreateNodeController extends BaseNodeController { ...@@ -32,8 +39,36 @@ public class CreateNodeController extends BaseNodeController {
LOG.debug("createNodeController called for node with URI {} and PATH {}", node.getUri(), path); LOG.debug("createNodeController called for node with URI {} and PATH {}", node.getUri(), path);
// Get Node path (and validates it too)
String decodedURIPathFromNode = URIUtils.returnVosPathFromNodeURI(node.getUri(), authority);
LOG.debug("createNodeController URI: {} decoded as {}", node.getUri(), decodedURIPathFromNode);
// Check if payload URI is consistent with http request
if (!decodedURIPathFromNode.equals(path)) {
throw new InvalidURIException(decodedURIPathFromNode, path);
}
// validate format of input node
this.validateInputNode(node);
return createNodeService.createNode(node, path, principal); return createNodeService.createNode(node, path, principal);
} }
private void validateInputNode(Node node) {
if (node instanceof LinkNode) {
LinkNode linkNode = (LinkNode) node;
String target = linkNode.getTarget();
// I validate it here to add context easily
if (target == null) {
throw new InvalidArgumentException("LinkNode in payload has no target element specified");
}
URIUtils.returnVosPathFromNodeURI(linkNode.getTarget(), authority);
}
}
} }
...@@ -10,7 +10,6 @@ import it.inaf.oats.vospace.datamodel.NodeProperties; ...@@ -10,7 +10,6 @@ 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.ContainerNotFoundException; import it.inaf.oats.vospace.exception.ContainerNotFoundException;
import it.inaf.oats.vospace.exception.DuplicateNodeException; import it.inaf.oats.vospace.exception.DuplicateNodeException;
import it.inaf.oats.vospace.exception.InvalidURIException;
import it.inaf.oats.vospace.exception.LinkFoundException; import it.inaf.oats.vospace.exception.LinkFoundException;
import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.exception.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO; import it.inaf.oats.vospace.persistence.NodeDAO;
...@@ -19,7 +18,6 @@ import net.ivoa.xml.vospace.v2.Property; ...@@ -19,7 +18,6 @@ import net.ivoa.xml.vospace.v2.Property;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.EnableTransactionManagement;
...@@ -30,25 +28,12 @@ public class CreateNodeService { ...@@ -30,25 +28,12 @@ public class CreateNodeService {
@Autowired @Autowired
private NodeDAO nodeDao; private NodeDAO nodeDao;
@Value("${vospace-authority}")
private String authority;
private static final Logger LOG = LoggerFactory.getLogger(CreateNodeService.class); private static final Logger LOG = LoggerFactory.getLogger(CreateNodeService.class);
public Node createNode(Node node, String path, User principal) { public Node createNode(Node node, String path, User principal) {
LOG.debug("createNodeService called for node with URI {} and PATH {}", node.getUri(), path); LOG.debug("createNodeService called for node with URI {} and PATH {}", node.getUri(), path);
// Get Node path (and validates it too)
String decodedURIPathFromNode = URIUtils.returnVosPathFromNodeURI(node.getUri(), authority);
LOG.debug("createNodeService URI: {} decoded as {}", node.getUri(), decodedURIPathFromNode);
// Check if payload URI is consistent with http request
if (!decodedURIPathFromNode.equals(path)) {
throw new InvalidURIException(decodedURIPathFromNode, path);
}
// Check if another node is already present at specified path // Check if another node is already present at specified path
// This checks if the user is trying to insert the root node at "/" too // This checks if the user is trying to insert the root node at "/" too
if (nodeDao.listNode(path).isPresent()) { if (nodeDao.listNode(path).isPresent()) {
......
...@@ -51,6 +51,10 @@ public class URIUtils { ...@@ -51,6 +51,10 @@ public class URIUtils {
String resultPath = null; String resultPath = null;
try { try {
if(nodeURI == null)
throw new IllegalArgumentException("URI string is null");
URI uri = new URI(nodeURI); URI uri = new URI(nodeURI);
// Check scheme // Check scheme
......
...@@ -30,6 +30,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean; ...@@ -30,6 +30,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.LinkNode; import net.ivoa.xml.vospace.v2.LinkNode;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
...@@ -185,6 +186,63 @@ public class CreateNodeControllerTest { ...@@ -185,6 +186,63 @@ public class CreateNodeControllerTest {
} }
@Test
public void testCreateInternalLinkNode() throws Exception {
String requestBody = getResourceFileContent("create-internal-link-node.xml");
when(nodeDao.listNode(eq("/")))
.thenReturn(Optional.of(getContainerParentNode("/")));
mockMvc.perform(put("/nodes/myInternalLink")
.header("Authorization", "Bearer user2_token")
.content(requestBody)
.contentType(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().isOk());
verifyLinkArguments("vos://example.com!vospace/myDummyDataNode1");
}
@Test
public void testCreateExternalLinkNode() throws Exception {
String requestBody = getResourceFileContent("create-external-link-node.xml");
when(nodeDao.listNode(eq("/")))
.thenReturn(Optional.of(getContainerParentNode("/")));
mockMvc.perform(put("/nodes/myExternalLink")
.header("Authorization", "Bearer user2_token")
.content(requestBody)
.contentType(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().is4xxClientError());
verifyLinkArguments("vos://external.com!vospace/myDummyDataNode1");
}
@Test
public void testCreateLinkNodeNoTarget() throws Exception {
String requestBody = getResourceFileContent("create-link-node-notarget.xml");
when(nodeDao.listNode(eq("/")))
.thenReturn(Optional.of(getContainerParentNode("/")));
mockMvc.perform(put("/nodes/myNoTargetLink")
.header("Authorization", "Bearer user2_token")
.content(requestBody)
.contentType(MediaType.APPLICATION_XML)
.accept(MediaType.APPLICATION_XML))
.andDo(print())
.andExpect(status().is4xxClientError());
verifyLinkArguments(null);
}
@Test @Test
public void testNodeAlreadyExisting() throws Exception { public void testNodeAlreadyExisting() throws Exception {
String requestBody = getResourceFileContent("create-unstructured-data-node.xml"); String requestBody = getResourceFileContent("create-unstructured-data-node.xml");
...@@ -409,6 +467,18 @@ public class CreateNodeControllerTest { ...@@ -409,6 +467,18 @@ public class CreateNodeControllerTest {
}), any()); }), any());
} }
private void verifyLinkArguments(String target) {
verify(controller).createNode(
argThat(node -> {
LinkNode linkNode = (LinkNode) node;
Property property = linkNode.getProperties().get(0);
return "vos:LinkNode".equals(linkNode.getType())
&& "test value".equals(property.getValue())
&& "ivo://ivoa.net/vospace/core#description".equals(property.getUri())
&& Objects.equals(target, linkNode.getTarget());
}), any());
}
protected static String getResourceFileContent(String fileName) throws Exception { protected static String getResourceFileContent(String fileName) throws Exception {
try (InputStream in = CreateNodeControllerTest.class.getClassLoader().getResourceAsStream(fileName)) { try (InputStream in = CreateNodeControllerTest.class.getClassLoader().getResourceAsStream(fileName)) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8); return new String(in.readAllBytes(), StandardCharsets.UTF_8);
......
...@@ -17,6 +17,7 @@ import java.util.Set; ...@@ -17,6 +17,7 @@ import java.util.Set;
import javax.sql.DataSource; import javax.sql.DataSource;
import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.ContainerNode;
import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.DataNode;
import net.ivoa.xml.vospace.v2.LinkNode;
import net.ivoa.xml.vospace.v2.View; import net.ivoa.xml.vospace.v2.View;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import net.ivoa.xml.vospace.v2.Node; import net.ivoa.xml.vospace.v2.Node;
...@@ -66,6 +67,23 @@ public class NodeDAOTest { ...@@ -66,6 +67,23 @@ public class NodeDAOTest {
assertEquals(retrievedNode.getProvides().get(0).getUri(), dataNode.getProvides().get(0).getUri()); assertEquals(retrievedNode.getProvides().get(0).getUri(), dataNode.getProvides().get(0).getUri());
} }
@Test
public void testCreateLinkNode() {
LinkNode linkNode = new LinkNode();
String target = "vos://example.com!vospace/myData1";
linkNode.setUri("vos://example.com!vospace/myLink1");
linkNode.setTarget(target);
dao.createNode(linkNode);
Node retrievedNode = dao.listNode("/myLink1").get();
assertTrue(retrievedNode instanceof LinkNode);
assertEquals(target, ((LinkNode) retrievedNode).getTarget());
}
@Test @Test
public void testListNode() { public void testListNode() {
ContainerNode root = (ContainerNode) dao.listNode("/").get(); ContainerNode root = (ContainerNode) dao.listNode("/").get();
......
<vos:node xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
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/myExternalLink">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property>
</vos:properties>
<vos:target>vos://external.com!vospace/myDummyDataNode1</vos:target>
<vos:accepts/>
<vos:provides/>
<vos:capabilities/>
</vos:node>
\ No newline at end of file
<vos:node xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
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/myInternalLink">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property>
</vos:properties>
<vos:target>vos://example.com!vospace/myDummyDataNode1</vos:target>
<vos:accepts/>
<vos:provides/>
<vos:capabilities/>
</vos:node>
\ No newline at end of file
<vos:node xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
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/myNoTargetLink">
<vos:properties>
<vos:property uri="ivo://ivoa.net/vospace/core#description">test value</vos:property>
</vos:properties>
<vos:accepts/>
<vos:provides/>
<vos:capabilities/>
</vos:node>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment