Loading src/main/java/it/inaf/oats/vospace/DeleteNodeController.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; 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.InternalFaultException; import it.inaf.oats.vospace.exception.LinkFoundException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.ArrayList; import java.util.List; 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.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController 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}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<Node> deleteNode(HttpServletRequest request, User principal) { String path = getPath(); // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it Node toBeDeletedNode = nodeDAO.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. List<String> pathComponents = NodeUtils.subPathComponents(path); if (pathComponents.size() == 0) { // Manage root node throw new PermissionDeniedException("root"); } else { // Manage all precursors in full path for (int i = 1; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDAO.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted throw new LinkFoundException(tmpPath); } } // 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); } } // DUPLICATED code from CreateNodeController - END return ResponseEntity.ok(toBeDeletedNode); //return toBeDeletedNode; //return ResponseEntity.ok(nodeDAO.deleteNode(path) // .orElseThrow(() -> new InternalFaultException(path))); } } src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) // Status code 500 public class InternalFaultException extends VoSpaceException { public InternalFaultException(String msg) { super("InternalFaultException: " + msg); } } src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java 0 → 100644 +207 −0 Original line number Diff line number Diff line package it.inaf.oats.vospace; import static it.inaf.oats.vospace.CreateNodeControllerTest.getResourceFileContent; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.Optional; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; 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.MediaType; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @ContextConfiguration(classes = {TokenFilterConfig.class}) @TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") @AutoConfigureMockMvc public class DeleteNodeControllerTest { private static final String URI_PREFIX = "vos://example.com!vospace"; @MockBean private NodeDAO nodeDao; @SpyBean @Autowired private DeleteNodeController controller; @Autowired private MockMvc mockMvc; @Test public void testDeleteRootNode() throws Exception { when(nodeDao.listNode(eq("/"))).thenReturn(getRootNode()); mockMvc.perform(MockMvcRequestBuilders .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 { when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getDataNode())); 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()); } @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()); } @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()); } @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()); } @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()); } @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()); } */ private Optional<Node> getRootNode() { ContainerNode root = new ContainerNode(); root.setUri(URI_PREFIX + "/"); root.getNodes().add(getDataNode()); return Optional.of(root); } private Node getDataNode() { DataNode node = new DataNode(); node.setUri(URI_PREFIX + "/mynode"); return node; } } Loading
src/main/java/it/inaf/oats/vospace/DeleteNodeController.java 0 → 100644 +130 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace; import it.inaf.ia2.aa.data.User; 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.InternalFaultException; import it.inaf.oats.vospace.exception.LinkFoundException; import it.inaf.oats.vospace.exception.NodeNotFoundException; import it.inaf.oats.vospace.exception.PermissionDeniedException; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.ArrayList; import java.util.List; 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.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController 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}, produces = {MediaType.APPLICATION_XML_VALUE, MediaType.TEXT_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}) public ResponseEntity<Node> deleteNode(HttpServletRequest request, User principal) { String path = getPath(); // Check if the node is present, // if the node does not exist the service SHALL throw a HTTP 404 status code // including a NodeNotFound fault in the entity-body // If note present, got it Node toBeDeletedNode = nodeDAO.listNode(path) .orElseThrow(() -> new NodeNotFoundException(path)); // If a parent node in the URI path is a LinkNode, the service SHALL throw // a HTTP 400 status code including a LinkFound fault in the entity-body. // For example, given the URI path /a/b/c, the service must throw a HTTP 400 // status code including a LinkFound fault in the entity-body if either /a // or /a/b are LinkNodes. List<String> pathComponents = NodeUtils.subPathComponents(path); if (pathComponents.size() == 0) { // Manage root node throw new PermissionDeniedException("root"); } else { // Manage all precursors in full path for (int i = 1; i < pathComponents.size(); i++) { String tmpPath = pathComponents.get(i); Node mynode = nodeDAO.listNode(tmpPath) .orElseThrow(() -> new NodeNotFoundException(tmpPath)); if (mynode.getType().equals("vos:LinkNode") && i < pathComponents.size()-1) // a LinkNode leaf can be deleted throw new LinkFoundException(tmpPath); } } // 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); } } // DUPLICATED code from CreateNodeController - END return ResponseEntity.ok(toBeDeletedNode); //return toBeDeletedNode; //return ResponseEntity.ok(nodeDAO.deleteNode(path) // .orElseThrow(() -> new InternalFaultException(path))); } }
src/main/java/it/inaf/oats/vospace/exception/InternalFaultException.java 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package it.inaf.oats.vospace.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) // Status code 500 public class InternalFaultException extends VoSpaceException { public InternalFaultException(String msg) { super("InternalFaultException: " + msg); } }
src/test/java/it/inaf/oats/vospace/DeleteNodeControllerTest.java 0 → 100644 +207 −0 Original line number Diff line number Diff line package it.inaf.oats.vospace; import static it.inaf.oats.vospace.CreateNodeControllerTest.getResourceFileContent; import it.inaf.oats.vospace.persistence.NodeDAO; import java.util.Optional; import net.ivoa.xml.vospace.v2.ContainerNode; import net.ivoa.xml.vospace.v2.DataNode; import net.ivoa.xml.vospace.v2.Node; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; 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.MediaType; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @SpringBootTest @ContextConfiguration(classes = {TokenFilterConfig.class}) @TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") @AutoConfigureMockMvc public class DeleteNodeControllerTest { private static final String URI_PREFIX = "vos://example.com!vospace"; @MockBean private NodeDAO nodeDao; @SpyBean @Autowired private DeleteNodeController controller; @Autowired private MockMvc mockMvc; @Test public void testDeleteRootNode() throws Exception { when(nodeDao.listNode(eq("/"))).thenReturn(getRootNode()); mockMvc.perform(MockMvcRequestBuilders .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 { when(nodeDao.listNode(eq("/mynode"))).thenReturn(Optional.of(getDataNode())); 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()); } @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()); } @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()); } @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()); } @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()); } @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()); } */ private Optional<Node> getRootNode() { ContainerNode root = new ContainerNode(); root.setUri(URI_PREFIX + "/"); root.getNodes().add(getDataNode()); return Optional.of(root); } private Node getDataNode() { DataNode node = new DataNode(); node.setUri(URI_PREFIX + "/mynode"); return node; } }