From a1a843a30e001af5a4440b747bf00c32235c355b Mon Sep 17 00:00:00 2001 From: Sara Bertocco <sara.bertocco@inaf.it> Date: Tue, 2 Feb 2021 18:20:08 +0100 Subject: [PATCH] Finalized task #3549 - Implement deleteNode endpoint --- .../oats/vospace/DeleteNodeController.java | 65 ++--- .../oats/vospace/persistence/NodeDAO.java | 15 +- .../vospace/DeleteNodeControllerTest.java | 226 +++++++++--------- 3 files changed, 128 insertions(+), 178 deletions(-) diff --git a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java index 54b5661..eae1a86 100644 --- a/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java +++ b/src/main/java/it/inaf/oats/vospace/DeleteNodeController.java @@ -20,8 +20,10 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,10 +34,9 @@ public class DeleteNodeController extends BaseNodeController { @Autowired private NodeDAO nodeDAO; - @GetMapping(value = {"/nodes", "/nodes/**"}, - consumes = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}, + @DeleteMapping(value = {"/nodes", "/nodes/**"}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) - public ResponseEntity<Node> deleteNode(HttpServletRequest request, User principal) { + public ResponseEntity<String> deleteNode(HttpServletRequest request, User principal) { String path = getPath(); @@ -60,7 +61,7 @@ public class DeleteNodeController extends BaseNodeController { } else { // Manage all precursors in full path - for (int i = 1; i < pathComponents.size(); i++) { + for (int i = 0; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDAO.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); @@ -71,54 +72,16 @@ public class DeleteNodeController extends BaseNodeController { } - // DUPLICATED code from CreateNodeController - BEGIN - List<String> nodeOwner - = NodeProperties.getNodePropertyByURI(toBeDeletedNode, NodeProperties.CREATOR_URI); - //= getNodePropertyByURI( - // toBeDeletedNode, "ivo://ivoa.net/vospace/core#creator"); - - - if (nodeOwner == null - || nodeOwner.isEmpty() - || !nodeOwner.get(0).equals(principal.getName())) { - // Node owner check has failed: let's check if user can write - // due to group privileges - - List<String> userGroups = principal.getGroups(); - - // If the user doesn't belong to any groups throw exception - if (userGroups == null || userGroups.isEmpty()) { - throw new PermissionDeniedException(path); - } - - List<String> groupWritePropValues - = NodeProperties.getNodePropertyByURI(toBeDeletedNode, - NodeProperties.CREATOR_URI); - - // If groupwrite property is absent in Parent Node throw exception - if (groupWritePropValues == null - || groupWritePropValues.isEmpty()) { - throw new PermissionDeniedException(path); - } - - List<String> nodeGroups - = NodeProperties.parsePropertyStringToList(groupWritePropValues.get(0)); - - if (nodeGroups.isEmpty() - || !nodeGroups.stream() - .anyMatch((i) -> userGroups.contains(i))) { - throw new PermissionDeniedException(path); - } - + if(!NodeUtils.checkIfWritable(toBeDeletedNode, principal.getName(), principal.getGroups())) { + throw new PermissionDeniedException(path); + } + + try { + nodeDAO.deleteNode(path); + return ResponseEntity.ok("Node deleted"); + } catch(Exception ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST); } - - // DUPLICATED code from CreateNodeController - END - - - return ResponseEntity.ok(toBeDeletedNode); - //return toBeDeletedNode; - //return ResponseEntity.ok(nodeDAO.deleteNode(path) - // .orElseThrow(() -> new InternalFaultException(path))); } diff --git a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java index 8ad7770..f9021ab 100644 --- a/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java +++ b/src/main/java/it/inaf/oats/vospace/persistence/NodeDAO.java @@ -23,10 +23,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; -/** - * - * @author bertocco - */ + @Repository public class NodeDAO { @@ -125,16 +122,6 @@ public class NodeDAO { return Optional.of(node); } - - public Optional<String> deleteNode(String path) { - - // To be filled - - // In case of failure return empty object to trigger HTTP 500 - // status code for InternalFault - return Optional.of("OK"); - - } 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 || '"; diff --git a/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java b/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java index e907320..f4c57e8 100644 --- a/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java +++ b/src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java @@ -1,11 +1,15 @@ package it.inaf.oats.vospace; import static it.inaf.oats.vospace.CreateNodeControllerTest.getResourceFileContent; +import it.inaf.oats.vospace.datamodel.NodeProperties; import it.inaf.oats.vospace.persistence.NodeDAO; +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 org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; @@ -17,6 +21,7 @@ import org.springframework.test.web.servlet.MockMvc; import org.junit.jupiter.api.Test; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; @@ -34,14 +39,10 @@ public class DeleteNodeControllerTest { @MockBean private NodeDAO nodeDao; - @SpyBean - @Autowired - private DeleteNodeController controller; - @Autowired private MockMvc mockMvc; - + @Test public void testDeleteRootNode() throws Exception { @@ -51,157 +52,156 @@ public class DeleteNodeControllerTest { .delete("/nodes") .header("Authorization", "Bearer user2_token")) .andExpect(status().isForbidden()); - /* - mockMvc.perform(delete("/members/1"). - .header("Authorization", "Bearer user2_token")) - .andExpect(status().isNotFound()); - mockMvc.perform(delete("/") - .header("Authorization", "Bearer user2_token")) - .andExpect(status().isNotFound()); - */ + } + - @Test - public void testNodeNotFound() throws Exception { + public void testDeleteFirstLevelNode() throws Exception { - when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getDataNode())); + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode())); mockMvc.perform(MockMvcRequestBuilders .delete("/nodes/mynode") .header("Authorization", "Bearer user2_token")) .andExpect(status().isOk()); - /* - mockMvc.perform(delete("/members/1"). - .header("Authorization", "Bearer user2_token")) - .andExpect(status().isNotFound()); - mockMvc.perform(delete("/") - .header("Authorization", "Bearer user2_token")) - .andExpect(status().isNotFound()); - */ + } - /* - @Test - public void testDeleteRoot1() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); + - - } - - @Test - public void testDeleteRoot2() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testDeleteMoreLevelNode() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode"))); + when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode"))); + when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/nodes/mynode/middlenode/myfile.txt") + .header("Authorization", "Bearer user2_token")) + .andExpect(status().isOk()); + } - + @Test - public void testDeleteExistingNodeInRoot() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testLeafNodeNotFound() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/nodes/mynode/notexisting") + .header("Authorization", "Bearer user2_token")) + .andExpect(status().isNotFound()); + } - + @Test - public void testDeleteExistingNodeLeaf() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testMiddleLevelNodeNotFound() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode"))); + when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/mynode/middlenode/myfile.txt") + .header("Authorization", "Bearer user2_token")) + .andExpect(status().isNotFound()); + } - + @Test - public void testDeleteExistingDataLinkInRoot() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testLinkNodeLeafDelete() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableLinkNode("/mynode"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/nodes/mynode") + .header("Authorization", "Bearer user2_token")) + .andExpect(status().isOk()); + } - + @Test - public void testDeleteExistingDataLinkLeaf() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testMiddleLevelLinkNodeDelete() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode"))); + when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableLinkNode("/mynode/middlenode"))); + when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/nodes/mynode/middlenode/myfile.txt") + .header("Authorization", "Bearer user2_token")) + .andExpect(status().isBadRequest()); + } @Test - public void testDeleteExistingNodeWithDataLinkInPath() throws Exception { - - mockMvc.perform(put("/nodes/mydata1") - .header("Authorization", "Bearer user2_token")) - //.content(requestBody) - //.contentType(MediaType.APPLICATION_JSON) - //.accept(MediaType.APPLICATION_XML)) - //.andDo(print()) - .andExpect(status().isOk()); - - + public void testDeleteMoreLevelNodeNotAllowed() throws Exception { + + when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getWritableDataNode("/mynode"))); + when(nodeDao.listNode(eq("/mynode/middlenode"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode"))); + when(nodeDao.listNode(eq("/mynode/middlenode/myfile.txt"))).thenReturn(Optional.of(getWritableDataNode("/mynode/middlenode/myfile.txt"))); + + mockMvc.perform(MockMvcRequestBuilders + .delete("/nodes/mynode/middlenode/myfile.txt")) + .andExpect(status().isForbidden()); + } -*/ - private Optional<Node> getRootNode() { ContainerNode root = new ContainerNode(); root.setUri(URI_PREFIX + "/"); - root.getNodes().add(getDataNode()); + root.getNodes().add(getWritableDataNode()); return Optional.of(root); } - private Node getDataNode() { + private Node getWritableDataNode() { DataNode node = new DataNode(); + List nodeProperties = node.getProperties(); + Property groupWriteProp = new Property(); + groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI); + groupWriteProp.setValue("group1"); + nodeProperties.add(groupWriteProp); node.setUri(URI_PREFIX + "/mynode"); return node; } + + private Node getWritableDataNode(String path) { + DataNode node = new DataNode(); + List nodeProperties = node.getProperties(); + Property groupWriteProp = new Property(); + groupWriteProp.setUri(NodeProperties.GROUP_WRITE_URI); + groupWriteProp.setValue("group1"); + nodeProperties.add(groupWriteProp); + node.setUri(URI_PREFIX + path); + return node; + } + + + + + + + + private LinkNode getWritableLinkNode(String path) { + LinkNode myNode = new LinkNode(); + // Set parent node address at / + myNode.setUri("vos://example.com!vospace" + path); + // Set groupwrite property + Property groups = new Property(); + groups.setUri("ivo://ivoa.net/vospace/core#groupwrite"); + groups.setValue("group1"); + myNode.setProperties(List.of(groups)); + return myNode; + } + } -- GitLab