Commit f1bf44a8 authored by Nicola Fulvio Calabria's avatar Nicola Fulvio Calabria
Browse files

Added LinkNodes validation to CreateNodeController

parent f329a67f
Loading
Loading
Loading
Loading
+35 −0
Original line number Diff line number Diff line
@@ -6,6 +6,9 @@
package it.inaf.oats.vospace;

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 org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
@@ -14,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PutMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

@RestController
public class CreateNodeController extends BaseNodeController {
@@ -23,6 +27,9 @@ public class CreateNodeController extends BaseNodeController {
    @Autowired
    private CreateNodeService createNodeService;

    @Value("${vospace-authority}")
    private String authority;

    @PutMapping(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})
@@ -32,8 +39,36 @@ public class CreateNodeController extends BaseNodeController {

        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);

    }

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

    }

}
+0 −15
Original line number Diff line number Diff line
@@ -10,7 +10,6 @@ import it.inaf.oats.vospace.datamodel.NodeProperties;
import it.inaf.oats.vospace.datamodel.NodeUtils;
import it.inaf.oats.vospace.exception.ContainerNotFoundException;
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.PermissionDeniedException;
import it.inaf.oats.vospace.persistence.NodeDAO;
@@ -19,7 +18,6 @@ import net.ivoa.xml.vospace.v2.Property;
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.stereotype.Service;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@@ -30,25 +28,12 @@ public class CreateNodeService {
    @Autowired
    private NodeDAO nodeDao;

    @Value("${vospace-authority}")
    private String authority;

    private static final Logger LOG = LoggerFactory.getLogger(CreateNodeService.class);

    public Node createNode(Node node, String path, User principal) {
        
        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
        // This checks if the user is trying to insert the root node at "/" too
        if (nodeDao.listNode(path).isPresent()) {
+5 −1
Original line number Diff line number Diff line
@@ -51,6 +51,10 @@ public class URIUtils {
        String resultPath = null;

        try {
            
            if(nodeURI == null)
                throw new IllegalArgumentException("URI string is null");
            
            URI uri = new URI(nodeURI);

            // Check scheme
+140 −70
Original line number Diff line number Diff line
@@ -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.LinkNode;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -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
    public void testNodeAlreadyExisting() throws Exception {
        String requestBody = getResourceFileContent("create-unstructured-data-node.xml");
@@ -409,6 +467,18 @@ public class CreateNodeControllerTest {
                }), 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 {
        try (InputStream in = CreateNodeControllerTest.class.getClassLoader().getResourceAsStream(fileName)) {
            return new String(in.readAllBytes(), StandardCharsets.UTF_8);
+18 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ import java.util.Set;
import javax.sql.DataSource;
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.View;
import static org.junit.jupiter.api.Assertions.assertEquals;
import net.ivoa.xml.vospace.v2.Node;
@@ -66,6 +67,23 @@ public class NodeDAOTest {
        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
    public void testListNode() {
        ContainerNode root = (ContainerNode) dao.listNode("/").get();
Loading